123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634 |
- /*
- * 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 GLib from "gi://GLib";
- import Clutter from "gi://Clutter";
- import GObject from "gi://GObject";
- import Meta from "gi://Meta";
- import St from "gi://St";
- // Gnome Shell imports
- import { gettext as _ } from "resource:///org/gnome/shell/extensions/extension.js";
- import * as Main from "resource:///org/gnome/shell/ui/main.js";
- import { PACKAGE_VERSION } from "resource:///org/gnome/shell/misc/config.js";
- // Shared state
- import { Logger } from "../shared/logger.js";
- // App imports
- import * as Utils from "./utils.js";
- import { Keybindings } from "./keybindings.js";
- import {
- Tree,
- Queue,
- Node,
- POSITION,
- LAYOUT_TYPES,
- ORIENTATION_TYPES,
- NODE_TYPES,
- } from "./tree.js";
- import { production } from "../shared/settings.js";
- /** @typedef {import('../../extension.js').default} ForgeExtension */
- export const WINDOW_MODES = Utils.createEnum(["FLOAT", "TILE", "GRAB_TILE", "DEFAULT"]);
- // Simplify the grab modes
- export const GRAB_TYPES = Utils.createEnum(["RESIZING", "MOVING", "UNKNOWN"]);
- export class WindowManager extends GObject.Object {
- static {
- GObject.registerClass(this);
- }
- /** @type {ForgeExtension} */
- ext;
- /** @param {ForgeExtension} ext */
- constructor(ext) {
- super();
- this.ext = ext;
- this.prefsTitle = `Forge ${_("Settings")} - ${
- !production ? "DEV" : `${PACKAGE_VERSION}-${ext.metadata.version}`
- }`;
- this.windowProps = this.ext.configMgr.windowProps;
- this.windowProps.overrides = this.windowProps.overrides.filter((override) => !override.wmId);
- this._kbd = this.ext.keybindings;
- this._tree = new Tree(this);
- this.eventQueue = new Queue();
- this.theme = this.ext.theme;
- this.lastFocusedWindow = null;
- Logger.info("forge initialized");
- }
- addFloatOverride(metaWindow, withWmId) {
- let overrides = this.windowProps.overrides;
- let wmClass = metaWindow.get_wm_class();
- let wmId = metaWindow.get_id();
- for (let override in overrides) {
- // if the window is already floating
- if (override.wmClass === wmClass && override.mode === "float" && !override.wmTitle) return;
- }
- overrides.push({
- wmClass: wmClass,
- wmId: withWmId ? wmId : undefined,
- mode: "float",
- });
- this.windowProps.overrides = overrides;
- this.ext.configMgr.windowProps = this.windowProps;
- }
- removeFloatOverride(metaWindow, withWmId) {
- let overrides = this.windowProps.overrides;
- let wmClass = metaWindow.get_wm_class();
- let wmId = metaWindow.get_id();
- overrides = overrides.filter(
- (override) =>
- !(
- override.wmClass === wmClass &&
- // rules with a Title are written by the user and peristent
- !override.wmTitle &&
- (!withWmId || override.wmId === wmId)
- )
- );
- this.windowProps.overrides = overrides;
- this.ext.configMgr.windowProps = this.windowProps;
- }
- toggleFloatingMode(action, metaWindow) {
- let nodeWindow = this.findNodeWindow(metaWindow);
- if (!nodeWindow || !(action || action.mode)) return;
- if (nodeWindow.nodeType !== NODE_TYPES.WINDOW) return;
- let withWmId = action.name === "FloatToggle";
- let floatingExempt = this.isFloatingExempt(metaWindow);
- if (floatingExempt) {
- this.removeFloatOverride(metaWindow, withWmId);
- if (!this.isActiveWindowWorkspaceTiled(metaWindow)) {
- nodeWindow.mode = WINDOW_MODES.FLOAT;
- } else {
- nodeWindow.mode = WINDOW_MODES.TILE;
- }
- } else {
- this.addFloatOverride(metaWindow, withWmId);
- nodeWindow.mode = WINDOW_MODES.FLOAT;
- }
- }
- queueEvent(eventObj, interval = 220) {
- this.eventQueue.enqueue(eventObj);
- if (!this._queueSourceId) {
- this._queueSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, interval, () => {
- const currEventObj = this.eventQueue.dequeue();
- if (currEventObj) {
- currEventObj.callback();
- }
- const result = this.eventQueue.length !== 0;
- if (!result) {
- this._queueSourceId = 0;
- }
- return result;
- });
- }
- }
- /**
- * This is the central place to bind all the non-window signals.
- */
- _bindSignals() {
- if (this._signalsBound) return;
- const display = global.display;
- const shellWm = global.window_manager;
- this._displaySignals = [
- display.connect("window-created", this.trackWindow.bind(this)),
- display.connect("grab-op-begin", this._handleGrabOpBegin.bind(this)),
- display.connect("window-entered-monitor", (_, monitor, metaWindow) => {
- this.updateMetaWorkspaceMonitor("window-entered-monitor", monitor, metaWindow);
- this.trackCurrentMonWs();
- }),
- display.connect("grab-op-end", this._handleGrabOpEnd.bind(this)),
- display.connect("showing-desktop-changed", () => {
- this.hideWindowBorders();
- this.updateDecorationLayout();
- }),
- display.connect("in-fullscreen-changed", () => {
- this.renderTree("full-screen-changed");
- }),
- display.connect("workareas-changed", (_display) => {
- if (global.display.get_n_monitors() == 0) {
- Logger.debug(`workareas-changed: no monitors, ignoring signal`);
- return;
- }
- if (this.tree.getNodeByType("WINDOW").length > 0) {
- let workspaceReload = this.workspaceAdded || this.workspaceRemoved;
- if (workspaceReload) {
- this.trackCurrentWindows();
- this.workspaceRemoved = false;
- this.workspaceAdded = false;
- } else {
- this.renderTree("workareas-changed");
- }
- }
- }),
- ];
- this._windowManagerSignals = [
- shellWm.connect("minimize", () => {
- this.hideWindowBorders();
- let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- if (focusNodeWindow) {
- if (this.tree.getTiledChildren(focusNodeWindow.parentNode.childNodes).length === 0) {
- this.tree.resetSiblingPercent(focusNodeWindow.parentNode.parentNode);
- }
- this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
- }
- let prevFrozen = this._freezeRender;
- if (prevFrozen) this.unfreezeRender();
- this.renderTree("minimize");
- if (prevFrozen) this.freezeRender();
- }),
- shellWm.connect("unminimize", () => {
- let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- if (focusNodeWindow) {
- this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
- }
- let prevFrozen = this._freezeRender;
- if (prevFrozen) this.unfreezeRender();
- this.renderTree("unminimize");
- if (prevFrozen) this.freezeRender();
- }),
- shellWm.connect("show-tile-preview", (_, _metaWindow, _rect, _num) => {
- // Empty
- }),
- ];
- const globalWsm = global.workspace_manager;
- this._workspaceManagerSignals = [
- globalWsm.connect("showing-desktop-changed", () => {
- this.hideWindowBorders();
- this.updateDecorationLayout();
- }),
- globalWsm.connect("workspace-added", (_, wsIndex) => {
- this.tree.addWorkspace(wsIndex);
- this.trackCurrentMonWs();
- this.workspaceAdded = true;
- this.renderTree("workspace-added");
- }),
- globalWsm.connect("workspace-removed", (_, wsIndex) => {
- this.tree.removeWorkspace(wsIndex);
- this.trackCurrentMonWs();
- this.workspaceRemoved = true;
- this.updateDecorationLayout();
- this.renderTree("workspace-removed");
- }),
- globalWsm.connect("active-workspace-changed", () => {
- this.hideWindowBorders();
- this.trackCurrentMonWs();
- this.updateDecorationLayout();
- this.renderTree("active-workspace-changed");
- }),
- ];
- let numberOfWorkspaces = globalWsm.get_n_workspaces();
- for (let i = 0; i < numberOfWorkspaces; i++) {
- let workspace = globalWsm.get_workspace_by_index(i);
- this.bindWorkspaceSignals(workspace);
- }
- let settings = this.ext.settings;
- settings.connect("changed", (_, settingName) => {
- switch (settingName) {
- case "focus-border-toggle":
- this.renderTree(settingName);
- break;
- case "tiling-mode-enabled":
- this.renderTree(settingName);
- break;
- case "window-gap-size-increment":
- case "window-gap-size":
- case "window-gap-hidden-on-single":
- case "workspace-skip-tile":
- this.renderTree(settingName, true);
- break;
- case "stacked-tiling-mode-enabled":
- if (!settings.get_boolean(settingName)) {
- let stackedNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.STACKED);
- stackedNodes.forEach((node) => {
- node.prevLayout = node.layout;
- node.layout = this.determineSplitLayout();
- });
- } else {
- let hSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.HSPLIT);
- let vSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.VSPLIT);
- Array.prototype.push.apply(hSplitNodes, vSplitNodes);
- hSplitNodes.forEach((node) => {
- if (node.prevLayout && node.prevLayout === LAYOUT_TYPES.STACKED) {
- node.layout = LAYOUT_TYPES.STACKED;
- }
- });
- }
- this.renderTree(settingName);
- break;
- case "tabbed-tiling-mode-enabled":
- if (!settings.get_boolean(settingName)) {
- let tabbedNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.TABBED);
- tabbedNodes.forEach((node) => {
- node.prevLayout = node.layout;
- node.layout = this.determineSplitLayout();
- });
- } else {
- let hSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.HSPLIT);
- let vSplitNodes = this.tree.getNodeByLayout(LAYOUT_TYPES.VSPLIT);
- Array.prototype.push.apply(hSplitNodes, vSplitNodes);
- hSplitNodes.forEach((node) => {
- if (node.prevLayout && node.prevLayout === LAYOUT_TYPES.TABBED) {
- node.layout = LAYOUT_TYPES.TABBED;
- }
- });
- }
- this.renderTree(settingName);
- break;
- case "css-updated":
- this.theme.reloadStylesheet();
- break;
- case "float-always-on-top-enabled":
- if (!settings.get_boolean(settingName)) {
- this.cleanupAlwaysFloat();
- } else {
- this.restoreAlwaysFloat();
- }
- break;
- default:
- break;
- }
- });
- this._overviewSignals = [
- Main.overview.connect("hiding", () => {
- this.fromOverview = true;
- const eventObj = {
- name: "focus-after-overview",
- callback: () => {
- const focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- this.updateStackedFocus(focusNodeWindow);
- this.updateTabbedFocus(focusNodeWindow);
- this.movePointerWith(focusNodeWindow);
- },
- };
- this.queueEvent(eventObj);
- }),
- Main.overview.connect("showing", () => {
- this.toOverview = true;
- }),
- ];
- this._signalsBound = true;
- }
- cleanupAlwaysFloat() {
- // remove the setting for each node window
- this.allNodeWindows.forEach((w) => {
- if (w.mode === WINDOW_MODES.FLOAT) {
- w.nodeValue.is_above() && w.nodeValue.unmake_above();
- }
- });
- }
- restoreAlwaysFloat() {
- this.allNodeWindows.forEach((w) => {
- if (w.mode === WINDOW_MODES.FLOAT) {
- !w.nodeValue.is_above() && w.nodeValue.make_above();
- }
- });
- }
- trackCurrentMonWs() {
- let metaWindow = this.focusMetaWindow;
- if (!metaWindow) return;
- const currentMonitor = global.display.get_current_monitor();
- const currentWorkspace = global.display.get_workspace_manager().get_active_workspace_index();
- let currentMonWs = `mo${currentMonitor}ws${currentWorkspace}`;
- let activeMetaMonWs = `mo${metaWindow.get_monitor()}ws${metaWindow.get_workspace().index()}`;
- let currentWsNode = this.tree.findNode(`ws${currentWorkspace}`);
- if (!currentWsNode) {
- return;
- }
- // Search for all the valid windows on the workspace
- const monWindows = currentWsNode.getNodeByType(NODE_TYPES.WORKSPACE).flatMap((ws) => {
- return ws
- .getNodeByType(NODE_TYPES.WINDOW)
- .filter(
- (w) =>
- !w.nodeValue.minimized &&
- w.isTile() &&
- w.nodeValue !== metaWindow &&
- // The searched window should be on the same monitor workspace
- // This ensures that Forge already updated the workspace node tree:
- currentMonWs === activeMetaMonWs
- )
- .map((w) => w.nodeValue);
- });
- this.sortedWindows = global.display.sort_windows_by_stacking(monWindows).reverse();
- }
- // TODO move this to workspace.js
- bindWorkspaceSignals(metaWorkspace) {
- if (metaWorkspace) {
- if (!metaWorkspace.workspaceSignals) {
- let workspaceSignals = [
- metaWorkspace.connect("window-added", (_, metaWindow) => {
- if (!this._wsWindowAddSrcId) {
- this._wsWindowAddSrcId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
- this.updateMetaWorkspaceMonitor(
- "window-added",
- metaWindow.get_monitor(),
- metaWindow
- );
- this._wsWindowAddSrcId = 0;
- return false;
- });
- }
- }),
- ];
- metaWorkspace.workspaceSignals = workspaceSignals;
- }
- }
- }
- // TODO move this in command.js
- command(action) {
- let focusWindow = this.focusMetaWindow;
- // Do not check if the node window is null, some of the commands do not need the focus window
- let focusNodeWindow = this.findNodeWindow(focusWindow);
- let currentLayout;
- switch (action.name) {
- case "FloatNonPersistentToggle":
- case "FloatToggle":
- case "FloatClassToggle":
- this.toggleFloatingMode(action, focusWindow);
- const rectRequest = {
- x: action.x,
- y: action.y,
- width: action.width,
- height: action.height,
- };
- let moveRect = {
- x: Utils.resolveX(rectRequest, focusWindow),
- y: Utils.resolveY(rectRequest, focusWindow),
- width: Utils.resolveWidth(rectRequest, focusWindow),
- height: Utils.resolveHeight(rectRequest, focusWindow),
- };
- this.move(focusWindow, moveRect);
- let existParent = focusNodeWindow.parentNode;
- if (this.tree.getTiledChildren(existParent.childNodes).length <= 1) {
- existParent.percent = 0.0;
- this.tree.resetSiblingPercent(existParent.parentNode);
- }
- this.tree.resetSiblingPercent(existParent);
- this.renderTree("float-toggle", true);
- break;
- case "Move":
- this.unfreezeRender();
- let moveDirection = Utils.resolveDirection(action.direction);
- let prev = focusNodeWindow;
- let moved = this.tree.move(focusNodeWindow, moveDirection);
- if (!focusNodeWindow) {
- focusNodeWindow = this.findNodeWindow(this.focusMetaWindow);
- }
- this.queueEvent({
- name: "move",
- callback: () => {
- if (this.eventQueue.length <= 0) {
- this.unfreezeRender();
- if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.STACKED) {
- focusNodeWindow.parentNode.appendChild(focusNodeWindow);
- focusNodeWindow.nodeValue.raise();
- focusNodeWindow.nodeValue.activate(global.display.get_current_time());
- this.renderTree("move-stacked-queue");
- }
- if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.TABBED) {
- focusNodeWindow.nodeValue.raise();
- focusNodeWindow.nodeValue.activate(global.display.get_current_time());
- if (prev) prev.parentNode.lastTabFocus = prev.nodeValue;
- this.renderTree("move-tabbed-queue");
- }
- this.movePointerWith(focusNodeWindow);
- }
- },
- });
- if (moved) {
- if (prev) prev.parentNode.lastTabFocus = prev.nodeValue;
- this.renderTree("move-window");
- }
- break;
- case "Focus":
- let focusDirection = Utils.resolveDirection(action.direction);
- focusNodeWindow = this.tree.focus(focusNodeWindow, focusDirection);
- if (!focusNodeWindow) {
- focusNodeWindow = this.findNodeWindow(this.focusMetaWindow);
- }
- break;
- case "Swap":
- if (!focusNodeWindow) return;
- this.unfreezeRender();
- let swapDirection = Utils.resolveDirection(action.direction);
- this.tree.swap(focusNodeWindow, swapDirection);
- focusNodeWindow.nodeValue.raise();
- this.updateTabbedFocus(focusNodeWindow);
- this.updateStackedFocus(focusNodeWindow);
- this.movePointerWith(focusNodeWindow);
- this.renderTree("swap", true);
- break;
- case "Split":
- if (!focusNodeWindow) return;
- currentLayout = focusNodeWindow.parentNode.layout;
- if (currentLayout === LAYOUT_TYPES.STACKED || currentLayout === LAYOUT_TYPES.TABBED) {
- return;
- }
- let orientation = action.orientation
- ? action.orientation.toUpperCase()
- : ORIENTATION_TYPES.NONE;
- this.tree.split(focusNodeWindow, orientation);
- this.renderTree("split");
- break;
- case "LayoutToggle":
- if (!focusNodeWindow) return;
- currentLayout = focusNodeWindow.parentNode.layout;
- if (currentLayout === LAYOUT_TYPES.HSPLIT) {
- focusNodeWindow.parentNode.layout = LAYOUT_TYPES.VSPLIT;
- } else if (currentLayout === LAYOUT_TYPES.VSPLIT) {
- focusNodeWindow.parentNode.layout = LAYOUT_TYPES.HSPLIT;
- }
- this.tree.attachNode = focusNodeWindow.parentNode;
- this.renderTree("layout-split-toggle");
- break;
- case "FocusBorderToggle":
- let focusBorderEnabled = this.ext.settings.get_boolean("focus-border-toggle");
- this.ext.settings.set_boolean("focus-border-toggle", !focusBorderEnabled);
- break;
- case "TilingModeToggle":
- // FIXME, not sure if this toggle is still needed from a use case
- // perspective, since Extension.disable also should do the same thing.
- let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
- this.ext.settings.set_boolean("tiling-mode-enabled", !tilingModeEnabled);
- if (tilingModeEnabled) {
- this.floatAllWindows();
- } else {
- this.unfloatAllWindows();
- }
- this.renderTree(`tiling-mode-toggle ${!tilingModeEnabled}`);
- break;
- case "GapSize":
- let gapIncrement = this.ext.settings.get_uint("window-gap-size-increment");
- let amount = action.amount;
- gapIncrement = gapIncrement + amount;
- if (gapIncrement < 0) gapIncrement = 0;
- if (gapIncrement > 8) gapIncrement = 8;
- this.ext.settings.set_uint("window-gap-size-increment", gapIncrement);
- break;
- case "WorkspaceActiveTileToggle":
- let activeWorkspace = global.workspace_manager.get_active_workspace_index();
- let skippedWorkspaces = this.ext.settings.get_string("workspace-skip-tile");
- let workspaceSkipped = false;
- let skippedArr = [];
- if (skippedWorkspaces.length === 0) {
- skippedArr.push(`${activeWorkspace}`);
- this.floatWorkspace(activeWorkspace);
- } else {
- skippedArr = skippedWorkspaces.split(",");
- for (let i = 0; i < skippedArr.length; i++) {
- if (`${skippedArr[i]}` === `${activeWorkspace}`) {
- workspaceSkipped = true;
- break;
- }
- }
- if (workspaceSkipped) {
- // tile this workspace
- let indexWs = skippedArr.indexOf(`${activeWorkspace}`);
- skippedArr.splice(indexWs, 1);
- this.unfloatWorkspace(activeWorkspace);
- } else {
- // skip tiling workspace
- skippedArr.push(`${activeWorkspace}`);
- this.floatWorkspace(activeWorkspace);
- }
- }
- this.ext.settings.set_string("workspace-skip-tile", skippedArr.toString());
- this.renderTree("workspace-toggle");
- break;
- case "LayoutStackedToggle":
- if (!focusNodeWindow) return;
- if (!this.ext.settings.get_boolean("stacked-tiling-mode-enabled")) return;
- if (focusNodeWindow.parentNode.isMonitor()) {
- this.tree.split(focusNodeWindow, ORIENTATION_TYPES.HORIZONTAL, true);
- }
- currentLayout = focusNodeWindow.parentNode.layout;
- if (currentLayout === LAYOUT_TYPES.STACKED) {
- focusNodeWindow.parentNode.layout = this.determineSplitLayout();
- this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
- } else {
- if (currentLayout === LAYOUT_TYPES.TABBED) {
- focusNodeWindow.parentNode.lastTabFocus = null;
- }
- focusNodeWindow.parentNode.layout = LAYOUT_TYPES.STACKED;
- let lastChild = focusNodeWindow.parentNode.lastChild;
- if (lastChild.nodeType === NODE_TYPES.WINDOW) {
- lastChild.nodeValue.activate(global.display.get_current_time());
- }
- }
- this.unfreezeRender();
- this.tree.attachNode = focusNodeWindow.parentNode;
- this.renderTree("layout-stacked-toggle");
- break;
- case "LayoutTabbedToggle":
- if (!focusNodeWindow) return;
- if (!this.ext.settings.get_boolean("tabbed-tiling-mode-enabled")) return;
- if (focusNodeWindow.parentNode.isMonitor()) {
- this.tree.split(focusNodeWindow, ORIENTATION_TYPES.HORIZONTAL, true);
- }
- currentLayout = focusNodeWindow.parentNode.layout;
- if (currentLayout === LAYOUT_TYPES.TABBED) {
- focusNodeWindow.parentNode.layout = this.determineSplitLayout();
- this.tree.resetSiblingPercent(focusNodeWindow.parentNode);
- focusNodeWindow.parentNode.lastTabFocus = null;
- } else {
- focusNodeWindow.parentNode.layout = LAYOUT_TYPES.TABBED;
- focusNodeWindow.parentNode.lastTabFocus = focusNodeWindow.nodeValue;
- }
- this.unfreezeRender();
- this.tree.attachNode = focusNodeWindow.parentNode;
- this.renderTree("layout-tabbed-toggle");
- break;
- case "CancelOperation":
- if (focusNodeWindow.mode === WINDOW_MODES.GRAB_TILE) {
- this.cancelGrab = true;
- }
- break;
- case "PrefsOpen":
- let existWindow = Utils.findWindowWith(this.prefsTitle);
- if (existWindow && existWindow.get_workspace()) {
- existWindow
- .get_workspace()
- .activate_with_focus(existWindow, global.display.get_current_time());
- this.moveCenter(existWindow);
- } else {
- this.ext.openPreferences();
- }
- break;
- case "WindowSwapLastActive":
- if (focusNodeWindow) {
- let lastActiveWindow = global.display.get_tab_next(
- Meta.TabList.NORMAL,
- global.display.get_workspace_manager().get_active_workspace(),
- focusNodeWindow.nodeValue,
- false
- );
- let lastActiveNodeWindow = this.tree.findNode(lastActiveWindow);
- this.tree.swapPairs(lastActiveNodeWindow, focusNodeWindow);
- this.movePointerWith(focusNodeWindow);
- this.renderTree("swap-last-active");
- }
- break;
- case "SnapLayoutMove":
- if (focusNodeWindow) {
- let workareaRect = focusNodeWindow.nodeValue.get_work_area_current_monitor();
- let layoutAmount = action.amount;
- let layoutDirection = action.direction.toUpperCase();
- let layout = {};
- let processGap = false;
- switch (layoutDirection) {
- case "LEFT":
- layout.width = layoutAmount * workareaRect.width;
- layout.height = workareaRect.height;
- layout.x = workareaRect.x;
- layout.y = workareaRect.y;
- processGap = true;
- break;
- case "RIGHT":
- layout.width = layoutAmount * workareaRect.width;
- layout.height = workareaRect.height;
- layout.x = workareaRect.x + (workareaRect.width - layout.width);
- layout.y = workareaRect.y;
- processGap = true;
- break;
- case "CENTER":
- let metaRect = this.focusMetaWindow.get_frame_rect();
- layout.x = "center";
- layout.y = "center";
- layout = {
- x: Utils.resolveX(layout, this.focusMetaWindow),
- y: Utils.resolveY(layout, this.focusMetaWindow),
- width: metaRect.width,
- height: metaRect.height,
- };
- break;
- default:
- break;
- }
- focusNodeWindow.rect = layout;
- if (processGap) {
- focusNodeWindow.rect = this.tree.processGap(focusNodeWindow);
- }
- if (!focusNodeWindow.isFloat()) {
- this.addFloatOverride(focusNodeWindow.nodeValue, false);
- }
- this.move(focusNodeWindow.nodeValue, focusNodeWindow.rect);
- this.queueEvent({
- name: "snap-layout-move",
- callback: () => {
- this.renderTree("snap-layout-move");
- },
- });
- break;
- }
- case "ShowTabDecorationToggle":
- if (!focusNodeWindow) return;
- if (!this.ext.settings.get_boolean("tabbed-tiling-mode-enabled")) return;
- let showTabs = this.ext.settings.get_boolean("showtab-decoration-enabled");
- this.ext.settings.set_boolean("showtab-decoration-enabled", !showTabs);
- this.unfreezeRender();
- this.tree.attachNode = focusNodeWindow.parentNode;
- this.renderTree("showtab-decoration-enabled");
- break;
- case "WindowResizeRight":
- this.resize(Meta.GrabOp.KEYBOARD_RESIZING_E, action.amount);
- break;
- case "WindowResizeLeft":
- this.resize(Meta.GrabOp.KEYBOARD_RESIZING_W, action.amount);
- break;
- case "WindowResizeTop":
- this.resize(Meta.GrabOp.KEYBOARD_RESIZING_N, action.amount);
- break;
- case "WindowResizeBottom":
- this.resize(Meta.GrabOp.KEYBOARD_RESIZING_S, action.amount);
- break;
- default:
- break;
- }
- }
- resize(grabOp, amount) {
- let metaWindow = this.focusMetaWindow;
- let display = global.display;
- this._handleGrabOpBegin(display, metaWindow, grabOp);
- let rect = metaWindow.get_frame_rect();
- let direction = Utils.directionFromGrab(grabOp);
- switch (direction) {
- case Meta.MotionDirection.RIGHT:
- rect.width = rect.width + amount;
- break;
- case Meta.MotionDirection.LEFT:
- rect.width = rect.width + amount;
- rect.x = rect.x - amount;
- break;
- case Meta.MotionDirection.UP:
- rect.height = rect.height + amount;
- break;
- case Meta.MotionDirection.DOWN:
- rect.height = rect.height + amount;
- rect.y = rect.y - amount;
- break;
- }
- this.move(metaWindow, rect);
- this.queueEvent(
- {
- name: "manual-resize",
- callback: () => {
- if (this.eventQueue.length === 0) {
- this._handleGrabOpEnd(display, metaWindow, grabOp);
- }
- },
- },
- 50
- );
- }
- disable() {
- Utils._disableDecorations();
- this._removeSignals();
- this.disabled = true;
- Logger.debug(`extension:disable`);
- }
- enable() {
- this._bindSignals();
- this.reloadTree("enable");
- Logger.debug(`extension:enable`);
- }
- findNodeWindow(metaWindow) {
- return this.tree.findNode(metaWindow);
- }
- get focusMetaWindow() {
- return global.display.get_focus_window();
- }
- get tree() {
- if (!this._tree) {
- this._tree = new Tree(this);
- }
- return this._tree;
- }
- get kbd() {
- if (!this._kbd) {
- this._kbd = new Keybindings(this.ext);
- this.ext.keybindings = this._kbd;
- }
- return this._kbd;
- }
- get windowsActiveWorkspace() {
- let wsManager = global.workspace_manager;
- return global.display.get_tab_list(Meta.TabList.NORMAL_ALL, wsManager.get_active_workspace());
- }
- get windowsAllWorkspaces() {
- let wsManager = global.workspace_manager;
- let windowsAll = [];
- for (let i = 0; i < wsManager.get_n_workspaces(); i++) {
- Array.prototype.push.apply(
- windowsAll,
- global.display.get_tab_list(Meta.TabList.NORMAL_ALL, wsManager.get_workspace_by_index(i))
- );
- }
- windowsAll.sort((w1, w2) => {
- return w1.get_stable_sequence() - w2.get_stable_sequence();
- });
- return windowsAll;
- }
- getWindowsOnWorkspace(workspaceIndex) {
- const workspaceNode = this.tree.findNode(`ws${workspaceIndex}`);
- const workspaceWindows = workspaceNode.getNodeByType(NODE_TYPES.WINDOW);
- return workspaceWindows;
- }
- determineSplitLayout() {
- // if the monitor width is less than height, the monitor could be vertical orientation;
- let monitorRect = global.display.get_monitor_geometry(global.display.get_current_monitor());
- if (monitorRect.width < monitorRect.height) {
- return LAYOUT_TYPES.VSPLIT;
- }
- return LAYOUT_TYPES.HSPLIT;
- }
- floatWorkspace(workspaceIndex) {
- const workspaceWindows = this.getWindowsOnWorkspace(workspaceIndex);
- if (!workspaceWindows) return;
- workspaceWindows.forEach((w) => {
- w.float = true;
- });
- }
- unfloatWorkspace(workspaceIndex) {
- const workspaceWindows = this.getWindowsOnWorkspace(workspaceIndex);
- if (!workspaceWindows) return;
- workspaceWindows.forEach((w) => {
- w.tile = true;
- });
- }
- hideWindowBorders() {
- this.tree.nodeWindows.forEach((nodeWindow) => {
- let actor = nodeWindow.windowActor;
- if (actor) {
- if (actor.border) {
- actor.border.hide();
- }
- if (actor.splitBorder) {
- actor.splitBorder.hide();
- }
- }
- if (nodeWindow.parentNode.isTabbed()) {
- if (nodeWindow.tab) {
- // TODO: review the cleanup of the tab:St.Widget variable
- try {
- nodeWindow.tab.remove_style_class_name("window-tabbed-tab-active");
- } catch (e) {
- // Logger.warn(e);
- }
- }
- }
- });
- }
- // Window movement API
- move(metaWindow, rect) {
- if (!metaWindow) return;
- if (metaWindow.grabbed) return;
- metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL);
- metaWindow.unmaximize(Meta.MaximizeFlags.VERTICAL);
- metaWindow.unmaximize(Meta.MaximizeFlags.BOTH);
- let windowActor = metaWindow.get_compositor_private();
- if (!windowActor) return;
- windowActor.remove_all_transitions();
- metaWindow.move_frame(true, rect.x, rect.y);
- metaWindow.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height);
- }
- moveCenter(metaWindow) {
- if (!metaWindow) return;
- let frameRect = metaWindow.get_frame_rect();
- const rectRequest = {
- x: "center",
- y: "center",
- width: frameRect.width,
- height: frameRect.height,
- };
- let moveRect = {
- x: Utils.resolveX(rectRequest, metaWindow),
- y: Utils.resolveY(rectRequest, metaWindow),
- width: Utils.resolveWidth(rectRequest, metaWindow),
- height: Utils.resolveHeight(rectRequest, metaWindow),
- };
- this.move(metaWindow, moveRect);
- }
- rectForMonitor(node, targetMonitor) {
- if (!node || (node && node.nodeType !== NODE_TYPES.WINDOW)) return null;
- if (targetMonitor < 0) return null;
- let currentWorkArea = node.nodeValue.get_work_area_current_monitor();
- let nextWorkArea = node.nodeValue.get_work_area_for_monitor(targetMonitor);
- if (currentWorkArea && nextWorkArea) {
- let rect = node.rect;
- if (!rect && node.mode === WINDOW_MODES.FLOAT) {
- rect = node.nodeValue.get_frame_rect();
- }
- let hRatio = 1;
- let wRatio = 1;
- hRatio = nextWorkArea.height / currentWorkArea.height;
- wRatio = nextWorkArea.width / currentWorkArea.width;
- rect.height *= hRatio;
- rect.width *= wRatio;
- if (nextWorkArea.y < currentWorkArea.y) {
- rect.y =
- ((nextWorkArea.y + rect.y - currentWorkArea.y) / currentWorkArea.height) *
- nextWorkArea.height;
- } else if (nextWorkArea.y > currentWorkArea.y) {
- rect.y = (rect.y / currentWorkArea.height) * nextWorkArea.height + nextWorkArea.y;
- }
- if (nextWorkArea.x < currentWorkArea.x) {
- rect.x =
- ((nextWorkArea.x + rect.x - currentWorkArea.x) / currentWorkArea.width) *
- nextWorkArea.width;
- } else if (nextWorkArea.x > currentWorkArea.x) {
- rect.x = (rect.x / currentWorkArea.width) * nextWorkArea.width + nextWorkArea.x;
- }
- return rect;
- }
- return null;
- }
- _removeSignals() {
- if (!this._signalsBound) return;
- if (this._displaySignals) {
- for (const displaySignal of this._displaySignals) {
- global.display.disconnect(displaySignal);
- }
- this._displaySignals.length = 0;
- this._displaySignals = undefined;
- }
- if (this._windowManagerSignals) {
- for (const windowManagerSignal of this._windowManagerSignals) {
- global.window_manager.disconnect(windowManagerSignal);
- }
- this._windowManagerSignals.length = 0;
- this._windowManagerSignals = undefined;
- }
- const globalWsm = global.workspace_manager;
- if (this._workspaceManagerSignals) {
- for (const workspaceManagerSignal of this._workspaceManagerSignals) {
- globalWsm.disconnect(workspaceManagerSignal);
- }
- this._workspaceManagerSignals.length = 0;
- this._workspaceManagerSignals = undefined;
- }
- let numberOfWorkspaces = globalWsm.get_n_workspaces();
- for (let i = 0; i < numberOfWorkspaces; i++) {
- let workspace = globalWsm.get_workspace_by_index(i);
- if (workspace.workspaceSignals) {
- for (const workspaceSignal of workspace.workspaceSignals) {
- workspace.disconnect(workspaceSignal);
- }
- workspace.workspaceSignals.length = 0;
- workspace.workspaceSignals = undefined;
- }
- }
- let allWindows = this.windowsAllWorkspaces;
- if (allWindows) {
- for (let metaWindow of allWindows) {
- if (metaWindow.windowSignals !== undefined) {
- for (const windowSignal of metaWindow.windowSignals) {
- metaWindow.disconnect(windowSignal);
- }
- metaWindow.windowSignals.length = 0;
- metaWindow.windowSignals = undefined;
- }
- let windowActor = metaWindow.get_compositor_private();
- if (windowActor && windowActor.actorSignals) {
- for (const actorSignal of windowActor.actorSignals) {
- windowActor.disconnect(actorSignal);
- }
- windowActor.actorSignals.length = 0;
- windowActor.actorSignals = undefined;
- }
- if (windowActor && windowActor.border) {
- windowActor.border.hide();
- if (global.window_group) {
- global.window_group.remove_child(windowActor.border);
- }
- windowActor.border = undefined;
- }
- if (windowActor && windowActor.splitBorder) {
- windowActor.splitBorder.hide();
- if (global.window_group) {
- global.window_group.remove_child(windowActor.splitBorder);
- }
- windowActor.splitBorder = undefined;
- }
- }
- }
- if (this._renderTreeSrcId) {
- GLib.Source.remove(this._renderTreeSrcId);
- this._renderTreeSrcId = 0;
- }
- if (this._reloadTreeSrcId) {
- GLib.Source.remove(this._reloadTreeSrcId);
- this._reloadTreeSrcId = 0;
- }
- if (this._wsWindowAddSrcId) {
- GLib.Source.remove(this._wsWindowAddSrcId);
- this._wsWindowAddSrcId = 0;
- }
- if (this._queueSourceId) {
- GLib.Source.remove(this._queueSourceId);
- this._queueSourceId = 0;
- }
- if (this._prefsOpenSrcId) {
- GLib.Source.remove(this._prefsOpenSrcId);
- this._prefsOpenSrcId = 0;
- }
- if (this._overviewSignals) {
- for (const overviewSignal of this._overviewSignals) {
- Main.overview.disconnect(overviewSignal);
- }
- this._overviewSignals.length = 0;
- this._overviewSignals = null;
- }
- this._signalsBound = false;
- }
- renderTree(from, force = false) {
- let wasFrozen = this._freezeRender;
- if (force && wasFrozen) this.unfreezeRender();
- if (this._freezeRender || !this.ext.settings.get_boolean("tiling-mode-enabled")) {
- this.updateDecorationLayout();
- this.updateBorderLayout();
- } else {
- if (!this._renderTreeSrcId) {
- this._renderTreeSrcId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
- this.processFloats();
- this.tree.render(from);
- this._renderTreeSrcId = 0;
- this.updateDecorationLayout();
- this.updateBorderLayout();
- if (wasFrozen) this.freezeRender();
- return false;
- });
- }
- }
- }
- processFloats() {
- this.allNodeWindows.forEach((nodeWindow) => {
- let metaWindow = nodeWindow.nodeValue;
- if (this.isFloatingExempt(metaWindow) || !this.isActiveWindowWorkspaceTiled(metaWindow)) {
- nodeWindow.float = true;
- } else {
- nodeWindow.float = false;
- }
- });
- }
- get allNodeWindows() {
- return this.tree.getNodeByType(NODE_TYPES.WINDOW);
- }
- /**
- * Reloads the tree. This is an expensive operation.
- * Useful when using dynamic workspaces in GNOME-shell.
- *
- * TODO: add support to reload the tree from a JSON dump file.
- * TODO: move this to tree.js
- */
- reloadTree(from) {
- if (!this._reloadTreeSrcId) {
- this._reloadTreeSrcId = GLib.idle_add(GLib.PRIORITY_LOW, () => {
- Utils._disableDecorations();
- let treeWorkspaces = this.tree.nodeWorkpaces;
- let wsManager = global.workspace_manager;
- let globalWsNum = wsManager.get_n_workspaces();
- // empty out the root children nodes
- this.tree.childNodes.length = 0;
- this.tree.attachNode = undefined;
- // initialize the workspaces and monitors id strings
- this.tree._initWorkspaces();
- this.trackCurrentWindows();
- this.renderTree(from);
- this._reloadTreeSrcId = 0;
- return false;
- });
- }
- }
- sameParentMonitor(firstNode, secondNode) {
- if (!firstNode || !secondNode) return false;
- if (!firstNode.nodeValue || !secondNode.nodeValue) return false;
- if (!firstNode.nodeValue.get_workspace()) return false;
- if (!secondNode.nodeValue.get_workspace()) return false;
- let firstMonWs = `mo${firstNode.nodeValue.get_monitor()}ws${firstNode.nodeValue
- .get_workspace()
- .index()}`;
- let secondMonWs = `mo${secondNode.nodeValue.get_monitor()}ws${secondNode.nodeValue
- .get_workspace()
- .index()}`;
- return firstMonWs === secondMonWs;
- }
- showWindowBorders() {
- let metaWindow = this.focusMetaWindow;
- if (!metaWindow) return;
- let windowActor = metaWindow.get_compositor_private();
- if (!windowActor) return;
- let nodeWindow = this.findNodeWindow(metaWindow);
- if (!nodeWindow) return;
- if (metaWindow.get_wm_class() === null) return;
- let borders = [];
- let focusBorderEnabled = this.ext.settings.get_boolean("focus-border-toggle");
- let splitBorderEnabled = this.ext.settings.get_boolean("split-border-toggle");
- let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
- let gap = this.calculateGaps(nodeWindow);
- let maximized = () => {
- return metaWindow.get_maximized() === 3 || metaWindow.is_fullscreen() || gap === 0;
- };
- let monitorCount = global.display.get_n_monitors();
- let tiledChildren = this.tree.getTiledChildren(nodeWindow.parentNode.childNodes);
- let inset = 3;
- let parentNode = nodeWindow.parentNode;
- const floatingWindow = nodeWindow.isFloat();
- const tiledBorder = windowActor.border;
- if (parentNode.isTabbed()) {
- if (nodeWindow.tab) {
- nodeWindow.tab.add_style_class_name("window-tabbed-tab-active");
- }
- }
- if (tiledBorder && focusBorderEnabled) {
- if (
- !maximized() ||
- (gap === 0 && tiledChildren.length === 1 && monitorCount > 1) ||
- (gap === 0 && tiledChildren.length > 1)
- ) {
- if (tilingModeEnabled) {
- if (parentNode.isStacked()) {
- if (!floatingWindow) {
- tiledBorder.set_style_class_name("window-stacked-border");
- } else {
- tiledBorder.set_style_class_name("window-floated-border");
- }
- } else if (parentNode.isTabbed()) {
- if (!floatingWindow) {
- tiledBorder.set_style_class_name("window-tabbed-border");
- if (nodeWindow.backgroundTab) {
- tiledBorder.add_style_class_name("window-tabbed-bg");
- }
- } else {
- tiledBorder.set_style_class_name("window-floated-border");
- }
- } else {
- if (!floatingWindow) {
- tiledBorder.set_style_class_name("window-tiled-border");
- } else {
- tiledBorder.set_style_class_name("window-floated-border");
- }
- }
- } else {
- tiledBorder.set_style_class_name("window-floated-border");
- }
- borders.push(tiledBorder);
- }
- }
- if (gap === 0 || metaWindow.get_maximized() === 1 || metaWindow.get_maximized() === 2) {
- inset = 0;
- }
- // handle the split border
- // It should only show when V or H-Split and with single child CONs
- if (
- splitBorderEnabled &&
- focusBorderEnabled &&
- tilingModeEnabled &&
- !nodeWindow.isFloat() &&
- !maximized &&
- parentNode.childNodes.length === 1 &&
- (parentNode.isCon() || parentNode.isMonitor()) &&
- !(parentNode.isTabbed() || parentNode.isStacked())
- ) {
- if (!windowActor.splitBorder) {
- let splitBorder = new St.Bin({ style_class: "window-split-border" });
- global.window_group.add_child(splitBorder);
- windowActor.splitBorder = splitBorder;
- }
- let splitBorder = windowActor.splitBorder;
- splitBorder.remove_style_class_name("window-split-vertical");
- splitBorder.remove_style_class_name("window-split-horizontal");
- if (parentNode.isVSplit()) {
- splitBorder.add_style_class_name("window-split-vertical");
- } else if (parentNode.isHSplit()) {
- splitBorder.add_style_class_name("window-split-horizontal");
- }
- borders.push(splitBorder);
- }
- let rect = metaWindow.get_frame_rect();
- borders.forEach((border) => {
- border.set_size(rect.width + inset * 2, rect.height + inset * 2);
- border.set_position(rect.x - inset, rect.y - inset);
- if (metaWindow.appears_focused && !metaWindow.minimized) {
- border.show();
- }
- if (global.window_group && global.window_group.contains(border)) {
- // TODO - sort the borders with split border being on top
- global.window_group.remove_child(border);
- // Add the border just above the focused window
- global.window_group.insert_child_above(border, metaWindow.get_compositor_private());
- }
- });
- }
- updateBorderLayout() {
- this.hideWindowBorders();
- this.showWindowBorders();
- }
- calculateGaps(node) {
- if (!node) return 0;
- let settings = this.ext.settings;
- let gapSize = settings.get_uint("window-gap-size");
- let gapIncrement = settings.get_uint("window-gap-size-increment");
- let gap = gapSize * gapIncrement;
- if (!node.isRoot()) {
- let hideGapWhenSingle = settings.get_boolean("window-gap-hidden-on-single");
- let parentNode = this.tree.findParent(node, NODE_TYPES.MONITOR);
- if (parentNode) {
- let tiled = parentNode
- .getNodeByMode(WINDOW_MODES.TILE)
- .filter((t) => t.isWindow() && !t.nodeValue.minimized);
- if (tiled.length == 1 && hideGapWhenSingle) gap = 0;
- }
- }
- return gap;
- }
- /**
- * Track meta/mutter windows and append them to the tree.
- * Windows can be attached on any of the following Node Types:
- * MONITOR, CONTAINER
- *
- */
- trackWindow(_display, metaWindow) {
- let autoSplit = this.ext.settings.get_boolean("auto-split-enabled");
- if (autoSplit && this.focusMetaWindow) {
- let currentFocusNode = this.tree.findNode(this.focusMetaWindow);
- if (currentFocusNode) {
- let currentParentFocusNode = currentFocusNode.parentNode;
- let layout = currentParentFocusNode.layout;
- if (layout === LAYOUT_TYPES.HSPLIT || layout === LAYOUT_TYPES.VSPLIT) {
- let frameRect = this.focusMetaWindow.get_frame_rect();
- let splitHorizontal = frameRect.width > frameRect.height;
- let orientation = splitHorizontal ? "horizontal" : "vertical";
- this.command({ name: "Split", orientation: orientation });
- }
- }
- }
- // Make window types configurable
- if (this._validWindow(metaWindow)) {
- let existNodeWindow = this.tree.findNode(metaWindow);
- Logger.debug(`Meta Window ${metaWindow.get_title()} ${metaWindow.get_window_type()}`);
- if (!existNodeWindow) {
- let attachTarget;
- const activeMonitor = global.display.get_current_monitor();
- const activeWorkspace = global.display.get_workspace_manager().get_active_workspace_index();
- let metaMonWs = `mo${activeMonitor}ws${activeWorkspace}`;
- // Check if the active monitor / workspace has windows
- let metaMonWsNode = this.tree.findNode(metaMonWs);
- if (!metaMonWsNode) {
- // Reload the tree as a last resort
- this.reloadTree("no-meta-monws");
- return;
- }
- let windowNodes = metaMonWsNode.getNodeByType(NODE_TYPES.WINDOW);
- let hasWindows = windowNodes.length > 0;
- attachTarget = this.tree.attachNode;
- attachTarget = attachTarget ? this.tree.findNode(attachTarget.nodeValue) : null;
- if (!attachTarget) {
- attachTarget = metaMonWsNode;
- } else {
- if (hasWindows) {
- if (attachTarget && metaMonWsNode.contains(attachTarget)) {
- // Use the attach target
- } else {
- // Find the first window
- attachTarget = windowNodes[0];
- }
- } else {
- attachTarget = metaMonWsNode;
- }
- }
- let nodeWindow = this.tree.createNode(
- attachTarget.nodeValue,
- NODE_TYPES.WINDOW,
- metaWindow,
- WINDOW_MODES.FLOAT
- );
- metaWindow.firstRender = true;
- let windowActor = metaWindow.get_compositor_private();
- if (!metaWindow.windowSignals) {
- let windowSignals = [
- metaWindow.connect("position-changed", (_metaWindow) => {
- let from = "position-changed";
- this.updateMetaPositionSize(_metaWindow, from);
- }),
- metaWindow.connect("size-changed", (_metaWindow) => {
- let from = "size-changed";
- this.updateMetaPositionSize(_metaWindow, from);
- }),
- metaWindow.connect("focus", (_metaWindowFocus) => {
- this.queueEvent({
- name: "focus-update",
- callback: () => {
- this.unfreezeRender();
- this.updateBorderLayout();
- this.updateDecorationLayout();
- this.updateStackedFocus();
- this.updateTabbedFocus();
- let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- this.movePointerWith(focusNodeWindow);
- },
- });
- let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- if (focusNodeWindow) {
- // handle the attach node
- this.tree.attachNode = focusNodeWindow._parent;
- if (this.floatingWindow(focusNodeWindow)) {
- this.queueEvent({
- name: "raise-float",
- callback: () => {
- this.renderTree("raise-float-queue");
- },
- });
- }
- this.tree.attachNode = focusNodeWindow;
- }
- this.renderTree("focus", true);
- }),
- metaWindow.connect("workspace-changed", (_metaWindow) => {
- this.updateMetaWorkspaceMonitor("metawindow-workspace-changed", null, _metaWindow);
- this.trackCurrentMonWs();
- }),
- ];
- metaWindow.windowSignals = windowSignals;
- }
- if (!windowActor.actorSignals) {
- let actorSignals = [windowActor.connect("destroy", this.windowDestroy.bind(this))];
- windowActor.actorSignals = actorSignals;
- }
- if (!windowActor.border) {
- let border = new St.Bin({ style_class: "window-tiled-border" });
- if (global.window_group) global.window_group.add_child(border);
- windowActor.border = border;
- border.show();
- }
- this.postProcessWindow(nodeWindow);
- this.queueEvent(
- {
- name: "window-create-queue",
- callback: () => {
- metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL);
- metaWindow.unmaximize(Meta.MaximizeFlags.VERTICAL);
- metaWindow.unmaximize(Meta.MaximizeFlags.BOTH);
- this.renderTree("window-create", true);
- },
- },
- 200
- );
- let childNodes = this.tree.getTiledChildren(nodeWindow.parentNode.childNodes);
- childNodes.forEach((n) => {
- n.percent = 0.0;
- });
- }
- }
- }
- postProcessWindow(nodeWindow) {
- let metaWindow = nodeWindow.nodeValue;
- if (metaWindow) {
- if (metaWindow.get_title() === this.prefsTitle) {
- metaWindow
- .get_workspace()
- .activate_with_focus(metaWindow, global.display.get_current_time());
- this.moveCenter(metaWindow);
- } else {
- this.movePointerWith(metaWindow);
- }
- }
- }
- updateStackedFocus(focusNodeWindow) {
- if (!focusNodeWindow) return;
- const parentNode = focusNodeWindow.parentNode;
- if (parentNode.layout === LAYOUT_TYPES.STACKED && !this._freezeRender) {
- parentNode.appendChild(focusNodeWindow);
- parentNode.childNodes
- .filter((child) => child.isWindow())
- .forEach((child) => child.nodeValue.raise());
- this.queueEvent({
- name: "render-focus-stack",
- callback: () => {
- this.renderTree("focus-stacked");
- },
- });
- }
- }
- updateTabbedFocus(focusNodeWindow) {
- if (!focusNodeWindow) return;
- if (focusNodeWindow.parentNode.layout === LAYOUT_TYPES.TABBED && !this._freezeRender) {
- const metaWindow = focusNodeWindow.nodeValue;
- metaWindow.raise();
- }
- }
- /**
- * Check if a Meta Window's workspace is skipped for tiling.
- */
- isActiveWindowWorkspaceTiled(metaWindow) {
- if (!metaWindow) return true;
- let skipWs = this.ext.settings.get_string("workspace-skip-tile");
- let skipArr = skipWs.split(",");
- let skipThisWs = false;
- for (let i = 0; i < skipArr.length; i++) {
- let activeWorkspaceForWin = metaWindow.get_workspace();
- if (activeWorkspaceForWin) {
- let wsIndex = activeWorkspaceForWin.index();
- if (skipArr[i].trim() === `${wsIndex}`) {
- skipThisWs = true;
- break;
- }
- }
- }
- return !skipThisWs;
- }
- /**
- * Check the current active workspace's tiling mode
- */
- isCurrentWorkspaceTiled() {
- let skipWs = this.ext.settings.get_string("workspace-skip-tile");
- let skipArr = skipWs.split(",");
- let skipThisWs = false;
- let wsMgr = global.workspace_manager;
- let wsIndex = wsMgr.get_active_workspace_index();
- for (let i = 0; i < skipArr.length; i++) {
- if (skipArr[i].trim() === `${wsIndex}`) {
- skipThisWs = true;
- break;
- }
- }
- return !skipThisWs;
- }
- trackCurrentWindows() {
- this.tree.attachNode = null;
- let windowsAll = this.windowsAllWorkspaces;
- for (let i = 0; i < windowsAll.length; i++) {
- let metaWindow = windowsAll[i];
- this.trackWindow(global.display, metaWindow);
- // This updates and handles dynamic workspaces
- this.updateMetaWorkspaceMonitor(
- "track-current-windows",
- metaWindow.get_monitor(),
- metaWindow
- );
- }
- this.updateDecorationLayout();
- }
- _validWindow(metaWindow) {
- let windowType = metaWindow.get_window_type();
- return (
- windowType === Meta.WindowType.NORMAL ||
- windowType === Meta.WindowType.MODAL_DIALOG ||
- windowType === Meta.WindowType.DIALOG
- );
- }
- windowDestroy(actor) {
- // Release any resources on the window
- let border = actor.border;
- if (border) {
- if (global.window_group) {
- global.window_group.remove_child(border);
- border.hide();
- border = null;
- }
- }
- let splitBorder = actor.splitBorder;
- if (splitBorder) {
- if (global.window_group) {
- global.window_group.remove_child(splitBorder);
- splitBorder.hide();
- splitBorder = null;
- }
- }
- let nodeWindow;
- nodeWindow = this.tree.findNodeByActor(actor);
- if (nodeWindow?.isWindow()) {
- this.tree.removeNode(nodeWindow);
- this.renderTree("window-destroy-quick", true);
- this.removeFloatOverride(nodeWindow.nodeValue, true);
- }
- // find the next attachNode here
- let focusNodeWindow = this.tree.findNode(this.focusMetaWindow);
- if (focusNodeWindow) {
- this.tree.attachNode = focusNodeWindow.parentNode;
- }
- this.queueEvent({
- name: "window-destroy",
- callback: () => {
- this.renderTree("window-destroy", true);
- },
- });
- }
- /**
- * Handles any workspace/monitor update for the Meta.Window.
- */
- updateMetaWorkspaceMonitor(from, _monitor, metaWindow) {
- if (this._validWindow(metaWindow)) {
- if (metaWindow.get_workspace() === null) return;
- let existNodeWindow = this.tree.findNode(metaWindow);
- let metaMonWs = `mo${metaWindow.get_monitor()}ws${metaWindow.get_workspace().index()}`;
- let metaMonWsNode = this.tree.findNode(metaMonWs);
- if (existNodeWindow) {
- if (existNodeWindow.parentNode && metaMonWsNode) {
- // Uses the existing workspace, monitor that the metaWindow
- // belongs to.
- let containsWindow = metaMonWsNode.contains(existNodeWindow);
- if (!containsWindow) {
- // handle cleanup of resize percentages
- let existParent = existNodeWindow.parentNode;
- this.tree.resetSiblingPercent(existParent);
- metaMonWsNode.appendChild(existNodeWindow);
- // Ensure that the workspace tiling is honored
- if (this.isActiveWindowWorkspaceTiled(metaWindow)) {
- if (!this.grabOp === Meta.GrabOp.WINDOW_BASE) this.updateTabbedFocus(existNodeWindow);
- this.updateStackedFocus(existNodeWindow);
- } else {
- if (this.floatingWindow(existNodeWindow)) {
- existNodeWindow.nodeValue.raise();
- }
- }
- }
- }
- }
- this.renderTree(from);
- }
- }
- /**
- * Handle any updates to the current focused window's position.
- * Useful for updating the active window border, etc.
- */
- updateMetaPositionSize(_metaWindow, from) {
- let focusMetaWindow = this.focusMetaWindow;
- if (!focusMetaWindow) return;
- let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
- if (!focusNodeWindow) return;
- let tilingModeEnabled = this.ext.settings.get_boolean("tiling-mode-enabled");
- if (focusNodeWindow.grabMode && tilingModeEnabled) {
- if (focusNodeWindow.grabMode === GRAB_TYPES.RESIZING) {
- this._handleResizing(focusNodeWindow);
- } else if (focusNodeWindow.grabMode === GRAB_TYPES.MOVING) {
- this._handleMoving(focusNodeWindow);
- }
- } else {
- if (focusMetaWindow.get_maximized() === 0) {
- this.renderTree(from);
- }
- }
- this.updateBorderLayout();
- this.updateDecorationLayout();
- }
- updateDecorationLayout() {
- if (this._freezeRender) return;
- let activeWsNode = this.currentWsNode;
- let allCons = this.tree.getNodeByType(NODE_TYPES.CON);
- // First, hide all decorations:
- allCons.forEach((con) => {
- if (con.decoration) {
- con.decoration.hide();
- }
- });
- // Next, handle showing-desktop usually by Super + D
- if (!activeWsNode) return;
- let allWindows = activeWsNode.getNodeByType(NODE_TYPES.WINDOW);
- let allHiddenWindows = allWindows.filter((w) => {
- let metaWindow = w.nodeValue;
- return !metaWindow.showing_on_its_workspace() || metaWindow.minimized;
- });
- // Then if all hidden, do not proceed showing the decorations at all;
- if (allWindows.length === allHiddenWindows.length) return;
- // Show the decoration where on all monitors of active workspace
- // But not on the monitor where there is a maximized or fullscreen window
- // Note, that when multi-display, user can have multi maximized windows,
- // So it needs to be fully filtered:
- let monWsNoMaxWindows = activeWsNode.getNodeByType(NODE_TYPES.MONITOR).filter((monitor) => {
- return (
- monitor.getNodeByType(NODE_TYPES.WINDOW).filter((w) => {
- return (
- w.nodeValue.get_maximized() === Meta.MaximizeFlags.BOTH || w.nodeValue.is_fullscreen()
- );
- }).length === 0
- );
- });
- monWsNoMaxWindows.forEach((monitorWs) => {
- let activeMonWsCons = monitorWs.getNodeByType(NODE_TYPES.CON);
- activeMonWsCons.forEach((con) => {
- let tiled = this.tree.getTiledChildren(con.childNodes);
- let showTabs = this.ext.settings.get_boolean("showtab-decoration-enabled");
- if (con.decoration && tiled.length > 0 && showTabs) {
- con.decoration.show();
- if (global.window_group.contains(con.decoration) && this.focusMetaWindow) {
- global.window_group.remove_child(con.decoration);
- // Show it below the focused window
- global.window_group.insert_child_below(
- con.decoration,
- this.focusMetaWindow.get_compositor_private()
- );
- }
- con.childNodes.forEach((cn) => {
- cn.render();
- });
- }
- });
- });
- }
- freezeRender() {
- this._freezeRender = true;
- }
- unfreezeRender() {
- this._freezeRender = false;
- }
- floatingWindow(node) {
- if (!node) return false;
- return node.nodeType === NODE_TYPES.WINDOW && node.mode === WINDOW_MODES.FLOAT;
- }
- /**
- * Moves the pointer along with the nodeWindow's meta
- *
- * This is useful for making sure that Forge calculates the attachNode
- * properly
- */
- movePointerWith(nodeWindow) {
- if (!nodeWindow || !nodeWindow._data) return;
- if (this.ext.settings.get_boolean("move-pointer-focus-enabled")) {
- this.storePointerLastPosition(this.lastFocusedWindow);
- if (this.canMovePointerInsideNodeWindow(nodeWindow)) {
- this.warpPointerToNodeWindow(nodeWindow);
- }
- }
- this.lastFocusedWindow = nodeWindow;
- this.tree.debugParentNodes(nodeWindow);
- }
- warpPointerToNodeWindow(nodeWindow) {
- const newCoord = this.getPointerPositionInside(nodeWindow);
- if (newCoord && newCoord.x && newCoord.y) {
- const seat = Clutter.get_default_backend().get_default_seat();
- if (seat) {
- const wmTitle = nodeWindow.nodeValue.get_title();
- Logger.debug(`moved pointer to [${wmTitle}] at (${newCoord.x},${newCoord.y})`);
- seat.warp_pointer(newCoord.x, newCoord.y);
- }
- }
- }
- getPointer() {
- return global.get_pointer();
- }
- minimizedWindow(node) {
- if (!node) return false;
- return node._type === NODE_TYPES.WINDOW && node._data && node._data.minimized;
- }
- swapWindowsUnderPointer(focusNodeWindow) {
- if (this.cancelGrab) {
- return;
- }
- let nodeWinAtPointer = this.findNodeWindowAtPointer(focusNodeWindow);
- if (nodeWinAtPointer) this.tree.swapPairs(focusNodeWindow, nodeWinAtPointer);
- }
- /**
- *
- * Handle previewing and applying where a drag-drop window is going to be tiled
- *
- */
- moveWindowToPointer(focusNodeWindow, preview = false) {
- if (this.cancelGrab) {
- return;
- }
- if (!focusNodeWindow || focusNodeWindow.mode !== WINDOW_MODES.GRAB_TILE) return;
- let nodeWinAtPointer = this.nodeWinAtPointer;
- if (nodeWinAtPointer) {
- const targetRect = nodeWinAtPointer.nodeValue.get_frame_rect();
- const parentNodeTarget = nodeWinAtPointer.parentNode;
- const currPointer = this.getPointer();
- const horizontal = parentNodeTarget.isHSplit() || parentNodeTarget.isTabbed();
- const isMonParent = parentNodeTarget.nodeType === NODE_TYPES.MONITOR;
- const isConParent = parentNodeTarget.nodeType === NODE_TYPES.CON;
- const centerLayout = this.ext.settings.get_string("dnd-center-layout").toUpperCase();
- const stacked = parentNodeTarget.isStacked();
- const tabbed = parentNodeTarget.isTabbed();
- const stackedOrTabbed = stacked || tabbed;
- const updatePreview = (focusNodeWindow, previewParams) => {
- let previewHint = focusNodeWindow.previewHint;
- let previewHintEnabled = this.ext.settings.get_boolean("preview-hint-enabled");
- const previewRect = previewParams.targetRect;
- if (previewHint && previewHintEnabled) {
- if (!previewRect) {
- previewHint.hide();
- return;
- }
- previewHint.set_style_class_name(previewParams.className);
- previewHint.set_position(previewRect.x, previewRect.y);
- previewHint.set_size(previewRect.width, previewRect.height);
- previewHint.show();
- }
- };
- const regions = (targetRect, regionWidth) => {
- leftRegion = {
- x: targetRect.x,
- y: targetRect.y,
- width: targetRect.width * regionWidth,
- height: targetRect.height,
- };
- rightRegion = {
- x: targetRect.x + targetRect.width * (1 - regionWidth),
- y: targetRect.y,
- width: targetRect.width * regionWidth,
- height: targetRect.height,
- };
- topRegion = {
- x: targetRect.x,
- y: targetRect.y,
- width: targetRect.width,
- height: targetRect.height * regionWidth,
- };
- bottomRegion = {
- x: targetRect.x,
- y: targetRect.y + targetRect.height * (1 - regionWidth),
- width: targetRect.width,
- height: targetRect.height * regionWidth,
- };
- centerRegion = {
- x: targetRect.x + targetRect.width * regionWidth,
- y: targetRect.y + targetRect.height * regionWidth,
- width: targetRect.width - targetRect.width * regionWidth * 2,
- height: targetRect.height - targetRect.height * regionWidth * 2,
- };
- return {
- left: leftRegion,
- right: rightRegion,
- top: topRegion,
- bottom: bottomRegion,
- center: centerRegion,
- };
- };
- let referenceNode = null;
- let containerNode;
- let childNode = focusNodeWindow;
- let previewParams = {
- className: "",
- targetRect: null,
- };
- let leftRegion;
- let rightRegion;
- let topRegion;
- let bottomRegion;
- let centerRegion;
- let previewWidth = 0.5;
- let hoverWidth = 0.3;
- // Hover region detects where the pointer is on the target drop window
- const hoverRegions = regions(targetRect, hoverWidth);
- // Preview region interprets the hover intersect where the focus window
- // would go when dropped
- const previewRegions = regions(targetRect, previewWidth);
- leftRegion = hoverRegions.left;
- rightRegion = hoverRegions.right;
- topRegion = hoverRegions.top;
- bottomRegion = hoverRegions.bottom;
- centerRegion = hoverRegions.center;
- const isLeft = Utils.rectContainsPoint(leftRegion, currPointer);
- const isRight = Utils.rectContainsPoint(rightRegion, currPointer);
- const isTop = Utils.rectContainsPoint(topRegion, currPointer);
- const isBottom = Utils.rectContainsPoint(bottomRegion, currPointer);
- const isCenter = Utils.rectContainsPoint(centerRegion, currPointer);
- if (isCenter) {
- if (centerLayout == "SWAP") {
- referenceNode = nodeWinAtPointer;
- previewParams = {
- targetRect: targetRect,
- };
- } else {
- if (stackedOrTabbed) {
- containerNode = parentNodeTarget;
- referenceNode = null;
- previewParams = {
- className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
- targetRect: targetRect,
- };
- } else {
- if (isMonParent) {
- childNode.createCon = true;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer;
- previewParams = {
- targetRect: targetRect,
- };
- } else {
- containerNode = parentNodeTarget;
- referenceNode = null;
- const parentTargetRect = this.tree.processGap(parentNodeTarget);
- previewParams = {
- targetRect: parentTargetRect,
- };
- }
- }
- }
- } else if (isLeft) {
- previewParams = {
- targetRect: previewRegions.left,
- };
- if (stackedOrTabbed) {
- // treat any windows on stacked or tabbed layouts to be
- // a single node unit: the con itself and then
- // split left, top, right or bottom accordingly (subsequent if conditions):
- childNode.detachWindow = true;
- if (!isMonParent) {
- referenceNode = parentNodeTarget;
- containerNode = parentNodeTarget.parentNode;
- } else {
- // It is a monitor that's a stack/tab
- // TODO: update the stacked/tabbed toggles to not
- // change layout if the parent is a monitor?
- }
- } else {
- if (horizontal) {
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer;
- } else {
- // vertical orientation
- childNode.createCon = true;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer;
- }
- }
- } else if (isRight) {
- previewParams = {
- targetRect: previewRegions.right,
- };
- if (stackedOrTabbed) {
- // treat any windows on stacked or tabbed layouts to be
- // a single node unit: the con itself and then
- // split left, top, right or bottom accordingly (subsequent if conditions):
- childNode.detachWindow = true;
- if (!isMonParent) {
- referenceNode = parentNodeTarget.nextSibling;
- containerNode = parentNodeTarget.parentNode;
- } else {
- // It is a monitor that's a stack/tab
- // TODO: update the stacked/tabbed toggles to not
- // change layout if the parent is a monitor?
- }
- } else {
- if (horizontal) {
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer.nextSibling;
- } else {
- childNode.createCon = true;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer.nextSibling;
- }
- }
- } else if (isTop) {
- previewParams = {
- targetRect: previewRegions.top,
- };
- if (stackedOrTabbed) {
- // treat any windows on stacked or tabbed layouts to be
- // a single node unit: the con itself and then
- // split left, top, right or bottom accordingly (subsequent if conditions):
- if (!isMonParent) {
- containerNode = parentNodeTarget;
- referenceNode = null;
- previewParams = {
- className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
- targetRect: targetRect,
- };
- } else {
- // It is a monitor that's a stack/tab
- // TODO: update the stacked/tabbed toggles to not
- // change layout if the parent is a monitor?
- }
- } else {
- if (horizontal) {
- childNode.createCon = true;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer;
- } else {
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer;
- }
- }
- } else if (isBottom) {
- previewParams = {
- targetRect: previewRegions.bottom,
- };
- if (stackedOrTabbed) {
- // treat any windows on stacked or tabbed layouts to be
- // a single node unit: the con itself and then
- // split left, top, right or bottom accordingly (subsequent if conditions):
- if (!isMonParent) {
- containerNode = parentNodeTarget;
- referenceNode = null;
- previewParams = {
- className: stacked ? "window-tilepreview-stacked" : "window-tilepreview-tabbed",
- targetRect: targetRect,
- };
- } else {
- // It is a monitor that's a stack/tab
- // TODO: update the stacked/tabbed toggles to not
- // change layout if the parent is a monitor?
- }
- } else {
- if (horizontal) {
- childNode = focusNodeWindow;
- childNode.createCon = true;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer.nextSibling;
- } else {
- childNode = focusNodeWindow;
- containerNode = parentNodeTarget;
- referenceNode = nodeWinAtPointer.nextSibling;
- }
- }
- }
- if (!isCenter) {
- if (stackedOrTabbed) {
- if (isLeft || isRight) {
- previewParams.className = "window-tilepreview-tiled";
- } else if (isTop || isBottom) {
- previewParams.className = stacked
- ? "window-tilepreview-stacked"
- : "window-tilepreview-tabbed";
- }
- } else {
- previewParams.className = "window-tilepreview-tiled";
- }
- } else if (isCenter) {
- if (!stackedOrTabbed) previewParams.className = this._getDragDropCenterPreviewStyle();
- }
- if (!preview) {
- const previousParent = focusNodeWindow.parentNode;
- this.tree.resetSiblingPercent(containerNode);
- this.tree.resetSiblingPercent(previousParent);
- if (focusNodeWindow.tab) {
- let decoParent = focusNodeWindow.tab.get_parent();
- if (decoParent) decoParent.remove_child(focusNodeWindow.tab);
- }
- if (childNode.createCon) {
- const numWin = parentNodeTarget.childNodes.filter(
- (c) => c.nodeType === NODE_TYPES.WINDOW
- ).length;
- const numChild = parentNodeTarget.childNodes.length;
- const sameNumChild = numWin === numChild;
- // Child Node will still be created
- if (
- !isCenter &&
- ((isConParent && numWin === 1 && sameNumChild) ||
- (isMonParent && numWin == 2 && sameNumChild))
- ) {
- childNode = parentNodeTarget;
- } else {
- childNode = new Node(NODE_TYPES.CON, new St.Bin());
- containerNode.insertBefore(childNode, referenceNode);
- childNode.appendChild(nodeWinAtPointer);
- }
- if (isLeft || isTop) {
- childNode.insertBefore(focusNodeWindow, nodeWinAtPointer);
- } else if (isRight || isBottom || isCenter) {
- childNode.insertBefore(focusNodeWindow, null);
- }
- if (isLeft || isRight) {
- childNode.layout = LAYOUT_TYPES.HSPLIT;
- } else if (isTop || isBottom) {
- childNode.layout = LAYOUT_TYPES.VSPLIT;
- } else if (isCenter) {
- childNode.layout = LAYOUT_TYPES[centerLayout];
- }
- } else if (childNode.detachWindow) {
- const orientation =
- isLeft || isRight ? ORIENTATION_TYPES.HORIZONTAL : ORIENTATION_TYPES.VERTICAL;
- this.tree.split(childNode, orientation);
- containerNode.insertBefore(childNode.parentNode, referenceNode);
- } else if (isCenter && centerLayout == "SWAP") {
- this.tree.swapPairs(referenceNode, focusNodeWindow);
- this.renderTree("drag-swap");
- } else {
- // Child Node is a WINDOW
- containerNode.insertBefore(childNode, referenceNode);
- if (isLeft || isRight) {
- containerNode.layout = LAYOUT_TYPES.HSPLIT;
- } else if (isTop || isBottom) {
- if (!stackedOrTabbed) containerNode.layout = LAYOUT_TYPES.VSPLIT;
- } else if (isCenter) {
- if (containerNode.isHSplit() || containerNode.isVSplit()) {
- containerNode.layout = LAYOUT_TYPES[centerLayout];
- }
- }
- }
- previousParent.resetLayoutSingleChild();
- } else {
- updatePreview(focusNodeWindow, previewParams);
- }
- childNode.createCon = false;
- childNode.detachWindow = false;
- }
- }
- canMovePointerInsideNodeWindow(nodeWindow) {
- if (nodeWindow && nodeWindow._data) {
- const metaWindow = nodeWindow.nodeValue;
- const metaRect = metaWindow.get_frame_rect();
- const pointerCoord = global.get_pointer();
- return (
- metaRect &&
- // xdg-copy creates a 1x1 pixel window to capture mouse events.
- metaRect.width > 8 &&
- metaRect.height > 8 &&
- !Utils.rectContainsPoint(metaRect, pointerCoord) &&
- !metaWindow.minimized &&
- !Main.overview.visible &&
- !this.pointerIsOverParentDecoration(nodeWindow, pointerCoord)
- );
- }
- return false;
- }
- pointerIsOverParentDecoration(nodeWindow, pointerCoord) {
- if (pointerCoord && nodeWindow && nodeWindow.parentNode) {
- let node = nodeWindow.parentNode;
- if (node.isTabbed() || node.isStacked()) {
- return Utils.rectContainsPoint(node.rect, pointerCoord);
- }
- }
- return false;
- }
- getPointerPositionInside(nodeWindow) {
- if (nodeWindow && nodeWindow._data) {
- const metaWindow = nodeWindow.nodeValue;
- const metaRect = metaWindow.get_frame_rect();
- // on: last position of cursor inside window
- // on: titlebar: near to app toolbars, menubar, tabs, etc...
- let [wx, wy] = nodeWindow.pointer
- ? [nodeWindow.pointer.x, nodeWindow.pointer.y]
- : [metaRect.width / 2, 8];
- let px = wx >= metaRect.width ? metaRect.width - 8 : wx;
- let py = wy >= metaRect.height ? metaRect.height - 8 : wy;
- return {
- x: metaRect.x + px,
- y: metaRect.y + py,
- };
- }
- return null;
- }
- storePointerLastPosition(nodeWindow) {
- if (nodeWindow && nodeWindow._data) {
- const metaWindow = nodeWindow.nodeValue;
- const metaRect = metaWindow.get_frame_rect();
- const pointerCoord = global.get_pointer();
- if (Utils.rectContainsPoint(metaRect, pointerCoord)) {
- let px = pointerCoord[0] - metaRect.x;
- let py = pointerCoord[1] - metaRect.y;
- if (px > 0 && py > 0) {
- nodeWindow.pointer = { x: px, y: py };
- Logger.debug(`stored pointer for [${metaWindow.get_title()}] at (${px},${py})`);
- }
- }
- }
- }
- findNodeWindowAtPointer(focusNodeWindow) {
- let pointerCoord = global.get_pointer();
- let nodeWinAtPointer = this._findNodeWindowAtPointer(focusNodeWindow.nodeValue, pointerCoord);
- return nodeWinAtPointer;
- }
- /**
- * Finds the NodeWindow under the Meta.Window and the
- * current pointer coordinates;
- */
- _findNodeWindowAtPointer(metaWindow, pointer) {
- if (!metaWindow) return undefined;
- let sortedWindows = this.sortedWindows;
- if (!sortedWindows) {
- Logger.warn("No sorted windows");
- return;
- }
- for (let i = 0, n = sortedWindows.length; i < n; i++) {
- const w = sortedWindows[i];
- const metaRect = w.get_frame_rect();
- const atPointer = Utils.rectContainsPoint(metaRect, pointer);
- if (atPointer) return this.tree.getNodeByValue(w);
- }
- return null;
- }
- _handleGrabOpBegin(_display, _metaWindow, grabOp) {
- this.grabOp = grabOp;
- this.trackCurrentMonWs();
- let focusMetaWindow = this.focusMetaWindow;
- if (focusMetaWindow) {
- let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
- if (!focusNodeWindow) return;
- const frameRect = focusMetaWindow.get_frame_rect();
- const gaps = this.calculateGaps(focusNodeWindow);
- focusNodeWindow.grabMode = Utils.grabMode(grabOp);
- if (
- focusNodeWindow.grabMode === GRAB_TYPES.MOVING &&
- focusNodeWindow.mode === WINDOW_MODES.TILE
- ) {
- this.freezeRender();
- focusNodeWindow.mode = WINDOW_MODES.GRAB_TILE;
- }
- focusNodeWindow.initGrabOp = grabOp;
- focusNodeWindow.initRect = Utils.removeGapOnRect(frameRect, gaps);
- }
- }
- _handleGrabOpEnd(_display, _metaWindow, grabOp) {
- this.unfreezeRender();
- let focusMetaWindow = this.focusMetaWindow;
- if (!focusMetaWindow) return;
- let focusNodeWindow = this.findNodeWindow(focusMetaWindow);
- if (focusNodeWindow && !this.cancelGrab) {
- // WINDOW_BASE is when grabbing the window decoration
- // COMPOSITOR is when something like Overview requesting a grab, especially when Super is pressed.
- if (
- grabOp === Meta.GrabOp.WINDOW_BASE ||
- grabOp === Meta.GrabOp.COMPOSITOR ||
- grabOp === Meta.GrabOp.MOVING_UNCONSTRAINED
- ) {
- if (this.allowDragDropTile()) {
- this.moveWindowToPointer(focusNodeWindow);
- }
- }
- }
- this._grabCleanup(focusNodeWindow);
- if (focusMetaWindow.get_maximized() === 0) {
- this.renderTree("grab-op-end");
- }
- this.updateStackedFocus(focusNodeWindow);
- this.updateTabbedFocus(focusNodeWindow);
- this.nodeWinAtPointer = null;
- }
- _grabCleanup(focusNodeWindow) {
- this.cancelGrab = false;
- if (!focusNodeWindow) return;
- focusNodeWindow.initRect = null;
- focusNodeWindow.grabMode = null;
- focusNodeWindow.initGrabOp = null;
- if (focusNodeWindow.previewHint) {
- focusNodeWindow.previewHint.hide();
- global.window_group.remove_child(focusNodeWindow.previewHint);
- focusNodeWindow.previewHint.destroy();
- focusNodeWindow.previewHint = null;
- }
- if (focusNodeWindow.mode === WINDOW_MODES.GRAB_TILE) {
- focusNodeWindow.mode = WINDOW_MODES.TILE;
- }
- }
- allowDragDropTile() {
- return this.kbd.allowDragDropTile();
- }
- _handleResizing(focusNodeWindow) {
- if (!focusNodeWindow || focusNodeWindow.isFloat()) return;
- let grabOps = Utils.decomposeGrabOp(this.grabOp);
- for (let grabOp of grabOps) {
- let initGrabOp = focusNodeWindow.initGrabOp;
- let direction = Utils.directionFromGrab(grabOp);
- let orientation = Utils.orientationFromGrab(grabOp);
- let parentNodeForFocus = focusNodeWindow.parentNode;
- let position = Utils.positionFromGrabOp(grabOp);
- // normalize the rect without gaps
- let frameRect = this.focusMetaWindow.get_frame_rect();
- let gaps = this.calculateGaps(focusNodeWindow);
- let currentRect = Utils.removeGapOnRect(frameRect, gaps);
- let firstRect;
- let secondRect;
- let parentRect;
- let resizePairForWindow;
- if (initGrabOp === Meta.GrabOp.RESIZING_UNKNOWN) {
- // the direction is null so do not process yet below.
- return;
- } else {
- resizePairForWindow = this.tree.nextVisible(focusNodeWindow, direction);
- }
- let sameParent = resizePairForWindow
- ? resizePairForWindow.parentNode === focusNodeWindow.parentNode
- : false;
- if (orientation === ORIENTATION_TYPES.HORIZONTAL) {
- if (sameParent) {
- // use the window or con pairs
- if (this.tree.getTiledChildren(parentNodeForFocus.childNodes).length <= 1) {
- return;
- }
- firstRect = focusNodeWindow.initRect;
- if (resizePairForWindow) {
- if (
- !this.floatingWindow(resizePairForWindow) &&
- !this.minimizedWindow(resizePairForWindow)
- ) {
- secondRect = resizePairForWindow.rect;
- } else {
- // TODO try to get the next resize pair?
- }
- }
- if (!firstRect || !secondRect) {
- return;
- }
- parentRect = parentNodeForFocus.rect;
- let changePx = currentRect.width - firstRect.width;
- let firstPercent = (firstRect.width + changePx) / parentRect.width;
- let secondPercent = (secondRect.width - changePx) / parentRect.width;
- focusNodeWindow.percent = firstPercent;
- resizePairForWindow.percent = secondPercent;
- } else {
- // use the parent pairs (con to another con or window)
- if (resizePairForWindow && resizePairForWindow.parentNode) {
- if (this.tree.getTiledChildren(resizePairForWindow.parentNode.childNodes).length <= 1) {
- return;
- }
- let firstWindowRect = focusNodeWindow.initRect;
- let index = resizePairForWindow.index;
- if (position === POSITION.BEFORE) {
- // Find the opposite node
- index = index + 1;
- } else {
- index = index - 1;
- }
- parentNodeForFocus = resizePairForWindow.parentNode.childNodes[index];
- firstRect = parentNodeForFocus.rect;
- secondRect = resizePairForWindow.rect;
- if (!firstRect || !secondRect) {
- return;
- }
- parentRect = parentNodeForFocus.parentNode.rect;
- let changePx = currentRect.width - firstWindowRect.width;
- let firstPercent = (firstRect.width + changePx) / parentRect.width;
- let secondPercent = (secondRect.width - changePx) / parentRect.width;
- parentNodeForFocus.percent = firstPercent;
- resizePairForWindow.percent = secondPercent;
- }
- }
- } else if (orientation === ORIENTATION_TYPES.VERTICAL) {
- if (sameParent) {
- // use the window or con pairs
- if (this.tree.getTiledChildren(parentNodeForFocus.childNodes).length <= 1) {
- return;
- }
- firstRect = focusNodeWindow.initRect;
- if (resizePairForWindow) {
- if (
- !this.floatingWindow(resizePairForWindow) &&
- !this.minimizedWindow(resizePairForWindow)
- ) {
- secondRect = resizePairForWindow.rect;
- } else {
- // TODO try to get the next resize pair?
- }
- }
- if (!firstRect || !secondRect) {
- return;
- }
- parentRect = parentNodeForFocus.rect;
- let changePx = currentRect.height - firstRect.height;
- let firstPercent = (firstRect.height + changePx) / parentRect.height;
- let secondPercent = (secondRect.height - changePx) / parentRect.height;
- focusNodeWindow.percent = firstPercent;
- resizePairForWindow.percent = secondPercent;
- } else {
- // use the parent pairs (con to another con or window)
- if (resizePairForWindow && resizePairForWindow.parentNode) {
- if (this.tree.getTiledChildren(resizePairForWindow.parentNode.childNodes).length <= 1) {
- return;
- }
- let firstWindowRect = focusNodeWindow.initRect;
- let index = resizePairForWindow.index;
- if (position === POSITION.BEFORE) {
- // Find the opposite node
- index = index + 1;
- } else {
- index = index - 1;
- }
- parentNodeForFocus = resizePairForWindow.parentNode.childNodes[index];
- firstRect = parentNodeForFocus.rect;
- secondRect = resizePairForWindow.rect;
- if (!firstRect || !secondRect) {
- return;
- }
- parentRect = parentNodeForFocus.parentNode.rect;
- let changePx = currentRect.height - firstWindowRect.height;
- let firstPercent = (firstRect.height + changePx) / parentRect.height;
- let secondPercent = (secondRect.height - changePx) / parentRect.height;
- parentNodeForFocus.percent = firstPercent;
- resizePairForWindow.percent = secondPercent;
- }
- }
- }
- }
- }
- _handleMoving(focusNodeWindow) {
- if (!focusNodeWindow || focusNodeWindow.mode !== WINDOW_MODES.GRAB_TILE) return;
- const nodeWinAtPointer = this.findNodeWindowAtPointer(focusNodeWindow);
- this.nodeWinAtPointer = nodeWinAtPointer;
- const hidePreview = () => {
- if (focusNodeWindow.previewHint) {
- focusNodeWindow.previewHint.hide();
- }
- };
- if (nodeWinAtPointer) {
- if (!focusNodeWindow.previewHint) {
- let previewHint = new St.Bin();
- global.window_group.add_child(previewHint);
- focusNodeWindow.previewHint = previewHint;
- }
- if (this.allowDragDropTile()) {
- this.moveWindowToPointer(focusNodeWindow, true);
- } else {
- hidePreview();
- }
- } else {
- hidePreview();
- }
- }
- isFloatingExempt(metaWindow) {
- if (!metaWindow) return true;
- let windowTitle = metaWindow.get_title();
- let windowType = metaWindow.get_window_type();
- let floatByType =
- windowType === Meta.WindowType.DIALOG ||
- windowType === Meta.WindowType.MODAL_DIALOG ||
- metaWindow.get_transient_for() !== null ||
- metaWindow.get_wm_class() === null ||
- windowTitle === null ||
- windowTitle === "" ||
- windowTitle.length === 0 ||
- !metaWindow.allows_resize();
- const knownFloats = this.windowProps.overrides.filter((wprop) => wprop.mode === "float");
- let floatOverride =
- knownFloats.filter((kf) => {
- let matchTitle = false;
- let matchClass = false;
- let matchId = false;
- if (kf.wmTitle) {
- if (kf.wmTitle === " ") {
- matchTitle = kf.wmTitle === windowTitle;
- } else {
- let titles = kf.wmTitle.split(",");
- matchTitle =
- titles.filter((t) => {
- if (windowTitle) {
- if (t.startsWith("!")) {
- return !windowTitle.includes(t.slice(1));
- } else {
- return windowTitle.includes(t);
- }
- }
- return false;
- }).length > 0;
- }
- }
- if (kf.wmClass) {
- matchClass = kf.wmClass.includes(metaWindow.get_wm_class());
- }
- if (kf.wmId) {
- matchId = kf.wmId === metaWindow.get_id();
- }
- return (!kf.wmId || matchId) && (!kf.wmTitle || matchTitle) && matchClass;
- }).length > 0;
- return floatByType || floatOverride;
- }
- _getDragDropCenterPreviewStyle() {
- const centerLayout = this.ext.settings.get_string("dnd-center-layout");
- return `window-tilepreview-${centerLayout}`;
- }
- get currentMonWsNode() {
- const monWs = this.currentMonWs;
- if (monWs) {
- return this.tree.findNode(monWs);
- }
- return null;
- }
- get currentWsNode() {
- const ws = this.currentWs;
- if (ws) {
- return this.tree.findNode(ws);
- }
- return null;
- }
- get currentMonWs() {
- const monWs = `${this.currentMon}${this.currentWs}`;
- return monWs;
- }
- get currentWs() {
- const display = global.display;
- const wsMgr = display.get_workspace_manager();
- return `ws${wsMgr.get_active_workspace_index()}`;
- }
- get currentMon() {
- const display = global.display;
- return `mo${display.get_current_monitor()}`;
- }
- floatAllWindows() {
- this.tree.getNodeByType(NODE_TYPES.WINDOW).forEach((w) => {
- if (w.isFloat()) {
- w.prevFloat = true;
- }
- w.mode = WINDOW_MODES.FLOAT;
- });
- }
- unfloatAllWindows() {
- this.tree.getNodeByType(NODE_TYPES.WINDOW).forEach((w) => {
- if (!w.prevFloat) {
- w.mode = WINDOW_MODES.TILE;
- } else {
- // Reset the float marker
- w.prevFloat = false;
- }
- });
- }
- }
|