|
|
@@ -1,2814 +0,0 @@
|
|
|
-/*
|
|
|
- * 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.reloadWindowOverrides();
|
|
|
- this._kbd = this.ext.keybindings;
|
|
|
- this._tree = new Tree(this);
|
|
|
- this.eventQueue = new Queue();
|
|
|
- this.theme = this.ext.theme;
|
|
|
- this.lastFocusedWindow = null;
|
|
|
- this.shouldFocusOnHover = this.ext.settings.get_boolean("focus-on-hover-enabled");
|
|
|
-
|
|
|
- Logger.info("forge initialized");
|
|
|
-
|
|
|
- if (this.shouldFocusOnHover) {
|
|
|
- // Start the pointer loop to observe the pointer position
|
|
|
- // and change the focus window accordingly
|
|
|
- this.pointerLoopInit();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pointerLoopInit() {
|
|
|
- if (this._pointerFocusTimeoutId) {
|
|
|
- GLib.Source.remove(this._pointerFocusTimeoutId);
|
|
|
- }
|
|
|
-
|
|
|
- this._pointerFocusTimeoutId = GLib.timeout_add(
|
|
|
- GLib.PRIORITY_DEFAULT,
|
|
|
- 16,
|
|
|
- this._focusWindowUnderPointer.bind(this)
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- addFloatOverride(metaWindow, withWmId) {
|
|
|
- let currentProps = this.ext.configMgr.windowProps;
|
|
|
- let overrides = currentProps.overrides;
|
|
|
- let wmClass = metaWindow.get_wm_class();
|
|
|
- let wmId = metaWindow.get_id();
|
|
|
-
|
|
|
- for (let override of 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",
|
|
|
- });
|
|
|
-
|
|
|
- // Save the updated overrides back to the ConfigManager
|
|
|
- currentProps.overrides = overrides;
|
|
|
- this.ext.configMgr.windowProps = currentProps;
|
|
|
- }
|
|
|
-
|
|
|
- removeFloatOverride(metaWindow, withWmId) {
|
|
|
- let currentProps = this.ext.configMgr.windowProps;
|
|
|
- let overrides = currentProps.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)
|
|
|
- )
|
|
|
- );
|
|
|
-
|
|
|
- // Save the updated overrides back to the ConfigManager
|
|
|
- currentProps.overrides = overrides;
|
|
|
- this.ext.configMgr.windowProps = currentProps;
|
|
|
- }
|
|
|
-
|
|
|
- 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 "window-overrides-reload-trigger":
|
|
|
- // Reload window overrides when triggered by preferences
|
|
|
- // This prevents the main extension from overwriting changes made by preferences
|
|
|
- this.reloadWindowOverrides();
|
|
|
- break;
|
|
|
- case "focus-border-toggle":
|
|
|
- this.renderTree(settingName);
|
|
|
- break;
|
|
|
- case "focus-on-hover-enabled":
|
|
|
- this.shouldFocusOnHover = settings.get_boolean(settingName);
|
|
|
-
|
|
|
- if (this.shouldFocusOnHover) {
|
|
|
- this.pointerLoopInit();
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- hideActorBorder(actor) {
|
|
|
- if (actor.border) {
|
|
|
- actor.border.hide();
|
|
|
- }
|
|
|
- if (actor.splitBorder) {
|
|
|
- actor.splitBorder.hide();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- hideWindowBorders() {
|
|
|
- this.tree.nodeWindows.forEach((nodeWindow) => {
|
|
|
- let actor = nodeWindow.windowActor;
|
|
|
- if (actor) {
|
|
|
- this.hideActorBorder(actor);
|
|
|
- }
|
|
|
- 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;
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- metaWindow.set_unmaximize_flags(Meta.MaximizeFlags.BOTH);
|
|
|
- metaWindow.unmaximize();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- 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._pointerFocusTimeoutId) {
|
|
|
- GLib.Source.remove(this._pointerFocusTimeoutId);
|
|
|
- this._pointerFocusTimeoutId = 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 = () => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- return metaWindow.is_maximized() || metaWindow.is_fullscreen() || gap === 0;
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- 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 ||
|
|
|
- (() => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- return metaWindow.is_maximized();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- return 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("unmanaged", (_metaWindow) => {
|
|
|
- this.hideActorBorder(windowActor);
|
|
|
- }),
|
|
|
- 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: () => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- metaWindow.set_unmaximize_flags(Meta.MaximizeFlags.BOTH);
|
|
|
- metaWindow.unmaximize();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- 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 (
|
|
|
- (() => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- return !focusMetaWindow.is_maximized();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- return 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 (() => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- return w.nodeValue.is_maximized() || w.nodeValue.is_fullscreen();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- 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, { force = false } = {}) {
|
|
|
- if (!nodeWindow || !nodeWindow._data) return;
|
|
|
- const shouldWarp = force || this.ext.settings.get_boolean("move-pointer-focus-enabled");
|
|
|
- if (shouldWarp) {
|
|
|
- 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;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Focus the window under the pointer and raise it.
|
|
|
- *
|
|
|
- * @returns {boolean} true if we should continue polling, false otherwise
|
|
|
- */
|
|
|
- _focusWindowUnderPointer() {
|
|
|
- // Break the loop if the user has disabled the feature
|
|
|
- // or if the window manager is disabled
|
|
|
- if (!this.shouldFocusOnHover || this.disabled) return false;
|
|
|
-
|
|
|
- // We don't want to focus windows when the overview is visible
|
|
|
- if (Main.overview.visible) return true;
|
|
|
-
|
|
|
- // Get the global mouse position
|
|
|
- let pointer = global.get_pointer();
|
|
|
-
|
|
|
- const metaWindow = this._getMetaWindowAtPointer(pointer);
|
|
|
-
|
|
|
- if (metaWindow) {
|
|
|
- // If window is not null, focus it
|
|
|
- metaWindow.focus(global.get_current_time());
|
|
|
- // Raise it to the top
|
|
|
- metaWindow.raise();
|
|
|
- }
|
|
|
-
|
|
|
- // Continue polling
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the Meta.Window at the pointer coordinates
|
|
|
- *
|
|
|
- * @param {[number, number]} pointer x and y coordinates
|
|
|
- * @returns null if no window is found, otherwise the Meta.Window
|
|
|
- */
|
|
|
- _getMetaWindowAtPointer(pointer) {
|
|
|
- const windows = global.get_window_actors();
|
|
|
- const [x, y] = pointer;
|
|
|
-
|
|
|
- // Iterate through the windows in reverse order to get the top-most window
|
|
|
- for (let i = windows.length - 1; i >= 0; i--) {
|
|
|
- let window = windows[i];
|
|
|
- let metaWindow = window.meta_window;
|
|
|
-
|
|
|
- let { x: wx, y: wy, width, height } = metaWindow.get_frame_rect();
|
|
|
-
|
|
|
- // Check if the position is within the window bounds
|
|
|
- if (x >= wx && x <= wx + width && y >= wy && y <= wy + height) {
|
|
|
- return metaWindow;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // No window found at the pointer
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 (
|
|
|
- (() => {
|
|
|
- try {
|
|
|
- // GNOME 49+
|
|
|
- return !focusMetaWindow.is_maximized();
|
|
|
- } catch (e) {
|
|
|
- // pre-49 fallback
|
|
|
- return 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()}`;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Reload window overrides from the configuration file
|
|
|
- * This is called when the preferences page modifies the overrides
|
|
|
- */
|
|
|
- reloadWindowOverrides() {
|
|
|
- // Get fresh data from the ConfigManager
|
|
|
- const freshProps = this.ext.configMgr.windowProps;
|
|
|
- if (freshProps) {
|
|
|
- this.windowProps = freshProps;
|
|
|
- this.windowProps.overrides = this.windowProps.overrides.filter((override) => !override.wmId);
|
|
|
- Logger.info(`Reloaded ${this.windowProps.overrides.length} window overrides from file`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-}
|