| 1 | # Guest cursor stream |
| 2 | |
| 3 | The executable is freestanding C99. `cursor.c` contains pixel conversion and dirty-rectangle detection; `windows.c` owns Win32 capture and transport. Zig supplies the cross compiler and Windows headers, with no Zig or C runtime in the executable. x86 and x64 target Windows 7; ARM64 targets Windows 10. PE subsystem versions and imported DLLs are checked by `python3 guest/test.py`. Runtime validation currently uses Windows 10, not a Windows 7 fixture. |
| 4 | |
| 5 | Build with `python3 guest/build.py --arch x86_64` (also `x86` and `aarch64`). In an elevated PowerShell session inside the logged-in administrator's desktop, run `./install.ps1 -Binary ./build/snowglobe-guest-x86_64.exe`. The installer uses Task Scheduler COM APIs available to PowerShell 2, installs into protected Program Files, and starts an elevated task at that user's logon. `-Remove` removes that task and executable. Elevation is required by the VirtIO serial driver's device ACL; capture must run in the interactive session, not session 0. The guest needs a compatible VirtIO serial driver and the channel named in `protocol.json`. A standard-user launcher requires a privileged handle broker; it is not part of this first backend. |
| 6 | |
| 7 | `BitBlt` copies the primary desktop into a CPU DIB without drawing the cursor. RGB comparisons ignore the unused fourth byte. Unchanged pixels produce no screen packets; changed pixels produce one bounding rectangle. Cursor polling runs every 8 ms; screen capture runs at most 30 Hz. The host's existing encoder supplies WebRTC video. Secure desktops, RDP sessions that differ from the physical console, and capture failures return the viewer to QEMU capture. |
| 8 | |
| 9 | `GetCursorInfo` supplies the shape, visibility, and observed position. `GetIconInfo` and black/white `DrawIconEx` renders recover RGBA and binary XOR planes, including colored XOR. Nonbinary color XOR and unsupported cursor sizes explicitly fall back to the browser's default cursor, preserving video. Animation timing uses the optional `GetCursorFrameInfo` user32 export, resolved by name. Missing or unusable animation metadata falls back to a static frame. Animation starts a browser-local clock; Windows does not expose the current animation phase through this interface. Ordinary shapes use the browser cursor; XOR shapes use local layers with difference blending. Neither follows streamed positions. |
| 10 | |
| 11 | ## Wire format |
| 12 | |
| 13 | `protocol.json` is the only home for packet IDs, field order, channel name, flags, and bounds. The build generates its private C header; the host reads that same schema. Each packet begins with the four-byte magic, a big-endian uint32 type, and a big-endian uint32 payload length. Fields listed in the schema are big-endian uint32 values. |
| 14 | |
| 15 | - Screen: tightly packed top-down BGRx rectangle rows following the listed fields. A connection, refresh request, resize, or resumed capture starts with a full frame. |
| 16 | - Cursor: each frame is a uint32 duration in milliseconds, width × height RGBA pixels, then one XOR bit mask byte per pixel. The schema assigns the channel bits; each selected channel inverts the underlying browser pixel. Zero dimensions and zero frames mean unsupported shape/default cursor. |
| 17 | - Pointer: coordinates use signed two's-complement values in the uint32 fields. These are observations, not a cursor hit map. |
| 18 | - Suspend and heartbeat have empty payloads. A heartbeat arrives at least once per second; four seconds without a packet falls back to QEMU. |
| 19 | - Refresh is the only host-to-guest message and has an empty payload. The agent validates the entire request. The host requests it on every connection, including reconnects that reuse an open guest port. |
| 20 | |
| 21 | The host broker validates a running VM's libvirt-owned channel path. One guest reader fans out to that VM's viewers. Guest bytes are untrusted and bounded before decoding; viewer buffers are bounded independently. No guest network listener or GPU is required. |
| 22 | |
| 23 | ## Later backends and surfaces |
| 24 | |
| 25 | Keep the core independent of an OS and add real platform implementations when their capture and cursor APIs can be tested. X11 cursor notifications, Wayland's compositor/portal permissions, and macOS capture permissions need separate backend decisions, not empty adapters. The next Windows experiment can enumerate HWNDs and attach full surfaces, geometry, stacking, and dirty regions to this transport; occlusion, layered windows, protected content, DPI, and secure desktops need explicit behavior. A window rectangle does not imply a uniform cursor: application hit testing can change the shape anywhere inside it. Region prediction should be based on validated application information or observations with invalidation, not fabricated bounding boxes. |