UNPKG

bond-wm

Version:

An X Window Manager built on web technologies.

1,493 lines (1,478 loc) 140 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // args.ts import yargs from "yargs"; import { hideBin } from "yargs/helpers"; function getArgs() { return argv; } function loggingEnabled() { return argv.consoleLogging || !!argv.fileLogging; } var argv; var init_args = __esm({ "args.ts"() { "use strict"; argv = yargs(hideBin(process.argv)).nargs("config", 1).option("config", { describe: "Config package specifier to load" }).boolean("console-logging").default("console-logging", false).option("console-logging", { describe: "Enable console log output" }).nargs("file-logging", 1).option("file-logging", { describe: "Enable logging output to a file" }).usage( `bond-wm window manager Usage: $0 [options]` ).help().argv; console.log(argv); } }); // log.ts import * as fs from "fs"; import * as util from "util"; function log(...args) { if (logFile || consoleLogging) { const logText = formatLogText(args); logFile?.write(logText); if (consoleLogging) { stdout.write(logText); } } } function logDir(obj, options) { if (logFile || consoleLogging) { const logText = util.inspect(obj, { showHidden: false, depth: 3, colors: false, ...options }) + "\n"; logFile?.write(logText); if (consoleLogging) { stdout.write(logText); } } } function logError(...args) { if (logFile || consoleLogging) { const logText = formatLogText(args); logFile?.write(logText); if (consoleLogging) { stderr.write(logText); } } } function formatLogText(args) { return util.format.apply(null, args) + "\n"; } var consoleLogging, fileLogging, logFile, stdout, stderr; var init_log = __esm({ "log.ts"() { "use strict"; init_args(); ({ consoleLogging, fileLogging } = getArgs()); logFile = fileLogging ? fs.createWriteStream(fileLogging, { flags: "w" }) : null; stdout = process.stdout; stderr = process.stderr; } }); // notifications.ts import { app, ipcMain } from "electron"; import { execSync } from "node:child_process"; import { EventEmitter } from "node:events"; import * as dbus from "@particle/dbus-next"; import { interface as dbusInterface, RequestNameReply, Message } from "@particle/dbus-next"; var NotificationIPCMessages, NotificationServer, NotificationInterface; var init_notifications = __esm({ "notifications.ts"() { "use strict"; init_log(); NotificationIPCMessages = { NewNotification: "notification:new", CloseNotification: "notification:close", ClearAllNotifications: "notification:clear-all", NotificationAction: "notification:action", NotificationClosed: "notification:user-closed", RequestNotifications: "notification:request-all" }; NotificationServer = class { bus = dbus.sessionBus(); activeNotifications = /* @__PURE__ */ new Map(); notificationInterface; broadcastCallback; constructor(broadcastCallback) { this.broadcastCallback = broadcastCallback; this.notificationInterface = new NotificationInterface( this.handleNotification.bind(this), this.parseActions.bind(this), this.bus // Pass bus to interface ); this.setupIPCHandlers(); } setupIPCHandlers() { ipcMain.on(NotificationIPCMessages.RequestNotifications, (event) => { const notifications = Array.from(this.activeNotifications.values()); notifications.forEach((notification) => { event.reply(NotificationIPCMessages.NewNotification, notification); }); }); ipcMain.on(NotificationIPCMessages.NotificationClosed, (event, id) => { if (this.activeNotifications.has(id)) { this.activeNotifications.delete(id); this.emitNotificationClosed(id, 2); this.broadcastToAllDesktops(NotificationIPCMessages.CloseNotification, id); } }); ipcMain.on( NotificationIPCMessages.NotificationAction, (event, data) => { const notification = this.activeNotifications.get(data.notificationId); if (notification) { try { this.emitActionInvoked(data.notificationId, data.actionId); this.activeNotifications.delete(data.notificationId); this.broadcastToAllDesktops(NotificationIPCMessages.CloseNotification, data.notificationId); } catch (error) { logError(`Error processing action:`, error); } } else { logError(`Warning: Notification ${data.notificationId} not found for action ${data.actionId}`); } } ); ipcMain.on(NotificationIPCMessages.ClearAllNotifications, () => { this.activeNotifications.clear(); this.broadcastToAllDesktops(NotificationIPCMessages.ClearAllNotifications); }); } broadcastToAllDesktops(channel, ...args) { if (this.broadcastCallback) { this.broadcastCallback(channel, ...args); } } emitActionInvoked(notificationId, actionId) { try { if (!this.notificationInterface) { throw new Error("Notification interface is not initialized"); } this.notificationInterface.emitActionInvoked(notificationId, actionId); try { const cmd = `dbus-send --session --type=signal /org/freedesktop/Notifications org.freedesktop.Notifications.ActionInvoked uint32:${notificationId} string:"${actionId}"`; execSync(cmd); } catch (err) { logError(`Error sending signal via dbus-send:`, err); } } catch (error) { logError("Error emitting ActionInvoked signal:", error); } } emitNotificationClosed(notificationId, reason = 3) { try { if (!this.notificationInterface) { throw new Error("Notification interface is not initialized"); } this.notificationInterface.emitNotificationClosed(notificationId, reason); } catch (error) { logError("Warning: Error emitting NotificationClosed signal:", error); } } async start() { try { log("Starting notification server..."); const dbusObj = await this.bus.getProxyObject("org.freedesktop.DBus", "/org/freedesktop/DBus"); const dbusInterface2 = dbusObj.getInterface("org.freedesktop.DBus"); const names = await dbusInterface2.ListNames(); if (names.includes("org.freedesktop.Notifications")) { log("Notification service already running"); return; } this.bus.export("/org/freedesktop/Notifications", this.notificationInterface); const result = await this.bus.requestName("org.freedesktop.Notifications", 4); if (result === RequestNameReply.PRIMARY_OWNER) { log("Notification service registered successfully"); setTimeout(() => { log(" Testing D-Bus signal emission..."); try { this.emitActionInvoked(999, "test-action"); this.emitNotificationClosed(999, 1); } catch (error) { logError("Error in signal test:", error); } }, 2e3); } else { logError("Warning: Could not register service. Code:", result); } } catch (error) { logError("Error starting DBus server:", error); } } handleNotification(notification) { this.activeNotifications.set(notification.id, notification); this.broadcastToAllDesktops(NotificationIPCMessages.NewNotification, notification); } parseActions(actions) { const parsedActions = []; for (let i = 0; i < actions.length; i += 2) { if (actions[i + 1]) { parsedActions.push({ id: actions[i], label: actions[i + 1] }); } } return parsedActions; } }; NotificationInterface = class extends dbusInterface.Interface { // Store the bus instance for signal emission constructor(notifyCallback, parseActions, bus) { super("org.freedesktop.Notifications"); this.notifyCallback = notifyCallback; this.parseActions = parseActions; this.bus = bus; this.emitter = new EventEmitter(); } notificationCounter = 1; emitter; bus; // Methods to emit signals directly emitActionInvoked(notificationId, actionId) { const signalMessage = new Message({ type: dbus.MessageType.SIGNAL, path: "/org/freedesktop/Notifications", interface: "org.freedesktop.Notifications", member: "ActionInvoked", signature: "us", body: [notificationId, actionId] }); if (this.bus && this.bus.send) { this.bus.send(signalMessage); } else { throw new Error("Bus not available in interface instance"); } this.emitter.emit("ActionInvoked", notificationId, actionId); } emitNotificationClosed(notificationId, reason) { const signalMessage = new Message({ type: dbus.MessageType.SIGNAL, path: "/org/freedesktop/Notifications", interface: "org.freedesktop.Notifications", member: "NotificationClosed", signature: "uu", body: [notificationId, reason] }); if (this.bus && this.bus.send) { this.bus.send(signalMessage); } else { throw new Error("Bus not available in interface instance"); } this.emitter.emit("NotificationClosed", notificationId, reason); } Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout) { const id = replacesId > 0 ? replacesId : this.notificationCounter++; const notification = { id, appName: String(appName), summary: String(summary), body: String(body), appIcon: appIcon ? String(appIcon) : void 0, expireTimeout, actions: this.parseActions(actions), timestamp: Date.now() }; this.notifyCallback(notification); return id; } CloseNotification(id) { log(`D-Bus call to CloseNotification for ID: ${id}`); } GetCapabilities() { return ["body", "actions", "persistence", "action-icons", "body-markup", "body-hyperlinks"]; } GetServerInformation() { return ["Bond WM Notifications", "Bond WM", app.getVersion(), "1.2"]; } }; NotificationInterface.configureMembers({ methods: { Notify: { inSignature: "susssasa{sv}i", outSignature: "u" }, CloseNotification: { inSignature: "u", outSignature: "" }, GetCapabilities: { inSignature: "", outSignature: "as" }, GetServerInformation: { inSignature: "", outSignature: "ssss" } }, signals: { ActionInvoked: { signature: "us" }, NotificationClosed: { signature: "uu" } } }); } }); // configureStore.ts import { applyMiddleware } from "redux"; import { configureStore } from "@reduxjs/toolkit"; import { composeWithStateSync } from "@wnayes/electron-redux/main"; import { configReducer, desktopReducer, pluginStateReducer, screenReducer, trayReducer, windowReducer } from "@bond-wm/shared"; function configureWMStore(middleware) { const enhancer = composeWithStateSync(applyMiddleware(...middleware)); const store = configureStore({ reducer: { config: configReducer, desktop: desktopReducer, pluginState: pluginStateReducer, screens: screenReducer, tray: trayReducer, windows: windowReducer }, // Could try to tune this, but for now just disable it. middleware: (getDefaultMiddleware) => getDefaultMiddleware({ immutableCheck: false, serializableCheck: false }), enhancers: (getDefaultEnhancers) => { return getDefaultEnhancers().concat(enhancer); } }); return store; } var init_configureStore = __esm({ "configureStore.ts"() { "use strict"; } }); // xutils.ts function internAtomAsync(X, name) { return new Promise((resolve3, reject) => { X.InternAtom(false, name, (err, atom) => { if (err) { reject(err); } else { resolve3(atom); } }); }); } async function getPropertyValue(X, wid, nameAtom, typeAtom) { const prop = await getRawPropertyValue(X, wid, nameAtom, typeAtom); switch (prop.type) { case X.atoms.STRING: return prop.data.toString(); case ExtraAtoms.UTF8_STRING: return prop.data.toString(); case X.atoms.WINDOW: if (prop.data && prop.data.length >= 4) { return prop.data.readInt32LE(0); } return void 0; default: log("Unhandled atom property type", prop); return void 0; } } function getRawPropertyValue(X, wid, nameAtom, typeAtom) { return new Promise((resolve3, reject) => { X.GetProperty(0, wid, nameAtom, typeAtom, 0, 1e7, function(err, prop) { if (err) { reject(err); return; } log("Got property value response", prop); resolve3(prop); }); }); } function changeWindowEventMask(X, wid, eventMask) { let failed; log("Changing event mask for", wid, eventMask); X.ChangeWindowAttributes(wid, { eventMask }, (err) => { if (err && err.error === 10) { logError( `Error while changing event mask for for ${wid} to ${eventMask}: Another window manager already running.`, err ); failed = true; return; } logError(`Error while changing event mask for for ${wid} to ${eventMask}`, err); failed = true; }); return !failed; } function numsToBuffer(nums) { const buffer = Buffer.alloc(nums.length * 4); for (let i = 0; i < nums.length; i++) { buffer.writeInt32LE(nums[i], i * 4); } return buffer; } var init_xutils = __esm({ "xutils.ts"() { "use strict"; init_log(); init_wm(); } }); // ewmh.ts import { WindowType, XWMWindowType, selectWindowMaximizeCanTakeEffect, selectWindowsFromTag, setTagCurrentLayoutAction, setWindowAlwaysOnTopAction, setWindowFullscreenAction, setWindowUrgentAction } from "@bond-wm/shared"; import { XCB_COPY_FROM_PARENT, XPropMode } from "@bond-wm/shared"; import { pid } from "process"; import { ResizeDirection } from "@bond-wm/shared"; function netWMMoveResizeTypeToInternal(newWmMoveResizeType) { switch (newWmMoveResizeType) { case 0 /* _NET_WM_MOVERESIZE_SIZE_TOPLEFT */: return ResizeDirection.TopLeft; case 1 /* _NET_WM_MOVERESIZE_SIZE_TOP */: return ResizeDirection.Top; case 2 /* _NET_WM_MOVERESIZE_SIZE_TOPRIGHT */: return ResizeDirection.TopRight; case 3 /* _NET_WM_MOVERESIZE_SIZE_RIGHT */: return ResizeDirection.Right; case 4 /* _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT */: return ResizeDirection.BottomRight; case 5 /* _NET_WM_MOVERESIZE_SIZE_BOTTOM */: return ResizeDirection.Bottom; case 6 /* _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT */: return ResizeDirection.BottomLeft; case 7 /* _NET_WM_MOVERESIZE_SIZE_LEFT */: return ResizeDirection.Left; default: throw new Error("Unexpected resize type"); } } async function createEWMHEventConsumer({ X, store, wmServer, getWindowIdFromFrameId, getLayoutPlugins }, dragModule) { const atoms = { _NET_SUPPORTED: await internAtomAsync(X, "_NET_SUPPORTED"), _NET_SUPPORTING_WM_CHECK: await internAtomAsync(X, "_NET_SUPPORTING_WM_CHECK"), _NET_WM_NAME: await internAtomAsync(X, "_NET_WM_NAME"), _NET_WM_ICON: await internAtomAsync(X, "_NET_WM_ICON"), _NET_WM_STATE: await internAtomAsync(X, "_NET_WM_STATE"), _NET_WM_STATE_ABOVE: await internAtomAsync(X, "_NET_WM_STATE_ABOVE"), _NET_WM_STATE_FULLSCREEN: await internAtomAsync(X, "_NET_WM_STATE_FULLSCREEN"), _NET_WM_STATE_DEMANDS_ATTENTION: await internAtomAsync(X, "_NET_WM_STATE_DEMANDS_ATTENTION"), _NET_WM_STATE_MAXIMIZED_VERT: await internAtomAsync(X, "_NET_WM_STATE_MAXIMIZED_VERT"), _NET_WM_STATE_MAXIMIZED_HORZ: await internAtomAsync(X, "_NET_WM_STATE_MAXIMIZED_HORZ"), _NET_WM_STATE_HIDDEN: await internAtomAsync(X, "_NET_WM_STATE_HIDDEN"), _NET_WM_ALLOWED_ACTIONS: await internAtomAsync(X, "_NET_WM_ALLOWED_ACTIONS"), _NET_WM_ACTION_MOVE: await internAtomAsync(X, "_NET_WM_ACTION_MOVE"), _NET_WM_ACTION_RESIZE: await internAtomAsync(X, "_NET_WM_ACTION_RESIZE"), _NET_WM_ACTION_MINIMIZE: await internAtomAsync(X, "_NET_WM_ACTION_MINIMIZE"), _NET_WM_ACTION_SHADE: await internAtomAsync(X, "_NET_WM_ACTION_SHADE"), _NET_WM_ACTION_STICK: await internAtomAsync(X, "_NET_WM_ACTION_STICK"), _NET_WM_ACTION_MAXIMIZE_HORZ: await internAtomAsync(X, "_NET_WM_ACTION_MAXIMIZE_HORZ"), _NET_WM_ACTION_MAXIMIZE_VERT: await internAtomAsync(X, "_NET_WM_ACTION_MAXIMIZE_VERT"), _NET_WM_ACTION_FULLSCREEN: await internAtomAsync(X, "_NET_WM_ACTION_FULLSCREEN"), _NET_WM_ACTION_CHANGE_DESKTOP: await internAtomAsync(X, "_NET_WM_ACTION_CHANGE_DESKTOP"), _NET_WM_ACTION_CLOSE: await internAtomAsync(X, "_NET_WM_ACTION_CLOSE"), _NET_WM_WINDOW_TYPE: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE"), _NET_WM_WINDOW_TYPE_DESKTOP: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_DESKTOP"), _NET_WM_WINDOW_TYPE_DOCK: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_DOCK"), _NET_WM_WINDOW_TYPE_TOOLBAR: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_TOOLBAR"), _NET_WM_WINDOW_TYPE_MENU: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_MENU"), _NET_WM_WINDOW_TYPE_UTILITY: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_UTILITY"), _NET_WM_WINDOW_TYPE_SPLASH: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_SPLASH"), _NET_WM_WINDOW_TYPE_DIALOG: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_DIALOG"), _NET_WM_WINDOW_TYPE_DROPDOWN_MENU: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"), _NET_WM_WINDOW_TYPE_POPUP_MENU: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_POPUP_MENU"), _NET_WM_WINDOW_TYPE_TOOLTIP: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_TOOLTIP"), _NET_WM_WINDOW_TYPE_NOTIFICATION: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_NOTIFICATION"), _NET_WM_WINDOW_TYPE_COMBO: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_COMBO"), _NET_WM_WINDOW_TYPE_DND: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_DND"), _NET_WM_WINDOW_TYPE_NORMAL: await internAtomAsync(X, "_NET_WM_WINDOW_TYPE_NORMAL"), _NET_FRAME_EXTENTS: await internAtomAsync(X, "_NET_FRAME_EXTENTS"), _NET_WM_PID: await internAtomAsync(X, "_NET_WM_PID"), _NET_WM_MOVERESIZE: await internAtomAsync(X, "_NET_WM_MOVERESIZE"), UTF8_STRING: await internAtomAsync(X, "UTF8_STRING") }; function updateWindowStateHints(wid) { const win = store.getState().windows[wid]; if (!win) { return; } const hintAtoms = []; if (win.alwaysOnTop) { hintAtoms.push(atoms._NET_WM_STATE_ABOVE); } if (win.fullscreen) { hintAtoms.push(atoms._NET_WM_STATE_FULLSCREEN); } if (win.urgent) { hintAtoms.push(atoms._NET_WM_STATE_DEMANDS_ATTENTION); } if (win.maximized) { hintAtoms.push(atoms._NET_WM_STATE_MAXIMIZED_VERT); hintAtoms.push(atoms._NET_WM_STATE_MAXIMIZED_HORZ); } if (win.minimized) { hintAtoms.push(atoms._NET_WM_STATE_HIDDEN); } X.ChangeProperty(XPropMode.Replace, wid, atoms._NET_WM_STATE, X.atoms.ATOM, 32, numsToBuffer(hintAtoms)); } function removeWindowStateHints(wid) { X.DeleteProperty(wid, atoms._NET_WM_STATE, (err) => { if (err) { log("Could not delete _NET_WM_STATE"); } }); } function updateWindowAllowedActions(wid) { const state = store.getState(); const win = state.windows[wid]; if (!win) { return; } const actionAtoms = [ atoms._NET_WM_ACTION_MOVE, atoms._NET_WM_ACTION_RESIZE, atoms._NET_WM_ACTION_MINIMIZE, atoms._NET_WM_ACTION_FULLSCREEN, atoms._NET_WM_ACTION_CHANGE_DESKTOP, atoms._NET_WM_ACTION_CLOSE ]; const canMaximize = selectWindowMaximizeCanTakeEffect(state, getLayoutPlugins(win.screenIndex), wid); if (canMaximize) { actionAtoms.push(atoms._NET_WM_ACTION_MAXIMIZE_HORZ); actionAtoms.push(atoms._NET_WM_ACTION_MAXIMIZE_VERT); } X.ChangeProperty( XPropMode.Replace, wid, atoms._NET_WM_ALLOWED_ACTIONS, X.atoms.ATOM, 32, numsToBuffer(actionAtoms) ); } function removeWindowAllowedActions(wid) { X.DeleteProperty(wid, atoms._NET_WM_ALLOWED_ACTIONS, (err) => { if (err) { log("Could not delete _NET_WM_ALLOWED_ACTIONS"); } }); } function processWindowStateChange(wid, action, atom) { let handled = true; switch (atom) { case atoms._NET_WM_STATE_ABOVE: processWindowAboveChange(wid, action); break; case atoms._NET_WM_STATE_DEMANDS_ATTENTION: processWindowUrgentChange(wid, action); break; case atoms._NET_WM_STATE_FULLSCREEN: processWindowFullscreenChange(wid, action); break; case atoms._NET_WM_STATE_MAXIMIZED_VERT: case atoms._NET_WM_STATE_MAXIMIZED_HORZ: processWindowMaximizeChange(wid, action); break; case atoms._NET_WM_STATE_HIDDEN: processWindowMinimizeChange(wid, action); break; default: handled = false; break; } if (handled) { updateWindowStateHints(wid); } } function processWindowAboveChange(wid, action) { const win = store.getState().windows[wid]; if (!win) { return; } switch (action) { case 1 /* _NET_WM_STATE_ADD */: if (!win.alwaysOnTop) { store.dispatch(setWindowAlwaysOnTopAction({ wid, alwaysOnTop: true })); } break; case 0 /* _NET_WM_STATE_REMOVE */: if (win.alwaysOnTop) { store.dispatch(setWindowAlwaysOnTopAction({ wid, alwaysOnTop: false })); } break; case 2 /* _NET_WM_STATE_TOGGLE */: store.dispatch(setWindowAlwaysOnTopAction({ wid, alwaysOnTop: !win.alwaysOnTop })); break; } } function processWindowFullscreenChange(wid, action) { const win = store.getState().windows[wid]; if (!win) { return; } switch (action) { case 1 /* _NET_WM_STATE_ADD */: if (!win.fullscreen) { store.dispatch(setWindowFullscreenAction({ wid, fullscreen: true })); } break; case 0 /* _NET_WM_STATE_REMOVE */: if (win.fullscreen) { store.dispatch(setWindowFullscreenAction({ wid, fullscreen: false })); } break; case 2 /* _NET_WM_STATE_TOGGLE */: store.dispatch(setWindowFullscreenAction({ wid, fullscreen: !win.fullscreen })); break; } } function processWindowUrgentChange(wid, action) { const win = store.getState().windows[wid]; if (!win) { return; } switch (action) { case 1 /* _NET_WM_STATE_ADD */: if (!win.urgent) { store.dispatch(setWindowUrgentAction({ wid, urgent: true })); } break; case 0 /* _NET_WM_STATE_REMOVE */: if (win.urgent) { store.dispatch(setWindowUrgentAction({ wid, urgent: false })); } break; case 2 /* _NET_WM_STATE_TOGGLE */: store.dispatch(setWindowUrgentAction({ wid, urgent: !win.urgent })); break; } } function processWindowMaximizeChange(wid, action) { switch (action) { case 1 /* _NET_WM_STATE_ADD */: wmServer.maximizeWindow(wid); break; case 0 /* _NET_WM_STATE_REMOVE */: wmServer.restoreWindow(wid); break; case 2 /* _NET_WM_STATE_TOGGLE */: { const win = store.getState().windows[wid]; if (!win) { return; } const newMaximized = !win.maximized; if (newMaximized) { wmServer.maximizeWindow(wid); } else { wmServer.restoreWindow(wid); } } break; } } function processWindowMinimizeChange(wid, action) { switch (action) { case 1 /* _NET_WM_STATE_ADD */: wmServer.minimizeWindow(wid); break; case 0 /* _NET_WM_STATE_REMOVE */: wmServer.restoreWindow(wid); break; case 2 /* _NET_WM_STATE_TOGGLE */: { const win = store.getState().windows[wid]; if (!win) { return; } const newMinimized = !win.minimized; if (newMinimized) { wmServer.minimizeWindow(wid); } else { wmServer.restoreWindow(wid); } } break; } } function getWindowTypeFromAtom(typeAtom) { switch (typeAtom) { case atoms._NET_WM_WINDOW_TYPE_DESKTOP: return WindowType.Desktop; case atoms._NET_WM_WINDOW_TYPE_DOCK: return WindowType.Dock; case atoms._NET_WM_WINDOW_TYPE_TOOLBAR: return WindowType.Toolbar; case atoms._NET_WM_WINDOW_TYPE_MENU: return WindowType.Menu; case atoms._NET_WM_WINDOW_TYPE_UTILITY: return WindowType.Utility; case atoms._NET_WM_WINDOW_TYPE_SPLASH: return WindowType.Splash; case atoms._NET_WM_WINDOW_TYPE_DIALOG: return WindowType.Dialog; case atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU: return WindowType.DropdownMenu; case atoms._NET_WM_WINDOW_TYPE_POPUP_MENU: return WindowType.PopupMenu; case atoms._NET_WM_WINDOW_TYPE_TOOLTIP: return WindowType.Tooltip; case atoms._NET_WM_WINDOW_TYPE_NOTIFICATION: return WindowType.Notification; case atoms._NET_WM_WINDOW_TYPE_COMBO: return WindowType.Combo; case atoms._NET_WM_WINDOW_TYPE_DND: return WindowType.DragDrop; case atoms._NET_WM_WINDOW_TYPE_NORMAL: return WindowType.Normal; default: return null; } } return { onScreenCreated({ root }) { X.ChangeProperty( XPropMode.Replace, root, atoms._NET_SUPPORTED, X.atoms.ATOM, 32, numsToBuffer([ atoms._NET_SUPPORTED, atoms._NET_SUPPORTING_WM_CHECK, atoms._NET_WM_NAME, atoms._NET_WM_ICON, atoms._NET_WM_STATE, atoms._NET_WM_STATE_ABOVE, atoms._NET_WM_STATE_FULLSCREEN, atoms._NET_WM_STATE_MAXIMIZED_VERT, atoms._NET_WM_STATE_MAXIMIZED_HORZ, atoms._NET_WM_STATE_HIDDEN, atoms._NET_WM_ALLOWED_ACTIONS, atoms._NET_WM_ACTION_MOVE, atoms._NET_WM_ACTION_RESIZE, atoms._NET_WM_ACTION_MINIMIZE, atoms._NET_WM_ACTION_SHADE, atoms._NET_WM_ACTION_STICK, atoms._NET_WM_ACTION_MAXIMIZE_HORZ, atoms._NET_WM_ACTION_MAXIMIZE_VERT, atoms._NET_WM_ACTION_FULLSCREEN, atoms._NET_WM_ACTION_CHANGE_DESKTOP, atoms._NET_WM_ACTION_CLOSE, atoms._NET_FRAME_EXTENTS, atoms._NET_WM_PID, atoms._NET_WM_MOVERESIZE ]) ); const wid = X.AllocID(); X.CreateWindow(wid, root, -1, -1, 1, 1, 0, XCB_COPY_FROM_PARENT, 0, 0); const widBuffer = numsToBuffer([wid]); X.ChangeProperty(XPropMode.Replace, root, atoms._NET_SUPPORTING_WM_CHECK, X.atoms.WINDOW, 32, widBuffer); X.ChangeProperty(XPropMode.Replace, wid, atoms._NET_SUPPORTING_WM_CHECK, X.atoms.WINDOW, 32, widBuffer); X.ChangeProperty(XPropMode.Replace, wid, atoms._NET_WM_NAME, atoms.UTF8_STRING, 8, "bond-wm"); X.ChangeProperty(XPropMode.Replace, wid, atoms._NET_WM_PID, X.atoms.CARDINAL, 32, numsToBuffer([pid])); }, onClientMessage({ wid, windowType, messageType, data }) { switch (messageType) { case atoms._NET_WM_STATE: { if (windowType === XWMWindowType.Client) { const stateData = data; processWindowStateChange(wid, stateData[0], stateData[1]); if (stateData[2] !== 0) { processWindowStateChange(wid, stateData[0], stateData[2]); } } } break; case atoms._NET_WM_MOVERESIZE: { if (windowType === XWMWindowType.Frame) { const trueWid = getWindowIdFromFrameId(wid); if (typeof trueWid === "number") { wid = trueWid; } } const moveResizeData = data; if (moveResizeData[2] === 11 /* _NET_WM_MOVERESIZE_CANCEL */) { dragModule.endMoveResize(wid); break; } const coords = [moveResizeData[0], moveResizeData[1]]; switch (moveResizeData[2]) { case 8 /* _NET_WM_MOVERESIZE_MOVE */: dragModule.startMove(wid, coords); break; case 0 /* _NET_WM_MOVERESIZE_SIZE_TOPLEFT */: case 1 /* _NET_WM_MOVERESIZE_SIZE_TOP */: case 2 /* _NET_WM_MOVERESIZE_SIZE_TOPRIGHT */: case 3 /* _NET_WM_MOVERESIZE_SIZE_RIGHT */: case 4 /* _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT */: case 5 /* _NET_WM_MOVERESIZE_SIZE_BOTTOM */: case 6 /* _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT */: case 7 /* _NET_WM_MOVERESIZE_SIZE_LEFT */: dragModule.startResize(wid, coords, netWMMoveResizeTypeToInternal(moveResizeData[2])); break; } } break; } }, onMapNotify({ wid, windowType }) { if (windowType === XWMWindowType.Client) { updateWindowStateHints(wid); updateWindowAllowedActions(wid); } }, onUnmapNotify({ wid, windowType }) { if (windowType === XWMWindowType.Client) { removeWindowStateHints(wid); removeWindowAllowedActions(wid); } }, onMinimize({ wid }) { updateWindowStateHints(wid); }, onMaximize({ wid }) { updateWindowStateHints(wid); }, onRestore({ wid }) { updateWindowStateHints(wid); }, onSetFrameExtents({ wid, frameExtents }) { const extentsInts = Buffer.alloc(16); extentsInts.writeInt32LE(frameExtents.left, 0); extentsInts.writeInt32LE(frameExtents.right, 4); extentsInts.writeInt32LE(frameExtents.top, 8); extentsInts.writeInt32LE(frameExtents.bottom, 12); X.ChangeProperty(XPropMode.Replace, wid, atoms._NET_FRAME_EXTENTS, X.atoms.CARDINAL, 32, extentsInts); }, onReduxAction({ action, getState }) { if (setTagCurrentLayoutAction.match(action)) { for (const win of selectWindowsFromTag(getState(), action.payload.screenIndex, action.payload.tag)) { updateWindowAllowedActions(win.id); } } }, async getNetWmType(wid) { const { data } = await getRawPropertyValue(X, wid, atoms._NET_WM_WINDOW_TYPE, X.atoms.ATOM); if (!data) { return null; } const types = []; let i = 0; while (i < data.byteLength) { const typeAtom = data.readInt32LE(i); const type = getWindowTypeFromAtom(typeAtom); if (type !== null) { types.push(type); } i += 4; } if (types.length > 1) { log(`Window ${wid} has more than one type: ${types.join(",")}`); } return types[0] ?? null; }, async getNetWmIcons(wid) { const { data } = await getRawPropertyValue(X, wid, atoms._NET_WM_ICON, X.atoms.CARDINAL); if (!data) { return []; } const icons = []; const dataLength = data.byteLength; let i = 0; while (i < dataLength) { const info = { width: data.readInt32LE(i), height: data.readInt32LE(i + 4), data: [] }; i += 8; for (let j = 0; j < info.width * info.height; j++) { if (i >= dataLength) { logError("Icon data truncated for " + wid); break; } info.data.push(data.readUint32LE(i)); i += 4; } icons.push(info); } return icons; } }; } var init_ewmh = __esm({ "ewmh.ts"() { "use strict"; init_xutils(); init_log(); init_xutils(); } }); // pointer.ts import { geometryContains } from "@bond-wm/shared"; function queryPointer(X, relativeWid) { return new Promise((resolve3, reject) => { X.QueryPointer(relativeWid, (err, result) => { if (err) { reject(err); } else { resolve3(result); } }); }); } async function getScreenIndexWithCursor(context, relativeWid) { const pointerInfo = await queryPointer(context.X, relativeWid); if (!pointerInfo) { return -1; } const screens = context.store.getState().screens.filter((s) => s.root === pointerInfo.root); if (!screens.length) { return -1; } if (screens.length === 1) { return screens[0].index; } for (const screen of screens) { if (geometryContains(screen, pointerInfo.rootX, pointerInfo.rootY)) { return screen.index; } } return screens[0].index; } var init_pointer = __esm({ "pointer.ts"() { "use strict"; } }); // icccm.ts import { XPropMode as XPropMode2, XWMWindowType as XWMWindowType2 } from "@bond-wm/shared"; async function createICCCMEventConsumer({ X, wmServer }) { const atoms = { WM_STATE: await internAtomAsync(X, "WM_STATE"), WM_CHANGE_STATE: await internAtomAsync(X, "WM_CHANGE_STATE") }; function updateWindowState(wid, state = 1 /* NormalState */) { const wmStateBuffer = Buffer.alloc(8); wmStateBuffer.writeUInt32LE(state, 0); wmStateBuffer.writeUInt32LE(0, 4); X.ChangeProperty(XPropMode2.Replace, wid, atoms.WM_STATE, atoms.WM_STATE, 32, wmStateBuffer); } function removeWindowState(wid) { X.DeleteProperty(wid, atoms.WM_STATE, (err) => { if (err) { log("Could not delete WM_STATE"); } }); } return { onMapNotify({ wid, windowType }) { if (windowType === XWMWindowType2.Client) { updateWindowState(wid); } }, onUnmapNotify({ wid, windowType }) { if (windowType === XWMWindowType2.Client) { removeWindowState(wid); } }, onMinimize({ wid }) { updateWindowState(wid, 3 /* IconicState */); }, onRestore({ wid }) { updateWindowState(wid, 1 /* NormalState */); }, onClientMessage({ wid, data, messageType, windowType }) { if (windowType !== XWMWindowType2.Client) { return; } if (messageType === atoms.WM_CHANGE_STATE) { const stateValue = data[0]; switch (stateValue) { case 3 /* IconicState */: wmServer.minimizeWindow(wid); break; case 1 /* NormalState */: wmServer.restoreWindow(wid); break; } } } }; } async function getWMTransientFor(X, wid) { return await getPropertyValue(X, wid, X.atoms.WM_TRANSIENT_FOR, X.atoms.WINDOW); } async function getWMClass(X, wid) { const { data } = await getRawPropertyValue(X, wid, X.atoms.WM_CLASS, X.atoms.STRING); if (!data) { return void 0; } const wmClass = ["", ""]; const firstNullByteIndex = data.indexOf(0); if (firstNullByteIndex > 0) { wmClass[0] = data.toString("utf8", 0, firstNullByteIndex); } if (firstNullByteIndex + 1 < data.length - 1) { wmClass[1] = data.toString("utf8", firstNullByteIndex + 1, data.length - 1); } return wmClass; } async function getWMHints(X, wid) { const { data } = await getRawPropertyValue(X, wid, X.atoms.WM_HINTS, X.atoms.WM_HINTS); if (!data || data.length < SIZEOF_WMHints) { return; } const hints = { flags: data.readInt32LE(0), input: data.readInt32LE(4), initialState: data.readInt32LE(8), iconPixmap: data.readInt32LE(12), iconWindow: data.readInt32LE(16), iconX: data.readInt32LE(20), iconY: data.readInt32LE(24), iconMask: data.readInt32LE(28) }; return hints; } async function getNormalHints(X, wid) { const { data } = await getRawPropertyValue(X, wid, X.atoms.WM_NORMAL_HINTS, X.atoms.WM_SIZE_HINTS); if (!data || data.length < SIZEOF_WMSizeHints) { return; } const hints = { flags: data.readInt32LE(0), minWidth: data.readInt32LE(20), minHeight: data.readInt32LE(24), maxWidth: data.readInt32LE(28), maxHeight: data.readInt32LE(32), widthIncrement: data.readInt32LE(36), heightIncrement: data.readInt32LE(40), minAspect: [data.readInt32LE(44), data.readInt32LE(48)], maxAspect: [data.readInt32LE(52), data.readInt32LE(56)], baseWidth: data.readInt32LE(60), baseHeight: data.readInt32LE(64), gravity: data.readInt32LE(68) }; return hints; } var SIZEOF_WMHints, SIZEOF_WMSizeHints; var init_icccm = __esm({ "icccm.ts"() { "use strict"; init_log(); init_xutils(); SIZEOF_WMHints = 32; SIZEOF_WMSizeHints = 72; } }); // motif.ts async function createMotifModule({ X }) { const atoms = { _MOTIF_WM_HINTS: await internAtomAsync(X, "_MOTIF_WM_HINTS") }; return { async getMotifHints(wid) { const { data } = await getRawPropertyValue(X, wid, atoms._MOTIF_WM_HINTS, atoms._MOTIF_WM_HINTS); if (!data || data.length < SIZEOF_MotifHints) { return; } const hints = { flags: data.readInt32LE(0), functions: data.readInt32LE(4), decorations: !!data.readInt32LE(8), inputMode: data.readInt32LE(12), status: data.readInt32LE(16) }; return hints; } }; } function hasMotifDecorations(motifHints) { if (!motifHints) { return true; } if (motifHints.flags & 2 /* MWM_HINTS_DECORATIONS */) { return motifHints.decorations; } return true; } var SIZEOF_MotifHints; var init_motif = __esm({ "motif.ts"() { "use strict"; init_xutils(); SIZEOF_MotifHints = 20; } }); // menus.ts import { app as app2, BrowserWindow, Menu } from "electron"; import { ContextMenuKind } from "@bond-wm/shared"; function showContextMenu(event, kind, version) { log("Showing context menu (kind=" + ContextMenuKind[kind]); switch (kind) { case ContextMenuKind.Desktop: showDesktopMenu(event, version); break; case ContextMenuKind.Frame: showFrameMenu(event); break; } } function showDesktopMenu(event, version) { const browserWindow = BrowserWindow.fromWebContents(event.sender); if (!browserWindow) { return; } const desktopMenu = Menu.buildFromTemplate([ { label: "bond-wm" + (version ? ` \u2014 ${version}` : ""), enabled: false }, { type: "separator" }, { label: "Reload Desktop", click: () => { browserWindow.reload(); } }, { label: "Desktop Developer Tools", click: () => { browserWindow.webContents.openDevTools(); } }, { label: "Quit", click: () => { app2.quit(); } } ]); desktopMenu.popup({ window: browserWindow }); } function showFrameMenu(event) { const browserWindow = BrowserWindow.fromWebContents(event.sender); if (!browserWindow) { return; } const frameMenu = Menu.buildFromTemplate([ { label: "Reload Frame", click: () => { browserWindow.reload(); } }, { label: "Frame Developer Tools", click: () => { browserWindow.webContents.openDevTools({ mode: "detach" }); } } ]); frameMenu.popup({ window: browserWindow }); } var init_menus = __esm({ "menus.ts"() { "use strict"; init_log(); } }); // exec.ts import { exec } from "child_process"; function execCommand(command, callback) { exec(command, (_error, stdout2) => { callback(stdout2); }); } var init_exec = __esm({ "exec.ts"() { "use strict"; } }); // autocomplete.ts import { ipcMain as ipcMain2 } from "electron"; function setupAutocompleteListener() { ipcMain2.on("completion-options-get", (event) => { getCompletionOptions().then((options) => { event.sender.send("completion-options-result", options); }); }); } function getCompletionOptions() { return new Promise((resolve3) => { try { execCommand("/usr/bin/env bash -c 'compgen -c'", (commands) => { resolve3( commands.split("\n").map((c) => c.trim()).filter((c) => !!c) ); }); } catch { resolve3([]); } }); } var init_autocomplete = __esm({ "autocomplete.ts"() { "use strict"; init_exec(); } }); // window.ts import { arraysEqual, intersect, setWindowTagsAction } from "@bond-wm/shared"; function updateWindowTagsForNextScreen(store, win, nextScreen) { const nextScreenTags = nextScreen.currentTags; const tagIntersect = intersect(win.tags, nextScreenTags); if (tagIntersect.length > 0) { if (!arraysEqual(tagIntersect, win.tags)) { store.dispatch(setWindowTagsAction({ wid: win.id, tags: tagIntersect })); } } else if (nextScreenTags.length > 0) { store.dispatch(setWindowTagsAction({ wid: win.id, tags: [nextScreenTags[0]] })); } } var init_window = __esm({ "window.ts"() { "use strict"; } }); // drag.ts import { configureWindowAction, endDragAction, setWindowIntoScreenAction, startDragAction, XWMWindowType as XWMWindowType3 } from "@bond-wm/shared"; import { selectWindowMaximizeCanTakeEffect as selectWindowMaximizeCanTakeEffect2 } from "@bond-wm/shared"; import { geometryArea, geometryIntersect } from "@bond-wm/shared"; import { getAbsoluteWindowGeometry, newHeightForWindow, newWidthForWindow, ResizeDirection as ResizeDirection2 } from "@bond-wm/shared"; import { XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XEventMask } from "@bond-wm/shared"; async function createDragModule({ X, store, getFrameIdFromWindowId, getWindowIdFromFrameId, getLayoutPlugins }) { function endMoveResize(wid) { const state = store.getState(); const win = state.windows[wid]; if (!win || !win._dragState) { return; } log("Ending drag for " + wid); X.UngrabPointer(XCB_CURRENT_TIME); X.UngrabKeyboard(XCB_CURRENT_TIME); store.dispatch(endDragAction({ wid })); setWindowIntoBestScreen(state.screens, win); } function doGrabsForDrag(wid) { const fid = getFrameIdFromWindowId(wid) ?? wid; X.GrabPointer( fid, false, XEventMask.PointerMotion | XEventMask.ButtonRelease, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, 0, // None 0, // None XCB_CURRENT_TIME, (err) => { if (err) { logError(err); } } ); X.GrabKeyboard(fid, false, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); } function setWindowIntoBestScreen(screens, win) { const prevWinScreen = screens[win.screenIndex]; const bestWinScreen = getBestScreenForWindow(screens, win); if (bestWinScreen && bestWinScreen !== prevWinScreen) { updateWindowTagsForNextScreen(store, win, bestWinScreen); store.dispatch(setWindowIntoScreenAction({ wid: win.id, screenIndex: screens.indexOf(bestWinScreen) })); store.dispatch( configureWindowAction({ wid: win.id, ...win.outer, x: prevWinScreen.x + win.outer.x - bestWinScreen.x, y: prevWinScreen.y + win.outer.y - bestWinScreen.y }) ); } } function getBestScreenForWindow(screens, win) { let bestScreen = null; let bestIntersectArea = Number.MIN_SAFE_INTEGER; const winAbsCoords = getAbsoluteWindowGeometry(screens[win.screenIndex], win); for (const screen of screens) { const intersect2 = geometryIntersect(screen, winAbsCoords); if (!intersect2) { continue; } const intersectArea = geometryArea(intersect2); if (intersectArea > bestIntersectArea) { bestIntersectArea = intersectArea; bestScreen = screen; } } return bestScreen; } return { startMove(wid, coords) { const state = store.getState(); const win = store.getState().windows[wid]; if (!win || win._dragState || win.maximized && selectWindowMaximizeCanTakeEffect2(state, getLayoutPlugins(win.screenIndex), wid) || win.fullscreen) { return; } log("Starting drag for " + wid, coords); store.dispatch(startDragAction({ wid, coords, moving: true })); doGrabsForDrag(wid); }, startResize(wid, coords, direction) { const state = store.getState(); const win = store.getState().windows[wid]; if (!win || win._dragState || win.maximized && selectWindowMaximizeCanTakeEffect2(state, getLayoutPlugins(win.screenIndex), wid) || win.fullscreen) { log("Choosing to not start resize for " + wid, coords, ResizeDirection2[direction]); return; } log("Starting resize for " + wid, coords, ResizeDirection2[direction]); store.dispatch(startDragAction({ wid, coords, resize: direction })); doGrabsForDrag(wid); }, endMoveResize, onPointerMotion({ wid, windowType, rootx, rooty }) { let fid; if (windowType === XWMWindowType3.Frame) { fid = wid; wid = getWindowIdFromFrameId(fid); } else if (windowType === XWMWindowType3.Client) { fid = getFrameIdFromWindowId(wid); } const win = store.getState().windows[wid]; if (!win || !win._dragState || !win._dragState.startOuterSize || !win._dragState.startCoordinates) { return; } const { startOuterSize, startCoordinates } = win._dragState; const xDiff = rootx - startCoordinates[0]; const yDiff = rooty - startCoordinates[1]; function configureWindow(win2, newConfig) { store.dispatch( configureWindowAction({ wid: win2.id, ...startOuterSize, x: typeof newConfig.x === "number" ? newConfig.x : void 0, y: typeof newConfig.y === "number" ? newConfig.y : void 0, width: typeof newConfig.width === "number" ? newWidthForWindow(win2, newConfig.width) : void 0, height: typeof newConfig.height === "number" ? newHeightForWindow(win2, newConfig.height) : void 0 }) ); } if (win._dragState.moving) { configureWindow(win, { x: startOuterSize.x + xDiff, y: startOuterSize.y + yDiff }); return; } if (typeof win._dragState.resize === "number") { switch (win._dragState.resize) { case ResizeDirection2.TopLeft: { const desiredWidth = startOuterSize.width - xDiff; const desiredHeight = startOuterSize.height - yDiff; const snappedWidth = newWidthForWindow(win, desiredWidth); const snappedHeight = newHeightForWindow(win, desiredHeight); configureWindow(win, { x: startOuterSize.x + (startOuterSize.width - snappedWidth), y: startOuterSize.y + (startOuterSize.height - snappedHeight), width: snappedWidth, height: snappedHeight }); } break; case ResizeDirection2.Top: { const desiredHeight = startOuterSize.height - yDiff; const snappedHeight = newHeightForWindow(win, desiredHeight); configureWindow(win, { y: startOuterSize.y + (startOuterSize.height - snappedHeight), height: snappedHeight }); } break; case ResizeDirection2.TopRight: { const desiredWidth = startOuterSize.width + xDiff; const desiredHeight = startOuterSize.height - yDiff; const snappedWidth = newWidthForWindow(win, desiredWidth); const snappedHeight = newHeightForWindow(win, desiredHeight); configureWindow(win, { y: startOuterSize.y + (startOuterSize.height - snappedHeight), width: snappedWidth, height: snappedHeight }); } break; case ResizeDirection2.Right: configureWindow(win, { width: startOuterSize.width + xDiff }); break; case ResizeDirection2.BottomRight: