Files
orca/src/preload/api/emulator-bridge.ts
T
Kelvin Amoaba be3d9b7e86 fix(emulator): type and paste into the Android emulator pane (#37139)
The pane's keyboard only sent serve-sim HID frames over the iOS WebSocket.
Android sessions have no serve-sim socket, so every key was dropped and every
paste failed with "device is not ready".

Android panes now send typed characters, paste and editing keys over a new
emulator:scrcpyKeyboard IPC beside emulator:scrcpyTouch, keyed by the streamed
serial. Main writes them straight to the live scrcpy control socket (text
injection, and keycodes with SHIFT_LEFT held for Shift), so keys keep their
order with touches. iOS keeps the HID path.
2026-11-01 20:21:15 +00:00

109 lines
3.8 KiB
TypeScript

import { ipcRenderer } from 'electron'
import type { PreloadApi } from '../api-types'
export const emulatorApi = {
startFrameStream: (args: {
streamUrl: string
streamKey?: string
}): Promise<{
streamId: string
}> => ipcRenderer.invoke('emulator:frameStreamStart', args),
stopFrameStream: (args: { streamId: string }): Promise<void> =>
ipcRenderer.invoke('emulator:frameStreamStop', args),
onFrameStreamFrame: (
callback: (data: { streamId: string; bytes: ArrayBuffer }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { streamId: string; bytes: ArrayBuffer }
) => callback(data)
ipcRenderer.on('emulator:frameStreamFrame', listener)
return () => ipcRenderer.removeListener('emulator:frameStreamFrame', listener)
},
onFrameStreamError: (
callback: (data: { streamId: string; message: string }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { streamId: string; message: string }
) => callback(data)
ipcRenderer.on('emulator:frameStreamError', listener)
return () => ipcRenderer.removeListener('emulator:frameStreamError', listener)
},
startVideoStream: (args: { deviceId: string; streamId: string }): Promise<{ streamId: string }> =>
ipcRenderer.invoke('emulator:videoStreamStart', args),
stopVideoStream: (args: { streamId: string }): Promise<void> =>
ipcRenderer.invoke('emulator:videoStreamStop', args),
onVideoStreamMeta: (
callback: (data: {
streamId: string
deviceId: string
meta: { codecId: string; width: number; height: number }
}) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
streamId: string
deviceId: string
meta: { codecId: string; width: number; height: number }
}
) => callback(data)
ipcRenderer.on('emulator:videoStreamMeta', listener)
return () => ipcRenderer.removeListener('emulator:videoStreamMeta', listener)
},
sendScrcpyTouch: (touch: {
deviceId: string
type: 'begin' | 'move' | 'end'
x: number
y: number
}): void => ipcRenderer.send('emulator:scrcpyTouch', touch),
sendScrcpyKeyboard: (
input: { deviceId: string; text: string } | { deviceId: string; key: string; shift: boolean }
): Promise<boolean> => ipcRenderer.invoke('emulator:scrcpyKeyboard', input),
onVideoStreamFrame: (
callback: (data: {
streamId: string
deviceId: string
config: boolean
keyFrame: boolean
bytes: ArrayBuffer
}) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
streamId: string
deviceId: string
config: boolean
keyFrame: boolean
bytes: ArrayBuffer
}
) => callback(data)
ipcRenderer.on('emulator:videoStreamFrame', listener)
return () => ipcRenderer.removeListener('emulator:videoStreamFrame', listener)
},
onPaneFocus: (callback: (data: { worktreeId: string }) => void): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: { worktreeId: string }) =>
callback(data)
ipcRenderer.on('emulator:pane-focus', listener)
return () => ipcRenderer.removeListener('emulator:pane-focus', listener)
},
onAutoAttach: (
callback: (data: {
worktreeId: string
info: { deviceUdid: string; streamUrl: string; wsUrl: string; axUrl?: string }
}) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: {
worktreeId: string
info: { deviceUdid: string; streamUrl: string; wsUrl: string; axUrl?: string }
}
) => callback(data)
ipcRenderer.on('ui:emulatorAutoAttach', listener)
return () => ipcRenderer.removeListener('ui:emulatorAutoAttach', listener)
}
} satisfies PreloadApi['emulator']