"use strict"; const Shortcut = { FocusNext: 1, FocusPrev: 2, DWMLeft: 3, DWMRight: 4, FocusUp: 5, FocusDown: 6, FocusLeft: 7, FocusRight: 8, ShiftLeft: 9, ShiftRight: 10, ShiftUp: 11, ShiftDown: 12, SwapUp: 13, SwapDown: 14, SwapLeft: 15, SwapRight: 16, GrowWidth: 17, GrowHeight: 18, ShrinkWidth: 19, ShrinkHeight: 20, Increase: 21, Decrease: 22, ShiftIncrease: 22, ShiftDecrease: 23, ToggleFloat: 24, ToggleFloatAll: 25, SetMaster: 26, NextLayout: 27, PreviousLayout: 28, SetLayout: 29, Rotate: 30, RotatePart: 31, ToggleDock: 32, }; const ShortcutsKeys = Object.keys(Shortcut); const LogModules = { newWindowAdded: 1, newWindowFiltered: 2, newWindowUnmanaged: 3, screensChanged: 4, virtualScreenGeometryChanged: 5, currentActivityChanged: 6, currentDesktopChanged: 7, windowAdded: 8, windowActivated: 9, windowRemoved: 10, activitiesChanged: 11, bufferGeometryChanged: 12, desktopsChanged: 13, fullScreenChanged: 14, interactiveMoveResizeStepped: 15, maximizedAboutToChange: 16, minimizedChanged: 17, moveResizedChanged: 18, outputChanged: 19, shortcut: 20, arrangeScreen: 21, printConfig: 22, setTimeout: 23, window: 24, }; const LogModulesKeys = Object.keys(LogModules); const LogPartitions = { newWindow: { number: 100, name: "newWindow", modules: [ LogModules.newWindowAdded, LogModules.newWindowFiltered, LogModules.newWindowUnmanaged, ], }, workspaceSignals: { number: 200, name: "workspaceSignal", modules: [ LogModules.screensChanged, LogModules.virtualScreenGeometryChanged, LogModules.currentActivityChanged, LogModules.currentDesktopChanged, LogModules.windowAdded, LogModules.windowActivated, LogModules.windowRemoved, ], }, windowSignals: { number: 300, name: "windowSignal", modules: [ LogModules.activitiesChanged, LogModules.bufferGeometryChanged, LogModules.desktopsChanged, LogModules.fullScreenChanged, LogModules.interactiveMoveResizeStepped, LogModules.maximizedAboutToChange, LogModules.minimizedChanged, LogModules.moveResizedChanged, LogModules.outputChanged, ], }, other: { number: 1000, name: "other", modules: [ LogModules.shortcut, LogModules.arrangeScreen, LogModules.printConfig, LogModules.setTimeout, LogModules.window, ], }, }; let CONFIG; let LOG; class Dock { constructor(cfg, priority = 0) { this.renderedOutputId = ""; this.renderedTime = null; this.priority = priority; this.position = null; this.cfg = Object.assign({}, cfg); this.autoDock = false; } clone() { let dock = new Dock(this.cfg, this.priority); dock.renderedOutputId = this.renderedOutputId; dock.renderedTime = this.renderedTime; dock.position = this.position; dock.autoDock = this.autoDock; return dock; } } class dockSurfaceCfg { constructor(outputName, activityId, vDesktopName, cfg) { this.outputName = outputName; this.activityId = activityId; this.vDesktopName = vDesktopName; this.cfg = cfg; } isFit(srf) { return ((this.outputName === "" || this.outputName === srf.output.name) && (this.vDesktopName === "" || this.vDesktopName === srf.desktop.name) && (this.activityId === "" || this.activityId === srf.activity)); } toString() { return `dockSurface: Output Name: ${this.outputName}, Activity ID: ${this.activityId}, Virtual Desktop Name: ${this.vDesktopName} cfg: ${this.cfg}`; } } const DockPosition = { left: 1, right: 2, top: 3, bottom: 4, }; const HDockAlignment = { center: 1, left: 2, right: 3, }; const VDockAlignment = { center: 1, top: 2, bottom: 3, }; const EdgeAlignment = { outside: 1, middle: 2, inside: 3, }; class DefaultDockCfg { constructor() { let hHeight = validateNumber(CONFIG.dockHHeight, 1, 50); if (hHeight instanceof Err) { warning(`getDefaultCfg: hHeight: ${hHeight}`); this.hHeight = 25; } else this.hHeight = hHeight; let hWide = validateNumber(CONFIG.dockHWide, 1, 100); if (hWide instanceof Err) { warning(`getDefaultCfg: hWide: ${hWide}`); this.hWide = 100; } else this.hWide = hWide; let vHeight = validateNumber(CONFIG.dockVHeight, 1, 100); if (vHeight instanceof Err) { warning(`getDefaultCfg: vHeight: ${vHeight}`); this.vHeight = 100; } else this.vHeight = vHeight; let vWide = validateNumber(CONFIG.dockVWide, 1, 50); if (vWide instanceof Err) { warning(`getDefaultCfg: vWide: ${vWide}`); this.vWide = 25; } else this.vWide = vWide; let hGap = validateNumber(CONFIG.dockHGap); if (hGap instanceof Err) { warning(`getDefaultCfg: hGap: ${hGap}`); this.hGap = 0; } else this.hGap = hGap; let hEdgeGap = validateNumber(CONFIG.dockHEdgeGap); if (hEdgeGap instanceof Err) { warning(`getDefaultCfg: hEdgeGap: ${hEdgeGap}`); this.hEdgeGap = 0; } else this.hEdgeGap = hEdgeGap; let vGap = validateNumber(CONFIG.dockVGap); if (vGap instanceof Err) { warning(`getDefaultCfg: vGap: ${vGap}`); this.vGap = 0; } else this.vGap = vGap; let vEdgeGap = validateNumber(CONFIG.dockVEdgeGap); if (vEdgeGap instanceof Err) { warning(`getDefaultCfg: vEdgeGap: ${vEdgeGap}`); this.vEdgeGap = 0; } else this.vEdgeGap = vEdgeGap; let hAlignmentNumber = validateNumber(CONFIG.dockHAlignment); if (hAlignmentNumber instanceof Err) { warning(`getDefaultCfg: hAlignment: ${hAlignmentNumber}`); this.hAlignment = HDockAlignment.center; } else { switch (hAlignmentNumber) { case 0: this.hAlignment = HDockAlignment.center; break; case 1: this.hAlignment = HDockAlignment.left; break; case 2: this.hAlignment = HDockAlignment.right; break; default: warning(`getDefaultCfg: hAlignment:${hAlignmentNumber} is not a valid alignment`); this.hAlignment = HDockAlignment.center; break; } } let hEdgeAlignmentNumber = validateNumber(CONFIG.dockHEdgeAlignment); if (hEdgeAlignmentNumber instanceof Err) { warning(`getDefaultCfg: hEdgeAlignment: ${hEdgeAlignmentNumber}`); this.hEdgeAlignment = EdgeAlignment.outside; } else { switch (hEdgeAlignmentNumber) { case 0: this.hEdgeAlignment = EdgeAlignment.outside; break; case 1: this.hEdgeAlignment = EdgeAlignment.middle; break; case 2: this.hEdgeAlignment = EdgeAlignment.inside; break; default: warning(`getDefaultCfg: hEdgeAlignment:${hEdgeAlignmentNumber} is not a valid alignment`); this.hEdgeAlignment = EdgeAlignment.outside; break; } } let vAlignmentNumber = validateNumber(CONFIG.dockVAlignment); if (vAlignmentNumber instanceof Err) { warning(`getDefaultCfg: vAlignment: ${vAlignmentNumber}`); this.vAlignment = VDockAlignment.center; } else { switch (vAlignmentNumber) { case 0: this.vAlignment = VDockAlignment.center; break; case 1: this.vAlignment = VDockAlignment.top; break; case 2: this.vAlignment = VDockAlignment.bottom; break; default: warning(`getDefaultCfg: vAlignment: ${vAlignmentNumber} is not valid alignment`); this.vAlignment = VDockAlignment.center; break; } } let vEdgeAlignmentNumber = validateNumber(CONFIG.dockVEdgeAlignment); if (vEdgeAlignmentNumber instanceof Err) { warning(`getDefaultCfg: vEdgeAlignment: ${vEdgeAlignmentNumber}`); this.vEdgeAlignment = EdgeAlignment.outside; } else { switch (vEdgeAlignmentNumber) { case 0: this.vEdgeAlignment = EdgeAlignment.outside; break; case 1: this.vEdgeAlignment = EdgeAlignment.middle; break; case 2: this.vEdgeAlignment = EdgeAlignment.inside; break; default: warning(`getDefaultCfg: vEdgeAlignment:${vEdgeAlignmentNumber} is not a valid alignment`); this.vEdgeAlignment = EdgeAlignment.outside; break; } } } static get instance() { if (!DefaultDockCfg._dockInstance) { DefaultDockCfg._dockInstance = new DefaultDockCfg(); } return DefaultDockCfg._dockInstance; } cloneAndUpdate(cfg) { return Object.assign({}, DefaultDockCfg.instance, cfg); } } class DockEntry { constructor(cfg, id) { this.surfaceCfg = cfg; this._slots = this.parseSlots(); this._id = id; } get id() { return this._id; } get slots() { return this._slots; } remove(window) { for (let slot of this.slots) { if (slot.window === window) { slot.window = null; break; } } } arrange(dockedWindows, workingArea) { const IS_WIDE = workingArea.width > workingArea.height; let dockCfg; let renderedTime = new Date().getTime(); let renderedOutputId = this.id; this.arrangeSlots(dockedWindows); this.assignSizes(workingArea); const leftSlot = this.getSlot(DockPosition.left); const rightSlot = this.getSlot(DockPosition.right); const topSlot = this.getSlot(DockPosition.top); const bottomSlot = this.getSlot(DockPosition.bottom); let leftBorder = workingArea.x; let width = workingArea.width; let topBorder = workingArea.y; let height = workingArea.height; function leftSlotArrange() { if (leftSlot !== null) { dockCfg = leftSlot.window.dock.cfg; let leftSlotHeight = Math.min((workingArea.height * dockCfg.vHeight) / 100, height); let initRect = new Rect(leftBorder, topBorder, (workingArea.width * dockCfg.vWide) / 100, leftSlotHeight); leftBorder = leftBorder + initRect.width; width = width - initRect.width; leftSlot.window.geometry = DockEntry.align(initRect, leftSlot.position, dockCfg, height); leftSlot.window.dock.renderedTime = renderedTime; leftSlot.window.dock.renderedOutputId = renderedOutputId; } } function rightSlotArrange() { if (rightSlot !== null) { dockCfg = rightSlot.window.dock.cfg; let rightSlotHeight = Math.min((workingArea.height * dockCfg.vHeight) / 100, height); let initRect = new Rect(workingArea.x + workingArea.width - (workingArea.width * dockCfg.vWide) / 100, topBorder, (workingArea.width * dockCfg.vWide) / 100, rightSlotHeight); width = width - initRect.width; rightSlot.window.geometry = DockEntry.align(initRect, rightSlot.position, dockCfg, height); rightSlot.window.dock.renderedTime = renderedTime; rightSlot.window.dock.renderedOutputId = renderedOutputId; } } function topSlotArrange() { if (topSlot !== null) { dockCfg = topSlot.window.dock.cfg; let topSlotWidth = Math.min((workingArea.width * dockCfg.hWide) / 100, width); let initRect = new Rect(leftBorder, topBorder, topSlotWidth, (workingArea.height * dockCfg.hHeight) / 100); topBorder = topBorder + initRect.height; height = height - initRect.height; topSlot.window.geometry = DockEntry.align(initRect, topSlot.position, dockCfg, width); topSlot.window.dock.renderedTime = renderedTime; topSlot.window.dock.renderedOutputId = renderedOutputId; } } function bottomSlotArrange() { if (bottomSlot !== null) { dockCfg = bottomSlot.window.dock.cfg; let bottomSlotWidth = Math.min((workingArea.width * dockCfg.hWide) / 100, width); let initRect = new Rect(leftBorder, workingArea.y + workingArea.height - (workingArea.height * dockCfg.hHeight) / 100, bottomSlotWidth, (workingArea.height * dockCfg.hHeight) / 100); height = height - initRect.height; bottomSlot.window.geometry = DockEntry.align(initRect, bottomSlot.position, dockCfg, width); bottomSlot.window.dock.renderedTime = renderedTime; bottomSlot.window.dock.renderedOutputId = renderedOutputId; } } if (IS_WIDE) { leftSlotArrange(); rightSlotArrange(); topSlotArrange(); bottomSlotArrange(); } else { topSlotArrange(); bottomSlotArrange(); leftSlotArrange(); rightSlotArrange(); } return new Rect(leftBorder, topBorder, width, height); } handleShortcut(window, shortcut) { const slot = this.getSlotByWindow(window); let desiredPosition; if (!slot || !window.dock) { return false; } switch (shortcut) { case Shortcut.SwapLeft: desiredPosition = DockPosition.left; break; case Shortcut.SwapUp: desiredPosition = DockPosition.top; break; case Shortcut.SwapRight: desiredPosition = DockPosition.right; break; case Shortcut.SwapDown: desiredPosition = DockPosition.bottom; break; default: return false; } if (slot.position !== desiredPosition) { const desiredSlot = this.getSlotByPosition(desiredPosition); if (desiredSlot !== null && desiredSlot.window === null) { window.dock.position = desiredPosition; return true; } } return false; } static align(initRect, position, cfg, wholeSize) { let leftBorder = initRect.x; let topBorder = initRect.y; let width = initRect.width; let height = initRect.height; switch (position) { case DockPosition.left: case DockPosition.right: [width, leftBorder] = DockEntry.alignEdge(width, leftBorder, cfg.vEdgeGap, position, cfg.vEdgeAlignment); if (2 * cfg.vGap < height) { if (wholeSize - height < 2 * cfg.vGap) { topBorder = topBorder + cfg.vGap; height = height - 2 * cfg.vGap; } else if (cfg.vAlignment === VDockAlignment.top) { topBorder = topBorder + cfg.vGap; } else if (cfg.vAlignment === VDockAlignment.center) { topBorder = topBorder + (wholeSize - height) / 2; } else if (cfg.vAlignment === VDockAlignment.bottom) { topBorder = topBorder + (wholeSize - height - cfg.vGap); } } return new Rect(leftBorder, topBorder, width, height); case DockPosition.top: case DockPosition.bottom: [height, topBorder] = DockEntry.alignEdge(height, topBorder, cfg.hEdgeGap, position, cfg.hEdgeAlignment); if (2 * cfg.hGap < width) { if (wholeSize - width < 2 * cfg.hGap) { leftBorder = leftBorder + cfg.hGap; width = width - 2 * cfg.hGap; } else if (cfg.hAlignment === HDockAlignment.left) { leftBorder = leftBorder + cfg.hGap; } else if (cfg.hAlignment === HDockAlignment.center) { leftBorder = leftBorder + (wholeSize - width) / 2; } else if (cfg.hAlignment === HDockAlignment.right) { leftBorder = leftBorder + (wholeSize - width - cfg.hGap); } } return new Rect(leftBorder, topBorder, width, height); } } static alignEdge(dimension, sideBorder, gap, position, alignment) { if (2 * gap > dimension) return [dimension, sideBorder]; switch (alignment) { case EdgeAlignment.outside: if (position === DockPosition.right || position === DockPosition.bottom) sideBorder = sideBorder + gap; return [dimension - gap, sideBorder]; case EdgeAlignment.middle: return [dimension - 2 * gap, sideBorder + gap]; case EdgeAlignment.inside: if (position === DockPosition.left || position === DockPosition.top) sideBorder = sideBorder + gap; return [dimension - gap, sideBorder]; } } getSlot(position) { let slot = this.slots.find((slot) => slot.position === position) || null; if (slot === null || slot.window === null) { return null; } else if (slot.window.state !== WindowState.Docked || slot.window.dock.position !== position) { slot.window = null; return null; } else { return slot; } } getSlotByPosition(position) { return this.slots.find((slot) => slot.position === position) || null; } getSlotByWindow(window) { let slot = this.slots.find((slot) => slot.window === window) || null; if (slot === null || slot.window === null) { return null; } else if (slot.window.state !== WindowState.Docked) { slot.window = null; return null; } else { return slot; } } arrangeSlots(dockedWindows) { let contenders = this.arrangeContenders(dockedWindows, true); if (contenders.length !== 0 && this.isHasEmptySlot()) { contenders.forEach((w) => (w.dock.position = null)); contenders = this.arrangeContenders(contenders, false); } contenders.forEach((w) => { w.state = WindowState.Tiled; }); } arrangeContenders(windows, init) { let contenders = []; for (const slot of this.slots) { if (!init && slot.window !== null) continue; if (windows.length === 0 && contenders.length === 0) { slot.window = null; continue; } let tempDockedWindows = []; contenders.push(...windows.filter((w) => { if (w.dock === null) { w.dock = new Dock(this.surfaceCfg.cfg); return true; } if (w.dock.position === slot.position) { return true; } if (w.dock.position === null) return true; tempDockedWindows.push(w); })); windows = tempDockedWindows; if (contenders.length !== 0) { this.contendersSort(contenders, slot.position, this.id); slot.window = contenders.pop(); slot.window.dock.position = slot.position; } else { slot.window = null; } } return contenders; } assignSizes(workingArea) { const MAX_SIZE = 50; const MAX_GAPS = { width: workingArea.width * 0.05, height: workingArea.height * 0.05, }; let oppositeSlot = null; let donePositions = []; let dockCfg; let oppositeDockCfg; for (const slot of this.slots) { if (slot.window === null) continue; dockCfg = slot.window.dock.cfg; const minSize = { width: Math.max((slot.window.minSize.width * 100) / workingArea.width, 5), height: Math.max((slot.window.minSize.height * 100) / workingArea.height, 5), }; if (donePositions.indexOf(slot.position) >= 0) continue; switch (slot.position) { case DockPosition.top: oppositeSlot = this.getSlot(DockPosition.bottom); break; case DockPosition.bottom: oppositeSlot = this.getSlot(DockPosition.top); break; case DockPosition.left: oppositeSlot = this.getSlot(DockPosition.right); break; case DockPosition.right: oppositeSlot = this.getSlot(DockPosition.left); break; default: warning("DockEntry assignSizes - invalid position"); break; } switch (slot.position) { case DockPosition.left: case DockPosition.right: if (dockCfg.vGap > MAX_GAPS.width) dockCfg.vGap = MAX_GAPS.width; if (dockCfg.vWide < minSize.width + (200 * dockCfg.vGap) / workingArea.width) dockCfg.vWide = minSize.width + (200 * dockCfg.vGap) / workingArea.width; if (oppositeSlot !== null) { oppositeDockCfg = oppositeSlot.window.dock.cfg; let opMinSize = { width: Math.max((oppositeSlot.window.minSize.width * 100) / workingArea.width, 5), height: Math.max((oppositeSlot.window.minSize.height * 100) / workingArea.height, 5), }; if (oppositeDockCfg.vGap > MAX_GAPS.width) oppositeDockCfg.vGap = MAX_GAPS.width; if (oppositeDockCfg.vWide < opMinSize.width + (200 * oppositeDockCfg.vGap) / workingArea.width) oppositeDockCfg.vWide = opMinSize.width + (200 * oppositeDockCfg.vGap) / workingArea.width; if (dockCfg.vWide + oppositeDockCfg.vWide > MAX_SIZE) { if (dockCfg.vWide > MAX_SIZE / 2 && oppositeDockCfg.vWide > MAX_SIZE / 2) { dockCfg.vWide = MAX_SIZE / 2; oppositeDockCfg.vWide = MAX_SIZE / 2; } else if (dockCfg.vWide > MAX_SIZE / 2) { dockCfg.vWide = MAX_SIZE - oppositeDockCfg.vWide; } else { oppositeDockCfg.vWide = MAX_SIZE - dockCfg.vWide; } } if (oppositeDockCfg.vHeight > 100) oppositeDockCfg.vHeight = 100; if (oppositeDockCfg.vHeight < minSize.height) oppositeDockCfg.vHeight = minSize.height; donePositions.push(oppositeSlot.position); } else { if (dockCfg.vWide > MAX_SIZE) { dockCfg.vWide = MAX_SIZE; } } if (dockCfg.vHeight > 100) dockCfg.vHeight = 100; if (dockCfg.vHeight < minSize.height) dockCfg.vHeight = minSize.height; break; case DockPosition.top: case DockPosition.bottom: if (dockCfg.hGap > MAX_GAPS.height) dockCfg.hGap = MAX_GAPS.height; if (dockCfg.hHeight < minSize.height + (200 * dockCfg.hGap) / workingArea.height) dockCfg.hHeight = minSize.height + (200 * dockCfg.hGap) / workingArea.height; if (oppositeSlot !== null) { oppositeDockCfg = oppositeSlot.window.dock.cfg; let opMinSize = { width: Math.max((oppositeSlot.window.minSize.width * 100) / workingArea.width, 5), height: Math.max((oppositeSlot.window.minSize.height * 100) / workingArea.height, 5), }; if (oppositeDockCfg.hGap > MAX_GAPS.height) oppositeDockCfg.hGap = MAX_GAPS.height; if (oppositeDockCfg.hHeight < opMinSize.height + (200 * oppositeDockCfg.hGap) / workingArea.height) oppositeDockCfg.hHeight = opMinSize.height + (200 * oppositeDockCfg.hGap) / workingArea.height; if (dockCfg.hHeight + oppositeDockCfg.hHeight > MAX_SIZE) { if (dockCfg.hHeight > MAX_SIZE / 2 && oppositeDockCfg.hHeight > MAX_SIZE / 2) { dockCfg.hHeight = MAX_SIZE / 2; oppositeDockCfg.hHeight = MAX_SIZE / 2; } else if (dockCfg.hHeight > MAX_SIZE / 2) { dockCfg.hHeight = MAX_SIZE - oppositeDockCfg.hHeight; } else { oppositeDockCfg.hHeight = MAX_SIZE - dockCfg.hHeight; } } if (oppositeDockCfg.hWide > 100) oppositeDockCfg.hWide = 100; if (oppositeDockCfg.hWide < opMinSize.width) oppositeDockCfg.hWide = opMinSize.width; donePositions.push(oppositeSlot.position); } else { if (dockCfg.hHeight > MAX_SIZE) { dockCfg.hHeight = MAX_SIZE; } } if (dockCfg.hWide > 100) dockCfg.hWide = 100; if (dockCfg.hWide < minSize.width) dockCfg.hWide = minSize.width; break; } } } contendersSort(contenders, position, id) { function compare(a, b) { if (a.dock === null && b.dock === null) return 0; else if (a.dock === null) return -1; else if (b.dock === null) return 1; else if (a.dock.position === position && b.dock.position !== position) return 1; else if (b.dock.position === position && a.dock.position !== position) return -1; else if (a.dock.priority > b.dock.priority) return 1; else if (b.dock.priority > a.dock.priority) return -1; else if (a.dock.renderedTime === null && b.dock.renderedTime === null) return 0; else if (a.dock.renderedTime === null) return -1; else if (b.dock.renderedTime === null) return 1; else if (a.dock.renderedOutputId === id && b.dock.renderedOutputId !== id) return 1; else if (b.dock.renderedOutputId === id && a.dock.renderedOutputId !== id) return -1; else if (a.dock.renderedTime > b.dock.renderedTime) return 1; else if (b.dock.renderedTime > a.dock.renderedTime) return -1; else return 0; } contenders.sort(compare); } parseSlots() { const slots = []; if (CONFIG.dockOrder[0] !== 0) slots.push(new DockSlot(DockPosition.left, CONFIG.dockOrder[0])); if (CONFIG.dockOrder[1] !== 0) slots.push(new DockSlot(DockPosition.top, CONFIG.dockOrder[1])); if (CONFIG.dockOrder[2] !== 0) slots.push(new DockSlot(DockPosition.right, CONFIG.dockOrder[2])); if (CONFIG.dockOrder[3] !== 0) slots.push(new DockSlot(DockPosition.bottom, CONFIG.dockOrder[3])); slots.sort((a, b) => a.order - b.order); return slots; } isHasEmptySlot() { return !this.slots.every((slot) => slot.window !== null); } } function parseDockUserSurfacesCfg() { let surfacesCfg = []; if (CONFIG.dockSurfacesConfig.length === 0) return surfacesCfg; CONFIG.dockSurfacesConfig.forEach((cfg) => { let surfaceCfgString = cfg.split(":").map((part) => part.trim()); if (surfaceCfgString.length !== 4) { warning(`Invalid User surface config: ${cfg}, config must have three colons`); return; } let splittedUserCfg = surfaceCfgString[3] .split(",") .map((part) => part.trim().toLowerCase()); let partialDockCfg = parseSplittedUserCfg(splittedUserCfg); if (partialDockCfg instanceof Err) { warning(`Invalid User surface config: ${cfg}. ${partialDockCfg}`); return; } if (Object.keys(partialDockCfg).length > 0) { surfacesCfg.push(new dockSurfaceCfg(surfaceCfgString[0], surfaceCfgString[1], surfaceCfgString[2], DefaultDockCfg.instance.cloneAndUpdate(partialDockCfg))); } }); return surfacesCfg; } function parseDockUserWindowClassesCfg() { let userWindowClassesCfg = {}; if (CONFIG.dockWindowClassConfig.length === 0) return userWindowClassesCfg; CONFIG.dockWindowClassConfig.forEach((cfg) => { let windowCfgString = cfg.split(":").map((part) => part.trim()); if (windowCfgString.length !== 3) { warning(`Invalid window class config: "${cfg}" should have two colons`); return; } let splittedUserCfg = windowCfgString[2] .split(",") .map((part) => part.trim().toLowerCase()); let partialDockCfg; if (splittedUserCfg[0] !== "") { partialDockCfg = parseSplittedUserCfg(splittedUserCfg); if (partialDockCfg instanceof Err) { warning(`Invalid User window class config: ${cfg}. ${partialDockCfg}`); return; } } else partialDockCfg = {}; let splittedSpecialFlags = windowCfgString[1] .split(",") .map((part) => part.trim().toLowerCase()); let dock = parseSpecialFlags(splittedSpecialFlags, partialDockCfg); if (dock instanceof Err) { warning(`Invalid User window class config: ${cfg}. ${dock}`); return; } userWindowClassesCfg[windowCfgString[0]] = dock; }); return userWindowClassesCfg; } function parseSpecialFlags(splittedSpecialFlags, partialDockCfg) { let dock = new Dock(DefaultDockCfg.instance.cloneAndUpdate(partialDockCfg)); splittedSpecialFlags.forEach((flag) => { switch (flag) { case "auto": case "a": dock.autoDock = true; break; case "pin": case "p": dock.priority = 5; break; case "left": case "l": dock.position = DockPosition.left; break; case "right": case "r": dock.position = DockPosition.right; break; case "top": case "t": dock.position = DockPosition.top; break; case "bottom": case "b": dock.position = DockPosition.bottom; break; default: warning(`parse Special Flags: ${splittedSpecialFlags}.Unknown special flag: ${flag}`); } }); return dock; } function parseSplittedUserCfg(splittedUserCfg) { let errors = []; const shortNames = { hh: "hHeight", hw: "hWide", hgv: "hEdgeGap", hgh: "hGap", ha: "hAlignment", he: "hEdgeAlignment", vh: "vHeight", vw: "vWide", vgh: "vEdgeGap", vgv: "vGap", ve: "vEdgeAlignment", va: "vAlignment", }; let dockCfg = {}; splittedUserCfg.forEach((part) => { let splittedPart = part.split("=").map((part) => part.trim()); if (splittedPart.length !== 2) { errors.push(`"${part}" can have only one equal sign`); return; } if (splittedPart[0].length === 0 || splittedPart[1].length === 0) { errors.push(`"${part}" can not have empty shortname or value`); return; } if (shortNames[splittedPart[0]] in dockCfg) { errors.push(`"${part}" has duplicate shortname`); return; } if (!(splittedPart[0] in shortNames)) { errors.push(`"${part}" has unknown shortname`); return; } if (["he", "ve"].indexOf(splittedPart[0]) >= 0) { switch (splittedPart[1]) { case "outside": case "o": case "0": dockCfg[shortNames[splittedPart[0]]] = EdgeAlignment.outside; break; case "middle": case "m": case "1": dockCfg[shortNames[splittedPart[0]]] = EdgeAlignment.middle; break; case "inside": case "i": case "2": dockCfg[shortNames[splittedPart[0]]] = EdgeAlignment.inside; break; default: errors.push(` "${part}" value can be o,m or i or output,middle,input or 0,1,2`); return; } } else if (splittedPart[0] === "va") { switch (splittedPart[1]) { case "center": case "c": case "0": dockCfg[shortNames[splittedPart[0]]] = VDockAlignment.center; break; case "1": case "top": case "t": dockCfg[shortNames[splittedPart[0]]] = VDockAlignment.top; break; case "2": case "bottom": case "b": dockCfg[shortNames[splittedPart[0]]] = VDockAlignment.bottom; break; default: errors.push(` "${part}" value can be c,t or b or center,top,bottom or 0,1,2`); return; } } else if (splittedPart[0] === "ha") { switch (splittedPart[1]) { case "center": case "c": case "0": dockCfg[shortNames[splittedPart[0]]] = HDockAlignment.center; break; case "1": case "left": case "l": dockCfg[shortNames[splittedPart[0]]] = HDockAlignment.left; break; case "2": case "right": case "r": dockCfg[shortNames[splittedPart[0]]] = HDockAlignment.right; break; default: errors.push(`"${part}" value can be c,l or r or center,left,right or 0,1,2`); return; } } else { let value; switch (splittedPart[0]) { case "hw": case "vh": value = validateNumber(splittedPart[1], 1, 100); break; case "hh": case "vw": value = validateNumber(splittedPart[1], 1, 50); break; case "hgh": case "vgv": case "vgh": case "vgv": value = validateNumber(splittedPart[1]); break; default: errors.push(`unknown shortname ${splittedPart[0]}`); return; } if (value instanceof Err) errors.push(`splittedPart[0]: ${value}`); else dockCfg[shortNames[splittedPart[0]]] = value; } }); if (errors.length > 0) { return new Err(errors.join("\n")); } return dockCfg; } class DockSlot { get position() { return this._position; } get order() { return this._order; } constructor(position, order) { this._position = position; this._order = order; this.window = null; } } class DockStore { constructor() { this.store = {}; this.defaultCfg = null; this.surfacesCfg = []; this.windowClassesCfg = {}; } render(srf, visibles, workingArea) { if (this.defaultCfg === null) { this.defaultCfg = DefaultDockCfg.instance; this.surfacesCfg = parseDockUserSurfacesCfg(); this.windowClassesCfg = parseDockUserWindowClassesCfg(); } if (!this.store[srf.id]) { this.store[srf.id] = new DockEntry(this.getSurfaceCfg(srf), srf.id); } let dockedWindows = visibles.filter((w) => { if (w.state === WindowState.Docked) { if (w.dock === null && w.windowClassName in this.windowClassesCfg) { w.dock = this.windowClassesCfg[w.windowClassName].clone(); } return true; } }); if (dockedWindows.length === 0) return workingArea; return this.store[srf.id].arrange(dockedWindows, workingArea); } remove(window) { for (let key in this.store) { this.store[key].remove(window); } } handleShortcut(ctx, window, shortcut) { switch (shortcut) { case Shortcut.SwapLeft: case Shortcut.SwapUp: case Shortcut.SwapRight: case Shortcut.SwapDown: const srf = ctx.currentSurface; if (this.store[srf.id]) { return this.store[srf.id].handleShortcut(window, shortcut); } return false; default: return false; } } isNewWindowHaveDocked(window) { if (window.windowClassName in this.windowClassesCfg && this.windowClassesCfg[window.windowClassName].autoDock === true) return true; return false; } getSurfaceCfg(srf) { let dockCfg = null; for (let surfaceCfg of this.surfacesCfg) { if (surfaceCfg.isFit(srf)) { dockCfg = Object.assign({}, surfaceCfg.cfg); break; } } if (dockCfg === null) dockCfg = this.defaultCfg.cloneAndUpdate({}); let [outputName, activityId, vDesktopName] = srf.getParams(); return new dockSurfaceCfg(outputName, activityId, vDesktopName, dockCfg); } } class KWinConfig { constructor() { function separate(str, separator) { if (!str || typeof str !== "string") return []; return str .split(separator) .map((part) => part.trim()) .filter((part) => part != ""); } if (KWIN.readConfig("logging", false)) { let logParts = []; let newWindowSubmodules = []; if (KWIN.readConfig("logNewWindows", false)) newWindowSubmodules.push("1"); if (KWIN.readConfig("logFilteredWindows", false)) newWindowSubmodules.push("2"); if (KWIN.readConfig("logUnmanagedWindows", false)) newWindowSubmodules.push("3"); if (newWindowSubmodules.length > 0) logParts.push([LogPartitions.newWindow, newWindowSubmodules]); if (KWIN.readConfig("logWorkspaceSignals", false)) { let workspaceSignalsSubmodules = separate(KWIN.readConfig("logWorkspaceSignalsSubmodules", ""), ","); logParts.push([ LogPartitions.workspaceSignals, workspaceSignalsSubmodules, ]); } if (KWIN.readConfig("logWindowSignals", false)) { let windowSignalsSubmodules = separate(KWIN.readConfig("logWindowSignalsSubmodules", ""), ","); logParts.push([LogPartitions.windowSignals, windowSignalsSubmodules]); } if (KWIN.readConfig("logOther", false)) { let otherSubmodules = separate(KWIN.readConfig("logOtherSubmodules", ""), ","); logParts.push([LogPartitions.other, otherSubmodules]); } const logFilters = KWIN.readConfig("logFilter", false) ? separate(KWIN.readConfig("logFilterStr", ""), ",") : []; LOG = new Logging(logParts, logFilters); } else LOG = undefined; let sortedLayouts = []; this.layoutFactories = {}; [ ["tileLayoutOrder", 1, TileLayout], ["monocleLayoutOrder", 2, MonocleLayout], ["threeColumnLayoutOrder", 3, ThreeColumnLayout], ["spiralLayoutOrder", 4, SpiralLayout], ["quarterLayoutOrder", 5, QuarterLayout], ["stackedLayoutOrder", 6, StackedLayout], ["columnsLayoutOrder", 7, ColumnsLayout], ["spreadLayoutOrder", 8, SpreadLayout], ["floatingLayoutOrder", 9, FloatingLayout], ["stairLayoutOrder", 10, StairLayout], ["binaryTreeLayoutOrder", 11, BTreeLayout], ["cascadeLayoutOrder", 12, CascadeLayout], ].forEach(([configKey, defaultValue, layoutClass]) => { let order = validateNumber(KWIN.readConfig(configKey, defaultValue), 0, 12); if (order instanceof Err) { order = defaultValue; warning(`kwinconfig: layout order for ${layoutClass.id} is invalid, using default value ${order}`); } if (order === 0) return; sortedLayouts.push({ order: order, layoutClass: layoutClass }); }); sortedLayouts.sort((a, b) => a.order - b.order); if (sortedLayouts.length === 0) { sortedLayouts.push({ order: 1, layoutClass: TileLayout }); } this.layoutOrder = []; sortedLayouts.forEach(({ layoutClass }) => { this.layoutOrder.push(layoutClass.id); this.layoutFactories[layoutClass.id] = () => new layoutClass(); }); this.dockOrder = [ KWIN.readConfig("dockOrderLeft", 1), KWIN.readConfig("dockOrderTop", 2), KWIN.readConfig("dockOrderRight", 3), KWIN.readConfig("dockOrderBottom", 4), ]; this.dockHHeight = KWIN.readConfig("dockHHeight", 15); this.dockHWide = KWIN.readConfig("dockHWide", 100); this.dockHGap = KWIN.readConfig("dockHGap", 0); this.dockHEdgeGap = KWIN.readConfig("dockHEdgeGap", 0); this.dockHAlignment = KWIN.readConfig("dockHAlignment", 0); this.dockHEdgeAlignment = KWIN.readConfig("dockHEdgeAlignment", 0); this.dockVHeight = KWIN.readConfig("dockVHeight", 100); this.dockVWide = KWIN.readConfig("dockVWide", 15); this.dockVEdgeGap = KWIN.readConfig("dockVEdgeGap", 0); this.dockVGap = KWIN.readConfig("dockVGap", 0); this.dockVAlignment = KWIN.readConfig("dockVAlignment", 0); this.dockVEdgeAlignment = KWIN.readConfig("dockVEdgeAlignment", 0); this.dockSurfacesConfig = separate(KWIN.readConfig("dockSurfacesConfig", ""), "\n"); this.dockWindowClassConfig = separate(KWIN.readConfig("dockWindowClassConfig", ""), "\n"); this.soleWindowWidth = KWIN.readConfig("soleWindowWidth", 100); this.soleWindowHeight = KWIN.readConfig("soleWindowHeight", 100); this.soleWindowNoBorders = KWIN.readConfig("soleWindowNoBorders", false); this.soleWindowNoGaps = KWIN.readConfig("soleWindowNoGaps", false); this.tileLayoutInitialAngle = KWIN.readConfig("tileLayoutInitialRotationAngle", "0"); this.columnsLayoutInitialAngle = KWIN.readConfig("columnsLayoutInitialRotationAngle", "0"); this.columnsBalanced = KWIN.readConfig("columnsBalanced", false); this.columnsLayerConf = separate(KWIN.readConfig("columnsLayerConf", ""), ","); this.tiledWindowsLayer = getWindowLayer(KWIN.readConfig("tiledWindowsLayer", 0)); this.floatedWindowsLayer = getWindowLayer(KWIN.readConfig("floatedWindowsLayer", 1)); this.quarterLayoutReset = KWIN.readConfig("quarterLayoutReset", false); this.monocleMaximize = KWIN.readConfig("monocleMaximize", true); this.monocleMinimizeRest = KWIN.readConfig("monocleMinimizeRest", false); this.stairReverse = KWIN.readConfig("stairReverse", false); this.adjustLayout = KWIN.readConfig("adjustLayout", true); this.adjustLayoutLive = KWIN.readConfig("adjustLayoutLive", true); this.keepTilingOnDrag = KWIN.readConfig("keepTilingOnDrag", true); this.noTileBorder = KWIN.readConfig("noTileBorder", false); this.limitTileWidthRatio = 0; if (KWIN.readConfig("limitTileWidth", false)) this.limitTileWidthRatio = KWIN.readConfig("limitTileWidthRatio", 1.6); this.screenGapBottom = KWIN.readConfig("screenGapBottom", 0); this.screenGapLeft = KWIN.readConfig("screenGapLeft", 0); this.screenGapRight = KWIN.readConfig("screenGapRight", 0); this.screenGapTop = KWIN.readConfig("screenGapTop", 0); this.screenGapBetween = KWIN.readConfig("screenGapBetween", 0); this.gapsOverrideConfig = separate(KWIN.readConfig("gapsOverrideConfig", ""), "\n"); const directionalKeyDwm = KWIN.readConfig("directionalKeyDwm", false); const directionalKeyFocus = KWIN.readConfig("directionalKeyFocus", true); this.directionalKeyMode = directionalKeyDwm ? "dwm" : "focus"; this.newWindowPosition = KWIN.readConfig("newWindowPosition", 0); this.layoutPerActivity = KWIN.readConfig("layoutPerActivity", true); this.layoutPerDesktop = KWIN.readConfig("layoutPerDesktop", true); this.floatUtility = KWIN.readConfig("floatUtility", true); this.preventMinimize = KWIN.readConfig("preventMinimize", false); this.preventProtrusion = KWIN.readConfig("preventProtrusion", true); this.notificationDuration = KWIN.readConfig("notificationDuration", 1000); this.floatSkipPager = KWIN.readConfig("floatSkipPagerWindows", false); this.floatingClass = separate(KWIN.readConfig("floatingClass", ""), ","); this.floatingTitle = separate(KWIN.readConfig("floatingTitle", ""), ","); this.ignoreActivity = separate(KWIN.readConfig("ignoreActivity", ""), ","); this.ignoreClass = separate(KWIN.readConfig("ignoreClass", "krunner,yakuake,spectacle,kded5,xwaylandvideobridge,plasmashell,ksplashqml,org.kde.plasmashell,org.kde.polkit-kde-authentication-agent-1,org.kde.kruler,kruler,kwin_wayland,ksmserver-logout-greeter"), ","); this.ignoreRole = separate(KWIN.readConfig("ignoreRole", "quake"), ","); this.ignoreScreen = separate(KWIN.readConfig("ignoreScreen", ""), ","); this.ignoreVDesktop = separate(KWIN.readConfig("ignoreVDesktop", ""), ","); this.ignoreTitle = separate(KWIN.readConfig("ignoreTitle", ""), ","); this.screenDefaultLayout = separate(KWIN.readConfig("screenDefaultLayout", ""), ","); this.tilingClass = separate(KWIN.readConfig("tilingClass", ""), ","); this.tileNothing = KWIN.readConfig("tileNothing", false); if (this.preventMinimize && this.monocleMinimizeRest) { this.preventMinimize = false; } } toString() { return "Config(" + JSON.stringify(this, undefined, 2) + ")"; } } var KWINCONFIG; var KWIN; class KWinDriver { get backend() { return KWinDriver.backendName; } get currentSurface() { return new KWinSurface(this.workspace.activeWindow ? this.workspace.activeWindow.output : this.workspace.activeScreen, this.workspace.currentActivity, this.workspace.currentDesktop, this.workspace); } set currentSurface(value) { const ksrf = value; if (this.workspace.currentDesktop.id !== ksrf.desktop.id) this.workspace.currentDesktop = ksrf.desktop; if (this.workspace.currentActivity !== ksrf.activity) this.workspace.currentActivity = ksrf.activity; } get currentWindow() { const client = this.workspace.activeWindow; return client ? this.windowMap.get(client) : null; } set currentWindow(window) { if (window !== null) { window.timestamp = new Date().getTime(); this.workspace.activeWindow = window.window.window; } } get screens() { const screens = []; this.workspace.screens.forEach((screen) => { screens.push(new KWinSurface(screen, this.workspace.currentActivity, this.workspace.currentDesktop, this.workspace)); }); return screens; } get cursorPosition() { const workspacePos = this.workspace.cursorPos; return workspacePos !== null ? [workspacePos.x, workspacePos.y] : null; } constructor(api) { KWIN = api.kwin; this.workspace = api.workspace; this.shortcuts = api.shortcuts; this.engine = new TilingEngine(); this.control = new TilingController(this.engine); this.windowMap = new WrapperMap((client) => KWinWindow.generateID(client), (client) => new WindowClass(new KWinWindow(client, this.workspace))); this.entered = false; } main() { CONFIG = KWINCONFIG = new KWinConfig(); LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.printConfig, undefined, `Config: ${CONFIG}`); this.bindEvents(); this.bindShortcut(); const clients = this.workspace.stackingOrder; for (let i = 0; i < clients.length; i++) { this.addWindow(clients[i]); } } addWindow(client) { if (!client.deleted && client.pid >= 0 && !client.popupWindow && client.normalWindow && !client.hidden && client.width * client.height > 10) { const window = this.windowMap.add(client); this.control.onWindowAdded(this, window); if (window.state !== WindowState.Unmanaged) { this.bindWindowEvents(window, client); if (client.maximizeMode > 0) client.setMaximize(false, false); LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.newWindowAdded, "", debugWin(client), { winClass: [`${client.resourceClass}`], }); return window; } else { this.windowMap.remove(client); LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.newWindowUnmanaged, "", debugWin(client), { winClass: [`${client.resourceClass}`], }); } } else { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.newWindowFiltered, "", debugWin(client), { winClass: [`${client.resourceClass}`], }); } return null; } setTimeout(func, timeout) { KWinSetTimeout(() => this.enter(func), timeout); } showNotification(text) { if (CONFIG.notificationDuration > 0) popupDialog.show(text, CONFIG.notificationDuration); } bindShortcut() { const callbackShortcut = (shortcut) => { return () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.shortcut, `Shortcut pressed:`, `${ShortcutsKeys[shortcut]}`); this.enter(() => this.control.onShortcut(this, shortcut)); }; }; this.shortcuts .getToggleDock() .activated.connect(callbackShortcut(Shortcut.ToggleDock)); this.shortcuts .getFocusNext() .activated.connect(callbackShortcut(Shortcut.FocusNext)); this.shortcuts .getFocusPrev() .activated.connect(callbackShortcut(Shortcut.FocusPrev)); this.shortcuts .getFocusDown() .activated.connect(callbackShortcut(Shortcut.FocusDown)); this.shortcuts .getFocusUp() .activated.connect(callbackShortcut(Shortcut.FocusUp)); this.shortcuts .getFocusLeft() .activated.connect(callbackShortcut(Shortcut.FocusLeft)); this.shortcuts .getFocusRight() .activated.connect(callbackShortcut(Shortcut.FocusRight)); this.shortcuts .getShiftDown() .activated.connect(callbackShortcut(Shortcut.ShiftDown)); this.shortcuts .getShiftUp() .activated.connect(callbackShortcut(Shortcut.ShiftUp)); this.shortcuts .getShiftLeft() .activated.connect(callbackShortcut(Shortcut.ShiftLeft)); this.shortcuts .getShiftRight() .activated.connect(callbackShortcut(Shortcut.ShiftRight)); this.shortcuts .getGrowHeight() .activated.connect(callbackShortcut(Shortcut.GrowHeight)); this.shortcuts .getShrinkHeight() .activated.connect(callbackShortcut(Shortcut.ShrinkHeight)); this.shortcuts .getShrinkWidth() .activated.connect(callbackShortcut(Shortcut.ShrinkWidth)); this.shortcuts .getGrowWidth() .activated.connect(callbackShortcut(Shortcut.GrowWidth)); this.shortcuts .getIncrease() .activated.connect(callbackShortcut(Shortcut.Increase)); this.shortcuts .getDecrease() .activated.connect(callbackShortcut(Shortcut.Decrease)); this.shortcuts .getToggleFloat() .activated.connect(callbackShortcut(Shortcut.ToggleFloat)); this.shortcuts .getFloatAll() .activated.connect(callbackShortcut(Shortcut.ToggleFloatAll)); this.shortcuts .getNextLayout() .activated.connect(callbackShortcut(Shortcut.NextLayout)); this.shortcuts .getPreviousLayout() .activated.connect(callbackShortcut(Shortcut.PreviousLayout)); this.shortcuts .getRotate() .activated.connect(callbackShortcut(Shortcut.Rotate)); this.shortcuts .getRotatePart() .activated.connect(callbackShortcut(Shortcut.RotatePart)); this.shortcuts .getSetMaster() .activated.connect(callbackShortcut(Shortcut.SetMaster)); const callbackShortcutLayout = (layoutClass) => { return () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.shortcut, "shortcut layout", `${layoutClass.id}`); this.enter(() => this.control.onShortcut(this, Shortcut.SetLayout, layoutClass.id)); }; }; this.shortcuts .getTileLayout() .activated.connect(callbackShortcutLayout(TileLayout)); this.shortcuts .getMonocleLayout() .activated.connect(callbackShortcutLayout(MonocleLayout)); this.shortcuts .getThreeColumnLayout() .activated.connect(callbackShortcutLayout(ThreeColumnLayout)); this.shortcuts .getSpreadLayout() .activated.connect(callbackShortcutLayout(SpreadLayout)); this.shortcuts .getStairLayout() .activated.connect(callbackShortcutLayout(StairLayout)); this.shortcuts .getFloatingLayout() .activated.connect(callbackShortcutLayout(FloatingLayout)); this.shortcuts .getQuarterLayout() .activated.connect(callbackShortcutLayout(QuarterLayout)); this.shortcuts .getStackedLayout() .activated.connect(callbackShortcutLayout(StackedLayout)); this.shortcuts .getColumnsLayout() .activated.connect(callbackShortcutLayout(ColumnsLayout)); this.shortcuts .getSpiralLayout() .activated.connect(callbackShortcutLayout(SpiralLayout)); this.shortcuts .getBTreeLayout() .activated.connect(callbackShortcutLayout(BTreeLayout)); } connect(signal, handler) { const wrapper = (...args) => { if (typeof this.workspace === "undefined") signal.disconnect(wrapper); else this.enter(() => handler.apply(this, args)); }; signal.connect(wrapper); return wrapper; } enter(callback) { if (this.entered) return; this.entered = true; try { callback(); } catch (e) { warning(`ProtectFunc: Error raised line: ${e.lineNumber}. Error: ${e}`); } finally { this.entered = false; } } bindEvents() { this.connect(this.workspace.screensChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.screensChanged, "eventFired"); this.control.onSurfaceUpdate(this); }); this.connect(this.workspace.virtualScreenGeometryChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.virtualScreenGeometryChanged, "eventFired"); this.control.onSurfaceUpdate(this); }); this.connect(this.workspace.currentActivityChanged, (activityId) => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.currentActivityChanged, "eventFired", `Activity ID:${activityId}`); this.control.onCurrentActivityChanged(this); }); this.connect(this.workspace.currentDesktopChanged, (virtualDesktop) => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.currentDesktopChanged, "eventFired", `Virtual Desktop. name:${virtualDesktop.name}, id:${virtualDesktop.id}`); this.control.onSurfaceUpdate(this); }); this.connect(this.workspace.windowAdded, (client) => { if (!client) return; LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.windowAdded, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}`, { winClass: [`${client.resourceClass}`] }); const window = this.addWindow(client); if (client.active && window !== null) this.control.onWindowFocused(this, window); }); this.connect(this.workspace.windowActivated, (client) => { if (!client) return; LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.windowActivated, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}`, { winClass: [`${client.resourceClass}`] }); const window = this.windowMap.get(client); if (client.active && window !== null) this.control.onWindowFocused(this, window); }); this.connect(this.workspace.windowRemoved, (client) => { if (!client) return; LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.windowRemoved, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}`, { winClass: [`${client.resourceClass}`] }); const window = this.windowMap.get(client); if (window) { this.control.onWindowRemoved(this, window); this.windowMap.remove(client); } }); } bindWindowEvents(window, client) { let moving = false; let resizing = false; this.connect(client.activitiesChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.activitiesChanged, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}, activities: ${client.activities.join(",")}`, { winClass: [`${client.resourceClass}`] }); this.control.onWindowChanged(this, window, "activity=" + client.activities.join(",")); }); this.connect(client.bufferGeometryChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.bufferGeometryChanged, "eventFired", `Window: caption:${client.caption} internalId:${client.internalId}, moving:${moving}, resizing:${resizing}, geometry:${window.geometry}`, { winClass: [`${client.resourceClass}`] }); if (moving) this.control.onWindowMove(window); else if (resizing) this.control.onWindowResize(this, window); else { if (!window.actualGeometry.equals(window.geometry)) this.control.onWindowGeometryChanged(this, window); } }); this.connect(client.desktopsChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.desktopsChanged, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}, desktops: ${client.desktops}`, { winClass: [`${client.resourceClass}`] }); this.control.onWindowChanged(this, window, "Window's desktop changed."); }); this.connect(client.fullScreenChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.fullScreenChanged, "eventFired", `window: caption:${client.caption} internalID:${client.internalId}, fullscreen: ${client.fullScreen}`, { winClass: [`${client.resourceClass}`] }); this.control.onWindowChanged(this, window, "fullscreen=" + client.fullScreen); }); this.connect(client.interactiveMoveResizeStepped, (geometry) => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.interactiveMoveResizeStepped, "eventFired", `window: caption:${client.caption} internalID:${client.internalId},interactiveMoveResizeStepped:${geometry}`, { winClass: [`${client.resourceClass}`] }); if (client.resize) return; this.control.onWindowDragging(this, window, geometry); }); this.connect(client.maximizedAboutToChange, (mode) => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.maximizedAboutToChange, "eventFired", `window: caption:${client.caption} internalID:${client.internalId},maximizedAboutToChange:${mode}`, { winClass: [`${client.resourceClass}`] }); window.window.maximized = mode > 0 ? true : false; this.control.onWindowMaximizeChanged(this, window); }); this.connect(client.minimizedChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.minimizedChanged, "eventFired", `window: caption:${client.caption} internalID:${client.internalId},minimized:${client.minimized}`, { winClass: [`${client.resourceClass}`] }); if (KWINCONFIG.preventMinimize) { client.minimized = false; this.workspace.activeWindow = client; } else { var comment = client.minimized ? "minimized" : "unminimized"; this.control.onWindowChanged(this, window, comment); } }); this.connect(client.moveResizedChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.moveResizedChanged, "eventFired", `Window: caption:${client.caption} internalId:${client.internalId}, moving:${moving}, resizing:${resizing}`, { winClass: [`${client.resourceClass}`] }); if (moving !== client.move) { moving = client.move; if (moving) { this.control.onWindowMoveStart(window); } else { this.control.onWindowMoveOver(this, window); } } if (resizing !== client.resize) { resizing = client.resize; if (resizing) this.control.onWindowResizeStart(window); else this.control.onWindowResizeOver(this, window); } }); this.connect(client.outputChanged, () => { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.outputChanged, "eventFired", `window: caption:${client.caption} internalID:${client.internalId} output: ${client.output.name}`, { winClass: [`${client.resourceClass}`] }); this.control.onWindowChanged(this, window, "screen=" + client.output.name); }); if (CONFIG.floatSkipPager) { this.connect(client.skipPagerChanged, () => { this.control.onWindowSkipPagerChanged(this, window, client.skipPager); }); } } } KWinDriver.backendName = "kwin"; class KWinTimerPool { constructor() { this.timers = []; this.numTimers = 0; } setTimeout(func, timeout) { if (this.timers.length === 0) { this.numTimers++; LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.setTimeout, "setTimeout/newTimer", `numTimers: ${this.numTimers}`); } const timer = this.timers.pop() || Qt.createQmlObject("import QtQuick 2.0; Timer {}", scriptRoot); const callback = () => { try { timer.triggered.disconnect(callback); } catch (e) { } try { func(); } catch (e) { } this.timers.push(timer); }; timer.interval = timeout; timer.repeat = false; timer.triggered.connect(callback); timer.start(); } } KWinTimerPool.instance = new KWinTimerPool(); function KWinSetTimeout(func, timeout) { KWinTimerPool.instance.setTimeout(func, timeout); } class KWinSurface { static getHash(s) { let hash = 0; if (s.length == 0) return `0`; for (let i = 0; i < s.length; i++) { let charCode = s.charCodeAt(i); hash = (hash << 5) - hash + charCode; hash = hash & hash; } return `${hash}`; } static generateId(screenName, activity, desktopName) { let path = screenName; if (KWINCONFIG.layoutPerActivity) path += "@" + activity; if (KWINCONFIG.layoutPerDesktop) path += "#" + desktopName; return KWinSurface.getHash(path); } constructor(output, activity, desktop, workspace) { this.id = KWinSurface.generateId(output.name, activity, desktop.id); this.ignore = KWINCONFIG.ignoreActivity.indexOf(activity) >= 0 || KWINCONFIG.ignoreScreen.indexOf(output.name) >= 0 || KWINCONFIG.ignoreVDesktop.indexOf(desktop.name) >= 0; this.workingArea = toRect(workspace.clientArea(0, output, desktop)); this.output = output; this.activity = activity; this.desktop = desktop; } getParams() { return [this.output.name, this.activity, this.desktop.name]; } next() { return null; } toString() { return ("KWinSurface(" + [this.output.name, this.activity, this.desktop.name].join(", ") + ")"); } } class KWinWindow { static generateID(w) { return w.internalId.toString(); } get fullScreen() { return this.window.fullScreen; } get geometry() { return toRect(this.window.frameGeometry); } get windowClassName() { return this.window.resourceClass; } get shouldIgnore() { if (this.window.deleted) return true; return (this.window.specialWindow || this.window.resourceClass === "plasmashell" || this.isIgnoredByConfig); } get shouldFloat() { return (this.isFloatByConfig || (CONFIG.floatSkipPager && this.window.skipPager) || this.window.modal || this.window.transient || !this.window.resizeable || (KWINCONFIG.floatUtility && (this.window.dialog || this.window.splash || this.window.utility))); } get minimized() { return this.window.minimized; } get surface() { let activity; let desktop; if (this.window.activities.length === 0) activity = this.workspace.currentActivity; else if (this.window.activities.indexOf(this.workspace.currentActivity) >= 0) activity = this.workspace.currentActivity; else activity = this.window.activities[0]; if (this.window.desktops.length === 1) { desktop = this.window.desktops[0]; } else if (this.window.desktops.length === 0) { desktop = this.workspace.currentDesktop; } else { if (this.window.desktops.indexOf(this.workspace.currentDesktop) >= 0) desktop = this.workspace.currentDesktop; else desktop = this.window.desktops[0]; } return new KWinSurface(this.window.output, activity, desktop, this.workspace); } set surface(srf) { const ksrf = srf; if (this.window.desktops[0] !== ksrf.desktop) this.window.desktops = [ksrf.desktop]; if (this.window.activities[0] !== ksrf.activity) this.window.activities = [ksrf.activity]; } get minSize() { return { width: this.window.minSize.width, height: this.window.minSize.height, }; } get maxSize() { return { width: this.window.maxSize.width, height: this.window.maxSize.height, }; } constructor(window, workspace) { this.workspace = workspace; this.window = window; this.id = KWinWindow.generateID(window); this.maximized = false; this.noBorderManaged = false; this.noBorderOriginal = window.noBorder; this.isIgnoredByConfig = KWinWindow.isContain(KWINCONFIG.ignoreClass, window.resourceClass) || KWinWindow.isContain(KWINCONFIG.ignoreClass, window.resourceName) || matchWords(this.window.caption, KWINCONFIG.ignoreTitle) >= 0 || KWinWindow.isContain(KWINCONFIG.ignoreRole, window.windowRole) || (KWINCONFIG.tileNothing && KWinWindow.isContain(KWINCONFIG.tilingClass, window.resourceClass)); this.isFloatByConfig = KWinWindow.isContain(KWINCONFIG.floatingClass, window.resourceClass) || KWinWindow.isContain(KWINCONFIG.floatingClass, window.resourceName) || matchWords(this.window.caption, KWINCONFIG.floatingTitle) >= 0; } commit(geometry, noBorder, windowLayer) { LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.window, "KwinWindow#commit", `geometry:${geometry}, noBorder:${noBorder}, windowLayer:${windowLayer}`); if (this.window.move || this.window.resize) return; if (noBorder !== undefined) { if (!this.noBorderManaged && noBorder) this.noBorderOriginal = this.window.noBorder; else if (this.noBorderManaged && !this.window.noBorder) this.noBorderOriginal = false; if (noBorder) this.window.noBorder = true; else if (this.noBorderManaged) this.window.noBorder = this.noBorderOriginal; this.noBorderManaged = noBorder; } if (windowLayer !== undefined) { if (windowLayer === 2) this.window.keepAbove = true; else if (windowLayer === 0) this.window.keepBelow = true; else if (windowLayer === 1) { this.window.keepAbove = false; this.window.keepBelow = false; } } if (geometry !== undefined) { geometry = this.adjustGeometry(geometry); if (KWINCONFIG.preventProtrusion) { const area = toRect(this.workspace.clientArea(0, this.window.output, this.workspace.currentDesktop)); if (!area.includes(geometry)) { const x = geometry.x + Math.min(area.maxX - geometry.maxX, 0); const y = geometry.y + Math.min(area.maxY - geometry.maxY, 0); geometry = new Rect(x, y, geometry.width, geometry.height); geometry = this.adjustGeometry(geometry); } } if (this.window.deleted) return; this.window.frameGeometry = toQRect(geometry); } } toString() { return ("KWin(" + this.window.internalId.toString() + "." + this.window.resourceClass + ")"); } visible(srf) { const ksrf = srf; return (!this.window.deleted && !this.window.minimized && (this.window.onAllDesktops || this.window.desktops.indexOf(ksrf.desktop) !== -1) && (this.window.activities.length === 0 || this.window.activities.indexOf(ksrf.activity) !== -1) && this.window.output === ksrf.output); } static isContain(filterList, s) { for (let filterWord of filterList) { if (filterWord[0] === "[" && filterWord[filterWord.length - 1] === "]") { if (s .toLowerCase() .includes(filterWord.toLowerCase().slice(1, filterWord.length - 1))) return true; } else if (s.toLowerCase() === filterWord.toLowerCase()) return true; } return false; } adjustGeometry(geometry) { let width = geometry.width; let height = geometry.height; if (!this.window.resizeable) { width = this.window.width; height = this.window.height; } else { width = clip(width, this.window.minSize.width, this.window.maxSize.width); height = clip(height, this.window.minSize.height, this.window.maxSize.height); } return new Rect(geometry.x, geometry.y, width, height); } } function debugWin(win) { var w_props = [ { name: "caption", opt: win.caption }, { name: "output.name", opt: win.output.name }, { name: "resourceName", opt: win.resourceName }, { name: "resourceClass", opt: win.resourceClass }, { name: "skipPager", opt: win.skipPager }, { name: "desktopWindow", opt: win.desktopWindow }, { name: "windowRole", opt: win.windowRole }, { name: "windowType", opt: win.windowType }, { name: "pid", opt: win.pid }, { name: "internalId", opt: win.internalId }, { name: "stackingOrder", opt: win.stackingOrder }, { name: "size", opt: win.size }, { name: "width", opt: win.width }, { name: "height", opt: win.height }, { name: "dock", opt: win.dock }, { name: "toolbar", opt: win.toolbar }, { name: "menu", opt: win.menu }, { name: "dialog", opt: win.dialog }, { name: "splash", opt: win.splash }, { name: "utility", opt: win.utility }, { name: "dropdownMenu", opt: win.dropdownMenu }, { name: "popupMenu", opt: win.popupMenu }, { name: "tooltip", opt: win.tooltip }, { name: "notification", opt: win.notification }, { name: "criticalNotification", opt: win.criticalNotification }, { name: "appletPopup", opt: win.appletPopup }, { name: "onScreenDisplay", opt: win.onScreenDisplay }, { name: "comboBox", opt: win.comboBox }, { name: "managed", opt: win.managed }, { name: "popupWindow", opt: win.popupWindow }, { name: "outline", opt: win.outline }, { name: "fullScreenable", opt: win.fullScreenable }, { name: "closeable", opt: win.closeable }, { name: "minimizable", opt: win.minimizable }, { name: "specialWindow", opt: win.specialWindow }, { name: "modal", opt: win.modal }, { name: "resizeable", opt: win.resizeable }, { name: "minimized", opt: win.minimized }, { name: "tile", opt: win.tile }, { name: "minSize", opt: win.minSize }, { name: "maxSize", opt: win.maxSize }, { name: "transient", opt: win.transient }, { name: "transientFor", opt: win.transientFor }, { name: "maximizable", opt: win.maximizable }, { name: "maximizeMode", opt: win.maximizeMode }, { name: "moveable", opt: win.moveable }, { name: "moveableAcrossScreens", opt: win.moveableAcrossScreens }, { name: "hidden", opt: win.hidden }, { name: "keepAbove", opt: win.keepAbove }, { name: "keepBelow", opt: win.keepBelow }, { name: "opacity", opt: win.opacity }, ]; var s = "kwin window props:"; w_props.forEach((el) => { if (typeof el.opt !== "undefined" && (el.opt || el.opt === 0 || el.opt === "0")) { s += "<"; s += el.name; s += ": "; s += el.opt; s += "> "; } }); return s; } class TilingController { constructor(engine) { this.engine = engine; this.isDragging = false; this.dragCompleteTime = null; } onSurfaceUpdate(ctx) { this.engine.arrange(ctx); } onCurrentActivityChanged(ctx) { this.engine.arrange(ctx); } onCurrentSurfaceChanged(ctx) { this.engine.arrange(ctx); } onWindowAdded(ctx, window) { this.engine.manage(window); if (window.tileable) { const srf = ctx.currentSurface; const tiles = this.engine.windows.getVisibleTiles(srf); const layoutCapcity = this.engine.layouts.getCurrentLayout(srf).capacity; if (layoutCapcity !== undefined && tiles.length > layoutCapcity) { const nsrf = ctx.currentSurface.next(); if (nsrf) { window.surface = nsrf; ctx.currentSurface = nsrf; } } } this.engine.arrange(ctx); } onWindowSkipPagerChanged(ctx, window, skipPager) { if (skipPager) window.state = WindowState.Floating; else window.state = WindowState.Undecided; this.engine.arrange(ctx); } onWindowRemoved(ctx, window) { this.engine.unmanage(window); this.engine.arrange(ctx); } onWindowMoveStart(window) { } onWindowMove(window) { } onWindowDragging(ctx, window, windowRect) { if (this.isDragging) return; if (this.dragCompleteTime !== null && Date.now() - this.dragCompleteTime < 100) return; const srf = ctx.currentSurface; const layout = this.engine.layouts.getCurrentLayout(srf); if (!layout.drag) return; if (window.state === WindowState.Tiled) { window.setDraggingState(); } if (window.state === WindowState.Dragging) { if (layout.drag(new EngineContext(ctx, this.engine), toRect(windowRect), window, srf.workingArea)) { this.engine.arrange(ctx); } this.dragCompleteTime = Date.now(); } this.isDragging = false; } onWindowMoveOver(ctx, window) { if (window.state === WindowState.Dragging) { window.setState(WindowState.Tiled); this.engine.arrange(ctx); return; } if (window.state === WindowState.Tiled) { const tiles = this.engine.windows.getVisibleTiles(ctx.currentSurface); const cursorPos = ctx.cursorPosition || window.actualGeometry.center; const targets = tiles.filter((tile) => tile !== window && tile.actualGeometry.includesPoint(cursorPos)); if (targets.length === 1) { this.engine.windows.swap(window, targets[0]); this.engine.arrange(ctx); return; } } if (!CONFIG.keepTilingOnDrag && window.state === WindowState.Tiled) { const diff = window.actualGeometry.subtract(window.geometry); const distance = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2)); if (distance > 30) { window.floatGeometry = window.actualGeometry; window.state = WindowState.Floating; this.engine.arrange(ctx); return; } } window.commit(); } onWindowResizeStart(window) { } onWindowResize(ctx, window) { if (CONFIG.adjustLayout && CONFIG.adjustLayoutLive && window.state === WindowState.Tiled) { this.engine.adjustLayout(window); this.engine.arrange(ctx); } else if (window.state === WindowState.Docked) { this.engine.adjustDock(window); this.engine.arrange(ctx); } } onWindowResizeOver(ctx, window) { if (CONFIG.adjustLayout && window.tiled) { this.engine.adjustLayout(window); this.engine.arrange(ctx); } else if (window.state === WindowState.Docked) { this.engine.adjustDock(window); this.engine.arrange(ctx); } else if (!CONFIG.adjustLayout) this.engine.enforceSize(ctx, window); } onWindowMaximizeChanged(ctx, window) { this.engine.arrange(ctx); } onWindowGeometryChanged(ctx, window) { this.engine.enforceSize(ctx, window); } onWindowChanged(ctx, window, comment) { if (window) { if (comment === "unminimized") ctx.currentWindow = window; const workingArea = window.surface.workingArea; if (window.floatGeometry.width > workingArea.width) { window.floatGeometry.width = workingArea.width; } if (window.floatGeometry.height > workingArea.height) { window.floatGeometry.height = workingArea.height; } window.floatGeometry.x = workingArea.x + (workingArea.width - window.floatGeometry.width) / 2; window.floatGeometry.y = workingArea.y + (workingArea.height - window.floatGeometry.height) / 2; this.engine.arrange(ctx); } } onWindowFocused(ctx, window) { window.timestamp = new Date().getTime(); } onDesktopsChanged(ctx, window) { if (window.state !== WindowState.Docked) window.state = WindowState.Undecided; } onShortcut(ctx, input, data) { if (CONFIG.directionalKeyMode === "dwm") { switch (input) { case Shortcut.FocusUp: input = Shortcut.FocusNext; break; case Shortcut.FocusDown: input = Shortcut.FocusPrev; break; case Shortcut.FocusLeft: input = Shortcut.DWMLeft; break; case Shortcut.FocusRight: input = Shortcut.DWMRight; break; } } else if (CONFIG.directionalKeyMode === "focus") { switch (input) { case Shortcut.ShiftUp: input = Shortcut.SwapUp; break; case Shortcut.ShiftDown: input = Shortcut.SwapDown; break; case Shortcut.ShiftLeft: input = Shortcut.SwapLeft; break; case Shortcut.ShiftRight: input = Shortcut.SwapRight; break; } } const window = ctx.currentWindow; if (window !== null && window.state === WindowState.Docked && this.engine.handleDockShortcut(ctx, window, input)) { this.engine.arrange(ctx); return; } else if (this.engine.handleLayoutShortcut(ctx, input, data)) { this.engine.arrange(ctx); return; } switch (input) { case Shortcut.FocusNext: this.engine.focusOrder(ctx, +1); break; case Shortcut.FocusPrev: this.engine.focusOrder(ctx, -1); break; case Shortcut.FocusUp: this.engine.focusDir(ctx, "up"); break; case Shortcut.FocusDown: this.engine.focusDir(ctx, "down"); break; case Shortcut.DWMLeft: case Shortcut.FocusLeft: this.engine.focusDir(ctx, "left"); break; case Shortcut.DWMRight: case Shortcut.FocusRight: this.engine.focusDir(ctx, "right"); break; case Shortcut.GrowWidth: if (window) { if (window.state === WindowState.Docked && window.dock) { if (window.dock.position === DockPosition.left || window.dock.position === DockPosition.right) { window.dock.cfg.vWide += 1; } else if (window.dock.position === DockPosition.top || window.dock.position === DockPosition.bottom) { window.dock.cfg.hWide += 1; } } else this.engine.resizeWindow(window, "east", 1); } break; case Shortcut.ShrinkWidth: if (window) { if (window.state === WindowState.Docked && window.dock) { if (window.dock.position === DockPosition.left || window.dock.position === DockPosition.right) { window.dock.cfg.vWide -= 1; } else if (window.dock.position === DockPosition.top || window.dock.position === DockPosition.bottom) { window.dock.cfg.hWide -= 1; } } else this.engine.resizeWindow(window, "east", -1); } break; case Shortcut.GrowHeight: if (window) { if (window.state === WindowState.Docked && window.dock) { if (window.dock.position === DockPosition.left || window.dock.position === DockPosition.right) { window.dock.cfg.vHeight += 1; } else if (window.dock.position === DockPosition.top || window.dock.position === DockPosition.bottom) { window.dock.cfg.hHeight += 1; } } else this.engine.resizeWindow(window, "south", 1); } break; case Shortcut.ShrinkHeight: if (window) { if (window.state === WindowState.Docked && window.dock) { if (window.dock.position === DockPosition.left || window.dock.position === DockPosition.right) { window.dock.cfg.vHeight -= 1; } else if (window.dock.position === DockPosition.top || window.dock.position === DockPosition.bottom) { window.dock.cfg.hHeight -= 1; } } else this.engine.resizeWindow(window, "south", -1); } break; case Shortcut.ShiftUp: if (window) this.engine.swapOrder(window, -1); break; case Shortcut.ShiftDown: if (window) this.engine.swapOrder(window, +1); break; case Shortcut.SwapUp: this.engine.swapDirOrMoveFloat(ctx, "up"); break; case Shortcut.SwapDown: this.engine.swapDirOrMoveFloat(ctx, "down"); break; case Shortcut.SwapLeft: this.engine.swapDirOrMoveFloat(ctx, "left"); break; case Shortcut.SwapRight: this.engine.swapDirOrMoveFloat(ctx, "right"); break; case Shortcut.SetMaster: if (window) this.engine.setMaster(window); break; case Shortcut.ToggleFloat: if (window) this.engine.toggleFloat(window); break; case Shortcut.ToggleFloatAll: this.engine.floatAll(ctx, ctx.currentSurface); break; case Shortcut.NextLayout: this.engine.cycleLayout(ctx, 1); break; case Shortcut.PreviousLayout: this.engine.cycleLayout(ctx, -1); break; case Shortcut.SetLayout: if (typeof data === "string") this.engine.setLayout(ctx, data); break; case Shortcut.ToggleDock: if (window) this.engine.toggleDock(window); break; } this.engine.arrange(ctx); } } class TilingEngine { constructor() { this.layouts = new LayoutStore(); this.windows = new WindowStore(); this.docks = new DockStore(); this._defaultGaps = null; this._gapsSurfacesCfg = []; } adjustLayout(basis) { let delta = basis.geometryDelta; if (delta === null) return; const srf = basis.surface; const layout = this.layouts.getCurrentLayout(srf); if (layout.adjust) { const gaps = this.getGaps(srf); const area = srf.workingArea.gap(gaps.left, gaps.right, gaps.top, gaps.bottom); const tiles = this.windows.getVisibleTiles(srf); layout.adjust(area, tiles, basis, delta, gaps.between); } } adjustDock(basis) { if (basis.actualGeometry === basis.geometry) return; let widthDiff = basis.actualGeometry.width - basis.geometry.width; let heightDiff = basis.actualGeometry.height - basis.geometry.height; let dockCfg = basis.dock.cfg; const workingArea = basis.surface.workingArea; switch (basis.dock.position) { case DockPosition.left: case DockPosition.right: dockCfg.vHeight = dockCfg.vHeight + (100 * heightDiff) / workingArea.height; dockCfg.vWide = dockCfg.vWide + (100 * widthDiff) / workingArea.width; break; case DockPosition.top: case DockPosition.bottom: dockCfg.hHeight = dockCfg.hHeight + (100 * heightDiff) / workingArea.height; dockCfg.hWide = dockCfg.hWide + (100 * widthDiff) / workingArea.width; break; } } resizeFloat(window, dir, step) { const srf = window.surface; const hStepSize = srf.workingArea.width * 0.05; const vStepSize = srf.workingArea.height * 0.05; let hStep, vStep; switch (dir) { case "east": (hStep = step), (vStep = 0); break; case "west": (hStep = -step), (vStep = 0); break; case "south": (hStep = 0), (vStep = step); break; case "north": (hStep = 0), (vStep = -step); break; } const geometry = window.actualGeometry; const width = geometry.width + hStepSize * hStep; const height = geometry.height + vStepSize * vStep; window.forceSetGeometry(new Rect(geometry.x, geometry.y, width, height)); } resizeTile(basis, dir, step) { const srf = basis.surface; const gaps = this.getGaps(srf); if (dir === "east") { const maxX = basis.geometry.maxX; const easternNeighbor = this.windows .getVisibleTiles(srf) .filter((tile) => tile.geometry.x >= maxX); if (easternNeighbor.length === 0) { dir = "west"; step *= -1; } } else if (dir === "south") { const maxY = basis.geometry.maxY; const southernNeighbor = this.windows .getVisibleTiles(srf) .filter((tile) => tile.geometry.y >= maxY); if (southernNeighbor.length === 0) { dir = "north"; step *= -1; } } const hStepSize = srf.workingArea.width * 0.03; const vStepSize = srf.workingArea.height * 0.03; let delta; switch (dir) { case "east": delta = new RectDelta(hStepSize * step, 0, 0, 0); break; case "west": delta = new RectDelta(0, hStepSize * step, 0, 0); break; case "south": delta = new RectDelta(0, 0, vStepSize * step, 0); break; case "north": default: delta = new RectDelta(0, 0, 0, vStepSize * step); break; } const layout = this.layouts.getCurrentLayout(srf); if (layout.adjust) { const area = srf.workingArea.gap(gaps.left, gaps.right, gaps.top, gaps.bottom); layout.adjust(area, this.windows.getVisibleTileables(srf), basis, delta, gaps.between); } } resizeWindow(window, dir, step) { const state = window.state; if (WindowClass.isFloatingState(state)) this.resizeFloat(window, dir, step); else if (WindowClass.isTiledState(state)) this.resizeTile(window, dir, step); } arrange(ctx) { ctx.screens.forEach((srf) => { this.arrangeScreen(ctx, srf); }); } arrangeScreen(ctx, srf) { const layout = this.layouts.getCurrentLayout(srf); const visibles = this.windows.getVisibleWindows(srf); LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.arrangeScreen, "Begin", `layout: ${layout}, surface: ${srf}, visibles number: ${visibles.length}`); const gaps = this.getGaps(srf); const workingArea = this.docks.render(srf, visibles, srf.workingArea.clone()); visibles.forEach((window) => { if (window.state === WindowState.Undecided) { window.state = window.shouldFloat ? WindowState.Floating : WindowState.Tiled; } }); const tileables = this.windows.getVisibleTileables(srf); let tilingArea; if ((CONFIG.monocleMaximize && layout instanceof MonocleLayout) || (tileables.length === 1 && CONFIG.soleWindowNoGaps)) tilingArea = workingArea; else if (tileables.length === 1 && ((CONFIG.soleWindowWidth < 100 && CONFIG.soleWindowWidth > 0) || (CONFIG.soleWindowHeight < 100 && CONFIG.soleWindowHeight > 0))) { const h_gap = (workingArea.height - workingArea.height * (CONFIG.soleWindowHeight / 100)) / 2; const v_gap = (workingArea.width - workingArea.width * (CONFIG.soleWindowWidth / 100)) / 2; tilingArea = workingArea.gap(v_gap, v_gap, h_gap, h_gap); } else tilingArea = workingArea.gap(gaps.left, gaps.right, gaps.top, gaps.bottom); if (tileables.length > 0) layout.apply(new EngineContext(ctx, this), tileables, tilingArea, gaps.between); if (CONFIG.limitTileWidthRatio > 0 && !(layout instanceof MonocleLayout)) { const maxWidth = Math.floor(workingArea.height * CONFIG.limitTileWidthRatio); tileables .filter((tile) => tile.tiled && tile.geometry.width > maxWidth) .forEach((tile) => { const g = tile.geometry; tile.geometry = new Rect(g.x + Math.floor((g.width - maxWidth) / 2), g.y, maxWidth, g.height); }); } if (CONFIG.soleWindowNoBorders && tileables.length === 1) { visibles.forEach((window) => { if (window.state === WindowState.Tiled) window.commit(CONFIG.soleWindowNoBorders); else window.commit(); }); } else { visibles.forEach((window) => window.commit()); } LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.arrangeScreen, "Finished", `${srf}`); } enforceSize(ctx, window) { if (window.tiled && !window.actualGeometry.equals(window.geometry)) ctx.setTimeout(() => { if (window.tiled) window.commit(); }, 10); } manage(window) { if (!window.shouldIgnore) { if (this.docks.isNewWindowHaveDocked(window)) { window.state = WindowState.Docked; } else window.state = WindowState.Undecided; if (CONFIG.newWindowPosition === 1) this.windows.unshift(window); else if (CONFIG.newWindowPosition === 2) { this.windows.beside_first(window); } else this.windows.push(window); } } unmanage(window) { if (window.state === WindowState.Docked) { this.docks.remove(window); } this.windows.remove(window); } focusOrder(ctx, step) { const window = ctx.currentWindow; if (window === null) { const tiles = this.windows.getVisibleTiles(ctx.currentSurface); if (tiles.length > 1) ctx.currentWindow = tiles[0]; return; } const visibles = this.windows.getVisibleWindows(ctx.currentSurface); if (visibles.length === 0) return; const idx = visibles.indexOf(window); if (!window || idx < 0) { ctx.currentWindow = visibles[0]; return; } const num = visibles.length; const newIndex = (idx + (step % num) + num) % num; ctx.currentWindow = visibles[newIndex]; } focusDir(ctx, dir) { const window = ctx.currentWindow; if (window === null) { const tiles = this.windows.getVisibleTiles(ctx.currentSurface); if (tiles.length > 1) ctx.currentWindow = tiles[0]; return; } const neighbor = this.getNeighborByDirection(ctx, window, dir); if (neighbor) ctx.currentWindow = neighbor; } swapOrder(window, step) { const srf = window.surface; const visibles = this.windows.getVisibleWindows(srf); if (visibles.length < 2) return; const vsrc = visibles.indexOf(window); const vdst = wrapIndex(vsrc + step, visibles.length); const dstWin = visibles[vdst]; this.windows.move(window, dstWin); } swapDirection(ctx, dir) { const window = ctx.currentWindow; if (window === null) { const tiles = this.windows.getVisibleTiles(ctx.currentSurface); if (tiles.length > 1) ctx.currentWindow = tiles[0]; return; } const neighbor = this.getNeighborByDirection(ctx, window, dir); if (neighbor) this.windows.swap(window, neighbor); } moveFloat(window, dir) { const srf = window.surface; const hStepSize = srf.workingArea.width * 0.05; const vStepSize = srf.workingArea.height * 0.05; let hStep, vStep; switch (dir) { case "up": (hStep = 0), (vStep = -1); break; case "down": (hStep = 0), (vStep = 1); break; case "left": (hStep = -1), (vStep = 0); break; case "right": (hStep = 1), (vStep = 0); break; } const geometry = window.actualGeometry; const x = geometry.x + hStepSize * hStep; const y = geometry.y + vStepSize * vStep; window.forceSetGeometry(new Rect(x, y, geometry.width, geometry.height)); } swapDirOrMoveFloat(ctx, dir) { const window = ctx.currentWindow; if (!window) return; const state = window.state; if (WindowClass.isFloatingState(state)) this.moveFloat(window, dir); else if (WindowClass.isTiledState(state)) this.swapDirection(ctx, dir); } toggleDock(window) { window.state = window.state !== WindowState.Docked ? WindowState.Docked : WindowState.Tiled; } toggleFloat(window) { window.state = !window.tileable ? WindowState.Tiled : WindowState.Floating; } floatAll(ctx, srf) { const windows = this.windows.getVisibleWindows(srf); const numFloats = windows.reduce((count, window) => { return window.state === WindowState.Floating ? count + 1 : count; }, 0); if (numFloats < windows.length / 2) { windows.forEach((window) => { window.floatGeometry = window.actualGeometry.gap(4, 4, 4, 4); window.state = WindowState.Floating; }); ctx.showNotification("Float All"); } else { windows.forEach((window) => { window.state = WindowState.Tiled; }); ctx.showNotification("Tile All"); } } setMaster(window) { this.windows.setMaster(window); } cycleLayout(ctx, step) { const layout = this.layouts.cycleLayout(ctx.currentSurface, step); if (layout) ctx.showNotification(layout.description); } setLayout(ctx, layoutClassID) { const layout = this.layouts.setLayout(ctx.currentSurface, layoutClassID); if (layout) ctx.showNotification(layout.description); } handleLayoutShortcut(ctx, input, data) { const layout = this.layouts.getCurrentLayout(ctx.currentSurface); if (layout.handleShortcut) return layout.handleShortcut(new EngineContext(ctx, this), input, data); return false; } handleDockShortcut(ctx, window, input) { return this.docks.handleShortcut(ctx, window, input); } getNeighborByDirection(ctx, basis, dir) { let vertical; let sign; switch (dir) { case "up": vertical = true; sign = -1; break; case "down": vertical = true; sign = 1; break; case "left": vertical = false; sign = -1; break; case "right": vertical = false; sign = 1; break; default: return null; } const candidates = this.windows .getVisibleTiles(ctx.currentSurface) .filter(vertical ? (tile) => tile.geometry.y * sign > basis.geometry.y * sign : (tile) => tile.geometry.x * sign > basis.geometry.x * sign) .filter(vertical ? (tile) => overlap(basis.geometry.x, basis.geometry.maxX, tile.geometry.x, tile.geometry.maxX) : (tile) => overlap(basis.geometry.y, basis.geometry.maxY, tile.geometry.y, tile.geometry.maxY)); if (candidates.length === 0) return null; const min = sign * candidates.reduce(vertical ? (prevMin, tile) => Math.min(tile.geometry.y * sign, prevMin) : (prevMin, tile) => Math.min(tile.geometry.x * sign, prevMin), Infinity); const closest = candidates.filter(vertical ? (tile) => tile.geometry.y === min : (tile) => tile.geometry.x === min); return closest.sort((a, b) => b.timestamp - a.timestamp)[0]; } getGaps(srf) { if (this._defaultGaps === null) { this._defaultGaps = DefaultGapsCfg.instance; this._gapsSurfacesCfg = gapsSurfaceCfg.parseGapsUserSurfacesCfg(); } const surfaceCfg = this._gapsSurfacesCfg.find((surfaceCfg) => surfaceCfg.isFit(srf)); if (surfaceCfg === undefined) return this._defaultGaps; return surfaceCfg.cfg; } } class EngineContext { get backend() { return this.drvctx.backend; } get currentWindow() { return this.drvctx.currentWindow; } set currentWindow(window) { this.drvctx.currentWindow = window; } get cursorPos() { return this.drvctx.cursorPosition; } get surfaceParams() { let srf = this.drvctx.currentSurface; return srf.output.name, srf.activity, srf.desktop.name; } constructor(drvctx, engine) { this.drvctx = drvctx; this.engine = engine; } setTimeout(func, timeout) { this.drvctx.setTimeout(func, timeout); } cycleFocus(step) { this.engine.focusOrder(this.drvctx, step); } moveWindow(window, target, after) { this.engine.windows.move(window, target, after); } moveWindowByWinId(window, targetId, after) { let target = this.engine.windows.getWindowById(targetId); if (target === null) return; this.engine.windows.moveNew(window, target, after); } getWindowById(id) { return this.engine.windows.getWindowById(id); } showNotification(text) { this.drvctx.showNotification(text); } } class DefaultGapsCfg { constructor() { let left = validateNumber(CONFIG.screenGapLeft); if (left instanceof Err) { warning(`DefaultGapsCfg: left: ${left}`); this.left = 0; } else this.left = left; let right = validateNumber(CONFIG.screenGapRight); if (right instanceof Err) { warning(`DefaultGapsCfg: right: ${right}`); this.right = 0; } else this.right = right; let top = validateNumber(CONFIG.screenGapTop); if (top instanceof Err) { warning(`DefaultGapsCfg: top: ${top}`); this.top = 0; } else this.top = top; let bottom = validateNumber(CONFIG.screenGapBottom); if (bottom instanceof Err) { warning(`DefaultGapsCfg: bottom: ${bottom}`); this.bottom = 0; } else this.bottom = bottom; let between = validateNumber(CONFIG.screenGapBetween); if (between instanceof Err) { warning(`DefaultGapsCfg: between: ${between}`); this.between = 0; } else this.between = between; } static get instance() { if (!DefaultGapsCfg._gapsInstance) { DefaultGapsCfg._gapsInstance = new DefaultGapsCfg(); } return DefaultGapsCfg._gapsInstance; } cloneAndUpdate(cfg) { return Object.assign({}, DefaultGapsCfg.instance, cfg); } } class gapsSurfaceCfg { constructor(outputName, activityId, vDesktopName, cfg) { this.outputName = outputName; this.activityId = activityId; this.vDesktopName = vDesktopName; this.cfg = cfg; } isFit(srf) { return ((this.outputName === "" || this.outputName === srf.output.name) && (this.vDesktopName === "" || this.vDesktopName === srf.desktop.name) && (this.activityId === "" || this.activityId === srf.activity)); } toString() { return `gapsSurfaceCfg: Output Name: ${this.outputName}, Activity ID: ${this.activityId}, Virtual Desktop Name: ${this.vDesktopName} cfg: ${this.cfg}`; } static parseGapsUserSurfacesCfg() { let surfacesCfg = []; if (CONFIG.gapsOverrideConfig.length === 0) return surfacesCfg; CONFIG.gapsOverrideConfig.forEach((cfg) => { let surfaceCfgString = cfg.split(":").map((part) => part.trim()); if ([2, 4].indexOf(surfaceCfgString.length) < 0) { warning(`Invalid Gaps surface config: ${cfg}, config must have one or three colons`); return; } let outputName = surfaceCfgString[0]; let activityId; let vDesktopName; let userCfg; if (surfaceCfgString.length === 4) { activityId = surfaceCfgString[1]; vDesktopName = surfaceCfgString[2]; userCfg = surfaceCfgString[3]; } else { activityId = ""; vDesktopName = ""; userCfg = surfaceCfgString[1]; } let splittedUserCfg = userCfg .split(",") .map((part) => part.trim().toLowerCase()); let partialGapsCfg = gapsSurfaceCfg.parseSplittedGapsCfg(splittedUserCfg); if (partialGapsCfg instanceof Err) { warning(`Invalid Gaps User surface config: ${cfg}. ${partialGapsCfg}`); return; } if (Object.keys(partialGapsCfg).length > 0) { surfacesCfg.push(new gapsSurfaceCfg(outputName, activityId, vDesktopName, DefaultGapsCfg.instance.cloneAndUpdate(partialGapsCfg))); } }); return surfacesCfg; } static parseSplittedGapsCfg(splittedUserCfg) { let errors = []; let value; let gapsCfg = {}; splittedUserCfg.forEach((part) => { let splittedPart = part .split("=") .map((part) => part.trim().toLowerCase()); if (splittedPart.length !== 2) { errors.push(`"${part}" can have only one equal sign`); return; } if (splittedPart[0].length === 0 || splittedPart[1].length === 0) { errors.push(`"${part}" can not have empty name or value`); return; } value = validateNumber(splittedPart[1]); if (value instanceof Err) { errors.push(`GapsCfg: ${part}, ${splittedPart[1]} ${value}`); return; } switch (splittedPart[0]) { case "left": case "l": gapsCfg["left"] = value; break; case "right": case "r": gapsCfg["right"] = value; break; case "top": case "t": gapsCfg["top"] = value; break; case "bottom": case "b": gapsCfg["bottom"] = value; break; case "between": case "e": gapsCfg["between"] = value; break; default: errors.push(` "${part}" unknown parameter name. It can be l,r,t,b,e or left,right,top,bottom,between`); return; } }); if (errors.length > 0) { return new Err(errors.join("\n")); } return gapsCfg; } } class LayoutStoreEntry { get currentLayout() { return this.loadLayout(this.currentID); } constructor(outputName, desktopName, activity) { let layouts = CONFIG.layoutOrder.map((layout) => layout.toLowerCase()); let layouts_str = layouts.map((layout, i) => i + "." + layout + " "); print(`Krohnkite: Screen(output):${outputName}, Desktop(name):${desktopName}, Activity: ${activity}, layouts: ${layouts_str}`); this.currentIndex = 0; this.currentID = CONFIG.layoutOrder[0]; CONFIG.screenDefaultLayout.some((entry) => { let cfg = entry.split(":"); const cfgLength = cfg.length; if (cfgLength < 2 && cfgLength > 4) return false; let cfgOutput = cfg[0]; let cfgActivity = ""; let cfgVDesktop = ""; let cfgLayout = undefined; if (cfgLength === 2) { cfgLayout = cfg[1]; } else if (cfgLength === 3) { cfgVDesktop = cfg[1]; cfgLayout = cfg[2]; } else if (cfgLength === 4) { cfgActivity = cfg[1]; cfgVDesktop = cfg[2]; cfgLayout = cfg[3]; } if (cfgLayout === undefined) return false; let cfgLayoutId = parseInt(cfgLayout); if (isNaN(cfgLayoutId)) { cfgLayoutId = layouts.indexOf(cfgLayout.toLowerCase()); cfgLayoutId = cfgLayoutId >= 0 ? cfgLayoutId : layouts.indexOf(cfgLayout.toLowerCase() + "layout"); } if ((outputName === cfgOutput || cfgOutput === "") && (desktopName === cfgVDesktop || cfgVDesktop === "") && (activity === cfgActivity || cfgActivity === "") && cfgLayoutId >= 0 && cfgLayoutId < CONFIG.layoutOrder.length) { this.currentIndex = cfgLayoutId; this.currentID = CONFIG.layoutOrder[this.currentIndex]; return true; } }); this.layouts = {}; this.previousID = this.currentID; this.loadLayout(this.currentID); } cycleLayout(step) { this.previousID = this.currentID; this.currentIndex = this.currentIndex !== null ? wrapIndex(this.currentIndex + step, CONFIG.layoutOrder.length) : 0; this.currentID = CONFIG.layoutOrder[this.currentIndex]; return this.loadLayout(this.currentID); } setLayout(targetID) { let targetLayout = this.loadLayout(targetID); if (targetLayout instanceof MonocleLayout && this.currentLayout instanceof MonocleLayout) { this.currentID = this.previousID; this.previousID = targetID; targetLayout = this.loadLayout(this.currentID); } else if (this.currentID !== targetID) { this.previousID = this.currentID; this.currentID = targetID; } this.updateCurrentIndex(); return targetLayout; } updateCurrentIndex() { const idx = CONFIG.layoutOrder.indexOf(this.currentID); this.currentIndex = idx === -1 ? null : idx; } loadLayout(ID) { let layout = this.layouts[ID]; if (!layout) layout = this.layouts[ID] = CONFIG.layoutFactories[ID](); return layout; } } class LayoutStore { constructor() { this.store = {}; } getCurrentLayout(srf) { return srf.ignore ? FloatingLayout.instance : this.getEntry(srf).currentLayout; } cycleLayout(srf, step) { if (srf.ignore) return null; return this.getEntry(srf).cycleLayout(step); } setLayout(srf, layoutClassID) { if (srf.ignore) return null; return this.getEntry(srf).setLayout(layoutClassID); } getEntry(srf) { if (!this.store[srf.id]) { let key_without_activity = KWinSurface.generateId(srf.output.name, "", srf.desktop.id); if (this.store[key_without_activity]) { this.store[srf.id] = this.store[key_without_activity]; delete this.store[key_without_activity]; } else { this.store[srf.id] = new LayoutStoreEntry(srf.output.name, srf.desktop.name, srf.activity); } } return this.store[srf.id]; } } var WindowState; (function (WindowState) { WindowState[WindowState["Unmanaged"] = 0] = "Unmanaged"; WindowState[WindowState["NativeFullscreen"] = 1] = "NativeFullscreen"; WindowState[WindowState["NativeMaximized"] = 2] = "NativeMaximized"; WindowState[WindowState["Floating"] = 3] = "Floating"; WindowState[WindowState["Maximized"] = 4] = "Maximized"; WindowState[WindowState["Tiled"] = 5] = "Tiled"; WindowState[WindowState["TiledAfloat"] = 6] = "TiledAfloat"; WindowState[WindowState["Undecided"] = 7] = "Undecided"; WindowState[WindowState["Dragging"] = 8] = "Dragging"; WindowState[WindowState["Docked"] = 9] = "Docked"; })(WindowState || (WindowState = {})); class WindowClass { static isTileableState(state) { return (state === WindowState.Dragging || state === WindowState.Tiled || state === WindowState.Maximized || state === WindowState.TiledAfloat); } static isTiledState(state) { return state === WindowState.Tiled || state === WindowState.Maximized; } static isFloatingState(state) { return state === WindowState.Floating || state === WindowState.TiledAfloat; } get actualGeometry() { return this.window.geometry; } get shouldFloat() { return this.window.shouldFloat; } get shouldIgnore() { return this.window.shouldIgnore; } get tileable() { return WindowClass.isTileableState(this.state); } get tiled() { return WindowClass.isTiledState(this.state); } get floating() { return WindowClass.isFloatingState(this.state); } get geometryDelta() { if (this.geometry === this.actualGeometry) return null; return RectDelta.fromRects(this.geometry, this.actualGeometry); } get state() { if (this.window.fullScreen) return WindowState.NativeFullscreen; if (this.window.maximized) return WindowState.NativeMaximized; return this.internalState; } set state(value) { const state = this.state; if (state === value || state === WindowState.Dragging) return; if ((state === WindowState.Unmanaged || WindowClass.isTileableState(state)) && WindowClass.isFloatingState(value)) this.shouldCommitFloat = true; else if (WindowClass.isFloatingState(state) && WindowClass.isTileableState(value)) this.floatGeometry = this.actualGeometry; this.internalState = value; } setDraggingState() { this.internalState = WindowState.Dragging; } setState(value) { this.internalState = value; } get surface() { return this.window.surface; } set surface(srf) { this.window.surface = srf; } get weight() { const srfID = this.window.surface.id; const weight = this.weightMap[srfID]; if (weight === undefined) { this.weightMap[srfID] = 1.0; return 1.0; } return weight; } set weight(value) { const srfID = this.window.surface.id; this.weightMap[srfID] = value; } get windowClassName() { return this.window.windowClassName; } constructor(window) { this.id = window.id; this.window = window; this.floatGeometry = window.geometry; this.geometry = window.geometry; this.timestamp = 0; this.internalState = WindowState.Unmanaged; this.shouldCommitFloat = this.shouldFloat; this.weightMap = {}; this.dock = null; this._minSize = window.minSize; this._maxSize = window.maxSize; } get minSize() { return this._minSize; } get maxSize() { return this._maxSize; } commit(noBorders) { const state = this.state; LOG === null || LOG === void 0 ? void 0 : LOG.send(LogModules.window, "commit", `state: ${WindowState[state]}`); switch (state) { case WindowState.Dragging: break; case WindowState.NativeMaximized: this.window.commit(undefined, undefined, undefined); break; case WindowState.NativeFullscreen: this.window.commit(undefined, undefined, 1); break; case WindowState.Floating: if (!this.shouldCommitFloat) break; this.window.commit(this.floatGeometry, false, CONFIG.floatedWindowsLayer); this.shouldCommitFloat = false; break; case WindowState.Maximized: this.window.commit(this.geometry, true, 1); break; case WindowState.Tiled: this.window.commit(this.geometry, CONFIG.noTileBorder || Boolean(noBorders), CONFIG.tiledWindowsLayer); break; case WindowState.TiledAfloat: if (!this.shouldCommitFloat) break; this.window.commit(this.floatGeometry, false, CONFIG.floatedWindowsLayer); this.shouldCommitFloat = false; break; case WindowState.Floating: this.window.commit(this.geometry, CONFIG.noTileBorder || Boolean(noBorders), CONFIG.floatedWindowsLayer); break; case WindowState.Docked: this.window.commit(this.geometry, CONFIG.noTileBorder, CONFIG.tiledWindowsLayer); break; } } forceSetGeometry(geometry) { this.window.commit(geometry); } visible(srf) { return this.window.visible(srf); } get minimized() { return this.window.minimized; } toString() { return "Window(" + String(this.window) + ")"; } } class WindowStore { constructor(windows) { this.list = windows || []; } move(srcWin, destWin, after) { const srcIdx = this.list.indexOf(srcWin); const destIdx = this.list.indexOf(destWin); if (srcIdx === -1 || destIdx === -1) return; this.list.splice(srcIdx, 1); this.list.splice(after ? destIdx + 1 : destIdx, 0, srcWin); } moveNew(srcWin, destWin, after) { const srcIdx = this.list.indexOf(srcWin); const destIdx = this.list.indexOf(destWin); if (srcIdx === -1 || destIdx === -1) return; if (srcIdx > destIdx) { this.list.splice(srcIdx, 1); this.list.splice(after ? destIdx + 1 : destIdx, 0, srcWin); } else if (destIdx > srcIdx) { this.list.splice(srcIdx, 1); this.list.splice(after ? destIdx : destIdx - 1, 0, srcWin); } } getWindowById(id) { let idx = this.list.map((w) => w.id).indexOf(id); return idx < 0 ? null : this.list[idx]; } setMaster(window) { const idx = this.list.indexOf(window); if (idx === -1) return; this.list.splice(idx, 1); this.list.splice(0, 0, window); } swap(alpha, beta) { const alphaIndex = this.list.indexOf(alpha); const betaIndex = this.list.indexOf(beta); if (alphaIndex < 0 || betaIndex < 0) return; this.list[alphaIndex] = beta; this.list[betaIndex] = alpha; } get length() { return this.list.length; } at(idx) { return this.list[idx]; } indexOf(window) { return this.list.indexOf(window); } push(window) { this.list.push(window); } beside_first(window) { this.list.splice(1, 0, window); } remove(window) { const idx = this.list.indexOf(window); if (idx >= 0) this.list.splice(idx, 1); } unshift(window) { this.list.unshift(window); } getVisibleWindows(srf) { return this.list.filter((win) => win.visible(srf)); } getVisibleTiles(srf) { return this.list.filter((win) => win.tiled && win.visible(srf)); } getVisibleTileables(srf) { return this.list.filter((win) => win.tileable && win.visible(srf)); } } function getWindowLayer(index) { if (index === 0) return 0; else if (index === 1) return 1; else if (index === 2) return 2; else return 1; } class BTreeLayout { get description() { return "BTree"; } constructor() { this.classID = BTreeLayout.id; this.parts = new HalfSplitLayoutPart(new FillLayoutPart(), new FillLayoutPart()); this.parts.angle = 0; } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); this.create_parts(tileables.length); let rectangles = this.parts.apply(area, tileables, gap); rectangles.forEach((geometry, i) => { tileables[i].geometry = geometry; }); } create_parts(tiles_len) { let head = this.get_head(); head.angle = 0; if (tiles_len > 2) { let level = Math.ceil(Math.log(tiles_len) * 1.442695); let level_capacity = Math.pow(2, (level - 1)); let half_level_capacity = Math.pow(2, (level - 2)); if (tiles_len > level_capacity + half_level_capacity) { head.primarySize = tiles_len - level_capacity; } else { head.primarySize = half_level_capacity; } this.build_binary_tree(head, level, 2, tiles_len); } this.parts = head; } build_binary_tree(head, max_level, current_level, tiles_len) { if (current_level <= max_level) { if (head.primarySize > 1) { let primary = this.get_head(); primary.primarySize = Math.floor(head.primarySize / 2); primary.angle = current_level % 2 ? 0 : 90; head.primary = primary; this.build_binary_tree(primary, max_level, current_level + 1, head.primarySize); } if (tiles_len - head.primarySize > 1) { let secondary = this.get_head(); secondary.primarySize = Math.floor((tiles_len - head.primarySize) / 2); secondary.angle = current_level % 2 ? 0 : 90; head.secondary = secondary; this.build_binary_tree(secondary, max_level, current_level + 1, tiles_len - head.primarySize); } } } get_head() { return new HalfSplitLayoutPart(new FillLayoutPart(), new FillLayoutPart()); } clone() { const other = new StackedLayout(); return other; } toString() { return "BTreeLayout()"; } } BTreeLayout.id = "BTreeLayout"; var CascadeDirection; (function (CascadeDirection) { CascadeDirection[CascadeDirection["NorthWest"] = 0] = "NorthWest"; CascadeDirection[CascadeDirection["North"] = 1] = "North"; CascadeDirection[CascadeDirection["NorthEast"] = 2] = "NorthEast"; CascadeDirection[CascadeDirection["East"] = 3] = "East"; CascadeDirection[CascadeDirection["SouthEast"] = 4] = "SouthEast"; CascadeDirection[CascadeDirection["South"] = 5] = "South"; CascadeDirection[CascadeDirection["SouthWest"] = 6] = "SouthWest"; CascadeDirection[CascadeDirection["West"] = 7] = "West"; })(CascadeDirection || (CascadeDirection = {})); class CascadeLayout { static decomposeDirection(dir) { switch (dir) { case CascadeDirection.NorthWest: return [-1, -1]; case CascadeDirection.North: return [-1, 0]; case CascadeDirection.NorthEast: return [-1, 1]; case CascadeDirection.East: return [0, 1]; case CascadeDirection.SouthEast: return [1, 1]; case CascadeDirection.South: return [1, 0]; case CascadeDirection.SouthWest: return [1, -1]; case CascadeDirection.West: return [0, -1]; } } get description() { return "Cascade [" + CascadeDirection[this.dir] + "]"; } constructor(dir = CascadeDirection.SouthEast) { this.dir = dir; this.classID = CascadeLayout.id; } apply(ctx, tileables, area, gap) { const [vertStep, horzStep] = CascadeLayout.decomposeDirection(this.dir); const stepSize = 25; const windowWidth = horzStep !== 0 ? area.width - stepSize * (tileables.length - 1) : area.width; const windowHeight = vertStep !== 0 ? area.height - stepSize * (tileables.length - 1) : area.height; const baseX = horzStep >= 0 ? area.x : area.maxX - windowWidth; const baseY = vertStep >= 0 ? area.y : area.maxY - windowHeight; let x = baseX, y = baseY; tileables.forEach((tile) => { tile.state = WindowState.Tiled; tile.geometry = new Rect(x, y, windowWidth, windowHeight); x += horzStep * stepSize; y += vertStep * stepSize; }); } clone() { return new CascadeLayout(this.dir); } handleShortcut(ctx, input, data) { switch (input) { case Shortcut.Increase: this.dir = (this.dir + 1 + 8) % 8; ctx.showNotification(this.description); break; case Shortcut.Decrease: this.dir = (this.dir - 1 + 8) % 8; ctx.showNotification(this.description); break; default: return false; } return true; } } CascadeLayout.id = "CascadeLayout"; class ColumnLayout { get description() { return "Column"; } toString() { let s = `ColumnLayout${this.windowIds.size}:`; this.windowIds.forEach((id) => (s = s + id + ",")); return s; } constructor() { this.classID = ColumnLayout.id; this.position = "single"; this.weight = 1.0; this.parts = new RotateLayoutPart(new StackLayoutPart()); this.windowIds = new Set(); this.renderedWindowsIds = []; this.renderedWindowsRects = []; this.numberFloatedOrMinimized = 0; this.timestamp = 0; } get size() { return this.windowIds.size - this.numberFloatedOrMinimized; } set isHorizontal(value) { if (value) this.parts.angle = 270; else this.parts.angle = 0; } isEmpty() { return this.windowIds.size === this.numberFloatedOrMinimized; } apply(ctx, tileables, area, gap) { this.renderedWindowsIds = []; let columnTileables = tileables.filter((w) => { if (this.windowIds.has(w.id)) { this.renderedWindowsIds.push(w.id); return true; } }); this.renderedWindowsRects = []; this.parts.apply(area, columnTileables, gap).forEach((geometry, i) => { columnTileables[i].geometry = geometry; this.renderedWindowsRects.push(geometry); }); } getUpperWindowId(id) { let winId = this.renderedWindowsIds.indexOf(id); if (winId < 1) return null; return this.renderedWindowsIds[winId - 1]; } getLowerWindowId(id) { let winId = this.renderedWindowsIds.indexOf(id); if (winId < 0 || winId === this.renderedWindowsIds.length - 1) return null; return this.renderedWindowsIds[winId + 1]; } getWindowIdOnRight(x) { for (let i = 0; i < this.renderedWindowsIds.length; i++) { if (x < this.renderedWindowsRects[i].center[0] + 10) return this.renderedWindowsIds[i]; } return null; } getWindowIdOnTop(y) { for (let i = 0; i < this.renderedWindowsIds.length; i++) { if (y < this.renderedWindowsRects[i].center[1] + 10) return this.renderedWindowsIds[i]; } return null; } adjust(area, tiles, basis, delta, gap) { let columnTiles = tiles.filter((t) => this.windowIds.has(t.id)); this.parts.adjust(area, columnTiles, basis, delta, gap); } actualizeWindowIds(ctx, ids) { let window; let floatedOrMinimized = 0; this.windowIds = new Set([...this.windowIds].filter((id) => { window = ctx.getWindowById(id); if (ids.has(id)) return true; else if (window !== null && (window.minimized || window.floating)) { floatedOrMinimized += 1; return true; } return false; })); this.numberFloatedOrMinimized = floatedOrMinimized; } } ColumnLayout.id = "Column"; class ColumnsLayout { get description() { return "Columns"; } get columns() { return this._columns; } constructor() { this.classID = ColumnsLayout.id; this.parts = [new ColumnLayout()]; this._columns = []; this.direction = new windRose(CONFIG.columnsLayoutInitialAngle); this.columnsConfiguration = null; } adjust(area, tiles, basis, delta, gap) { let columnId = this.getColumnId(basis); if (columnId === null) return; let isReverse = this.direction.east || this.direction.south; let columnsLength = this.columns.length; if (((this.direction.east || this.direction.west) && (delta.east !== 0 || delta.west !== 0)) || ((this.direction.north || this.direction.south) && (delta.north !== 0 || delta.south !== 0))) { let oldWeights; if (isReverse) { oldWeights = this.columns .slice(0) .reverse() .map((column) => column.weight); } else { oldWeights = this.columns.map((column) => column.weight); } const weights = LayoutUtils.adjustAreaWeights(area, oldWeights, gap, isReverse ? columnsLength - 1 - columnId : columnId, delta, this.direction.east || this.direction.west); weights.forEach((weight, i) => { this.columns[isReverse ? columnsLength - 1 - i : i].weight = weight * columnsLength; }); } if (((delta.north !== 0 || delta.south !== 0) && (this.direction.east || this.direction.west)) || ((delta.east !== 0 || delta.west !== 0) && (this.direction.north || this.direction.south))) { this.columns[columnId].adjust(area, tiles, basis, delta, gap); } } apply(ctx, tileables, area, gap) { if (this.columnsConfiguration === null) this.columnsConfiguration = this.getDefaultConfig(ctx); this.arrangeTileables(ctx, tileables); if (this.columns.length === 0) return; let weights; if (this.direction.east || this.direction.south) { weights = this.columns .slice(0) .reverse() .map((tile) => tile.weight); } else { weights = this.columns.map((tile) => tile.weight); } const rects = LayoutUtils.splitAreaWeighted(area, weights, gap, this.direction.east || this.direction.west); if (this.direction.east || this.direction.south) { let i = 0; for (var idx = this.columns.length - 1; idx >= 0; idx--) { this.columns[idx].isHorizontal = this.direction.south; this.columns[idx].apply(ctx, tileables, rects[i], gap); i++; } } else { for (var idx = 0; idx < this.columns.length; idx++) { this.columns[idx].isHorizontal = this.direction.north; this.columns[idx].apply(ctx, tileables, rects[idx], gap); } } } drag(ctx, draggingRect, window, workingArea) { const cursorOrActivationPoint = ctx.cursorPos || draggingRect.activationPoint; const cursorOrMiddlePoint = ctx.cursorPos || draggingRect.center; if (this.columns.length === 0 || (this.columns.length === 1 && this.columns[0].windowIds.size === 1)) return false; let columnId = this.getColumnId(window); let windowId = window.id; if (((this.direction.north && workingArea.isTopZone(cursorOrActivationPoint)) || (this.direction.south && workingArea.isBottomZone(cursorOrMiddlePoint)) || (this.direction.west && workingArea.isLeftZone(cursorOrActivationPoint)) || (this.direction.east && workingArea.isRightZone(cursorOrActivationPoint))) && !(this.columns[0].windowIds.size === 1 && this.columns[0].windowIds.has(windowId))) { if (columnId !== null) this.columns[columnId].windowIds.delete(windowId); const column = this.insertColumn(true); column.windowIds.add(windowId); return true; } if (((this.direction.north && workingArea.isBottomZone(cursorOrMiddlePoint)) || (this.direction.south && workingArea.isTopZone(cursorOrActivationPoint)) || (this.direction.west && workingArea.isRightZone(cursorOrActivationPoint)) || (this.direction.east && workingArea.isLeftZone(cursorOrActivationPoint))) && !(this.columns[this.columns.length - 1].windowIds.size === 1 && this.columns[this.columns.length - 1].windowIds.has(windowId))) { if (columnId !== null) this.columns[columnId].windowIds.delete(windowId); const column = this.insertColumn(false); column.windowIds.add(windowId); return true; } for (let colIdx = 0; colIdx < this.columns.length; colIdx++) { const column = this.columns[colIdx]; for (let i = 0; i < column.renderedWindowsRects.length; i++) { const renderedRect = column.renderedWindowsRects[i]; if ((this.direction.west && renderedRect.includesPoint(cursorOrActivationPoint, 0)) || (this.direction.north && renderedRect.includesPoint(cursorOrActivationPoint, 2)) || (this.direction.east && renderedRect.includesPoint(cursorOrActivationPoint, 0)) || (this.direction.south && renderedRect.includesPoint(cursorOrActivationPoint, 2))) { if (column.renderedWindowsIds[i] === windowId) return false; if (i > 0 && column.renderedWindowsIds[i - 1] === windowId) return false; const renderedId = column.renderedWindowsIds[i]; if (columnId !== null && columnId !== colIdx) this.columns[columnId].windowIds.delete(windowId); column.windowIds.add(windowId); ctx.moveWindowByWinId(window, renderedId); return true; } if ((this.direction.west && renderedRect.includesPoint(cursorOrActivationPoint, 1)) || (this.direction.north && renderedRect.includesPoint(cursorOrActivationPoint, 3)) || (this.direction.east && renderedRect.includesPoint(cursorOrActivationPoint, 1)) || (this.direction.south && renderedRect.includesPoint(cursorOrActivationPoint, 3))) { if (column.renderedWindowsIds[i] === windowId) return false; if (i < column.renderedWindowsIds.length - 1 && column.renderedWindowsIds[i + 1] === windowId) return false; const renderedId = column.renderedWindowsIds[i]; if (columnId !== null && columnId !== colIdx) this.columns[columnId].windowIds.delete(windowId); column.windowIds.add(windowId); ctx.moveWindowByWinId(window, renderedId, true); return true; } } } return false; } arrangeTileables(ctx, tileables) { let latestTimestamp = 0; let partId = null; let newWindows = []; let tileableIds = new Set(); let currentColumnId = 0; tileables.forEach((tileable) => { tileable.state = WindowState.Tiled; partId = this.getPartsId(tileable); if (partId !== null) { if (this.parts[partId].timestamp < tileable.timestamp) { this.parts[partId].timestamp = tileable.timestamp; } if (this.parts[partId].timestamp > latestTimestamp) { latestTimestamp = tileable.timestamp; currentColumnId = partId; } } else { newWindows.push(tileable.id); } tileableIds.add(tileable.id); }); if (this.columnsConfiguration !== null && tileableIds.size > 0 && newWindows.length > 0 && this.columnsConfiguration.length > this.columns.length) { let new_columns_length = this.columnsConfiguration.length - this.columns.length > newWindows.length ? newWindows.length : this.columnsConfiguration.length - this.columns.length; for (let i = 0; i < new_columns_length; i++) { let winId = newWindows.shift(); if (winId === undefined) continue; let column = this.insertColumn(false); column.windowIds.add(winId); } this.applyColumnsPosition(); if (this.columnsConfiguration[0] !== 0) { let sumWeights = this.columnsConfiguration.reduce((a, b) => a + b, 0); for (let i = 0; i < this.columns.length; i++) { this.columns[i].weight = (this.columnsConfiguration[i] / sumWeights) * this.columns.length; } } } if (CONFIG.columnsBalanced) { for (var [_, id] of newWindows.entries()) { let minSizeColumn = this.parts.reduce((prev, curr) => { return prev.size < curr.size ? prev : curr; }); minSizeColumn.windowIds.add(id); } } else { this.parts[currentColumnId].windowIds = new Set([ ...this.parts[currentColumnId].windowIds, ...newWindows, ]); } this.parts.forEach((column) => { column.actualizeWindowIds(ctx, tileableIds); }); this.parts = this.parts.filter((column) => column.windowIds.size !== 0); if (this.parts.length === 0) this.insertColumn(true); this.applyColumnsPosition(); } getColumnId(t) { for (var i = 0; i < this.columns.length; i++) { if (this.columns[i].windowIds.has(t.id)) return i; } return null; } getPartsId(t) { for (var i = 0; i < this.parts.length; i++) { if (this.parts[i].windowIds.has(t.id)) return i; } return null; } getCurrentColumnId(currentWindowId) { if (currentWindowId !== null) { for (const [i, column] of this.columns.entries()) { if (column.windowIds.has(currentWindowId)) return i; } } return null; } applyColumnsPosition() { this._columns = this.parts.filter((column) => !column.isEmpty()); const columnsLength = this.columns.length; if (columnsLength === 1) { this.columns[0].position = "single"; } else if (columnsLength > 1) { this.columns[0].position = "left"; this.columns[columnsLength - 1].position = "right"; for (let i = 1; i < columnsLength - 1; i++) { this.columns[i].position = "middle"; } } } toColumnWithBiggerIndex(ctx) { const currentWindow = ctx.currentWindow; const currentWindowId = currentWindow !== null ? currentWindow.id : null; const activeColumnId = this.getCurrentColumnId(currentWindowId); if (currentWindow === null || currentWindowId === null || activeColumnId === null || (this.columns[activeColumnId].size < 2 && (this.columns[activeColumnId].position === "right" || this.columns[activeColumnId].position === "single"))) return false; let targetColumn; const column = this.columns[activeColumnId]; const center = column.renderedWindowsRects[column.renderedWindowsIds.indexOf(currentWindowId)].center; column.windowIds.delete(currentWindowId); if (column.position === "single" || column.position === "right") { targetColumn = this.insertColumn(false); targetColumn.windowIds.add(currentWindowId); } else { targetColumn = this.columns[activeColumnId + 1]; targetColumn.windowIds.add(currentWindowId); } let idOnTarget; if (this.direction.north || this.direction.south) idOnTarget = targetColumn.getWindowIdOnRight(center[0]); else idOnTarget = targetColumn.getWindowIdOnTop(center[1]); if (idOnTarget !== null) ctx.moveWindowByWinId(currentWindow, idOnTarget); else { const targetId = targetColumn.renderedWindowsIds[targetColumn.renderedWindowsIds.length - 1]; ctx.moveWindowByWinId(currentWindow, targetId); } this.applyColumnsPosition(); return true; } toColumnWithSmallerIndex(ctx) { const currentWindow = ctx.currentWindow; const currentWindowId = currentWindow !== null ? currentWindow.id : null; const activeColumnId = this.getCurrentColumnId(currentWindowId); if (currentWindow === null || currentWindowId === null || activeColumnId === null || (this.columns[activeColumnId].windowIds.size < 2 && (this.columns[activeColumnId].position === "left" || this.columns[activeColumnId].position === "single"))) return false; let targetColumn; const column = this.columns[activeColumnId]; const center = column.renderedWindowsRects[column.renderedWindowsIds.indexOf(currentWindowId)].center; column.windowIds.delete(currentWindowId); if (column.position === "single" || column.position === "left") { targetColumn = this.insertColumn(true); targetColumn.windowIds.add(currentWindowId); } else { targetColumn = this.columns[activeColumnId - 1]; targetColumn.windowIds.add(currentWindowId); } let idOnTarget; if (this.direction.north || this.direction.south) idOnTarget = targetColumn.getWindowIdOnRight(center[0]); else idOnTarget = targetColumn.getWindowIdOnTop(center[1]); if (idOnTarget !== null) ctx.moveWindowByWinId(currentWindow, idOnTarget); else { const targetId = targetColumn.renderedWindowsIds[targetColumn.renderedWindowsIds.length - 1]; ctx.moveWindowByWinId(currentWindow, targetId); } this.applyColumnsPosition(); return true; } toUpOrLeft(ctx) { let currentWindow = ctx.currentWindow; let currentWindowId = currentWindow !== null ? currentWindow.id : null; let activeColumnId = this.getCurrentColumnId(currentWindowId); if (currentWindow === null || currentWindowId === null || activeColumnId === null || this.columns[activeColumnId].windowIds.size < 2) return false; let upperWinId = this.columns[activeColumnId].getUpperWindowId(currentWindowId); if (upperWinId === null) return false; ctx.moveWindowByWinId(currentWindow, upperWinId); return true; } toBottomOrRight(ctx) { let currentWindow = ctx.currentWindow; let currentWindowId = currentWindow !== null ? currentWindow.id : null; let activeColumnId = this.getCurrentColumnId(currentWindowId); if (currentWindow === null || currentWindowId === null || activeColumnId === null || this.columns[activeColumnId].windowIds.size < 2) return false; let lowerWinId = this.columns[activeColumnId].getLowerWindowId(currentWindowId); if (lowerWinId === null) return false; ctx.moveWindowByWinId(currentWindow, lowerWinId, true); return true; } showDirection(ctx) { let notification; if (this.direction.east) notification = "vertical ⟰"; else if (this.direction.north) notification = "horizontal ⭆"; else if (this.direction.west) notification = "vertical ⟱"; else if (this.direction.south) notification = "horizontal ⭅"; else notification = ""; ctx.showNotification(notification); } handleShortcut(ctx, input) { let isApply = false; switch (input) { case Shortcut.SwapLeft: if (this.direction.north || this.direction.south) { isApply = this.toUpOrLeft(ctx); } else if (this.direction.east) { isApply = this.toColumnWithBiggerIndex(ctx); } else isApply = this.toColumnWithSmallerIndex(ctx); break; case Shortcut.SwapRight: if (this.direction.north || this.direction.south) { isApply = this.toBottomOrRight(ctx); } else if (this.direction.east) { isApply = this.toColumnWithSmallerIndex(ctx); } else isApply = this.toColumnWithBiggerIndex(ctx); break; case Shortcut.SwapUp: if (this.direction.north) { isApply = this.toColumnWithSmallerIndex(ctx); } else if (this.direction.south) { isApply = this.toColumnWithBiggerIndex(ctx); } else isApply = this.toUpOrLeft(ctx); break; case Shortcut.SwapDown: if (this.direction.north) { isApply = this.toColumnWithBiggerIndex(ctx); } else if (this.direction.south) { isApply = this.toColumnWithSmallerIndex(ctx); } else isApply = this.toBottomOrRight(ctx); break; case Shortcut.Rotate: this.direction.cwRotation(); this.showDirection(ctx); isApply = true; break; case Shortcut.RotatePart: this.direction.ccwRotation(); this.showDirection(ctx); isApply = true; break; default: return false; } return isApply; } insertColumn(onTop) { let column = new ColumnLayout(); this.parts.splice(onTop ? 0 : this.parts.length, 0, column); return column; } getDefaultConfig(ctx) { let returnValue = []; let [outputName, activityId, vDesktopName] = ctx.surfaceParams; for (let conf of CONFIG.columnsLayerConf) { if (!conf || typeof conf !== "string") continue; let conf_arr = conf.split(":").map((part) => part.trim()); if (conf_arr.length < 5) { warning(`Columns conf: ${conf} has less then 5 elements`); continue; } if ((outputName === conf_arr[0] || conf_arr[0] === "") && (activityId === conf_arr[1] || conf_arr[1] === "") && (vDesktopName === conf_arr[2] || conf_arr[2] === "")) { for (let i = 3; i < conf_arr.length; i++) { let columnWeight = parseFloat(conf_arr[i]); if (isNaN(columnWeight)) { warning(`Columns conf:${conf_arr}: ${conf_arr[i]} is not a number.`); returnValue = []; break; } if (columnWeight === 0) { warning(`Columns conf:${conf_arr}: weight cannot be zero`); returnValue = []; break; } returnValue.push(columnWeight); } if (returnValue.length > 1 && returnValue.every((el) => el === returnValue[0])) { returnValue.fill(0); } return returnValue; } } return returnValue; } } ColumnsLayout.id = "Columns"; class FloatingLayout { constructor() { this.classID = FloatingLayout.id; this.description = "Floating"; } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.TiledAfloat)); } clone() { return this; } toString() { return "FloatingLayout()"; } } FloatingLayout.id = "FloatingLayout "; FloatingLayout.instance = new FloatingLayout(); class FillLayoutPart { adjust(area, tiles, basis, delta, gap) { return delta; } apply(area, tiles, gap) { return tiles.map((tile) => { return area; }); } toString() { return `FillLayoutPart`; } } class HalfSplitLayoutPart { get horizontal() { return this.angle === 0 || this.angle === 180; } get reversed() { return this.angle === 180 || this.angle === 270; } constructor(primary, secondary) { this.primary = primary; this.secondary = secondary; this.angle = 0; this.primarySize = 1; this.ratio = 0.5; } adjust(area, tiles, basis, delta, gap) { const basisIndex = tiles.indexOf(basis); if (basisIndex < 0) return delta; if (tiles.length <= this.primarySize) { return this.primary.adjust(area, tiles, basis, delta, gap); } else if (this.primarySize === 0) { return this.secondary.adjust(area, tiles, basis, delta, gap); } else { const targetIndex = basisIndex < this.primarySize ? 0 : 1; if (targetIndex === 0) { delta = this.primary.adjust(area, tiles.slice(0, this.primarySize), basis, delta, gap); } else { delta = this.secondary.adjust(area, tiles.slice(this.primarySize), basis, delta, gap); } this.ratio = LayoutUtils.adjustAreaHalfWeights(area, this.reversed ? 1 - this.ratio : this.ratio, gap, this.reversed ? 1 - targetIndex : targetIndex, delta, this.horizontal); if (this.reversed) this.ratio = 1 - this.ratio; switch (this.angle * 10 + targetIndex + 1) { case 1: case 1802: return new RectDelta(0, delta.west, delta.south, delta.north); case 2: case 1801: return new RectDelta(delta.east, 0, delta.south, delta.north); case 901: case 2702: return new RectDelta(delta.east, delta.west, 0, delta.north); case 902: case 2701: return new RectDelta(delta.east, delta.west, delta.south, 0); } return delta; } } toString() { return `>>`; } apply(area, tiles, gap) { if (tiles.length <= this.primarySize) { return this.primary.apply(area, tiles, gap); } else if (this.primarySize === 0) { return this.secondary.apply(area, tiles, gap); } else { const reversed = this.reversed; const ratio = reversed ? 1 - this.ratio : this.ratio; const [area1, area2] = LayoutUtils.splitAreaHalfWeighted(area, ratio, gap, this.horizontal); const result1 = this.primary.apply(reversed ? area2 : area1, tiles.slice(0, this.primarySize), gap); const result2 = this.secondary.apply(reversed ? area1 : area2, tiles.slice(this.primarySize), gap); return result1.concat(result2); } } } class StackLayoutPart { adjust(area, tiles, basis, delta, gap) { const weights = LayoutUtils.adjustAreaWeights(area, tiles.map((tile) => tile.weight), gap, tiles.indexOf(basis), delta, false); weights.forEach((weight, i) => { tiles[i].weight = weight * tiles.length; }); const idx = tiles.indexOf(basis); return new RectDelta(delta.east, delta.west, idx === tiles.length - 1 ? delta.south : 0, idx === 0 ? delta.north : 0); } apply(area, tiles, gap) { const weights = tiles.map((tile) => tile.weight); return LayoutUtils.splitAreaWeighted(area, weights, gap); } } class RotateLayoutPart { constructor(inner, angle = 0) { this.inner = inner; this.angle = angle; } adjust(area, tiles, basis, delta, gap) { switch (this.angle) { case 0: break; case 90: area = new Rect(area.y, area.x, area.height, area.width); delta = new RectDelta(delta.south, delta.north, delta.east, delta.west); break; case 180: delta = new RectDelta(delta.west, delta.east, delta.south, delta.north); break; case 270: area = new Rect(area.y, area.x, area.height, area.width); delta = new RectDelta(delta.north, delta.south, delta.east, delta.west); break; } delta = this.inner.adjust(area, tiles, basis, delta, gap); switch (this.angle) { case 0: delta = delta; break; case 90: delta = new RectDelta(delta.south, delta.north, delta.east, delta.west); break; case 180: delta = new RectDelta(delta.west, delta.east, delta.south, delta.north); break; case 270: delta = new RectDelta(delta.north, delta.south, delta.east, delta.west); break; } return delta; } apply(area, tiles, gap) { switch (this.angle) { case 0: break; case 90: area = new Rect(area.y, area.x, area.height, area.width); break; case 180: break; case 270: area = new Rect(area.y, area.x, area.height, area.width); break; } const innerResult = this.inner.apply(area, tiles, gap); switch (this.angle) { case 0: return innerResult; case 90: return innerResult.map((g) => new Rect(g.y, g.x, g.height, g.width)); case 180: return innerResult.map((g) => { const rx = g.x - area.x; const newX = area.x + area.width - (rx + g.width); return new Rect(newX, g.y, g.width, g.height); }); case 270: return innerResult.map((g) => { const rx = g.x - area.x; const newY = area.x + area.width - (rx + g.width); return new Rect(g.y, newY, g.height, g.width); }); } } rotate(amount) { let angle = this.angle + amount; if (angle < 0) angle = 270; else if (angle >= 360) angle = 0; this.angle = angle; } } class LayoutUtils { static splitWeighted([begin, length], weights, gap) { gap = gap !== undefined ? gap : 0; const n = weights.length; const actualLength = length - (n - 1) * gap; const weightSum = weights.reduce((sum, weight) => sum + weight, 0); let weightAcc = 0; const parts = weights.map((weight, i) => { const partBegin = (actualLength * weightAcc) / weightSum + i * gap; const partLength = (actualLength * weight) / weightSum; weightAcc += weight; return [begin + Math.floor(partBegin), Math.floor(partLength)]; }); let finalLength = parts.reduce((sum, [, length]) => sum + length, 0); finalLength += (n - 1) * gap; let remainder = length - finalLength; if (remainder > 0 && remainder < n) { for (let i = 0; i < remainder; i++) { parts[n - i - 1][1] += 1; } } return parts; } static splitAreaWeighted(area, weights, gap, horizontal) { gap = gap !== undefined ? gap : 0; horizontal = horizontal !== undefined ? horizontal : false; const line = horizontal ? [area.x, area.width] : [area.y, area.height]; const parts = LayoutUtils.splitWeighted(line, weights, gap); return parts.map(([begin, length]) => horizontal ? new Rect(begin, area.y, length, area.height) : new Rect(area.x, begin, area.width, length)); } static splitAreaHalfWeighted(area, weight, gap, horizontal) { return LayoutUtils.splitAreaWeighted(area, [weight, 1 - weight], gap, horizontal); } static adjustWeights([begin, length], weights, gap, target, deltaFw, deltaBw) { const minLength = 1; const parts = this.splitWeighted([begin, length], weights, gap); const [targetBase, targetLength] = parts[target]; if (target > 0 && deltaBw !== 0) { const neighbor = target - 1; const [neighborBase, neighborLength] = parts[neighbor]; const delta = clip(deltaBw, minLength - targetLength, neighborLength - minLength); parts[target] = [targetBase - delta, targetLength + delta]; parts[neighbor] = [neighborBase, neighborLength - delta]; } if (target < parts.length - 1 && deltaFw !== 0) { const neighbor = target + 1; const [neighborBase, neighborLength] = parts[neighbor]; const delta = clip(deltaFw, minLength - targetLength, neighborLength - minLength); parts[target] = [targetBase, targetLength + delta]; parts[neighbor] = [neighborBase + delta, neighborLength - delta]; } return LayoutUtils.calculateWeights(parts); } static adjustAreaWeights(area, weights, gap, target, delta, horizontal) { const line = horizontal ? [area.x, area.width] : [area.y, area.height]; const [deltaFw, deltaBw] = horizontal ? [delta.east, delta.west] : [delta.south, delta.north]; return LayoutUtils.adjustWeights(line, weights, gap, target, deltaFw, deltaBw); } static adjustAreaHalfWeights(area, weight, gap, target, delta, horizontal) { const weights = [weight, 1 - weight]; const newWeights = LayoutUtils.adjustAreaWeights(area, weights, gap, target, delta, horizontal); return newWeights[0]; } static calculateWeights(parts) { const totalLength = parts.reduce((acc, [base, length]) => acc + length, 0); return parts.map(([base, length]) => length / totalLength); } static calculateAreaWeights(area, geometries, gap, horizontal) { gap = gap !== undefined ? gap : 0; horizontal = horizontal !== undefined ? horizontal : false; const line = horizontal ? area.width : area.height; const parts = horizontal ? geometries.map((geometry) => [geometry.x, geometry.width]) : geometries.map((geometry) => [geometry.y, geometry.height]); return LayoutUtils.calculateWeights(parts); } } class MonocleLayout { constructor() { this.description = "Monocle"; this.classID = MonocleLayout.id; } apply(ctx, tileables, area, gap) { tileables.forEach((tile) => { tile.state = CONFIG.monocleMaximize ? WindowState.Maximized : WindowState.Tiled; tile.geometry = area; }); if (ctx.backend === KWinDriver.backendName && KWINCONFIG.monocleMinimizeRest) { const tiles = [...tileables]; ctx.setTimeout(() => { const current = ctx.currentWindow; if (current && current.tiled) { tiles.forEach((window) => { if (window !== current) window.window.window.minimized = true; }); } }, 50); } } clone() { return this; } handleShortcut(ctx, input, data) { switch (input) { case Shortcut.DWMLeft: case Shortcut.FocusNext: case Shortcut.FocusUp: case Shortcut.FocusLeft: ctx.cycleFocus(-1); return true; case Shortcut.DWMRight: case Shortcut.FocusPrev: case Shortcut.FocusDown: case Shortcut.FocusRight: ctx.cycleFocus(1); return true; default: return false; } } toString() { return "MonocleLayout()"; } } MonocleLayout.id = "MonocleLayout"; class QuarterLayout { get capacity() { return 4; } constructor() { this.classID = QuarterLayout.id; this.description = "Quarter"; this.lhsplit = 0.5; this.rhsplit = 0.5; this.vsplit = 0.5; this.prevTileCount = 0; } resetSplits() { this.lhsplit = 0.5; this.rhsplit = 0.5; this.vsplit = 0.5; } adjust(area, tiles, basis, delta, gap) { if (tiles.length <= 1 || tiles.length > 4) return; const idx = tiles.indexOf(basis); if (idx < 0) return; if ((idx === 0 || idx === 3) && delta.east !== 0) this.vsplit = (area.width * this.vsplit + delta.east) / area.width; else if ((idx === 1 || idx === 2) && delta.west !== 0) this.vsplit = (area.width * this.vsplit - delta.west) / area.width; if (tiles.length === 4) { if (idx === 0 && delta.south !== 0) this.lhsplit = (area.height * this.lhsplit + delta.south) / area.height; if (idx === 3 && delta.north !== 0) this.lhsplit = (area.height * this.lhsplit - delta.north) / area.height; } if (tiles.length >= 3) { if (idx === 1 && delta.south !== 0) this.rhsplit = (area.height * this.rhsplit + delta.south) / area.height; if (idx === 2 && delta.north !== 0) this.rhsplit = (area.height * this.rhsplit - delta.north) / area.height; } this.vsplit = clip(this.vsplit, 1 - QuarterLayout.MAX_PROPORTION, QuarterLayout.MAX_PROPORTION); this.lhsplit = clip(this.lhsplit, 1 - QuarterLayout.MAX_PROPORTION, QuarterLayout.MAX_PROPORTION); this.rhsplit = clip(this.rhsplit, 1 - QuarterLayout.MAX_PROPORTION, QuarterLayout.MAX_PROPORTION); } clone() { const other = new QuarterLayout(); other.lhsplit = this.lhsplit; other.rhsplit = this.rhsplit; other.vsplit = this.vsplit; other.prevTileCount = this.prevTileCount; return other; } apply(ctx, tileables, area, gap) { if (CONFIG.quarterLayoutReset) { if (tileables.length < this.prevTileCount) { this.resetSplits(); } this.prevTileCount = tileables.length; } for (let i = 0; i < 4 && i < tileables.length; i++) tileables[i].state = WindowState.Tiled; if (tileables.length > 4) tileables .slice(4) .forEach((tile) => (tile.state = WindowState.TiledAfloat)); if (tileables.length === 1) { tileables[0].geometry = area; return; } const gap1 = gap / 2; const gap2 = gap - gap1; const leftWidth = area.width * this.vsplit; const rightWidth = area.width - leftWidth; const rightX = area.x + leftWidth; if (tileables.length === 2) { tileables[0].geometry = new Rect(area.x, area.y, leftWidth, area.height).gap(0, gap1, 0, 0); tileables[1].geometry = new Rect(rightX, area.y, rightWidth, area.height).gap(gap2, 0, 0, 0); return; } const rightTopHeight = area.height * this.rhsplit; const rightBottomHeight = area.height - rightTopHeight; const rightBottomY = area.y + rightTopHeight; if (tileables.length === 3) { tileables[0].geometry = new Rect(area.x, area.y, leftWidth, area.height).gap(0, gap1, 0, 0); tileables[1].geometry = new Rect(rightX, area.y, rightWidth, rightTopHeight).gap(gap2, 0, 0, gap1); tileables[2].geometry = new Rect(rightX, rightBottomY, rightWidth, rightBottomHeight).gap(gap2, 0, gap2, 0); return; } const leftTopHeight = area.height * this.lhsplit; const leftBottomHeight = area.height - leftTopHeight; const leftBottomY = area.y + leftTopHeight; if (tileables.length >= 4) { tileables[0].geometry = new Rect(area.x, area.y, leftWidth, leftTopHeight).gap(0, gap1, 0, gap1); tileables[1].geometry = new Rect(rightX, area.y, rightWidth, rightTopHeight).gap(gap2, 0, 0, gap1); tileables[2].geometry = new Rect(rightX, rightBottomY, rightWidth, rightBottomHeight).gap(gap2, 0, gap2, 0); tileables[3].geometry = new Rect(area.x, leftBottomY, leftWidth, leftBottomHeight).gap(0, gap2, gap2, 0); } } toString() { return "QuarterLayout()"; } } QuarterLayout.MAX_PROPORTION = 0.8; QuarterLayout.id = "QuarterLayout"; class SpiralLayout { constructor() { this.description = "Spiral"; this.classID = SpiralLayout.id; this.depth = 1; this.parts = new HalfSplitLayoutPart(new FillLayoutPart(), new FillLayoutPart()); this.parts.angle = 0; } adjust(area, tiles, basis, delta, gap) { this.parts.adjust(area, tiles, basis, delta, gap); } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); this.bore(tileables.length); this.parts.apply(area, tileables, gap).forEach((geometry, i) => { tileables[i].geometry = geometry; }); } toString() { return "Spiral()"; } bore(depth) { if (this.depth >= depth) return; let hpart = this.parts; let i; for (i = 0; i < this.depth - 1; i++) { hpart = hpart.secondary; } const lastFillPart = hpart.secondary; let npart; while (i < depth - 1) { npart = new HalfSplitLayoutPart(new FillLayoutPart(), lastFillPart); npart.angle = (((i + 1) % 4) * 90); hpart.secondary = npart; hpart = npart; i++; } this.depth = depth; } } SpiralLayout.id = "SpiralLayout"; class SpreadLayout { constructor() { this.classID = SpreadLayout.id; this.description = "Spread"; this.space = 0.07; } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); const tiles = tileables; let numTiles = tiles.length; const spaceWidth = Math.floor(area.width * this.space); let cardWidth = area.width - spaceWidth * (numTiles - 1); const miniumCardWidth = area.width * 0.4; while (cardWidth < miniumCardWidth) { cardWidth += spaceWidth; numTiles -= 1; } for (let i = 0; i < tiles.length; i++) tiles[i].geometry = new Rect(area.x + (i < numTiles ? spaceWidth * (numTiles - i - 1) : 0), area.y, cardWidth, area.height); } clone() { const other = new SpreadLayout(); other.space = this.space; return other; } handleShortcut(ctx, input) { switch (input) { case Shortcut.Decrease: this.space = Math.max(0.04, this.space - 0.01); break; case Shortcut.Increase: this.space = Math.min(0.1, this.space + 0.01); break; default: return false; } return true; } toString() { return "SpreadLayout(" + this.space + ")"; } } SpreadLayout.id = "SpreadLayout"; class StackedLayout { get description() { return "Stacked"; } constructor() { this.classID = StackedLayout.id; this.parts = new RotateLayoutPart(new HalfSplitLayoutPart(new StackLayoutPart(), new StackLayoutPart())); } adjust(area, tiles, basis, delta, gap) { this.parts.adjust(area, tiles, basis, delta, gap); } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); if (tileables.length > 1) { this.parts.inner.angle = 90; } this.parts.apply(area, tileables, gap).forEach((geometry, i) => { tileables[i].geometry = geometry; }); } clone() { const other = new StackedLayout(); return other; } handleShortcut(ctx, input) { switch (input) { case Shortcut.Rotate: this.parts.rotate(90); break; default: return false; } return true; } toString() { return "StackedLayout()"; } } StackedLayout.id = "StackedLayout"; class StairLayout { constructor() { this.classID = StairLayout.id; this.description = "Stair"; this.space = 24; } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); const tiles = tileables; const len = tiles.length; const space = this.space; const alignRight = Number(!KWINCONFIG.stairReverse); for (let i = 0; i < len; i++) { const dx = space * (len - i - 1); const dy = space * i; tiles[i].geometry = new Rect(area.x + alignRight * dx, area.y + dy, area.width - dx, area.height - dy); } } clone() { const other = new StairLayout(); other.space = this.space; return other; } handleShortcut(ctx, input) { switch (input) { case Shortcut.Decrease: this.space = Math.max(16, this.space - 8); break; case Shortcut.Increase: this.space = Math.min(160, this.space + 8); break; default: return false; } return true; } toString() { return "StairLayout(" + this.space + ")"; } } StairLayout.id = "StairLayout"; class ThreeColumnLayout { get description() { return "Three-Column [" + this.masterSize + "]"; } constructor() { this.classID = ThreeColumnLayout.id; this.masterRatio = 0.6; this.masterSize = 1; } adjust(area, tiles, basis, delta, gap) { const basisIndex = tiles.indexOf(basis); if (basisIndex < 0) return; if (tiles.length === 0) return; else if (tiles.length <= this.masterSize) { LayoutUtils.adjustAreaWeights(area, tiles.map((tile) => tile.weight), gap, tiles.indexOf(basis), delta).forEach((newWeight, i) => (tiles[i].weight = newWeight * tiles.length)); } else if (tiles.length === this.masterSize + 1) { this.masterRatio = LayoutUtils.adjustAreaHalfWeights(area, this.masterRatio, gap, basisIndex < this.masterSize ? 0 : 1, delta, true); if (basisIndex < this.masterSize) { const masterTiles = tiles.slice(0, -1); LayoutUtils.adjustAreaWeights(area, masterTiles.map((tile) => tile.weight), gap, basisIndex, delta).forEach((newWeight, i) => (masterTiles[i].weight = newWeight * masterTiles.length)); } } else if (tiles.length > this.masterSize + 1) { let basisGroup; if (basisIndex < this.masterSize) basisGroup = 1; else if (basisIndex < Math.floor((this.masterSize + tiles.length) / 2)) basisGroup = 2; else basisGroup = 0; const stackRatio = 1 - this.masterRatio; const newRatios = LayoutUtils.adjustAreaWeights(area, [stackRatio, this.masterRatio, stackRatio], gap, basisGroup, delta, true); const newMasterRatio = newRatios[1]; const newStackRatio = basisGroup === 0 ? newRatios[0] : newRatios[2]; this.masterRatio = newMasterRatio / (newMasterRatio + newStackRatio); const rstackNumTile = Math.floor((tiles.length - this.masterSize) / 2); const [masterTiles, rstackTiles, lstackTiles] = partitionArrayBySizes(tiles, [ this.masterSize, rstackNumTile, ]); const groupTiles = [lstackTiles, masterTiles, rstackTiles][basisGroup]; LayoutUtils.adjustAreaWeights(area, groupTiles.map((tile) => tile.weight), gap, groupTiles.indexOf(basis), delta).forEach((newWeight, i) => (groupTiles[i].weight = newWeight * groupTiles.length)); } } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); const tiles = tileables; if (tiles.length <= this.masterSize) { LayoutUtils.splitAreaWeighted(area, tiles.map((tile) => tile.weight), gap).forEach((tileArea, i) => (tiles[i].geometry = tileArea)); } else if (tiles.length === this.masterSize + 1) { const [masterArea, stackArea] = LayoutUtils.splitAreaHalfWeighted(area, this.masterRatio, gap, true); const masterTiles = tiles.slice(0, this.masterSize); LayoutUtils.splitAreaWeighted(masterArea, masterTiles.map((tile) => tile.weight), gap).forEach((tileArea, i) => (masterTiles[i].geometry = tileArea)); tiles[tiles.length - 1].geometry = stackArea; } else if (tiles.length > this.masterSize + 1) { const stackRatio = 1 - this.masterRatio; const groupAreas = LayoutUtils.splitAreaWeighted(area, [stackRatio, this.masterRatio, stackRatio], gap, true); const rstackSize = Math.floor((tiles.length - this.masterSize) / 2); const [masterTiles, rstackTiles, lstackTiles] = partitionArrayBySizes(tiles, [ this.masterSize, rstackSize, ]); [lstackTiles, masterTiles, rstackTiles].forEach((groupTiles, group) => { LayoutUtils.splitAreaWeighted(groupAreas[group], groupTiles.map((tile) => tile.weight), gap).forEach((tileArea, i) => (groupTiles[i].geometry = tileArea)); }); } } clone() { const other = new ThreeColumnLayout(); other.masterRatio = this.masterRatio; other.masterSize = this.masterSize; return other; } handleShortcut(ctx, input, data) { switch (input) { case Shortcut.Increase: this.resizeMaster(ctx, +1); return true; case Shortcut.Decrease: this.resizeMaster(ctx, -1); return true; case Shortcut.DWMLeft: this.masterRatio = clip(slide(this.masterRatio, -0.05), ThreeColumnLayout.MIN_MASTER_RATIO, ThreeColumnLayout.MAX_MASTER_RATIO); return true; case Shortcut.DWMRight: this.masterRatio = clip(slide(this.masterRatio, +0.05), ThreeColumnLayout.MIN_MASTER_RATIO, ThreeColumnLayout.MAX_MASTER_RATIO); return true; default: return false; } } toString() { return "ThreeColumnLayout(nmaster=" + this.masterSize + ")"; } resizeMaster(ctx, step) { this.masterSize = clip(this.masterSize + step, 1, 10); ctx.showNotification(this.description); } } ThreeColumnLayout.MIN_MASTER_RATIO = 0.2; ThreeColumnLayout.MAX_MASTER_RATIO = 0.75; ThreeColumnLayout.id = "ThreeColumnLayout"; class TileLayout { get description() { return "Tile [" + this.numMaster + "]"; } get numMaster() { return this.parts.inner.primarySize; } set numMaster(value) { this.parts.inner.primarySize = value; } get masterRatio() { return this.parts.inner.ratio; } set masterRatio(value) { this.parts.inner.ratio = value; } constructor() { this.classID = TileLayout.id; this.parts = new RotateLayoutPart(new HalfSplitLayoutPart(new RotateLayoutPart(new StackLayoutPart()), new StackLayoutPart())); switch (CONFIG.tileLayoutInitialAngle) { case "1": { this.parts.angle = 90; break; } case "2": { this.parts.angle = 180; break; } case "3": { this.parts.angle = 270; break; } } } adjust(area, tiles, basis, delta, gap) { this.parts.adjust(area, tiles, basis, delta, gap); } apply(ctx, tileables, area, gap) { tileables.forEach((tileable) => (tileable.state = WindowState.Tiled)); this.parts.apply(area, tileables, gap).forEach((geometry, i) => { tileables[i].geometry = geometry; }); } clone() { const other = new TileLayout(); other.masterRatio = this.masterRatio; other.numMaster = this.numMaster; return other; } handleShortcut(ctx, input) { switch (input) { case Shortcut.DWMLeft: this.masterRatio = clip(slide(this.masterRatio, -0.05), TileLayout.MIN_MASTER_RATIO, TileLayout.MAX_MASTER_RATIO); break; case Shortcut.DWMRight: this.masterRatio = clip(slide(this.masterRatio, +0.05), TileLayout.MIN_MASTER_RATIO, TileLayout.MAX_MASTER_RATIO); break; case Shortcut.Increase: if (this.numMaster < 10) this.numMaster += 1; ctx.showNotification(this.description); break; case Shortcut.Decrease: if (this.numMaster > 0) this.numMaster -= 1; ctx.showNotification(this.description); break; case Shortcut.Rotate: this.parts.rotate(90); break; case Shortcut.RotatePart: this.parts.inner.primary.rotate(90); break; default: return false; } return true; } toString() { return ("TileLayout(nmaster=" + this.numMaster + ", ratio=" + this.masterRatio + ")"); } } TileLayout.MIN_MASTER_RATIO = 0.2; TileLayout.MAX_MASTER_RATIO = 0.8; TileLayout.id = "TileLayout"; class Err { constructor(s) { this.error = s; } toString() { return `${this.error}`; } } function warning(s) { print(`Krohnkite.WARNING: ${s}`); } function clip(value, min, max) { if (value < min) return min; if (value > max) return max; return value; } function slide(value, step) { if (step === 0) return value; return Math.floor(value / step + 1.000001) * step; } function matchWords(str, words) { for (let i = 0; i < words.length; i++) { if (str.indexOf(words[i]) >= 0) return i; } return -1; } function wrapIndex(index, length) { if (index < 0) return index + length; if (index >= length) return index - length; return index; } function partitionArray(array, predicate) { return array.reduce((parts, item, index) => { parts[predicate(item, index) ? 0 : 1].push(item); return parts; }, [[], []]); } function partitionArrayBySizes(array, sizes) { let base = 0; const chunks = sizes.map((size) => { const chunk = array.slice(base, base + size); base += size; return chunk; }); chunks.push(array.slice(base)); return chunks; } function overlap(min1, max1, min2, max2) { const min = Math.min; const max = Math.max; const dx = max(0, min(max1, max2) - max(min1, min2)); return dx > 0; } function surfaceIdParse(id) { let i1 = id.indexOf("@"); let i2 = id.indexOf("#"); let outputName = i1 !== -1 ? id.slice(0, i1) : id; let activity = i1 !== -1 && i2 !== -1 ? id.slice(i1 + 1, i2) : ""; let desktopName = i2 !== -1 ? id.slice(i2 + 1) : ""; return [outputName, activity, desktopName]; } function toQRect(rect) { return Qt.rect(rect.x, rect.y, rect.width, rect.height); } function toRect(qrect) { return new Rect(qrect.x, qrect.y, qrect.width, qrect.height); } class Logging { constructor(modules, filters) { this._isIncludeMode = true; this._logModules = Logging.parseModules(modules); this._filters = this.parseFilters(filters); this._started = new Date().getTime(); } send(module, action, message, filter) { if (module !== undefined && !this._logModules.has(module)) return; if (filter !== undefined) { if (this.isFiltered(filter)) return; } this._print(module, action, message); } isFiltered(filter) { if (this._filters === null) return false; let key; for (key in filter) { if (this._filters[key] == null || filter[key] == null) continue; const isContain = KWinWindow.isContain(this._filters[key], filter[key][0]); if (this._isIncludeMode) { return isContain ? false : true; } else { return isContain ? true : false; } } return false; } static parseModules(modules) { let logModules = new Set(); for (const module of modules) { const userModules = Logging._logParseUserModules(module[0], module[1]); if (userModules !== null) { userModules.forEach((el) => { }); logModules = new Set([...logModules, ...userModules]); } } return logModules; } parseFilters(filters) { if (filters.length === 0 || (filters.length === 1 && filters[0] === "")) return null; let logFilters; if (filters[0] !== "" && filters[0][0] === "!") { this._isIncludeMode = false; filters[0] = filters[0].slice(1); } logFilters = { winClass: null }; for (const filter of filters) { const filterParts = filter.split("="); if (filterParts.length !== 2) { warning(`Invalid Log filter: ${filter}.Every filter have contain "=" equal sign`); continue; } if (filterParts[0].toLowerCase() === "winclass") { logFilters.winClass = filterParts[1].split(":"); continue; } warning(`Unknown Log filter name:${filterParts[0]} in filter ${filter}.`); continue; } return logFilters; } static _getLogModulesStr(module) { return LogModulesKeys[module - 1]; } _print(module, action, message) { const timestamp = (new Date().getTime() - this._started) / 1000; print(`Krohnkite.log [${timestamp}], ${module !== undefined ? `[${Logging._getLogModulesStr(module)}]` : ""} ${action !== undefined ? action : ""} ${message !== undefined ? message : ""}`); } static _logParseUserModules(logPartition, userStr) { let submodules; let includeMode = true; if (userStr.length === 0) { return new Set(logPartition.modules); } if (userStr[0] !== "" && userStr[0][0] === "!") { includeMode = false; userStr[0] = userStr[0].substring(1); submodules = new Set(logPartition.modules); } else { submodules = new Set(); } for (let moduleStr of userStr) { if (moduleStr.includes("-")) { const range = moduleStr.split("-"); if (range.length !== 2) { warning(`Invalid module range:${range} in ${moduleStr}, ignoring module ${logPartition.name} `); return null; } const start = validateNumber(range[0]); let end; if (range[1] === "") { end = logPartition.modules.length; } else { end = validateNumber(range[1]); } if (start instanceof Err || end instanceof Err) { let err = start instanceof Err ? start : end; warning(`Invalid module number: ${err} in ${moduleStr}, ignoring module ${logPartition.name}`); return null; } if (start > end || start < 1) { warning(`Invalid module range:${range}. The start must be less than end and both must be greater than zero. Module string: ${moduleStr}, ignoring module ${logPartition.name} `); return null; } if (end > logPartition.modules.length) { warning(`Invalid module range:${range}. The end must be less than or equal to the number of submodules:${logPartition.modules.length} in the module. Module string: ${moduleStr}, ignoring module ${logPartition.name} `); return null; } if (includeMode) { for (let i = start - 1; i < end; i++) { submodules.add(logPartition.modules[i]); } } else { for (let i = start - 1; i < end; i++) { submodules.delete(logPartition.modules[i]); } } } else { let moduleNumber = validateNumber(moduleStr); if (moduleNumber instanceof Err) { warning(`Invalid module number:${moduleNumber}. The module number must be a number. Module string: ${moduleStr}, ignoring module ${logPartition.name} `); return null; } if (moduleNumber < 1 || moduleNumber > logPartition.modules.length) { warning(`Invalid module number:${moduleNumber}. The module number must be >=1 and <= number of submodules:${logPartition.modules.length}. Module string: ${moduleStr}, ignoring module ${logPartition.name} `); return null; } if (includeMode) { submodules.add(logPartition.modules[moduleNumber - 1]); } else { submodules.delete(logPartition.modules[moduleNumber - 1]); } } } return submodules; } } class Rect { constructor(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; } get maxX() { return this.x + this.width; } get maxY() { return this.y + this.height; } get center() { return [ this.x + Math.floor(this.width / 2), this.y + Math.floor(this.height / 2), ]; } get activationPoint() { return [this.x + Math.floor(this.width / 2), this.y + 10]; } clone() { return new Rect(this.x, this.y, this.width, this.height); } equals(other) { return (this.x === other.x && this.y === other.y && this.width === other.width && this.height === other.height); } gap(left, right, top, bottom) { return new Rect(this.x + left, this.y + top, this.width - (left + right), this.height - (top + bottom)); } gap_mut(left, right, top, bottom) { this.x += left; this.y += top; this.width -= left + right; this.height -= top + bottom; return this; } includes(other) { return (this.x <= other.x && this.y <= other.y && other.maxX < this.maxX && other.maxY < this.maxY); } includesPoint([x, y], part = 4) { if (part === 0) return (this.x <= x && x <= this.maxX && this.y <= y && y <= this.y + this.height / 2); else if (part === 1) return (this.x <= x && x <= this.maxX && y > this.y + this.height / 2 && y <= this.maxY); else if (part === 2) { return (this.y <= y && y <= this.maxY && this.x <= x && x <= this.x + this.height / 2); } else if (part === 3) { return (this.y <= y && y <= this.maxY && x > this.x + this.width / 2 && x <= this.maxX); } else { return this.x <= x && x <= this.maxX && this.y <= y && y <= this.maxY; } } isTopZone([x, y], activeZone = 10) { return (this.y <= y && y <= this.y + (this.height * activeZone) / 100 && this.x <= x && x <= this.maxX); } isBottomZone([x, y], activeZone = 10) { return (y >= this.maxY - (this.height * activeZone) / 100 && y <= this.maxY && this.x <= x && x <= this.maxX); } isLeftZone([x, y], activeZone = 10) { return (this.x <= x && x <= this.x + (this.width * activeZone) / 100 && this.y <= y && y <= this.maxY); } isRightZone([x, y], activeZone = 10) { return (x >= this.maxX - (this.width * activeZone) / 100 && x <= this.maxX && this.y <= y && y <= this.maxY); } subtract(other) { return new Rect(this.x - other.x, this.y - other.y, this.width - other.width, this.height - other.height); } toString() { return "Rect(" + [this.x, this.y, this.width, this.height].join(", ") + ")"; } } class RectDelta { static fromRects(basis, target) { const diff = target.subtract(basis); return new RectDelta(diff.width + diff.x, -diff.x, diff.height + diff.y, -diff.y); } constructor(east, west, south, north) { this.east = east; this.west = west; this.south = south; this.north = north; } toString() { return ("WindowResizeDelta(" + [ "east=" + this.east, "west=" + this.west, "north=" + this.north, "south=" + this.south, ].join(" ") + ")"); } } function isNumeric(s) { if (typeof s != "string") return false; return !isNaN(s) && !isNaN(parseFloat(s)); } function parseNumber(value, float = false) { if (!isNumeric(value)) { return new Err("Invalid number"); } if (float) { return parseFloat(value); } else { return parseInt(value); } } function validateNumber(value, from, to, float = false) { let num; if (typeof value === "number") { num = value; } else { num = parseNumber(value, float); if (num instanceof Err) { return num; } } if (from !== undefined && num < from) { return new Err(`Number must be greater than or equal to ${from}`); } else if (to !== undefined && num > to) { return new Err(`Number must be less than or equal to ${to}`); } return num; } class windRose { constructor(direction) { switch (direction) { case "0": this.direction = 0; break; case "1": this.direction = 1; break; case "2": this.direction = 2; break; case "3": this.direction = 3; break; default: this.direction = 0; } } get north() { return this.direction === 0; } get east() { return this.direction === 1; } get south() { return this.direction === 2; } get west() { return this.direction === 3; } cwRotation() { this.direction = (this.direction + 1) % 4; } ccwRotation() { this.direction = this.direction - 1 >= 0 ? this.direction - 1 : 3; } toString() { switch (this.direction) { case 0: { return "North"; } case 1: { return "East"; } case 2: { return "South"; } case 3: { return "West"; } default: { return "Unknown"; } } } } class WrapperMap { constructor(hasher, wrapper) { this.hasher = hasher; this.wrapper = wrapper; this.items = {}; } add(item) { const key = this.hasher(item); if (this.items[key] !== undefined) throw "WrapperMap: the key [" + key + "] already exists!"; const wrapped = this.wrapper(item); this.items[key] = wrapped; return wrapped; } get(item) { const key = this.hasher(item); return this.items[key] || null; } getByKey(key) { return this.items[key] || null; } remove(item) { const key = this.hasher(item); return delete this.items[key]; } length() { return Object.keys(this.items).length; } }