1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612 |
- /*
- * This file is part of the Forge extension for GNOME
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- // Gnome imports
- import GObject from "gi://GObject";
- import Meta from "gi://Meta";
- import Shell from "gi://Shell";
- import St from "gi://St";
- // Shared state
- import { Logger } from "../shared/logger.js";
- // App imports
- import * as Utils from "./utils.js";
- import * as Window from "./window.js";
- export const NODE_TYPES = Utils.createEnum([
- "ROOT",
- "MONITOR", //Output in i3
- "CON", //Container in i3
- "WINDOW",
- "WORKSPACE",
- ]);
- export const LAYOUT_TYPES = Utils.createEnum([
- "STACKED",
- "TABBED",
- "ROOT",
- "HSPLIT",
- "VSPLIT",
- "PRESET",
- ]);
- export const ORIENTATION_TYPES = Utils.createEnum(["NONE", "HORIZONTAL", "VERTICAL"]);
- export const POSITION = Utils.createEnum(["BEFORE", "AFTER", "UNKNOWN"]);
- /**
- * The Node data representation of the following elements in the user's display:
- *
- * Monitor,
- * Window,
- * Container (generic),
- * Workspace
- *
- */
- export class Node extends GObject.Object {
- static {
- GObject.registerClass(this);
- }
- constructor(type, data) {
- super();
- // TODO - move to GObject property definitions?
- this._type = type; // see NODE_TYPES
- // _data: Meta.Window, unique id strings (Monitor,
- // Workspace or St.Bin - a representation of Container)
- this._data = data;
- this._parent = null;
- this._nodes = []; // Child elements of this node
- this.mode = Window.WINDOW_MODES.DEFAULT;
- this.percent = 0.0;
- this._rect = null;
- this.tab = null;
- this.decoration = null;
- this.app = null;
- if (this.isWindow()) {
- // When destroy() is called on Meta.Window, it might not be
- // available so we store it immediately
- this._initMetaWindow();
- this._actor = this._data.get_compositor_private();
- this._createWindowTab();
- }
- if (this.isCon()) {
- this._createDecoration();
- }
- }
- get windowActor() {
- return this._actor;
- }
- get actor() {
- switch (this.nodeType) {
- case NODE_TYPES.WINDOW:
- // A Meta.Window was assigned during creation
- // But obtain the Clutter.Actor
- return this._actor;
- case NODE_TYPES.CON:
- case NODE_TYPES.ROOT:
- // A St.Bin was assigned during creation
- return this.nodeValue;
- case NODE_TYPES.MONITOR:
- case NODE_TYPES.WORKSPACE:
- // A separate St.Bin was assigned on another attribute during creation
- return this.actorBin;
- }
- }
- set rect(rect) {
- this._rect = rect;
- switch (this.nodeType) {
- case NODE_TYPES.WINDOW:
- break;
- case NODE_TYPES.CON:
- case NODE_TYPES.MONITOR:
- case NODE_TYPES.ROOT:
- case NODE_TYPES.WORKSPACE:
- if (this.actor) {
- this.actor.set_size(rect.width, rect.height);
- this.actor.set_position(rect.x, rect.y);
- }
- break;
- }
- }
- get rect() {
- return this._rect;
- }
- get childNodes() {
- return this._nodes;
- }
- set childNodes(nodes) {
- this._nodes = nodes;
- }
- get firstChild() {
- if (this._nodes && this._nodes.length >= 1) {
- return this._nodes[0];
- }
- return null;
- }
- get level() {
- let _level = 0;
- let refNode = this.parentNode;
- while (refNode) {
- _level += 1;
- refNode = refNode.parentNode;
- }
- return _level;
- }
- /**
- * Find the index of this relative to the siblings
- */
- get index() {
- if (this.parentNode) {
- let childNodes = this.parentNode.childNodes;
- for (let i = 0; i < childNodes.length; i++) {
- if (childNodes[i] === this) {
- return i;
- }
- }
- }
- return null;
- }
- get lastChild() {
- if (this._nodes && this._nodes.length >= 1) {
- return this._nodes[this._nodes.length - 1];
- }
- return null;
- }
- get nextSibling() {
- if (this.parentNode) {
- if (this.parentNode.lastChild !== this) {
- return this.parentNode.childNodes[this.index + 1];
- }
- }
- return null;
- }
- get nodeType() {
- return this._type;
- }
- get nodeValue() {
- return this._data;
- }
- get parentNode() {
- return this._parent;
- }
- set parentNode(node) {
- this._parent = node;
- }
- get previousSibling() {
- if (this.parentNode) {
- if (this.parentNode.firstChild !== this) {
- return this.parentNode.childNodes[this.index - 1];
- }
- }
- return null;
- }
- appendChild(node) {
- if (!node) return null;
- if (node.parentNode) node.parentNode.removeChild(node);
- this.childNodes.push(node);
- node.parentNode = this;
- return node;
- }
- /**
- * Checks if node is a descendant of this,
- * or a descendant of its childNodes, etc
- */
- contains(node) {
- if (!node) return false;
- let searchNode = this.getNodeByValue(node.nodeValue);
- return searchNode ? true : false;
- }
- getNodeByLayout(layout) {
- let results = this._search(layout, "LAYOUT");
- return results;
- }
- getNodeByMode(mode) {
- let results = this._search(mode, "MODE");
- return results;
- }
- getNodeByValue(value) {
- let results = this._search(value, "VALUE");
- return results && results.length >= 1 ? results[0] : null;
- }
- getNodeByType(type) {
- let results = this._search(type, "TYPE");
- return results;
- }
- /**
- * @param childNode - is a child of this
- */
- insertBefore(newNode, childNode) {
- if (!newNode) return null;
- if (newNode === childNode) return null;
- if (!childNode) {
- this.appendChild(newNode);
- return newNode;
- }
- if (childNode.parentNode !== this) return null;
- if (newNode.parentNode) newNode.parentNode.removeChild(newNode);
- let index = childNode.index;
- if (childNode.index === 0) {
- this.childNodes.unshift(newNode);
- } else if (childNode.index > 0) {
- this.childNodes.splice(index, 0, newNode);
- }
- newNode.parentNode = this;
- return newNode;
- }
- isLayout(name) {
- let layout = this.layout;
- if (!layout) return false;
- return name === layout;
- }
- isHSplit() {
- return this.isLayout(LAYOUT_TYPES.HSPLIT);
- }
- isVSplit() {
- return this.isLayout(LAYOUT_TYPES.VSPLIT);
- }
- isStacked() {
- return this.isLayout(LAYOUT_TYPES.STACKED);
- }
- isTabbed() {
- return this.isLayout(LAYOUT_TYPES.TABBED);
- }
- isType(name) {
- const type = this.nodeType;
- if (!type) return false;
- return name === type;
- }
- isWindow() {
- return this.isType(NODE_TYPES.WINDOW);
- }
- isCon() {
- return this.isType(NODE_TYPES.CON);
- }
- isMonitor() {
- return this.isType(NODE_TYPES.MONITOR);
- }
- isWorkspace() {
- return this.isType(NODE_TYPES.WORKSPACE);
- }
- isRoot() {
- return this.isType(NODE_TYPES.ROOT);
- }
- isMode(name) {
- const mode = this.mode;
- if (!name) return false;
- return name === mode;
- }
- isFloat() {
- return this.isMode(Window.WINDOW_MODES.FLOAT);
- }
- isTile() {
- return this.isMode(Window.WINDOW_MODES.TILE);
- }
- isGrabTile() {
- return this.isMode(Window.WINDOW_MODES.GRAB_TILE);
- }
- removeChild(node) {
- if (node.isTabbed() && node.decoration) {
- node.decoration.hide();
- node.decoration.destroy_all_children();
- node.decoration.destroy();
- node.decoration = null;
- }
- let refNode;
- if (this.contains(node)) {
- // Since contains() tries to find node on all descendants,
- // detach only from the immediate parent
- let parentNode = node.parentNode;
- refNode = parentNode.childNodes.splice(node.index, 1);
- refNode.parentNode = null;
- }
- if (!refNode) {
- throw `NodeNotFound ${node}`;
- }
- return refNode;
- }
- /**
- * Backend for getNodeBy[attribute]. It is similar to DOM.getElementBy functions
- */
- _search(term, criteria) {
- let results = [];
- let searchFn = (candidate) => {
- if (criteria) {
- switch (criteria) {
- case "VALUE":
- if (candidate.nodeValue === term) {
- results.push(candidate);
- }
- break;
- case "TYPE":
- if (candidate.nodeType === term) {
- results.push(candidate);
- }
- break;
- case "MODE":
- if (candidate.mode === term) {
- results.push(candidate);
- }
- case "LAYOUT":
- if (candidate.layout && candidate.layout === term) {
- results.push(candidate);
- }
- }
- } else {
- if (candidate === term) {
- results.push(candidate);
- }
- }
- };
- this._walk(searchFn, this._traverseBreadthFirst);
- return results;
- }
- // start walking from root and all child nodes
- _traverseBreadthFirst(callback) {
- let queue = new Queue();
- queue.enqueue(this);
- let currentNode = queue.dequeue();
- while (currentNode) {
- for (let i = 0, length = currentNode.childNodes.length; i < length; i++) {
- queue.enqueue(currentNode.childNodes[i]);
- }
- callback(currentNode);
- currentNode = queue.dequeue();
- }
- }
- // start walking from bottom to root
- _traverseDepthFirst(callback) {
- let recurse = (currentNode) => {
- for (let i = 0, length = currentNode.childNodes.length; i < length; i++) {
- recurse(currentNode.childNodes[i]);
- }
- callback(currentNode);
- };
- recurse(this);
- }
- _walk(callback, traversal) {
- traversal.call(this, callback);
- }
- _initMetaWindow() {
- if (this.isWindow()) {
- let windowTracker = Shell.WindowTracker.get_default();
- let metaWin = this.nodeValue;
- let app = windowTracker.get_window_app(metaWin);
- this.app = app;
- }
- }
- _createWindowTab() {
- if (this.tab || !this.isWindow()) return;
- let tabContents = new St.BoxLayout({
- style_class: "window-tabbed-tab",
- x_expand: true,
- });
- let app = this.app;
- let labelText = this._getTitle();
- let metaWin = this.nodeValue;
- let titleButton = new St.Button({
- x_expand: true,
- label: `${labelText}`,
- });
- let iconBin = new St.Button({
- style_class: "window-tabbed-tab-icon",
- });
- let icon = app.create_icon_texture(24 * Utils.dpi());
- iconBin.child = icon;
- let closeButton = new St.Button({
- style_class: "window-tabbed-tab-close",
- child: new St.Icon({ icon_name: "window-close-symbolic" }),
- });
- tabContents.add(iconBin);
- tabContents.add(titleButton);
- tabContents.add(closeButton);
- let clickFn = () => {
- this.parentNode.childNodes.forEach((c) => {
- if (c.tab) {
- c.tab.remove_style_class_name("window-tabbed-tab-active");
- c.render();
- }
- });
- tabContents.add_style_class_name("window-tabbed-tab-active");
- metaWin.activate(global.display.get_current_time());
- };
- iconBin.connect("clicked", clickFn);
- titleButton.connect("clicked", clickFn);
- closeButton.connect("clicked", () => {
- metaWin.delete(global.get_current_time());
- });
- if (metaWin === global.display.get_focus_window()) {
- tabContents.add_style_class_name("window-tabbed-tab-active");
- }
- this.tab = tabContents;
- }
- _createDecoration() {
- if (this.decoration) return;
- let decoration = new St.BoxLayout();
- decoration.type = "forge-deco";
- decoration.parentNode = this;
- let globalWinGrp = global.window_group;
- decoration.style_class = "window-tabbed-bg";
- if (!globalWinGrp.contains(decoration)) {
- globalWinGrp.add_child(decoration);
- }
- decoration.hide();
- this.decoration = decoration;
- }
- _getTitle() {
- if (this.isWindow()) {
- return this.nodeValue.title ? this.nodeValue.title : this.app.get_name();
- }
- return null;
- }
- render() {
- // Always update the title for the tab
- if (this.tab !== null && this.tab !== undefined) {
- let titleLabel = this.tab.get_child_at_index(1);
- if (titleLabel) titleLabel.label = this._getTitle();
- }
- }
- set float(value) {
- if (this.isWindow()) {
- let metaWindow = this.nodeValue;
- let floatAlwaysOnTop = this.settings.get_boolean("float-always-on-top-enabled");
- if (value) {
- this.mode = Window.WINDOW_MODES.FLOAT;
- if (!metaWindow.is_above()) {
- floatAlwaysOnTop && metaWindow.make_above();
- }
- } else {
- this.mode = Window.WINDOW_MODES.TILE;
- if (metaWindow.is_above()) {
- metaWindow.unmake_above();
- }
- }
- }
- }
- set tile(value) {
- this.float = !value;
- }
- }
- /**
- * An implementation of Queue using arrays
- */
- export class Queue extends GObject.Object {
- static {
- GObject.registerClass(this);
- }
- constructor() {
- super();
- this._elements = [];
- }
- get length() {
- return this._elements.length;
- }
- enqueue(item) {
- this._elements.push(item);
- }
- dequeue() {
- return this._elements.shift();
- }
- }
- export class Tree extends Node {
- static {
- GObject.registerClass(this);
- }
- /** @param {Window.WindowManager} extWm */
- constructor(extWm) {
- let rootBin = new St.Bin();
- super(NODE_TYPES.ROOT, rootBin);
- this._extWm = extWm;
- this.defaultStackHeight = 35;
- this.settings = this.extWm.ext.settings;
- this.layout = LAYOUT_TYPES.ROOT;
- if (!global.window_group.contains(rootBin)) global.window_group.add_child(rootBin);
- this._initWorkspaces();
- }
- /** @type {Window.WindowManager} */
- get extWm() {
- return this._extWm;
- }
- /**
- * Handles new and existing workspaces in the tree
- */
- _initWorkspaces() {
- let wsManager = global.display.get_workspace_manager();
- let workspaces = wsManager.get_n_workspaces();
- for (let i = 0; i < workspaces; i++) {
- this.addWorkspace(i);
- }
- }
- // TODO move to monitor.js
- addMonitor(wsIndex) {
- let monitors = global.display.get_n_monitors();
- for (let mi = 0; mi < monitors; mi++) {
- let monitorWsNode = this.createNode(
- `ws${wsIndex}`,
- NODE_TYPES.MONITOR,
- `mo${mi}ws${wsIndex}`
- );
- monitorWsNode.layout = this.extWm.determineSplitLayout();
- monitorWsNode.actorBin = new St.Bin();
- if (!global.window_group.contains(monitorWsNode.actorBin))
- global.window_group.add_child(monitorWsNode.actorBin);
- }
- }
- // TODO move to workspace.js
- addWorkspace(wsIndex) {
- let wsManager = global.display.get_workspace_manager();
- let workspaceNodeValue = `ws${wsIndex}`;
- let existingWsNode = this.findNode(workspaceNodeValue);
- if (existingWsNode) {
- return false;
- }
- let newWsNode = this.createNode(this.nodeValue, NODE_TYPES.WORKSPACE, workspaceNodeValue);
- let workspace = wsManager.get_workspace_by_index(wsIndex);
- newWsNode.layout = LAYOUT_TYPES.HSPLIT;
- newWsNode.actorBin = new St.Bin({ style_class: "workspace-actor-bg" });
- if (!global.window_group.contains(newWsNode.actorBin))
- global.window_group.add_child(newWsNode.actorBin);
- this.extWm.bindWorkspaceSignals(workspace);
- this.addMonitor(wsIndex);
- return true;
- }
- // TODO move to workspace.js
- removeWorkspace(wsIndex) {
- let workspaceNodeData = `ws${wsIndex}`;
- let existingWsNode = this.findNode(workspaceNodeData);
- if (!existingWsNode) {
- return false;
- }
- if (global.window_group.contains(existingWsNode.actorBin))
- global.window_group.remove_child(existingWsNode.actorBin);
- this.removeChild(existingWsNode);
- return true;
- }
- get nodeWorkpaces() {
- let nodeWorkspaces = this.getNodeByType(NODE_TYPES.WORKSPACE);
- return nodeWorkspaces;
- }
- get nodeWindows() {
- let nodeWindows = this.getNodeByType(NODE_TYPES.WINDOW);
- return nodeWindows;
- }
- /**
- * Creates a new Node and attaches it to a parent toData.
- * Parent can be MONITOR or CON types only.
- */
- createNode(parentObj, type, value, mode = Window.WINDOW_MODES.TILE) {
- let parentNode = this.findNode(parentObj);
- let child;
- if (parentNode) {
- child = new Node(type, value);
- child.settings = this.settings;
- if (child.isWindow()) child.mode = mode;
- // Append after a window
- if (parentNode.isWindow()) {
- const grandParentNode = parentNode.parentNode;
- grandParentNode.insertBefore(child, parentNode.nextSibling);
- Logger.debug(
- `Parent is a window, attaching to this window's parent ${grandParentNode.nodeType}`
- );
- } else {
- // Append as the last item of the container
- parentNode.appendChild(child);
- }
- }
- return child;
- }
- /**
- * Finds any Node in the tree using data
- * Data types can be in the form of Meta.Window or unique id strings
- * for Workspace, Monitor and Container
- *
- * Workspace id strings takes the form `ws{n}`.
- * Monitor id strings takes the form `mo{m}ws{n}`
- * Container id strings takes the form `mo{m}ws{n}c{x}`
- *
- */
- findNode(data) {
- let searchNode = this.getNodeByValue(data);
- return searchNode;
- }
- /**
- * Find the NodeWindow using the Meta.WindowActor
- */
- findNodeByActor(windowActor) {
- let searchNode;
- let criteriaMatchFn = (node) => {
- if (node.isWindow() && node.actor === windowActor) {
- searchNode = node;
- }
- };
- this._walk(criteriaMatchFn, this._traverseDepthFirst);
- return searchNode;
- }
- /**
- * Focuses on the next node, if metaWindow and tiled, raise it
- */
- focus(node, direction) {
- if (!node) return null;
- let next = this.next(node, direction);
- if (!next) return null;
- let type = next.nodeType;
- let position = Utils.positionFromDirection(direction);
- const previous = position === POSITION.BEFORE;
- switch (type) {
- case NODE_TYPES.WINDOW:
- break;
- case NODE_TYPES.CON:
- const tiledConWindows = next.getNodeByType(NODE_TYPES.WINDOW).filter((w) => w.isTile());
- if (next.layout === LAYOUT_TYPES.STACKED) {
- next = next.lastChild;
- } else {
- if (tiledConWindows.length > 1) {
- if (previous) {
- next = tiledConWindows[tiledConWindows.length - 1];
- } else {
- next = tiledConWindows[0];
- }
- } else {
- next = tiledConWindows[0];
- }
- }
- break;
- case NODE_TYPES.MONITOR:
- if (next.layout === LAYOUT_TYPES.STACKED) {
- next = next.lastChild;
- } else {
- if (previous) {
- next = next.lastChild;
- } else {
- next = next.firstChild;
- }
- }
- if (next && next.nodeType === NODE_TYPES.CON) {
- const tiledConWindows = next.getNodeByType(NODE_TYPES.WINDOW).filter((w) => w.isTile());
- if (next.layout === LAYOUT_TYPES.STACKED) {
- next = next.lastChild;
- } else {
- if (tiledConWindows.length > 1) {
- if (previous) {
- next = tiledConWindows[tiledConWindows.length - 1];
- } else {
- next = tiledConWindows[0];
- }
- } else {
- next = tiledConWindows[0];
- }
- }
- }
- break;
- }
- if (!next) return null;
- let metaWindow = next.nodeValue;
- if (!metaWindow) return null;
- if (metaWindow.minimized) {
- next = this.focus(next, direction);
- } else {
- metaWindow.raise();
- metaWindow.focus(global.display.get_current_time());
- metaWindow.activate(global.display.get_current_time());
- if (this.settings.get_boolean("move-pointer-focus-enabled")) {
- this.extWm.movePointerWith(next);
- } else {
- let monitorArea = metaWindow.get_work_area_current_monitor();
- let ptr = this.extWm.getPointer();
- if (!Utils.rectContainsPoint(monitorArea, [ptr[0], ptr[1]])) {
- this.extWm.movePointerWith(next);
- }
- }
- }
- return next;
- }
- /**
- * Obtains the non-floating, non-minimized list of nodes
- * Useful for calculating the rect areas
- */
- getTiledChildren(items) {
- let filterFn = (node) => {
- if (node.isWindow()) {
- let floating = node.isFloat();
- let grabTiling = node.isGrabTile();
- // A Node[Window]._data is a Meta.Window
- if (!node.nodeValue.minimized && !(floating || grabTiling)) {
- return true;
- }
- }
- // handle split containers
- if (node.isCon()) {
- return this.getTiledChildren(node.childNodes).length > 0;
- }
- return false;
- };
- return items ? items.filter(filterFn) : [];
- }
- /**
- * Move a given node into a direction
- *
- * TODO, handle minimized or floating windows
- *
- */
- move(node, direction) {
- let next = this.next(node, direction);
- let position = Utils.positionFromDirection(direction);
- if (!next || next === -1) {
- if (next === -1) {
- // TODO - update appending or prepending on the same monitor
- const currMonWsNode = this.extWm.currentMonWsNode;
- if (currMonWsNode) {
- if (position === POSITION.AFTER) {
- currMonWsNode.appendChild(node);
- } else {
- currMonWsNode.insertBefore(node, next.firstChild);
- }
- return true;
- }
- }
- return false;
- }
- let parentNode = node.parentNode;
- let parentTarget;
- switch (next.nodeType) {
- case NODE_TYPES.WINDOW:
- // If same parent, swap
- if (next === node.previousSibling || next === node.nextSibling) {
- parentTarget = next.parentNode;
- this.swapPairs(node, next);
- if (this.settings.get_boolean("move-pointer-focus-enabled")) {
- this.extWm.movePointerWith(node);
- }
- // do not reset percent when swapped
- return true;
- } else {
- parentTarget = next.parentNode;
- if (parentTarget) {
- if (position === POSITION.AFTER) {
- parentTarget.insertBefore(node, next);
- } else {
- parentTarget.insertBefore(node, next.nextSibling);
- }
- }
- }
- break;
- case NODE_TYPES.CON:
- parentTarget = next;
- if (next.isStacked()) {
- next.appendChild(node);
- } else {
- if (position === POSITION.AFTER) {
- next.insertBefore(node, next.firstChild);
- } else {
- next.appendChild(node);
- }
- }
- break;
- case NODE_TYPES.MONITOR:
- parentTarget = next;
- const currMonWsNode = this.extWm.currentMonWsNode;
- if (
- !next.contains(node) &&
- (node === currMonWsNode.firstChild || node === currMonWsNode.lastChild)
- ) {
- let targetMonRect = this.extWm.rectForMonitor(node, Utils.monitorIndex(next.nodeValue));
- if (!targetMonRect) return false;
- if (position === POSITION.AFTER) {
- next.insertBefore(node, next.firstChild);
- } else {
- next.appendChild(node);
- }
- let rect = targetMonRect;
- this.extWm.move(node.nodeValue, rect);
- this.extWm.movePointerWith(node);
- } else {
- if (position === POSITION.AFTER) {
- currMonWsNode.appendChild(node);
- } else {
- currMonWsNode.insertBefore(node, currMonWsNode.firstChild);
- }
- }
- break;
- default:
- break;
- }
- this.resetSiblingPercent(parentNode);
- this.resetSiblingPercent(parentTarget);
- return true;
- }
- /**
- * Give the next sibling/parent/descendant on the tree based
- * on a given Meta.MotionDirection
- *
- * @param {Tree.Node} node
- * @param {Meta.MotionDirection} direction
- *
- * Credits: borrowed logic from tree.c of i3
- */
- next(node, direction) {
- if (!node) return null;
- let orientation = Utils.orientationFromDirection(direction);
- let position = Utils.positionFromDirection(direction);
- let previous = position === POSITION.BEFORE;
- const type = node.nodeType;
- switch (type) {
- case NODE_TYPES.ROOT:
- // Root is the top of the tree
- if (node.childNodes.length > 1) {
- if (previous) {
- return node.firstChild;
- } else {
- return node.lastChild;
- }
- } else {
- return node.firstChild;
- }
- case NODE_TYPES.WORKSPACE:
- // Let gnome-shell handle this?
- break;
- case NODE_TYPES.MONITOR:
- // Find the next monitor
- const nodeWindow = this.findFirstNodeWindowFrom(node);
- return this.nextMonitor(nodeWindow, position, orientation);
- }
- while (node.nodeType !== NODE_TYPES.WORKSPACE) {
- if (node.nodeType === NODE_TYPES.MONITOR) {
- return this.next(node, direction);
- }
- const parentNode = node.parentNode;
- const parentOrientation = Utils.orientationFromLayout(parentNode.layout);
- if (parentNode.childNodes.length > 1 && orientation === parentOrientation) {
- const next = previous ? node.previousSibling : node.nextSibling;
- if (next) {
- return next;
- }
- }
- node = node.parentNode;
- }
- }
- nextMonitor(nodeWindow, position, orientation) {
- if (!nodeWindow) return null;
- // Use the built in logic to determine adjacent monitors
- let monitorNode = null;
- let monitorDirection = Utils.directionFrom(position, orientation);
- let targetMonitor = -1;
- targetMonitor = global.display.get_monitor_neighbor_index(
- nodeWindow.nodeValue.get_monitor(),
- monitorDirection
- );
- if (targetMonitor < 0) return targetMonitor;
- let monWs = `mo${targetMonitor}ws${nodeWindow.nodeValue.get_workspace().index()}`;
- monitorNode = this.findNode(monWs);
- return monitorNode;
- }
- findAncestorMonitor(node) {
- return this.findAncestor(node, NODE_TYPES.MONITOR);
- }
- findAncestor(node, ancestorType) {
- let ancestorNode;
- while (node && ancestorType && !node.isRoot()) {
- if (node.isType(ancestorType)) {
- ancestorNode = node;
- break;
- } else {
- node = node.parentNode;
- }
- }
- return ancestorNode;
- }
- nextVisible(node, direction) {
- if (!node) return null;
- let next = this.next(node, direction);
- if (next && next.nodeType === NODE_TYPES.WINDOW && next.nodeValue && next.nodeValue.minimized) {
- next = this.nextVisible(next, direction);
- }
- return next;
- }
- /**
- * Credits: i3-like split
- */
- split(node, orientation, forceSplit = false) {
- if (!node) return;
- let type = node.nodeType;
- if (type === NODE_TYPES.WINDOW && node.mode === Window.WINDOW_MODES.FLOAT) {
- return;
- }
- if (!(type === NODE_TYPES.MONITOR || type === NODE_TYPES.CON || type === NODE_TYPES.WINDOW)) {
- return;
- }
- let parentNode = node.parentNode;
- let numChildren = parentNode.childNodes.length;
- // toggle the split
- if (
- !forceSplit &&
- numChildren === 1 &&
- (parentNode.layout === LAYOUT_TYPES.HSPLIT || parentNode.layout === LAYOUT_TYPES.VSPLIT)
- ) {
- parentNode.layout =
- orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
- this.attachNode = parentNode;
- return;
- }
- // Push down the Meta.Window into a new Container
- let currentIndex = node.index;
- let container = new St.Bin();
- let newConNode = new Node(NODE_TYPES.CON, container);
- newConNode.settings = this.settings;
- // Take the direction of the parent
- newConNode.layout =
- orientation === ORIENTATION_TYPES.HORIZONTAL ? LAYOUT_TYPES.HSPLIT : LAYOUT_TYPES.VSPLIT;
- newConNode.rect = node.rect;
- newConNode.percent = node.percent;
- newConNode.parentNode = parentNode;
- parentNode.childNodes[currentIndex] = newConNode;
- this.createNode(container, node.nodeType, node.nodeValue);
- node.parentNode = newConNode;
- this.attachNode = newConNode;
- }
- swap(node, direction) {
- let nextSwapNode = this.next(node, direction);
- if (!nextSwapNode) {
- return;
- }
- let nodeSwapType = nextSwapNode.nodeType;
- switch (nodeSwapType) {
- case NODE_TYPES.WINDOW:
- break;
- case NODE_TYPES.CON:
- case NODE_TYPES.MONITOR:
- let childWindowNodes = nextSwapNode
- .getNodeByMode(Window.WINDOW_MODES.TILE)
- .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
- if (nextSwapNode.layout === LAYOUT_TYPES.STACKED) {
- nextSwapNode = childWindowNodes[childWindowNodes.length - 1];
- } else {
- nextSwapNode = childWindowNodes[0];
- }
- break;
- }
- let isNextNodeWin =
- nextSwapNode && nextSwapNode.nodeValue && nextSwapNode.nodeType === NODE_TYPES.WINDOW;
- if (isNextNodeWin) {
- if (!this.extWm.sameParentMonitor(node, nextSwapNode)) {
- // TODO, there is a freeze bug if there are not in same monitor.
- return;
- }
- this.swapPairs(node, nextSwapNode);
- }
- return nextSwapNode;
- }
- swapPairs(fromNode, toNode, focus = true) {
- if (!(this._swappable(fromNode) && this._swappable(toNode))) return;
- // Swap the items in the array
- let parentForFrom = fromNode ? fromNode.parentNode : undefined;
- let parentForTo = toNode.parentNode;
- if (parentForTo && parentForFrom) {
- let nextIndex = toNode.index;
- let focusIndex = fromNode.index;
- let transferMode = fromNode.mode;
- fromNode.mode = toNode.mode;
- toNode.mode = transferMode;
- let transferRect = fromNode.nodeValue.get_frame_rect();
- let transferToRect = toNode.nodeValue.get_frame_rect();
- let transferPercent = fromNode.percent;
- fromNode.percent = toNode.percent;
- toNode.percent = transferPercent;
- parentForTo.childNodes[nextIndex] = fromNode;
- fromNode.parentNode = parentForTo;
- parentForFrom.childNodes[focusIndex] = toNode;
- toNode.parentNode = parentForFrom;
- this.extWm.move(fromNode.nodeValue, transferToRect);
- this.extWm.move(toNode.nodeValue, transferRect);
- if (focus) {
- // The fromNode is now on the parent-target
- fromNode.nodeValue.raise();
- fromNode.nodeValue.focus(global.get_current_time());
- }
- }
- }
- _swappable(node) {
- if (!node) return false;
- if (node.nodeType === NODE_TYPES.WINDOW && !node.nodeValue.minimized) {
- return true;
- }
- return false;
- }
- /**
- * Performs cleanup of dangling parents in addition to removing the
- * node from the parent.
- */
- removeNode(node) {
- let oldChild;
- let cleanUpParent = (existParent) => {
- if (this.getTiledChildren(existParent.childNodes).length === 0) {
- existParent.percent = 0.0;
- this.resetSiblingPercent(existParent.parentNode);
- }
- this.resetSiblingPercent(existParent);
- };
- let parentNode = node.parentNode;
- // If parent has only this window, remove the parent instead
- if (parentNode.childNodes.length === 1 && parentNode.nodeType !== NODE_TYPES.MONITOR) {
- let existParent = parentNode.parentNode;
- oldChild = existParent.removeChild(parentNode);
- cleanUpParent(existParent);
- } else {
- let existParent = node.parentNode;
- oldChild = existParent.removeChild(node);
- if (!this.extWm.floatingWindow(node)) cleanUpParent(existParent);
- }
- if (node === this.attachNode) {
- this.attachNode = null;
- } else {
- // Find the next focus node as attachNode
- this.attachNode = this.findNode(this.extWm.focusMetaWindow);
- }
- return oldChild ? true : false;
- }
- render(from) {
- Logger.debug(`render tree ${from ? "from " + from : ""}`);
- this.processNode(this);
- this.apply(this);
- this.cleanTree();
- let debugMode = true;
- if (debugMode) {
- this.debugTree();
- }
- Logger.debug(`*********************************************`);
- }
- apply(node) {
- if (!node) return;
- let tiledChildren = node
- .getNodeByMode(Window.WINDOW_MODES.TILE)
- .filter((t) => t.nodeType === NODE_TYPES.WINDOW);
- tiledChildren.forEach((w) => {
- if (w.renderRect) {
- if (w.renderRect.width > 0 && w.renderRect.height > 0) {
- let metaWin = w.nodeValue;
- this.extWm.move(metaWin, w.renderRect);
- } else {
- Logger.debug(`ignoring apply for ${w.renderRect.width}x${w.renderRect.height}`);
- }
- }
- if (w.nodeValue.firstRender) w.nodeValue.firstRender = false;
- });
- }
- cleanTree() {
- // Phase 1: remove any cons with empty children
- const orphanCons = this.getNodeByType(NODE_TYPES.CON).filter((c) => c.childNodes.length === 0);
- const hasOrphanCons = orphanCons.length > 0;
- orphanCons.forEach((o) => {
- this.removeNode(o);
- });
- const invalidWindows = this.getNodeByType(NODE_TYPES.WINDOW).filter((w) => {
- const metaWindow = w.nodeValue;
- const title = metaWindow.title;
- const wmClass = metaWindow.wm_class;
- return wmClass === "gjs";
- });
- invalidWindows.forEach((w) => {
- this.removeNode(w);
- });
- // Phase 2: remove any empty parent cons up to the single intermediate parent-window level
- // Basically, flatten them?
- // [con[con[con[con[window]]]]] --> [con[window]]
- // TODO: help :)
- const grandParentCons = this.getNodeByType(NODE_TYPES.CON).filter(
- (c) => c.childNodes.length === 1 && c.childNodes[0].nodeType === NODE_TYPES.CON
- );
- grandParentCons.forEach((c) => {
- c.layout = LAYOUT_TYPES.HSPLIT;
- });
- if (hasOrphanCons || invalidWindows.length > 0) {
- this.processNode(this);
- this.apply(this);
- }
- }
- /**
- *
- * Credits: Do the i3-like calculations
- *
- */
- processNode(node) {
- if (!node) return;
- // Render the Root, Workspace and Monitor
- // For now, we let them render their children recursively
- if (node.nodeType === NODE_TYPES.ROOT) {
- node.childNodes.forEach((child) => {
- this.processNode(child);
- });
- }
- if (node.nodeType === NODE_TYPES.WORKSPACE) {
- node.childNodes.forEach((child) => {
- this.processNode(child);
- });
- }
- let params = {};
- if (node.nodeType === NODE_TYPES.MONITOR || node.nodeType === NODE_TYPES.CON) {
- // The workarea from Meta.Window's assigned monitor
- // is important so it computes to `remove` the panel size
- // really well. However, this type of workarea would only
- // appear if there is window present on the monitor.
- if (node.childNodes.length === 0) {
- return;
- }
- // If monitor, get the workarea
- if (node.nodeType === NODE_TYPES.MONITOR) {
- let monitorIndex = Utils.monitorIndex(node.nodeValue);
- let monitorArea = global.display
- .get_workspace_manager()
- .get_active_workspace()
- .get_work_area_for_monitor(monitorIndex);
- if (!monitorArea) return; // there is no visible child window
- node.rect = monitorArea;
- node.rect = this.processGap(node);
- }
- let tiledChildren = this.getTiledChildren(node.childNodes);
- let sizes = this.computeSizes(node, tiledChildren);
- params.sizes = sizes;
- let showTabs = this.settings.get_boolean("showtab-decoration-enabled");
- params.stackedHeight = showTabs ? this.defaultStackHeight * Utils.dpi() : 0;
- params.tiledChildren = tiledChildren;
- let decoration = node.decoration;
- if (decoration) {
- let decoChildren = decoration.get_children();
- decoChildren.forEach((decoChild) => {
- decoration.remove_child(decoChild);
- });
- }
- tiledChildren.forEach((child, index) => {
- // A monitor can contain a window or container child
- if (node.layout === LAYOUT_TYPES.HSPLIT || node.layout === LAYOUT_TYPES.VSPLIT) {
- this.processSplit(node, child, params, index);
- } else if (node.layout === LAYOUT_TYPES.STACKED) {
- this.processStacked(node, child, params, index);
- } else if (node.layout === LAYOUT_TYPES.TABBED) {
- this.processTabbed(node, child, params, index);
- }
- this.processNode(child);
- });
- }
- if (node.isWindow()) {
- if (!node.rect) node.rect = node.nodeValue.get_work_area_current_monitor();
- node.renderRect = this.processGap(node);
- }
- }
- /**
- * Forge processes both non-Window and Window gaps
- */
- processGap(node) {
- let nodeWidth = node.rect.width;
- let nodeHeight = node.rect.height;
- let nodeX = node.rect.x;
- let nodeY = node.rect.y;
- let gap = this.extWm.calculateGaps(node);
- if (nodeWidth > gap * 2 && nodeHeight > gap * 2) {
- nodeX += gap;
- nodeY += gap;
- // TODO - detect inbetween windows and adjust accordingly
- // Also adjust depending on display scaling
- nodeWidth -= gap * 2;
- nodeHeight -= gap * 2;
- }
- return { x: nodeX, y: nodeY, width: nodeWidth, height: nodeHeight };
- }
- processSplit(node, child, params, index) {
- let layout = node.layout;
- let nodeRect = node.rect;
- let nodeWidth;
- let nodeHeight;
- let nodeX;
- let nodeY;
- if (layout === LAYOUT_TYPES.HSPLIT) {
- // Divide the parent container's width
- // depending on number of children. And use this
- // to setup each child window's width.
- nodeWidth = params.sizes[index];
- nodeHeight = nodeRect.height;
- nodeX = nodeRect.x;
- if (index != 0) {
- let i = 1;
- while (i <= index) {
- nodeX += params.sizes[i - 1];
- i++;
- }
- }
- nodeY = nodeRect.y;
- } else if (layout === LAYOUT_TYPES.VSPLIT) {
- // split vertically
- // Conversely for vertical split, divide the parent container's height
- // depending on number of children. And use this
- // to setup each child window's height.
- nodeWidth = nodeRect.width;
- nodeHeight = params.sizes[index];
- nodeX = nodeRect.x;
- nodeY = nodeRect.y;
- if (index != 0) {
- let i = 1;
- while (i <= index) {
- nodeY += params.sizes[i - 1];
- i++;
- }
- }
- }
- child.rect = {
- x: nodeX,
- y: nodeY,
- width: nodeWidth,
- height: nodeHeight,
- };
- }
- /**
- * Process the child node here for the dimensions of the child stack/window,
- * It will be moved to the Node class in the future as Node.render()
- *
- */
- processStacked(node, child, params, index) {
- let layout = node.layout;
- let nodeWidth = node.rect.width;
- let nodeHeight = node.rect.height;
- let nodeX = node.rect.x;
- let nodeY = node.rect.y;
- let stackHeight = this.defaultStackHeight;
- if (layout === LAYOUT_TYPES.STACKED) {
- if (node.childNodes.length > 1) {
- nodeY += stackHeight * index;
- nodeHeight -= stackHeight * index;
- }
- child.rect = {
- x: nodeX,
- y: nodeY,
- width: nodeWidth,
- height: nodeHeight,
- };
- }
- }
- /**
- * Process the child node here for the dimensions of the child tab/window,
- * It will be moved to the Node class in the future as Node.render()
- *
- */
- processTabbed(node, child, params, _index) {
- let layout = node.layout;
- let nodeRect = node.rect;
- let nodeWidth;
- let nodeHeight;
- let nodeX;
- let nodeY;
- if (layout === LAYOUT_TYPES.TABBED) {
- nodeWidth = nodeRect.width;
- nodeX = nodeRect.x;
- nodeY = nodeRect.y;
- nodeHeight = nodeRect.height;
- let alwaysShowDecorationTab = true;
- if (node.childNodes.length > 1 || alwaysShowDecorationTab) {
- nodeY = nodeRect.y + params.stackedHeight;
- nodeHeight = nodeRect.height - params.stackedHeight;
- if (node.decoration && child.isWindow()) {
- let gap = this.extWm.calculateGaps(node);
- let renderRect = this.processGap(node);
- let borderWidth = child.actor.border.get_theme_node().get_border_width(St.Side.TOP);
- // Make adjustments to the gaps
- let adjust = 4 * Utils.dpi();
- let adjustWidth = renderRect.width + (borderWidth * 2 + gap) / adjust;
- let adjustX = renderRect.x - (gap + borderWidth * 2) / (adjust * 2);
- let adjustY = renderRect.y - adjust;
- if (gap === 0) {
- adjustY = renderRect.y;
- nodeY = renderRect.y + params.stackedHeight + adjust / 4;
- }
- let decoration = node.decoration;
- if (decoration !== null && decoration !== undefined) {
- decoration.set_size(adjustWidth, params.stackedHeight);
- decoration.set_position(adjustX, adjustY);
- if (params.tiledChildren.length > 0 && params.stackedHeight !== 0) {
- decoration.show();
- } else {
- decoration.hide();
- }
- if (!decoration.contains(child.tab)) decoration.add(child.tab);
- }
- child.render();
- }
- }
- child.rect = {
- x: nodeX,
- y: nodeY,
- width: nodeWidth,
- height: nodeHeight,
- };
- }
- }
- computeSizes(node, childItems) {
- let sizes = [];
- let orientation = Utils.orientationFromLayout(node.layout);
- let totalSize =
- orientation === ORIENTATION_TYPES.HORIZONTAL ? node.rect.width : node.rect.height;
- let grabTiled = node.getNodeByMode(Window.WINDOW_MODES.GRAB_TILE).length > 0;
- childItems.forEach((childNode, index) => {
- let percent =
- childNode.percent && childNode.percent > 0.0 && !grabTiled
- ? childNode.percent
- : 1.0 / childItems.length;
- sizes[index] = Math.floor(percent * totalSize);
- });
- // TODO - make sure the totalSize = the sizes total
- return sizes;
- }
- findFirstNodeWindowFrom(node) {
- let results = node.getNodeByType(NODE_TYPES.WINDOW);
- if (results.length > 0) {
- return results[0];
- }
- return null;
- }
- resetSiblingPercent(parentNode) {
- if (!parentNode) return;
- let children = parentNode.childNodes;
- children.forEach((n) => {
- n.percent = 0.0;
- });
- }
- debugTree() {
- this.debugNode(this);
- }
- debugNode(node) {
- let spacing = "";
- let dashes = "-->";
- let level = node.level;
- for (let i = 0; i < level; i++) {
- let parentSpacing = i === 0 ? " " : "|";
- spacing += `${parentSpacing} `;
- }
- let rootSpacing = level === 0 ? "#" : "*";
- let attributes = "";
- if (node.isWindow()) {
- let metaWindow = node.nodeValue;
- attributes += `class:'${metaWindow.get_wm_class()}',title:'${
- metaWindow.title
- }',string:'${metaWindow}'${metaWindow === this.extWm.focusMetaWindow ? " FOCUS" : ""}`;
- } else if (node.isCon() || node.isMonitor() || node.isWorkspace()) {
- attributes += `${node.nodeValue}`;
- if (node.isCon() || node.isMonitor()) {
- attributes += `,layout:${node.layout}`;
- }
- }
- if (node.rect) {
- attributes += `,rect:${node.rect.width}x${node.rect.height}+${node.rect.x}+${node.rect.y}`;
- }
- if (level !== 0) Logger.debug(`${spacing}|`);
- Logger.debug(
- `${spacing}${rootSpacing}${dashes} ${node.nodeType}#${
- node.index !== null ? node.index : "-"
- } @${attributes}`
- );
- node.childNodes.forEach((child) => {
- this.debugNode(child);
- });
- }
- findParent(childNode, parentNodeType) {
- let parents = this.getNodeByType(parentNodeType);
- // Only get the first parent
- return parents.filter((p) => p.contains(childNode))[0];
- }
- }
|