From 2446520ced19a0b3f7c3b8b6baee3cbbff647d16 Mon Sep 17 00:00:00 2001 From: Lucas Oskorep Date: Thu, 16 Oct 2025 00:09:11 -0400 Subject: [PATCH] feat: adding in ability to resize windows in a container --- extension.ts | 14 +++ ...ome.shell.extensions.aerospike.gschema.xml | 6 + src/wm/container.ts | 109 +++++++++++++++--- src/wm/monitor.ts | 17 +++ src/wm/windowManager.ts | 31 ++++- 5 files changed, 156 insertions(+), 21 deletions(-) diff --git a/extension.ts b/extension.ts index a503b40..77813ff 100644 --- a/extension.ts +++ b/extension.ts @@ -53,6 +53,11 @@ export default class aerospike extends Extension { this.refreshKeybinding('join-with-right'); }); + this.settings.connect('changed::reset-window-sizes', () => { + log(`Reset window sizes keybinding changed to: ${this.settings.get_strv('reset-window-sizes')}`); + this.refreshKeybinding('reset-window-sizes'); + }); + this.settings.connect('changed::dropdown-option', () => { log(`Dropdown option changed to: ${this.settings.get_string('dropdown-option')}`); }); @@ -88,6 +93,11 @@ export default class aerospike extends Extension { Logger.info('Keybinding 4 was pressed!'); }); break; + case 'reset-window-sizes': + this.bindKeybinding('reset-window-sizes', () => { + this.windowManager.resetAllWindowSizes(); + }); + break; } } @@ -114,6 +124,10 @@ export default class aerospike extends Extension { this.bindKeybinding('join-with-right', () => { Logger.info('Keybinding 4 was pressed!'); }); + + this.bindKeybinding('reset-window-sizes', () => { + this.windowManager.resetAllWindowSizes(); + }); } private bindKeybinding(settingName: string, callback: () => void) { diff --git a/schemas/org.gnome.shell.extensions.aerospike.gschema.xml b/schemas/org.gnome.shell.extensions.aerospike.gschema.xml index cdea8da..e8a8f68 100644 --- a/schemas/org.gnome.shell.extensions.aerospike.gschema.xml +++ b/schemas/org.gnome.shell.extensions.aerospike.gschema.xml @@ -37,5 +37,11 @@ Keyboard shortcut for triggering action 4 + + z']]]> + Reset all window sizes + Remove all custom window sizes and return to equal distribution + + \ No newline at end of file diff --git a/src/wm/container.ts b/src/wm/container.ts index 159d477..4135dd7 100644 --- a/src/wm/container.ts +++ b/src/wm/container.ts @@ -16,12 +16,13 @@ export default class WindowContainer { _tiledWindowLookup: Map; _orientation: Orientation = Orientation.HORIZONTAL; _workArea: Rect; + _customSizes: Map; constructor(workspaceArea: Rect,) { - // this._id = monitorId; this._tiledItems = []; this._tiledWindowLookup = new Map(); this._workArea = workspaceArea; + this._customSizes = new Map(); } @@ -34,7 +35,6 @@ export default class WindowContainer { // Add window to managed windows this._tiledItems.push(winWrap); this._tiledWindowLookup.set(winWrap.getWindowId(), winWrap); - // winWrap.setParent(this); queueEvent({ name: "tiling-windows", callback: () => { @@ -74,6 +74,7 @@ export default class WindowContainer { removeWindow(win_id: number): void { if (this._tiledWindowLookup.has(win_id)) { this._tiledWindowLookup.delete(win_id); + this._customSizes.delete(win_id); // Clean up custom size when window is removed const index = this._getIndexOfWindow(win_id) this._tiledItems.splice(index, 1); } else { @@ -104,10 +105,7 @@ export default class WindowContainer { tileWindows() { Logger.log("TILING WINDOWS IN CONTAINER") - Logger.log("WorkArea", this._workArea); - - // Get all windows for current workspaceArea this._tileItems() return true @@ -137,30 +135,72 @@ export default class WindowContainer { } getVerticalBounds(): Rect[] { - const items = this._tiledItems - const containerHeight = Math.floor(this._workArea.height / items.length); - return items.map((_, index) => { - const y = this._workArea.y + (index * containerHeight); - return { + // Calculate available height after accounting for custom-sized windows + let totalCustomHeight = 0; + let numFlexibleItems = 0; + + this._tiledItems.forEach((item) => { + if (item instanceof WindowWrapper && this._customSizes.has(item.getWindowId())) { + totalCustomHeight += this._customSizes.get(item.getWindowId())!; + } else { + numFlexibleItems++; + } + }); + + const remainingHeight = this._workArea.height - totalCustomHeight; + const flexHeight = numFlexibleItems > 0 ? Math.floor(remainingHeight / numFlexibleItems) : 0; + + // Build the bounds array + let currentY = this._workArea.y; + return this._tiledItems.map((item) => { + let height = flexHeight; + if (item instanceof WindowWrapper && this._customSizes.has(item.getWindowId())) { + height = this._customSizes.get(item.getWindowId())!; + } + + const rect = { x: this._workArea.x, - y: y, + y: currentY, width: this._workArea.width, - height: containerHeight + height: height } as Rect; + currentY += height; + return rect; }); } getHorizontalBounds(): Rect[] { - const windowWidth = Math.floor(this._workArea.width / this._tiledItems.length); + // Calculate available width after accounting for custom-sized windows + let totalCustomWidth = 0; + let numFlexibleItems = 0; - return this._tiledItems.map((_, index) => { - const x = this._workArea.x + (index * windowWidth); - return { - x: x, + this._tiledItems.forEach((item) => { + if (item instanceof WindowWrapper && this._customSizes.has(item.getWindowId())) { + totalCustomWidth += this._customSizes.get(item.getWindowId())!; + } else { + numFlexibleItems++; + } + }); + + const remainingWidth = this._workArea.width - totalCustomWidth; + const flexWidth = numFlexibleItems > 0 ? Math.floor(remainingWidth / numFlexibleItems) : 0; + + // Build the bounds array + let currentX = this._workArea.x; + return this._tiledItems.map((item) => { + let width = flexWidth; + if (item instanceof WindowWrapper && this._customSizes.has(item.getWindowId())) { + width = this._customSizes.get(item.getWindowId())!; + } + + const rect = { + x: currentX, y: this._workArea.y, - width: windowWidth, + width: width, height: this._workArea.height } as Rect; + currentX += width; + return rect; }); } @@ -201,5 +241,38 @@ export default class WindowContainer { } + windowManuallyResized(win_id: number): void { + const window = this.getWindow(win_id); + if (!window) { + // Check nested containers + for (const item of this._tiledItems) { + if (item instanceof WindowContainer) { + item.windowManuallyResized(win_id); + } + } + return; + } + + const rect = window.getRect(); + if (this._orientation === Orientation.HORIZONTAL) { + this._customSizes.set(win_id, rect.width); + Logger.log(`Window ${win_id} manually resized to width: ${rect.width}`); + } else { + this._customSizes.set(win_id, rect.height); + Logger.log(`Window ${win_id} manually resized to height: ${rect.height}`); + } + } + + resetAllWindowSizes(): void { + Logger.log("Clearing all custom window sizes in container"); + this._customSizes.clear(); + // Also clear nested containers + for (const item of this._tiledItems) { + if (item instanceof WindowContainer) { + item.resetAllWindowSizes(); + } + } + } + } \ No newline at end of file diff --git a/src/wm/monitor.ts b/src/wm/monitor.ts index 640f74d..6d12a6c 100644 --- a/src/wm/monitor.ts +++ b/src/wm/monitor.ts @@ -83,4 +83,21 @@ export default class Monitor { this._workspaces[item.getWorkspace()].itemDragged(item, x, y); } + windowManuallyResized(win_id: number): void { + // Find which workspace contains the window and notify it + for (const container of this._workspaces) { + const win = container.getWindow(win_id); + if (win) { + container.windowManuallyResized(win_id); + return; + } + } + } + + resetAllWindowSizes(): void { + for (const container of this._workspaces) { + container.resetAllWindowSizes(); + } + } + } \ No newline at end of file diff --git a/src/wm/windowManager.ts b/src/wm/windowManager.ts index d095593..1fd5ac6 100644 --- a/src/wm/windowManager.ts +++ b/src/wm/windowManager.ts @@ -199,9 +199,6 @@ export default class WindowManager implements IWindowManager { handleGrabOpBegin(display: Meta.Display, window: Meta.Window, op: Meta.GrabOp): void { - if (op === Meta.GrabOp.MOVING_UNCONSTRAINED){ - - } Logger.log("Grab Op Start", op); Logger.log(display, window, op) Logger.log(window.get_monitor()) @@ -210,9 +207,29 @@ export default class WindowManager implements IWindowManager { this._grabbedWindowId = window.get_id(); } + _isResizeOperation(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_E || + op === Meta.GrabOp.RESIZING_W || + op === Meta.GrabOp.RESIZING_N || + op === Meta.GrabOp.RESIZING_S || + op === Meta.GrabOp.RESIZING_NE || + op === Meta.GrabOp.RESIZING_NW || + op === Meta.GrabOp.RESIZING_SE || + op === Meta.GrabOp.RESIZING_SW; + } + handleGrabOpEnd(display: Meta.Display, window: Meta.Window, op: Meta.GrabOp): void { Logger.log("Grab Op End ", op); Logger.log("primary display", display.get_primary_monitor()) + + // Check if this was a resize operation + if (this._isResizeOperation(op)) { + const monitor = this._monitors.get(window.get_monitor()); + if (monitor) { + monitor.windowManuallyResized(window.get_id()); + } + } + this._grabbedWindowId = _UNUSED_WINDOW_ID; this._getWrappedWindow(window)?.stopDragging(); this._tileMonitors(); @@ -419,5 +436,13 @@ export default class WindowManager implements IWindowManager { return null; } + public resetAllWindowSizes(): void { + Logger.log("Resetting all custom window sizes"); + this._monitors.forEach((monitor: Monitor) => { + monitor.resetAllWindowSizes(); + }); + this._tileMonitors(); + } + }