Skip to content

Commit 4f813ce

Browse files
committed
Improve inspector source switching and simulator controls
1 parent a47ce0d commit 4f813ce

2,296 files changed

Lines changed: 21217 additions & 373 deletions

File tree

Some content is hidden

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

.github/workflows/release.yml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
release:
8+
types:
9+
- published
10+
11+
jobs:
12+
release:
13+
runs-on: macos-latest
14+
permissions:
15+
contents: write
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
cache: npm
24+
cache-dependency-path: |
25+
package-lock.json
26+
client/package-lock.json
27+
28+
- uses: dtolnay/rust-toolchain@stable
29+
with:
30+
components: rustfmt, clippy
31+
32+
- name: Resolve release metadata
33+
id: meta
34+
run: |
35+
if [ "${{ github.event_name }}" = "release" ]; then
36+
TAG_NAME="${{ github.event.release.tag_name }}"
37+
else
38+
TAG_NAME="${GITHUB_REF_NAME}"
39+
fi
40+
41+
VERSION="${TAG_NAME#v}"
42+
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
43+
44+
if [ "${VERSION}" != "${PACKAGE_VERSION}" ]; then
45+
echo "Tag ${TAG_NAME} does not match package version ${PACKAGE_VERSION}" >&2
46+
exit 1
47+
fi
48+
49+
echo "tag=${TAG_NAME}" >> "${GITHUB_OUTPUT}"
50+
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
51+
52+
- name: Install root dependencies
53+
run: npm ci
54+
55+
- name: Install client dependencies
56+
run: npm ci --prefix client
57+
58+
- name: Lint, build, and test
59+
run: npm run ci
60+
61+
- name: Prepare release assets
62+
env:
63+
VERSION: ${{ steps.meta.outputs.version }}
64+
run: |
65+
ARTIFACTS_DIR="${RUNNER_TEMP}/release-assets"
66+
BUNDLE_DIR="${RUNNER_TEMP}/xcode-canvas-web-${VERSION}-macos"
67+
68+
rm -rf "${ARTIFACTS_DIR}" "${BUNDLE_DIR}"
69+
mkdir -p "${ARTIFACTS_DIR}" "${BUNDLE_DIR}"
70+
71+
cp build/xcode-canvas-web "${BUNDLE_DIR}/"
72+
cp build/xcode-canvas-web-bin "${BUNDLE_DIR}/"
73+
cp README.md LICENSE "${BUNDLE_DIR}/"
74+
75+
tar -czf "${ARTIFACTS_DIR}/xcode-canvas-web-${VERSION}-macos.tar.gz" \
76+
-C "${RUNNER_TEMP}" \
77+
"xcode-canvas-web-${VERSION}-macos"
78+
79+
ditto -c -k --keepParent \
80+
"${BUNDLE_DIR}" \
81+
"${ARTIFACTS_DIR}/xcode-canvas-web-${VERSION}-macos.zip"
82+
83+
npm pack --pack-destination "${ARTIFACTS_DIR}"
84+
85+
- name: Publish package to npm
86+
env:
87+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
88+
VERSION: ${{ steps.meta.outputs.version }}
89+
run: |
90+
if npm view "xcode-canvas-web@${VERSION}" version >/dev/null 2>&1; then
91+
echo "xcode-canvas-web@${VERSION} is already published on npm."
92+
exit 0
93+
fi
94+
95+
if [ -z "${NODE_AUTH_TOKEN}" ]; then
96+
echo "NPM_TOKEN repository secret is required to publish xcode-canvas-web@${VERSION}." >&2
97+
exit 1
98+
fi
99+
100+
npm publish
101+
102+
- name: Upload GitHub release assets
103+
env:
104+
GH_TOKEN: ${{ github.token }}
105+
TAG_NAME: ${{ steps.meta.outputs.tag }}
106+
run: |
107+
ARTIFACTS_DIR="${RUNNER_TEMP}/release-assets"
108+
109+
if gh release view "${TAG_NAME}" >/dev/null 2>&1; then
110+
gh release upload "${TAG_NAME}" "${ARTIFACTS_DIR}"/* --clobber
111+
else
112+
gh release create "${TAG_NAME}" "${ARTIFACTS_DIR}"/* \
113+
--generate-notes \
114+
--title "${TAG_NAME}"
115+
fi

AGENTS.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The native side should own anything that depends on macOS frameworks, `xcrun sim
2626
- `cli/DFPrivateSimulatorDisplayBridge.*`
2727
Owns headless private display frames plus HID-based touch and keyboard injection.
2828
- `cli/XCWPrivateSimulatorSession.*`
29-
Owns one private display bridge per booted simulator plus hardware H.264 encode.
29+
Owns one private display bridge per booted simulator plus selectable HEVC/H.264 encode.
3030
- `cli/native/XCWNativeBridge.*`
3131
Narrow C ABI for simulator control, chrome rendering, and native frame callbacks into Rust.
3232
- `cli/native/XCWNativeSession.*`
@@ -39,12 +39,17 @@ The native side should own anything that depends on macOS frameworks, `xcrun sim
3939
Renders Apple’s CoreSimulator device-type PDF chrome assets into PNGs for the browser.
4040
- `client/src/app/App.tsx`
4141
Browser entrypoint for the React control surface.
42+
- `nativescript-inspector/src/index.ts`
43+
NativeScript in-app inspector runtime that connects to the Rust server over
44+
WebSocket, publishes NativeScript/UIKit hierarchies, and performs debug UIKit
45+
property edits from JavaScript.
4246

4347
## Working Rules
4448

4549
- Keep simulator-native logic in Objective-C under `cli/`.
4650
- Keep Rust server logic under `server/`.
4751
- Keep browser-only presentation logic in `client/`.
52+
- Keep NativeScript app runtime inspection logic in `nativescript-inspector/`.
4853
- Prefer adding a native API endpoint before adding client-only assumptions.
4954
- Do not add a Node or Swift dependency to solve work that already fits in Foundation/AppKit.
5055
- When touching private API usage, keep the adaptation small and explicit and document any simulator/runtime assumptions here.
@@ -82,6 +87,12 @@ Run the local server:
8287
./build/xcode-canvas-web serve --port 4310
8388
```
8489

90+
Use software H.264 when macOS screen recording starves the hardware encoder:
91+
92+
```sh
93+
./build/xcode-canvas-web serve --port 4310 --video-codec h264-software
94+
```
95+
8596
For LAN access:
8697

8798
```sh

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- private CoreSimulator boot fallback
99
- vendored private display bridge for continuous frames plus touch and keyboard injection
1010
- CoreSimulator chrome asset rendering for device bezels
11+
- NativeScript runtime inspector in `nativescript-inspector/` for JS-driven UIKit querying and property edits
1112
- local HTTP API plus static client hosting in Rust
1213
- WebTransport video delivery over a self-signed local or LAN endpoint
1314
- React client in `client/`
@@ -43,6 +44,12 @@ xcode-canvas-web serve --port 4310
4344

4445
Then open [http://127.0.0.1:4310](http://127.0.0.1:4310).
4546

47+
The default stream encoder is hardware HEVC. If macOS screen recording makes the stream sluggish, run the server with software H.264 so the live stream does not compete with the screen recorder's hardware encoder:
48+
49+
```sh
50+
xcode-canvas-web serve --port 4310 --video-codec h264-software
51+
```
52+
4653
The Rust server exposes HTTP on the requested port and WebTransport on `port + 1`.
4754
The browser bootstrap comes from `GET /api/health`, which returns the WebTransport URL template,
4855
certificate hash, and packet version needed by the client.
@@ -71,6 +78,54 @@ xcode-canvas-web open-url <udid> https://example.com
7178
xcode-canvas-web launch <udid> com.apple.Preferences
7279
```
7380

81+
## NativeScript Inspector
82+
83+
NativeScript apps can connect directly to the running server from JS and expose
84+
their NativeScript logical hierarchy plus raw UIKit backing views without
85+
linking the Swift inspector framework:
86+
87+
```ts
88+
import { startXcodeCanvasInspector } from "@nativescript/xcode-canvas-inspector";
89+
90+
if (__DEV__) {
91+
startXcodeCanvasInspector({ port: 4310 });
92+
}
93+
```
94+
95+
The runtime connects to `GET /api/inspector/connect` as a WebSocket. The Rust
96+
server prefers connected NativeScript inspectors for hierarchy requests and
97+
falls back to the Swift TCP inspector or AXe when no matching app inspector is
98+
available.
99+
100+
Dynamic UIKit mutation is exposed through
101+
`POST /api/simulators/:udid/inspector/request` with methods such as
102+
`View.getProperties` and `View.setProperty`. This endpoint is intentionally a
103+
debug/tooling API for now; the browser UI can add a focused property editor on
104+
top of it once the safe editable-property surface settles.
105+
106+
## Releases
107+
108+
Push a `vX.Y.Z` tag, or publish a GitHub release for that tag, to run the release workflow on `macos-latest`.
109+
The tag has to match `package.json`. The workflow builds the CLI, uploads the macOS archives and npm tarball
110+
to the GitHub release, and publishes the package to npm when `NPM_TOKEN` is configured in repository secrets.
111+
112+
## VS Code
113+
114+
Package the local VS Code extension:
115+
116+
```sh
117+
npm run package:vscode-extension
118+
```
119+
120+
Install it into local VS Code:
121+
122+
```sh
123+
npm run install:vscode-extension
124+
```
125+
126+
Then run `Xcode Canvas Web: Open Simulator View` from the Command Palette. The extension will open the simulator
127+
inside a VS Code panel and auto-start the local server when it is not already reachable.
128+
74129
## License
75130

76131
Copyright 2026 Dj

bin/xcode-canvas-web.mjs

100644100755
File mode changed.

cli/DFPrivateSimulatorDisplayBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ NS_SWIFT_NAME(PrivateSimulatorDisplayBridge)
5959
- (BOOL)pressHomeButton:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(pressHomeButton());
6060

6161
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(rotateRight());
62+
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(rotateLeft());
6263

6364
- (void)disconnect;
6465

cli/DFPrivateSimulatorDisplayBridge.m

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@
114114
static const NSUInteger DFKeyboardModifierOption = 1 << 2;
115115
static const NSUInteger DFKeyboardModifierCommand = 1 << 3;
116116
static const NSUInteger DFKeyboardModifierCapsLock = 1 << 4;
117-
static const NSUInteger DFKeyboardModifierFunction = 1 << 5;
118117

119118
typedef struct {
120119
__unsafe_unretained id unit;
@@ -2657,7 +2656,7 @@ - (BOOL)pressHomeButton:(NSError * _Nullable __autoreleasing *)error {
26572656
return success;
26582657
}
26592658

2660-
- (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
2659+
- (BOOL)rotateByDegrees:(double)deltaDegrees error:(NSError * _Nullable __autoreleasing *)error {
26612660
__block BOOL success = NO;
26622661
__block NSError *dispatchError = nil;
26632662

@@ -2700,7 +2699,7 @@ - (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
27002699
}
27012700
}
27022701

2703-
measurement.value = DFNormalizedDegrees(measurement.value + 90.0);
2702+
measurement.value = DFNormalizedDegrees(measurement.value + deltaDegrees);
27042703
self->_deviceRotationDegrees = measurement.value;
27052704

27062705
if (readFromSimulatorKit) {
@@ -2768,6 +2767,14 @@ - (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
27682767
return success;
27692768
}
27702769

2770+
- (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
2771+
return [self rotateByDegrees:90.0 error:error];
2772+
}
2773+
2774+
- (BOOL)rotateLeft:(NSError * _Nullable __autoreleasing *)error {
2775+
return [self rotateByDegrees:-90.0 error:error];
2776+
}
2777+
27712778
- (void)disconnect {
27722779
dispatch_block_t work = ^{
27732780
if (self->_screenAdapter != nil && self->_screenAdapterCallbackUUID != nil) {
@@ -2932,12 +2939,13 @@ - (BOOL)sendKeyCode:(uint16_t)keyCode
29322939
NSUInteger mask;
29332940
uint16_t keyCode;
29342941
} modifierMap[] = {
2942+
// IndigoHIDMessageForKeyboardArbitrary expects USB HID keyboard
2943+
// usages. These are left-side modifier usages from the Keyboard page.
29352944
{ DFKeyboardModifierCapsLock, 57 },
2936-
{ DFKeyboardModifierControl, 59 },
2937-
{ DFKeyboardModifierOption, 58 },
2938-
{ DFKeyboardModifierShift, 56 },
2939-
{ DFKeyboardModifierCommand, 55 },
2940-
{ DFKeyboardModifierFunction, 63 },
2945+
{ DFKeyboardModifierControl, 224 },
2946+
{ DFKeyboardModifierShift, 225 },
2947+
{ DFKeyboardModifierOption, 226 },
2948+
{ DFKeyboardModifierCommand, 227 },
29412949
};
29422950

29432951
NSMutableArray<NSNumber *> *modifierKeyCodes = [NSMutableArray array];

cli/XCWH264Encoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef void (^XCWH264EncoderOutputHandler)(NSData *sampleData,
1919

2020
- (void)encodePixelBuffer:(CVPixelBufferRef)pixelBuffer;
2121
- (void)requestKeyFrame;
22+
- (NSDictionary *)statsRepresentation;
2223
- (void)invalidate;
2324

2425
@end

0 commit comments

Comments
 (0)