932 lines
36 KiB
JavaScript
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();
|
|
}
|
|
}
|