// src/main.ts import { app, BrowserWindow, dialog, ipcMain, Menu, protocol } from "electron"; import { createConnection, createServer } from "net"; import { fileURLToPath as fileURLToPath2 } from "url"; import { dirname, resolve as resolvePath, join as joinPath } from "path"; import { createHash as createHash2, randomUUID } from "crypto"; import fs from "fs"; import { createRequire } from "module"; import { pathToFileURL } from "node:url"; // src/xdg.ts import { existsSync, readFileSync, readdirSync } from "node:fs"; import { basename, delimiter, extname, join, resolve } from "node:path"; import { spawn, spawnSync } from "node:child_process"; function getXdgDirectories(env = process.env) { const home = env.HOME || "/"; const configHome = env.XDG_CONFIG_HOME || join(home, ".config"); const dataHome = env.XDG_DATA_HOME || join(home, ".local", "share"); const dataDirs = splitDirs(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share"); const configDirs = splitDirs(env.XDG_CONFIG_DIRS || "/etc/xdg"); const userDirsFile = join(configHome, "user-dirs.dirs"); const userDirs = parseUserDirs(userDirsFile, home); const userDir = (key, fallback) => userDirs[key] || join(home, fallback); return { home, configHome, configDirs, dataHome, dataDirs, cacheHome: env.XDG_CACHE_HOME || join(home, ".cache"), stateHome: env.XDG_STATE_HOME || join(home, ".local", "state"), runtimeDir: env.XDG_RUNTIME_DIR || null, desktop: userDir("XDG_DESKTOP_DIR", "Desktop"), documents: userDir("XDG_DOCUMENTS_DIR", "Documents"), downloads: userDir("XDG_DOWNLOAD_DIR", "Downloads"), music: userDir("XDG_MUSIC_DIR", "Music"), pictures: userDir("XDG_PICTURES_DIR", "Pictures"), publicShare: userDir("XDG_PUBLICSHARE_DIR", "Public"), templates: userDir("XDG_TEMPLATES_DIR", "Templates"), videos: userDir("XDG_VIDEOS_DIR", "Videos") }; } function splitDirs(value) { return [...new Set(value.split(delimiter).filter(Boolean).map((dir) => resolve(dir)))]; } function parseUserDirs(file, home) { if (!existsSync(file)) return {}; const result = {}; for (const line of readFileSync(file, "utf8").split(/\r?\n/)) { const match = line.match(/^\s*(XDG_[A-Z]+_DIR)="((?:\\.|[^"])*)"\s*$/); if (!match) continue; const value = match[2].replace(/\\"/g, '"').replace(/\\\\/g, "\\").replace(/^\$HOME(?=\/|$)/, home); if (value.startsWith("/")) result[match[1]] = resolve(value); } return result; } function decodeDesktopValue(value) { return value.replace(/\\([snrt\\])/g, (_match, code) => ({ s: " ", n: ` `, r: "\r", t: "\t", "\\": "\\" })[code] ?? code); } function desktopKeyValues(content, locale) { const values = new Map; let inEntry = false; const localized = new Map; const language = locale.split(/[.@]/)[0]; for (const line of content.split(/\r?\n/)) { const trimmed = line.trim(); if (trimmed.startsWith("[")) { inEntry = trimmed === "[Desktop Entry]"; continue; } if (!inEntry || !trimmed || trimmed.startsWith("#")) continue; const splitAt = trimmed.indexOf("="); if (splitAt < 1) continue; const key = trimmed.slice(0, splitAt); const value = decodeDesktopValue(trimmed.slice(splitAt + 1)); const localizedKey = key.match(/^Name\[(.+)\]$/); if (localizedKey) localized.set(localizedKey[1], value); else values.set(key, value); } if (language && localized.has(language)) values.set("Name", localized.get(language)); return values; } function availableCommand(command) { if (!command) return false; if (command.includes("/")) return existsSync(command); const result = spawnSync("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "wo", command], { stdio: "ignore", env: process.env }); return result.status === 0; } function resolveDesktopIcon(name, options = {}) { const xdg = getXdgDirectories(options.env); const size = Math.max(16, options.size ?? 48); const theme = options.theme || options.env?.XDG_ICON_THEME || process.env.XDG_ICON_THEME || "hicolor"; const suffixes = extname(name) ? [""] : [".png", ".svg"]; const candidates = []; if (name.startsWith("/")) candidates.push(name); for (const suffix of suffixes) { for (const root of [xdg.dataHome, ...xdg.dataDirs]) { for (const iconTheme of [theme, "hicolor"]) { const themeRoot = join(root, "icons", iconTheme); for (const directory of iconSizeDirectories(themeRoot, size)) { candidates.push(join(themeRoot, directory, "apps", `${name}${suffix}`)); } candidates.push(join(themeRoot, "apps", `${name}${suffix}`)); } candidates.push(join(root, "pixmaps", `${name}${suffix}`)); } } const file = candidates.find((candidate) => existsSync(candidate)); if (!file) return; const extension = extname(file).toLowerCase(); const mimeType = extension === ".svg" ? "image/svg+xml" : "image/png"; try { return { type: "base64", data: readFileSync(file).toString("base64"), mimeType }; } catch { return; } } function iconSizeDirectories(themeRoot, target) { try { return readdirSync(themeRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => /^(?:scalable|\d+x\d+(?:@\d+(?:\.\d+)?x)?)$/.test(name)).sort((a, b) => iconDirectoryDistance(a, target) - iconDirectoryDistance(b, target)); } catch { return ["scalable", `${target}x${target}`, "48x48", "32x32", "24x24", "16x16"]; } } function iconDirectoryDistance(directory, target) { if (directory === "scalable") return 0; const match = directory.match(/^(\d+)x\d+/); return match ? Math.abs(Number(match[1]) - target) + 1 : Number.MAX_SAFE_INTEGER; } function listDesktopApplications(options = {}) { const env = options.env || process.env; const xdg = getXdgDirectories(env); const locale = options.locale || env.LC_ALL || env.LC_MESSAGES || env.LANG || "C"; const dirs = [join(xdg.dataHome, "applications"), ...xdg.dataDirs.map((dir) => join(dir, "applications"))]; const seen = new Set; const apps = []; for (const dir of dirs) { let entries; try { entries = readdirSync(dir).filter((entry) => entry.endsWith(".desktop")).sort(); } catch { continue; } for (const entry of entries) { const file = join(dir, entry); let keys; try { const content = readFileSync(file, "utf8"); if (content.length > 512000) continue; keys = desktopKeyValues(content, locale); } catch { continue; } const id = entry; if (seen.has(id) || keys.get("Type") !== "Application") continue; seen.add(id); if (keys.get("Hidden") === "true" || keys.get("NoDisplay") === "true") continue; const onlyShowIn = (keys.get("OnlyShowIn") || "").split(";").filter(Boolean); const notShowIn = (keys.get("NotShowIn") || "").split(";").filter(Boolean); const desktops = (env.XDG_CURRENT_DESKTOP || "").split(":").filter(Boolean); if (onlyShowIn.length && !onlyShowIn.some((desktop) => desktops.includes(desktop))) continue; if (notShowIn.some((desktop) => desktops.includes(desktop))) continue; const execLine = keys.get("Exec") || ""; const command = parseDesktopExec(execLine).argv[0] || ""; if (!command || keys.has("TryExec") && !availableCommand(keys.get("TryExec"))) continue; const name = keys.get("Name") || basename(entry, ".desktop"); const iconName = keys.get("Icon"); const icon = iconName ? resolveDesktopIcon(iconName, { env }) : undefined; apps.push({ name, command, desktopId: id, desktopFile: file, ...icon ? { icon } : {}, ...keys.has("GenericName") ? { genericName: keys.get("GenericName") } : {}, ...keys.has("Comment") ? { comment: keys.get("Comment") } : {}, ...keys.has("Categories") ? { categories: keys.get("Categories").split(";").filter(Boolean) } : {}, ...keys.has("Keywords") ? { keywords: keys.get("Keywords").split(";").filter(Boolean) } : {}, ...keys.has("StartupWMClass") ? { startupWMClass: keys.get("StartupWMClass") } : {}, ...keys.has("Terminal") ? { terminal: keys.get("Terminal") === "true" } : {}, ...keys.has("StartupNotify") ? { startupNotify: keys.get("StartupNotify") === "true" } : {} }); } } return apps; } async function launchDesktopApplication(desktopId, files = []) { const app = listDesktopApplications().find((entry) => entry.desktopId === desktopId || entry.name === desktopId); if (!app) return { ok: false, error: `Desktop application not found: ${desktopId}` }; const keys = desktopKeyValues(readFileSync(app.desktopFile, "utf8"), process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || "C"); const parsed = parseDesktopExec(keys.get("Exec") || "", { ...app, iconName: keys.get("Icon") }, files); const [command, ...args] = parsed.argv; if (!command) return { ok: false, error: `No executable in desktop entry: ${desktopId}` }; return new Promise((resolveResult) => { try { const child = spawn(command, args, { detached: true, stdio: "ignore", env: process.env }); child.once("error", (error) => resolveResult({ ok: false, error: error.message })); child.once("spawn", () => { child.unref(); resolveResult({ ok: true, pid: child.pid }); }); } catch (error) { resolveResult({ ok: false, error: String(error) }); } }); } function parseDesktopExec(exec, app, files = []) { const tokens = exec.match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s]+/g) || []; const argv = []; for (const token of tokens) { const unquoted = token.replace(/^['"]|['"]$/g, "").replace(/\\(["'\\`$])/g, "$1"); if (unquoted === "%i") { if (app?.iconName) argv.push("--icon", app.iconName); continue; } if (unquoted === "%F" || unquoted === "%U") { argv.push(...files); continue; } const value = unquoted.replace(/%%|%[fFuUdDnNickvm]/g, (code) => { if (code === "%%") return "%"; if (code === "%c") return app?.name || ""; if (code === "%k") return app?.desktopFile || ""; if (code === "%f" || code === "%u") return files[0] || ""; return ""; }); if (value) argv.push(value); } return { argv }; } // src/wo-capabilities.ts import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; var WO_CAPABILITIES = [ "window-management", "overlay-rendering", "desktop-applications", "xdg", "dbus", "portals", "filesystem", "process-launch", "system-control", "notifications", "audio-capture" ]; var MAX_INDEX_BYTES = 4 * 1024 * 1024; var MAX_ASSET_BYTES = 16 * 1024 * 1024; function sha256(bytes) { return createHash("sha256").update(bytes).digest("hex"); } function isHash(value) { return typeof value === "string" && /^[a-f0-9]{64}$/.test(value); } function validateWoManifest(value, indexHash, pageUrl) { if (!value || typeof value !== "object") return null; const m = value; let page; try { page = new URL(pageUrl); } catch { return null; } if (m.schemaVersion !== 1 || typeof m.appId !== "string" || !/^[a-zA-Z0-9._-]{1,128}$/.test(m.appId)) return null; if (m.indexSha256 !== indexHash || m.origin !== undefined && m.origin !== page.origin) return null; if (!Array.isArray(m.capabilities) || !m.capabilities.every((cap) => WO_CAPABILITIES.includes(cap))) return null; if (!m.componentsCss || typeof m.componentsCss.url !== "string" || !isHash(m.componentsCss.sha256)) return null; if (!Array.isArray(m.scripts) || m.scripts.length > 256) return null; if (m.styles !== undefined && (!Array.isArray(m.styles) || m.styles.length > 256)) return null; const trustedPath = (path) => { try { const resolved = new URL(path, page); return resolved.origin === page.origin && (resolved.protocol === "https:" || resolved.protocol === "http:" || resolved.protocol === "file:"); } catch { return false; } }; if (!trustedPath(m.componentsCss.url)) return null; const seen = new Set; for (const script of m.scripts) { if (!script || typeof script.url !== "string" || !isHash(script.sha256) || !trustedPath(script.url)) return null; const absolute = new URL(script.url, page).href; if (seen.has(absolute)) return null; seen.add(absolute); } for (const style of m.styles ?? []) { if (!style || typeof style.url !== "string" || !isHash(style.sha256) || !trustedPath(style.url)) return null; const absolute = new URL(style.url, page).href; if (seen.has(absolute)) return null; seen.add(absolute); } return m; } async function readUrl(url, maxBytes, fetcher) { try { let bytes; if (new URL(url).protocol === "file:") { bytes = await readFile(fileURLToPath(url)); } else { const response = await fetcher(url); if (!response.ok) return null; bytes = new Uint8Array(await response.arrayBuffer()); } return bytes.byteLength <= maxBytes ? bytes : null; } catch { return null; } } var assetCache = new Map; async function loadAsset(url, hash, maxBytes, fetcher) { const cacheKey = `${url}#${hash}`; const cached = assetCache.get(cacheKey); if (cached) return cached; const bytes = await readUrl(url, maxBytes, fetcher); if (!bytes || sha256(bytes) !== hash) return null; assetCache.set(cacheKey, bytes); return bytes; } async function prefetchWoSiteAssets(pageUrl, fetcher = async (url) => { try { const response = await fetch(url); if (response.ok) return response; const parsed = new URL(url); if (parsed.hostname !== "localhost" || parsed.protocol !== "http:") return response; parsed.hostname = "127.0.0.1"; return await fetch(parsed.href); } catch (error) { const parsed = new URL(url); if (parsed.hostname !== "localhost" || parsed.protocol !== "http:") throw error; parsed.hostname = "127.0.0.1"; return await fetch(parsed.href); } }) { const empty = (trustError, indexSha256 = "") => ({ pageUrl, appId: "website", capabilities: [], styles: "", stylesUrl: "", stylesHash: "", styleBodies: [], scripts: [], scriptBodies: [], indexSha256, indexBytes: new Uint8Array, stylesBytes: new Uint8Array, trusted: false, trustError }); let page; try { page = new URL(pageUrl); } catch { return empty("invalid page URL"); } if (!["http:", "https:", "file:"].includes(page.protocol)) return empty(`unsupported page protocol: ${page.protocol}`); const index = await readUrl(page.href, MAX_INDEX_BYTES, fetcher); if (!index) return empty(`could not read page: ${page.href}`); const indexSha256 = sha256(index); const manifestUrl = new URL("_wo.capabilities.json", new URL(".", page)).href; const manifestBytes = await readUrl(manifestUrl, 256 * 1024, fetcher); if (!manifestBytes) return empty(`could not read capability manifest: ${manifestUrl}`, indexSha256); let parsed; try { parsed = JSON.parse(new TextDecoder().decode(manifestBytes)); } catch { return empty("capability manifest is not valid JSON", indexSha256); } const manifest = validateWoManifest(parsed, indexSha256, page.href); if (!manifest) return empty("capability manifest does not match this page or contains invalid fields", indexSha256); const cssUrl = new URL(manifest.componentsCss.url, page).href; const css = await loadAsset(cssUrl, manifest.componentsCss.sha256, 1024 * 1024, fetcher); if (!css) return empty(`component stylesheet failed SHA-256 verification: ${cssUrl}`, indexSha256); const scripts = []; const scriptBodies = []; for (const script of manifest.scripts) { const scriptUrl = new URL(script.url, page).href; const bytes = await loadAsset(scriptUrl, script.sha256, MAX_ASSET_BYTES, fetcher); if (!bytes) return empty(`script failed SHA-256 verification: ${scriptUrl}`, indexSha256); scripts.push(scriptUrl); scriptBodies.push({ url: scriptUrl, hash: script.sha256, bytes }); } const styleBodies = []; for (const style of manifest.styles ?? []) { const styleUrl = new URL(style.url, page).href; const bytes = await loadAsset(styleUrl, style.sha256, MAX_ASSET_BYTES, fetcher); if (!bytes) return empty(`stylesheet failed SHA-256 verification: ${styleUrl}`, indexSha256); styleBodies.push({ url: styleUrl, hash: style.sha256, bytes }); } return { pageUrl: page.href, appId: manifest.appId, capabilities: [...new Set(manifest.capabilities)], styles: new TextDecoder().decode(css), stylesUrl: cssUrl, stylesHash: manifest.componentsCss.sha256, styleBodies, scripts, scriptBodies, indexSha256, indexBytes: index, stylesBytes: css, trusted: true }; } // src/frame-pacing.ts function chooseFrameRate(configuredFps, displayFps) { const display = Number.isFinite(displayFps) && (displayFps ?? 0) > 0 ? Math.round(displayFps) : 60; const configured = Number.isFinite(configuredFps) && (configuredFps ?? 0) > 0 ? Math.round(configuredFps) : display; return Math.max(1, Math.min(configured, display)); } function frameIntervalMs(fps) { return 1000 / Math.max(1, fps); } // src/protocol.ts var MAGIC = { HELLO: 1464813644, FRAME: 1464813138, MOUSE_MOVE: 1464814925, MOUSE_BUTTON: 1464814914, KEYBOARD: 1464814402, SCROLL: 1464816451, ACTION: 1464812353, FOCUS_CHANGE: 1464813123, WINDOW_META: 1464817485, WINDOW_POS: 1464817488, SYSCALL: 1464816473, FRAME_ACK: 1464813121, SURFACE_BUFFER: 1464816450, SHM_BUFFER: 1464816461, DMABUF_FRAME: 1464812614, FORWARD_POINTER: 1464815685, FORWARD_KEYBOARD: 1464814405, FORWARD_RELATIVE_POINTER: 1464816197, FORWARD_POINTER_BUTTON: 1464815682, FORWARD_POINTER_SCROLL: 1464815699, FORWARD_POINTER_LEAVE: 1464815694, POINTER_LOCK_REQUEST: 1464815692, ENV_UPDATE: 1464812885, SCREENCOPY_EVENT: 1464816453, WINDOW_SIZE: 1464816218, SITE_HASHES: 1464816456 }; function stringToBuffer(str) { return Buffer.from(str, "utf-8"); } // src/damage-buffer.ts import { writeSync } from "fs"; var ARGB8888 = fourccCode("A", "R", "2", "4"); class DamageBuffer { width; height; stride; frame; constructor(width, height, stride = width * 4) { this.width = width; this.height = height; this.stride = stride; this.frame = Buffer.alloc(this.stride * this.height); } getWidth() { return this.width; } getHeight() { return this.height; } getStride() { return this.stride; } getFrameBuffer() { return this.frame; } reset(width, height, stride = width * 4) { this.width = width; this.height = height; this.stride = stride; this.frame = Buffer.alloc(this.stride * this.height); } applyFullFrame(frame, frameStride = this.width * 4) { if (frameStride === this.stride) { this.frame.set(new Uint8Array(frame.buffer, frame.byteOffset, this.frame.length)); return; } const rowBytes = Math.min(frameStride, this.stride); for (let y = 0;y < this.height; y += 1) { const srcStart = y * frameStride; const dstStart = y * this.stride; this.frame.set(new Uint8Array(frame.buffer, frame.byteOffset + srcStart, rowBytes), dstStart); } } applyPatch(patch) { const rect = clampRect(patch.rect, this.width, this.height); if (rect.width <= 0 || rect.height <= 0) { return; } const patchStride = patch.stride ?? patch.rect.width * 4; const sourceX = rect.x - patch.rect.x; const sourceY = rect.y - patch.rect.y; const dstStartX = rect.x * 4; const rowBytes = rect.width * 4; for (let row = 0;row < rect.height; row += 1) { const srcStart = (sourceY + row) * patchStride + sourceX * 4; const dstStart = (rect.y + row) * this.stride + dstStartX; this.frame.set(new Uint8Array(patch.rgba.buffer, patch.rgba.byteOffset + srcStart, rowBytes), dstStart); } } applyPatches(patches) { for (const patch of patches) { this.applyPatch(patch); } } } class WoDmabufSender { socket; native; seq = 1n; wireBuf; sharedWireBuf; nameBuf; nameLen; seqOffset; widthOffset; heightOffset; pendingReleases = new Map; constructor(socket, windowName, native) { this.socket = socket; this.native = native; const nameBuf = Buffer.from(windowName, "utf8"); this.nameBuf = nameBuf; this.nameLen = nameBuf.length; const totalLen = 32 + this.nameLen + 16; this.wireBuf = Buffer.alloc(totalLen); this.sharedWireBuf = Buffer.alloc(32 + this.nameLen + 4 * 16); let off = 0; this.wireBuf.writeUInt32LE(MAGIC.FRAME, off); off += 4; this.wireBuf.writeUInt32LE(this.nameLen, off); off += 4; nameBuf.copy(this.wireBuf, off); off += this.nameLen; this.seqOffset = off; off += 8; this.widthOffset = off; off += 4; this.heightOffset = off; off += 4; this.wireBuf.writeUInt32LE(ARGB8888, off); off += 4; this.wireBuf.writeUInt32LE(1, off); } send(buffer) { if (buffer.getWidth() <= 0 || buffer.getHeight() <= 0 || buffer.getStride() < buffer.getWidth() * 4 || buffer.getFrameBuffer().byteLength < buffer.getStride() * buffer.getHeight()) { throw new RangeError("Cannot send an incomplete or zero-sized DMA-BUF frame"); } const imported = this.native.importRgba(buffer.getFrameBuffer(), buffer.getWidth(), buffer.getHeight(), buffer.getStride()); let sent = false; const sentSeq = this.seq; try { const socketFd = getSocketFd(this.socket); this.wireBuf.writeBigUInt64LE(this.seq, this.seqOffset); this.wireBuf.writeUInt32LE(buffer.getWidth(), this.widthOffset); this.wireBuf.writeUInt32LE(buffer.getHeight(), this.heightOffset); const planeOff = this.wireBuf.length - 16; this.wireBuf.writeUInt32LE(imported.offset, planeOff); this.wireBuf.writeUInt32LE(imported.stride, planeOff + 4); this.wireBuf.writeUInt32LE(imported.modifier_hi, planeOff + 8); this.wireBuf.writeUInt32LE(imported.modifier_lo, planeOff + 12); writeSync(socketFd, this.wireBuf, 0, this.wireBuf.length); this.native.sendFd(socketFd, imported.fd); this.pendingReleases.set(sentSeq.toString(), () => this.native.releaseBuffer(imported.token)); this.seq += 1n; sent = true; return sentSeq; } finally { if (!sent) this.native.releaseBuffer(imported.token); } } sendSharedTexture(texture, release) { const pixmap = texture.handle?.nativePixmap; const planes = pixmap?.planes; if (!planes || planes.length < 1 || planes.length > 4) { throw new Error("shared texture has no supported DMA-BUF planes"); } const fourcc = texture.pixelFormat === "bgra" ? fourccCode("A", "R", "2", "4") : texture.pixelFormat === "rgba" ? fourccCode("A", "B", "2", "4") : texture.pixelFormat === "nv12" ? fourccCode("N", "V", "1", "2") : 0; if (fourcc === 0) throw new Error(`unsupported shared texture pixel format: ${texture.pixelFormat}`); const width = texture.codedSize.width; const height = texture.codedSize.height; if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) { throw new Error("shared texture has invalid coded dimensions"); } const headerLength = 32 + this.nameLen + planes.length * 16; let off = 0; this.sharedWireBuf.writeUInt32LE(MAGIC.FRAME, off); off += 4; this.sharedWireBuf.writeUInt32LE(this.nameLen, off); off += 4; this.nameBuf.copy(this.sharedWireBuf, off); off += this.nameLen; this.sharedWireBuf.writeBigUInt64LE(this.seq, off); off += 8; this.sharedWireBuf.writeUInt32LE(width, off); off += 4; this.sharedWireBuf.writeUInt32LE(height, off); off += 4; this.sharedWireBuf.writeUInt32LE(fourcc, off); off += 4; this.sharedWireBuf.writeUInt32LE(planes.length, off); off += 4; const modifier = BigInt(pixmap.modifier || "0"); for (const plane of planes) { this.sharedWireBuf.writeUInt32LE(plane.offset >>> 0, off); off += 4; this.sharedWireBuf.writeUInt32LE(plane.stride >>> 0, off); off += 4; this.sharedWireBuf.writeUInt32LE(Number(modifier >> 32n & 0xffffffffn), off); off += 4; this.sharedWireBuf.writeUInt32LE(Number(modifier & 0xffffffffn), off); off += 4; } const socketFd = getSocketFd(this.socket); for (const plane of planes) { if (!Number.isInteger(plane.fd) || plane.fd < 0 || !Number.isInteger(plane.stride) || plane.stride <= 0 || !Number.isInteger(plane.offset) || plane.offset < 0) { throw new Error("shared texture has invalid DMA-BUF plane metadata"); } } writeAllSync(socketFd, this.sharedWireBuf, headerLength); for (const plane of planes) this.native.sendFd(socketFd, plane.fd); const sentSeq = this.seq; this.pendingReleases.set(sentSeq.toString(), release); this.seq += 1n; return sentSeq; } acknowledge(seq) { const key = seq.toString(); const release = this.pendingReleases.get(key); if (!release) return; this.pendingReleases.delete(key); release(); } releaseAll() { for (const release of this.pendingReleases.values()) release(); this.pendingReleases.clear(); } } function writeAllSync(fd, buffer, length) { let offset = 0; while (offset < length) { const written = writeSync(fd, buffer, offset, length - offset); if (written <= 0) throw new Error("IPC socket closed while sending a DMA-BUF frame"); offset += written; } } function applyDamagePayload(buffer, payload) { if (payload.width !== buffer.getWidth() || payload.height !== buffer.getHeight()) { buffer.reset(payload.width, payload.height); } if (payload.fullFrame) { buffer.applyFullFrame(payload.fullFrame, payload.fullStride ?? payload.width * 4); } if (payload.patches && payload.patches.length > 0) { buffer.applyPatches(payload.patches); } } function loadNativeDmabufModule(requireFn, nativePath) { const native = requireFn(nativePath); return native; } function getSocketFd(socket) { const fd = socket?._handle?.fd; if (typeof fd !== "number" || !Number.isFinite(fd) || fd < 0) { throw new Error("Unable to read underlying socket file descriptor"); } return fd; } function clampRect(rect, width, height) { const x = Math.max(0, Math.min(width, rect.x)); const y = Math.max(0, Math.min(height, rect.y)); const right = Math.max(x, Math.min(width, rect.x + rect.width)); const bottom = Math.max(y, Math.min(height, rect.y + rect.height)); return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) }; } function fourccCode(a, b, c, d) { return (a.charCodeAt(0) | b.charCodeAt(0) << 8 | c.charCodeAt(0) << 16 | d.charCodeAt(0) << 24) >>> 0; } // src/main.ts var __filename2 = fileURLToPath2(import.meta.url); var __dirname2 = dirname(__filename2); var require2 = createRequire(import.meta.url); protocol.registerSchemesAsPrivileged([{ scheme: "wo-cache", privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, codeCache: true } }]); var verifiedScriptCache = new Map; var approvedResourceRequests = new Map; var approvedAllResourcePages = new Set; var resourceConsentQueue = Promise.resolve(); var MAX_APPROVED_RESOURCE_BYTES = 16 * 1024 * 1024; function setupWoCacheProtocol() { protocol.handle("wo-cache", (request) => { const url = new URL(request.url); const [, hash = "", kind = "script.js", encodedSourceUrl = ""] = url.pathname.split("/"); const bytes = verifiedScriptCache.get(hash); if (!bytes) { debugLog(`[Wo] Verified-script cache miss: ${url.pathname}`); return new Response("Missing verified Wo script", { status: 404 }); } debugLog(`[Wo] Serving verified script ${hash.slice(0, 12)} (${bytes.byteLength} bytes)`); const isStylesheet = kind === "style.css"; let responseBytes = bytes; if (encodedSourceUrl) { try { const sourceUrl = Buffer.from(encodedSourceUrl, "base64url").toString("utf8"); responseBytes = isStylesheet ? rewriteStylesheetUrls(bytes, sourceUrl) : rewriteModuleImports(bytes, sourceUrl); } catch (error) { debugLog(`[Wo] Could not rewrite cached module imports for ${hash.slice(0, 12)}: ${String(error)}`); } } return new Response(responseBytes, { headers: { "Content-Type": isStylesheet ? "text/css; charset=utf-8" : "text/javascript; charset=utf-8", "Access-Control-Allow-Origin": "*", "Cross-Origin-Resource-Policy": "cross-origin", "Cache-Control": "public, max-age=31536000, immutable" } }); }); } function rewriteStylesheetUrls(bytes, sourceUrl) { const css = bytes.toString("utf8"); const rewritten = css.replace(/url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*?))\s*\)/gi, (whole, doubleQuoted, singleQuoted, bare) => { const value = String(doubleQuoted ?? singleQuoted ?? bare ?? "").trim(); if (!value || /^(?:data:|blob:|#|[a-z][a-z\d+.-]*:|\/\/)/i.test(value)) return whole; try { return `url(${JSON.stringify(new URL(value, sourceUrl).href)})`; } catch { return whole; } }); return Buffer.from(rewritten); } function rewriteModuleImports(bytes, sourceUrl) { const source = bytes.toString("utf8"); const absolute = (specifier) => { try { return new URL(specifier, sourceUrl).href; } catch { return specifier; } }; const tokens = []; const isIdentifierStart = (char) => /[A-Za-z_$]/.test(char); const isIdentifierPart = (char) => /[\w$]/.test(char); let i = 0; let previousToken = ""; while (i < source.length) { const char = source[i]; if (/\s/.test(char)) { i++; continue; } if (char === "/" && source[i + 1] === "/") { i += 2; while (i < source.length && source[i] !== ` `) i++; continue; } if (char === "/" && source[i + 1] === "*") { i += 2; while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i++; i = Math.min(source.length, i + 2); continue; } if (char === "/" && !["", "(", "[", "{", "=", ":", ",", ";", "!", "?", "&&", "||", "=>", "return"].includes(previousToken)) { tokens.push({ kind: "punctuation", value: "/", start: i, end: i + 1 }); previousToken = "/"; i++; continue; } if (char === "/" && ["(", "[", "{", "=", ":", ",", ";", "!", "?", "&&", "||", "=>", "return"].includes(previousToken)) { const start = i++; let inClass = false; while (i < source.length) { if (source[i] === "\\") { i += 2; continue; } if (source[i] === "[") inClass = true; else if (source[i] === "]") inClass = false; else if (source[i] === "/" && !inClass) { i++; break; } else if (source[i] === ` `) break; i++; } while (/[A-Za-z]/.test(source[i] || "")) i++; tokens.push({ kind: "punctuation", value: "regex", start, end: i }); previousToken = "regex"; continue; } if (char === '"' || char === "'") { const start = i++; while (i < source.length) { if (source[i] === "\\") { i += 2; continue; } if (source[i++] === char) break; } const raw = source.slice(start + 1, Math.max(start + 1, i - 1)); tokens.push({ kind: "string", value: raw, start, end: i }); previousToken = "string"; continue; } if (char === "`") { const start = i++; while (i < source.length) { if (source[i] === "\\") { i += 2; continue; } if (source[i++] === "`") break; } tokens.push({ kind: "punctuation", value: "template", start, end: i }); previousToken = "template"; continue; } if (isIdentifierStart(char)) { const start = i++; while (i < source.length && isIdentifierPart(source[i])) i++; const value = source.slice(start, i); tokens.push({ kind: "identifier", value, start, end: i }); previousToken = value; continue; } const start = i++; const value = source.slice(start, i); tokens.push({ kind: "punctuation", value, start, end: i }); previousToken = value; } const edits = []; for (let t = 0;t < tokens.length; t++) { const token = tokens[t]; if (token.kind !== "identifier" || token.value !== "import" && token.value !== "export") continue; const next = tokens[t + 1]; if (token.value === "import" && next?.value === "(") { const specifier = tokens[t + 2]; if (specifier?.kind === "string") { edits.push({ start: specifier.start, end: specifier.end, replacement: JSON.stringify(absolute(specifier.value)) }); } continue; } if (token.value === "import" && next?.kind === "string") { edits.push({ start: next.start, end: next.end, replacement: JSON.stringify(absolute(next.value)) }); continue; } if (token.value === "import" && next?.value === ".") continue; for (let j = t + 1;j < tokens.length && j <= t + 256; j++) { const candidate = tokens[j]; if (candidate.value === ";" || j > t + 1 && candidate.kind === "identifier" && (candidate.value === "import" || candidate.value === "export")) break; if (candidate.kind === "identifier" && candidate.value === "from" && tokens[j + 1]?.kind === "string") { const specifier = tokens[j + 1]; edits.push({ start: specifier.start, end: specifier.end, replacement: JSON.stringify(absolute(specifier.value)) }); break; } } } let rewritten = source; for (const edit of edits.sort((a, b) => b.start - a.start)) { rewritten = rewritten.slice(0, edit.start) + edit.replacement + rewritten.slice(edit.end); } return Buffer.from(rewritten); } function woCacheUrl(hash, resourceUrl, isStylesheet) { const source = Buffer.from(resourceUrl).toString("base64url"); return `wo-cache://cache/${hash}/${isStylesheet ? "style.css" : "script.js"}/${source}`; } function resourceApprovalKey(pageUrl, resourceUrl) { return createHash2("sha256").update(`${pageUrl} ${resourceUrl}`).digest("hex"); } function resourceCacheDirectory() { return joinPath(app.getPath("userData"), "wo-approved-resources"); } async function loadApprovedResource(pageUrl, resourceUrl) { const directory = resourceCacheDirectory(); const key = resourceApprovalKey(pageUrl, resourceUrl); try { const record = JSON.parse(await fs.promises.readFile(joinPath(directory, `${key}.json`), "utf8")); if (record.pageUrl !== pageUrl || record.resourceUrl !== resourceUrl || typeof record.hash !== "string" || !/^[a-f0-9]{64}$/.test(record.hash)) return null; const bytes = await fs.promises.readFile(joinPath(directory, `${record.hash}.bin`)); const actualHash = createHash2("sha256").update(bytes).digest("hex"); if (actualHash !== record.hash || bytes.byteLength > MAX_APPROVED_RESOURCE_BYTES) return null; verifiedScriptCache.set(actualHash, bytes); debugLog(`[Wo] Loaded user-approved resource cache ${actualHash.slice(0, 12)} for ${resourceUrl}`); return actualHash; } catch { return null; } } async function fetchApprovedResourceBytes(resourceUrl) { const parsed = new URL(resourceUrl); let bytes; if (parsed.protocol === "file:") { const path = fileURLToPath2(parsed); const fileStat = await fs.promises.stat(path); if (fileStat.size > MAX_APPROVED_RESOURCE_BYTES) throw new Error("resource exceeds the 16 MiB approval cache limit"); bytes = await fs.promises.readFile(path); } else if (parsed.protocol === "http:" || parsed.protocol === "https:") { let response; try { response = await fetch(resourceUrl, { redirect: "error" }); } catch (error) { if (parsed.protocol !== "http:" || parsed.hostname !== "localhost") throw error; parsed.hostname = "127.0.0.1"; response = await fetch(parsed.href, { redirect: "error" }); } if (!response.ok) throw new Error(`request returned HTTP ${response.status}`); const contentLength = Number(response.headers.get("content-length") || 0); if (contentLength > MAX_APPROVED_RESOURCE_BYTES) throw new Error("resource exceeds the 16 MiB approval cache limit"); if (!response.body) throw new Error("resource response has no body"); const reader = response.body.getReader(); const chunks = []; let totalBytes = 0; while (true) { const { done, value } = await reader.read(); if (done) break; totalBytes += value.byteLength; if (totalBytes > MAX_APPROVED_RESOURCE_BYTES) { await reader.cancel(); throw new Error("resource exceeds the 16 MiB approval cache limit"); } chunks.push(Buffer.from(value)); } bytes = Buffer.concat(chunks, totalBytes); } else { throw new Error(`unsupported resource protocol: ${parsed.protocol}`); } if (bytes.byteLength === 0) throw new Error("resource is empty"); if (bytes.byteLength > MAX_APPROVED_RESOURCE_BYTES) throw new Error("resource exceeds the 16 MiB approval cache limit"); return bytes; } async function persistApprovedResource(pageUrl, resourceUrl, hash, bytes) { const directory = resourceCacheDirectory(); const key = resourceApprovalKey(pageUrl, resourceUrl); await fs.promises.mkdir(directory, { recursive: true, mode: 448 }); const bytesPath = joinPath(directory, `${hash}.bin`); try { await fs.promises.access(bytesPath); } catch { await fs.promises.writeFile(bytesPath, bytes, { mode: 384, flag: "wx" }).catch((error) => { if (error.code !== "EEXIST") throw error; }); } const recordPath = joinPath(directory, `${key}.json`); const temporaryPath = `${recordPath}.${randomUUID()}.tmp`; await fs.promises.writeFile(temporaryPath, JSON.stringify({ pageUrl, resourceUrl, hash }), { mode: 384 }); await fs.promises.rename(temporaryPath, recordPath); } function promptForResourceApproval(pageUrl, resourceUrl, resourceType, portalStyles) { const prompt = resourceConsentQueue.then(async () => { if (approvedAllResourcePages.has(pageUrl)) return "allow"; debugLog(`[Wo] Waiting for user approval to fetch and cache: ${resourceUrl}`); const decision = await showResourceConsentOverlay(pageUrl, resourceUrl, resourceType, portalStyles); if (decision === "allow-all") approvedAllResourcePages.add(pageUrl); debugLog(`[Wo] User ${decision} resource: ${resourceUrl}`); return decision; }); resourceConsentQueue = prompt.then(() => { return; }, () => { return; }); return prompt; } async function showResourceConsentOverlay(pageUrl, resourceUrl, resourceType, portalStyles) { const targetWindow = mainWindow; if (!targetWindow || targetWindow.isDestroyed()) return "deny"; if (!mainPageLoadFinished && !resourceConsentUiActive) { await new Promise((resolve) => { const ready = () => { clearTimeout(timeout); targetWindow.webContents.removeListener("did-finish-load", ready); resolve(); }; const timeout = setTimeout(ready, 5000); targetWindow.webContents.once("did-finish-load", ready); }); } if (targetWindow.isDestroyed()) return "deny"; const requestId = randomUUID(); const token = randomUUID(); const payload = JSON.stringify({ requestId, token, pageUrl, resourceUrl, resourceType }).replace(/Wo security portal

Allow this page to load a resource?

This resource is not covered by the page's verified Wo manifest.

Resource type
Requested URL
Page

Approve all allows this page's future script, worker, and stylesheet requests for this session.

`; return new Promise((resolve) => { resourceApprovalPending = { requestId, token, resolve }; resourceConsentUiActive = true; targetWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}#wo-resource-consent`).then(() => { debugLog(`[Wo] Displayed resource approval portal in the compositor root surface: ${resourceUrl}`); }).catch((error) => { if (resourceApprovalPending?.requestId === requestId) resourceApprovalPending = null; resourceConsentUiActive = false; debugLog(`[Wo] Could not load resource approval portal: ${String(error)}`); resolve("deny"); if (!targetWindow.isDestroyed()) { targetWindow.loadURL(pageUrl).catch((restoreError) => { debugLog(`[Wo] Could not restore page after approval UI failure: ${String(restoreError)}`); }); } }); }); } function handleResourceConsentNavigation(event, targetUrl) { if (!targetUrl.startsWith("wo-consent://")) return; event.preventDefault(); try { const decision = new URL(targetUrl); if (decision.hostname !== "decision") return; const action = decision.searchParams.get("action"); resolveResourceConsent(decision.pathname.slice(1), decision.searchParams.get("token") || "", action === "allow-all" ? "allow-all" : action === "allow" ? "allow" : "deny"); } catch {} } function resolveResourceConsent(requestId, token, decision) { const pending = resourceApprovalPending; if (!resourceConsentUiActive || !pending || pending.requestId !== requestId || pending.token !== token) return false; resourceApprovalPending = null; pending.resolve(decision); return true; } async function approveAndCacheResource(pageUrl, resourceUrl, resourceType, portalStyles) { const requestKey = resourceApprovalKey(pageUrl, resourceUrl); const pending = approvedResourceRequests.get(requestKey); if (pending) return pending; const operation = (async () => { const cachedHash = await loadApprovedResource(pageUrl, resourceUrl); if (cachedHash) return cachedHash; if (await promptForResourceApproval(pageUrl, resourceUrl, resourceType, portalStyles) === "deny") return null; const bytes = await fetchApprovedResourceBytes(resourceUrl); const hash = createHash2("sha256").update(bytes).digest("hex"); await persistApprovedResource(pageUrl, resourceUrl, hash, bytes); verifiedScriptCache.set(hash, bytes); debugLog(`[Wo] Cached user-approved resource ${hash.slice(0, 12)} (${bytes.byteLength} bytes): ${resourceUrl}`); return hash; })(); approvedResourceRequests.set(requestKey, operation); try { return await operation; } finally { approvedResourceRequests.delete(requestKey); } } var DEBUG_LOG = "/tmp/wo-electron-debug.log"; var _logBuffer = []; var _logTimer = null; function _flushLog() { if (_logBuffer.length === 0) return; try { fs.appendFileSync(DEBUG_LOG, _logBuffer.join("")); } catch (e) {} _logBuffer = []; _logTimer = null; } function debugLog(...args) { const msg = `[${new Date().toISOString()}] ` + args.join(" ") + ` `; _logBuffer.push(msg); if (!_logTimer) { _logTimer = setTimeout(_flushLog, 100); } console.log(...args); } debugLog("[Wo] Electron starting, NODE_VERSION=", process.version); var requestedOzonePlatform = (process.env.WO_ELECTRON_OZONE_PLATFORM || "auto").toLowerCase(); var defaultGpuBackend = "angle"; var requestedGpuBackend = (process.env.WO_ELECTRON_GPU_BACKEND || defaultGpuBackend).toLowerCase(); var gpuBackend = ["vulkan", "gl", "angle", "desktop", "auto"].includes(requestedGpuBackend) ? requestedGpuBackend : defaultGpuBackend; if (gpuBackend === "desktop") { app.commandLine.appendSwitch("use-gl", "desktop"); } else if (gpuBackend === "angle") { app.commandLine.appendSwitch("use-gl", "angle"); app.commandLine.appendSwitch("use-angle", "gl"); } else if (gpuBackend !== "auto") { app.commandLine.appendSwitch("use-gl", "angle"); app.commandLine.appendSwitch("use-angle", gpuBackend); } if (requestedOzonePlatform === "wayland" || requestedOzonePlatform === "x11") { app.commandLine.appendSwitch("ozone-platform", requestedOzonePlatform); } app.commandLine.appendSwitch("ignore-gpu-blocklist"); var chromiumFeatures = [ "VaapiVideoDecoder", "VaapiVideoEncoder", "SharedArrayBuffer" ]; if (gpuBackend === "vulkan") { chromiumFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE"); } else if (requestedOzonePlatform === "wayland" || gpuBackend !== "auto") { app.commandLine.appendSwitch("disable-vulkan"); app.commandLine.appendSwitch("disable-features", "Vulkan"); } app.commandLine.appendSwitch("enable-features", chromiumFeatures.join(",")); app.commandLine.appendSwitch("enable-accelerated-video-decode"); app.commandLine.appendSwitch("num-raster-threads", "4"); app.commandLine.appendSwitch("disable-renderer-backgrounding"); debugLog(`[Wo] GPU backend preference: ${gpuBackend}; Ozone platform: ${requestedOzonePlatform}`); function logGraphicsRuntime() { const relevantEnv = [ "WAYLAND_DISPLAY", "DISPLAY", "XDG_SESSION_TYPE", "XDG_RUNTIME_DIR", "EGL_PLATFORM", "__EGL_VENDOR_LIBRARY_FILENAMES", "__GLX_VENDOR_LIBRARY_NAME", "GBM_BACKENDS_PATH", "LIBGL_DRIVERS_PATH", "MESA_LOADER_DRIVER_OVERRIDE", "DRI_PRIME", "NVIDIA_VISIBLE_DEVICES" ]; const env = Object.fromEntries(relevantEnv.filter((key) => process.env[key] !== undefined).map((key) => [key, process.env[key]])); const renderNode = process.env.WO_DRM_RENDER_NODE || "/dev/dri/renderD128"; let renderNodeAccess; try { const stat = fs.statSync(renderNode); fs.accessSync(renderNode, fs.constants.R_OK | fs.constants.W_OK); renderNodeAccess = { path: renderNode, mode: (stat.mode & 511).toString(8), readable: true, writable: true }; } catch (error) { renderNodeAccess = { path: renderNode, error: String(error) }; } const switches = ["ozone-platform", "use-gl", "use-angle", "disable-gpu", "disable-vulkan", "disable-features"].map((name) => ({ name, present: app.commandLine.hasSwitch(name), value: app.commandLine.getSwitchValue(name) })); debugLog("[Wo] Graphics process context:", JSON.stringify({ uid: typeof process.getuid === "function" ? process.getuid() : null, groups: typeof process.getgroups === "function" ? process.getgroups() : [], env, renderNodeAccess, switches })); } logGraphicsRuntime(); var IPC_SOCKET = process.env.WO_IPC_SOCKET || "/run/user/1000/wo-ipc.sock"; debugLog("[Wo] WO_WINDOW_CONFIG env =", process.env.WO_WINDOW_CONFIG); var WINDOW_CONFIG = JSON.parse(process.env.WO_WINDOW_CONFIG || "{}"); debugLog("[Wo] WINDOW_CONFIG parsed =", JSON.stringify(WINDOW_CONFIG)); var CLIENT_MODE = process.env.WO_CLIENT_MODE === "1"; var SYSTEM_SHELL = process.env.WO_SYSTEM_SHELL === "1"; function isLoopbackHttp(url) { try { const parsed = new URL(url); return parsed.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]", "::1"].includes(parsed.hostname); } catch { return false; } } var { name = "default", url = null, html = null, width = 1920, height = 1080 } = WINDOW_CONFIG; var initialWindowWidth = Number.isInteger(width) && width > 0 ? Math.min(width, 16384) : 1920; var initialWindowHeight = Number.isInteger(height) && height > 0 ? Math.min(height, 16384) : 1080; var mainWindow = null; var resourceConsentUiActive = false; var mainPageLoadFinished = false; var resourceApprovalPending = null; var displayScreen = null; var apiGrants = new Set; var apiGrantPageUrl = null; var lockedScriptUrls = new Map; var framePacer = null; var framePacerRate = 0; var gpuCompositingStatus = "pending"; var webglStatus = "pending"; var sharedDmabufStatus = null; var offscreenSharedTextureEnabled = false; var ipcSocket = null; var portalUiServer = null; var portalPendingRequests = new Map; var portalUiEvents = new Map; var portalUiRendererReady = false; var portalUiRendererGeneration = 0; var PORTAL_UI_SOCKET = joinPath(process.env.XDG_RUNTIME_DIR || "/tmp", "wo-portal-ui.sock"); var ipcConnected = false; var activeSiteHashes = null; var ipcReconnectTimer = null; var dmabufSender = null; var damageBuffer = null; var nativeDmabuf = null; var audioCapture = null; var compositorRxBuffer = Buffer.alloc(64 * 1024); var rxWriteOffset = 0; var rxReadOffset = 0; var surfaceSabCache = new Map; var surfaceUpdatePending = new Map; var surfaceUpdateGeneration = 0; var surfaceUpdateFlushScheduled = false; var shmFdCache = new Map; function isDisabledGpuFeature(status) { return status.startsWith("disabled") || status.startsWith("unavailable") || status.startsWith("failed"); } function getPerformanceStatus() { const reasons = []; if (isDisabledGpuFeature(gpuCompositingStatus)) reasons.push("GPU compositing is disabled"); if (isDisabledGpuFeature(webglStatus)) reasons.push("WebGL is unavailable"); if (sharedDmabufStatus === false) reasons.push("Electron is using bitmap frames instead of shared GPU DMA-BUFs"); return { limited: reasons.length > 0, gpuCompositing: gpuCompositingStatus, webgl: webglStatus, sharedDmabuf: sharedDmabufStatus, reasons }; } function publishPerformanceStatus() { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:performance-status", getPerformanceStatus()); } } function rxAppend(chunk) { const needed = rxWriteOffset + chunk.length; if (needed > compositorRxBuffer.length) { if (rxReadOffset > 0) { compositorRxBuffer.copy(compositorRxBuffer, 0, rxReadOffset, rxWriteOffset); rxWriteOffset -= rxReadOffset; rxReadOffset = 0; } if (rxWriteOffset + chunk.length > compositorRxBuffer.length) { const next = Buffer.alloc(Math.max(compositorRxBuffer.length * 2, rxWriteOffset + chunk.length)); compositorRxBuffer.copy(next, 0, 0, rxWriteOffset); compositorRxBuffer = next; } } chunk.copy(compositorRxBuffer, rxWriteOffset); rxWriteOffset += chunk.length; } function rxAvailable() { return rxWriteOffset - rxReadOffset; } function rxConsume(n) { rxReadOffset += n; if (rxReadOffset > compositorRxBuffer.length / 2) { compositorRxBuffer.copy(compositorRxBuffer, 0, rxReadOffset, rxWriteOffset); rxWriteOffset -= rxReadOffset; rxReadOffset = 0; } } function closeShmFdCache() { for (const entry of shmFdCache.values()) { try { fs.closeSync(entry.extFd); } catch {} } shmFdCache.clear(); } function getOrOpenShmFd(windowName, pid, fd) { const cached = shmFdCache.get(windowName); if (cached && cached.pid === pid && cached.fd === fd) { return cached.extFd; } if (cached) { try { fs.closeSync(cached.extFd); } catch {} } const fdPath = `/proc/${pid}/fd/${fd}`; const extFd = fs.openSync(fdPath, "r"); shmFdCache.set(windowName, { pid, fd, extFd }); return extFd; } function getOrCreateSab(windowName, width, height, stride) { const existing = surfaceSabCache.get(windowName); const neededSize = stride * height; if (existing && existing.sab.byteLength >= neededSize && existing.width === width && existing.height === height && existing.stride === stride) { return existing; } const sab = new SharedArrayBuffer(neededSize); const entry = { sab, width, height, stride }; surfaceSabCache.set(windowName, entry); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:surface-sab", { name: windowName, sab, width, height, stride }); } return entry; } function flushSurfaceUpdates() { surfaceUpdateFlushScheduled = false; if (!mainWindow || mainWindow.isDestroyed()) { surfaceUpdatePending.clear(); return; } for (const entry of surfaceUpdatePending.values()) { mainWindow.webContents.send("wo:surface-update", entry); } surfaceUpdatePending.clear(); } var inFlightFrameSeqs = new Set; var MAX_IN_FLIGHT_FRAMES = 2; var receivedSocketFds = []; var fdReaderSocketFd = null; function displayRefreshForWindow() { if (!displayScreen) return 60; const bounds = mainWindow && !mainWindow.isDestroyed() ? mainWindow.getBounds() : null; const display = bounds ? displayScreen.getDisplayMatching(bounds) : displayScreen.getPrimaryDisplay(); return display.displayFrequency; } function configureFramePacing() { if (CLIENT_MODE || !mainWindow || mainWindow.isDestroyed()) return; const rate = chooseFrameRate(Number(WINDOW_CONFIG.fps), displayRefreshForWindow()); if (rate === framePacerRate && framePacer) return; framePacerRate = rate; if (mainWindow.webContents.isOffscreen() && !mainWindow.webContents.isPainting()) { mainWindow.webContents.startPainting(); } mainWindow.webContents.setFrameRate(rate); if (framePacer) clearInterval(framePacer); framePacer = setInterval(() => { if (mainWindow && !mainWindow.isDestroyed() && inFlightFrameSeqs.size < MAX_IN_FLIGHT_FRAMES) { mainWindow.webContents.invalidate(); } }, frameIntervalMs(rate)); framePacer.unref(); debugLog(`[Wo] Offscreen frame pacing: ${rate} Hz (window cap ${WINDOW_CONFIG.fps || "auto"})`); } function senderHasCapability(sender, capability) { if (!mainWindow || mainWindow.isDestroyed() || sender.id !== mainWindow.webContents.id || !apiGrants.has(capability) || !apiGrantPageUrl) return false; try { const approved = new URL(apiGrantPageUrl); const current = new URL(sender.getURL()); approved.hash = ""; current.hash = ""; return approved.href === current.href; } catch { return false; } } function syscallCapability(type) { if (["list_applications", "launch_desktop"].includes(type)) return "desktop-applications"; if (["xdg_directories", "resolve_icon"].includes(type)) return "xdg"; if (type.startsWith("dbus_")) return "dbus"; if (type === "portal_respond") return "portals"; if (type.startsWith("audio_")) return "audio-capture"; if (["listdir", "read", "write"].includes(type)) return "filesystem"; if (["launch", "exec"].includes(type)) return "process-launch"; if (["shutdown", "restart", "logout", "lock", "sleep"].includes(type)) return "system-control"; if (type === "notify") return "notifications"; if (type === "load_local_file") return "filesystem"; return null; } function permissionDenied(capability) { return { ok: false, error: `Wo capability not granted: ${capability}` }; } var appUsedDamageHelper = false; var pointerX = 0; var pointerY = 0; var encodedWindowNames = new Map; function getEncodedWindowName(windowName) { let encoded = encodedWindowNames.get(windowName); if (!encoded) { encoded = Buffer.from(windowName, "utf8"); encodedWindowNames.set(windowName, encoded); } return encoded; } function respondToPortalRequest(requestId, params) { const pending = portalPendingRequests.get(requestId); if (!pending) { return { ok: false, error: `No pending portal request: ${requestId}` }; } const responsePayload = params.response !== undefined ? params.response : { allowed: Boolean(params.allowed), sourceType: params.type === "window" ? "Window" : "Monitor", windowName: typeof params.windowName === "string" ? params.windowName : null }; if (responsePayload === undefined) { portalPendingRequests.delete(requestId); return { ok: false, error: "Missing portal response payload" }; } try { pending.socket.write(`${JSON.stringify(responsePayload)} `); pending.socket.end(); portalPendingRequests.delete(requestId); portalUiEvents.delete(requestId); return { ok: true }; } catch (error) { portalPendingRequests.delete(requestId); portalUiEvents.delete(requestId); return { ok: false, error: `Failed to send portal response: ${String(error)}` }; } } function startPortalUiBridge() { try { if (fs.existsSync(PORTAL_UI_SOCKET)) { fs.unlinkSync(PORTAL_UI_SOCKET); } } catch {} portalUiServer = createServer((socket) => { let received = ""; socket.on("data", (chunk) => { received += chunk.toString("utf8"); let nl = received.indexOf(` `); while (nl >= 0) { const line = received.slice(0, nl).trim(); received = received.slice(nl + 1); if (line.length > 0) { try { const req = JSON.parse(line); const requestId = String(req?.requestId ?? req?.sessionId ?? req?.id ?? randomUUID()); portalPendingRequests.set(requestId, { socket, requestId, payload: req }); socket.setTimeout(90000, () => { const pending = portalPendingRequests.get(requestId); if (pending) { portalPendingRequests.delete(requestId); portalUiEvents.delete(requestId); pending.socket.end(JSON.stringify({ error: "timeout", requestId }) + ` `); } }); portalUiEvents.set(requestId, { data: { requestId, payload: req, kind: typeof req?.type === "string" ? req.type : typeof req?.kind === "string" ? req.kind : undefined }, sentGeneration: -1 }); flushPortalUiEvents(); } catch { socket.end(`{"error":"invalid_json"} `); } } nl = received.indexOf(` `); } }); socket.on("close", () => { for (const [requestId, pending] of portalPendingRequests.entries()) { if (pending.socket === socket) { portalPendingRequests.delete(requestId); portalUiEvents.delete(requestId); } } }); socket.on("error", () => {}); }); portalUiServer.listen(PORTAL_UI_SOCKET, () => { try { fs.chmodSync(PORTAL_UI_SOCKET, 384); } catch (err) { console.error("[Wo] Could not restrict portal socket permissions:", err); } debugLog("[Wo] Portal UI bridge listening at", PORTAL_UI_SOCKET); }); portalUiServer.on("error", (err) => { console.error("[Wo] Portal UI bridge error:", err); }); } function stopPortalUiBridge() { for (const pending of portalPendingRequests.values()) { try { pending.socket.end(JSON.stringify({ error: "shutdown", requestId: pending.requestId }) + ` `); } catch {} } portalPendingRequests.clear(); portalUiEvents.clear(); portalUiRendererReady = false; if (portalUiServer) { portalUiServer.close(); portalUiServer = null; } try { if (fs.existsSync(PORTAL_UI_SOCKET)) { fs.unlinkSync(PORTAL_UI_SOCKET); } } catch {} } function parseClientWindows() { try { const raw = process.env.WO_CLIENT_WINDOWS; if (raw) { const parsed = JSON.parse(raw); if (Array.isArray(parsed) && parsed.length > 0) { return parsed.map((w, idx) => ({ id: String(w.id ?? w.name ?? `win-${idx + 1}`), title: String(w.title ?? w.name ?? `Window ${idx + 1}`), width: Number(w.width ?? 960), height: Number(w.height ?? 540) })); } } } catch (error) { console.warn("[Wo client] invalid WO_CLIENT_WINDOWS:", error); } return [{ id: String(name), title: String(name), width: Number(width), height: Number(height) }]; } var clientWindows = parseClientWindows(); var compositorWindows = []; function runBusctl(bus, args) { try { const { spawnSync } = require2("child_process"); const res = spawnSync("busctl", [bus === "system" ? "--system" : "--user", ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); return { ok: res.status === 0, stdout: res.stdout?.trim?.() || "", stderr: res.stderr?.trim?.() || "", exitCode: res.status ?? -1 }; } catch (error) { return { ok: false, stdout: "", stderr: String(error), exitCode: -1 }; } } function listPipeWireAudioSources() { const { spawnSync } = require2("node:child_process"); const result = spawnSync("pw-dump", [], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); if (result.status !== 0) { return { ok: false, sources: [], error: String(result.stderr || "Could not read PipeWire sources").trim() }; } try { const objects = JSON.parse(result.stdout); const sources = objects.flatMap((object) => { const props = object.info?.props; if (!object.id || object.type !== "PipeWire:Interface:Node" || !props) return []; const mediaClass = String(props["media.class"] || ""); if (!mediaClass.startsWith("Audio/Source")) return []; const name = String(props["node.name"] || `pipewire-${object.id}`); const description = String(props["node.description"] || name); return [{ id: String(object.id), name, description, monitor: String(props["device.class"] || "") === "monitor" || name.endsWith(".monitor") || description.toLowerCase().startsWith("monitor of ") }]; }); return { ok: true, sources }; } catch (error) { return { ok: false, sources: [], error: `Could not parse PipeWire source list: ${String(error)}` }; } } function broadcastAudioState(state) { if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("wo:audio-state", state); } function startPipeWireAudioCapture(source) { if (audioCapture) audioCapture.kill(); const { spawn } = require2("node:child_process"); const child = spawn("pw-cat", [ "--record", "--target", source.id, "--format", "s16", "--rate", "48000", "--channels", "2", "-" ], { stdio: ["ignore", "pipe", "pipe"], env: process.env }); audioCapture = child; child.stdout?.on("data", (data) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:audio-data", { format: "s16le", sampleRate: 48000, channels: 2, data, timestamp: Date.now() }); } }); child.stderr?.on("data", (data) => debugLog("[Wo] pw-cat:", data.toString().trim())); child.once("close", (code) => { if (audioCapture === child) { audioCapture = null; broadcastAudioState({ active: false, ...code && code !== 0 ? { error: `pw-cat exited (${code})` } : {} }); } }); return new Promise((resolve) => { child.once("error", (error) => { if (audioCapture === child) audioCapture = null; const message = error.message; broadcastAudioState({ active: false, error: message }); resolve({ ok: false, error: message }); }); child.once("spawn", () => { broadcastAudioState({ active: true }); resolve({ ok: true, source }); }); }); } function scheduleIpcReconnect(delayMs = 1000) { if (CLIENT_MODE || process.env.WO_STANDALONE === "1") { return; } if (ipcReconnectTimer) { return; } ipcReconnectTimer = setTimeout(() => { ipcReconnectTimer = null; connectToCompositor().then(() => { debugLog("[Wo] IPC reconnect succeeded"); }).catch((error) => { debugLog("[Wo] IPC reconnect failed:", String(error)); scheduleIpcReconnect(); }); }, delayMs); } function sendActionToCompositor(action, payload) { if (!ipcSocket || !ipcConnected) { return false; } try { const actionBuf = stringToBuffer(action); const payloadStr = payload ? JSON.stringify(payload) : ""; const payloadBuf = stringToBuffer(payloadStr); const messageBuf = Buffer.alloc(12 + actionBuf.length + payloadBuf.length); let offset = 0; messageBuf.writeUInt32LE(MAGIC.ACTION, offset); offset += 4; messageBuf.writeUInt32LE(actionBuf.length, offset); offset += 4; actionBuf.copy(messageBuf, offset); offset += actionBuf.length; messageBuf.writeUInt32LE(payloadBuf.length, offset); offset += 4; payloadBuf.copy(messageBuf, offset); ipcSocket.write(messageBuf); return true; } catch { return false; } } function sendForwardedPointer(windowName, x, y) { if (!ipcSocket || !ipcConnected) return; const nameBuf = getEncodedWindowName(windowName); const msg = Buffer.allocUnsafe(8 + nameBuf.length + 16); let off = 0; msg.writeUInt32LE(MAGIC.FORWARD_POINTER, off); off += 4; msg.writeUInt32LE(nameBuf.length, off); off += 4; nameBuf.copy(msg, off); off += nameBuf.length; msg.writeDoubleLE(x, off); off += 8; msg.writeDoubleLE(y, off); ipcSocket.write(msg); } function sendForwardedPointerButton(windowName, x, y, button, pressed, time) { if (!ipcSocket || !ipcConnected) return; const nameBuf = getEncodedWindowName(windowName); const msg = Buffer.allocUnsafe(8 + nameBuf.length + 28); let off = 0; msg.writeUInt32LE(MAGIC.FORWARD_POINTER_BUTTON, off); off += 4; msg.writeUInt32LE(nameBuf.length, off); off += 4; nameBuf.copy(msg, off); off += nameBuf.length; msg.writeDoubleLE(x, off); off += 8; msg.writeDoubleLE(y, off); off += 8; msg.writeUInt32LE(button >>> 0, off); off += 4; msg.writeUInt32LE(pressed ? 1 : 0, off); off += 4; msg.writeUInt32LE(Math.trunc(time) >>> 0, off); ipcSocket.write(msg); } function sendForwardedPointerScroll(windowName, x, y, dx, dy, time) { if (!ipcSocket || !ipcConnected) return; const nameBuf = getEncodedWindowName(windowName); const msg = Buffer.allocUnsafe(8 + nameBuf.length + 36); let off = 0; msg.writeUInt32LE(MAGIC.FORWARD_POINTER_SCROLL, off); off += 4; msg.writeUInt32LE(nameBuf.length, off); off += 4; nameBuf.copy(msg, off); off += nameBuf.length; msg.writeDoubleLE(x, off); off += 8; msg.writeDoubleLE(y, off); off += 8; msg.writeDoubleLE(dx, off); off += 8; msg.writeDoubleLE(dy, off); off += 8; msg.writeUInt32LE(Math.trunc(time) >>> 0, off); ipcSocket.write(msg); } function linuxButtonToElectron(btn) { if (btn === 273) return "right"; if (btn === 274) return "middle"; return "left"; } var modifierState = { shift: false, control: false, alt: false, meta: false }; var MODIFIER_KEYCODES = { 29: "control", 42: "shift", 54: "shift", 56: "alt", 97: "control", 100: "alt", 125: "meta", 126: "meta" }; function getModifierArray() { const mods = []; if (modifierState.shift) mods.push("shift"); if (modifierState.control) mods.push("control"); if (modifierState.alt) mods.push("alt"); if (modifierState.meta) mods.push("meta"); return mods; } var SHIFTED_CHARS = { "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", "[": "{", "]": "}", "\\": "|", ";": ":", "'": '"', "`": "~", ",": "<", ".": ">", "/": "?" }; var LINUX_KEYCODE_MAP = { 1: "Escape", 59: "F1", 60: "F2", 61: "F3", 62: "F4", 63: "F5", 64: "F6", 65: "F7", 66: "F8", 67: "F9", 68: "F10", 87: "F11", 88: "F12", 41: "`", 2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0", 12: "-", 13: "=", 14: "Backspace", 15: "Tab", 16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p", 26: "[", 27: "]", 43: "\\", 58: "CapsLock", 30: "a", 31: "s", 32: "d", 33: "f", 34: "g", 35: "h", 36: "j", 37: "k", 38: "l", 39: ";", 40: "'", 28: "Return", 42: "Shift", 44: "z", 45: "x", 46: "c", 47: "v", 48: "b", 49: "n", 50: "m", 51: ",", 52: ".", 53: "/", 54: "Shift", 29: "Control", 125: "Meta", 56: "Alt", 57: "Space", 100: "Alt", 126: "Meta", 97: "Control", 110: "Insert", 102: "Home", 104: "PageUp", 111: "Delete", 107: "End", 109: "PageDown", 103: "Up", 105: "Left", 108: "Down", 106: "Right", 69: "NumLock", 98: "numdiv", 55: "nummult", 74: "numsub", 71: "num7", 72: "num8", 73: "num9", 78: "numadd", 75: "num4", 76: "num5", 77: "num6", 79: "num1", 80: "num2", 81: "num3", 96: "Enter", 82: "num0", 83: "numdec", 99: "PrintScreen", 70: "ScrollLock", 119: "Pause", 113: "VolumeMute", 114: "VolumeDown", 115: "VolumeUp", 163: "MediaNextTrack", 165: "MediaPreviousTrack", 164: "MediaPlayPause", 166: "MediaStop" }; function connectToCompositor() { return new Promise((resolve, reject) => { if (ipcReconnectTimer) { clearTimeout(ipcReconnectTimer); ipcReconnectTimer = null; } if (ipcSocket && !ipcSocket.destroyed) { ipcSocket.destroy(); } debugLog("[Wo] Connecting to compositor IPC:", IPC_SOCKET); ipcSocket = createConnection(IPC_SOCKET, () => { ipcSocket.setNoDelay(true); const windowName = name; const nameBuf = stringToBuffer(windowName); const messageBuf = Buffer.alloc(8 + nameBuf.length + 8); let offset = 0; messageBuf.writeUInt32LE(MAGIC.HELLO, offset); offset += 4; messageBuf.writeUInt32LE(nameBuf.length, offset); offset += 4; nameBuf.copy(messageBuf, offset); offset += nameBuf.length; messageBuf.writeUInt32LE(initialWindowWidth, offset); offset += 4; messageBuf.writeUInt32LE(initialWindowHeight, offset); ipcSocket.write(messageBuf); ipcConnected = true; debugLog("[Wo] IPC connected, sent HELLO message"); sendSiteHashesToCompositor(); try { const nativePath = resolvePath(__dirname2, "../native/build/Release/wo_dmabuf.node"); nativeDmabuf = loadNativeDmabufModule(require2, nativePath); const renderNode = process.env.WO_DRM_RENDER_NODE || "/dev/dri/renderD128"; debugLog("[Wo] Initializing GBM on render node:", renderNode); nativeDmabuf.init(renderNode); dmabufSender = new WoDmabufSender(ipcSocket, name, nativeDmabuf); const socketFd = ipcSocket?._handle?.fd; if (typeof socketFd === "number" && socketFd >= 0 && nativeDmabuf.startFdReader) { ipcSocket.pause(); fdReaderSocketFd = socketFd; nativeDmabuf.startFdReader(socketFd, (chunk, fds, closed) => { receivedSocketFds.push(...fds); if (chunk.length > 0) onIpcData(chunk); if (closed) ipcSocket?.destroy(); }); } else { ipcSocket.on("data", onIpcData); } debugLog("[Wo] DMABUF sender initialized"); } catch (error) { debugLog("[Wo] DMABUF sender unavailable:", error); dmabufSender = null; ipcSocket.on("data", onIpcData); } resolve(); }); let ipcDataCalls = 0; let ipcDataLastLog = Date.now(); let pendingMouseMove = null; let inputFlushTimer = null; const flushInputEvents = () => { inputFlushTimer = null; if (pendingMouseMove && mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: pendingMouseMove.x, y: pendingMouseMove.y }); pendingMouseMove = null; } }; const onIpcData = (chunk) => { ipcDataCalls++; const now = Date.now(); if (now - ipcDataLastLog > 2000) { ipcDataCalls = 0; ipcDataLastLog = now; } const dataT0 = Date.now(); rxAppend(chunk); while (rxAvailable() >= 4) { const magic = compositorRxBuffer.readUInt32LE(rxReadOffset); if (magic === MAGIC.FRAME_ACK) { if (rxAvailable() < 12) break; const seq = compositorRxBuffer.readBigUInt64LE(rxReadOffset + 4); dmabufSender?.acknowledge(seq); const wasBackpressured = inFlightFrameSeqs.size >= MAX_IN_FLIGHT_FRAMES; inFlightFrameSeqs.delete(seq.toString()); rxConsume(12); if (wasBackpressured && mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.invalidate(); } continue; } if (magic === MAGIC.MOUSE_MOVE) { if (rxAvailable() < 20) break; const x = compositorRxBuffer.readDoubleLE(rxReadOffset + 4); const y = compositorRxBuffer.readDoubleLE(rxReadOffset + 12); rxConsume(20); pointerX = Math.round(x); pointerY = Math.round(y); pendingMouseMove = { x: pointerX, y: pointerY }; if (!inputFlushTimer) inputFlushTimer = setImmediate(flushInputEvents); continue; } if (magic === MAGIC.MOUSE_BUTTON) { if (rxAvailable() < 16) break; const button = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); const pressed = compositorRxBuffer.readUInt32LE(rxReadOffset + 8) !== 0; rxConsume(16); if (pendingMouseMove && mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: pendingMouseMove.x, y: pendingMouseMove.y }); pendingMouseMove = null; } if (inputFlushTimer) { clearImmediate(inputFlushTimer); inputFlushTimer = null; } if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.sendInputEvent({ type: pressed ? "mouseDown" : "mouseUp", x: pointerX, y: pointerY, button: linuxButtonToElectron(button), clickCount: 1, modifiers: getModifierArray() }); } continue; } if (magic === MAGIC.KEYBOARD) { if (rxAvailable() < 16) break; const key = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); const pressed = compositorRxBuffer.readUInt32LE(rxReadOffset + 8) !== 0; rxConsume(16); const modKey = MODIFIER_KEYCODES[key]; if (modKey) { modifierState[modKey] = pressed; } const keyCode = LINUX_KEYCODE_MAP[key]; if (keyCode && mainWindow && !mainWindow.isDestroyed()) { const modifiers = getModifierArray(); mainWindow.webContents.sendInputEvent({ type: pressed ? "keyDown" : "keyUp", keyCode, modifiers }); if (pressed && keyCode.length === 1) { let charKey = keyCode; if (modifierState.shift) { if (charKey >= "a" && charKey <= "z") { charKey = charKey.toUpperCase(); } else if (SHIFTED_CHARS[charKey]) { charKey = SHIFTED_CHARS[charKey]; } } mainWindow.webContents.sendInputEvent({ type: "char", keyCode: charKey, modifiers }); } } continue; } if (magic === MAGIC.SCROLL) { if (rxAvailable() < 16) break; const vertical = compositorRxBuffer.readInt32LE(rxReadOffset + 4); const horizontal = compositorRxBuffer.readInt32LE(rxReadOffset + 8); rxConsume(16); if (pendingMouseMove && mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: pendingMouseMove.x, y: pendingMouseMove.y }); pendingMouseMove = null; } if (inputFlushTimer) { clearImmediate(inputFlushTimer); inputFlushTimer = null; } if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.sendInputEvent({ type: "mouseWheel", x: pointerX, y: pointerY, deltaX: horizontal, deltaY: vertical, canScroll: true }); } continue; } if (magic === MAGIC.FOCUS_CHANGE) { if (rxAvailable() < 12) break; const nameLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); if (rxAvailable() < 12 + nameLen) break; const focused = compositorRxBuffer.readUInt32LE(rxReadOffset + 8) !== 0; const windowName = compositorRxBuffer.slice(rxReadOffset + 12, rxReadOffset + 12 + nameLen).toString("utf8"); rxConsume(12 + nameLen); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:focus-change", { window: windowName, focused }); } continue; } if (magic === MAGIC.WINDOW_META) { if (rxAvailable() < 8) break; const payloadLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); if (rxAvailable() < 8 + payloadLen) break; const metadata = compositorRxBuffer.slice(rxReadOffset + 8, rxReadOffset + 8 + payloadLen).toString("utf8"); rxConsume(8 + payloadLen); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:window-metadata", { type: "windowMetadata", metadata }); try { const parsed = JSON.parse(metadata); if (parsed && Array.isArray(parsed.windows)) { const currentNames = new Set(parsed.windows.map((w) => w.name).filter(Boolean)); for (const cachedName of surfaceSabCache.keys()) { if (!currentNames.has(cachedName)) { surfaceSabCache.delete(cachedName); } } for (const cachedName of shmFdCache.keys()) { if (!currentNames.has(cachedName)) { const entry = shmFdCache.get(cachedName); if (entry) { try { fs.closeSync(entry.extFd); } catch {} } shmFdCache.delete(cachedName); } } compositorWindows = parsed.windows; mainWindow.webContents.send("wo:windows", compositorWindows); if (Array.isArray(parsed.monitors)) { mainWindow.webContents.send("wo:monitors", parsed.monitors); } } } catch {} } continue; } if (magic === MAGIC.SHM_BUFFER) { if (rxAvailable() < 8) break; const nameLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); const baseHeader = 8 + nameLen + 20 + 4; if (rxAvailable() < baseHeader) break; let off = rxReadOffset + 8; const windowName = compositorRxBuffer.slice(off, off + nameLen).toString("utf8"); off += nameLen; const sbWidth = compositorRxBuffer.readUInt32LE(off); off += 4; const sbHeight = compositorRxBuffer.readUInt32LE(off); off += 4; const sbStride = compositorRxBuffer.readUInt32LE(off); off += 4; const pid = compositorRxBuffer.readUInt32LE(off); off += 4; const fd = compositorRxBuffer.readUInt32LE(off); off += 4; const numRects = compositorRxBuffer.readUInt32LE(off); off += 4; const fullHeader = baseHeader + numRects * 16; if (rxAvailable() < fullHeader) break; const damageRects = []; for (let i = 0;i < numRects; i++) { damageRects.push({ x: compositorRxBuffer.readUInt32LE(off), y: compositorRxBuffer.readUInt32LE(off + 4), width: compositorRxBuffer.readUInt32LE(off + 8), height: compositorRxBuffer.readUInt32LE(off + 12) }); off += 16; } rxConsume(fullHeader); try { const extFd = getOrOpenShmFd(windowName, pid, fd); const fullSize = sbStride * sbHeight; const sabEntry = getOrCreateSab(windowName, sbWidth, sbHeight, sbStride); const hasUsableRects = damageRects.length > 0 && damageRects.length < 64; if (hasUsableRects && nativeDmabuf?.copyMmapDamageToSab) { let minY = sbHeight, maxY = 0; for (const r of damageRects) { const ry = Math.max(0, r.y); const ryEnd = Math.min(sbHeight, r.y + r.height); if (ry < minY) minY = ry; if (ryEnd > maxY) maxY = ryEnd; } if (minY < maxY) { nativeDmabuf.copyMmapDamageToSab(extFd, sabEntry.sab, sbStride, [ { y: minY, h: maxY - minY } ]); } } else if (nativeDmabuf?.copyMmapToSab) { nativeDmabuf.copyMmapToSab(extFd, sabEntry.sab, fullSize); } else { const tmpBuf = Buffer.alloc(fullSize); let totalRead = 0; while (totalRead < fullSize) { const bytesRead = fs.readSync(extFd, tmpBuf, totalRead, fullSize - totalRead, totalRead); if (bytesRead <= 0) break; totalRead += bytesRead; } new Uint8Array(sabEntry.sab).set(new Uint8Array(tmpBuf.buffer, tmpBuf.byteOffset, tmpBuf.byteLength)); } surfaceUpdatePending.set(windowName, { name: windowName, width: sbWidth, height: sbHeight, stride: sbStride, generation: ++surfaceUpdateGeneration, damageRects: hasUsableRects ? damageRects : undefined }); if (!surfaceUpdateFlushScheduled) { surfaceUpdateFlushScheduled = true; setImmediate(flushSurfaceUpdates); } } catch (err) { const cached = shmFdCache.get(windowName); if (cached) { try { fs.closeSync(cached.extFd); } catch {} shmFdCache.delete(windowName); } debugLog(`[Wo] SHM_BUFFER fs.readSync failed for ${windowName}:`, err); } continue; } if (magic === MAGIC.DMABUF_FRAME) { if (rxAvailable() < 8) break; const nameLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); const numPlanesOffset = 8 + nameLen + 12; if (rxAvailable() < numPlanesOffset + 4) break; let off = rxReadOffset + 8; const dmabufName = compositorRxBuffer.slice(off, off + nameLen).toString("utf8"); off += nameLen; const dmabufW = compositorRxBuffer.readUInt32LE(off); off += 4; const dmabufH = compositorRxBuffer.readUInt32LE(off); off += 4; const dmabufFormat = compositorRxBuffer.readUInt32LE(off); off += 4; const numPlanes = compositorRxBuffer.readUInt32LE(off); off += 4; const planeSectionSize = numPlanes * 24; const totalSize = off - rxReadOffset + planeSectionSize; if (rxAvailable() < totalSize) break; const planes = []; let planeOff = off; for (let i = 0;i < numPlanes; i++) { planeOff += 4; const planeOffset = compositorRxBuffer.readBigUInt64LE(planeOff); planeOff += 8; const planeStride = compositorRxBuffer.readUInt32LE(planeOff); planeOff += 4; const modifier = compositorRxBuffer.readBigUInt64LE(planeOff); planeOff += 8; planes.push({ offset: planeOffset, stride: planeStride, modifier }); } try { if (nativeDmabuf && ipcSocket) { const socketFd = ipcSocket?._handle?.fd; if (typeof socketFd === "number" && socketFd >= 0) { const planeFds = receivedSocketFds.splice(0, numPlanes); const primaryFd = planeFds.find((fd) => fd >= 0); const primaryPlane = planes[0]; const stride = primaryPlane?.stride || dmabufW * 4; const offset = primaryPlane ? Number(primaryPlane.offset) : 0; if (primaryFd !== undefined) { try { const sabEntry = getOrCreateSab(dmabufName, dmabufW, dmabufH, stride); const copySize = offset + stride * dmabufH; nativeDmabuf.copyMmapToSab(primaryFd, sabEntry.sab, copySize, offset); surfaceUpdatePending.set(dmabufName, { name: dmabufName, width: dmabufW, height: dmabufH, stride, generation: ++surfaceUpdateGeneration }); if (!surfaceUpdateFlushScheduled) { surfaceUpdateFlushScheduled = true; setImmediate(flushSurfaceUpdates); } const textureInfo = nativeDmabuf.importDmabufTexture(dmabufName, primaryFd, dmabufW, dmabufH, dmabufFormat); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:dmabuf-frame", { name: dmabufName, texture: textureInfo.texture, width: textureInfo.width, height: textureInfo.height }); } } finally { for (const fd of planeFds) { try { fs.closeSync(fd); } catch {} } } } } } } catch (err) { debugLog(`[Wo] DMABUF import failed for ${dmabufName}:`, err); } rxConsume(totalSize); continue; } if (magic === MAGIC.POINTER_LOCK_REQUEST) { if (rxAvailable() < 12) break; const nameLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); if (rxAvailable() < 12 + nameLen) break; const lock = compositorRxBuffer.readUInt32LE(rxReadOffset + 8) !== 0; const windowName = compositorRxBuffer.slice(rxReadOffset + 12, rxReadOffset + 12 + nameLen).toString("utf8"); rxConsume(12 + nameLen); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:pointer-lock-request", { window: windowName, lock }); } continue; } if (magic === MAGIC.ENV_UPDATE) { if (rxAvailable() < 8) break; const jsonLen = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); if (rxAvailable() < 8 + jsonLen) break; const jsonStr = compositorRxBuffer.slice(rxReadOffset + 8, rxReadOffset + 8 + jsonLen).toString("utf8"); rxConsume(8 + jsonLen); try { const vars = JSON.parse(jsonStr); for (const [key, value] of Object.entries(vars)) { process.env[key] = value; debugLog(`[Wo] env update: ${key}=${value}`); } if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:env-update", vars); } } catch (err) { debugLog("[Wo] ENV_UPDATE parse error:", err); } continue; } if (magic === MAGIC.SCREENCOPY_EVENT) { if (rxAvailable() < 12) break; const active = compositorRxBuffer.readUInt32LE(rxReadOffset + 4) !== 0; const clientCount = compositorRxBuffer.readUInt32LE(rxReadOffset + 8); rxConsume(12); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:screencopy-event", { active, clientCount }); } continue; } if (magic === MAGIC.WINDOW_SIZE) { if (rxAvailable() < 12) break; const width = compositorRxBuffer.readUInt32LE(rxReadOffset + 4); const height = compositorRxBuffer.readUInt32LE(rxReadOffset + 8); rxConsume(12); if (mainWindow && !mainWindow.isDestroyed() && width > 0 && height > 0) { mainWindow.setSize(width, height, false); } continue; } debugLog("[Wo] UNKNOWN magic 0x" + magic.toString(16).padStart(8, "0") + " bufLen=" + rxAvailable() + " — stream may be corrupted"); rxConsume(1); } const dataElapsed = Date.now() - dataT0; if (dataElapsed > 50) { console.warn(`[IPC SLOW] data handler took ${dataElapsed}ms chunkLen=${chunk.length}`); } }; ipcSocket.on("error", (error) => { debugLog("[Wo] IPC connection error:", error); const hadConnected = ipcConnected; ipcConnected = false; dmabufSender = null; if (hadConnected) { scheduleIpcReconnect(); return; } reject(error); }); ipcSocket.on("close", () => { debugLog("[Wo] IPC connection closed"); ipcConnected = false; if (fdReaderSocketFd !== null) { nativeDmabuf?.stopFdReader?.(fdReaderSocketFd); fdReaderSocketFd = null; } for (const fd of receivedSocketFds.splice(0)) { try { fs.closeSync(fd); } catch {} } ipcSocket = null; dmabufSender?.releaseAll(); dmabufSender = null; inFlightFrameSeqs.clear(); compositorRxBuffer = Buffer.alloc(64 * 1024); rxWriteOffset = 0; rxReadOffset = 0; surfaceSabCache.clear(); surfaceUpdatePending.clear(); closeShmFdCache(); scheduleIpcReconnect(); }); setTimeout(() => { if (!ipcConnected) { debugLog("[Wo] IPC connection timeout"); reject(new Error("Failed to connect to compositor IPC socket")); } }, 5000); }); } function sendSiteHashesToCompositor() { if (!ipcSocket || !ipcConnected || !activeSiteHashes) return; const windowName = stringToBuffer(name); const payload = Buffer.from(JSON.stringify(activeSiteHashes)); if (payload.length > 24 * 1024 * 1024) return; const message = Buffer.allocUnsafe(12 + windowName.length + payload.length); let offset = 0; message.writeUInt32LE(MAGIC.SITE_HASHES, offset); offset += 4; message.writeUInt32LE(windowName.length, offset); offset += 4; message.writeUInt32LE(payload.length, offset); offset += 4; windowName.copy(message, offset); offset += windowName.length; payload.copy(message, offset); ipcSocket.write(message); } function setupIpcHandlers() { ipcMain.handle("wo:performance-status", () => getPerformanceStatus()); ipcMain.on("wo:portal-ui-ready", (event) => { if (CLIENT_MODE || !mainWindow || event.sender !== mainWindow.webContents) return; portalUiRendererReady = true; portalUiRendererGeneration += 1; flushPortalUiEvents(); }); ipcMain.on("wo:portal-ui-delivered", (event, requestId) => { if (CLIENT_MODE || !mainWindow || event.sender !== mainWindow.webContents || typeof requestId !== "string") return; portalUiEvents.delete(requestId); }); ipcMain.handle("wo:resource-consent-respond", (event, requestId, token, decision) => { if (CLIENT_MODE || !mainWindow || event.sender !== mainWindow.webContents || typeof requestId !== "string" || typeof token !== "string" || !["deny", "allow", "allow-all"].includes(decision)) { debugLog("[Wo] Rejected malformed resource-consent IPC response"); return { ok: false }; } const ok = resolveResourceConsent(requestId, token, decision); debugLog(`[Wo] Resource-consent IPC response received: accepted=${ok} decision=${decision}`); return { ok }; }); ipcMain.handle("wo:portal-ui-respond", (event, requestId, response) => { if (CLIENT_MODE || !mainWindow || event.sender !== mainWindow.webContents) { return { ok: false, error: "Portal UI response is only available to the compositor shell" }; } if (typeof requestId !== "string" || !requestId) { return { ok: false, error: "Missing portal requestId" }; } return respondToPortalRequest(requestId, { response }); }); ipcMain.handle("wo:portal-context-menu", async (event, options) => { if (!senderHasCapability(event.sender, "portals")) return permissionDenied("portals"); if (!mainWindow || mainWindow.isDestroyed() || !Array.isArray(options?.items) || options.items.length === 0 || options.items.length > 100) { return { ok: false, error: "A context menu requires between 1 and 100 items" }; } const selected = new Map; const template = []; for (const [index, raw] of options.items.entries()) { const type = raw.type === "separator" ? "separator" : raw.type === "checkbox" || raw.type === "radio" ? raw.type : "normal"; const id = typeof raw.id === "string" ? raw.id.slice(0, 128) : String(index); if (type !== "separator" && (typeof raw.label !== "string" || raw.label.length > 256)) { return { ok: false, error: `Invalid context-menu label at item ${index}` }; } const item = type === "separator" ? { type: "separator" } : { id, label: String(raw.label), type, enabled: raw.enabled !== false, checked: raw.checked === true, ...typeof raw.accelerator === "string" ? { accelerator: raw.accelerator.slice(0, 64) } : {}, click: () => { selected.set(0, id); } }; template.push(item); } const menu = Menu.buildFromTemplate(template); return await new Promise((resolve) => { let settled = false; const finish = () => { if (settled) return; settled = true; const selectedId = selected.get(0); resolve(selectedId === undefined ? { ok: true } : { ok: true, selectedId }); }; menu.popup({ window: mainWindow, x: Number.isFinite(options.x) ? Math.trunc(options.x) : undefined, y: Number.isFinite(options.y) ? Math.trunc(options.y) : undefined, callback: finish }); }); }); ipcMain.handle("action", (event, action, payload) => { if (!senderHasCapability(event.sender, "window-management")) return permissionDenied("window-management"); return sendActionToCompositor(action, payload); }); ipcMain.on("action-sync", (event, action, payload) => { if (!senderHasCapability(event.sender, "window-management")) return; sendActionToCompositor(action, payload); }); ipcMain.on("wo:forward-pointer", (event, windowName, x, y) => { if (!senderHasCapability(event.sender, "window-management")) return; sendForwardedPointer(windowName, x, y); }); ipcMain.on("wo:forward-pointer-button", (event, windowName, x, y, button, pressed, time) => { if (!senderHasCapability(event.sender, "window-management")) return; sendForwardedPointerButton(windowName, x, y, button, pressed, time); }); ipcMain.on("wo:forward-pointer-scroll", (event, windowName, x, y, dx, dy, time) => { if (!senderHasCapability(event.sender, "window-management")) return; sendForwardedPointerScroll(windowName, x, y, dx, dy, time); }); ipcMain.on("wo:forward-pointer-leave", (event, windowName) => { if (!senderHasCapability(event.sender, "window-management")) return; if (!ipcSocket || !ipcConnected) return; const nameBuf = Buffer.from(windowName, "utf8"); const msg = Buffer.allocUnsafe(8 + nameBuf.length); msg.writeUInt32LE(MAGIC.FORWARD_POINTER_LEAVE, 0); msg.writeUInt32LE(nameBuf.length, 4); nameBuf.copy(msg, 8); ipcSocket.write(msg); }); ipcMain.handle("wo:get-windows", async (event) => senderHasCapability(event.sender, "window-management") ? compositorWindows.length > 0 ? compositorWindows : clientWindows : permissionDenied("window-management")); ipcMain.on("wo:keybind-action", (event, action) => { if (!senderHasCapability(event.sender, "window-management")) return; if (action === "shuffle") { clientWindows = [...clientWindows].sort(() => Math.random() - 0.5); } else if (action === "reverse") { clientWindows = [...clientWindows].reverse(); } if (mainWindow) { mainWindow.webContents.send("wo:windows", clientWindows); } }); ipcMain.handle("wo:submit-damage-frame", (event, payload) => { if (!senderHasCapability(event.sender, "overlay-rendering")) return permissionDenied("overlay-rendering"); if (!dmabufSender) { return { ok: false, reason: "dmabuf-sender-not-ready" }; } if (inFlightFrameSeqs.size >= MAX_IN_FLIGHT_FRAMES) { return { ok: true, skipped: true, reason: "backpressure" }; } if (!damageBuffer) { damageBuffer = new DamageBuffer(payload.width, payload.height); } appUsedDamageHelper = true; applyDamagePayload(damageBuffer, payload); const seq = dmabufSender.send(damageBuffer); inFlightFrameSeqs.add(seq.toString()); return { ok: true, seq: seq.toString() }; }); ipcMain.on("wo:forward-keyboard", (event, windowName, key, pressed, time) => { if (!senderHasCapability(event.sender, "window-management")) return; if (ipcSocket && ipcConnected) { const nameBuf = getEncodedWindowName(windowName); const msg = Buffer.alloc(4 + 4 + nameBuf.length + 12); let off = 0; msg.writeUInt32LE(MAGIC.FORWARD_KEYBOARD, off); off += 4; msg.writeUInt32LE(nameBuf.length, off); off += 4; nameBuf.copy(msg, off); off += nameBuf.length; msg.writeUInt32LE(key, off); off += 4; msg.writeUInt32LE(pressed ? 1 : 0, off); off += 4; msg.writeUInt32LE(Math.trunc(time) >>> 0, off); ipcSocket.write(msg); } }); ipcMain.on("wo:forward-relative-pointer", (event, windowName, dx, dy) => { if (!senderHasCapability(event.sender, "window-management")) return; if (ipcSocket && ipcConnected) { const nameBuf = getEncodedWindowName(windowName); const msg = Buffer.alloc(4 + 4 + nameBuf.length + 16); let off = 0; msg.writeUInt32LE(MAGIC.FORWARD_RELATIVE_POINTER, off); off += 4; msg.writeUInt32LE(nameBuf.length, off); off += 4; nameBuf.copy(msg, off); off += nameBuf.length; msg.writeDoubleLE(dx, off); off += 8; msg.writeDoubleLE(dy, off); ipcSocket.write(msg); } }); ipcMain.handle("wo:syscall", async (event, type, params) => { const capability = syscallCapability(type); if (!capability || !senderHasCapability(event.sender, capability)) return permissionDenied(capability || "window-management"); try { switch (type) { case "list_applications": { return listDesktopApplications({ locale: typeof params.locale === "string" ? params.locale : undefined }); } case "launch_desktop": { const desktopId = String(params.desktopId || ""); const files = Array.isArray(params.files) ? params.files.map(String) : []; return launchDesktopApplication(desktopId, files); } case "xdg_directories": { return { ok: true, directories: getXdgDirectories() }; } case "resolve_icon": { const iconName = String(params.name || ""); if (!iconName) return null; return resolveDesktopIcon(iconName, { size: typeof params.size === "number" ? params.size : undefined, theme: typeof params.theme === "string" ? params.theme : undefined }) ?? null; } case "launch": case "exec": { const { spawn } = await import("child_process"); const command = params.command || ""; if (!command) { return { ok: false, error: "No command specified" }; } try { const child = spawn(command, { detached: true, stdio: "ignore", shell: true }); child.unref(); return { ok: true, pid: child.pid }; } catch (error) { return { ok: false, error: String(error) }; } } case "listdir": { const fs2 = await import("fs"); const path = await import("path"); const dirPath = params.path || process.env.HOME || "/home"; try { const entries = fs2.default.readdirSync(dirPath, { withFileTypes: true }); return { ok: true, path: dirPath, entries: entries.map((e) => ({ name: e.name, type: e.isDirectory() ? "dir" : "file" })) }; } catch (error) { return { ok: false, error: String(error) }; } } case "read": { const fs2 = await import("fs"); const filePath = params.path; if (!filePath) { return { ok: false, error: "No file path specified" }; } try { const content = fs2.default.readFileSync(filePath, "utf-8"); return { ok: true, content, size: content.length }; } catch (error) { return { ok: false, error: String(error) }; } } case "write": { const fs2 = await import("fs"); const filePath = params.path; const content = params.content; if (!filePath || content === undefined) { return { ok: false, error: "Missing file path or content" }; } try { fs2.default.writeFileSync(filePath, content); return { ok: true, bytesWritten: content.length }; } catch (error) { return { ok: false, error: String(error) }; } } case "shutdown": { const { spawn } = await import("child_process"); spawn("systemctl", ["poweroff"], { detached: true, stdio: "ignore" }).unref(); return { ok: true }; } case "restart": { const { spawn } = await import("child_process"); spawn("systemctl", ["reboot"], { detached: true, stdio: "ignore" }).unref(); return { ok: true }; } case "logout": { const { spawn } = await import("child_process"); const sessionId = process.env.XDG_SESSION_ID || ""; if (sessionId) { spawn("loginctl", ["terminate-session", sessionId], { detached: true, stdio: "ignore" }).unref(); } else { sendActionToCompositor("quit", { code: 0 }); } return { ok: true }; } case "lock": { const { spawn } = await import("child_process"); spawn("loginctl", ["lock-session"], { detached: true, stdio: "ignore" }).unref(); return { ok: true }; } case "sleep": { const { spawn } = await import("child_process"); spawn("systemctl", ["suspend"], { detached: true, stdio: "ignore" }).unref(); return { ok: true }; } case "notify": { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("wo:notification", { id: params.id || `notify-${Date.now()}`, title: params.title || "Notification", body: params.body || "", icon: params.icon, timeout: params.timeout ?? 5000, timestamp: Date.now() }); } return { ok: true }; } case "portal_respond": { const requestId = String(params.requestId || ""); if (!requestId) { return { ok: false, error: "Missing portal requestId" }; } return respondToPortalRequest(requestId, { response: params.response, allowed: typeof params.allowed === "boolean" ? params.allowed : undefined, type: typeof params.type === "string" ? params.type : undefined, windowName: params.windowName }); } case "dbus_call": { const bus = params.bus === "system" ? "system" : "user"; const service = String(params.service || ""); const objectPath = String(params.objectPath || ""); const iface = String(params.interface || params.iface || ""); const method = String(params.method || ""); const signature = typeof params.signature === "string" ? params.signature : ""; const argList = Array.isArray(params.args) ? params.args : []; if (!service || !objectPath || !iface || !method) { return { ok: false, error: "Missing dbus parameters" }; } const args = ["call", service, objectPath, iface, method, signature || ""]; if (signature) { args.push(...argList.map((v) => String(v))); } const result = runBusctl(bus, args); return result; } case "dbus_get_property": { const bus = params.bus === "system" ? "system" : "user"; const service = String(params.service || ""); const objectPath = String(params.objectPath || ""); const iface = String(params.interface || params.iface || ""); const prop = String(params.property || params.prop || ""); if (!service || !objectPath || !iface || !prop) { return { ok: false, error: "Missing dbus parameters" }; } const result = runBusctl(bus, ["get-property", service, objectPath, iface, prop]); return result; } case "dbus_get_all_properties": { const bus = params.bus === "system" ? "system" : "user"; const service = String(params.service || ""); const objectPath = String(params.objectPath || ""); const iface = String(params.interface || params.iface || ""); if (!service || !objectPath || !iface) return { ok: false, error: "Missing D-Bus parameters" }; return runBusctl(bus, ["call", service, objectPath, "org.freedesktop.DBus.Properties", "GetAll", "s", iface]); } case "dbus_list_names": { const bus = params.bus === "system" ? "system" : "user"; const result = runBusctl(bus, ["--no-pager", "list"]); const names = result.stdout.split(/\r?\n/).slice(1).map((line) => line.trim().split(/\s+/, 1)[0]).filter((name) => name && name !== ""); return { ...result, names }; } case "audio_sources": { const result = listPipeWireAudioSources(); return result; } case "audio_capture_start": { const result = listPipeWireAudioSources(); if (!result.ok) return { ok: false, error: result.error }; const requestedId = typeof params.sourceId === "string" ? params.sourceId : ""; const source = requestedId ? result.sources.find((entry) => entry.id === requestedId) : result.sources.find((entry) => entry.monitor); if (!source) return { ok: false, error: requestedId ? "PipeWire source not found" : "No desktop monitor source is available" }; return startPipeWireAudioCapture(source); } case "audio_capture_stop": { const child = audioCapture; audioCapture = null; if (child && !child.killed) child.kill(); broadcastAudioState({ active: false }); return { ok: true }; } case "load_local_file": { const filePath = String(params.path || ""); if (!filePath) { return { ok: false, error: "Missing path" }; } if (!fs.existsSync(filePath)) { return { ok: false, error: `Path does not exist: ${filePath}` }; } if (!mainWindow || mainWindow.isDestroyed()) { return { ok: false, error: "Main window unavailable" }; } try { debugLog("[Wo] Loading local file:", filePath); await mainWindow.loadFile(filePath); return { ok: true }; } catch (error) { return { ok: false, error: String(error) }; } } default: return { ok: false, error: `Unknown syscall: ${type}` }; } } catch (error) { return { ok: false, error: `Syscall error: ${String(error)}` }; } }); } function flushPortalUiEvents() { if (!portalUiRendererReady || !mainWindow || mainWindow.isDestroyed()) return; for (const [requestId, event] of portalUiEvents) { if (!portalPendingRequests.has(requestId) || event.sentGeneration === portalUiRendererGeneration) continue; event.sentGeneration = portalUiRendererGeneration; mainWindow.webContents.send("wo:portal-request", event.data); } } async function createWindow() { debugLog("[Wo] createWindow starting"); const pageUrl = url || (html ? html.startsWith("file://") ? html : pathToFileURL(resolvePath(html)).href : null) || "data:text/html,"; let siteAssets = { pageUrl, appId: "website", capabilities: [], styles: "", stylesUrl: "", stylesHash: "", styleBodies: [], scripts: [], scriptBodies: [], indexSha256: "", indexBytes: new Uint8Array, stylesBytes: new Uint8Array, trusted: false }; if (/^(https?:|file:)/.test(pageUrl)) { siteAssets = await prefetchWoSiteAssets(pageUrl); } debugLog(siteAssets.trusted ? `[Wo] Verified site ${siteAssets.appId}; requested capabilities: ${siteAssets.capabilities.join(", ") || "(none)"}` : `[Wo] Site capabilities not granted because verification failed: ${siteAssets.trustError || "no verifiable site manifest"}`); apiGrants = new Set; apiGrantPageUrl = null; verifiedScriptCache.clear(); lockedScriptUrls = new Map; if (siteAssets.trusted && siteAssets.stylesUrl && siteAssets.stylesHash) { verifiedScriptCache.set(siteAssets.stylesHash, Buffer.from(siteAssets.stylesBytes)); lockedScriptUrls.set(siteAssets.stylesUrl, siteAssets.stylesHash); } for (const script of siteAssets.scriptBodies) { verifiedScriptCache.set(script.hash, Buffer.from(script.bytes)); lockedScriptUrls.set(script.url, script.hash); } for (const style of siteAssets.styleBodies) { verifiedScriptCache.set(style.hash, Buffer.from(style.bytes)); lockedScriptUrls.set(style.url, style.hash); } activeSiteHashes = siteAssets.trusted ? { pageUrl: siteAssets.pageUrl, indexBytes: Array.from(siteAssets.indexBytes), stylesBytes: Array.from(siteAssets.stylesBytes) } : null; sendSiteHashesToCompositor(); const trustedSystemShell = SYSTEM_SHELL && siteAssets.trusted && siteAssets.appId === "rip.crit.comraw" && isLoopbackHttp(pageUrl); if (trustedSystemShell) { apiGrants = new Set(siteAssets.capabilities); apiGrantPageUrl = siteAssets.pageUrl; debugLog("[Wo] Granted verified comraw system-shell capabilities"); } else if (siteAssets.trusted && siteAssets.capabilities.length > 0) { const permissionResult = await dialog.showMessageBox({ type: "question", title: "Wo API permission request", message: `${siteAssets.appId} requests access to Wo APIs`, detail: `Page: ${pageUrl} Verified index.html SHA-256: ${siteAssets.indexSha256} Requested permissions: ${siteAssets.capabilities.join(", ")} The site APIs are exposed only for this window while it is open.`, buttons: ["Allow requested APIs", "Open without Wo APIs"], defaultId: 1, cancelId: 1, noLink: true }); if (permissionResult.response === 0) { apiGrants = new Set(siteAssets.capabilities); apiGrantPageUrl = siteAssets.pageUrl; } } const requested = siteAssets.trusted ? siteAssets.capabilities : []; const granted = [...apiGrants]; const preloadPath = joinPath(__dirname2, "preload.js"); const opaqueDesktopRoot = !CLIENT_MODE && WINDOW_CONFIG.name === "main"; debugLog("[Wo] preloadPath=" + preloadPath); try { mainWindow = new BrowserWindow({ width: CLIENT_MODE ? 1400 : initialWindowWidth, height: CLIENT_MODE ? 900 : initialWindowHeight, transparent: !opaqueDesktopRoot, backgroundColor: opaqueDesktopRoot ? "#0b0d12" : "#00000000", frame: CLIENT_MODE, webPreferences: { sandbox: false, contextIsolation: true, preload: preloadPath, additionalArguments: [ `--wo-api-requested=${encodeURIComponent(JSON.stringify(requested))}`, `--wo-api-granted=${encodeURIComponent(JSON.stringify(granted))}`, `--wo-api-page=${encodeURIComponent(apiGrantPageUrl || "")}`, ...!CLIENT_MODE ? ["--wo-system-portal-ui=1"] : [] ], nodeIntegration: false, offscreen: CLIENT_MODE ? false : { useSharedTexture: offscreenSharedTextureEnabled, ...offscreenSharedTextureEnabled ? { sharedTexturePixelFormat: "argb" } : {} }, webgl: true, backgroundThrottling: false }, show: CLIENT_MODE }); mainPageLoadFinished = false; let scriptLockActive = true; let resourceApprovalPageUrl = siteAssets.pageUrl; let firstNavigation = true; let pendingResourceChecks = 0; let navigationGeneration = 0; let reloadAfterResourceApproval = false; let resourceReloadTimer = null; const deniedResourceKeys = new Set; const finishResourceCheck = (approved) => { if (approved) reloadAfterResourceApproval = true; pendingResourceChecks = Math.max(0, pendingResourceChecks - 1); if (pendingResourceChecks !== 0 || !reloadAfterResourceApproval && !resourceConsentUiActive || resourceReloadTimer) return; resourceReloadTimer = setTimeout(() => { resourceReloadTimer = null; if ((reloadAfterResourceApproval || resourceConsentUiActive) && pendingResourceChecks === 0 && mainWindow && !mainWindow.isDestroyed()) { reloadAfterResourceApproval = false; resourceConsentUiActive = false; debugLog("[Wo] Returning to page after resource approval decisions"); mainWindow.webContents.loadURL(resourceApprovalPageUrl).catch((error) => { debugLog("[Wo] Could not restore page after resource approval:", String(error)); }); } }, 250); }; mainWindow.webContents.on("will-navigate", handleResourceConsentNavigation); mainWindow.webContents.on("did-finish-load", () => { if (!resourceConsentUiActive) mainPageLoadFinished = true; }); mainWindow.webContents.on("did-start-navigation", (_event, navigationUrl, _isInPlace, isMainFrame) => { if (!isMainFrame) return; portalUiRendererReady = false; if (resourceConsentUiActive) return; mainPageLoadFinished = false; if (resourceReloadTimer) clearTimeout(resourceReloadTimer); resourceReloadTimer = null; reloadAfterResourceApproval = false; pendingResourceChecks = 0; navigationGeneration += 1; deniedResourceKeys.clear(); const wasFirstNavigation = firstNavigation; firstNavigation = false; try { const requestedUrl = new URL(pageUrl); const navigatingUrl = new URL(navigationUrl); requestedUrl.hash = ""; navigatingUrl.hash = ""; if (requestedUrl.href === navigatingUrl.href) return; } catch { if (wasFirstNavigation) return; } apiGrants.clear(); apiGrantPageUrl = null; scriptLockActive = true; try { const currentPage = new URL(navigationUrl); currentPage.hash = ""; resourceApprovalPageUrl = currentPage.href; } catch { resourceApprovalPageUrl = navigationUrl; } lockedScriptUrls.clear(); verifiedScriptCache.clear(); }); mainWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => { if (details.url.startsWith("wo-cache://")) { callback({}); return; } if (!scriptLockActive || !mainWindow || details.webContentsId !== mainWindow.webContents.id || !["script", "worker", "stylesheet"].includes(details.resourceType)) { callback({}); return; } const hash = lockedScriptUrls.get(details.url); const isStylesheet = details.resourceType === "stylesheet"; if (hash) { debugLog(`[Wo] Locking verified script ${details.url} -> ${hash.slice(0, 12)}`); callback({ redirectURL: woCacheUrl(hash, details.url, isStylesheet) }); return; } const approvalPageUrl = resourceApprovalPageUrl; const approvalKey = resourceApprovalKey(approvalPageUrl, details.url); const requestGeneration = navigationGeneration; pendingResourceChecks += 1; let checkFinished = false; const finishCheck = (approved) => { if (checkFinished) return; checkFinished = true; if (requestGeneration !== navigationGeneration) return; finishResourceCheck(approved); }; loadApprovedResource(approvalPageUrl, details.url).then((cachedHash) => { if (requestGeneration !== navigationGeneration) { callback({ cancel: true }); finishCheck(false); return; } if (cachedHash) { callback({ redirectURL: woCacheUrl(cachedHash, details.url, isStylesheet) }); finishCheck(false); return; } callback({ cancel: true }); if (deniedResourceKeys.has(approvalKey)) { debugLog(`[Wo] Resource remains blocked after user denial: ${details.url}`); finishCheck(false); return; } approveAndCacheResource(approvalPageUrl, details.url, details.resourceType, siteAssets.styles).then((approvedHash) => { if (!approvedHash) { deniedResourceKeys.add(approvalKey); debugLog(`[Wo] Blocked unapproved resource: ${details.url} (${details.resourceType})`); finishCheck(false); return; } finishCheck(true); }).catch((error) => { debugLog(`[Wo] Could not approve/cache resource ${details.url}: ${String(error)}`); finishCheck(false); }); }).catch((error) => { callback({ cancel: true }); debugLog(`[Wo] Could not check approved resource cache for ${details.url}: ${String(error)}`); finishCheck(false); }); }); mainWindow.webContents.session.webRequest.onErrorOccurred((details) => { if (mainWindow && details.webContentsId === mainWindow.webContents.id) { debugLog(`[Wo] Resource request failed: ${details.error} ${details.resourceType} ${details.url}`); } }); mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, "Cross-Origin-Opener-Policy": ["same-origin"], "Cross-Origin-Embedder-Policy": ["require-corp"], ...scriptLockActive && ["script", "worker", "stylesheet"].includes(details.resourceType) ? { "Access-Control-Allow-Origin": ["*"], "Cross-Origin-Resource-Policy": ["cross-origin"] } : {} } }); }); debugLog("[Wo] BrowserWindow created, CLIENT_MODE=" + CLIENT_MODE + " offscreen=" + mainWindow.webContents.isOffscreen() + " painting=" + mainWindow.webContents.isPainting()); mainWindow.webContents.on("console-message", (details) => { debugLog(`[Wo] page console level=${details.level} ${details.sourceId}:${details.lineNumber}: ${details.message}`); }); mainWindow.webContents.on("render-process-gone", (_event, details) => { debugLog("[Wo] page render process exited:", JSON.stringify(details)); }); setupIpcHandlers(); debugLog("[Wo] setupIpcHandlers completed"); mainWindow.webContents.send("wo:windows", clientWindows); mainWindow.webContents.on("dom-ready", () => { if (siteAssets.trusted && !resourceConsentUiActive) { if (siteAssets.styles && mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.insertCSS(siteAssets.styles, { cssOrigin: "user" }).catch((error) => { debugLog("[Wo] Could not apply _wo.components.css:", String(error)); }); } } }); let pageHasLoaded = false; mainWindow.webContents.on("did-navigate", (_event, navigatedUrl) => { if (/^https?:/.test(navigatedUrl)) pageHasLoaded = true; }); mainWindow.webContents.on("did-fail-load", (_event, errorCode, errorDescription, failedUrl, isMainFrame) => { if (!isMainFrame || resourceConsentUiActive || errorCode === -3 || pageHasLoaded || !/^https?:/.test(failedUrl)) return; debugLog(`[Wo] Page load failed (${errorCode} ${errorDescription}); retrying ${failedUrl}`); setTimeout(() => { if (mainWindow && !mainWindow.isDestroyed() && !resourceConsentUiActive && !pageHasLoaded) { mainWindow.loadURL(pageUrl).catch((error) => debugLog("[Wo] Page retry failed:", String(error))); } }, 750); }); if (CLIENT_MODE) { return; } debugLog("[Wo] Loading content URL: " + pageUrl); try { const loadPromise = mainWindow.loadURL(pageUrl); await Promise.race([ loadPromise, new Promise((r) => setTimeout(() => { debugLog("[Wo] Load timeout, continuing"); r(undefined); }, 3000)) ]); debugLog("[Wo] Content loaded from: " + pageUrl); } catch (e) { debugLog("[Wo] Load failed:", String(e)); } try { const pageState = await mainWindow.webContents.executeJavaScript(`(() => { const root = document.getElementById('root'); const wallpaper = document.querySelector('.wo-wallpaper'); const portalCard = document.querySelector('main[role="dialog"]'); return { readyState: document.readyState, title: document.title, bodyText: (document.body?.innerText || '').slice(0, 240), bodyBackground: document.body ? getComputedStyle(document.body).backgroundColor : '', bodyBackgroundImage: document.body ? getComputedStyle(document.body).backgroundImage : '', bodyDisplay: document.body ? getComputedStyle(document.body).display : '', portalCardBackground: portalCard ? getComputedStyle(portalCard).backgroundColor : '', portalCardBounds: portalCard ? (() => { const r = portalCard.getBoundingClientRect(); return { x:r.x, y:r.y, width:r.width, height:r.height }; })() : null, buttons: document.querySelectorAll('button').length, resourceConsentBridge: !!window.woResourceConsent, rootChildren: root?.childElementCount ?? -1, shellPresent: !!document.querySelector('.wo-shell'), wallpaperImage: wallpaper ? getComputedStyle(wallpaper).backgroundImage : '', compositorApi: !!window.compositor, woApi: !!window.wo, }; })()`); debugLog("[Wo] Page render state:", JSON.stringify(pageState)); } catch (error) { debugLog("[Wo] Could not inspect loaded page:", String(error)); } debugLog("[Wo] Page initialized, setting up rendering"); mainWindow.webContents.focus(); if (!CLIENT_MODE) { configureFramePacing(); mainWindow.webContents.on("did-finish-load", configureFramePacing); let patchBuf = null; let emptyPaintRecoveryAt = 0; let emptyPaintLogged = false; let skippedCount = 0; let loggedFirstBitmapFrame = false; let loggedSecondBitmapFrame = false; mainWindow.webContents.on("paint", (paintEvent, dirty, image) => { const sharedTexture = paintEvent.texture; if (sharedTexture) { if (sharedDmabufStatus !== true) { sharedDmabufStatus = true; publishPerformanceStatus(); } let releasedByAck = false; try { if (!dmabufSender || !ipcConnected || appUsedDamageHelper || inFlightFrameSeqs.size >= MAX_IN_FLIGHT_FRAMES) { return; } const seq = dmabufSender.sendSharedTexture(sharedTexture.textureInfo, () => sharedTexture.release()); releasedByAck = true; inFlightFrameSeqs.add(seq.toString()); } catch (error) { debugLog("[Wo] Native shared-texture frame failed:", String(error)); sharedDmabufStatus = false; publishPerformanceStatus(); } finally { if (!releasedByAck) sharedTexture.release(); } return; } if (sharedDmabufStatus !== false) { sharedDmabufStatus = false; publishPerformanceStatus(); } if (!dmabufSender || !ipcConnected) { skippedCount++; debugLog("[Wo] SKIPPED paint (dmabufSender=" + !!dmabufSender + " ipcConnected=" + ipcConnected + ")"); return; } if (appUsedDamageHelper) { return; } if (inFlightFrameSeqs.size >= MAX_IN_FLIGHT_FRAMES) { skippedCount++; if (skippedCount === 1 || skippedCount % 120 === 0) { debugLog("[Wo] SKIPPED paint due to frame backpressure inFlight=" + inFlightFrameSeqs.size); } return; } const size = image.getSize(); if (size.width <= 0 || size.height <= 0) { const now = Date.now(); if (!emptyPaintLogged) { const contentSize = mainWindow?.getContentSize() ?? [0, 0]; debugLog(`[Wo] Ignoring empty offscreen paint ${size.width}x${size.height}; content=${contentSize[0]}x${contentSize[1]} dirty=${dirty.x},${dirty.y},${dirty.width},${dirty.height}`); emptyPaintLogged = true; } if (now - emptyPaintRecoveryAt >= 250) { emptyPaintRecoveryAt = now; if (mainWindow && !mainWindow.isDestroyed()) { const [contentWidth, contentHeight] = mainWindow.getContentSize(); if (contentWidth <= 0 || contentHeight <= 0) { mainWindow.setSize(initialWindowWidth, initialWindowHeight); } mainWindow.webContents.invalidate(); } } return; } let resized = !damageBuffer; if (!damageBuffer) { damageBuffer = new DamageBuffer(size.width, size.height); } else if (damageBuffer.getWidth() !== size.width || damageBuffer.getHeight() !== size.height) { damageBuffer.reset(size.width, size.height); resized = true; } const pixels = image.toBitmap(); const pixelData = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength); if (pixelData.byteLength < size.width * size.height * 4) { if (!emptyPaintLogged) { debugLog(`[Wo] Ignoring incomplete offscreen bitmap: ${pixelData.byteLength} bytes for ${size.width}x${size.height}`); emptyPaintLogged = true; } if (Date.now() - emptyPaintRecoveryAt >= 250 && mainWindow && !mainWindow.isDestroyed()) { emptyPaintRecoveryAt = Date.now(); mainWindow.webContents.invalidate(); } return; } emptyPaintLogged = false; if (!loggedFirstBitmapFrame) { const centerOffset = (Math.floor(size.height / 2) * size.width + Math.floor(size.width / 2)) * 4; const sampleOffsets = [0, Math.max(0, Math.floor(pixelData.byteLength / 2 / 4) * 4), centerOffset, Math.max(0, pixelData.byteLength - 4)]; const samples = sampleOffsets.map((offset) => Array.from(pixelData.subarray(offset, offset + 4))); debugLog(`[Wo] First bitmap pixels (BGRA top-left/mid-left/center/bottom-right): ${JSON.stringify(samples)}`); } else if (!loggedSecondBitmapFrame) { const centerOffset = (Math.floor(size.height / 2) * size.width + Math.floor(size.width / 2)) * 4; const sampleOffsets = [0, Math.max(0, Math.floor(pixelData.byteLength / 2 / 4) * 4), centerOffset, Math.max(0, pixelData.byteLength - 4)]; const samples = sampleOffsets.map((offset) => Array.from(pixelData.subarray(offset, offset + 4))); debugLog(`[Wo] Second bitmap pixels (BGRA top-left/mid-left/center/bottom-right): ${JSON.stringify(samples)}`); loggedSecondBitmapFrame = true; } const isFullFrame = resized || dirty.x === 0 && dirty.y === 0 && dirty.width === size.width && dirty.height === size.height; if (isFullFrame) { applyDamagePayload(damageBuffer, { width: size.width, height: size.height, fullFrame: pixelData }); } else { const stride = size.width * 4; const patchStride = dirty.width * 4; const patchBytes = patchStride * dirty.height; if (!patchBuf || patchBuf.byteLength < patchBytes) { patchBuf = new Uint8Array(patchBytes); } const patchData = patchBuf.byteLength === patchBytes ? patchBuf : patchBuf.subarray(0, patchBytes); for (let row = 0;row < dirty.height; row++) { const srcOff = (dirty.y + row) * stride + dirty.x * 4; const dstOff = row * patchStride; patchData.set(pixelData.subarray(srcOff, srcOff + patchStride), dstOff); } applyDamagePayload(damageBuffer, { width: size.width, height: size.height, patches: [{ rect: { x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height }, rgba: patchData, stride: patchStride }] }); } try { const seq = dmabufSender.send(damageBuffer); inFlightFrameSeqs.add(seq.toString()); if (!loggedFirstBitmapFrame) { debugLog(`[Wo] First bitmap frame sent as DMA-BUF: seq=${seq} size=${size.width}x${size.height} stride=${damageBuffer.getStride()}`); loggedFirstBitmapFrame = true; } } catch (e) { debugLog("[Wo] ERROR sending frame:", String(e)); } }); } if (process.env.WO_DEBUG === "1") { mainWindow.webContents.openDevTools(); } debugLog("[Wo] createWindow completed successfully"); } catch (error) { debugLog("[Wo] createWindow error:", error); throw error; } mainWindow.on("closed", () => { mainWindow = null; if (framePacer) clearInterval(framePacer); framePacer = null; framePacerRate = 0; }); } app.on("ready", async () => { debugLog("[Wo] App ready, CLIENT_MODE=", CLIENT_MODE, "WO_STANDALONE=", process.env.WO_STANDALONE); displayScreen = (await import("electron")).screen; displayScreen.on("display-added", configureFramePacing); displayScreen.on("display-removed", configureFramePacing); displayScreen.on("display-metrics-changed", configureFramePacing); setupWoCacheProtocol(); const gpuInfo = await app.getGPUInfo("complete").catch((error) => ({ error: String(error) })); const gpuFeatures = app.getGPUFeatureStatus(); gpuCompositingStatus = gpuFeatures.gpu_compositing || "unknown"; webglStatus = gpuFeatures.webgl || "unknown"; const threadedTextureMailbox = gpuInfo.auxAttributes?.canSupportThreadedTextureMailbox; offscreenSharedTextureEnabled = gpuCompositingStatus === "enabled" && threadedTextureMailbox !== false; debugLog("[Wo] GPU feature status:", JSON.stringify(gpuFeatures)); debugLog("[Wo] GPU info:", JSON.stringify(gpuInfo)); debugLog(`[Wo] Offscreen shared texture: ${offscreenSharedTextureEnabled ? "enabled" : "disabled"} (GPU compositor=${gpuCompositingStatus}, threaded texture mailbox=${threadedTextureMailbox ?? "unknown"}, WebGL=${webglStatus})`); publishPerformanceStatus(); try { startPortalUiBridge(); if (!CLIENT_MODE && process.env.WO_STANDALONE !== "1") { debugLog("[Wo] Will connect to compositor"); await connectToCompositor(); } debugLog("[Wo] Creating window"); await createWindow(); debugLog("[Wo] Window created successfully"); } catch (error) { debugLog("[Wo] Failed to start app:", error); app.quit(); } }); app.on("window-all-closed", () => { stopPortalUiBridge(); if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { if (mainWindow === null) { createWindow().catch(console.error); } }); app.on("before-quit", () => { if (audioCapture && !audioCapture.killed) audioCapture.kill(); audioCapture = null; stopPortalUiBridge(); closeShmFdCache(); }); export { connectToCompositor, sendActionToCompositor };