Files
dotfiles/gnome/.local/share/gnome-shell/extensions/grimble@lmt.github.io/tileWindowManager.js

932 lines
36 KiB
JavaScript

import Meta from 'gi://Meta';
import GLib from 'gi://GLib';
import {Orientation, Tile, TileState} from './tile.js';
import {Position} from './position.js';
import * as Resize from './resize.js';
import Gio from 'gi://Gio';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {Monitor} from './monitor.js';
import Shell from 'gi://Shell';
import Mtk from 'gi://Mtk';
import {TopBarSearchEntry} from './topBarSearchEntry.js';
export var Direction;
(function (Direction) {
Direction[Direction['North'] = 1] = 'North';
Direction[Direction['South'] = 2] = 'South';
Direction[Direction['West'] = 3] = 'West';
Direction[Direction['East'] = 4] = 'East';
})(Direction || (Direction = {}));
export class TileWindowManager {
/** ************************************************/
// Store all signals to be restored when extension is disabled
_wrappedWindows;
_nMonitors;
_windowCreatedSignal;
_windowGrabSignal;
_workspaceAddedSignal;
_workspaceRemovedSignal;
_activeWorkspaceSignal;
_grabBeginSignal;
_monitorChangedSignal;
_workareasChangedSignal;
/** ************************************************/
_settings;
_userResize;
static locked = false;
// Alternate windows rotation
static rotateEven = [0, 0];
// Search bar widgets
_topBarSearchEntry;
_modalSearchEntry;
_sourceId;
// Tiles structures
static _workspaces = new Map();
static _main_monitor;
constructor(extension) {
if (!TileWindowManager._workspaces)
TileWindowManager._workspaces = new Map();
TileWindowManager._main_monitor = global.display.get_primary_monitor();
this._settings = extension._settings;
this._nMonitors = global.display.get_n_monitors();
for (let i = 0; i < global.workspace_manager.n_workspaces; i++) {
let _monitors = new Array(this._nMonitors);
for (let j = 0; j < _monitors.length; j++)
_monitors[j] = new Monitor(j);
TileWindowManager._workspaces.set(i, _monitors);
}
this._wrappedWindows = new Map();
this._userResize = new Set();
this._sourceId = null;
if (TileWindowManager.locked)
this._loadAfterSessionLock();
global.get_window_actors().forEach(actor => {
if (actor.meta_window &&
actor.meta_window.get_window_type() === Meta.WindowType.NORMAL)
this._addNewWindow(actor.meta_window);
});
this.updateMonitors();
this._windowCreatedSignal = global.display.connect('window-created', (display, obj) => this._onWindowCreated(display, obj));
this._workareasChangedSignal = global.display.connect('workareas-changed', () => {
TileWindowManager._workspaces.forEach(val => {
val.forEach(m => m.updateSize());
});
this.updateMonitors();
});
this._grabBeginSignal = global.display.connect('grab-op-begin', (_, w) => this._userResize.add(w));
this._windowGrabSignal = global.display.connect('grab-op-end', (_, window, op) => {
this._onGrab(window, op);
this._userResize.delete(window);
});
this._workspaceAddedSignal = global.workspace_manager.connect('workspace-added', (_, index) => {
this._onWorkspaceCreated(index);
});
this._workspaceRemovedSignal = global.workspace_manager.connect('workspace-removed', (_, index) => {
this._onWorkspaceRemoved(index);
});
this._activeWorkspaceSignal = global.workspace_manager.connect('active-workspace-changed', () => {
this.updateMonitors();
this.updateAdjacents();
});
this._monitorChangedSignal = global.backend.get_monitor_manager().connect('monitors-changed', () => {
const n = global.display.get_n_monitors();
if (n !== this._nMonitors) {
let diff = n - this._nMonitors;
this._nMonitors = n;
if (diff > 0)
this._addMonitors();
else
this._removeMonitors();
}
});
}
static getMonitors(workspaceIndex = undefined) {
let wk;
if (!workspaceIndex)
wk = TileWindowManager._workspaces.get(global.workspace_manager.get_active_workspace_index());
else
wk = TileWindowManager._workspaces.get(workspaceIndex);
if (wk)
return wk;
else
return [];
}
_addMonitors() {
let newPrimMonitor = global.display.get_primary_monitor();
TileWindowManager._workspaces.forEach((val, _, __) => {
let currPrim = null;
if (newPrimMonitor !== TileWindowManager._main_monitor)
currPrim = val.filter(e => e.index === TileWindowManager._main_monitor)[0];
while (val.length !== this._nMonitors) {
const index = val.length;
val.push(new Monitor(index));
// Move content on the new primary screen
if (index === newPrimMonitor) {
val[index].fullscreen = currPrim?.fullscreen ?? false;
val[index].root = currPrim?.root ?? null;
val[index].root?.forEach(el => {
el.monitor = index;
});
if (currPrim?.fullscreen)
currPrim.fullscreen = false;
if (currPrim?.root)
currPrim.root = null;
}
}
});
TileWindowManager._main_monitor = newPrimMonitor;
}
_removeMonitors() {
TileWindowManager._workspaces.forEach((val, _, __) => {
let windows = [];
while (val.length !== this._nMonitors)
windows.push(val.pop());
for (let i = 0; i < windows.length; i++) {
windows[i]?.root?.forEach(t => {
if (t.window)
this._insertWindow(t.window, null, 0);
});
}
});
TileWindowManager._main_monitor = global.display.get_primary_monitor();
this.updateMonitors();
this.updateAdjacents();
}
/**
* Refresh **ALL** existing tiles.
*/
updateMonitors() {
TileWindowManager._workspaces.forEach((value, _) => {
value.forEach(el => el.root?.update());
});
}
updateAdjacents() {
TileWindowManager._workspaces.forEach((value, _) => {
value.forEach(el => el.root?.forEach(t => t.findAdjacents()));
});
}
destroy() {
global.display.disconnect(this._windowCreatedSignal);
global.display.disconnect(this._windowGrabSignal);
global.display.disconnect(this._grabBeginSignal);
global.display.disconnect(this._workareasChangedSignal);
global.workspace_manager.disconnect(this._workspaceAddedSignal);
global.workspace_manager.disconnect(this._workspaceRemovedSignal);
global.workspace_manager.disconnect(this._activeWorkspaceSignal);
global.backend.disconnect(this._monitorChangedSignal);
if (Resize.resizeSourceId !== null)
GLib.Source.remove(Resize.resizeSourceId);
// Disconnect each window
this._wrappedWindows.forEach((value, key) => {
key.minimize = value[0];
key.maximize = value[1];
key.disconnect(value[2]);
key.disconnect(value[3]);
key.disconnect(value[4]);
key.disconnect(value[5]);
key.disconnect(value[6]);
key.disconnect(value[7]);
this._wrappedWindows.delete(key);
});
this._wrappedWindows.clear();
this._topBarSearchEntry?.destroy();
TileWindowManager._main_monitor = null;
TileWindowManager._workspaces = null;
}
/** Check if the window is a `valid` window.
* A `valid` window is a window created by user and
* running an app.
* It is tricky to filter windows correctly. Here we exclude
* windows that don't have app id (the id is just the app number).
* This method must be called when we're sure the window is **fully**
* created (basically when wait for first-frame signal). Otherwise
* we may badly filter some windows.
*
* @param {Meta.Window} window
* @returns boolean
*/
_isValidWindow(window) {
if (!window)
return false;
if (window.get_window_type() !== Meta.WindowType.NORMAL)
return false;
let app = Shell.WindowTracker.get_default().get_window_app(window);
if (!app)
return false;
if (app.get_id().startsWith('window:'))
return false;
for (var [_, value] of TileWindowManager._workspaces) {
let containsWindow = value.reduce((acc, val) => val.root ? acc || val.root.contains(window) : acc, false);
if (containsWindow)
return false;
}
return true;
}
_onWorkspaceCreated(index) {
let _monitors = new Array(global.display.get_n_monitors());
for (let i = 0; i < _monitors.length; i++)
_monitors[i] = new Monitor(i);
if (!TileWindowManager._workspaces.has(index))
TileWindowManager._workspaces.set(index, _monitors);
}
_onWorkspaceRemoved(index) {
TileWindowManager._workspaces.delete(index);
let newMap = new Map();
TileWindowManager._workspaces.forEach((value, key) => {
if (key > index) {
value.forEach(el => el.root?.forEach(t => {
t.workspace = key - 1;
}));
newMap.set(key - 1, value);
} else {
newMap.set(key, value);
}
});
TileWindowManager._workspaces = newMap;
this.updateMonitors();
this.updateAdjacents();
}
_onWindowCreated(_, window) {
// Wait for first frame to be sure window is fully created
window.get_compositor_private().connect('first-frame', () => {
this._addNewWindow(window);
});
}
_windowWorkspaceChanged(window) {
let tile = window.tile;
if (tile) {
let w = window.get_workspace()?.index();
if (w !== null) {
window.change_workspace_by_index(w, false);
if (w !== window.tile.workspace) {
this._removeWindow(window);
this._insertWindow(window, w);
tile.workspace = w;
}
this.updateMonitors();
this.updateAdjacents();
}
}
}
/** Connect to signals and remove some functions
*
* @param {Meta.Window} window
*/
configureWindowSignals(window) {
let minimizeSignal = window.connect('notify::minimized', () => {
if (!window.minimized) {
let tile = window.tile;
if (tile.state === TileState.MINIMIZED) {
if (TileWindowManager.getMonitors()[tile.monitor].fullscreen) {
if (this._settings?.get_int('fullscreen-switch') === 0)
this.maximizeTile(window, true);
else
this.maximizeTile(window);
}
}
} else {
if (window.tile.state === TileState.MINIMIZED)
return;
window.unminimize();
}
});
let maximizeSignal1 = window.connect('notify::maximized-horizontally', () => {
if (window.tile.state === TileState.MAXIMIZED)
return;
if (window.maximized_horizontally || window.maximized_vertically)
window.unmaximize(Meta.MaximizeFlags.BOTH);
});
let maximizeSignal2 = window.connect('notify::maximized-vertically', () => {
if (window.tile.state === TileState.MAXIMIZED)
return;
if (window.maximized_horizontally || window.maximized_vertically)
window.unmaximize(Meta.MaximizeFlags.BOTH);
});
let unmanagedSignal = window.connect('unmanaged', () => {
this._removeWindow(window);
this._removeWindowSignals(window);
});
let workspaceChangedSignal = window.connect('workspace-changed', w => this._windowWorkspaceChanged(w));
let sizeChangedSignal = window.connect('size-changed', w => {
let tile = w.tile;
if (!this._userResize.has(w) &&
(tile.position.width !== w.get_frame_rect().width ||
tile.position.height !== w.get_frame_rect().height))
tile.update();
});
window._originalMaximize = window.maximize;
window._originalMinimize = window.minimize;
this._wrappedWindows.set(window, [window.minimize, window.maximize, minimizeSignal,
maximizeSignal1, maximizeSignal2,
unmanagedSignal, workspaceChangedSignal, sizeChangedSignal]);
window.minimize = () => { };
window.maximize = () => { };
}
_addNewWindow(window) {
if (!this._isValidWindow(window))
return;
this.configureWindowSignals(window);
this._insertWindow(window);
}
_insertWindow(window, workspace = null, monitor = null) {
let _monitors = TileWindowManager._workspaces.get(workspace != null ? workspace : window.get_workspace()?.index());
if (!_monitors)
return;
let selectedMonitor;
// Select monitor
if (monitor !== null) {
selectedMonitor = _monitors[monitor];
} else if (this._settings?.get_int('monitor-tile-insertion-behavior') === 0) {
selectedMonitor = Monitor.bestFitMonitor(_monitors);
} else {
let m = global.display.get_current_monitor();
selectedMonitor = _monitors[m];
}
// Selected monitor index
let index = selectedMonitor.index;
// Now insert tile on selected monitor
if (selectedMonitor.size() === 0) {
let tile = Tile.createTileLeaf(window, new Position(1.0, 0, 0, 0, 0), index);
tile.workspace = window.get_workspace().index();
window.tile = tile;
_monitors[index].root = tile;
_monitors[index].root?.update();
} else {
_monitors[index].root?.addWindowOnBlock(window);
window.tile.workspace = window.get_workspace().index();
if (_monitors[index].fullscreen)
window.tile.state = TileState.MINIMIZED;
_monitors[index].root?.update();
}
}
_removeWindowSignals(window) {
window.tile?.destroy();
// Disconnect signals
let s = this._wrappedWindows.get(window);
if (s) {
window.minimize = s[0];
window.maximize = s[1];
window.disconnect(s[2]);
window.disconnect(s[3]);
window.disconnect(s[4]);
window.disconnect(s[5]);
window.disconnect(s[6]);
window.disconnect(s[7]);
}
this._wrappedWindows.delete(window);
}
_removeWindow(window) {
// get Tile from window
let tile = window.tile;
// Not found
if (!tile)
return;
let m = tile.monitor;
if (TileWindowManager.getMonitors()[m].fullscreen) {
TileWindowManager.getMonitors()[m].fullscreen = false;
let focus = null;
TileWindowManager.getMonitors()[m].root?.forEach(el => {
el.state = TileState.DEFAULT;
el.window?.unminimize();
if (!focus && el.window && el.window !== window) {
el.window?.focus(0);
focus = el;
}
});
}
if (tile.removeTile() === null)
TileWindowManager.getMonitors(tile.workspace)[m].root = null;
else
TileWindowManager.getMonitors()[m].root?.update();
}
_onGrab(window, op) {
if (!window)
return;
let tile = window.tile;
if (!tile)
return;
let m = tile.monitor;
let rect;
switch (op) {
case Meta.GrabOp.MOVING:
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
TileWindowManager.getMonitors()[m].root?.update();
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_E:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeE(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_W:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeW(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_N:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeN(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_S:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeS(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_NE:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeN(tile, rect);
Resize.resizeE(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_NW:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeN(tile, rect);
Resize.resizeW(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_SE:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeS(tile, rect);
Resize.resizeE(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
case Meta.GrabOp.RESIZING_SW:
rect = window.get_frame_rect();
if (this._sourceId !== null)
GLib.Source.remove(this._sourceId);
this._sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Resize.resizeS(tile, rect);
Resize.resizeW(tile, rect);
this._sourceId = null;
return GLib.SOURCE_REMOVE;
});
break;
default:
break;
}
}
/** Resize operation with keyboard. This operation is
* different from the one operated with the mouse (grab operation)
* because we handle left/right resize differently.
*
* @param {Meta.GrabOp} op
* @param {number} resizeGap
* @returns void
*/
resizeFocusedWindow(op, resizeGap = 10) {
let window = global.display.focusWindow;
if (!window)
return;
let tile = window.tile;
if (op === Meta.GrabOp.RESIZING_E) {
if (tile.adjacents[1]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y,
width: tile.position.width + resizeGap,
height: tile.position.height,
});
Resize.resizeE(tile, r);
} else if (tile.adjacents[0]) {
let r = new Mtk.Rectangle({
x: tile.position.x + resizeGap,
y: tile.position.y,
width: tile.position.width - resizeGap,
height: tile.position.height,
});
Resize.resizeW(tile, r);
}
} else if (op === Meta.GrabOp.RESIZING_W) {
if (tile.adjacents[0]) {
let r = new Mtk.Rectangle({
x: tile.position.x - resizeGap,
y: tile.position.y,
width: tile.position.width + resizeGap,
height: tile.position.height,
});
Resize.resizeW(tile, r);
} else if (tile.adjacents[1]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y,
width: tile.position.width - resizeGap,
height: tile.position.height,
});
Resize.resizeE(tile, r);
}
} else if (op === Meta.GrabOp.RESIZING_N) {
if (tile.adjacents[2]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y - resizeGap,
width: tile.position.width,
height: tile.position.height + resizeGap,
});
Resize.resizeN(tile, r);
} else if (tile.adjacents[3]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y,
width: tile.position.width,
height: tile.position.height - resizeGap,
});
Resize.resizeS(tile, r);
}
} else if (op === Meta.GrabOp.RESIZING_S) {
if (tile.adjacents[3]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y,
width: tile.position.width,
height: tile.position.height + resizeGap,
});
Resize.resizeS(tile, r);
} else if (tile.adjacents[2]) {
let r = new Mtk.Rectangle({
x: tile.position.x,
y: tile.position.y + resizeGap,
width: tile.position.width,
height: tile.position.height - resizeGap,
});
Resize.resizeN(tile, r);
}
}
}
/** Clock wise windows rotation.
* Rotates the parent tile (if existing).
*
* @param {Meta.Window} window
* @returns
*/
rotateWindow(window) {
let tile = window.tile;
if (!tile)
return;
let parent = tile.parent;
if (!parent)
return;
let newPositions;
if (parent.orientation === Orientation.Horizontal) {
newPositions = parent.position.split(Orientation.Vertical);
if (parent.child1 && parent.child2) {
newPositions[TileWindowManager.rotateEven[0] === 0 ? 0 : 1].splitProportion = parent.child1.position.splitProportion;
newPositions[TileWindowManager.rotateEven[0] === 0 ? 1 : 0].splitProportion = parent.child2.position.splitProportion;
parent.child1.resize(newPositions[TileWindowManager.rotateEven[0] === 0 ? 0 : 1]);
parent.child2.resize(newPositions[TileWindowManager.rotateEven[0] === 0 ? 1 : 0]);
parent.forEach(el => el.findAdjacents());
TileWindowManager.rotateEven[0] = (TileWindowManager.rotateEven[0] + 1) % 2;
parent.update();
} else {
return;
}
parent.orientation = Orientation.Vertical;
} else if (parent.orientation === Orientation.Vertical) {
newPositions = parent.position.split(Orientation.Horizontal);
if (parent.child1 && parent.child2) {
newPositions[TileWindowManager.rotateEven[1] === 0 ? 1 : 0].splitProportion = parent.child1.position.splitProportion;
newPositions[TileWindowManager.rotateEven[1] === 0 ? 0 : 1].splitProportion = parent.child2.position.splitProportion;
parent.child1.resize(newPositions[TileWindowManager.rotateEven[1] === 0 ? 1 : 0]);
parent.child2.resize(newPositions[TileWindowManager.rotateEven[1] === 0 ? 0 : 1]);
parent.forEach(el => el.findAdjacents());
TileWindowManager.rotateEven[1] = (TileWindowManager.rotateEven[1] + 1) % 2;
parent.update();
} else {
return;
}
parent.orientation = Orientation.Horizontal;
}
}
/** Maximize the currently focused window.
* Others windows are reduced using tile specific state.
*
* @param {Meta.Window} window
* @param {boolean} replace true to stay in fullscreen and just replace the window otherwise false
* @returns
*/
maximizeTile(window, replace = false) {
let tile = window.tile;
if (!tile)
return;
let m = tile.monitor;
if (TileWindowManager.getMonitors()[tile.monitor].fullscreen) {
TileWindowManager.getMonitors()[tile.monitor].fullscreen = false;
TileWindowManager.getMonitors()[m].root?.forEach(el => {
el.state = TileState.DEFAULT;
if (el.window?.minimized)
el.window?.unminimize();
});
if (replace) {
TileWindowManager.getMonitors()[m].fullscreen = true;
TileWindowManager.getMonitors()[m].root?.forEach(el => {
if (el.id === tile.id)
el.state = TileState.MAXIMIZED;
else
el.state = TileState.MINIMIZED;
});
}
} else {
TileWindowManager.getMonitors()[tile.monitor].fullscreen = true;
TileWindowManager.getMonitors()[m].root?.forEach(el => {
if (el.id === tile.id)
el.state = TileState.MAXIMIZED;
else
el.state = TileState.MINIMIZED;
});
}
TileWindowManager.getMonitors()[m].root?.update();
}
moveTile(dir) {
let window = global.display.get_focus_window();
if (!window)
return;
let tile = window.tile;
if (!tile.window)
return;
let exchangeTile = TileWindowManager.getMonitors()[tile.monitor].closestTile(tile, dir);
if (!exchangeTile || !exchangeTile.window)
return;
let tmpWindow = exchangeTile.window;
exchangeTile.window = tile.window;
tile.window = tmpWindow;
tile.window.tile = tile;
exchangeTile.window.tile = exchangeTile;
TileWindowManager.getMonitors()[tile.monitor].root?.update();
}
changeFocus(dir) {
let window = global.display.get_focus_window();
if (!window)
return;
let tile = window.tile;
if (!tile.window)
return;
if (TileWindowManager.getMonitors()[tile.monitor].fullscreen === true) {
let mon = TileWindowManager.getMonitors()[tile.monitor].closestMonitor(dir);
if (mon === null)
return;
let newFocus;
switch (dir) {
case Direction.North:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.South);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.South:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.North);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.East:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.West);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.West:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.East);
if (newFocus)
newFocus?.window?.focus(0);
break;
}
} else {
let newFocus = TileWindowManager.getMonitors()[tile.monitor].closestTile(tile, dir);
if (newFocus === null) {
let mon = TileWindowManager.getMonitors()[tile.monitor].closestMonitor(dir);
if (mon === null)
return;
switch (dir) {
case Direction.North:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.South);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.South:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.North);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.East:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.West);
if (newFocus)
newFocus?.window?.focus(0);
break;
case Direction.West:
newFocus = TileWindowManager.getMonitors()[mon].getTile(Direction.East);
if (newFocus)
newFocus?.window?.focus(0);
break;
default:
}
} else {
newFocus?.window?.focus(0);
}
}
}
createSearchBar() {
if (this._topBarSearchEntry && this._topBarSearchEntry.isAlive()) {
this._topBarSearchEntry.destroy();
this._topBarSearchEntry = undefined;
return;
}
if (this._settings)
this._topBarSearchEntry = new TopBarSearchEntry(this._settings);
}
refresh() {
TileWindowManager.getMonitors().forEach(el => el.root ? el.root.update() : null);
}
moveToWorkspace(next) {
let window = global.display.get_focus_window();
let tile = window.tile;
let current = tile.workspace;
if (next) {
if (current < global.workspaceManager.get_n_workspaces() - 1) {
tile.workspace = current + 1;
window.change_workspace_by_index(tile.workspace, false);
this._removeWindow(window);
this._insertWindow(window, current + 1);
}
} else if (current > 0) {
tile.workspace = current - 1;
window.change_workspace_by_index(tile.workspace, false);
this._removeWindow(window);
this._insertWindow(window, current - 1);
}
this.updateMonitors();
this.updateAdjacents();
}
moveToNextMonitor() {
let window = global.display.get_focus_window();
this._removeWindow(window);
let tile = window.tile;
let monitors = TileWindowManager.getMonitors();
let newMonitorIndex = (tile.monitor + 1) % monitors.length;
let newMonitor = monitors[newMonitorIndex];
if (newMonitor.size() === 0) {
let newTile = Tile.createTileLeaf(window, new Position(1.0, 0, 0, 0, 0), newMonitorIndex);
newTile.workspace = window.get_workspace().index();
window.tile = newTile;
newMonitor.root = newTile;
newMonitor.root?.update();
} else {
// Easier to create a new tile for insertion
newMonitor.root?.addWindowOnBlock(window);
window.tile.workspace = window.get_workspace().index();
if (newMonitor.fullscreen)
window.tile.state = TileState.MINIMIZED;
newMonitor.root?.update();
}
}
/** Extension is disabled on screen lock.
* We save the state of the Monitors before we quit.
*
* @returns
*/
_saveBeforeSessionLock() {
if (!Main.sessionMode.isLocked)
return;
TileWindowManager.locked = true;
const userPath = GLib.get_user_config_dir();
const parentPath = GLib.build_filenamev([userPath, '/grimble']);
const parent = Gio.File.new_for_path(parentPath);
try {
parent.make_directory_with_parents(null);
} catch (e) {
if (e.code !== Gio.IOErrorEnum.EXISTS)
throw e;
}
const path = GLib.build_filenamev([parentPath, '/tilingWmSession2.json']);
const file = Gio.File.new_for_path(path);
try {
file.create(Gio.FileCreateFlags.NONE, null);
} catch (e) {
if (e.code !== Gio.IOErrorEnum.EXISTS)
throw e;
}
file.replace_contents(JSON.stringify({
windows: Array.from(TileWindowManager._workspaces.entries()),
}, (key, value) => {
if (value instanceof Meta.Window)
return value.get_id();
else if (key === '_parent') // remove cyclic references
return undefined;
else
return value;
}), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
}
_loadAfterSessionLock() {
if (!TileWindowManager.locked)
return;
TileWindowManager.locked = false;
const userPath = GLib.get_user_config_dir();
const path = GLib.build_filenamev([userPath, '/grimble/tilingWmSession2.json']);
const file = Gio.File.new_for_path(path);
if (!file.query_exists(null))
return;
try {
file.create(Gio.FileCreateFlags.NONE, null);
} catch (e) {
if (e.code !== Gio.IOErrorEnum.EXISTS)
throw e;
}
const [success, contents] = file.load_contents(null);
if (!success || !contents.length)
return;
const openWindows = global.display.list_all_windows();
const states = JSON.parse(new TextDecoder().decode(contents), (key, value) => key === '_window' && typeof value === 'number'
? openWindows.find(val => val.get_id() === value)
: value);
let map = new Map(states.windows);
map.forEach((mapValue, mapKey, _) => {
mapValue.forEach((value, index, array) => {
// We have to rebuild correct types of each object
array[index] = Monitor.fromObject(value);
array[index].root?.forEach(el => {
if (el.window) {
this.configureWindowSignals(el.window);
el.window.change_workspace_by_index(mapKey, false);
}
});
});
TileWindowManager._workspaces.set(mapKey, mapValue);
});
this.updateMonitors();
}
}