2026-08-15 16:03:36 +02:00

5230 lines
200 KiB
JavaScript

"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 `<HalfSplitLayout: angle:${this.angle},ratio:${this.ratio},pr_size:${this.primarySize}.<<<Primary:${this.primary}---Secondary:${this.secondary}>>>`;
}
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;
}
}