Update legacy.js

This commit is contained in:
2025-08-11 01:50:53 +02:00
committed by GitHub
parent 7c9e3e8122
commit 8feccbde10
+461 -338
View File
@@ -1,27 +1,24 @@
/////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////
// SimpleTiling LEGACY (GNOME Shell 3.38  44) // // Simple-Tiling LEGACY (for GNOME Shell 3.38) //
// © 2025domoel  MIT // // © 2025 domoel MIT //
///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////
'use strict'; 'use strict';
// ── GLOBAL IMPORTS ──────────────────────────────────────── const Main = imports.ui.main;
const Main = imports.ui.main; const Meta = imports.gi.Meta;
const Meta = imports.gi.Meta; const Shell = imports.gi.Shell;
const Shell = imports.gi.Shell; const Gio = imports.gi.Gio;
const Gio = imports.gi.Gio; const GLib = imports.gi.GLib;
const GLib = imports.gi.GLib; const ExtensionUtils = imports.misc.extensionUtils;
const ExtensionUtils= imports.misc.extensionUtils; const ByteArray = imports.byteArray;
const ByteArray = imports.byteArray;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const SCHEMA_NAME = "org.gnome.shell.extensions.simple-tiling.domoel";
const WM_SCHEMA = "org.gnome.desktop.wm.keybindings";
// ── CONST ──────────────────────────────────────────── const TILING_DELAY_MS = 20; // Change Tiling Window Delay
const SCHEMA_NAME = 'org.gnome.shell.extensions.simple-tiling.domoel'; const CENTERING_DELAY_MS = 5; // Change Centered Window Delay
const WM_SCHEMA = 'org.gnome.desktop.wm.keybindings';
const TILING_DELAY_MS = 20; // Change Tiling Window Delay
const CENTERING_DELAY_MS = 5; // Change Centered Window Delay
const KEYBINDINGS = { const KEYBINDINGS = {
'swap-master-window': (self) => self._swapWithMaster(), 'swap-master-window': (self) => self._swapWithMaster(),
@@ -35,285 +32,319 @@ const KEYBINDINGS = {
'focus-down': (self) => self._focusInDirection('down'), 'focus-down': (self) => self._focusInDirection('down'),
}; };
// ── HELPERFUNCTION ──────────────────────────────────────── // --- INTERACTIONHANDLER ---
function addKeybinding(name, settings, flags, mode, handler) {
if (Main.wm?.addKeybinding)
Main.wm.addKeybinding(name, settings, flags, mode, handler);
else
global.display.add_keybinding(name, settings, flags, mode, handler);
}
function removeKeybinding(name) {
if (Main.wm?.removeKeybinding)
Main.wm.removeKeybinding(name);
else
global.display.remove_keybinding(name);
}
function getWorkAreaForMonitor(monitorIndex) {
if (Main.layoutManager?.getWorkAreaForMonitor)
return Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
return global.workspace_manager
.get_active_workspace()
.get_work_area_for_monitor(monitorIndex);
}
function decodeUtf8(bytes) {
if (typeof ByteArray !== 'undefined')
return ByteArray.toString(bytes);
return new TextDecoder('utf-8').decode(bytes);
}
function getPointer() {
return global.get_pointer ? global.get_pointer()
: global.display.get_pointer();
}
// ── INTERACTIONHANDLER ───────────────────────────────────
class InteractionHandler { class InteractionHandler {
constructor(tiler) { constructor(tiler) {
this.tiler = tiler; this.tiler = tiler;
this._settings = ExtensionUtils.getSettings(SCHEMA_NAME); this._settings = ExtensionUtils.getSettings(SCHEMA_NAME);
this._wmSettings = new Gio.Settings({ schema: WM_SCHEMA }); this._wmSettings = new Gio.Settings({ schema: WM_SCHEMA });
this._wmKeysToDisable = []; this._wmKeysToDisable = [];
this._savedWmShortcuts= {}; this._savedWmShortcuts = {};
this._grabOpIds = []; this._grabOpIds = [];
this._settingsChangedId = null; this._settingsChangedId = null;
this._onSettingsChanged = this._onSettingsChanged.bind(this); this._onSettingsChanged = this._onSettingsChanged.bind(this);
this._prepareWmShortcuts(); this._prepareWmShortcuts();
} }
enable() { enable() {
if (this._wmKeysToDisable.length) if (this._wmKeysToDisable.length) {
this._wmKeysToDisable.forEach(k => this._wmKeysToDisable.forEach((key) =>
this._wmSettings.set_value(k, new GLib.Variant('as', []))); this._wmSettings.set_value(key, new GLib.Variant("as", []))
);
}
this._bindAllShortcuts(); this._bindAllShortcuts();
this._settingsChangedId = this._settingsChangedId = this._settings.connect(
this._settings.connect('changed', this._onSettingsChanged); "changed",
this._onSettingsChanged
);
this._grabOpIds.push( this._grabOpIds.push(
global.display.connect('grab-op-begin', global.display.connect(
(display, screen, win) => { "grab-op-begin",
if (this.tiler.windows.includes(win)) (display, screen, window) => {
this.tiler.grabbedWindow = win; if (this.tiler.windows.includes(window)) {
})); this.tiler.grabbedWindow = window;
}
}
)
);
this._grabOpIds.push( this._grabOpIds.push(
global.display.connect('grab-op-end', this._onGrabEnd.bind(this))); global.display.connect("grab-op-end", this._onGrabEnd.bind(this))
);
} }
disable() { disable() {
if (this._wmKeysToDisable.length) if (this._wmKeysToDisable.length) {
this._wmKeysToDisable.forEach(k => this._wmKeysToDisable.forEach((key) =>
this._wmSettings.set_value(k, this._savedWmShortcuts[k])); this._wmSettings.set_value(key, this._savedWmShortcuts[key])
);
}
this._unbindAllShortcuts(); this._unbindAllShortcuts();
if (this._settingsChangedId) { if (this._settingsChangedId) {
this._settings.disconnect(this._settingsChangedId); this._settings.disconnect(this._settingsChangedId);
this._settingsChangedId = null; this._settingsChangedId = null;
} }
this._grabOpIds.forEach(id => global.display.disconnect(id)); this._grabOpIds.forEach((id) => global.display.disconnect(id));
this._grabOpIds = []; this._grabOpIds = [];
} }
_bind(key, callback) { _bind(key, callback) {
addKeybinding(key, this._settings, Main.wm.addKeybinding(key, this._settings, Meta.KeyBindingFlags.NONE, Shell.ActionMode.NORMAL,
Meta.KeyBindingFlags.NONE, () => callback(this));
Shell.ActionMode.NORMAL, }
() => callback(this));
_bindAllShortcuts() {
for (const [key, handler] of Object.entries(KEYBINDINGS)) {
this._bind(key, handler);
}
}
_unbindAllShortcuts() {
for (const key in KEYBINDINGS) {
Main.wm.removeKeybinding(key);
}
} }
_bindAllShortcuts() { for (const [k,h] of Object.entries(KEYBINDINGS)) this._bind(k,h); }
_unbindAllShortcuts(){ for (const k in KEYBINDINGS) removeKeybinding(k); }
_onSettingsChanged() { _onSettingsChanged() {
this._unbindAllShortcuts(); this._unbindAllShortcuts();
this._bindAllShortcuts(); this._bindAllShortcuts();
} }
_prepareWmShortcuts() { _prepareWmShortcuts() {
const schema = this._wmSettings.settings_schema; const schema = this._wmSettings.settings_schema;
const keys = []; const keys = [];
if (schema.has_key("toggle-tiled-left"))
if (schema.has_key('toggle-tiled-left')) keys.push("toggle-tiled-left", "toggle-tiled-right");
keys.push('toggle-tiled-left','toggle-tiled-right'); else if (schema.has_key("tile-left"))
else if (schema.has_key('tile-left')) keys.push("tile-left", "tile-right");
keys.push('tile-left','tile-right'); if (schema.has_key("toggle-maximized")) keys.push("toggle-maximized");
if (schema.has_key('toggle-maximized'))
keys.push('toggle-maximized');
else { else {
if (schema.has_key('maximize')) keys.push('maximize'); if (schema.has_key("maximize")) keys.push("maximize");
if (schema.has_key('unmaximize')) keys.push('unmaximize'); if (schema.has_key("unmaximize")) keys.push("unmaximize");
} }
if (keys.length) { if (keys.length) {
this._wmKeysToDisable = keys; this._wmKeysToDisable = keys;
keys.forEach(k => this._savedWmShortcuts[k] = keys.forEach(
this._wmSettings.get_value(k)); (key) => (this._savedWmShortcuts[key] = this._wmSettings.get_value(key))
);
} }
} }
_focusInDirection(direction) { _focusInDirection(direction) {
const src = global.display.get_focus_window(); const sourceWindow = global.display.get_focus_window();
if (!src || !this.tiler.windows.includes(src)) return; if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return;
const tgt = this._findTargetInDirection(src, direction);
if (tgt) tgt.activate(global.get_current_time()); const targetWindow = this._findTargetInDirection(
sourceWindow,
direction
);
if (targetWindow) {
targetWindow.activate(global.get_current_time());
}
} }
_swapWithMaster() { _swapWithMaster() {
const w = this.tiler.windows; const windows = this.tiler.windows;
if (w.length < 2) return; if (windows.length < 2) return;
const foc = global.display.get_focus_window(); const focusedWindow = global.display.get_focus_window();
if (!foc || !w.includes(foc)) return; if (!focusedWindow || !windows.includes(focusedWindow)) return;
const idx = w.indexOf(foc); const focusedIndex = windows.indexOf(focusedWindow);
if (idx > 0) if (focusedIndex > 0) {
[w[0], w[idx]] = [w[idx], w[0]]; [windows[0], windows[focusedIndex]] = [
else windows[focusedIndex],
[w[0], w[1]] = [w[1], w[0]]; windows[0],
];
} else if (focusedIndex === 0) {
[windows[0], windows[1]] = [windows[1], windows[0]];
}
this.tiler.tileNow(); this.tiler.tileNow();
w[0]?.activate(global.get_current_time()); if (windows.length > 0) windows[0].activate(global.get_current_time());
} }
_swapInDirection(direction) { _swapInDirection(direction) {
const src = global.display.get_focus_window(); const sourceWindow = global.display.get_focus_window();
if (!src || !this.tiler.windows.includes(src)) return; if (!sourceWindow || !this.tiler.windows.includes(sourceWindow)) return;
let targetWindow = null;
let tgt = null; const sourceIndex = this.tiler.windows.indexOf(sourceWindow);
const srcIdx = this.tiler.windows.indexOf(src); if (
if (srcIdx === 0 && direction === 'right' && this.tiler.windows.length>1) sourceIndex === 0 &&
tgt = this.tiler.windows[1]; direction === "right" &&
else this.tiler.windows.length > 1
tgt = this._findTargetInDirection(src, direction); ) {
targetWindow = this.tiler.windows[1];
if (!tgt) return; } else {
const tgtIdx = this.tiler.windows.indexOf(tgt); targetWindow = this._findTargetInDirection(sourceWindow, direction);
[this.tiler.windows[srcIdx], this.tiler.windows[tgtIdx]] = }
[this.tiler.windows[tgtIdx], this.tiler.windows[srcIdx]]; if (!targetWindow) return;
const targetIndex = this.tiler.windows.indexOf(targetWindow);
[this.tiler.windows[sourceIndex], this.tiler.windows[targetIndex]] = [
this.tiler.windows[targetIndex],
this.tiler.windows[sourceIndex],
];
this.tiler.tileNow(); this.tiler.tileNow();
src.activate(global.get_current_time()); sourceWindow.activate(global.get_current_time());
} }
_findTargetInDirection(src, direction) { _findTargetInDirection(source, direction) {
const sRect = src.get_frame_rect(); const sourceRect = source.get_frame_rect();
const cands = []; let candidates = [];
for (const win of this.tiler.windows) { for (const win of this.tiler.windows) {
if (win === src) continue; if (win === source) continue;
const tRect = win.get_frame_rect(); const targetRect = win.get_frame_rect();
switch (direction) { switch (direction) {
case 'left': if (tRect.x < sRect.x) cands.push(win); break; case "left":
case 'right': if (tRect.x > sRect.x) cands.push(win); break; if (targetRect.x < sourceRect.x) candidates.push(win);
case 'up': if (tRect.y < sRect.y) cands.push(win); break; break;
case 'down': if (tRect.y > sRect.y) cands.push(win); break; case "right":
if (targetRect.x > sourceRect.x) candidates.push(win);
break;
case "up":
if (targetRect.y < sourceRect.y) candidates.push(win);
break;
case "down":
if (targetRect.y > sourceRect.y) candidates.push(win);
break;
} }
} }
if (!cands.length) return null; if (candidates.length === 0) return null;
let bestTarget = null;
let best=null, min=Infinity; let minDeviation = Infinity;
for (const win of cands) { for (const win of candidates) {
const tRect = win.get_frame_rect(); const targetRect = win.get_frame_rect();
const dev = (direction==='left'||direction==='right') let deviation;
? Math.abs(sRect.y - tRect.y) if (direction === "left" || direction === "right") {
: Math.abs(sRect.x - tRect.x); deviation = Math.abs(sourceRect.y - targetRect.y);
if (dev < min) { min=dev; best=win; } } else {
deviation = Math.abs(sourceRect.x - targetRect.x);
}
if (deviation < minDeviation) {
minDeviation = deviation;
bestTarget = win;
}
} }
return best; return bestTarget;
} }
_onGrabEnd() { _onGrabEnd() {
const grabbed = this.tiler.grabbedWindow; const grabbedWindow = this.tiler.grabbedWindow;
if (!grabbed) return; if (!grabbedWindow) return;
const targetWindow = this._findTargetUnderPointer(grabbedWindow);
const tgt = this._findTargetUnderPointer(grabbed); if (targetWindow) {
if (tgt) { const sourceIndex = this.tiler.windows.indexOf(grabbedWindow);
const a = this.tiler.windows.indexOf(grabbed); const targetIndex = this.tiler.windows.indexOf(targetWindow);
const b = this.tiler.windows.indexOf(tgt); [
[this.tiler.windows[a], this.tiler.windows[b]] = this.tiler.windows[sourceIndex],
[this.tiler.windows[b], this.tiler.windows[a]]; this.tiler.windows[targetIndex],
] = [
this.tiler.windows[targetIndex],
this.tiler.windows[sourceIndex],
];
} }
this.tiler.queueTile(); this.tiler.queueTile();
this.tiler.grabbedWindow = null; this.tiler.grabbedWindow = null;
} }
_findTargetUnderPointer(exclude) { _findTargetUnderPointer(excludeWindow) {
const [x,y] = getPointer(); let [pointerX, pointerY] = global.get_pointer();
const wins = global.get_window_actors() let windows = global
.map(a => a.meta_window) .get_window_actors()
.filter(w => w && w!==exclude && .map((actor) => actor.meta_window)
this.tiler.windows.includes(w) && .filter((win) => {
((()=>{ const f=w.get_frame_rect(); if (
return x>=f.x && x<f.x+f.width && !win ||
y>=f.y && y<f.y+f.height;})())); win === excludeWindow ||
if (wins.length) return wins[wins.length-1]; !this.tiler.windows.includes(win)
)
let best=null, max=0, sRect=exclude.get_frame_rect(); return false;
for (const w of this.tiler.windows) { let frame = win.get_frame_rect();
if (w===exclude) continue; return (
const tRect=w.get_frame_rect(); pointerX >= frame.x &&
const ovX = Math.max(0, Math.min(sRect.x+sRect.width, pointerX < frame.x + frame.width &&
tRect.x+tRect.width) - pointerY >= frame.y &&
Math.max(sRect.x, tRect.x)); pointerY < frame.y + frame.height
const ovY = Math.max(0, Math.min(sRect.y+sRect.height, );
tRect.y+tRect.height) - });
Math.max(sRect.y, tRect.y)); if (windows.length > 0) {
const area = ovX*ovY; return windows[windows.length - 1];
if (area>max){ max=area; best=w; }
} }
return best;
let bestTarget = null;
let maxOverlap = 0;
const sourceFrame = excludeWindow.get_frame_rect();
for (const win of this.tiler.windows) {
if (win === excludeWindow) continue;
const targetFrame = win.get_frame_rect();
const overlapX = Math.max(
0,
Math.min(
sourceFrame.x + sourceFrame.width,
targetFrame.x + targetFrame.width
) - Math.max(sourceFrame.x, targetFrame.x)
);
const overlapY = Math.max(
0,
Math.min(
sourceFrame.y + sourceFrame.height,
targetFrame.y + targetFrame.height
) - Math.max(sourceFrame.y, targetFrame.y)
);
const overlapArea = overlapX * overlapY;
if (overlapArea > maxOverlap) {
maxOverlap = overlapArea;
bestTarget = win;
}
}
return bestTarget;
} }
} }
// ── TILER ──────────────────────────────────────────────── // --- TILER ---
class Tiler { class Tiler {
constructor() { constructor() {
this.windows = []; this.windows = [];
this.grabbedWindow = null; this.grabbedWindow = null;
this._settings = ExtensionUtils.getSettings(SCHEMA_NAME); this._settings = ExtensionUtils.getSettings(SCHEMA_NAME);
this._signalIds = new Map();
this._tileInProgress = false;
this._signalIds = new Map(); this._innerGap = this._settings.get_int("inner-gap");
this._tileTimeoutId = null; this._outerGapVertical = this._settings.get_int("outer-gap-vertical");
this._centerTimeoutIds= []; this._outerGapHorizontal = this._settings.get_int("outer-gap-horizontal");
this._tileInProgress = false;
this._innerGap = this._settings.get_int('inner-gap'); this._tilingDelay = TILING_DELAY_MS;
this._outerGapVertical= this._settings.get_int('outer-gap-vertical'); this._centeringDelay = CENTERING_DELAY_MS;
this._outerGapHorizontal = this._settings.get_int('outer-gap-horizontal');
this._tilingDelay = TILING_DELAY_MS; this._exceptions = [];
this._centeringDelay= CENTERING_DELAY_MS;
this._exceptions = [];
this._interactionHandler = new InteractionHandler(this); this._interactionHandler = new InteractionHandler(this);
this._onWindowAdded = this._onWindowAdded.bind(this); this._tileTimeoutId = null;
this._onWindowRemoved= this._onWindowRemoved.bind(this); this._centerTimeoutIds = [];
this._onActiveWorkspaceChanged =
this._onActiveWorkspaceChanged.bind(this); this._onWindowAdded = this._onWindowAdded.bind(this);
this._onWindowMinimizedStateChanged = this._onWindowRemoved = this._onWindowRemoved.bind(this);
this._onWindowMinimizedStateChanged.bind(this); this._onActiveWorkspaceChanged = this._onActiveWorkspaceChanged.bind(
this
);
this._onWindowMinimizedStateChanged = this._onWindowMinimizedStateChanged.bind(
this
);
this._onSettingsChanged = this._onSettingsChanged.bind(this); this._onSettingsChanged = this._onSettingsChanged.bind(this);
} }
enable() { enable() {
this._loadExceptions(); this._loadExceptions();
const workspaceManager = global.workspace_manager;
const wm = global.workspace_manager; this._signalIds.set("workspace-changed", {
this._signalIds.set('workspace-changed', { object: workspaceManager,
object: wm, id: workspaceManager.connect(
id: wm.connect('active-workspace-changed', "active-workspace-changed",
this._onActiveWorkspaceChanged), this._onActiveWorkspaceChanged
),
}); });
this._connectToWorkspace(); this._connectToWorkspace();
this._interactionHandler.enable(); this._interactionHandler.enable();
this._signalIds.set("settings-changed", {
this._signalIds.set('settings-changed', {
object: this._settings, object: this._settings,
id: this._settings.connect('changed', this._onSettingsChanged), id: this._settings.connect("changed", this._onSettingsChanged),
}); });
} }
@@ -327,112 +358,147 @@ class Tiler {
this._interactionHandler.disable(); this._interactionHandler.disable();
this._disconnectFromWorkspace(); this._disconnectFromWorkspace();
for (const [, signal] of this._signalIds) {
for (const [,sig] of this._signalIds) { try {
try { sig.object.disconnect(sig.id); } catch {} signal.object.disconnect(signal.id);
} catch (e) {}
} }
this._signalIds.clear(); this._signalIds.clear();
this.windows = []; this.windows = [];
} }
_onSettingsChanged() { _onSettingsChanged() {
this._innerGap = this._settings.get_int('inner-gap'); this._innerGap = this._settings.get_int("inner-gap");
this._outerGapVertical = this._settings.get_int('outer-gap-vertical'); this._outerGapVertical = this._settings.get_int("outer-gap-vertical");
this._outerGapHorizontal= this._settings.get_int('outer-gap-horizontal'); this._outerGapHorizontal = this._settings.get_int("outer-gap-horizontal");
this.queueTile(); this.queueTile();
} }
_loadExceptions() { _loadExceptions() {
const file = Gio.File.new_for_path(Me.path + '/exceptions.txt'); const file = Gio.file_new_for_path(Me.path + "/exceptions.txt");
if (!file.query_exists(null)) { this._exceptions=[]; return; } if (!file.query_exists(null)) {
this._exceptions = [];
return;
}
const [ok, data] = file.load_contents(null); const [ok, data] = file.load_contents(null);
this._exceptions = ok ? decodeUtf8(data) this._exceptions = ok
.split('\n') ? ByteArray.toString(data)
.map(l => l.trim()) .split("\n")
.filter(l => l && !l.startsWith('#')) .map((l) => l.trim())
.map(l => l.toLowerCase()) .filter((l) => l && !l.startsWith("#"))
: []; .map((l) => l.toLowerCase())
: [];
} }
_isException(win) { _isException(win) {
if (!win) return false; if (!win) return false;
const wmClass = (win.get_wm_class() || '').toLowerCase(); const wmClass = (win.get_wm_class() || "").toLowerCase();
const appId = (win.get_gtk_application_id() || '').toLowerCase(); const appId = (win.get_gtk_application_id() || "").toLowerCase();
return this._exceptions.includes(wmClass) || this._exceptions.includes(appId); return this._exceptions.includes(wmClass) || this._exceptions.includes(appId);
} }
_isTileable(win) { _isTileable(win) {
return win && !win.minimized && !this._isException(win) && return (
win.get_window_type() === Meta.WindowType.NORMAL; win &&
!win.minimized &&
!this._isException(win) &&
win.get_window_type() === Meta.WindowType.NORMAL
);
} }
_centerWindow(win) { _centerWindow(win) {
const id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, const timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._centeringDelay, () => {
this._centeringDelay, () => { const index = this._centerTimeoutIds.indexOf(timeoutId);
const idx = this._centerTimeoutIds.indexOf(id); if (index > -1) {
if (idx>-1) this._centerTimeoutIds.splice(idx,1); this._centerTimeoutIds.splice(index, 1);
}
if (!win || !win.get_display()) return GLib.SOURCE_REMOVE; if (!win || !win.get_display()) return GLib.SOURCE_REMOVE;
if (win.get_maximized()) if (win.get_maximized()) {
win.unmaximize(Meta.MaximizeFlags.BOTH); win.unmaximize(Meta.MaximizeFlags.BOTH);
}
const monitorIndex = win.get_monitor(); const monitorIndex = win.get_monitor();
const workArea = getWorkAreaForMonitor(monitorIndex); const workArea = Main.layoutManager.getWorkAreaForMonitor(
const frame = win.get_frame_rect(); monitorIndex
win.move_frame(true, );
workArea.x + Math.floor((workArea.width - frame.width )/2), const frame = win.get_frame_rect();
workArea.y + Math.floor((workArea.height - frame.height)/2)); win.move_frame(
true,
workArea.x + Math.floor((workArea.width - frame.width) / 2),
workArea.y + Math.floor((workArea.height - frame.height) / 2)
);
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
if (win.get_display()) { if (win.get_display()) {
if (typeof win.set_keep_above === 'function') if (typeof win.set_keep_above === "function")
win.set_keep_above(true); win.set_keep_above(true);
else if (typeof win.make_above === 'function') else if (typeof win.make_above === "function")
win.make_above(); win.make_above();
} }
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
this._centerTimeoutIds.push(id);
this._centerTimeoutIds.push(timeoutId);
} }
_onWindowMinimizedStateChanged(){ this.queueTile(); } _onWindowMinimizedStateChanged() {
this.queueTile();
}
_onWindowAdded(workspace, win) { _onWindowAdded(workspace, win) {
if (this.windows.includes(win)) return; if (this.windows.includes(win)) return;
if (this._isException(win)) { this._centerWindow(win); return; } if (this._isException(win)) {
this._centerWindow(win);
return;
}
if (this._isTileable(win)) { if (this._isTileable(win)) {
if (this._settings.get_string('new-window-behavior') === 'master') if (this._settings.get_string("new-window-behavior") === "master") {
this.windows.unshift(win); this.windows.unshift(win);
else } else {
this.windows.push(win); this.windows.push(win);
}
const id = win.get_id(); const id = win.get_id();
this._signalIds.set(`unmanaged-${id}`, { this._signalIds.set(`unmanaged-${id}`, {
object: win, id: win.connect('unmanaged', object: win,
()=>this._onWindowRemoved(null, win))}); id: win.connect("unmanaged", () =>
this._signalIds.set(`size-${id}`, { this._onWindowRemoved(null, win)
object: win, id: win.connect('size-changed', ),
()=>{ if (!this.grabbedWindow) this.queueTile(); })}); });
this._signalIds.set(`min-${id}`, { this._signalIds.set(`size-changed-${id}`, {
object: win, id: win.connect('notify::minimized', object: win,
this._onWindowMinimizedStateChanged)}); id: win.connect("size-changed", () => {
if (!this.grabbedWindow) this.queueTile();
}),
});
this._signalIds.set(`minimized-${id}`, {
object: win,
id: win.connect(
"notify::minimized",
this._onWindowMinimizedStateChanged
),
});
this.queueTile(); this.queueTile();
} }
} }
_onWindowRemoved(workspace, win) { _onWindowRemoved(workspace, win) {
const idx = this.windows.indexOf(win); const index = this.windows.indexOf(win);
if (idx>-1) this.windows.splice(idx,1); if (index > -1) {
this.windows.splice(index, 1);
}
['unmanaged','size','min'].forEach(pref=>{ ["unmanaged", "size-changed", "minimized"].forEach((prefix) => {
const key = `${pref}-${win.get_id()}`; const key = `${prefix}-${win.get_id()}`;
if (this._signalIds.has(key)) { if (this._signalIds.has(key)) {
const {object,id} = this._signalIds.get(key); const { object, id } = this._signalIds.get(key);
try{ object.disconnect(id);}catch{} try {
object.disconnect(id);
} catch (e) {}
this._signalIds.delete(key); this._signalIds.delete(key);
} }
}); });
@@ -445,21 +511,31 @@ class Tiler {
} }
_connectToWorkspace() { _connectToWorkspace() {
const ws = global.workspace_manager.get_active_workspace(); const workspace = global.workspace_manager.get_active_workspace();
ws.list_windows().forEach(w=>this._onWindowAdded(ws,w)); workspace
this._signalIds.set('win-add', { .list_windows()
object: ws, id: ws.connect('window-added', this._onWindowAdded)}); .forEach((win) => this._onWindowAdded(workspace, win));
this._signalIds.set('win-rem', { this._signalIds.set("window-added", {
object: ws, id: ws.connect('window-removed', this._onWindowRemoved)}); object: workspace,
id: workspace.connect("window-added", this._onWindowAdded),
});
this._signalIds.set("window-removed", {
object: workspace,
id: workspace.connect("window-removed", this._onWindowRemoved),
});
this.queueTile(); this.queueTile();
} }
_disconnectFromWorkspace() { _disconnectFromWorkspace() {
this.windows.slice().forEach(w=>this._onWindowRemoved(null,w)); this.windows.slice().forEach((win) => this._onWindowRemoved(null, win));
['win-add','win-rem'].forEach(k=>{
if (this._signalIds.has(k)) { ["window-added", "window-removed"].forEach((key) => {
const {object,id}=this._signalIds.get(k); if (this._signalIds.has(key)) {
try{ object.disconnect(id);}catch{} const { object, id } = this._signalIds.get(key);
this._signalIds.delete(k); try {
object.disconnect(id);
} catch (e) {}
this._signalIds.delete(key);
} }
}); });
} }
@@ -467,88 +543,135 @@ class Tiler {
queueTile() { queueTile() {
if (this._tileInProgress || this._tileTimeoutId) return; if (this._tileInProgress || this._tileTimeoutId) return;
this._tileInProgress = true; this._tileInProgress = true;
this._tileTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
this._tilingDelay, () => { this._tileTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._tilingDelay, () => {
this._tileWindows(); this._tileWindows();
this._tileInProgress = false; this._tileInProgress = false;
this._tileTimeoutId = null; this._tileTimeoutId = null;
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
} }
tileNow() { if (!this._tileInProgress) this._tileWindows(); }
tileNow() {
if (!this._tileInProgress) {
this._tileWindows();
}
}
_splitLayout(windows, area) { _splitLayout(windows, area) {
if (!windows.length) return; if (windows.length === 0) return;
if (windows.length === 1) { if (windows.length === 1) {
windows[0].move_resize_frame(true, windows[0].move_resize_frame(
area.x, area.y, area.width, area.height); true,
area.x,
area.y,
area.width,
area.height
);
return; return;
} }
const gap = Math.floor(this._innerGap/2); const gap = Math.floor(this._innerGap / 2);
const prim = [windows[0]]; const primaryWindows = [windows[0]];
const sec = windows.slice(1); const secondaryWindows = windows.slice(1);
let primaryArea, secondaryArea;
let primArea, secArea;
if (area.width > area.height) { if (area.width > area.height) {
const pW = Math.floor(area.width/2) - gap; const primaryWidth = Math.floor(area.width / 2) - gap;
primArea = {x: area.x, y: area.y, primaryArea = {
width: pW, height: area.height}; x: area.x,
secArea = {x: area.x+pW+this._innerGap, y: area.y, y: area.y,
width: area.width-pW-this._innerGap, width: primaryWidth,
height: area.height}; height: area.height,
};
secondaryArea = {
x: area.x + primaryWidth + this._innerGap,
y: area.y,
width: area.width - primaryWidth - this._innerGap,
height: area.height,
};
} else { } else {
const pH = Math.floor(area.height/2) - gap; const primaryHeight = Math.floor(area.height / 2) - gap;
primArea = {x: area.x, y: area.y, primaryArea = {
width: area.width, height: pH}; x: area.x,
secArea = {x: area.x, y: area.y+pH+this._innerGap, y: area.y,
width: area.width, width: area.width,
height: area.height-pH-this._innerGap}; height: primaryHeight,
};
secondaryArea = {
x: area.x,
y: area.y + primaryHeight + this._innerGap,
width: area.width,
height: area.height - primaryHeight - this._innerGap,
};
} }
this._splitLayout(prim, primArea);
this._splitLayout(sec, secArea); this._splitLayout(primaryWindows, primaryArea);
this._splitLayout(secondaryWindows, secondaryArea);
} }
_tileWindows() { _tileWindows() {
const wins = this.windows.filter(w=>!w.minimized); const windowsToTile = this.windows.filter((win) => !win.minimized);
if (!wins.length) return; if (windowsToTile.length === 0) return;
const monitor = Main.layoutManager.primaryMonitor; const monitor = Main.layoutManager.primaryMonitor;
const work = getWorkAreaForMonitor(monitor.index); const workArea = Main.layoutManager.getWorkAreaForMonitor(
const inner = { x: work.x + this._outerGapHorizontal, monitor.index
y: work.y + this._outerGapVertical, );
width: work.width - 2*this._outerGapHorizontal, const innerArea = {
height: work.height - 2*this._outerGapVertical }; x: workArea.x + this._outerGapHorizontal,
y: workArea.y + this._outerGapVertical,
width: workArea.width - 2 * this._outerGapHorizontal,
height: workArea.height - 2 * this._outerGapVertical,
};
wins.forEach(w=>{ if (w.get_maximized()) windowsToTile.forEach((win) => {
w.unmaximize(Meta.MaximizeFlags.BOTH); }); if (win.get_maximized()) win.unmaximize(Meta.MaximizeFlags.BOTH);
});
if (wins.length===1) { if (windowsToTile.length === 1) {
wins[0].move_resize_frame(true, windowsToTile[0].move_resize_frame(
inner.x, inner.y, inner.width, inner.height); true,
innerArea.x,
innerArea.y,
innerArea.width,
innerArea.height
);
return; return;
} }
const gap = Math.floor(this._innerGap/2); const gap = Math.floor(this._innerGap / 2);
const masterW = Math.floor(inner.width/2) - gap; const masterWidth = Math.floor(innerArea.width / 2) - gap;
const master = wins[0]; const master = windowsToTile[0];
master.move_resize_frame(true, master.move_resize_frame(
inner.x, inner.y, masterW, inner.height); true,
innerArea.x,
const stack = { x: inner.x + masterW + this._innerGap, innerArea.y,
y: inner.y, masterWidth,
width: inner.width - masterW - this._innerGap, innerArea.height
height: inner.height }; );
this._splitLayout(wins.slice(1), stack); const stackArea = {
x: innerArea.x + masterWidth + this._innerGap,
y: innerArea.y,
width: innerArea.width - masterWidth - this._innerGap,
height: innerArea.height,
};
this._splitLayout(windowsToTile.slice(1), stackArea);
} }
} }
// ── EXTENSIONWRAPPER ─────────────────────────────────── // --- EXTENSION-WRAPPER (for legacy loader) ---
class SimpleTilingExtension { var LegacyExtension = class {
enable() { this.tiler = new Tiler(); this.tiler.enable(); } constructor(metadata) {
disable() { this.tiler?.disable(); this.tiler = null; } this.tiler = null;
} }
enable() {
function init() { this.tiler = new Tiler();
return new SimpleTilingExtension(); this.tiler.enable();
} }
disable() {
if (this.tiler) {
this.tiler.disable();
this.tiler = null;
}
}
};