Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/XCWChromeRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN

+ (nullable NSData *)PNGDataForDeviceName:(NSString *)deviceName
error:(NSError * _Nullable * _Nullable)error;
+ (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
error:(NSError * _Nullable * _Nullable)error;
+ (nullable NSDictionary<NSString *, id> *)profileForDeviceName:(NSString *)deviceName
error:(NSError * _Nullable * _Nullable)error;

Expand Down
703 changes: 573 additions & 130 deletions cli/XCWChromeRenderer.m

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cli/native/XCWNativeBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ bool xcw_native_open_url(const char * _Nonnull udid, const char * _Nonnull url,
bool xcw_native_launch_bundle(const char * _Nonnull udid, const char * _Nonnull bundle_id, char * _Nullable * _Nullable error_message);
char * _Nullable xcw_native_get_chrome_profile(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
xcw_native_owned_bytes xcw_native_render_chrome_png(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
xcw_native_owned_bytes xcw_native_render_screen_mask_png(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
xcw_native_owned_bytes xcw_native_screenshot_png(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
char * _Nullable xcw_native_recent_logs(const char * _Nonnull udid, double seconds, size_t limit, char * _Nullable * _Nullable error_message);
char * _Nullable xcw_native_accessibility_snapshot(const char * _Nonnull udid, bool has_point, double x, double y, size_t max_depth, char * _Nullable * _Nullable error_message);
Expand Down
20 changes: 20 additions & 0 deletions cli/native/XCWNativeBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,26 @@ xcw_native_owned_bytes xcw_native_render_chrome_png(const char *udid, char **err
}
}

xcw_native_owned_bytes xcw_native_render_screen_mask_png(const char *udid, char **error_message) {
@autoreleasepool {
NSDictionary *simulator = XCWSimulatorRecordForUDID(udid, error_message);
if (simulator == nil) {
return (xcw_native_owned_bytes){0};
}

NSError *renderError = nil;
NSString *deviceName = simulator[@"deviceTypeName"] ?: simulator[@"name"] ?: @"";
NSData *pngData = [XCWChromeRenderer screenMaskPNGDataForDeviceName:deviceName
error:&renderError];
if (pngData == nil) {
XCWSetErrorMessage(error_message, renderError);
return (xcw_native_owned_bytes){0};
}

return XCWOwnedBytesFromData(pngData);
}
}

xcw_native_owned_bytes xcw_native_screenshot_png(const char *udid, char **error_message) {
@autoreleasepool {
XCWSimctl *simctl = [[XCWSimctl alloc] init];
Expand Down
6 changes: 5 additions & 1 deletion client/src/api/controls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { apiRequest } from "./client";
import { accessTokenFromLocation, apiRequest } from "./client";
import type {
KeyPayload,
LaunchPayload,
Expand Down Expand Up @@ -61,6 +61,10 @@ export function simulatorControlSocketUrl(udid: string) {
`/api/simulators/${encodeURIComponent(udid)}/control`,
window.location.href,
);
const token = accessTokenFromLocation();
if (token) {
url.searchParams.set("simdeckToken", token);
}
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
return url.toString();
}
Expand Down
1 change: 1 addition & 0 deletions client/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ChromeProfile {
screenWidth: number;
screenHeight: number;
cornerRadius: number;
hasScreenMask?: boolean;
}

export interface AccessibilityFrame {
Expand Down
81 changes: 74 additions & 7 deletions client/src/app/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { useCallback, useEffect, useRef, useState } from "react";

import {
useCallback,
useEffect,
useRef,
useState,
type CSSProperties,
} from "react";

import { accessTokenFromLocation } from "../api/client";
import {
bootSimulator,
dismissKeyboard,
Expand All @@ -21,13 +28,18 @@ import type {
AccessibilitySourcePreference,
AccessibilityTreeResponse,
ChromeProfile,
SimulatorMetadata,
TouchPhase,
} from "../api/types";
import { AccessibilityInspector } from "../features/accessibility/AccessibilityInspector";
import { useKeyboardInput } from "../features/input/useKeyboardInput";
import { usePointerInput } from "../features/input/usePointerInput";
import { simulatorRuntimeLabel } from "../features/simulators/simulatorDisplay";
import { useSimulatorList } from "../features/simulators/useSimulatorList";
import {
isWebRtcStreamMode,
sendWebRtcControlMessage,
} from "../features/stream/streamWorkerClient";
import { useLiveStream } from "../features/stream/useLiveStream";
import { DebugPanel } from "../features/toolbar/DebugPanel";
import { Toolbar } from "../features/toolbar/Toolbar";
Expand Down Expand Up @@ -75,7 +87,38 @@ const LOGICAL_INSPECTOR_MAX_DEPTH = 80;
clearLegacyVolatileUiState();

function buildChromeUrl(udid: string, stamp: number): string {
return `${STREAM_ORIGIN}/api/simulators/${udid}/chrome.png?stamp=${stamp}`;
return buildAuthenticatedAssetUrl(
`/api/simulators/${udid}/chrome.png`,
stamp,
);
}

function buildScreenMaskUrl(udid: string, stamp: number): string {
return buildAuthenticatedAssetUrl(
`/api/simulators/${udid}/screen-mask.png`,
stamp,
);
}

function buildAuthenticatedAssetUrl(path: string, stamp: number): string {
const url = new URL(path, `${STREAM_ORIGIN || window.location.origin}/`);
url.searchParams.set("stamp", String(stamp));
const token = accessTokenFromLocation();
if (token) {
url.searchParams.set("simdeckToken", token);
}
return url.toString();
}

function shouldRenderNativeChrome(simulator: SimulatorMetadata): boolean {
const identifier = simulator.deviceTypeIdentifier ?? "";
const name = simulator.name ?? "";
return (
identifier.includes(".iPhone-") ||
identifier.includes(".iPad-") ||
name.startsWith("iPhone") ||
name.startsWith("iPad")
);
}

function mergeAccessibilitySources(
Expand Down Expand Up @@ -541,6 +584,10 @@ export function AppShell() {
setChromeProfile(null);
return;
}
if (!shouldRenderNativeChrome(selectedSimulator)) {
setChromeProfile(null);
return;
}

try {
const profile = await fetchChromeProfile(selectedSimulator.udid);
Expand Down Expand Up @@ -670,13 +717,30 @@ export function AppShell() {
);
const chromeScreenStyle =
chromeProfile && chromeScreenRect
? {
? ({
left: `${(chromeScreenRect.x / chromeProfile.totalWidth) * 100}%`,
top: `${(chromeScreenRect.y / chromeProfile.totalHeight) * 100}%`,
width: `${(chromeScreenRect.width / chromeProfile.totalWidth) * 100}%`,
height: `${(chromeScreenRect.height / chromeProfile.totalHeight) * 100}%`,
borderRadius: chromeScreenBorderRadius ?? "0",
}
...(chromeProfile.hasScreenMask && selectedSimulator
? {
maskImage: `url("${buildScreenMaskUrl(
selectedSimulator.udid,
streamStamp,
)}")`,
maskMode: "alpha",
maskRepeat: "no-repeat",
maskSize: "100% 100%",
WebkitMaskImage: `url("${buildScreenMaskUrl(
selectedSimulator.udid,
streamStamp,
)}")`,
WebkitMaskRepeat: "no-repeat",
WebkitMaskSize: "100% 100%",
}
: {}),
} satisfies CSSProperties)
: null;
const shellStyle = chromeProfile
? {
Expand Down Expand Up @@ -763,8 +827,11 @@ export function AppShell() {

function sendControl(udid: string, message: ControlMessage) {
setLocalError("");
const state = ensureControlSocket(udid);
const encoded = JSON.stringify(message);
if (sendWebRtcControlMessage(encoded)) {
return;
}
const state = ensureControlSocket(udid);
if (state.socket.readyState === WebSocket.OPEN) {
state.socket.send(encoded);
} else {
Expand All @@ -773,7 +840,7 @@ export function AppShell() {
}

useEffect(() => {
if (selectedSimulator?.isBooted) {
if (selectedSimulator?.isBooted && !isWebRtcStreamMode()) {
ensureControlSocket(selectedSimulator.udid);
} else {
closeControlSocket();
Expand Down
Loading
Loading