diff --git a/README.md b/README.md index 2e0ff42..accd172 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# Aerospike Gnome (Tiling Window Manager) \ No newline at end of file +# Aerospike Gnome (Tiling Window Manager) diff --git a/extension.ts b/extension.ts index a503b40..bbb1c85 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::remove-all-dividers', () => { + log(`Keybinding remove-all-dividers changed to: ${this.settings.get_strv('remove-all-dividers')}`); + this.refreshKeybinding('remove-all-dividers'); + }); + this.settings.connect('changed::dropdown-option', () => { log(`Dropdown option changed to: ${this.settings.get_string('dropdown-option')}`); }); @@ -88,6 +93,12 @@ export default class aerospike extends Extension { Logger.info('Keybinding 4 was pressed!'); }); break; + case 'remove-all-dividers': + this.bindKeybinding('remove-all-dividers', () => { + Logger.info('Remove all dividers keybinding pressed!'); + this.windowManager.removeAllDividersFromActiveContainer(); + }); + break; } } @@ -114,6 +125,11 @@ export default class aerospike extends Extension { this.bindKeybinding('join-with-right', () => { Logger.info('Keybinding 4 was pressed!'); }); + + this.bindKeybinding('remove-all-dividers', () => { + Logger.info('Remove all dividers keybinding pressed!'); + this.windowManager.removeAllDividersFromActiveContainer(); + }); } private bindKeybinding(settingName: string, callback: () => void) { diff --git a/justfile b/justfile index 86e9f39..1c9b80b 100644 --- a/justfile +++ b/justfile @@ -28,7 +28,7 @@ install: build cp -r dist/* ~/.local/share/gnome-shell/extensions/{{NAME}}@{{DOMAIN}}/ run: - env MUTTER_DEBUG_DUMMY_MODE_SPECS=1280x720 dbus-run-session -- gnome-shell --devkit --wayland + env MUTTER_DEBUG_DUMMY_MODE_SPECS=1280x720 dbus-run-session -- gnome-shell --nested --wayland install-and-run: install run @@ -42,4 +42,4 @@ live-debug: # --schema ../schemas/org.gnome.shell.extensions.aerospike.gschema.xml # #install-pack: pack -# gnome-extensions install ./{{FULL_NAME}}.shell-extension.zip --force \ No newline at end of file +# gnome-extensions install ./{{FULL_NAME}}.shell-extension.zip --force diff --git a/prettyborders.zip b/prettyborders.zip deleted file mode 100644 index 8ad6a1b..0000000 Binary files a/prettyborders.zip and /dev/null differ diff --git a/schemas/org.gnome.shell.extensions.aerospike.gschema.xml b/schemas/org.gnome.shell.extensions.aerospike.gschema.xml index cdea8da..172e380 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']]]> + Remove all dividers from active container + Keyboard shortcut for removing all dividers from the container with the active window + + \ No newline at end of file diff --git a/src/wm/container.ts b/src/wm/container.ts index 159d477..c36e033 100644 --- a/src/wm/container.ts +++ b/src/wm/container.ts @@ -3,16 +3,18 @@ import {Logger} from "../utils/logger.js"; import Meta from "gi://Meta"; import queueEvent from "../utils/events.js"; import {Rect} from "../utils/rect.js"; +import {Divider} from "./divider.js"; enum Orientation { HORIZONTAL = 0, VERTICAL = 1, } +type ContainerItem = WindowWrapper | WindowContainer | Divider; export default class WindowContainer { - _tiledItems: (WindowWrapper | WindowContainer)[]; + _tiledItems: ContainerItem[]; _tiledWindowLookup: Map; _orientation: Orientation = Orientation.HORIZONTAL; _workArea: Rect; @@ -49,12 +51,14 @@ export default class WindowContainer { return this._tiledWindowLookup.get(win_id); } for (const item of this._tiledItems) { - if (item instanceof WindowContainer) { + if (Divider.isDivider(item)) { + continue; // Skip dividers + } else if (item instanceof WindowContainer) { const win = item.getWindow(win_id); if (win) { return win; } - } else if (item.getWindowId() === win_id) { + } else if (item instanceof WindowWrapper && item.getWindowId() === win_id) { return item; } } @@ -76,6 +80,7 @@ export default class WindowContainer { this._tiledWindowLookup.delete(win_id); const index = this._getIndexOfWindow(win_id) this._tiledItems.splice(index, 1); + this._cleanupInvalidDividers(); } else { for (const item of this._tiledItems) { if (item instanceof WindowContainer) { @@ -86,11 +91,44 @@ export default class WindowContainer { this.tileWindows() } + /** + * Removes invalid dividers from the items list. + * Invalid dividers are: + * - Dividers at the start or end of the list (no window on one side) + * - Consecutive dividers (two dividers in a row) + */ + _cleanupInvalidDividers(): void { + let i = 0; + while (i < this._tiledItems.length) { + const item = this._tiledItems[i]; + + if (Divider.isDivider(item)) { + // Check if divider is at start or end + const isAtStart = i === 0; + const isAtEnd = i === this._tiledItems.length - 1; + + // Check if next item is also a divider + const nextIsDivider = i < this._tiledItems.length - 1 && + Divider.isDivider(this._tiledItems[i + 1]); + + if (isAtStart || isAtEnd || nextIsDivider) { + Logger.log(`Removing invalid divider at index ${i}`); + this._tiledItems.splice(i, 1); + continue; // Don't increment i, check the same position again + } + } + i++; + } + } + disconnectSignals(): void { this._tiledItems.forEach((item) => { - if (item instanceof WindowContainer) { + if (Divider.isDivider(item)) { + // Skip dividers - they don't have signals + return; + } else if (item instanceof WindowContainer) { item.disconnectSignals() - } else { + } else if (item instanceof WindowWrapper) { item.disconnectWindowSignals(); } } @@ -118,13 +156,21 @@ export default class WindowContainer { return; } const bounds = this.getBounds(); - this._tiledItems.forEach((item, index) => { - const rect = bounds[index]; + + // Apply bounds to non-divider items + let boundsIndex = 0; + this._tiledItems.forEach((item) => { + if (Divider.isDivider(item)) { + return; // Skip dividers + } + + const rect = bounds[boundsIndex]; if (item instanceof WindowContainer) { item.move(rect); - } else { + } else if (item instanceof WindowWrapper) { item.safelyResizeWindow(rect); } + boundsIndex++; }) } @@ -137,42 +183,177 @@ 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 { - x: this._workArea.x, - y: y, - width: this._workArea.width, - height: containerHeight - } as Rect; - }); + // Filter out dividers to get only windows/containers + const nonDividerItems = this._tiledItems.filter(item => !Divider.isDivider(item)); + + if (nonDividerItems.length === 0) { + return []; + } + + // If no dividers, use equal distribution + const hasDividers = this._tiledItems.some(item => Divider.isDivider(item)); + if (!hasDividers) { + const containerHeight = Math.floor(this._workArea.height / nonDividerItems.length); + return nonDividerItems.map((_, index) => { + const y = this._workArea.y + (index * containerHeight); + return { + x: this._workArea.x, + y: y, + width: this._workArea.width, + height: containerHeight + } as Rect; + }); + } + + // Calculate bounds based on divider positions + const bounds: Rect[] = []; + let currentY = this._workArea.y; + let itemIndex = 0; + + for (let i = 0; i < this._tiledItems.length; i++) { + const item = this._tiledItems[i]; + + if (Divider.isDivider(item)) { + // Next segment starts at divider position + currentY = this._workArea.y + Math.floor(item.getPosition() * this._workArea.height); + } else { + // Find the end position for this item + let endY: number = this._workArea.y + this._workArea.height; + + // Look ahead to find next divider or end of container + for (let j = i + 1; j < this._tiledItems.length; j++) { + if (Divider.isDivider(this._tiledItems[j])) { + const divider = this._tiledItems[j] as Divider; + endY = this._workArea.y + Math.floor(divider.getPosition() * this._workArea.height); + break; + } + } + + // Count non-divider items until next divider + let itemCount = 0; + let itemsInSegment: number[] = []; + for (let j = i; j < this._tiledItems.length; j++) { + if (Divider.isDivider(this._tiledItems[j])) { + break; + } + itemsInSegment.push(j); + itemCount++; + } + + // Divide space equally among items in this segment + const segmentHeight = endY - currentY; + const itemHeight = Math.floor(segmentHeight / itemCount); + + for (let k = 0; k < itemsInSegment.length; k++) { + const itemY = currentY + (k * itemHeight); + bounds.push({ + x: this._workArea.x, + y: itemY, + width: this._workArea.width, + height: itemHeight + } as Rect); + } + + // Skip the items we just processed + i += itemCount - 1; + currentY = endY; + } + } + + return bounds; } getHorizontalBounds(): Rect[] { - const windowWidth = Math.floor(this._workArea.width / this._tiledItems.length); + // Filter out dividers to get only windows/containers + const nonDividerItems = this._tiledItems.filter(item => !Divider.isDivider(item)); - return this._tiledItems.map((_, index) => { - const x = this._workArea.x + (index * windowWidth); - return { - x: x, - y: this._workArea.y, - width: windowWidth, - height: this._workArea.height - } as Rect; - }); + if (nonDividerItems.length === 0) { + return []; + } + + // If no dividers, use equal distribution + const hasDividers = this._tiledItems.some(item => Divider.isDivider(item)); + if (!hasDividers) { + const windowWidth = Math.floor(this._workArea.width / nonDividerItems.length); + return nonDividerItems.map((_, index) => { + const x = this._workArea.x + (index * windowWidth); + return { + x: x, + y: this._workArea.y, + width: windowWidth, + height: this._workArea.height + } as Rect; + }); + } + + // Calculate bounds based on divider positions + const bounds: Rect[] = []; + let currentX = this._workArea.x; + + for (let i = 0; i < this._tiledItems.length; i++) { + const item = this._tiledItems[i]; + + if (Divider.isDivider(item)) { + // Next segment starts at divider position + currentX = this._workArea.x + Math.floor(item.getPosition() * this._workArea.width); + } else { + // Find the end position for this item + let endX: number = this._workArea.x + this._workArea.width; + + // Look ahead to find next divider or end of container + for (let j = i + 1; j < this._tiledItems.length; j++) { + if (Divider.isDivider(this._tiledItems[j])) { + const divider = this._tiledItems[j] as Divider; + endX = this._workArea.x + Math.floor(divider.getPosition() * this._workArea.width); + break; + } + } + + // Count non-divider items until next divider + let itemCount = 0; + let itemsInSegment: number[] = []; + for (let j = i; j < this._tiledItems.length; j++) { + if (Divider.isDivider(this._tiledItems[j])) { + break; + } + itemsInSegment.push(j); + itemCount++; + } + + // Divide space equally among items in this segment + const segmentWidth = endX - currentX; + const itemWidth = Math.floor(segmentWidth / itemCount); + + for (let k = 0; k < itemsInSegment.length; k++) { + const itemX = currentX + (k * itemWidth); + bounds.push({ + x: itemX, + y: this._workArea.y, + width: itemWidth, + height: this._workArea.height + } as Rect); + } + + // Skip the items we just processed + i += itemCount - 1; + currentX = endX; + } + } + + return bounds; } getIndexOfItemNested(item: WindowWrapper): number { for (let i = 0; i < this._tiledItems.length; i++) { const container = this._tiledItems[i]; - if (container instanceof WindowContainer) { + if (Divider.isDivider(container)) { + continue; // Skip dividers + } else if (container instanceof WindowContainer) { const index = container.getIndexOfItemNested(item); if (index !== -1) { return i; } - } else if (container.getWindowId() === item.getWindowId()) { + } else if (container instanceof WindowWrapper && container.getWindowId() === item.getWindowId()) { return i; } } @@ -181,24 +362,271 @@ export default class WindowContainer { // TODO: update this to work with nested containers - all other logic should already be working itemDragged(item: WindowWrapper, x: number, y: number): void { - let original_index = this.getIndexOfItemNested(item); + // Find the actual index in _tiledItems (including dividers) + const original_actual_index = this._getIndexOfWindow(item.getWindowId()); - if (original_index === -1) { + if (original_actual_index === -1) { Logger.error("Item not found in container during drag op", item.getWindowId()); return; } - let new_index = this.getIndexOfItemNested(item); - this.getBounds().forEach((rect, index) => { + + // Find which visual slot (non-divider index) we're moving to + let new_visual_index = this.getIndexOfItemNested(item); + const bounds = this.getBounds(); + bounds.forEach((rect, index) => { if (rect.x < x && rect.x + rect.width > x && rect.y < y && rect.y + rect.height > y) { - new_index = index; + new_visual_index = index; } }) - if (original_index !== new_index) { - this._tiledItems.splice(original_index, 1); - this._tiledItems.splice(new_index, 0, item); - this.tileWindows() + + // Get current visual index (counting only non-dividers before this item) + let original_visual_index = 0; + for (let i = 0; i < original_actual_index; i++) { + if (!Divider.isDivider(this._tiledItems[i])) { + original_visual_index++; + } } + if (original_visual_index === new_visual_index) { + return; // No movement needed + } + + Logger.log(`Swapping window from visual index ${original_visual_index} to ${new_visual_index}`); + + // Find the target window at the new visual index + let target_actual_index = -1; + let visual_count = 0; + for (let i = 0; i < this._tiledItems.length; i++) { + if (!Divider.isDivider(this._tiledItems[i])) { + if (visual_count === new_visual_index) { + target_actual_index = i; + break; + } + visual_count++; + } + } + + if (target_actual_index === -1) { + Logger.warn("Could not find target position for drag"); + return; + } + + // Simply swap the two windows in place, leaving dividers where they are + const temp = this._tiledItems[original_actual_index]; + this._tiledItems[original_actual_index] = this._tiledItems[target_actual_index]; + this._tiledItems[target_actual_index] = temp; + + this.tileWindows(); + } + + /** + * Handles window resize operations. Creates or updates dividers based on resize direction. + * @param item - The window being resized + * @param resizeEdge - The edge being resized (N, S, E, W, etc.) + * @param newRect - The new rectangle after resize + */ + handleWindowResize(item: WindowWrapper, resizeEdge: Meta.GrabOp, newRect: Rect): void { + const itemIndex = this._getIndexOfWindow(item.getWindowId()); + if (itemIndex === -1) { + Logger.warn("Window not found in container during resize", item.getWindowId()); + return; + } + + // Determine if this is a valid resize for this container orientation + const isHorizontalResize = this._isHorizontalResizeOp(resizeEdge); + const isVerticalResize = this._isVerticalResizeOp(resizeEdge); + + // Only allow horizontal resizes in horizontal containers + // Only allow vertical resizes in vertical containers + if (this._orientation === Orientation.HORIZONTAL && !isHorizontalResize) { + Logger.log("Ignoring vertical resize in horizontal container"); + return; + } + if (this._orientation === Orientation.VERTICAL && !isVerticalResize) { + Logger.log("Ignoring horizontal resize in vertical container"); + return; + } + + // Determine which edge is being resized and find adjacent window + let adjacentIndex = -1; + let dividerPosition = 0; + + if (this._orientation === Orientation.HORIZONTAL) { + // East/West resize + if (this._isEastResizeOp(resizeEdge)) { + // Resizing east edge - divider goes after this window + adjacentIndex = itemIndex + 1; + // Calculate divider position as ratio of container width + const rightEdge = newRect.x + newRect.width; + dividerPosition = (rightEdge - this._workArea.x) / this._workArea.width; + } else if (this._isWestResizeOp(resizeEdge)) { + // Resizing west edge - divider goes before this window + adjacentIndex = itemIndex - 1; + dividerPosition = (newRect.x - this._workArea.x) / this._workArea.width; + } + } else { + // Vertical orientation - North/South resize + if (this._isSouthResizeOp(resizeEdge)) { + // Resizing south edge - divider goes after this window + adjacentIndex = itemIndex + 1; + const bottomEdge = newRect.y + newRect.height; + dividerPosition = (bottomEdge - this._workArea.y) / this._workArea.height; + } else if (this._isNorthResizeOp(resizeEdge)) { + // Resizing north edge - divider goes before this window + adjacentIndex = itemIndex - 1; + dividerPosition = (newRect.y - this._workArea.y) / this._workArea.height; + } + } + + // Make sure there's an adjacent item + if (adjacentIndex < 0 || adjacentIndex >= this._tiledItems.length) { + Logger.log("No adjacent window for resize operation"); + return; + } + + // Skip if adjacent item is already a divider + if (Divider.isDivider(this._tiledItems[adjacentIndex])) { + // Update existing divider + const divider = this._tiledItems[adjacentIndex] as Divider; + divider.setPosition(dividerPosition); + Logger.log(`Updated divider at index ${adjacentIndex} to position ${dividerPosition}`); + } else { + // Insert new divider between items + const dividerIndex = Math.max(itemIndex, adjacentIndex); + const newDivider = new Divider(dividerPosition, this._orientation); + this._tiledItems.splice(dividerIndex, 0, newDivider); + Logger.log(`Inserted new divider at index ${dividerIndex} with position ${dividerPosition}`); + } + + this.tileWindows(); + } + + private _isHorizontalResizeOp(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_E || + op === Meta.GrabOp.RESIZING_W || + op === Meta.GrabOp.RESIZING_NE || + op === Meta.GrabOp.RESIZING_NW || + op === Meta.GrabOp.RESIZING_SE || + op === Meta.GrabOp.RESIZING_SW; + } + + private _isVerticalResizeOp(op: Meta.GrabOp): boolean { + return 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; + } + + private _isEastResizeOp(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_E || + op === Meta.GrabOp.RESIZING_NE || + op === Meta.GrabOp.RESIZING_SE; + } + + private _isWestResizeOp(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_W || + op === Meta.GrabOp.RESIZING_NW || + op === Meta.GrabOp.RESIZING_SW; + } + + private _isSouthResizeOp(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_S || + op === Meta.GrabOp.RESIZING_SE || + op === Meta.GrabOp.RESIZING_SW; + } + + private _isNorthResizeOp(op: Meta.GrabOp): boolean { + return op === Meta.GrabOp.RESIZING_N || + op === Meta.GrabOp.RESIZING_NE || + op === Meta.GrabOp.RESIZING_NW; + } + + /** + * Removes all dividers from this container, reverting to equal space distribution + */ + removeAllDividers(): void { + Logger.log("Removing all dividers from container"); + this._tiledItems = this._tiledItems.filter(item => !Divider.isDivider(item)); + this.tileWindows(); + } + + /** + * Updates divider position during a live resize operation (or creates if doesn't exist) + * This is called repeatedly during resize for live feedback + */ + updateDividerDuringResize(item: WindowWrapper, resizeEdge: Meta.GrabOp, newRect: Rect): void { + const itemIndex = this._getIndexOfWindow(item.getWindowId()); + if (itemIndex === -1) { + return; + } + + // Determine if this is a valid resize for this container orientation + const isHorizontalResize = this._isHorizontalResizeOp(resizeEdge); + const isVerticalResize = this._isVerticalResizeOp(resizeEdge); + + if (this._orientation === Orientation.HORIZONTAL && !isHorizontalResize) { + return; + } + if (this._orientation === Orientation.VERTICAL && !isVerticalResize) { + return; + } + + // Determine which edge is being resized and find adjacent window + let adjacentIndex = -1; + let dividerPosition = 0; + + if (this._orientation === Orientation.HORIZONTAL) { + if (this._isEastResizeOp(resizeEdge)) { + adjacentIndex = itemIndex + 1; + const rightEdge = newRect.x + newRect.width; + dividerPosition = (rightEdge - this._workArea.x) / this._workArea.width; + } else if (this._isWestResizeOp(resizeEdge)) { + adjacentIndex = itemIndex - 1; + dividerPosition = (newRect.x - this._workArea.x) / this._workArea.width; + } + } else { + if (this._isSouthResizeOp(resizeEdge)) { + adjacentIndex = itemIndex + 1; + const bottomEdge = newRect.y + newRect.height; + dividerPosition = (bottomEdge - this._workArea.y) / this._workArea.height; + } else if (this._isNorthResizeOp(resizeEdge)) { + adjacentIndex = itemIndex - 1; + dividerPosition = (newRect.y - this._workArea.y) / this._workArea.height; + } + } + + // Make sure there's an adjacent item (window or container, not out of bounds) + if (adjacentIndex < 0 || adjacentIndex >= this._tiledItems.length) { + Logger.log(`No adjacent item at index ${adjacentIndex}`); + return; + } + + // Determine where divider should be inserted/updated + // For East/South resizes: divider between current (itemIndex) and next (itemIndex+1) + // For West/North resizes: divider between previous (itemIndex-1) and current (itemIndex) + let dividerIndex: number; + + if (this._orientation === Orientation.HORIZONTAL) { + dividerIndex = this._isEastResizeOp(resizeEdge) ? itemIndex + 1 : itemIndex; + } else { + dividerIndex = this._isSouthResizeOp(resizeEdge) ? itemIndex + 1 : itemIndex; + } + + // Check if there's already a divider at this position + if (dividerIndex < this._tiledItems.length && Divider.isDivider(this._tiledItems[dividerIndex])) { + // Update existing divider + const divider = this._tiledItems[dividerIndex] as Divider; + divider.setPosition(dividerPosition); + } else { + // Insert new divider + const newDivider = new Divider(dividerPosition, this._orientation); + this._tiledItems.splice(dividerIndex, 0, newDivider); + } + + // Retile to show live updates + this.tileWindows(); } diff --git a/src/wm/divider.ts b/src/wm/divider.ts new file mode 100644 index 0000000..c64b380 --- /dev/null +++ b/src/wm/divider.ts @@ -0,0 +1,45 @@ +import {Logger} from "../utils/logger.js"; + +enum Orientation { + HORIZONTAL = 0, + VERTICAL = 1, +} + +/** + * Represents a divider between windows in a container. + * Dividers track the split position as a ratio (0-1) of the container's size. + */ +export class Divider { + private _position: number; // Position as ratio 0-1 + private _orientation: Orientation; + + /** + * Creates a new divider + * @param position - Position as ratio between 0 and 1 + * @param orientation - Orientation of the divider (HORIZONTAL or VERTICAL) + */ + constructor(position: number, orientation: Orientation) { + this._position = Math.max(0, Math.min(1, position)); // Clamp between 0 and 1 + this._orientation = orientation; + } + + getPosition(): number { + return this._position; + } + + setPosition(position: number): void { + this._position = Math.max(0, Math.min(1, position)); // Clamp between 0 and 1 + Logger.log(`Divider position updated to ${this._position}`); + } + + getOrientation(): Orientation { + return this._orientation; + } + + /** + * Check if this is a divider instance + */ + static isDivider(item: any): item is Divider { + return item instanceof Divider; + } +} diff --git a/src/wm/monitor.ts b/src/wm/monitor.ts index 640f74d..d5f652a 100644 --- a/src/wm/monitor.ts +++ b/src/wm/monitor.ts @@ -83,4 +83,17 @@ export default class Monitor { this._workspaces[item.getWorkspace()].itemDragged(item, x, y); } + handleWindowResize(item: WindowWrapper, resizeEdge: Meta.GrabOp, newRect: Rect): void { + this._workspaces[item.getWorkspace()].handleWindowResize(item, resizeEdge, newRect); + } + + updateDividerDuringResize(item: WindowWrapper, resizeEdge: Meta.GrabOp, newRect: Rect): void { + this._workspaces[item.getWorkspace()].updateDividerDuringResize(item, resizeEdge, newRect); + } + + removeAllDividersFromActiveContainer(): void { + const activeWorkspace = global.workspace_manager.get_active_workspace(); + this._workspaces[activeWorkspace.index()].removeAllDividers(); + } + } \ No newline at end of file diff --git a/src/wm/window.ts b/src/wm/window.ts index 5fab360..5ab9f08 100644 --- a/src/wm/window.ts +++ b/src/wm/window.ts @@ -16,6 +16,7 @@ export class WindowWrapper { readonly _signals: number[] = []; _parent: WindowContainer | null = null; _dragging: boolean = false; + _resizing: boolean = false; constructor( window: Meta.Window, @@ -53,6 +54,13 @@ export class WindowWrapper { this._dragging = false; } + startResizing(): void { + this._resizing = true; + } + stopResizing(): void { + this._resizing = false; + } + // setParent(parent: WindowContainer): void { // this._parent = parent; // } @@ -124,8 +132,8 @@ export class WindowWrapper { safelyResizeWindow(rect: Rect, _retry: number = 2): void { // Keep minimal logging - if (this._dragging) { - Logger.info("STOPPED RESIZE BECAUSE ITEM IS BEING DRAGGED") + if (this._dragging && !this._resizing) { + // During drag operations (not resize), skip this entirely return; } // Logger.log("SAFELY RESIZE", rect.x, rect.y, rect.width, rect.height); @@ -142,6 +150,12 @@ export class WindowWrapper { 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); + + // Don't retry during live resize operations - it causes spam and isn't needed + if (this._resizing) { + return; + } + let new_rect = this._window.get_frame_rect(); if ( _retry > 0 && (new_rect.x != rect.x || rect.y != new_rect.y || rect.width < new_rect.width || rect.height < new_rect.height)) { Logger.warn("RESIZING FAILED AS SMALLER", new_rect.x, new_rect.y, new_rect.width, new_rect.height, rect.x, rect.y, rect.width, rect.height); diff --git a/src/wm/windowManager.ts b/src/wm/windowManager.ts index d095593..2ea7fe7 100644 --- a/src/wm/windowManager.ts +++ b/src/wm/windowManager.ts @@ -44,6 +44,7 @@ export default class WindowManager implements IWindowManager { _grabbedWindowMonitor: number = _UNUSED_MONITOR_ID; _grabbedWindowId: number = _UNUSED_WINDOW_ID; + _grabbedOp: Meta.GrabOp | null = null; _changingGrabbedMonitor: boolean = false; _showingOverview: boolean = false; @@ -205,20 +206,54 @@ export default class WindowManager implements IWindowManager { Logger.log("Grab Op Start", op); Logger.log(display, window, op) Logger.log(window.get_monitor()) - this._getWrappedWindow(window)?.startDragging(); + + const winWrap = this._getWrappedWindow(window); + if (this._isResizeOp(op)) { + winWrap?.startResizing(); + } else { + winWrap?.startDragging(); + } + this._grabbedWindowMonitor = window.get_monitor(); this._grabbedWindowId = window.get_id(); + this._grabbedOp = op; } 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()) + + // Handle resize operations + if (this._isResizeOp(op)) { + const winWrap = this._getWrappedWindow(window); + if (winWrap && this._grabbedOp) { + const newRect = window.get_frame_rect(); + const monitorId = window.get_monitor(); + Logger.log(`Handling resize operation: ${op}, new rect:`, newRect); + this._monitors.get(monitorId)?.handleWindowResize(winWrap, this._grabbedOp, newRect); + winWrap.stopResizing(); + } + } else { + this._getWrappedWindow(window)?.stopDragging(); + } + this._grabbedWindowId = _UNUSED_WINDOW_ID; - this._getWrappedWindow(window)?.stopDragging(); + this._grabbedOp = null; this._tileMonitors(); Logger.info("monitor_start and monitor_end", this._grabbedWindowMonitor, window.get_monitor()); } + private _isResizeOp(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; + } + _getWrappedWindow(window: Meta.Window): WindowWrapper | undefined { let wrapped = undefined; for (const monitor of this._monitors.values()) { @@ -258,6 +293,16 @@ export default class WindowManager implements IWindowManager { if (this._changingGrabbedMonitor) { return; } + + // Handle resize operations - update dividers in real-time + if (this._grabbedOp && this._isResizeOp(this._grabbedOp)) { + const window = winWrap.getWindow(); + const newRect = window.get_frame_rect(); + const monitorId = window.get_monitor(); + this._monitors.get(monitorId)?.updateDividerDuringResize(winWrap, this._grabbedOp, newRect); + return; + } + if (winWrap.getWindowId() === this._grabbedWindowId) { const [mouseX, mouseY, _] = global.get_pointer(); @@ -419,5 +464,26 @@ export default class WindowManager implements IWindowManager { return null; } + /** + * Removes all dividers from the container with the currently active window + */ + public removeAllDividersFromActiveContainer(): void { + const activeWindow = global.display.focus_window; + if (!activeWindow) { + Logger.log("No active window, cannot remove dividers"); + return; + } + + const monitorId = activeWindow.get_monitor(); + const monitor = this._monitors.get(monitorId); + + if (monitor) { + Logger.log(`Removing all dividers from monitor ${monitorId}`); + monitor.removeAllDividersFromActiveContainer(); + } else { + Logger.warn(`Monitor ${monitorId} not found`); + } + } + }