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 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 19:50:23 -07:00
parent 1681580bea
commit 3d79b0336a
3 changed files with 110 additions and 56 deletions

View File

@@ -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

View File

@@ -333,6 +333,7 @@ ZoneManager.prototype = {
this.layouts = DEFAULT_LAYOUTS;
this.isShowing = false;
this.dragInProgress = false;
this.draggedWindow = null;
this.editor = new ZoneEditor();
// Connect to window grab events
@@ -346,6 +347,9 @@ ZoneManager.prototype = {
Lang.bind(this, this._onGrabBegin));
this.grabOpEndId = global.display.connect('grab-op-end',
Lang.bind(this, this._onGrabEnd));
// Monitor for modifier key changes
this.modifierPollTimeoutId = null;
},
_setupKeybindings: function() {
@@ -363,38 +367,68 @@ ZoneManager.prototype = {
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,
'<Super>KP_' + i,
Lang.bind(this, function() { this._snapToZone(i - 1); })
'<Super><Ctrl>' + 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;
}
},
@@ -527,8 +561,10 @@ ZoneManager.prototype = {
},
_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);
}
@@ -538,8 +574,13 @@ ZoneManager.prototype = {
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);
}
}
};

View File

@@ -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!"