wo/electron/src/protocol.ts
2026-03-10 13:11:57 -04:00

252 lines
5.9 KiB
TypeScript

/**
* Wo Compositor - Shared Types and Protocol
*
* This file defines the protocol messages and types used for communication
* between the Wo compositor and Electron/Wayland clients.
*
* These types are shared across the Rust compositor and TypeScript servers/clients.
*/
/**
* IPC Protocol Magic Numbers (little-endian u32)
*/
export const MAGIC = {
HELLO: 0x574F484C, // "WOHL"
FRAME: 0x574F4652, // "WOFR"
MOUSE_MOVE: 0x574F4D4D, // "WOMM"
MOUSE_BUTTON: 0x574F4D42, // "WOMB"
KEYBOARD: 0x574F4B42, // "WOKB"
SCROLL: 0x574F5343, // "WOSC"
ACTION: 0x574F4341, // "WOAC"
FOCUS_CHANGE: 0x574F4643, // "WOFC"
WINDOW_META: 0x574F574D, // "WOWM"
WINDOW_POS: 0x574F5750, // "WOWP"
SYSCALL: 0x574F5359, // "WOSY"
FRAME_ACK: 0x574F4641, // "WOFA"
SURFACE_BUFFER: 0x574F5342, // "WOSB" — Wayland surface pixels from compositor
SHM_BUFFER: 0x574F534D, // "WOSM" — Wayland SHM surface sent to Electron via process FD
DMABUF_FRAME: 0x574F4446, // "WODF" — Wayland surface DMABUF FDs from compositor
FORWARD_POINTER: 0x574F5045, // "WOPE" — forwarded pointer from web UI canvas
FORWARD_KEYBOARD: 0x574F4B45, // "WOKE" — forwarded keyboard from web UI canvas
FORWARD_RELATIVE_POINTER: 0x574F5245, // "WORE" — forwarded relative pointer (dx/dy)
POINTER_LOCK_REQUEST: 0x574F504C, // "WOPL" — server-to-client pointer lock request
ENV_UPDATE: 0x574F4555, // "WOEU" — environment variable update broadcast
SCREENCOPY_EVENT: 0x574F5345, // "WOSE" — screencopy capture event notification
} as const;
/**
* Mouse button codes
*/
export const MOUSE_BUTTON = {
LEFT: 1,
MIDDLE: 2,
RIGHT: 3,
BACK: 4,
FORWARD: 5,
} as const;
/**
* Input Events from Compositor → Client
*/
export interface MouseMoveEvent {
type: 'mouseMove';
x: number;
y: number;
}
export interface MouseButtonEvent {
type: 'mouseButton';
button: number;
pressed: boolean;
time: number;
}
export interface KeyboardEvent {
type: 'keyboard';
key: number;
pressed: boolean;
time: number;
}
export interface ScrollEvent {
type: 'scroll';
vertical: number;
horizontal: number;
time: number;
}
export interface FocusChangeEvent {
type: 'focusChange';
window: string;
focused: boolean;
}
export interface WindowMetadataEvent {
type: 'windowMetadata';
metadata: string; // JSON string
}
export interface PointerLockRequestEvent {
type: 'pointerLockRequest';
window: string;
lock: boolean;
}
export interface EnvUpdateEvent {
type: 'envUpdate';
vars: Record<string, string>;
}
export interface ScreencopyEvent {
type: 'screencopyEvent';
active: boolean;
clientCount: number;
}
export type InputEvent =
| MouseMoveEvent
| MouseButtonEvent
| KeyboardEvent
| ScrollEvent
| FocusChangeEvent
| WindowMetadataEvent
| PointerLockRequestEvent
| EnvUpdateEvent
| ScreencopyEvent;
/**
* Compositor Actions from Client → Compositor
*/
export interface QuitAction {
type: 'quit';
code?: number;
}
export interface CustomAction {
type: 'custom';
action: string;
payload?: Record<string, any>;
}
export type CompositorAction = QuitAction | CustomAction;
/**
* Frame information for rendering
*/
export interface FrameInfo {
seq: number;
width: number;
height: number;
format: string; // DRM fourcc as string
planes: PlaneInfo[];
}
export interface PlaneInfo {
offset: number;
stride: number;
modifier: bigint;
}
/**
* DMABUF window frame information (metadata-only; FDs passed via ancillary data)
*/
export interface DmabufFrameInfo {
name: string;
width: number;
height: number;
format: number; // DRM fourcc as u32
numPlanes: number;
planes: DmabufPlaneInfo[];
fds: number[]; // File descriptors passed with this message
}
export interface DmabufPlaneInfo {
offset: number;
stride: number;
modifier: bigint;
}
/**
* Window configuration
*/
export interface WindowConfig {
name: string;
width: number;
height: number;
x?: number;
y?: number;
z_order?: number;
}
/**
* Utility functions for working with the protocol
*/
export function numberToMagic(magic: number): string {
return String.fromCharCode(
magic & 0xff,
(magic >> 8) & 0xff,
(magic >> 16) & 0xff,
(magic >> 24) & 0xff
);
}
export function bufferToU32LE(buf: Buffer, offset: number = 0): number {
return (
buf[offset] |
(buf[offset + 1] << 8) |
(buf[offset + 2] << 16) |
(buf[offset + 3] << 24)
);
}
export function bufferToU64LE(buf: Buffer, offset: number = 0): bigint {
const lo = BigInt(bufferToU32LE(buf, offset));
const hi = BigInt(bufferToU32LE(buf, offset + 4));
return (hi << BigInt(32)) | lo;
}
export function bufferToF64LE(buf: Buffer, offset: number = 0): number {
return buf.readDoubleLE(offset);
}
export function bufferToI32LE(buf: Buffer, offset: number = 0): number {
const val = bufferToU32LE(buf, offset);
// Convert unsigned to signed
return val > 0x7fffffff ? val - 0x100000000 : val;
}
export function u32ToBufferLE(val: number): Buffer {
const buf = Buffer.alloc(4);
buf.writeUInt32LE(val, 0);
return buf;
}
export function u64ToBufferLE(val: bigint): Buffer {
const buf = Buffer.alloc(8);
const lo = Number(val & BigInt(0xffffffff));
const hi = Number((val >> BigInt(32)) & BigInt(0xffffffff));
buf.writeUInt32LE(lo, 0);
buf.writeUInt32LE(hi, 4);
return buf;
}
export function f64ToBufferLE(val: number): Buffer {
const buf = Buffer.alloc(8);
buf.writeDoubleLE(val, 0);
return buf;
}
export function i32ToBufferLE(val: number): Buffer {
const buf = Buffer.alloc(4);
buf.writeInt32LE(val, 0);
return buf;
}
export function stringToBuffer(str: string): Buffer {
return Buffer.from(str, 'utf-8');
}
export function bufferToString(buf: Buffer, offset: number = 0, length?: number): string {
return buf.toString('utf-8', offset, length !== undefined ? offset + length : undefined);
}