bond-wm
Version:
An X Window Manager built on web technologies.
1,569 lines (1,550 loc) • 116 kB
JavaScript
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;
}
});
// 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,
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, getWindowIdFromFrameId }, 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_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);
}
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 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;
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 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_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);
}
},
onUnmapNotify({ wid, windowType }) {
if (windowType === XWMWindowType.Client) {
removeWindowStateHints(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);
},
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 }) {
const atoms = {
WM_STATE: await internAtomAsync(X, "WM_STATE")
};
function updateWindowState(wid) {
const wmStateBuffer = Buffer.alloc(8);
wmStateBuffer.writeUInt32LE(1 /* NormalState */, 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);
}
}
};
}
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, 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: () => {
app.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 } from "electron";
function setupAutocompleteListener() {
ipcMain.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 } 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 && selectWindowMaximizeCanTakeEffect(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 && selectWindowMaximizeCanTakeEffect(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:
configureWindow(win, {
x: startOuterSize.x + xDiff,
y: startOuterSize.y + yDiff,
width: startOuterSize.width - xDiff,
height: startOuterSize.height - yDiff
});
break;
case ResizeDirection2.Top:
configureWindow(win, {
y: startOuterSize.y + yDiff,
height: startOuterSize.height - yDiff
});
break;
case ResizeDirection2.TopRight:
configureWindow(win, {
y: startOuterSize.y + yDiff,
width: startOuterSize.width + xDiff,
height: startOuterSize.height - yDiff
});
break;
case ResizeDirection2.Right:
configureWindow(win, {
width: startOuterSize.width + xDiff
});
break;
case ResizeDirection2.BottomRight:
configureWindow(win, {
width: startOuterSize.width + xDiff,
height: startOuterSize.height + yDiff
});
break;
case ResizeDirection2.Bottom:
configureWindow(win, {
height: startOuterSize.height + yDiff
});
break;
case ResizeDirection2.BottomLeft:
configureWindow(win, {
x: startOuterSize.x + xDiff,
width: startOuterSize.width - xDiff,
height: startOuterSize.height + yDiff
});
break;
case ResizeDirection2.Left:
configureWindow(win, {
x: startOuterSize.x + xDiff,
width: startOuterSize.width - xDiff
});
break;
}
}
},
onButtonRelease({ wid, windowType }) {
let fid;
if (windowType === XWMWindowType3.Frame) {
fid = wid;
wid = getWindowIdFromFrameId(fid);
} else if (windowType === XWMWindowType3.Client) {
fid = getFrameIdFromWindowId(wid);
}
endMoveResize(wid);
},
onKeyPress({ wid, windowType }) {
let fid;
if (windowType === XWMWindowType3.Frame) {
fid = wid;
wid = getWindowIdFromFrameId(fid);
} else if (windowType === XWMWindowType3.Client) {
fid = getFrameIdFromWindowId(wid);
}
endMoveResize(wid);
return false;
}
};
}
var init_drag = __esm({
"drag.ts"() {
"use strict";
init_log();
init_window();
}
});
// shortcuts.ts
import { X11_KEY_MODIFIER } from "@bond-wm/shared";
import * as nodeKeySym from "@bond-wm/keysym";
async function createShortcutsModule({ X, XDisplay }) {
const mapping = await getKeyboardMapping(XDisplay);
const keycodeToKeysyms = [];
const keysymsToKeycode = [];
const keysymsToKeycodeShift = [];
for (let i = 0; i < mapping.length; i++) {
const keycode = XDisplay.min_keycode + i;
const keysyms = mapping[i];
keycodeToKeysyms[keycode] = keysyms;
if (keysyms[0] > 0) {
keysymsToKeycode[keysyms[0]] = keycode;
}
if (keysyms[1] > 0) {
keysymsToKeycodeShift[keysyms[1]] = keycode;
}
}
const processedRegisteredKeys = {};
function getXModifierForShortcutPiece(piece) {
switch (piece.toLowerCase()) {
case "shift":
return X11_KEY_MODIFIER.ShiftMask;
case "ctrl":
case "ctl":
case "control":
return X11_KEY_MODIFIER.ControlMask;
case "mod4":
case "win":
return X11_KEY_MODIFIER.Mod4Mask;
default:
return null;
}
}
function registerShortcut(rootWid, keyString, callback) {
const pieces = keyString.split("+").map((s) => s.trim()).filter((s) => !!s);
if (pieces.length === 0) {
return;
}
let xModifiers = 0;
for (let i = 0; i < pieces.length - 1; i++) {
const xModifier = getXModifierForShortcutPiece(pieces[i]);
if (typeof xModifier === "number") {
xModifiers |= xModifier;
} else {
logError("Unrecognized key modifier: " + pieces[i]);
}
}
const lastPiece = pieces[pieces.length - 1];
if (!lastPiece) {
return;
}
const hasShift = !!(xModifiers & X11_KEY_MODIFIER.ShiftMask);
let keySym = nodeKeySym.fromName(hasShift ? toUpper(lastPiece) : toLower(lastPiece));
if (!keySym) {
keySym = nodeKeySym.fromName(lastPiece);
}
const keySymMap = hasShift ? keysymsToKeycodeShift : keysymsToKeycode;
const keySymMapFallback = hasShift ? keysymsToKeycode : keysymsToKeycodeShift;
const keycode = keySymMap[keySym?.keysym ?? -1] ?? keySymMapFallback[keySym?.keysym ?? -1];
if (keycode > 0) {
processedRegisteredKeys[xModifiers] ||= {};
if (!processedRegisteredKeys[xModifiers][keycode]) {
processedRegisteredKeys[xModifiers][keycode] = {
originalKeyString: keyString,
callback
};
X.GrabKey(
rootWid,
true,
xModifiers,
keycode,
1,
1
/* Async */
);
log(`Registered modifiers: ${xModifiers}, keycode: ${keycode} for ${keyString}`);
}
} else {
logError("Could not register " + keyString);
}
}
return {
registerShortcuts(rootWid, registeredKeys) {
for (const keyString in registeredKeys) {
registerShortcut(rootWid, keyString, registeredKeys[keyString]);
}
},
registerShortcut,
onKeyPress(args) {
const { keycode, modifiers } = args;
const keysyms = keycodeToKeysyms[keycode];
log("keysyms", keysyms);
if (keysyms) {
const keysym = keysyms[modifiers & X11_KEY_MODIFIER.ShiftMask ? 1 : 0];
if (keysym) {
log("keysym", keysym);
log("fromKeysym", nodeKeySym.fromKeysym(keysym));
}
}
if (processedRegisteredKeys[args.modifiers]) {
const info = processedRegisteredKeys[args.modifiers][args.keycode];
if (typeof info === "object" && typeof info.callback === "function") {
log(`Running ${info.originalKeyString} shortcut handler`);
args.originalKeyString = info.originalKeyString;
info.callback(args);
return true;
}
}
return false;
}
};
}
async function getKeyboardMapping(XDisplay) {
return new Promise((resolve3, reject) => {
const { min_keycode, max_keycode } = XDisplay;
XDisplay.client.GetKeyboardMapping(min_keycode, max_keycode - min_keycode, (err, list) => {
if (err) {
reject(err);
return;
}
resolve3(list);
});
});
}
function toUpper(value) {
if (value in _toUpperMap) {
return _toUpperMap[value];
}
return value.toUpperCase();
}
function toLower(value) {
if (value in _toLowerMap) {
return _toLowerMap[value];
}
return value.toLowerCase();
}
var _toUpperMap, _toLowerMap;
var init_shortcuts = __esm({
"shortcuts.ts"() {
"use strict";
init_log();
_toUpperMap = Object.assign(/* @__PURE__ */ Object.create(null), {
"0": ")",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"`": "~"
});
_toLowerMap = /* @__PURE__ */ Object.create(null);
for (const lower in _toUpperMap) {
_toLowerMap[_toUpperMap[lower]] = lower;
}
}
});
// assert.ts
import { strict } from "assert";
var init_assert = __esm({
"assert.ts"() {
"use strict";
}
});
// xdg.ts
import { env } from "node:process";
import { join } from "node:path";
import { existsSync } from "node:fs";
function getXDGConfigHome() {
let XDG_CONFIG_HOME = env["XDG_CONFIG_HOME"];
if (!XDG_CONFIG_HOME) {
const HOME = env["HOME"] || "~";
XDG_CONFIG_HOME = join(HOME, ".config");
}
return XDG_CONFIG_HOME;
}
async function getXDGUserDirectory(kind) {
if (!_userDirs) {
_userDirs = await getXdgUserDirs();
}
let dir;
switch (kind) {
case 0 /* Desktop */:
dir = _userDirs?.XDG_DESKTOP_DIR ?? null;
break;
case 1 /* Documents */:
dir = _userDirs?.XDG_DOCUMENTS_DIR ?? null;
break;
case 2 /* Download */:
dir = _userDirs?.XDG_DOWNLOAD_DIR ?? null;
break;
case 3 /* Music */:
dir = _userDirs?.XDG_MUSIC_DIR ?? null;
break;
case 4 /* Pictures */:
dir = _userDirs?.XDG_PICTURES_DIR ?? null;
break;
case 5 /* PublicShare */:
dir = _userDirs?.XDG_PUBLICSHARE_DIR ?? null;
break;
case 6 /* Templates */:
dir = _userDirs?.XDG_TEMPLATES_DIR ?? null;
break;
case 7 /* Videos */:
dir = _userDirs?.XDG_VIDEOS_DIR ?? null;
break;
default:
throw new Error("Unknown XDG user directory kind");
}
if (dir && !existsSync(dir)) {
dir = null;
}
return dir;
}
var getXdgUserDirs, _userDirs;
var init_xdg = __esm({
"xdg.ts"() {
"use strict";
getXdgUserDirs = __require("xdg-user-dir");
_userDirs = null;
}
});
// config.ts
import { existsSync as existsSync2 } from "node:fs";
import { dirname, join as join2, resolve, sep } from "node:path";
import { setConfigPath, setConfigPathAction } from "@bond-wm/shared";
async function determineConfigPath(store) {
let configPath = getArgs().config;
if (configPath) {
if (configPath.startsWith(".")) {
configPath = resolve(configPath);
} else if (!configPath.startsWith(sep)) {
configPath = dirname(__require.resolve(`${configPath}/package.json`));
}
if (!existsSync2(configPath)) {
throw new Error(`The --config path ${configPath} failed to resolve or does not exist.`);
}
} else {
const XDG_CONFIG_HOME = getXDGConfigHome();
log("XDG_CONFIG_HOME", XDG_CONFIG_HOME);
configPath = join2(XDG_CONFIG_HOME, "bond-wm-config");
if (!existsSync2(configPath)) {
throw new Error("No --config path was specified, and no default config locations existed.");
}
}
setConfigPath(configPath);
store.dispatch(setConfigPathAction(configPath));
return configPath;
}
var init_config = __esm({
"config.ts"() {
"use strict";
init_log();
init_args();
init_xdg();
}
});
// systray.ts
import {
addTrayWindowAction,
configureTrayWindowAction,
removeTrayWindowAction,
setTrayBackgroundColorAction
} from "@bond-wm/shared";
import { X11_EVENT_TYPE, XCB_COPY_FROM_PARENT as XCB_COPY_FROM_PARENT2, XPropMode as XPropMode3 } from "@bond-wm/shared";
async function createTrayEventConsumer({ X, store, XDisplay }) {
const TraySelectionAtom = `_NET_SYSTEM_TRAY_S${X.screenNum}`;
const atoms = {
MANAGER: await internAtomAsync(X, "MANAGER"),
[TraySelectionAtom]: await internAtomAsync(X, TraySelectionAtom),
_NET_SYSTEM_TRAY_OPCODE: await internAtomAsync(X, "_NET_SYSTEM_TRAY_OPCODE"),
_NET_SYSTEM_TRAY_ORIENTATION: await internAtomAsync(X, "_NET_SYSTEM_TRAY_ORIENTATION"),
_NET_SYSTEM_TRAY_MESSAGE_DATA: await internAtomAsync(X, "_NET_SYSTEM_TRAY_MESSAGE_DATA")
};
let _registered = false;
let _trayOwnerWid = 0;
let _currentColorPixel;
const frameBrowserWinIdToFrameId = /* @__PURE__ */ new Map();
const _notificationState = {};
function isTrayWin(win) {
return win in store.getState().tray.windows;
}
function dockTrayWindow(trayWid) {
if (isTrayWin(trayWid)) {
return;
}
store.dispatch(addTrayWindowAction({ wid: trayWid }));
changeWindowEventMask(X, trayWid, TRAY_WIN_EVENT_MASK);
X.ChangeWindowAttributes(trayWid, {
backgroundPixel: _currentColorPixel
});
X.ConfigureWindow(trayWid, { width: 16, height: 16 });
}
return {
onScreenCreated(args) {
if (_registered) {
return;
}
_registered = true;
if (typeof _currentColorPixel !== "number") {
_currentColorPixel = XDisplay.screen[0].black_pixel;
}
_trayOwnerWid = X.AllocID();
X.CreateWindow(
_trayOwnerWid,
args.root,
-1,
-1,
1,
1,
0,
XCB_COPY_FROM_PARENT2,
1,
XDisplay.screen[0].root_visual,
{
colormap: XDisplay.screen[0].default_colormap,
backgroundPixel: _currentColorPixel,
borderPixel: 0
}
);
changeWindowEventMask(X, _trayOwnerWid, TRAY_OWNER_EVENT_MASK);
X.ChangeProperty(
XPropMode3.Replace,
_trayOwnerWid,
atoms._NET_SYSTEM_TRAY_ORIENTATION,
X.atoms.INTEGER,
32,
numsToBuffer([0 /* _NET_SYSTEM_TRAY_ORIENTATION_HORZ */])
);
const selection = atoms[TraySelectionAtom];
const eventData = Buffer.alloc(32);
eventData.writeUInt8(X11_EVENT_TYPE.ClientMessage, 0);
eventData.writeUInt8(32, 1);
eventData.writeUInt32LE(args.root, 4);
eventData.writeUInt32LE(atoms.MANAGER, 8);
eventData.writeUInt32LE(0, 12);
eventData.writeUInt32LE(selection, 16);
eventData.writeUInt32LE(_trayOwnerWid, 20);
eventData.writeUInt32LE(0, 24);
eventData.writeUInt32LE(0, 28);
X.SetSelectionOwner(_trayOwnerWid, selection);
X.SendEvent(args.root, false, 16777215, eventData);
log(`Registered ${_trayOwnerWid} as tray selection owner for ${TraySelectionAtom}.`);
},
onUnmapNotify(args) {
if (isTrayWin(args.wid)) {
store.dispatch(removeTrayWindowAction(args.wid));
const frameId = frameBrowserWinIdToFrameId.get(args.wid);
if (typeof frameId === "number") {
X.DestroyWindow(frameId);
}
}
},
onClientMessage(args) {
if (args.messageType === atoms._NET_SYSTEM_TRAY_OPCODE) {
switch (args.data[1]) {
case 0 /* SYSTEM_TRAY_REQUEST_DOCK */:
{
const widToDock = args.data[2];
log(`SYSTEM_TRAY_REQUEST_DOCK, widToDock=${widToDock}`);
dockTrayWindow(widToDock);
}
break;
case 1 /* SYSTEM_TRAY_BEGIN_MESSAGE */:
{
const trayWid = args.wid;
const timeout = args.data[2];
const messageLength = args.data[3];
const messageId = args.data[4];
log(
`SYSTEM_TRAY_BEGIN_MESSAGE, trayWid=${trayWid}, id=${messageId}, len=${messageLength}, timeout=${timeout}`
);
_notificationState[trayWid][messageId] = {
text: "",
totalSize: messageLength,
receivedSize: 0
};
}
break;
case 2 /* SYSTEM_TRAY_CANCEL_MESSAGE */:
{
const trayWid = args.wid;
const messageId = args.data[2];
log(`SYSTEM_TRAY_CANCEL_MESSAGE, trayWid=${trayWid}, id=${messageId}`);
delete _notificationState[trayWid][messageId];
}
break;
default:
log("Unhandled system tray op", args.data[1], SystemTrayOps[args.data[1]]);
break;
}
} else if (args.messageType === atoms._NET_SYSTEM_TRAY_MESSAGE_DATA) {
const trayWid = args.wid;
let stateEntry;
for (const messageId in _notificationState[trayWid]) {
if (stateEntry) {
logError(`_NET_SYSTEM_TRAY_MESSAGE_DATA: Unexpected: multiple notification entries`);
}
stateEntry = _notificationState[trayWid][messageId];
}
if (!stateEntry) {
logError(`_NET_SYSTEM_TRAY_MESSAGE_DATA: Unexpected: message data for non-existent notification`);
return;
}
const sizeToRead = Math.min(20, stateEntry.totalSize - stateEntry.receivedSize);
const textBuffer = numsToBuffer(args.data);
const partialText = textBuffer.toString("utf8", 0, sizeToRead);
log(`_NET_SYSTEM_TRAY_MESSAGE_DATA, trayWid=${trayWid}, partial=${partialText}`);
stateEntry.text += partialText;
stateEntry.receivedSize += sizeToRead;
if (stateEntry.receivedSize === stateEntry.totalSize) {
log(`_NET_SYSTEM_TRAY_MESSAGE_DATA, trayWid=${trayWid}, message complete=${stateEntry.text}`);
}
}
},
onReduxAction(args) {
if (configureTrayWindowAction.match(args.action)) {
const state = args.getState();
const payload = args.action.payload;
const wid = payload.wid;
const win = state.tray.windows[wid];
if (!win) {
return;
}
const screen = state.screens[payload.screenIndex];
const trayConfig = {
x: screen.x + payload.x,
y: screen.y + payload.y,
width: payload.width,
height: payload.height
};
log(`Configuring tray window ${wid}`, trayConfig);
const frameWid = frameBrowserWinIdToFrameId.get(wid);
if (typeof frameWid === "number") {
X.ConfigureWindow(frameWid, trayConfig);
X.ConfigureWindow(wid, {
x: 0,
y: 0,
width: payload.width,
height: payload.height
});
} else {
X.ConfigureWindow(wid, trayConfig);
}
X.MapWindow(wid);
if (typeof frameWid === "number") {
X.RaiseWindow(frameWid);
}
X.RaiseWindow(wid);
} else if (setTrayBackgroundColorAction.ma