Skip to content

Commit 02753cd

Browse files
committed
Accessibility improvements: HTTP utilities, element tracking, and annotated screenshots
Deduplicate HTTP infrastructure into shared HTTPUtilities. Add element recency tracking with color-coded overlays. Fix double-click registration, improve screen coordinate mapping, add z-order filtering for overlays, and render annotated screenshots with element labels. Add middle-click support and placeholder Dock stack icons.
1 parent 1f00924 commit 02753cd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+6060
-780
lines changed

.github/workflows/deploy-website.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ jobs:
2929
- uses: actions/setup-node@v4
3030
with:
3131
node-version: 22
32-
cache: npm
33-
cache-dependency-path: Website/package-lock.json
3432

35-
- run: npm ci
33+
- run: npm install
3634
- run: npm run build
3735

3836
- uses: actions/upload-pages-artifact@v3

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
build/
55
*.local.json
66
*.xcodeproj/
7+
*.profraw
78
.claude/
89
.vscode/

Makefile

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
-include .env
66
export
77

8-
CODESIGN_ID ?= -
8+
CODESIGN_ID ?= Apple Development
9+
GHOSTTOOLS_SIGN_ID ?= $(CODESIGN_ID)
910

1011
# Xcode project settings (generated via xcodegen)
1112
XCODE_PROJECT = macOS/GhostVM.xcodeproj
@@ -58,12 +59,17 @@ app: $(XCODE_PROJECT) dmg ghostvm-icon
5859
codesign --entitlements macOS/GhostVM/entitlements.plist --force -s "$(CODESIGN_ID)" "$(BUILD_DIR)/Build/Products/$(XCODE_CONFIG)/$(APP_NAME).app"
5960
@echo "App built at: $(BUILD_DIR)/Build/Products/$(XCODE_CONFIG)/$(APP_NAME).app"
6061

61-
# Build the SwiftUI app in Debug configuration
62+
# Build the SwiftUI app in Debug configuration (ad-hoc signing)
6263
debug: $(XCODE_PROJECT) dmg ghostvm-icon
6364
xcodebuild -project $(XCODE_PROJECT) \
6465
-scheme $(APP_NAME) \
6566
-configuration Debug \
6667
-derivedDataPath $(BUILD_DIR) \
68+
CODE_SIGN_STYLE=Manual \
69+
CODE_SIGN_IDENTITY=- \
70+
DEVELOPMENT_TEAM= \
71+
CODE_SIGNING_ALLOWED=NO \
72+
CODE_SIGNING_REQUIRED=NO \
6773
build
6874
@# Copy icons into app bundle Resources
6975
@mkdir -p "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app/Contents/Resources"
@@ -72,8 +78,9 @@ debug: $(XCODE_PROJECT) dmg ghostvm-icon
7278
@cp build/GhostVMIcon.icns "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app/Contents/Resources/GhostVMIcon.icns"
7379
@# Copy GhostTools.dmg into app bundle Resources
7480
@cp "$(GHOSTTOOLS_DMG)" "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app/Contents/Resources/"
75-
@# Re-sign after adding resources
76-
codesign --entitlements macOS/GhostVM/entitlements.plist --force -s "$(CODESIGN_ID)" "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app"
81+
@# Sign helper and app ad-hoc with entitlements
82+
codesign --entitlements macOS/GhostVMHelper/entitlements.plist --force --deep -s "-" "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app/Contents/PlugIns/Helpers/GhostVMHelper.app"
83+
codesign --entitlements macOS/GhostVM/entitlements.plist --force -s "-" "$(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app"
7784
@echo "Debug app built at: $(BUILD_DIR)/Build/Products/Debug/$(APP_NAME).app"
7885

7986
# Build and run the app (attached to terminal)
@@ -96,6 +103,12 @@ GHOSTTOOLS_DMG = $(BUILD_DIR)/GhostTools.dmg
96103
# Build GhostTools guest agent
97104
tools:
98105
@echo "Building GhostTools..."
106+
@echo "Injecting build timestamp..."
107+
@TIMESTAMP=$$(date +%s); \
108+
plutil -replace CFBundleVersion -string "$$TIMESTAMP" \
109+
"$(GHOSTTOOLS_DIR)/Sources/GhostTools/Resources/Info.plist"
110+
@# Force relink so the embedded __TEXT/__info_plist picks up the new timestamp
111+
@rm -f "$(GHOSTTOOLS_DIR)/.build/release/GhostTools"
99112
cd $(GHOSTTOOLS_DIR) && swift build -c release
100113
@echo "GhostTools built at: $(GHOSTTOOLS_DIR)/.build/release/GhostTools"
101114

@@ -176,8 +189,8 @@ dmg: tools ghosttools-icon
176189
@cp "$(GHOSTTOOLS_ICON_ICNS)" "$(GHOSTTOOLS_BUILD_DIR)/dmg-stage/GhostTools.app/Contents/Resources/"
177190
@# Copy README to DMG root
178191
@cp "$(GHOSTTOOLS_DIR)/README.txt" "$(GHOSTTOOLS_BUILD_DIR)/dmg-stage/"
179-
@# Sign with developer identity (preserves TCC permissions across rebuilds)
180-
codesign --force --deep -s "Apple Development" "$(GHOSTTOOLS_BUILD_DIR)/dmg-stage/GhostTools.app"
192+
@# Sign GhostTools (Apple Development preserves TCC; ad-hoc for debug)
193+
codesign --force --deep --entitlements "$(GHOSTTOOLS_DIR)/Sources/GhostTools/Resources/entitlements.plist" -s "$(GHOSTTOOLS_SIGN_ID)" "$(GHOSTTOOLS_BUILD_DIR)/dmg-stage/GhostTools.app"
181194
@# Create the DMG
182195
@rm -f "$(GHOSTTOOLS_DMG)"
183196
hdiutil makehybrid -o "$(GHOSTTOOLS_DMG)" \
@@ -264,7 +277,7 @@ dist: app cli
264277
@cp "$(GHOSTTOOLS_DIR)/Sources/GhostTools/Resources/Info.plist" "$(DIST_DIR)/ghosttools-stage/GhostTools.app/Contents/"
265278
@cp "$(GHOSTTOOLS_ICON_ICNS)" "$(DIST_DIR)/ghosttools-stage/GhostTools.app/Contents/Resources/"
266279
@cp "$(GHOSTTOOLS_DIR)/README.txt" "$(DIST_DIR)/ghosttools-stage/"
267-
codesign --force --options runtime --timestamp --deep -s "$(DIST_CODESIGN_ID)" "$(DIST_DIR)/ghosttools-stage/GhostTools.app"
280+
codesign --force --options runtime --timestamp --deep --entitlements "$(GHOSTTOOLS_DIR)/Sources/GhostTools/Resources/entitlements.plist" -s "$(DIST_CODESIGN_ID)" "$(DIST_DIR)/ghosttools-stage/GhostTools.app"
268281
@rm -f "$(DIST_DIR)/dmg-stage/$(APP_NAME).app/Contents/Resources/GhostTools.dmg"
269282
hdiutil makehybrid -o "$(DIST_DIR)/dmg-stage/$(APP_NAME).app/Contents/Resources/GhostTools.dmg" \
270283
-hfs -hfs-volume-name "GhostTools" \
@@ -345,7 +358,7 @@ help:
345358
@echo " make clean - Remove build artifacts and generated project"
346359
@echo ""
347360
@echo "Variables:"
348-
@echo " CODESIGN_ID - Code signing identity (default: - for ad-hoc)"
361+
@echo " CODESIGN_ID - Code signing identity (default: Apple Development)"
349362
@echo " XCODE_CONFIG - Xcode build configuration (default: Release)"
350363
@echo " VERSION - Version for DMG (default: git describe)"
351364
@echo ""

README.md

Lines changed: 55 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,84 @@
11
# GhostVM
22

3-
GhostVM is a native macOS app and CLI tool for provisioning and managing macOS virtual machines on Apple Silicon using Apple's `Virtualization.framework`. VMs are stored as self-contained `.GhostVM` bundles.
3+
GhostVM is a native macOS app for creating and managing macOS virtual machines on Apple Silicon using Apple's `Virtualization.framework`. VMs are stored as self-contained `.GhostVM` bundles.
4+
5+
## Features
6+
7+
- Create macOS VMs with customizable CPU, memory, and disk
8+
- Download and manage restore images (IPSW) with built-in feed
9+
- Start, stop, suspend, and resume VMs
10+
- Create, revert, and delete snapshots
11+
- Shared folders between host and guest
12+
- Clipboard sync, file transfer, and URL forwarding via the GhostTools guest agent
13+
- Port forwarding through vsock tunnels
14+
- Per-VM Dock icons with a helper-per-VM architecture
15+
- `vmctl` CLI for headless VM management and scripting
16+
- `vmctl remote` for programmatic guest control (accessibility, pointer, keyboard)
417

518
## Requirements
619

720
- macOS 15+ on Apple Silicon (arm64)
8-
- Xcode 15+ and XcodeGen for building (`brew install xcodegen`)
9-
- `com.apple.security.virtualization` entitlement (included in builds)
21+
- Xcode 15+ and XcodeGen (`brew install xcodegen`)
1022

1123
## Building
1224

1325
```bash
1426
make # Show available targets
27+
make app # Build GhostVM.app
1528
make cli # Build vmctl CLI
16-
make app # Build GhostVM.app via xcodebuild
29+
make test # Run unit tests
1730
make run # Build and run attached to terminal
1831
make launch # Build and launch detached
19-
make test # Run unit tests
20-
make dist # Create distribution DMG with app + vmctl
21-
make clean # Remove build artifacts and generated project
32+
make dist # Create distribution DMG
33+
make clean # Remove build artifacts
2234
```
2335

24-
Builds are ad-hoc signed by default. Override `CODESIGN_ID` for a different identity.
36+
## Project Structure
2537

26-
## GUI App
27-
28-
The GhostVM.app provides a SwiftUI interface for managing VMs:
29-
30-
- Create macOS VMs with customizable CPU, memory, and disk
31-
- Manage restore images (IPSW) with built-in download support
32-
- Start, stop, suspend, and resume VMs
33-
- Create, revert, and delete snapshots
34-
- Configure shared folders (read-only or writable)
35-
- VM menu with keyboard shortcuts for Start, Suspend, Shut Down, and Terminate
36-
37-
## CLI Usage
38-
39-
```bash
40-
./vmctl --help
4138
```
42-
43-
**Commands:**
44-
45-
- `init <bundle-path>` – Create a new macOS VM bundle
46-
- Options: `--cpus N`, `--memory GiB`, `--disk GiB`, `--restore-image PATH`, `--shared-folder PATH`, `--writable`
47-
- `install <bundle-path>` – Install macOS from restore image
48-
- `start <bundle-path>` – Launch the VM (GUI by default)
49-
- Options: `--headless`, `--shared-folder PATH`, `--writable|--read-only`
50-
- `stop <bundle-path>` – Graceful shutdown
51-
- `status <bundle-path>` – Report running state and config
52-
- `resume <bundle-path>` – Resume from suspended state
53-
- Options: `--headless`, `--shared-folder PATH`, `--writable|--read-only`
54-
- `discard-suspend <bundle-path>` – Discard suspended state
55-
- `snapshot <bundle-path> list` – List snapshots
56-
- `snapshot <bundle-path> create|revert|delete <name>` – Manage snapshots
57-
58-
Restore images are auto-discovered from `~/Downloads/*.ipsw` and `/Applications/Install macOS*.app`.
59-
60-
## Examples
61-
62-
```bash
63-
make cli
64-
./vmctl init ~/VMs/sandbox.GhostVM --cpus 6 --memory 16 --disk 128
65-
./vmctl install ~/VMs/sandbox.GhostVM
66-
./vmctl start ~/VMs/sandbox.GhostVM
67-
./vmctl stop ~/VMs/sandbox.GhostVM
39+
.
40+
├── Makefile # Build orchestration
41+
├── macOS/
42+
│ ├── project.yml # XcodeGen project definition
43+
│ ├── GhostVM/ # Main SwiftUI app (VM manager/launcher)
44+
│ ├── GhostVMHelper/ # Per-VM helper process (display, toolbar, services)
45+
│ ├── GhostVMKit/ # Shared framework (types, VM controller, utilities)
46+
│ ├── GhostTools/ # Guest agent (runs inside VM, vsock communication)
47+
│ ├── GhostVMTests/ # Unit tests
48+
│ └── GhostVMUITests/ # UI tests
49+
└── Website/ # GitHub Pages site
6850
```
6951

70-
**Suspend and Resume:**
52+
## CLI Usage
7153

7254
```bash
73-
# Use VM > Suspend menu (Cmd+Option+S) in the app, then:
74-
./vmctl resume ~/VMs/sandbox.GhostVM
55+
vmctl init ~/VMs/sandbox.GhostVM --cpus 6 --memory 16 --disk 128
56+
vmctl install ~/VMs/sandbox.GhostVM
57+
vmctl start ~/VMs/sandbox.GhostVM
58+
vmctl stop ~/VMs/sandbox.GhostVM
7559
```
7660

77-
**Snapshots:**
61+
**Commands:**
7862

79-
```bash
80-
./vmctl snapshot ~/VMs/sandbox.GhostVM list
81-
./vmctl snapshot ~/VMs/sandbox.GhostVM create clean
82-
./vmctl snapshot ~/VMs/sandbox.GhostVM revert clean
83-
./vmctl snapshot ~/VMs/sandbox.GhostVM delete clean
84-
```
63+
| Command | Description |
64+
|---------|-------------|
65+
| `init <bundle>` | Create a new VM bundle (`--cpus`, `--memory`, `--disk`, `--restore-image`) |
66+
| `install <bundle>` | Install macOS from a restore image |
67+
| `start <bundle>` | Launch the VM (`--headless`, `--shared-folder`) |
68+
| `stop <bundle>` | Graceful shutdown |
69+
| `suspend` / `resume` | Suspend and resume VM state |
70+
| `status <bundle>` | Report running state and config |
71+
| `snapshot <bundle> list\|create\|revert\|delete` | Manage snapshots |
72+
| `list` | List all VMs and their status |
73+
| `remote <vm> screenshot` | Capture guest screenshot |
74+
| `remote <vm> elements` | Inspect accessibility tree |
75+
| `remote <vm> leftclick --label <text>` | Click a UI element |
76+
| `remote <vm> type --text <text>` | Type text into the guest |
8577

8678
## Notes
8779

88-
- Disk images are raw sparse files (default 64 GiB)
89-
- NAT networking via `VZNATNetworkDeviceAttachment`
90-
- Shared folders use `VZVirtioFileSystemDeviceConfiguration` (read-only by default)
91-
- Enable Remote Login (SSH) in guest for headless use
80+
- VMs use `VZNATNetworkDeviceAttachment` for networking
81+
- Shared folders use `VZVirtioFileSystemDeviceConfiguration`
82+
- GhostTools auto-installs to `/Applications` in the guest and auto-updates
83+
- Enable Remote Login (SSH) in the guest for headless use
9284
- Apple's EULA requires macOS guests to run on Apple-branded hardware

Website/image-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function ghostVMLoader({
77
}) {
88
// In static export with basePath, unoptimized images don't get the prefix.
99
// This loader ensures all images are served from the correct subdirectory.
10-
const basePath = "/GhostVM";
10+
const basePath = "/ghostvm";
1111
if (src.startsWith("/") && !src.startsWith(basePath)) {
1212
return `${basePath}${src}`;
1313
}

Website/next.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
44
output: "export",
5-
basePath: "/GhostVM",
5+
basePath: "/ghostvm",
66
images: {
77
loader: "custom",
88
loaderFile: "./image-loader.ts",

0 commit comments

Comments
 (0)