diff --git a/src/monitor.ts b/src/container.ts similarity index 58% rename from src/monitor.ts rename to src/container.ts index 9749cbe..8ccfddf 100644 --- a/src/monitor.ts +++ b/src/container.ts @@ -2,26 +2,37 @@ import {WindowWrapper} from "./window.js"; import {Logger} from "./utils/logger.js"; import Mtk from "@girs/mtk-16"; import Meta from "gi://Meta"; +import GLib from "gi://GLib"; +import queueEvent from "./utils/events.js"; +import {Rect} from "./utils/rect.js"; -export default class MonitorManager { +export default class WindowContainer { _id: number; _windows: Map; - _minimized: Map; + _minimizedWindows: Map; + _workArea: Rect; - - constructor(monitorId: number) { + constructor(monitorId: number, workspaceArea: Rect) { this._windows = new Map(); - this._minimized = new Map(); - + this._minimizedWindows = new Map(); + const workspace = global.workspace_manager.get_active_workspace(); this._id = monitorId; + const _workArea = workspace.get_work_area_for_monitor( + this._id + ); } addWindow(winWrap: WindowWrapper): void { // Add window to managed windows this._windows.set(winWrap.getWindowId(), winWrap); - this._tileWindows(); + queueEvent({ + name: "tiling-windows", + callback: () => { + this._tileWindows(); + } + }, 100) } getWindow(win_id: number): WindowWrapper | undefined { @@ -30,18 +41,19 @@ export default class MonitorManager { removeWindow(win_id: number): void { this._windows.delete(win_id) + // TODO: Should there be re-tiling in this function? this._tileWindows() } minimizeWindow(winWrap: WindowWrapper): void { this._windows.delete(winWrap.getWindowId()) - this._minimized.set(winWrap.getWindowId(), winWrap) + this._minimizedWindows.set(winWrap.getWindowId(), winWrap) } unminimizeWindow(winWrap: WindowWrapper): void { - if (this._minimized.has(winWrap.getWindowId())) { + if (this._minimizedWindows.has(winWrap.getWindowId())) { this._windows.set(winWrap.getWindowId(), winWrap); - this._minimized.delete(winWrap.getWindowId()); + this._minimizedWindows.delete(winWrap.getWindowId()); } } @@ -62,27 +74,31 @@ export default class MonitorManager { const workArea = workspace.get_work_area_for_monitor( this._id ); + Logger.log("Workspace", workspace); Logger.log("WorkArea", workArea); // Get all windows for current workspace - const windows = Array.from(this._windows.values()) - .filter(({_window}) => { - if (_window != null) { - return _window.get_workspace() === workspace; - } - }) - .map(x => x); + let windows = this._getTilableWindows(workspace) - if (windows.length === 0) { - return; + if (windows.length !== 0) { + this._tileHorizontally(windows, workArea) } - this._tileHorizontally(windows, workArea) + return true } + _getTilableWindows(workspace: Meta.Workspace): WindowWrapper[] { - _tileHorizontally(windows: (WindowWrapper | null)[], workArea: Mtk.Rectangle) { + return Array.from(this._windows.values()) + .filter(({_window}) => { + Logger.log("TILING WINDOW:", _window.get_id()) + return _window.get_workspace() === workspace; + }) + .map(x => x); + } + + _tileHorizontally(windows: (WindowWrapper)[], workArea: Mtk.Rectangle) { const windowWidth = Math.floor(workArea.width / windows.length); windows.forEach((window, index) => { @@ -94,7 +110,7 @@ export default class MonitorManager { height: workArea.height }; if (window != null) { - window.safelyResizeWindow(rect.x, rect.y, rect.width, rect.height); + window.safelyResizeWindow(rect); } }); } diff --git a/src/utils/events.ts b/src/utils/events.ts new file mode 100644 index 0000000..0332280 --- /dev/null +++ b/src/utils/events.ts @@ -0,0 +1,20 @@ +import GLib from "gi://GLib"; + + +export type QueuedEvent = { + name: string; + callback: () => void; +} + +const queuedEvents: QueuedEvent[] = []; + +export default function queueEvent(event: QueuedEvent, interval = 200) { + queuedEvents.push(event); + GLib.timeout_add(GLib.PRIORITY_DEFAULT, interval, () => { + const e = queuedEvents.pop() + if (e) { + e.callback(); + } + return queuedEvents.length !== 0; + }); +} \ No newline at end of file diff --git a/src/utils/rect.ts b/src/utils/rect.ts new file mode 100644 index 0000000..8e4eea3 --- /dev/null +++ b/src/utils/rect.ts @@ -0,0 +1,6 @@ +export type Rect = { + x: number; + y: number; + width: number; + height: number; +} \ No newline at end of file diff --git a/src/window.ts b/src/window.ts index 02dd895..ae08531 100644 --- a/src/window.ts +++ b/src/window.ts @@ -1,24 +1,16 @@ import Meta from 'gi://Meta'; -import St from "gi://St"; import Clutter from "gi://Clutter"; -import GObject from "gi://GObject"; -import GLib from "gi://GLib"; -import * as Main from "resource:///org/gnome/shell/ui/main.js"; import {IWindowManager} from "./windowManager.js"; import {Logger} from "./utils/logger.js"; -import MaximizeFlags = Meta.MaximizeFlags; +import {Rect} from "./utils/rect.js"; -export type Signal = { - name: string; - id: number; -} type WindowMinimizedHandler = (window: WindowWrapper) => void; export class WindowWrapper { readonly _window: Meta.Window; readonly _windowMinimizedHandler: WindowMinimizedHandler; - readonly _signals: Signal[]; + readonly _signals: number[]; constructor(window: Meta.Window, winMinimized: WindowMinimizedHandler) { this._window = window; @@ -37,58 +29,46 @@ export class WindowWrapper { connectWindowSignals( windowManager: IWindowManager, ): void { - - const windowId = this._window.get_id(); - + const windowId = this._window.get_id() // Handle window destruction - const destroyId = this._window.connect('unmanaging', window => { - Logger.log("REMOVING WINDOW", windowId); - windowManager.handleWindowClosed(this) - }); - this._signals.push({name: 'unmanaging', id: destroyId}); + this._signals.push( + this._window.connect('unmanaging', window => { + Logger.log("REMOVING WINDOW", windowId); + windowManager.handleWindowClosed(this) + }), + this._window.connect('notify::minimized', () => { + if (this._window.minimized) { + Logger.log(`Window minimized: ${windowId}`); + windowManager.handleWindowMinimized(this); - // Handle focus changes - const focusId = this._window.connect('notify::has-focus', () => { - if (this._window.has_focus()) { - windowManager._activeWindowId = windowId; - } - }); - this._signals.push({name: 'notify::has-focus', id: focusId}); + } else if (!this._window.minimized) { + Logger.log(`Window unminimized: ${windowId}`); + windowManager.handleWindowUnminimized(this); - - // Handle minimization - const minimizeId = this._window.connect('notify::minimized', () => { - if (this._window.minimized) { - Logger.log(`Window minimized: ${windowId}`); - windowManager.handleWindowMinimized(this); - - } else if (!this._window.minimized) { - Logger.log(`Window unminimized: ${windowId}`); - windowManager.handleWindowUnminimized(this); - - } - }); - this._signals.push({name: 'notify::minimized', id: minimizeId}); - - // Handle maximization - const maximizeId = this._window.connect('notify::maximized-horizontally', () => { - if (this._window.get_maximized()) { - Logger.log(`Window maximized: ${windowId}`); - } else { - Logger.log(`Window unmaximized: ${windowId}`); - } - }); - this._signals.push({name: 'notify::maximized-horizontally', id: maximizeId}); + } + }), + this._window.connect('notify::has-focus', () => { + if (this._window.has_focus()) { + windowManager._activeWindowId = windowId; + } + }), + this._window.connect('notify::maximized-horizontally', () => { + if (this._window.get_maximized()) { + Logger.log(`Window maximized: ${windowId}`); + } else { + Logger.log(`Window unmaximized: ${windowId}`); + } + }), + ); } disconnectWindowSignals(): void { - // Disconnect signals if (this._signals) { this._signals.forEach(signal => { try { if (this._window != null) { - this._window.disconnect(signal.id); + this._window.disconnect(signal); } } catch (e) { Logger.warn("error disconnecting signal", signal, e); @@ -97,49 +77,24 @@ export class WindowWrapper { } } - resizeWindow(x: number, y: number, width: number, height: number) { - Logger.info(this._window.allows_move()) - Logger.info(this._window.allows_resize()) - - if (this._window.get_maximized() == MaximizeFlags.BOTH || this._window.is_fullscreen() || this._window.is_monitor_sized()) { - Logger.info("is monitor sized?", this._window.is_monitor_sized()); - Logger.info("is monitor sized?", this._window.is_fullscreen()); - Logger.info("is monitor sized?", this._window.get_maximized()); - Logger.info("is monitor sized?", this._window.get_maximized() == MaximizeFlags.BOTH); - - Logger.info("window is fullscreen or maximized and will not be resized", this._window) - return; - // Logger.log("WINDOW IS FULLSCREEN") - // this._window.unmake_fullscreen(); - } - this._window.move_resize_frame(false, x, y, width, height); - } - // This is meant to be an exact copy of Forge's move function, renamed to maintain your API - safelyResizeWindow(x: number, y: number, width: number, height: number): void { + safelyResizeWindow(rect: Rect): void { // Keep minimal logging - Logger.log("SAFELY RESIZE", x, y, width, height); - - // Simple early returns like Forge - if (!this._window) return; - - // Skip the this._window.grabbed check since we confirmed it doesn't exist in Meta.Window - - // Unmaximize in all directions - no try/catch to match Forge - this._window.unmaximize(Meta.MaximizeFlags.HORIZONTAL); - this._window.unmaximize(Meta.MaximizeFlags.VERTICAL); - this._window.unmaximize(Meta.MaximizeFlags.BOTH); - - // Get actor and return early if not available - no try/catch - const windowActor = this._window.get_compositor_private() as Clutter.Actor; + Logger.log("SAFELY RESIZE", rect.x, rect.y, rect.width, rect.height); + const actor = this._window.get_compositor_private(); + + if (!actor) { + Logger.log("No actor available, can't resize safely yet"); + return; + } + let windowActor = this._window.get_compositor_private() as Clutter.Actor; if (!windowActor) return; - - // Remove transitions - no try/catch windowActor.remove_all_transitions(); - - // Move and resize in exact order as Forge - no try/catch - this._window.move_frame(true, x, y); - this._window.move_resize_frame(true, x, y, width, height); + Logger.info("MOVING") + this._window.move_frame(true, rect.x, rect.y); + Logger.info("RESIZING MOVING") + this._window.move_resize_frame(true, rect.x, rect.y, rect.width, rect.height); + } diff --git a/src/windowManager.ts b/src/windowManager.ts index 5380091..aba8da1 100644 --- a/src/windowManager.ts +++ b/src/windowManager.ts @@ -6,8 +6,9 @@ import {WindowWrapper} from './window.js'; import * as Main from "resource:///org/gnome/shell/ui/main.js"; import Mtk from "@girs/mtk-16"; import {Logger} from "./utils/logger.js"; -import MonitorManager from "./monitor.js"; +import WindowContainer from "./container.js"; import {MessageTray} from "@girs/gnome-shell/ui/messageTray"; +import queueEvent, {QueuedEvent} from "./utils/events.js"; export interface IWindowManager { @@ -34,7 +35,7 @@ export default class WindowManager implements IWindowManager { _activeWindowId: number | null; _grabbedWindowMonitor: number; - _monitors: Map; + _monitors: Map; _sessionProxy: Gio.DBusProxy | null; _lockedSignalId: number | null; _isScreenLocked: boolean; @@ -46,7 +47,7 @@ export default class WindowManager implements IWindowManager { this._overviewSignals = []; this._activeWindowId = null; this._grabbedWindowMonitor = _UNUSED_MONITOR_ID; - this._monitors = new Map(); + this._monitors = new Map(); this._sessionProxy = null; this._lockedSignalId = null; this._isScreenLocked = false; // Initialize to unlocked state @@ -60,7 +61,7 @@ export default class WindowManager implements IWindowManager { const mon_count = global.display.get_n_monitors(); for (let i = 0; i < mon_count; i++) { - this._monitors.set(i, new MonitorManager(i)); + this._monitors.set(i, new WindowContainer(i)); } this.captureExistingWindows(); @@ -116,16 +117,12 @@ export default class WindowManager implements IWindowManager { Main.overview.connect("hiding", () => { // this.fromOverview = true; Logger.log("HIDING OVERVIEW") - const eventObj = { - name: "focus-after-overview", - callback: () => { - // const focusNodeWindow = this.tree.findNode(this.focusMetaWindow); - // this.updateStackedFocus(focusNodeWindow); - // this.updateTabbedFocus(focusNodeWindow); - // this.movePointerWith(focusNodeWindow); - Logger.log("FOCUSING AFTER OVERVIEW"); - }, - }; + // const eventObj = { + // name: "focus-after-overview", + // callback: () => { + // Logger.log("FOCUSING AFTER OVERVIEW"); + // }, + // }; // this.queueEvent(eventObj); }), Main.overview.connect("showing", () => { @@ -145,7 +142,7 @@ export default class WindowManager implements IWindowManager { } removeAllWindows(): void { - this._monitors.forEach((monitor: MonitorManager) => { + this._monitors.forEach((monitor: WindowContainer) => { monitor.removeAllWindows(); }) } @@ -157,7 +154,7 @@ export default class WindowManager implements IWindowManager { } disconnectMonitorSignals(): void { - this._monitors.forEach((monitor: MonitorManager) => { + this._monitors.forEach((monitor: WindowContainer) => { monitor.disconnectSignals(); }) } @@ -251,10 +248,6 @@ export default class WindowManager implements IWindowManager { return; } Logger.log("WINDOW IS TILABLE"); - const actor = window.get_compositor_private(); - if (!actor) { - return; - } this.addWindowToMonitor(window); } @@ -264,7 +257,7 @@ export default class WindowManager implements IWindowManager { */ handleWindowClosed(window: WindowWrapper): void { - window.disconnectWindowSignals() + // window.disconnectWindowSignals() const mon_id = window._window.get_monitor(); this._monitors.get(mon_id)?.removeWindow(window.getWindowId()); @@ -276,18 +269,22 @@ export default class WindowManager implements IWindowManager { public addWindowToMonitor(window: Meta.Window) { + Logger.log("ADDING WINDOW TO MONITOR", window, window); var wrapper = new WindowWrapper(window, this.handleWindowMinimized) - wrapper.connectWindowSignals(this) + wrapper.connectWindowSignals(this); + // wrapper.connectWindowSignals(this) this._monitors.get(window.get_monitor())?.addWindow(wrapper) } _tileMonitors(): void { + for (const monitor of this._monitors.values()) { monitor._tileWindows() } } _isWindowTileable(window: Meta.Window) { + if (!window || !window.get_compositor_private()) { return false; }