From 3d79b0336a416265e3e501b1e2a5f8359dc8f511 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Thu, 15 Jan 2026 19:50:23 -0700 Subject: [PATCH] Fix critical bugs and improve laptop compatibility Major fixes: - Fix keybinding closure bug causing all zones to map to zone 9 - Change keybindings from Super+Numpad to Super+Ctrl+1-9 for laptop compatibility - Fix shift-drag detection by implementing continuous modifier polling during window drag - Fix window object reference by using global.display.focus_window - Fix window API compatibility (use property checks instead of get_maximized()) - Correct grab-op-begin/end signal handler parameters Technical improvements: - Add modifier polling (50ms interval) during window drag to detect Shift key press/release - Use Mainloop.idle_add to defer window resize operations - Remove debug logging for production use - Improve error handling and cleanup in destroy() method The extension now fully supports: - Shift-drag window snapping with visual zone overlay - Keyboard shortcuts for direct zone snapping (Super+Ctrl+1-9) - Zone overlay toggle (Super+Z) - Layout cycling (Super+Shift+Z) - Graphical zone editor (Super+Shift+E) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 2 +- extension.js | 157 ++++++++++++++++++++++++++++++++++----------------- install.sh | 7 ++- 3 files changed, 110 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index b18faea..698db93 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ A window tiling manager extension for Linux Mint's Cinnamon Desktop, inspired by - **Super + Z**: Toggle zone overlay (show/hide zones) - **Super + Shift + Z**: Cycle through different layouts - **Super + Shift + E**: Open graphical zone editor -- **Super + Numpad 1-9**: Snap focused window to zone 1-9 +- **Super + Ctrl + 1-9**: Snap focused window to zone 1-9 ### Mouse Usage diff --git a/extension.js b/extension.js index 4d7e9f3..ee421b8 100644 --- a/extension.js +++ b/extension.js @@ -333,8 +333,9 @@ ZoneManager.prototype = { this.layouts = DEFAULT_LAYOUTS; this.isShowing = false; this.dragInProgress = false; + this.draggedWindow = null; this.editor = new ZoneEditor(); - + // Connect to window grab events this._connectSignals(); this._setupKeybindings(); @@ -342,10 +343,13 @@ ZoneManager.prototype = { _connectSignals: function() { // Monitor for window grab begin/end - this.grabOpBeginId = global.display.connect('grab-op-begin', + this.grabOpBeginId = global.display.connect('grab-op-begin', Lang.bind(this, this._onGrabBegin)); - this.grabOpEndId = global.display.connect('grab-op-end', + this.grabOpEndId = global.display.connect('grab-op-end', Lang.bind(this, this._onGrabEnd)); + + // Monitor for modifier key changes + this.modifierPollTimeoutId = null; }, _setupKeybindings: function() { @@ -355,46 +359,76 @@ ZoneManager.prototype = { 'z', Lang.bind(this, this._toggleZonesOverlay) ); - + // Add keybinding to open zone editor (Super+Shift+E) Main.keybindingManager.addHotKey( 'open-zone-editor', 'e', Lang.bind(this, function() { this.editor.startEditor(); }) ); - - // Add keybindings for quick snap to zones (Super+Numpad) + + // Add keybindings for quick snap to zones (Super+Ctrl+1-9) for (let i = 1; i <= 9; i++) { + const zoneIndex = i - 1; // Capture the value for this iteration Main.keybindingManager.addHotKey( 'snap-to-zone-' + i, - 'KP_' + i, - Lang.bind(this, function() { this._snapToZone(i - 1); }) + '' + i, + Lang.bind(this, function() { + this._snapToZone(zoneIndex); + }) ); } }, - _onGrabBegin: function(display, window, op) { - // Check if this is a window move operation - if (op === Meta.GrabOp.MOVING) { - this.dragInProgress = true; - - // Check if Shift is held - show overlay - let mods = global.get_pointer()[2]; - if (mods & Clutter.ModifierType.SHIFT_MASK) { - this._showZonesOverlay(); - } + _checkModifiersDuringDrag: function() { + if (!this.dragInProgress) { + return false; // Stop polling } + + let pointerInfo = global.get_pointer(); + let mods = pointerInfo[2]; + let shiftPressed = !!(mods & Clutter.ModifierType.SHIFT_MASK); + + if (shiftPressed && !this.isShowing) { + this._showZonesOverlay(); + } else if (!shiftPressed && this.isShowing) { + this._hideZonesOverlay(); + } + + return true; // Continue polling }, - - _onGrabEnd: function(display, window, op) { - if (op === Meta.GrabOp.MOVING && this.dragInProgress) { + + _onGrabBegin: function(display, window) { + // Window drag has begun + this.dragInProgress = true; + + // Get the actual window being dragged - use focus_window as it's the dragged window + this.draggedWindow = global.display.focus_window; + + // Start polling for modifier key changes during drag + if (this.modifierPollTimeoutId) { + Mainloop.source_remove(this.modifierPollTimeoutId); + } + this.modifierPollTimeoutId = Mainloop.timeout_add(50, Lang.bind(this, this._checkModifiersDuringDrag)); + }, + + _onGrabEnd: function(display, window) { + if (this.dragInProgress) { this.dragInProgress = false; - + + // Stop polling for modifiers + if (this.modifierPollTimeoutId) { + Mainloop.source_remove(this.modifierPollTimeoutId); + this.modifierPollTimeoutId = null; + } + if (this.isShowing) { // Snap to zone if cursor is over one - this._snapWindowToZone(window); + this._snapWindowToZone(this.draggedWindow); this._hideZonesOverlay(); } + + this.draggedWindow = null; } }, @@ -489,18 +523,18 @@ ZoneManager.prototype = { _snapWindowToZone: function(window) { if (!window) return; - + let [x, y] = global.get_pointer(); let monitor = Main.layoutManager.primaryMonitor; - + // Convert to relative coordinates let relX = (x - monitor.x) / monitor.width; let relY = (y - monitor.y) / monitor.height; - + // Find which zone the cursor is in let layout = this.layouts[this.currentLayout]; let targetZone = null; - + for (let zone of layout.zones) { if (relX >= zone.x && relX < zone.x + zone.width && relY >= zone.y && relY < zone.y + zone.height) { @@ -508,7 +542,7 @@ ZoneManager.prototype = { break; } } - + if (targetZone) { this._moveWindowToZone(window, targetZone, monitor); } @@ -518,28 +552,35 @@ ZoneManager.prototype = { // Snap the focused window to a specific zone let window = global.display.focus_window; if (!window) return; - + let layout = this.layouts[this.currentLayout]; if (zoneIndex >= layout.zones.length) return; - + let monitor = Main.layoutManager.primaryMonitor; this._moveWindowToZone(window, layout.zones[zoneIndex], monitor); }, _moveWindowToZone: function(window, zone, monitor) { + if (!window || !zone || !monitor) return; + // Unmaximize if maximized - if (window.get_maximized()) { + if (window.maximized_horizontally || window.maximized_vertically) { window.unmaximize(Meta.MaximizeFlags.BOTH); } - + // Calculate absolute coordinates let x = monitor.x + Math.round(monitor.width * zone.x); let y = monitor.y + Math.round(monitor.height * zone.y); let width = Math.round(monitor.width * zone.width); let height = Math.round(monitor.height * zone.height); - - // Move and resize window - window.move_resize_frame(true, x, y, width, height); + + // Use Mainloop.idle_add to defer the operation + // This ensures the unmaximize completes before move/resize + Mainloop.idle_add(Lang.bind(this, function() { + // Move and resize window (false = programmatic operation, not user-initiated) + window.move_resize_frame(false, x, y, width, height); + return false; // Don't repeat + })); }, cycleLayout: function() { @@ -558,28 +599,40 @@ ZoneManager.prototype = { }, destroy: function() { - // Disconnect signals - if (this.grabOpBeginId) { - global.display.disconnect(this.grabOpBeginId); - } - if (this.grabOpEndId) { - global.display.disconnect(this.grabOpEndId); - } - - // Remove keybindings - Main.keybindingManager.removeHotKey('show-zones'); - Main.keybindingManager.removeHotKey('open-zone-editor'); - for (let i = 1; i <= 9; i++) { - Main.keybindingManager.removeHotKey('snap-to-zone-' + i); - } - - // Clean up overlay + // Clean up overlay FIRST to prevent corrupt display this._hideZonesOverlay(); - + + // Stop modifier polling if active + if (this.modifierPollTimeoutId) { + Mainloop.source_remove(this.modifierPollTimeoutId); + this.modifierPollTimeoutId = null; + } + // Clean up editor if (this.editor) { this.editor._cancelEditor(); } + + // Disconnect signals + if (this.grabOpBeginId) { + global.display.disconnect(this.grabOpBeginId); + this.grabOpBeginId = null; + } + if (this.grabOpEndId) { + global.display.disconnect(this.grabOpEndId); + this.grabOpEndId = null; + } + + // Remove keybindings + try { + Main.keybindingManager.removeHotKey('show-zones'); + Main.keybindingManager.removeHotKey('open-zone-editor'); + for (let i = 1; i <= 9; i++) { + Main.keybindingManager.removeHotKey('snap-to-zone-' + i); + } + } catch(e) { + global.logError('GridSnap: Error removing keybindings - ' + e); + } } }; diff --git a/install.sh b/install.sh index 154c322..73d364d 100755 --- a/install.sh +++ b/install.sh @@ -27,8 +27,9 @@ echo "2. Open System Settings → Extensions" echo "3. Find 'GridSnap' and toggle it on" echo "" echo "Keyboard shortcuts:" -echo " Super+Z : Show/hide zones" -echo " Super+Shift+Z : Cycle layouts" -echo " Super+Numpad1-9 : Snap to zone" +echo " Super+Z : Show/hide zones" +echo " Super+Shift+Z : Cycle layouts" +echo " Super+Ctrl+1-9 : Snap to zone" +echo " Super+Shift+E : Open zone editor" echo "" echo "Enjoy your new window manager!"