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:
@@ -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
|
||||
|
||||
|
||||
157
extension.js
157
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 = {
|
||||
'<Super>z',
|
||||
Lang.bind(this, this._toggleZonesOverlay)
|
||||
);
|
||||
|
||||
|
||||
// Add keybinding to open zone editor (Super+Shift+E)
|
||||
Main.keybindingManager.addHotKey(
|
||||
'open-zone-editor',
|
||||
'<Super><Shift>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,
|
||||
'<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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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!"
|
||||
|
||||
Reference in New Issue
Block a user