// 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 typeRequested 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
};