Skip to content

Native emulator capture and input

Use the patched EKA2L1 frontend to capture the guest display, send logical pointer events and inspect native process exits. The control endpoint belongs to a local disposable emulator session; it provides no physical-device control.

Implementation and lifecycle

cpp/symbian/emulator is a separate GPL-3.0-or-later native research adapter. guest-control.patch links it only into the research Qt frontend through the injected CMake hook. The host Python extension and wheel do not link EKA2L1 or this adapter. Its native code uses Abseil Status/StatusOr, C++20 and disabled exceptions, with the repository's formatting and layout conventions.

Capture calls the original graphics driver's read_bitmap on screen_texture, then encodes PNG in native Qt code. It captures the guest display, excluding the host desktop and frontend overlays. Pointer coordinates are logical guest coordinates; events enter the same Window Server driver queue as normal frontend input. A queued response acknowledges enqueueing; check the next capture for visible delivery. Process exit records copy scalar/string values from the real kernel callback, avoiding dangling process pointers.

The Qt thread holds the frontend state lock while validating pointers/captures. Kernel access uses a try-lock and reports UNAVAILABLE while busy. Capture takes a protected screen snapshot and releases kernel/screen locks before waiting for graphics. Pointer delivery releases its validation lock before the existing Window Server path acquires the kernel lock internally. Teardown detaches the callback before the OS worker destroys the kernel, saves a final report, then stops the existing workers. Pending replies drain with at most 100 ms per accepted socket after the Qt loop stops.

Start a controlled disposable instance

Build the patched frontend and prepare a disposable instance using the firmware guide. For the RM-807 profile the virtual C mapping is data/drives/rm-807/c. Use cpu: dynarmic or cpu: dyncom in that copy's config.

From the repository root, after setting SYMBIAN_GUI_INSTANCE to that private copy and SYMBIAN_EMULATOR to the built executable:

SYMBIAN_CONTROL_DIR=$(mktemp -d /tmp/symbian-control.XXXXXX)
export SYMBIAN_CONTROL_SOCKET="$SYMBIAN_CONTROL_DIR/control.sock"
printf '%s\n' "$SYMBIAN_CONTROL_SOCKET"
EKA2L1_DATA_ROOT="$SYMBIAN_GUI_INSTANCE" \
EKA2L1_EXPERIMENTAL_SVC_PROFILE=rm807-113.010.1508 \
EKA2L1_RESEARCH_CONTROL_SOCKET="$SYMBIAN_CONTROL_SOCKET" \
"$SYMBIAN_EMULATOR" --device RM-807 --run 'C:\sys\bin\gui_app.exe'

Use a second terminal to set SYMBIAN_CONTROL_SOCKET to the printed path:

symbian emu status --endpoint "$SYMBIAN_CONTROL_SOCKET"
symbian emu screenshot --endpoint "$SYMBIAN_CONTROL_SOCKET" --name initial
symbian emu pointer --endpoint "$SYMBIAN_CONTROL_SOCKET" 64 575 press
symbian emu pointer --endpoint "$SYMBIAN_CONTROL_SOCKET" 64 575 release
symbian emu screenshot --endpoint "$SYMBIAN_CONTROL_SOCKET" --name after-tap

Allow guest startup/redraw to complete between commands. A busy response means UNAVAILABLE before delivery; it can be retried. A client timeout does not cancel native work, so do not blindly repeat a pointer request after an ambiguous failure. The live test retries only explicit UNAVAILABLE responses.

For the current 360x640 logical layout, reset is centered at (180,575) and exit at (296,575). Exit is handled on press; do not send release to the closed window. Captures are 720x1280 with this fixture's display scale two; pointer coordinates remain 360x640. These coordinates apply to this portrait layout; adjust them for other orientations or display scales. Current captured control colors are green, blue and purple, reflecting the source SDK's packed TRgb ordering. The operations are increment/reset/exit regardless of color.

After the guest exit the frontend closes its endpoint. Read its explicit saved status with:

symbian emu status --endpoint "$SYMBIAN_CONTROL_SOCKET" --saved

The native report is $SYMBIAN_CONTROL_SOCKET.status.json; it retains the exit record after shutdown. Without --saved, status requires a live endpoint and never silently returns an old report. Keep the native report, matching PNGs, frontend log and build/input digests together. Do not infer normal guest exit from the host's exit code alone.

Bounds and protocol

Control is disabled without EKA2L1_RESEARCH_CONTROL_SOCKET. An enabled path must be absolute, at most 100 UTF-8 bytes and in an existing directory owned by the current user with no group/other access. This parent-directory protection is essential on macOS: Qt's socket access flags alone do not restrict access there. See QLocalServer.

One compact JSON object plus newline is accepted per connection, at most 4096 bytes. There are at most 16 active clients with a five-second idle bound. The only operations are status, capture and pointer; all others return UNIMPLEMENTED. There is no hardware transport, guest command runner or arbitrary filesystem path argument. Responses use symbian.emulator-control/v1 and canonical Abseil status codes. Status retains at most 256 process exit records.

Captures accept only 1–64 ASCII letters, digits, hyphens or underscores as a basename, refuse existing files/symlinks, and write into the private socket parent. Native image dimensions are bounded at 4096 per axis. Publication uses QSaveFile. Existing final reports also prevent reusing an endpoint name. These are local accidental-overwrite bounds, not an immutable archive against the owning user. A timed-out client cannot interrupt a stalled graphics driver; the launcher must enforce a process deadline if graphics stops responding.