From 72b8a3badf603f60214af6fe609302906468e75b Mon Sep 17 00:00:00 2001 From: Lucas Oskorep Date: Thu, 17 Apr 2025 23:13:20 -0400 Subject: [PATCH] feat: adding in demo settings page for gnome extensions --- extension.ts | 746 +++++++++++++++++++++++++++++++++++++----------- package.json | 8 +- pnpm-lock.yaml | 166 ++++++----- src/winGroup.ts | 164 +++++++++++ tsconfig.json | 2 +- winGroup.ts | 0 6 files changed, 843 insertions(+), 243 deletions(-) create mode 100644 src/winGroup.ts delete mode 100644 winGroup.ts diff --git a/extension.ts b/extension.ts index 9693118..b6ae6b0 100644 --- a/extension.ts +++ b/extension.ts @@ -7,21 +7,26 @@ import Mtk from "@girs/mtk-16"; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import Gio from 'gi://Gio'; import Shell from 'gi://Shell'; -// import cairo from "cairo"; -// import Shell from 'gi://Shell'; -// import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -type WinWrapper = { - window: Meta.Window | null; - signals: Signal[] | null; -} +import { WindowTree, WindowNode, createWindowNode, addNodeChild, removeNode, findNodeByWindowId, calculateLayout } from './src/winGroup.js'; type Signal = { name: string; id: number; } +type WinWrapper = { + window: Meta.Window | null; + signals: Signal[] | null; +} +type WorkspaceMonitorKey = `${number}-${number}`; // format: "workspace-monitor" + +type DraggedWindowInfo = { + id: number; + originalMonitor: number; + originalWorkspace: number; +} export default class aerospike extends Extension { settings: Gio.Settings; @@ -32,7 +37,12 @@ export default class aerospike extends Extension { _focusSignal: number | null; _windowCreateId: number | null; _windows: Map; + _windowTrees: Map; _activeWindowId: number | null; + _windowDragBeginId: number | null; + _windowDragEndId: number | null; + _draggedWindowInfo: DraggedWindowInfo | null; + _workspaceChangedId: number | null; constructor(metadata: ExtensionMetadata) { super(metadata); @@ -45,23 +55,189 @@ export default class aerospike extends Extension { this._focusSignal = null; this._windowCreateId = null; this._windows = new Map(); + this._windowTrees = new Map(); this._activeWindowId = null; - + this._windowDragBeginId = null; + this._windowDragEndId = null; + this._draggedWindowInfo = null; + this._workspaceChangedId = null; } enable() { - console.log("STARTING AEROSPIKE!") - this._captureExistingWindows(); - // Connect window signals - this._windowCreateId = global.display.connect( - 'window-created', - (display, window) => { - this.handleWindowCreated(window); - } - ); - this.bindSettings(); + try { + console.log("STARTING AEROSPIKE!"); + + // Initialize data structures + this._windows = new Map(); + this._windowTrees = new Map(); + this._activeWindowId = null; + this._draggedWindowInfo = null; + + // Connect to window creation + this._windowCreateId = global.display.connect( + 'window-created', + (display, window) => { + try { + this.handleWindowCreated(window); + } catch (e) { + console.error("Error handling window creation:", e); + } + } + ); + + // Connect to window drag operations + this._connectDraggingSignals(); + + // Connect to workspace change signals + this._workspaceChangedId = global.workspace_manager.connect( + 'workspace-switched', + (_workspaceManager, _oldWorkspaceIndex, _newWorkspaceIndex) => { + try { + this._refreshActiveWorkspace(); + } catch (e) { + console.error("Error refreshing workspace:", e); + } + } + ); + + // Setup keybindings + this.bindSettings(); + + // Capture existing windows - do this last + this._captureExistingWindows(); + + console.log("AEROSPIKE STARTED SUCCESSFULLY"); + } catch (e) { + console.error("Error enabling Aerospike:", e); + // Perform cleanup if something failed + this.disable(); + } } + _connectDraggingSignals() { + // Handle window drag begin + this._windowDragBeginId = global.display.connect( + 'grab-op-begin', + (_display, window, op) => { + if (window && (op === Meta.GrabOp.MOVING || op === Meta.GrabOp.KEYBOARD_MOVING)) { + this._handleDragBegin(window); + } + } + ); + + // Handle window drag end + this._windowDragEndId = global.display.connect( + 'grab-op-end', + (_display, window, op) => { + if (window && (op === Meta.GrabOp.MOVING || op === Meta.GrabOp.KEYBOARD_MOVING)) { + this._handleDragEnd(window); + } + } + ); + } + + _handleDragBegin(window: Meta.Window) { + try { + if (!window) { + console.error("Received null window in _handleDragBegin"); + return; + } + + const workspace = window.get_workspace(); + if (!workspace) { + console.error("Window has no workspace in _handleDragBegin"); + return; + } + + const id = window.get_id(); + console.log(`Drag begin for window ${id}`); + + this._draggedWindowInfo = { + id: id, + originalMonitor: window.get_monitor(), + originalWorkspace: workspace.index() + }; + + console.log(`Original location: workspace ${this._draggedWindowInfo.originalWorkspace}, monitor ${this._draggedWindowInfo.originalMonitor}`); + } catch (e) { + console.error("Error in _handleDragBegin:", e); + this._draggedWindowInfo = null; + } + } + + _handleDragEnd(window: Meta.Window) { + try { + if (!window) { + console.error("Received null window in _handleDragEnd"); + this._draggedWindowInfo = null; + return; + } + + if (!this._draggedWindowInfo) { + console.log("No drag info available, ignoring drag end"); + return; + } + + const workspace = window.get_workspace(); + if (!workspace) { + console.error("Window has no workspace in _handleDragEnd"); + this._draggedWindowInfo = null; + return; + } + + const id = window.get_id(); + const newMonitor = window.get_monitor(); + const newWorkspace = workspace.index(); + + console.log(`Drag end for window ${id}: new location - workspace ${newWorkspace}, monitor ${newMonitor}`); + + // Check if monitor or workspace changed + if (this._draggedWindowInfo.originalMonitor !== newMonitor || + this._draggedWindowInfo.originalWorkspace !== newWorkspace) { + + console.log(`Window moved from workspace ${this._draggedWindowInfo.originalWorkspace}, monitor ${this._draggedWindowInfo.originalMonitor}`); + console.log(`to workspace ${newWorkspace}, monitor ${newMonitor}`); + + // Remove from old tree + const oldKey = `${this._draggedWindowInfo.originalWorkspace}-${this._draggedWindowInfo.originalMonitor}` as WorkspaceMonitorKey; + this._removeWindowFromTree(id, oldKey); + + // Add to new tree + this._addWindowToTree(window, newWorkspace, newMonitor); + + // Retile both affected trees with a small delay + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => { + try { + this._tileWindowsInTree(oldKey); + this._tileWindowsInTree(`${newWorkspace}-${newMonitor}` as WorkspaceMonitorKey); + } catch (e) { + console.error("Error retiling after drag:", e); + } + return GLib.SOURCE_REMOVE; + }); + } else { + console.log("Window position unchanged after drag"); + } + } catch (e) { + console.error("Error in _handleDragEnd:", e); + } finally { + this._draggedWindowInfo = null; + } + } + + _refreshActiveWorkspace() { + // Refresh all trees in the current workspace + const workspace = global.workspace_manager.get_active_workspace(); + const workspaceIndex = workspace.index(); + + // Find all trees in the current workspace + for (const key of this._windowTrees.keys()) { + const [wsIndex, _] = key.split('-').map(Number); + if (wsIndex === workspaceIndex) { + this._tileWindowsInTree(key as WorkspaceMonitorKey); + } + } + } private bindSettings() { // Monitor settings changes @@ -93,6 +269,7 @@ export default class aerospike extends Extension { log(`Color selection changed to: ${this.settings.get_string('color-selection')}`); }); } + private refreshKeybinding(settingName: string) { if (this.keyBindings.has(settingName)) { Main.wm.removeKeybinding(settingName); @@ -167,32 +344,238 @@ export default class aerospike extends Extension { } handleWindowCreated(window: Meta.Window) { - console.log("WINDOW CREATED", window); - if (!this._isWindowTileable(window)) { - return; + try { + if (!window) { + console.error("Received null or undefined window"); + return; + } + + console.log("WINDOW CREATED", window); + + if (!this._isWindowTileable(window)) { + console.log("Window is not tileable, ignoring"); + return; + } + + console.log("WINDOW IS TILABLE"); + + const actor = window.get_compositor_private(); + if (!actor) { + console.log("Window has no compositor actor, ignoring"); + return; + } + + // Get workspace safely + const workspace = window.get_workspace(); + if (!workspace) { + console.error("Window has no workspace, ignoring"); + return; + } + + // Track window for signal management + this._addWindow(window); + + // Add to appropriate tree + const workspaceIndex = workspace.index(); + const monitor = window.get_monitor(); + + console.log(`Adding window to workspace ${workspaceIndex}, monitor ${monitor}`); + this._addWindowToTree(window, workspaceIndex, monitor); + } catch (e) { + console.error("Error in handleWindowCreated:", e); } - console.log("WINDOW IS TILABLE"); - const actor = window.get_compositor_private(); - if (!actor) { - return; - } - - - this._addWindow(window); } _captureExistingWindows() { - console.log("CAPTURING WINDOWS") - const workspace = global.workspace_manager.get_active_workspace(); - const windows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace); - console.log("WINDOWS", windows); - windows.forEach(window => { - if (this._isWindowTileable(window)) { - this._addWindow(window); + try { + console.log("CAPTURING WINDOWS"); + + // Get all workspaces + const workspaceCount = global.workspace_manager.get_n_workspaces(); + const monitorCount = global.display.get_n_monitors(); + + console.log(`Found ${workspaceCount} workspaces and ${monitorCount} monitors`); + + // Initialize trees for all workspace-monitor combinations + for (let wsIndex = 0; wsIndex < workspaceCount; wsIndex++) { + const workspace = global.workspace_manager.get_workspace_by_index(wsIndex); + if (!workspace) { + console.error(`Workspace at index ${wsIndex} not found`); + continue; + } + + for (let monIndex = 0; monIndex < monitorCount; monIndex++) { + try { + // Create empty tree for this workspace-monitor combination + this._getWindowTree(wsIndex, monIndex); + + // Get windows for this workspace + const windows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace); + console.log(`Found ${windows.length} windows in workspace ${wsIndex}`); + + // Add tileable windows to the appropriate tree + let addedWindows = 0; + for (const window of windows) { + try { + if (window && this._isWindowTileable(window) && window.get_monitor() === monIndex) { + // Track window for signal management + this._addWindow(window); + + // Add to tree + this._addWindowToTree(window, wsIndex, monIndex); + addedWindows++; + } + } catch (e) { + console.error(`Error processing window in workspace ${wsIndex}, monitor ${monIndex}:`, e); + } + } + + console.log(`Added ${addedWindows} windows to workspace ${wsIndex}, monitor ${monIndex}`); + } catch (e) { + console.error(`Error processing monitor ${monIndex} in workspace ${wsIndex}:`, e); + } + } } - }); - - this._tileWindows(); + + // Tile all trees with a slight delay to ensure all windows are ready + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { + try { + for (const key of this._windowTrees.keys()) { + this._tileWindowsInTree(key as WorkspaceMonitorKey); + } + } catch (e) { + console.error("Error tiling windows:", e); + } + return GLib.SOURCE_REMOVE; + }); + + console.log("FINISHED CAPTURING WINDOWS"); + } catch (e) { + console.error("Error in _captureExistingWindows:", e); + } + } + + _getWindowTree(workspace: number, monitor: number): WindowTree { + const key: WorkspaceMonitorKey = `${workspace}-${monitor}` as WorkspaceMonitorKey; + + if (!this._windowTrees.has(key)) { + this._windowTrees.set(key, { + root: null, + monitor: monitor, + workspace: workspace + }); + } + + return this._windowTrees.get(key)!; + } + + _addWindowToTree(window: Meta.Window, workspace: number, monitor: number) { + const tree = this._getWindowTree(workspace, monitor); + const windowNode = createWindowNode(window); + + if (!tree.root) { + // First window in this tree + tree.root = windowNode; + } else { + // Add to existing tree + addNodeChild(tree.root, windowNode); + } + + // Update the layout + this._tileWindowsInTree(`${workspace}-${monitor}` as WorkspaceMonitorKey); + } + + _removeWindowFromTree(windowId: number, key: WorkspaceMonitorKey) { + const tree = this._windowTrees.get(key); + if (!tree) return; + + const node = findNodeByWindowId(tree, windowId); + if (node) { + removeNode(node, tree); + } + } + + _tileWindowsInTree(key: WorkspaceMonitorKey) { + try { + console.log(`Tiling windows for ${key}`); + + const tree = this._windowTrees.get(key); + if (!tree || !tree.root) { + console.log(`No tree or empty tree for ${key}`); + return; + } + + // Get workspace and monitor info + const [workspaceIndex, monitorIndex] = key.split('-').map(Number); + + const workspace = global.workspace_manager.get_workspace_by_index(workspaceIndex); + if (!workspace) { + console.error(`Workspace ${workspaceIndex} not found`); + return; + } + + const workArea = workspace.get_work_area_for_monitor(monitorIndex); + if (!workArea) { + console.error(`WorkArea for monitor ${monitorIndex} in workspace ${workspaceIndex} not found`); + return; + } + + console.log(`Work area for ${key}: ${workArea.x},${workArea.y} ${workArea.width}x${workArea.height}`); + + // Calculate layout + calculateLayout(tree.root, { + x: workArea.x, + y: workArea.y, + width: workArea.width, + height: workArea.height + }); + + // Apply layout to all windows in the tree + this._applyLayoutToTree(tree.root); + + console.log(`Finished tiling windows for ${key}`); + } catch (e) { + console.error(`Error tiling windows for ${key}:`, e); + } + } + + _applyLayoutToTree(node: WindowNode) { + try { + // Apply layout to this node + if (node.window) { + // Validate window object + if (!node.window.get_compositor_private) { + console.error(`Window at node ${node.windowId} is invalid`); + return; + } + + // Check for valid rect dimensions + if (node.rect.width <= 0 || node.rect.height <= 0) { + console.error(`Invalid rect dimensions for window ${node.windowId}: ${node.rect.width}x${node.rect.height}`); + return; + } + + // Resize window + this.safelyResizeWindow( + node.window, + node.rect.x, + node.rect.y, + node.rect.width, + node.rect.height + ); + } + + // Apply layout to all children + for (const child of node.children) { + try { + this._applyLayoutToTree(child); + } catch (e) { + console.error(`Error applying layout to child node:`, e); + } + } + } catch (e) { + console.error(`Error in _applyLayoutToTree:`, e); + } } getUsableMonitorSpace(window: Meta.Window) { @@ -213,7 +596,6 @@ export default class aerospike extends Extension { }; } - // Function to safely resize a window after it's ready safelyResizeWindow(win: Meta.Window, x: number, y: number, width: number, height: number): void { const actor = win.get_compositor_private(); @@ -222,7 +604,33 @@ export default class aerospike extends Extension { console.log("No actor available, can't resize safely yet"); return; } - + + // Check if the window type needs special handling + const windowType = win.get_window_type(); + + // Try immediate resize first for most window types + if (windowType === Meta.WindowType.NORMAL) { + // Standard resizing path with safety checks + this.resizeWindow(win, x, y, width, height); + + // Set up a verification check + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { + const rect = win.get_frame_rect(); + + // If window didn't resize well, try again with the first-frame signal + if (Math.abs(rect.width - width) > 5 || Math.abs(rect.height - height) > 5) { + this._setupFirstFrameResize(win, actor, x, y, width, height); + } + + return GLib.SOURCE_REMOVE; + }); + } else { + // For non-standard windows, use the original approach + this._setupFirstFrameResize(win, actor, x, y, width, height); + } + } + + _setupFirstFrameResize(win: Meta.Window, actor: any, x: number, y: number, width: number, height: number): void { // Set a flag to track if the resize has been done let resizeDone = false; @@ -235,7 +643,7 @@ export default class aerospike extends Extension { resizeDone = true; // Add a small delay - GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => { try { this.resizeWindow(win, x, y, width, height); } catch (e) { @@ -248,7 +656,7 @@ export default class aerospike extends Extension { // Fallback timeout in case the first-frame signal doesn't fire // (for windows that are already mapped) - GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => { if (!resizeDone) { resizeDone = true; try { @@ -263,21 +671,54 @@ export default class aerospike extends Extension { resizeWindow(win: Meta.Window, x:number, y:number, width:number, height:number) { // First, ensure window is not maximized or fullscreen - if (win.get_maximized()) { + const wasMaximized = win.get_maximized(); + const wasFullscreen = win.is_fullscreen(); + + if (wasMaximized) { console.log("WINDOW MAXIMIZED") win.unmaximize(Meta.MaximizeFlags.BOTH); } - if (win.is_fullscreen()) { + if (wasFullscreen) { console.log("WINDOW IS FULLSCREEN") win.unmake_fullscreen(); } + + // Wait for state change to complete if needed + if (wasMaximized || wasFullscreen) { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + this._performResize(win, x, y, width, height); + return GLib.SOURCE_REMOVE; + }); + } else { + // Immediate resize if no state change needed + this._performResize(win, x, y, width, height); + } + } + + _performResize(win: Meta.Window, x:number, y:number, width:number, height:number) { console.log("WINDOW", win.get_window_type(), win.allows_move()); console.log("MONITOR INFO", this.getUsableMonitorSpace(win)); console.log("NEW_SIZE", x, y, width, height); - // win.move_resize_frame(false, 50, 50, 300, 300); + + // Perform the actual resize win.move_resize_frame(false, x, y, width, height); - console.log("RESIZED WINDOW", win.get_frame_rect().height, win.get_frame_rect().width, win.get_frame_rect().x, win.get_frame_rect().y); + + // Check result + const newRect = win.get_frame_rect(); + console.log("RESIZED WINDOW", newRect.height, newRect.width, newRect.x, newRect.y); + + // Validate the resize was successful + if (Math.abs(newRect.x - x) > 5 || Math.abs(newRect.y - y) > 5 || + Math.abs(newRect.width - width) > 5 || Math.abs(newRect.height - height) > 5) { + console.warn(`Resize did not achieve expected dimensions for window ${win.get_id()}`); + + // Try a second time if the resize didn't work well + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => { + win.move_resize_frame(false, x, y, width, height); + return GLib.SOURCE_REMOVE; + }); + } } _addWindow(window: Meta.Window) { @@ -286,11 +727,6 @@ export default class aerospike extends Extension { // Connect to window signals const signals: Signal[] = []; console.log("ADDING WINDOW", window); - // const act = window.get_compositor_private(); - // const id = act.connect('first-frame', _ => { - // this.resizeWindow(window); - // act.disconnect(id); - // }); const destroyId = window.connect('unmanaging', () => { console.log("REMOVING WINDOW", windowId); @@ -304,6 +740,18 @@ export default class aerospike extends Extension { } }); signals.push({name: 'notify::has-focus', id: focusId}); + + // Monitor change signal + const monitorChangedId = window.connect('notify::monitor', () => { + this._handleWindowMonitorChanged(window); + }); + signals.push({name: 'notify::monitor', id: monitorChangedId}); + + // Workspace change signal + const workspaceChangedId = window.connect('workspace-changed', () => { + this._handleWindowWorkspaceChanged(window); + }); + signals.push({name: 'workspace-changed', id: workspaceChangedId}); // Add window to managed windows this._windows.set(windowId, { @@ -315,8 +763,41 @@ export default class aerospike extends Extension { if (this._windows.size === 1 || window.has_focus()) { this._activeWindowId = windowId; } - - this._tileWindows(); + } + + _handleWindowMonitorChanged(window: Meta.Window) { + const windowId = window.get_id(); + + // Find which tree this window is in + for (const [key, tree] of this._windowTrees.entries()) { + const node = findNodeByWindowId(tree, windowId); + if (node) { + // Found the window - get new workspace/monitor + const newWorkspace = window.get_workspace().index(); + const newMonitor = window.get_monitor(); + const newKey = `${newWorkspace}-${newMonitor}` as WorkspaceMonitorKey; + + // Skip if it's already in the right tree + if (key === newKey) return; + + // Remove from old tree + this._removeWindowFromTree(windowId, key as WorkspaceMonitorKey); + + // Add to new tree + this._addWindowToTree(window, newWorkspace, newMonitor); + + // Retile both trees + this._tileWindowsInTree(key as WorkspaceMonitorKey); + this._tileWindowsInTree(newKey); + + return; + } + } + } + + _handleWindowWorkspaceChanged(window: Meta.Window) { + // Similar to monitor change, but for workspace changes + this._handleWindowMonitorChanged(window); // This handles both cases } _handleWindowClosed(windowId: number) { @@ -330,7 +811,6 @@ export default class aerospike extends Extension { if (windowData.signals) { windowData.signals.forEach(signal => { try { - if (windowData.window != null) { windowData.window.disconnect(signal.id); } @@ -343,59 +823,18 @@ export default class aerospike extends Extension { // Remove from managed windows this._windows.delete(windowId); + // Remove from all trees + for (const key of this._windowTrees.keys()) { + this._removeWindowFromTree(windowId, key as WorkspaceMonitorKey); + this._tileWindowsInTree(key as WorkspaceMonitorKey); + } + // If this was the active window, find a new one if (this._activeWindowId === windowId && this._windows.size > 0) { this._activeWindowId = Array.from(this._windows.keys())[0]; } else if (this._windows.size === 0) { this._activeWindowId = null; } - - // Retile remaining windows - this._tileWindows(); - } - - - _tileWindows() { - console.log("TILING WINDOWS") - const workspace = global.workspace_manager.get_active_workspace(); - const workArea = workspace.get_work_area_for_monitor( - global.display.get_primary_monitor() - ); - console.log("Workspace", workspace); - console.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(({window}) => window); - - if (windows.length === 0) { - return; - } - this._tileHorizontally(windows, workArea) - - } - - _tileHorizontally(windows: (Meta.Window | null)[], workArea: Mtk.Rectangle) { - const windowWidth = Math.floor(workArea.width / windows.length); - - windows.forEach((window, index) => { - const x = workArea.x + (index * windowWidth); - const rect = { - x: x, - y: workArea.y, - width: windowWidth, - height: workArea.height - }; - if (window != null) { - this.safelyResizeWindow(window, rect.x, rect.y, rect.width, rect.height); - } - }); } _isWindowTileable(window: Meta.Window) { @@ -415,66 +854,55 @@ export default class aerospike extends Extension { windowType !== Meta.WindowType.MENU; } - // _updateBorder(window: Meta.Window) { - // console.log("UPDATING THE BORDER") - // // Clear the previous border - // this._clearBorder(); - // // Set a new border for the currently focused window - // if (window) { - // this._setBorder(window); - // this.lastFocusedWindow = window; - // } - // } - // - // _setBorder(window: Meta.Window) { - // console.log("SETTING THE BORDER") - // if (!window) return; - // - // const rect = window.get_frame_rect(); - // if (!rect) return; - // - // // Create a new actor for the border using St.Widget - // this.borderActor = new St.Widget({ - // name: 'active-window-border', - // // style_class: 'active-window-border', - // reactive: false, - // x: rect.x - 1, // Adjust for border width - // y: rect.y - 1, - // width: rect.width + 2, // Increased to accommodate border - // height: rect.height + 2, - // // Initial style with default color.ts - // // style: `border: 4px solid hsl(${this.hue}, 100%, 50%); border-radius: 5px;`, - // // style: `border: 2px solid rgba(0, 0, 0, 0.5); border-radius: 3px;` - // }); - // - // // Add the border actor to the UI group - // global.window_group.add_child(this.borderActor); - // // Main.layoutManager.uiGroup.add_child(this.borderActor); - // - // // Listen to window's changes in position and size - // this.focusWindowSignals?.push(window.connect('position-changed', () => this._updateBorderPosition(window))); - // this.focusWindowSignals?.push(window.connect('size-changed', () => this._updateBorderPosition(window))); - // this.focusWindowSignals?.push(window.connect('unmanaged', () => this._clearBorder())); - // - // this._updateBorderPosition(window); - // - // // Start the color.ts cycling - // this._startColorCycle(); - // } - - disable() { console.log("DISABLED AEROSPIKE!") - // Disconnect the focus signal and remove any existing borders - if (this._focusSignal) { - global.display.disconnect(this._focusSignal); - this._focusSignal = null; + + // Disconnect window creation signal + if (this._windowCreateId) { + global.display.disconnect(this._windowCreateId); + this._windowCreateId = null; } - - // Clear the border on the last focused window if it exists - // this._clearBorder(); + + // Disconnect workspace signals + if (this._workspaceChangedId) { + global.workspace_manager.disconnect(this._workspaceChangedId); + this._workspaceChangedId = null; + } + + // Disconnect drag signals + if (this._windowDragBeginId) { + global.display.disconnect(this._windowDragBeginId); + this._windowDragBeginId = null; + } + + if (this._windowDragEndId) { + global.display.disconnect(this._windowDragEndId); + this._windowDragEndId = null; + } + + // Disconnect all window signals + this._windows.forEach((windowData) => { + if (windowData.signals && windowData.window) { + windowData.signals.forEach(signal => { + try { + windowData.window!.disconnect(signal.id); + } catch (e) { + // Window might already be gone + } + }); + } + }); + + // Clear all window data + this._windows.clear(); + this._windowTrees.clear(); + + // Remove keybindings + this.removeKeybindings(); + + // Reset state + this._activeWindowId = null; this.lastFocusedWindow = null; + this._draggedWindowInfo = null; } - - } \ No newline at end of file diff --git a/package.json b/package.json index a0c1c6a..2c43749 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ "homepage": "https://github.com/example/my-extension#readme", "sideEffects": false, "devDependencies": { - "@girs/gjs": "^4.0.0-beta.23", - "@girs/gnome-shell": "^48.0.1", - "eslint": "^9.23.0", + "@girs/gjs": "4.0.0-beta.23", + "@girs/gnome-shell": "^48.0.2", + "eslint": "^9.24.0", "eslint-plugin-jsdoc": "^50.6.9", - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "dependencies": { "@girs/mtk-16": "16.0.0-4.0.0-beta.23" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acc2036..70aec75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,20 +13,20 @@ importers: version: 16.0.0-4.0.0-beta.23 devDependencies: '@girs/gjs': - specifier: ^4.0.0-beta.23 + specifier: 4.0.0-beta.23 version: 4.0.0-beta.23 '@girs/gnome-shell': - specifier: ^48.0.1 - version: 48.0.1 + specifier: ^48.0.2 + version: 48.0.2 eslint: - specifier: ^9.23.0 - version: 9.23.0 + specifier: ^9.24.0 + version: 9.24.0 eslint-plugin-jsdoc: specifier: ^50.6.9 - version: 50.6.9(eslint@9.23.0) + version: 50.6.9(eslint@9.24.0) typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.8.3 + version: 5.8.3 packages: @@ -34,8 +34,8 @@ packages: resolution: {integrity: sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==} engines: {node: '>=16'} - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + '@eslint-community/eslint-utils@4.5.1': + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -44,32 +44,36 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.2': - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.0': - resolution: {integrity: sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==} + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.12.0': resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.23.0': - resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} + '@eslint/js@9.24.0': + resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.23': @@ -129,8 +133,8 @@ packages: '@girs/gmodule-2.0@2.0.0-4.0.0-beta.23': resolution: {integrity: sha512-Dc+Pq1peNlwQ0o/WFsUzT1qt3oqgMLBhzjEfOTGAD0Jw1Ut3QCoBuryVFFNMIruOKnSSBoBnQO7Qelly5aSd2w==} - '@girs/gnome-shell@48.0.1': - resolution: {integrity: sha512-6Oj8cLTD2vwfiNAI7AexLEW/UpZswbiw9Kdg4scf0Te3DpK/YD2M+KKYFyxXafQUlfNg/HEHlRpeo+0f4OWjnQ==} + '@girs/gnome-shell@48.0.2': + resolution: {integrity: sha512-hrlnTCc6y9O7GTn7M7YAufKdmIF8Et7ZFTRRUVXyv3hBHAor0bUFDrHdmVD+D7KdMHCJrtaLW6EtUCRQyovU2A==} '@girs/gnomebg-4.0@4.0.0-4.0.0-beta.23': resolution: {integrity: sha512-mXipjnVd+lUSMhUeugo49TXo32ihlKiN2ggpGDLIcD6ZfGe644DSgxoHFeYkn44HmNW39DvSqF9AcSvLP0cZJA==} @@ -218,12 +222,12 @@ packages: resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + '@pkgr/core@0.1.2': + resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -233,8 +237,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true @@ -284,8 +288,8 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -296,8 +300,8 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -321,8 +325,8 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.23.0: - resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} + eslint@9.24.0: + resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -372,8 +376,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -490,8 +494,8 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true @@ -512,8 +516,8 @@ packages: spdx-expression-parse@4.0.0: resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -534,8 +538,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true @@ -563,31 +567,35 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 - '@eslint-community/eslint-utils@4.4.1(eslint@9.23.0)': + '@eslint-community/eslint-utils@4.5.1(eslint@9.24.0)': dependencies: - eslint: 9.23.0 + eslint: 9.24.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.2': + '@eslint/config-array@0.20.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.3.7 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.0': {} + '@eslint/config-helpers@0.2.1': {} '@eslint/core@0.12.0': dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.4.0 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -598,13 +606,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.23.0': {} + '@eslint/js@9.24.0': {} '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.7': + '@eslint/plugin-kit@0.2.8': dependencies: - '@eslint/core': 0.12.0 + '@eslint/core': 0.13.0 levn: 0.4.1 '@girs/accountsservice-1.0@1.0.0-4.0.0-beta.23': @@ -767,7 +775,7 @@ snapshots: '@girs/glib-2.0': 2.84.0-4.0.0-beta.23 '@girs/gobject-2.0': 2.84.0-4.0.0-beta.23 - '@girs/gnome-shell@48.0.1': + '@girs/gnome-shell@48.0.2': dependencies: '@girs/accountsservice-1.0': 1.0.0-4.0.0-beta.23 '@girs/adw-1': 1.8.0-4.0.0-beta.23 @@ -1065,17 +1073,17 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} - '@pkgr/core@0.1.1': {} + '@pkgr/core@0.1.2': {} - '@types/estree@1.0.6': {} + '@types/estree@1.0.7': {} '@types/json-schema@7.0.15': {} - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: - acorn: 8.14.0 + acorn: 8.14.1 - acorn@8.14.0: {} + acorn@8.14.1: {} ajv@6.12.6: dependencies: @@ -1122,28 +1130,28 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.3.7: + debug@4.4.0: dependencies: ms: 2.1.3 deep-is@0.1.4: {} - es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} escape-string-regexp@4.0.0: {} - eslint-plugin-jsdoc@50.6.9(eslint@9.23.0): + eslint-plugin-jsdoc@50.6.9(eslint@9.24.0): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.3.7 + debug: 4.4.0 escape-string-regexp: 4.0.0 - eslint: 9.23.0 + eslint: 9.24.0 espree: 10.3.0 esquery: 1.6.0 parse-imports: 2.2.1 - semver: 7.6.3 + semver: 7.7.1 spdx-expression-parse: 4.0.0 synckit: 0.9.2 transitivePeerDependencies: @@ -1158,25 +1166,25 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.23.0: + eslint@9.24.0: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.23.0) + '@eslint-community/eslint-utils': 4.5.1(eslint@9.24.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/config-helpers': 0.2.0 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 '@eslint/core': 0.12.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.23.0 - '@eslint/plugin-kit': 0.2.7 + '@eslint/js': 9.24.0 + '@eslint/plugin-kit': 0.2.8 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.3.7 + debug: 4.4.0 escape-string-regexp: 4.0.0 eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0 @@ -1200,8 +1208,8 @@ snapshots: espree@10.3.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 esquery@1.6.0: @@ -1233,10 +1241,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.1: {} + flatted@3.3.3: {} glob-parent@6.0.2: dependencies: @@ -1321,7 +1329,7 @@ snapshots: parse-imports@2.2.1: dependencies: - es-module-lexer: 1.5.4 + es-module-lexer: 1.6.0 slashes: 3.0.12 path-exists@4.0.0: {} @@ -1334,7 +1342,7 @@ snapshots: resolve-from@4.0.0: {} - semver@7.6.3: {} + semver@7.7.1: {} shebang-command@2.0.0: dependencies: @@ -1349,9 +1357,9 @@ snapshots: spdx-expression-parse@4.0.0: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.21 - spdx-license-ids@3.0.20: {} + spdx-license-ids@3.0.21: {} strip-json-comments@3.1.1: {} @@ -1361,7 +1369,7 @@ snapshots: synckit@0.9.2: dependencies: - '@pkgr/core': 0.1.1 + '@pkgr/core': 0.1.2 tslib: 2.8.1 tslib@2.8.1: {} @@ -1370,7 +1378,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript@5.8.2: {} + typescript@5.8.3: {} uri-js@4.4.1: dependencies: diff --git a/src/winGroup.ts b/src/winGroup.ts new file mode 100644 index 0000000..4367efd --- /dev/null +++ b/src/winGroup.ts @@ -0,0 +1,164 @@ +import Meta from 'gi://Meta'; +import Mtk from '@girs/mtk-16'; + +export interface WindowTree { + root: WindowNode | null; + monitor: number; + workspace: number; +} + +export interface WindowNode { + window: Meta.Window | null; + windowId: number | null; + children: WindowNode[]; + parent: WindowNode | null; + splitRatio: number; + splitDirection: 'horizontal' | 'vertical'; + rect: { + x: number; + y: number; + width: number; + height: number; + }; +} + +export function createWindowNode(window: Meta.Window | null = null): WindowNode { + return { + window: window, + windowId: window ? window.get_id() : null, + children: [], + parent: null, + splitRatio: 1.0, + splitDirection: 'horizontal', + rect: { x: 0, y: 0, width: 0, height: 0 } + }; +} + +export function addNodeChild(parent: WindowNode, child: WindowNode): void { + child.parent = parent; + parent.children.push(child); + + // Update split ratios to be equal + const childCount = parent.children.length; + parent.children.forEach(node => { + node.splitRatio = 1.0 / childCount; + }); +} + +export function removeNode(node: WindowNode, tree: WindowTree): WindowNode | null { + if (!node.parent) { + // This is the root node + if (node.children.length > 0) { + // Promote first child to root + const newRoot = node.children[0]; + newRoot.parent = null; + + // Transfer any other children to the new root + for (let i = 1; i < node.children.length; i++) { + addNodeChild(newRoot, node.children[i]); + } + + tree.root = newRoot; + return newRoot; + } else { + // No children, tree is now empty + tree.root = null; + return null; + } + } else { + // Remove from parent's children + const parent = node.parent; + const index = parent.children.indexOf(node); + if (index !== -1) { + parent.children.splice(index, 1); + } + + // Update split ratios of remaining siblings + if (parent.children.length > 0) { + const ratio = 1.0 / parent.children.length; + parent.children.forEach(child => { + child.splitRatio = ratio; + }); + } + + // Transfer any children to the parent + node.children.forEach(child => { + addNodeChild(parent, child); + }); + + return parent; + } +} + +export function findNodeByWindowId(tree: WindowTree, windowId: number): WindowNode | null { + if (!tree.root) return null; + + function search(node: WindowNode): WindowNode | null { + if (node.windowId === windowId) return node; + + for (const child of node.children) { + const result = search(child); + if (result) return result; + } + + return null; + } + + return search(tree.root); +} + +export function calculateLayout(node: WindowNode, rect: {x: number, y: number, width: number, height: number}): void { + // Update node's rect + node.rect = {...rect}; + + // Process children recursively + if (node.children.length > 0) { + if (node.splitDirection === 'horizontal') { + // Divide width with remainder handling + let currentX = rect.x; + let remainingWidth = rect.width; + const lastChildIndex = node.children.length - 1; + + node.children.forEach((child, index) => { + const isLastChild = index === lastChildIndex; + // Last child gets remainder to avoid gaps + const childWidth = isLastChild ? + remainingWidth : + Math.floor(rect.width * child.splitRatio); + + calculateLayout(child, { + x: currentX, + y: rect.y, + width: childWidth, + height: rect.height + }); + + currentX += childWidth; + remainingWidth -= childWidth; + }); + } else { + // Divide height with remainder handling + let currentY = rect.y; + let remainingHeight = rect.height; + const lastChildIndex = node.children.length - 1; + + node.children.forEach((child, index) => { + const isLastChild = index === lastChildIndex; + // Last child gets remainder to avoid gaps + const childHeight = isLastChild ? + remainingHeight : + Math.floor(rect.height * child.splitRatio); + + calculateLayout(child, { + x: rect.x, + y: currentY, + width: rect.width, + height: childHeight + }); + + currentY += childHeight; + remainingHeight -= childHeight; + }); + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b9a89fa..8c9e296 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ ], "files": [ "extension.ts", - "winGroup.ts" + "src/winGroup.ts" ], } diff --git a/winGroup.ts b/winGroup.ts deleted file mode 100644 index e69de29..0000000