diff --git a/.eslintrc.yml b/.eslintrc.yml index 3683f2c..fa19dbe 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -175,7 +175,7 @@ rules: no-unused-vars: - error # Vars use a suffix _ instead of a prefix because of file-scope private vars - - varsIgnorePattern: (^unused|Manager|buildPrefsWidget|enable|disable|init|_$) + - varsIgnorePattern: (^unused|Manager|fillPreferencesWindow|enable|disable|init|_$) argsIgnorePattern: ^(unused|_) no-useless-call: error no-useless-computed-key: error diff --git a/extension.js b/extension.js index 37037a9..8c94b21 100644 --- a/extension.js +++ b/extension.js @@ -1,16 +1,28 @@ /* global global */ +/* BEGIN NON-G45 */ const Meta = imports.gi.Meta; const Main = imports.ui.main; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; - const ExtensionUtils = imports.misc.extensionUtils; - -const {Clutter, St} = imports.gi; - +const Clutter = imports.gi.Clutter; +const St = imports.gi.St; const Config = imports.misc.config; const SHELL_VERSION = parseFloat(Config.PACKAGE_VERSION); +/* END NON-G45 */ + +/* BEGIN G45 */ +// import Meta from 'gi://Meta'; +// import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +// import Gio from 'gi://Gio'; +// import GLib from 'gi://GLib'; +// import Extension from 'resource:///org/gnome/shell/extensions/extension.js'; +// import Clutter from 'gi://Clutter'; +// import St from 'gi://St'; +// import Config from imports.misc.config; +// const SHELL_VERSION = parseFloat(Config.PACKAGE_VERSION); +/* END G45 */ let onWindowGrabBegin, onWindowGrabEnd; let requestMoveTimer, checkForMoveTimer, windowGrabBeginTimer, windowGrabEndTimer, checkIfNearGridTimer, keyManagerTimer; @@ -20,7 +32,10 @@ let dragStart = null; let gsettings; -// View logs with `journalctl -qf |grep -i -e Wintile -e 'js error' ` +/* View logs with +journalctl -qf | grep -i -e Wintile -e 'js error' +*/ + /** * @param {string} message - the actual log to be logged */ @@ -315,18 +330,23 @@ function sendMove(direction, ctrlPressed = false) { let monitorToLeft = -1; let monitorToRight = -1; + _log(`sendMove) curMonitor: ${JSON.stringify(curMonitor)}`); for (var i = 0; i < Main.layoutManager.monitors.length; i++) { if (i === monitorIndex) continue; let testMonitor = getMonitorInfo(i); - _log(`sendMove) curMonitor: ${JSON.stringify(curMonitor)}`); + let monitorHeightDiff = Math.abs(testMonitor.height - curMonitor.height); _log(`sendMove) testMonitor: ${JSON.stringify(testMonitor)}`); + _log(`sendMove) monitorsHeightDiff :${monitorHeightDiff}`); - if (testMonitor.x + testMonitor.width === curMonitor.x) + if (testMonitor.x < curMonitor.x && + Math.abs(testMonitor.y - curMonitor.y) < (100 + monitorHeightDiff) && + (monitorToLeft === -1 || (monitorToLeft >= 0 && testMonitor.x > Main.layoutManager.monitors[monitorToLeft].x))) monitorToLeft = i; - if (curMonitor.x + curMonitor.width === testMonitor.x) + if (testMonitor.x > curMonitor.x && Math.abs(testMonitor.y - curMonitor.y) < (100 + monitorHeightDiff) && + (monitorToRight === -1 || (monitorToRight >= 0 && testMonitor.x < Main.layoutManager.monitors[monitorToRight].x))) monitorToRight = i; } _log(`sendMove) monitorToLeft: ${monitorToLeft} monitorToRight: ${monitorToRight}`); @@ -458,8 +478,12 @@ function sendMove(direction, ctrlPressed = false) { } else if (monitorToLeft !== -1) { // There is a monitor to the left, so let's go there _log('sendMove) left - yes monitor'); + let newMonitor = getMonitorInfo(monitorToLeft); app.move_to_monitor(monitorToLeft); - moveApp(app, {'row': app.wintile.row, 'col': colCount - 1, 'height': app.wintile.height, 'width': 1}); + if (app.wintile.height === rowCount) + moveApp(app, {'row': app.wintile.row, 'col': newMonitor.colCount - 1, 'height': newMonitor.rowCount, 'width': 1}); + else + moveApp(app, {'row': app.wintile.row, 'col': newMonitor.colCount - 1, 'height': app.wintile.height, 'width': 1}); } else { // We are already on the left, and there is no other monitor to the left // Move to the left most column at full height @@ -480,8 +504,12 @@ function sendMove(direction, ctrlPressed = false) { } else if (monitorToRight !== -1) { // There is a monitor to the right, so let's go there _log('sendMove) right - yes monitor'); + let newMonitor = getMonitorInfo(monitorToRight); app.move_to_monitor(monitorToRight); - moveApp(app, {'row': app.wintile.row, 'col': 0, 'height': app.wintile.height, 'width': 1}); + if (app.wintile.height === rowCount) + moveApp(app, {'row': app.wintile.row, 'col': 0, 'height': newMonitor.rowCount, 'width': 1}); + else + moveApp(app, {'row': app.wintile.row, 'col': 0, 'height': app.wintile.height, 'width': 1}); } else { // We are already on the right, and there is no other monitor to the right // Move to the right most column at full height @@ -766,220 +794,217 @@ function checkIfNearGrid(app) { var colWidth = Math.floor(monitor.width / colCount); var rowHeight = Math.floor(monitor.height / rowCount); - var inMonitorBounds = false; - if (mouseX >= monitor.x && mouseX < monitor.x + monitor.width && mouseY >= monitor.y && mouseY < monitor.y + monitor.height) - inMonitorBounds = true; - let window = app.get_frame_rect(); _log(`checkIfNearGrid) mouse - mouseX:${mouseX} mouseY:${mouseY} mask:${mask}`); _log(`checkIfNearGrid) keys - ctrl:${ctrlPressed} superPressed:${superPressed}`); - _log(`checkIfNearGrid) monitor - x:${monitor.x} y:${monitor.y} w:${monitor.width} h:${monitor.height} inB:${inMonitorBounds}`); + _log(`checkIfNearGrid) monitor - x:${monitor.x} y:${monitor.y} w:${monitor.width} h:${monitor.height}`); _log(`checkIfNearGrid) window - x:${window.x} y:${window.y} w:${window.width} h:${window.height}`); - if (inMonitorBounds) { - var c = Math.floor((mouseX - monitor.x) / colWidth); - var r = Math.floor((mouseY - monitor.y) / rowHeight); - c = Math.max(0, Math.min(c, colCount - 1)); - r = Math.max(0, Math.min(r, rowCount - 1)); - - var gridX = c * colWidth + monitor.x; - var inGrid = mouseX > gridX && mouseX < gridX + colWidth; - var centerOfGrid = mouseX > Math.floor(gridX + colWidth / 3) && mouseX < Math.floor(gridX + (2 * colWidth / 3)); - var topRow = mouseY < monitor.y + rowHeight; - var bottomRow = mouseY > monitor.y + monitor.height - rowHeight; - var nearTop = isClose(mouseY, monitor.y) || mouseY < monitor.y; - var nearBottom = isClose(mouseY, monitor.y + monitor.height) || mouseY > monitor.y + monitor.height; - var nearLeft = isClose(mouseX, monitor.x) || mouseX < monitor.x; - var nearRight = isClose(mouseX, monitor.x + monitor.width) || mouseX > monitor.x + monitor.width; - - var centerOfScreenH = monitor.x + Math.floor(monitor.width / 2); - var columnWidthFraction = Math.floor(colWidth / 5); - var nearCenterH = mouseX > centerOfScreenH - (columnWidthFraction / 2) && mouseX < centerOfScreenH + (columnWidthFraction / 2); - - var centerOfScreenV = monitor.y + Math.floor(monitor.height / 2); - var rowHeightFraction = Math.floor(rowHeight / 5); - var nearCenterV = mouseY > centerOfScreenV - (rowHeightFraction / 2) && mouseY < centerOfScreenV + (rowHeightFraction / 2); - - if (ctrlPressed && superPressed && dragStart === null) { - dragStart = {col: c, row: r, monitorIndex}; - _log(`checkIfNearGrid) dragStart: ${JSON.stringify(dragStart)}`); - } + var c = Math.floor((mouseX - monitor.x) / colWidth); + var r = Math.floor((mouseY - monitor.y) / rowHeight); + c = Math.max(0, Math.min(c, colCount - 1)); + r = Math.max(0, Math.min(r, rowCount - 1)); + + var gridX = c * colWidth + monitor.x; + var inGrid = mouseX > gridX && mouseX < gridX + colWidth; + var centerOfGrid = mouseX > Math.floor(gridX + colWidth / 3) && mouseX < Math.floor(gridX + (2 * colWidth / 3)); + var topRow = mouseY < monitor.y + rowHeight; + var bottomRow = mouseY > monitor.y + monitor.height - rowHeight; + var nearTop = isClose(mouseY, monitor.y) || mouseY < monitor.y; + var nearBottom = isClose(mouseY, monitor.y + monitor.height) || mouseY > monitor.y + monitor.height; + var nearLeft = isClose(mouseX, monitor.x) || mouseX < monitor.x; + var nearRight = isClose(mouseX, monitor.rightEdge) || mouseX > monitor.rightEdge; + + var centerOfScreenH = monitor.x + Math.floor(monitor.width / 2); + var columnWidthFraction = Math.floor(colWidth / 3); + var nearCenterH = mouseX > centerOfScreenH - (columnWidthFraction / 2) && mouseX < centerOfScreenH + (columnWidthFraction / 2); + + var centerOfScreenV = monitor.y + Math.floor(monitor.height / 2); + var rowHeightFraction = Math.floor(rowHeight / 3); + var nearCenterV = mouseY > centerOfScreenV - (rowHeightFraction / 2) && mouseY < centerOfScreenV + (rowHeightFraction / 2); + + if (ctrlPressed && superPressed && dragStart === null) { + dragStart = {col: c, row: r, monitorIndex}; + _log(`checkIfNearGrid) dragStart: ${JSON.stringify(dragStart)}`); + } - if (ctrlPressed && superPressed) { - // check if it's on the samescreen it was on before, otherwise reinitialize dragStart - if (dragStart.monitorIndex !== monitorIndex) - dragStart = {col: c, row: r, monitor}; - // If ctrl and super are pressed, draw the box from start to finish + if (ctrlPressed && superPressed) { + // check if it's on the samescreen it was on before, otherwise reinitialize dragStart + if (dragStart.monitorIndex !== monitorIndex) + dragStart = {col: c, row: r, monitor}; + // If ctrl and super are pressed, draw the box from start to finish + showPreview({ + col: Math.min(c, dragStart.col), + row: Math.min(r, dragStart.row), + width: Math.abs(c - dragStart.col) + 1, + height: Math.abs(r - dragStart.row) + 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (nearTop && nearCenterH) { + // If we are in the center top, show a preview for maximize + showPreview({ + col: 0, + row: 0, + width: colCount, + height: rowCount, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (nearBottom && nearCenterH) { + // If we are in the center bottom, show a preview for bottom maximized horizontally + showPreview({ + col: 0, + row: rowCount - 1, + width: colCount, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (nearLeft && nearCenterV) { + // If we are in the center left, show a preview for left maximize + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { showPreview({ - col: Math.min(c, dragStart.col), - row: Math.min(r, dragStart.row), - width: Math.abs(c - dragStart.col) + 1, - height: Math.abs(r - dragStart.row) + 1, + col: 0, + row: 0, + width: 2, + height: rowCount, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; - } else if (nearTop && nearCenterH) { - // If we are in the center top, show a preview for maximize + } else { showPreview({ col: 0, row: 0, - width: colCount, + width: 1, height: rowCount, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; - } else if (nearBottom && nearCenterH) { - // If we are in the center bottom, show a preview for bottom maximized horizontally + } + close = true; + } else if (nearRight && nearCenterV) { + // If we are in the center right, show a preview for right maximize + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { + showPreview({ + col: colCount - 2, + row: 0, + width: 2, + height: rowCount, + }, monitor.x, monitor.y, colWidth, rowHeight); + } else { + showPreview({ + col: colCount - 1, + row: 0, + width: 1, + height: rowCount, + }, monitor.x, monitor.y, colWidth, rowHeight); + } + close = true; + } else if (nearLeft && topRow) { + // If we are close to the top left, show the top left grid item + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { showPreview({ col: 0, - row: rowCount - 1, - width: colCount, + row: 0, + width: 2, height: 1, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; - } else if (nearLeft && nearCenterV) { - // If we are in the center left, show a preview for left maximize - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: 0, - row: 0, - width: 2, - height: rowCount, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: 0, - row: 0, - width: 1, - height: rowCount, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearRight && nearCenterV) { - // If we are in the center right, show a preview for right maximize - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: colCount - 2, - row: 0, - width: 2, - height: rowCount, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: colCount - 1, - row: 0, - width: 1, - height: rowCount, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearLeft && topRow) { - // If we are close to the top left, show the top left grid item - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: 0, - row: 0, - width: 2, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: 0, - row: 0, - width: 1, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearLeft && bottomRow) { - // If we are close to the bottom left, show the bottom left grid item - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: 0, - row: rowCount - 1, - width: 2, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: 0, - row: rowCount - 1, - width: 1, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearRight && topRow) { - // If we are close to the top right, show the top right grid item - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: colCount - 2, - row: 0, - width: 2, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: colCount - 1, - row: 0, - width: 1, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearRight && bottomRow) { - // If we are close to the bottom right, show the bottom right grid item - if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { - showPreview({ - col: colCount - 2, - row: rowCount - 1, - width: 2, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } else { - showPreview({ - col: colCount - 1, - row: rowCount - 1, - width: 1, - height: 1, - }, monitor.x, monitor.y, colWidth, rowHeight); - } - close = true; - } else if (nearTop && inGrid) { - // If we are close to the top, show a preview for the top grid item + } else { showPreview({ - col: c, + col: 0, row: 0, width: 1, height: 1, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; - } else if (nearBottom && centerOfGrid) { - // If we are close to the bottom and in the middle of a grid, show a preview for the bottom grid item at full height + } + close = true; + } else if (nearLeft && bottomRow) { + // If we are close to the bottom left, show the bottom left grid item + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { + showPreview({ + col: 0, + row: rowCount - 1, + width: 2, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + } else { + showPreview({ + col: 0, + row: rowCount - 1, + width: 1, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + } + close = true; + } else if (nearRight && topRow) { + // If we are close to the top right, show the top right grid item + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { + showPreview({ + col: colCount - 2, + row: 0, + width: 2, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + } else { showPreview({ - col: c, + col: colCount - 1, row: 0, width: 1, - height: rowCount, + height: 1, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; - } else if (nearBottom && inGrid) { - // If we are close to the bottom, show a preview for the bottom grid item + } + close = true; + } else if (nearRight && bottomRow) { + // If we are close to the bottom right, show the bottom right grid item + if ((colCount === 4 || colCount === 5) && config.preview.doubleWidth) { showPreview({ - col: c, + col: colCount - 2, row: rowCount - 1, - width: 1, + width: 2, height: 1, }, monitor.x, monitor.y, colWidth, rowHeight); close = true; - } else if (ctrlPressed) { + } else if (ctrlPressed || nearLeft || nearRight) { // If we are close to the left or right or ctrl pressed, show the preview, wherever the pointer is showPreview({ - col: c, - row: r, + col: colCount - 1, + row: rowCount - 1, width: 1, height: 1, }, monitor.x, monitor.y, colWidth, rowHeight); - close = true; } + close = true; + } else if (nearTop && inGrid) { + // If we are close to the top, show a preview for the top grid item + showPreview({ + col: c, + row: 0, + width: 1, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (nearBottom && centerOfGrid) { + // If we are close to the bottom and in the middle of a grid, show a preview for the bottom grid item at full height + showPreview({ + col: c, + row: 0, + width: 1, + height: rowCount, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (nearBottom && inGrid) { + // If we are close to the bottom, show a preview for the bottom grid item + showPreview({ + col: c, + row: rowCount - 1, + width: 1, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; + } else if (ctrlPressed || nearLeft || nearRight) { + // If we are close to the left or right or ctrl pressed, show the preview, wherever the pointer is + showPreview({ + col: c, + row: r, + width: 1, + height: 1, + }, monitor.x, monitor.y, colWidth, rowHeight); + close = true; } + if (!close) hidePreview(); @@ -1028,6 +1053,10 @@ function getMonitorInfo(monitorIndex) { y: space.y, width: space.width, height: space.height, + leftEdge: space.x, + rightEdge: space.x + space.width, + topEdge: space.y, + bottomEdge: space.y + space.height, isPortrait, isNotUltrawide, colCount, @@ -1039,119 +1068,129 @@ function getMonitorInfo(monitorIndex) { /** * */ -function enable() { - _log('enable) Keymanager is being defined'); - keyManager = new KeyBindings.Manager(); - let desktopSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.keybindings'}); - let mutterKeybindingSettings = new Gio.Settings({schema_id: 'org.gnome.mutter.keybindings'}); - let mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'}); - try { - let shellSettings = new Gio.Settings({schema_id: 'org.gnome.shell.overrides'}); - shellSettings.set_boolean('edge-tiling', false); - } catch (error) { - _log('enable) org.gnome.shell.overrides does not exist'); - } - oldbindings['unmaximize'] = desktopSettings.get_strv('unmaximize'); - oldbindings['maximize'] = desktopSettings.get_strv('maximize'); - oldbindings['toggle_tiled_left'] = mutterKeybindingSettings.get_strv('toggle-tiled-left'); - oldbindings['toggle_tiled_right'] = mutterKeybindingSettings.get_strv('toggle-tiled-right'); - changeBinding(desktopSettings, 'unmaximize', 'Down', 'Down'); - changeBinding(desktopSettings, 'maximize', 'Up', 'Up'); - changeBinding(mutterKeybindingSettings, 'toggle-tiled-left', 'Left', 'Left'); - changeBinding(mutterKeybindingSettings, 'toggle-tiled-right', 'Right', 'Right'); - mutterSettings.set_boolean('edge-tiling', false); - keyManagerTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, () => { - keyManager.add('left', () => { - requestMove('left', true); - }); - keyManager.add('right', () => { - requestMove('right', true); - }); - keyManager.add('up', () => { - requestMove('up', true); - }); - keyManager.add('down', () => { - requestMove('down', true); - }); - keyManager.add('left', () => { - requestMove('left'); - }); - keyManager.add('right', () => { - requestMove('right'); +class WintileExtension { + enable() { + _log('enable) Keymanager is being defined'); + keyManager = new KeyBindings.Manager(); + let desktopSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.keybindings'}); + let mutterKeybindingSettings = new Gio.Settings({schema_id: 'org.gnome.mutter.keybindings'}); + let mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'}); + try { + let shellSettings = new Gio.Settings({schema_id: 'org.gnome.shell.overrides'}); + shellSettings.set_boolean('edge-tiling', false); + } catch (error) { + _log('enable) org.gnome.shell.overrides does not exist'); + } + oldbindings['unmaximize'] = desktopSettings.get_strv('unmaximize'); + oldbindings['maximize'] = desktopSettings.get_strv('maximize'); + oldbindings['toggle_tiled_left'] = mutterKeybindingSettings.get_strv('toggle-tiled-left'); + oldbindings['toggle_tiled_right'] = mutterKeybindingSettings.get_strv('toggle-tiled-right'); + changeBinding(desktopSettings, 'unmaximize', 'Down', 'Down'); + changeBinding(desktopSettings, 'maximize', 'Up', 'Up'); + changeBinding(mutterKeybindingSettings, 'toggle-tiled-left', 'Left', 'Left'); + changeBinding(mutterKeybindingSettings, 'toggle-tiled-right', 'Right', 'Right'); + mutterSettings.set_boolean('edge-tiling', false); + keyManagerTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 3000, () => { + keyManager.add('left', () => { + requestMove('left', true); + }); + keyManager.add('right', () => { + requestMove('right', true); + }); + keyManager.add('up', () => { + requestMove('up', true); + }); + keyManager.add('down', () => { + requestMove('down', true); + }); + keyManager.add('left', () => { + requestMove('left'); + }); + keyManager.add('right', () => { + requestMove('right'); + }); + keyManager.add('up', () => { + requestMove('up'); + }); + keyManager.add('down', () => { + requestMove('down'); + }); }); - keyManager.add('up', () => { - requestMove('up'); + + // Since GNOME 40 the metaDisplay argument isn't passed anymore to these callbacks. + // We "translate" the parameters here so that things work on both GNOME 3 and 40. + onWindowGrabBegin = global.display.connect('grab-op-begin', (metaDisplay, metaScreen, metaWindow, metaGrabOp, _gpointer) => { + if (SHELL_VERSION >= 40) + windowGrabBegin(metaScreen, metaWindow); + else + windowGrabBegin(metaWindow, metaGrabOp); }); - keyManager.add('down', () => { - requestMove('down'); + onWindowGrabEnd = global.display.connect('grab-op-end', (metaDisplay, metaScreen, metaWindow, metaGrabOp, _gpointer) => { + if (SHELL_VERSION >= 40) + windowGrabEnd(metaScreen, metaWindow); + else + windowGrabEnd(metaWindow, metaGrabOp); }); - }); - // Since GNOME 40 the metaDisplay argument isn't passed anymore to these callbacks. - // We "translate" the parameters here so that things work on both GNOME 3 and 40. - onWindowGrabBegin = global.display.connect('grab-op-begin', (metaDisplay, metaScreen, metaWindow, metaGrabOp, _gpointer) => { - if (SHELL_VERSION >= 40) - windowGrabBegin(metaScreen, metaWindow); - else - windowGrabBegin(metaWindow, metaGrabOp); - }); - onWindowGrabEnd = global.display.connect('grab-op-end', (metaDisplay, metaScreen, metaWindow, metaGrabOp, _gpointer) => { - if (SHELL_VERSION >= 40) - windowGrabEnd(metaScreen, metaWindow); - else - windowGrabEnd(metaWindow, metaGrabOp); - }); + // Create a new gsettings object + preview = new St.BoxLayout({ + style_class: 'tile-preview', + visible: false, + }); + Main.uiGroup.add_actor(preview); - // Create a new gsettings object - preview = new St.BoxLayout({ - style_class: 'tile-preview', - visible: false, - }); - Main.uiGroup.add_actor(preview); + gsettings = ExtensionUtils.getSettings(); + updateSettings(); - gsettings = ExtensionUtils.getSettings(); - updateSettings(); + // Watch the gsettings for changes + gsettings.connect('changed', updateSettings.bind()); + } - // Watch the gsettings for changes - gsettings.connect('changed', updateSettings.bind()); + /** + * + */ + disable() { + _log('disable) Keymanager is being removed'); + keyManager.removeAll(); + keyManager.destroy(); + keyManager = null; + let desktopSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.keybindings'}); + let mutterKeybindingSettings = new Gio.Settings({schema_id: 'org.gnome.mutter.keybindings'}); + let mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'}); + try { + let shellSettings = new Gio.Settings({schema_id: 'org.gnome.shell.overrides'}); + shellSettings.reset('edge-tiling'); + } catch (error) { + _log('disable) org.gnome.shell.overrides does not exist'); + } + desktopSettings.reset('unmaximize'); + desktopSettings.reset('maximize'); + desktopSettings = null; + mutterKeybindingSettings.reset('toggle-tiled-left'); + mutterKeybindingSettings.reset('toggle-tiled-right'); + mutterKeybindingSettings = null; + mutterSettings.reset('edge-tiling'); + mutterSettings = null; + global.display.disconnect(onWindowGrabBegin); + global.display.disconnect(onWindowGrabEnd); + onWindowGrabBegin = null; + onWindowGrabEnd = null; + GLib.source_remove(requestMoveTimer); + GLib.source_remove(checkForMoveTimer); + GLib.source_remove(windowGrabBeginTimer); + GLib.source_remove(windowGrabEndTimer); + GLib.source_remove(checkIfNearGridTimer); + GLib.source_remove(keyManagerTimer); + gsettings = null; + preview = null; + dragStart = null; + } } /** * + * @param {object} _meta = standard meta object */ -function disable() { - _log('disable) Keymanager is being removed'); - keyManager.removeAll(); - keyManager.destroy(); - keyManager = null; - let desktopSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.keybindings'}); - let mutterKeybindingSettings = new Gio.Settings({schema_id: 'org.gnome.mutter.keybindings'}); - let mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'}); - try { - let shellSettings = new Gio.Settings({schema_id: 'org.gnome.shell.overrides'}); - shellSettings.reset('edge-tiling'); - } catch (error) { - _log('disable) org.gnome.shell.overrides does not exist'); - } - desktopSettings.reset('unmaximize'); - desktopSettings.reset('maximize'); - desktopSettings = null; - mutterKeybindingSettings.reset('toggle-tiled-left'); - mutterKeybindingSettings.reset('toggle-tiled-right'); - mutterKeybindingSettings = null; - mutterSettings.reset('edge-tiling'); - mutterSettings = null; - global.display.disconnect(onWindowGrabBegin); - global.display.disconnect(onWindowGrabEnd); - onWindowGrabBegin = null; - onWindowGrabEnd = null; - GLib.source_remove(requestMoveTimer); - GLib.source_remove(checkForMoveTimer); - GLib.source_remove(windowGrabBeginTimer); - GLib.source_remove(windowGrabEndTimer); - GLib.source_remove(checkIfNearGridTimer); - GLib.source_remove(keyManagerTimer); - gsettings = null; - preview = null; - dragStart = null; +function init(_meta) { + return new WintileExtension(); } diff --git a/metadata.json b/metadata.json index 7133246..3db8a33 100644 --- a/metadata.json +++ b/metadata.json @@ -1,17 +1,13 @@ { "name": "WinTile", - "description": "WinTile is a hotkey driven window tiling system for GNOME that imitates the standard Win-Arrow keys of Windows 10, allowing you to maximize, maximize to sides, or 1/4 sized to corner across a single or multiple monitors using just Super+Arrow.\n\nAs of v14, WinTile also supports:\n- 2-5 columns and 1-4 rows for standard or ultrawide monitors\n- Top/bottom half support\n- Mouse preview and snapping for placing windows\n- 'Maximize' mode, which adds/removes GNOME animations\n- 'Ultrawide-only' mode, to allow standard screens to have different cols/row than ultrawides\n- Portrait screens will automatically swap columns and rows\n- Add gaps around tiles to avoid the 'crowded elevator' feeling'\n- Ctrl+Super+Arrow to grow a tile in that direction if space is available\n- Ctrl+drag to drop a tile in a specific spot\n- Ctrl+Super+drag to draw a grid for the new tile", + "description": "WinTile is a hotkey driven window tiling system for GNOME that imitates the standard Win-Arrow keys of Windows 10, allowing you to maximize, maximize to sides, or 1/4 sized to corner across a single or multiple monitors using just Super+Arrow.\n\nAs of v16, WinTile also supports:\n- 1-5 columns and 1-5 rows for standard or ultrawide monitors\n- Top/bottom half support\n- Mouse preview and snapping for placing windows\n- 'Maximize' mode, which adds/removes GNOME animations\n- 'Ultrawide-only' mode, to allow standard screens to have different cols/row than ultrawides\n- Portrait screens will automatically swap columns and rows\n- Add gaps around tiles to avoid the 'crowded elevator' feeling'\n- Ctrl+Super+Arrow to grow a tile in that direction if space is available\n- Ctrl+drag to drop a tile in a specific spot\n- Ctrl+Super+drag to draw a grid for the new tile", "uuid": "wintile@nowsci.com", "url": "https://github.com/fmstrat/wintile", "settings-schema":"org.gnome.shell.extensions.wintile", "shell-version": [ - "3.36", - "3.38", - "40", - "41", "42", "43", "44" ], - "version": 14 + "version": 16 } diff --git a/prefs.js b/prefs.js index 70d814d..654dbe2 100644 --- a/prefs.js +++ b/prefs.js @@ -9,8 +9,7 @@ const Me = ExtensionUtils.getCurrentExtension(); const Gettext = imports.gettext; const _ = Gettext.domain('wintile').gettext; -const Config = imports.misc.config; -const SHELL_VERSION = parseFloat(Config.PACKAGE_VERSION); +const gsettings = ExtensionUtils.getSettings(); /** * @@ -19,373 +18,64 @@ function init() { // empty } + /** * + * @param {object} window - Don't worry about this. Gnome handles it for you. */ -function buildPrefsWidget() { - // Create a parent widget that we'll return from this function - let layout = new Gtk.Grid({ - margin_bottom: 18, - margin_end: 18, - margin_start: 18, - margin_top: 18, - column_spacing: 12, - row_spacing: 12, - visible: true, - }); - - let gsettings; - gsettings = ExtensionUtils.getSettings(); - layout._gsettings = gsettings; - - let row = 0; - - // Add a simple title and add it to the layout - let title = new Gtk.Label({ - label: `${Me.metadata.name} Extension Preferences`, - halign: Gtk.Align.CENTER, - use_markup: true, - visible: true, - }); - layout.attach(title, 0, row++, 2, 1); - - // Column setting - let colsLabel = new Gtk.Label({ - label: _('Number of columns'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let colsInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let colsAdjustment = new Gtk.Adjustment({ - lower: 1, - upper: 5, - step_increment: 1, - }); - let colsSettingInt = new Gtk.SpinButton({ - adjustment: colsAdjustment, - snap_to_ticks: true, - visible: true, - }); - colsSettingInt.set_value(gsettings.get_int('cols')); - if (SHELL_VERSION >= 40) - colsInput.append(colsSettingInt); - else - colsInput.add(colsSettingInt); - layout.attach(colsLabel, 0, row, 1, 1); - layout.attach(colsInput, 1, row++, 1, 1); - - // Rows setting - let rowsLabel = new Gtk.Label({ - label: _('Number of rows'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let rowsInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let rowsAdjustment = new Gtk.Adjustment({ - lower: 1, - upper: 5, - step_increment: 1, - }); - let rowsSettingInt = new Gtk.SpinButton({ - adjustment: rowsAdjustment, - snap_to_ticks: true, - visible: true, - }); - rowsSettingInt.set_value(gsettings.get_int('rows')); - if (SHELL_VERSION >= 40) - rowsInput.append(rowsSettingInt); - else - rowsInput.add(rowsSettingInt); - layout.attach(rowsLabel, 0, row, 1, 1); - layout.attach(rowsInput, 1, row++, 1, 1); - - // 16:9 and 16:10 always 2x2 setting - let ultrawideOnlyLabel = new Gtk.Label({ - label: _('Use different rows and columns for non-ultrawide monitors'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let ultrawideOnlyInput = new Gtk.Switch({ - active: gsettings.get_boolean('ultrawide-only'), - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(ultrawideOnlyLabel, 0, row, 1, 1); - layout.attach(ultrawideOnlyInput, 1, row++, 1, 1); +function fillPreferencesWindow(window) { + let builder = Gtk.Builder.new(); + builder.add_from_file(`${Me.path}/settings.ui`); + let gridPage = builder.get_object('gridPage'); + let behaviorPage = builder.get_object('behaviorPage'); + window.add(gridPage); + window.add(behaviorPage); + + // let gsettings; + // gsettings = ExtensionUtils.getSettings(); + const bindSettings = (key, input) => { + key.value = gsettings.get_value(input).deep_unpack(); + gsettings.bind(input, key, 'active', Gio.SettingsBindFlags.DEFAULT); + }; - // ultrawide-only cols - let nonUltraColsLabel = new Gtk.Label({ - label: _(' Number of columns for non-ultrawide'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let nonUltraColsInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let nonUltraColsAdjustment = new Gtk.Adjustment({ - lower: 1, - upper: 5, - step_increment: 1, - }); - let nonUltraColsSettingInt = new Gtk.SpinButton({ - adjustment: nonUltraColsAdjustment, - snap_to_ticks: true, - visible: true, - }); - nonUltraColsSettingInt.set_value(gsettings.get_int('non-ultra-cols')); - if (SHELL_VERSION >= 40) - nonUltraColsInput.append(nonUltraColsSettingInt); - else - nonUltraColsInput.add(nonUltraColsSettingInt); - layout.attach(nonUltraColsLabel, 0, row, 1, 1); - layout.attach(nonUltraColsInput, 1, row++, 1, 1); + const connectAndSetInt = (key, input) => { + key.value = gsettings.get_value(input).deep_unpack(); + key.connect('value-changed', entry => { + gsettings.set_int(input, entry.value); + }); + }; - // ultrawide-only rows - let nonUltraRowsLabel = new Gtk.Label({ - label: _(' Number of rows for non-ultrawide'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let nonUltraRowsInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let nonUltraRowsAdjustment = new Gtk.Adjustment({ - lower: 1, - upper: 5, - step_increment: 1, - }); - let nonUltraRowsSettingInt = new Gtk.SpinButton({ - adjustment: nonUltraRowsAdjustment, - snap_to_ticks: true, - visible: true, - }); - nonUltraRowsSettingInt.set_value(gsettings.get_int('non-ultra-rows')); - if (SHELL_VERSION >= 40) - nonUltraRowsInput.append(nonUltraRowsSettingInt); - else - nonUltraRowsInput.add(nonUltraRowsSettingInt); - layout.attach(nonUltraRowsLabel, 0, row, 1, 1); - layout.attach(nonUltraRowsInput, 1, row++, 1, 1); + let ultrawideOnlyInput = builder.get_object('ultrawideOnlyInput'); + let nonUltrawideGroup = builder.get_object('nonUltrawideGroup'); - ultrawideOnlyInput.connect('notify::active', function () { + const toggleUltrawide = () => { if (ultrawideOnlyInput.active) { // Show rows and columns options - nonUltraRowsLabel.show(); - nonUltraRowsInput.show(); - nonUltraColsLabel.show(); - nonUltraColsInput.show(); + nonUltrawideGroup.show(); } else { // Hide rows and columns options - nonUltraRowsLabel.hide(); - nonUltraRowsInput.hide(); - nonUltraColsLabel.hide(); - nonUltraColsInput.hide(); + nonUltrawideGroup.hide(); } - }); - - // Maximize setting - let maximizeLabel = new Gtk.Label({ - label: _('Use true maximizing of windows'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let maximizeInput = new Gtk.Switch({ - active: gsettings.get_boolean('use-maximize'), - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(maximizeLabel, 0, row, 1, 1); - layout.attach(maximizeInput, 1, row++, 1, 1); - - // Minimize setting - let minimizeLabel = new Gtk.Label({ - label: _('Allow minimizing of windows'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let minimizeInput = new Gtk.Switch({ - active: gsettings.get_boolean('use-minimize'), - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(minimizeLabel, 0, row, 1, 1); - layout.attach(minimizeInput, 1, row++, 1, 1); - - // Preview settings - let previewEnabled = gsettings.get_boolean('preview'); - let previewLabel = new Gtk.Label({ - label: _('Enable preview and snapping when dragging windows'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let previewInput = new Gtk.Switch({ - active: previewEnabled, - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(previewLabel, 0, row, 1, 1); - layout.attach(previewInput, 1, row++, 1, 1); - - // Double width previews - let doubleWidthLabel = new Gtk.Label({ - label: _(' Use double width previews on sides in 4 column mode'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let doubleWidthInput = new Gtk.Switch({ - active: gsettings.get_boolean('double-width'), - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(doubleWidthLabel, 0, row, 1, 1); - layout.attach(doubleWidthInput, 1, row++, 1, 1); - - // Preview distance - let previewDistanceLabel = new Gtk.Label({ - label: _(' Pixels from edge to start preview'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let previewDistanceInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let previewDistanceAdjustment = new Gtk.Adjustment({ - lower: 0, - upper: 150, - step_increment: 1, - }); - let previewDistanceSettingInt = new Gtk.SpinButton({ - adjustment: previewDistanceAdjustment, - snap_to_ticks: true, - visible: true, - }); - previewDistanceSettingInt.set_value(gsettings.get_int('distance')); - if (SHELL_VERSION >= 40) - previewDistanceInput.append(previewDistanceSettingInt); - else - previewDistanceInput.add(previewDistanceSettingInt); - layout.attach(previewDistanceLabel, 0, row, 1, 1); - layout.attach(previewDistanceInput, 1, row++, 1, 1); - - // Delay - let previewDelayLabel = new Gtk.Label({ - label: _(' Delay in ms before preview displays'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let previewDelayInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let previewDelayAdjustment = new Gtk.Adjustment({ - lower: 25, - upper: 1000, - step_increment: 1, - }); - let previewDelaySettingInt = new Gtk.SpinButton({ - adjustment: previewDelayAdjustment, - snap_to_ticks: true, - visible: true, - }); - previewDelaySettingInt.set_value(gsettings.get_int('delay')); - if (SHELL_VERSION >= 40) - previewDelayInput.append(previewDelaySettingInt); - else - previewDelayInput.add(previewDelaySettingInt); - layout.attach(previewDelayLabel, 0, row, 1, 1); - layout.attach(previewDelayInput, 1, row++, 1, 1); - - // Gap setting - let gapLabel = new Gtk.Label({ - label: _('Gap width around tiles'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let gapInput = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - visible: true, - }); - let gapAdjustment = new Gtk.Adjustment({ - lower: 0, - upper: 50, - step_increment: 2, - }); - let gapSettingInt = new Gtk.SpinButton({ - adjustment: gapAdjustment, - snap_to_ticks: true, - visible: true, - }); - gapSettingInt.set_value(gsettings.get_int('gap')); - if (SHELL_VERSION >= 40) - gapInput.append(gapSettingInt); - else - gapInput.add(gapSettingInt); - layout.attach(gapLabel, 0, row, 1, 1); - layout.attach(gapInput, 1, row++, 1, 1); - - // Debug setting - let debugLabel = new Gtk.Label({ - label: _('Turn on debugging'), - visible: true, - hexpand: true, - halign: Gtk.Align.START, - }); - let debugInput = new Gtk.Switch({ - active: gsettings.get_boolean('debug'), - halign: Gtk.Align.END, - visible: true, - }); - layout.attach(debugLabel, 0, row, 1, 1); - layout.attach(debugInput, 1, row++, 1, 1); - - const bindSettings = (key, input) => { - gsettings.bind(key, input, 'active', Gio.SettingsBindFlags.DEFAULT); - }; - - const connectAndSetInt = (setting, key) => { - setting.connect('value-changed', entry => { - gsettings.set_int(key, entry.value); - }); }; // settings that aren't toggles need a connect - connectAndSetInt(colsSettingInt, 'cols'); - connectAndSetInt(rowsSettingInt, 'rows'); - connectAndSetInt(nonUltraColsSettingInt, 'non-ultra-cols'); - connectAndSetInt(nonUltraRowsSettingInt, 'non-ultra-rows'); - connectAndSetInt(previewDistanceSettingInt, 'distance'); - connectAndSetInt(previewDelaySettingInt, 'delay'); - connectAndSetInt(gapSettingInt, 'gap'); + connectAndSetInt(builder.get_object('colsSettingInt'), 'cols'); + connectAndSetInt(builder.get_object('rowsSettingInt'), 'rows'); + connectAndSetInt(builder.get_object('nonUltraColsSettingInt'), 'non-ultra-cols'); + connectAndSetInt(builder.get_object('nonUltraRowsSettingInt'), 'non-ultra-rows'); + connectAndSetInt(builder.get_object('previewDistanceSettingInt'), 'distance'); + connectAndSetInt(builder.get_object('previewDelaySettingInt'), 'delay'); + connectAndSetInt(builder.get_object('gapSettingInt'), 'gap'); // all other settings need a bind - bindSettings('ultrawide-only', ultrawideOnlyInput); - bindSettings('use-maximize', maximizeInput); - bindSettings('use-minimize', minimizeInput); - bindSettings('preview', previewInput); - bindSettings('double-width', doubleWidthInput); - bindSettings('debug', debugInput); - - // Return our widget which will be added to the window - return layout; + bindSettings(builder.get_object('ultrawideOnlyInput'), 'ultrawide-only'); + bindSettings(builder.get_object('maximizeInput'), 'use-maximize'); + bindSettings(builder.get_object('minimizeInput'), 'use-minimize'); + bindSettings(builder.get_object('previewInput'), 'preview'); + bindSettings(builder.get_object('doubleWidthInput'), 'double-width'); + bindSettings(builder.get_object('debugInput'), 'debug'); + ultrawideOnlyInput.connect('notify::active', toggleUltrawide); + + // make sure that the non-ultrawide menu is hidden unless it's enabled + toggleUltrawide(); } diff --git a/settings.ui b/settings.ui new file mode 100644 index 0000000..1fce634 --- /dev/null +++ b/settings.ui @@ -0,0 +1,263 @@ + + + + Dimensions + Dimensions + preferences-desktop-apps-symbolic + + + + Grid size + Configure the rows and columns of YourExtensionName + + + + Columns + + + + colsAdjustment + + + + + + 1 + 1 + 5 + 2 + + + + + + + + Rows + + + + rowsAdjustment + + + + + + 1 + 1 + 5 + 2 + + + + + + + + Use different rows and columns for non-ultrawide monitors + + + + false + + + + + + + + + + Number of columns for non-ultrawide + Configure the separate rows and columns of non-ultrawides + + + + Columns + + + + nonUltraColsAdjustment + + + + + + 1 + 1 + 5 + 2 + + + + + + + + Rows + + + + nonUltraRowsAdjustment + + + + + + 1 + 1 + 5 + 2 + + + + + + + + + + + + Behavior + Behavior + applications-system-symbolic + + + + + Behavior + + + + Use true maximizing of windows + + + + true + + + + + + + + + Use true minimizing of windows + + + + true + + + + + + + + + Enable preview and snapping when dragging windows + + + + true + + + + + + + + + Pixels from edge to start preview + + + + previewDistanceAdjustment + + + + + + 0 + 1 + 150 + 0 + + + + + + + + + Delay in ms before preview displays + + + + previewDelayAdjustment + + + + + + 25 + 1 + 1000 + 25 + + + + + + + + + Use double width previews on sides in 4 and 5 column mode + + + + true + + + + + + + + + Gap width around tiles + + + + gapAdjustment + + + + + + 0 + 2 + 50 + 0 + + + + + + + + + Turn on debugging + + + + true + + + + + + + +