"use strict";
-// SimpleDwindle 0.1
-// Proof of Concept: Tiling / Floating Z-order
+// SimpleDwindle 0.2
+// Minimal chronological Dwindle layout for KWin 6
-const tiledWindows = [];
+const GAP = 8;
-function log(message) {
- print("[SimpleDwindle] " + message);
+const layouts = [];
+const floating = [];
+const managed = [];
+
+function log(msg) {
+ print("[SimpleDwindle] " + msg);
+}
+
+function eligible(w) {
+ return w && w.normalWindow &&
+ w.moveable && w.resizeable &&
+ !w.fullScreen && !w.minimized &&
+ !w.transient;
+}
+
+function contains(list, w) {
+ return list.indexOf(w) !== -1;
+}
+
+function layoutFor(w) {
+ const desktop = w.desktops.length
+ ? w.desktops[0]
+ : workspace.currentDesktop;
+
+ let layout = layouts.find(l =>
+ l.output === w.output &&
+ l.desktop === desktop
+ );
+
+ if (!layout) {
+ layout = {
+ output: w.output,
+ desktop: desktop,
+ root: null,
+ last: null
+ };
+ layouts.push(layout);
+ }
+
+ return layout;
+}
+
+function leaf(w, parent = null) {
+ return {
+ window: w,
+ parent: parent,
+ first: null,
+ second: null,
+ vertical: false
+ };
+}
+
+function findLeaf(node, w) {
+ if (!node)
+ return null;
+
+ if (node.window === w)
+ return node;
+
+ return findLeaf(node.first, w) ||
+ findLeaf(node.second, w);
+}
+
+function rightmost(node) {
+ if (!node)
+ return null;
+
+ while (node.second)
+ node = node.second;
+
+ return node;
+}
+
+function depth(node) {
+ let d = 0;
+
+ while (node && node.parent) {
+ d++;
+ node = node.parent;
+ }
+
+ return d;
+}
+
+function insertWindow(layout, w) {
+ if (!layout.root) {
+ layout.root = leaf(w);
+ layout.last = w;
+ return;
+ }
+
+ let target = findLeaf(layout.root, layout.last);
+
+ if (!target)
+ target = rightmost(layout.root);
+
+ const d = depth(target);
+ const oldWindow = target.window;
+
+ target.window = null;
+ target.vertical = (d % 2 === 0);
+
+ target.first = leaf(oldWindow, target);
+ target.second = leaf(w, target);
+
+ layout.last = w;
+}
+
+function removeNode(layout, w) {
+ const node = findLeaf(layout.root, w);
+
+ if (!node)
+ return false;
+
+ if (!node.parent) {
+ layout.root = null;
+ layout.last = null;
+ return true;
+ }
+
+ const parent = node.parent;
+ const sibling = parent.first === node
+ ? parent.second
+ : parent.first;
+
+ const grandparent = parent.parent;
+
+ sibling.parent = grandparent;
+
+ if (!grandparent) {
+ layout.root = sibling;
+ } else if (grandparent.first === parent) {
+ grandparent.first = sibling;
+ } else {
+ grandparent.second = sibling;
+ }
+
+ if (layout.last === w) {
+ const lastNode = rightmost(layout.root);
+ layout.last = lastNode ? lastNode.window : null;
+ }
+
+ return true;
}
-function isCandidate(window) {
- return window && window.normalWindow &&
- !window.fullScreen;
+function applyNode(node, rect) {
+ if (!node)
+ return;
+
+ if (node.window) {
+ const w = node.window;
+
+ const x = Math.round(rect.x + GAP / 2);
+ const y = Math.round(rect.y + GAP / 2);
+ const width = Math.max(
+ 1, Math.round(rect.width - GAP)
+ );
+ const height = Math.max(
+ 1, Math.round(rect.height - GAP)
+ );
+
+ w.keepAbove = false;
+ w.keepBelow = true;
+
+ w.frameGeometry = {
+ x: x,
+ y: y,
+ width: width,
+ height: height
+ };
+
+ return;
+ }
+
+ if (node.vertical) {
+ const half = rect.width / 2;
+
+ applyNode(node.first, {
+ x: rect.x,
+ y: rect.y,
+ width: half,
+ height: rect.height
+ });
+
+ applyNode(node.second, {
+ x: rect.x + half,
+ y: rect.y,
+ width: rect.width - half,
+ height: rect.height
+ });
+ } else {
+ const half = rect.height / 2;
+
+ applyNode(node.first, {
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: half
+ });
+
+ applyNode(node.second, {
+ x: rect.x,
+ y: rect.y + half,
+ width: rect.width,
+ height: rect.height - half
+ });
+ }
}
-function isTiled(window) {
- return tiledWindows.indexOf(window) !== -1;
+function arrange(layout) {
+ if (!layout.root)
+ return;
+
+ const area = workspace.clientArea(
+ KWin.MaximizeArea,
+ layout.output,
+ layout.desktop
+ );
+
+ applyNode(layout.root, area);
}
-function setTiled(window) {
- if (!isCandidate(window) || isTiled(window))
+function tileWindow(w) {
+ if (!eligible(w) || contains(managed, w))
return;
- tiledWindows.push(window);
+ const fi = floating.indexOf(w);
+ if (fi !== -1)
+ floating.splice(fi, 1);
- window.keepAbove = false;
- window.keepBelow = true;
+ managed.push(w);
- log("TILED: " + window.caption);
+ const layout = layoutFor(w);
+
+ insertWindow(layout, w);
+ arrange(layout);
+
+ log("TILED: " + w.caption);
}
-function setFloating(window) {
- const index = tiledWindows.indexOf(window);
+function detachWindow(w) {
+ const index = managed.indexOf(w);
if (index === -1)
+ return false;
+
+ managed.splice(index, 1);
+
+ for (const layout of layouts) {
+ if (removeNode(layout, w)) {
+ arrange(layout);
+ break;
+ }
+ }
+
+ return true;
+}
+
+function floatWindow(w) {
+ if (!detachWindow(w))
return;
- tiledWindows.splice(index, 1);
+ if (!contains(floating, w))
+ floating.push(w);
- window.keepBelow = false;
+ w.keepBelow = false;
- log("FLOATING: " + window.caption);
+ log("FLOATING: " + w.caption);
}
function toggleFloating() {
- const window = workspace.activeWindow;
+ const w = workspace.activeWindow;
- if (!window)
+ if (!w || !w.normalWindow)
return;
- if (isTiled(window))
- setFloating(window);
- else
- setTiled(window);
+ if (contains(managed, w)) {
+ floatWindow(w);
+ } else {
+ tileWindow(w);
+ }
}
-// Remove closed windows from our list
-workspace.windowRemoved.connect(function(window) {
- const index = tiledWindows.indexOf(window);
+workspace.windowAdded.connect(function(w) {
+ if (eligible(w))
+ tileWindow(w);
+});
+
+workspace.windowRemoved.connect(function(w) {
+ detachWindow(w);
+ const index = floating.indexOf(w);
if (index !== -1)
- tiledWindows.splice(index, 1);
+ floating.splice(index, 1);
+});
+
+workspace.screensChanged.connect(function() {
+ for (const layout of layouts)
+ arrange(layout);
});
-// Keyboard shortcut
registerShortcut(
"SimpleDwindleToggleFloating",
"SimpleDwindle: Toggle Floating",
toggleFloating
);
-log("Version 0.1 initialized");
-
+log("Version 0.2 initialized");