diff --git a/README.md b/README.md index 3badb41..aef558a 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,3 @@ WinTile: Windows 10 window tiling for GNOME 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 a window using just ``+``. - -Selecting the Super key ------------------------ -By default, this extension uses ``+`` to move windows. This is because ``+`` is reserved by GNOME in the keyboard shortcut settings. The below script will toggle these default key bindings so you can use ``+`` for this extension. - -To use ``+`` for this extension: -``` -$ bash ~/.local/share/gnome-shell/extensions/wintile@nowsci.com/setHotKey.sh Super -``` - -To reset ``+`` to default for GNOME: -``` -$ bash ~/.local/share/gnome-shell/extensions/wintile@nowsci.com/setHotKey.sh ControlShiftSuper -``` diff --git a/extension.js b/extension.js index 2072627..b3c05fe 100644 --- a/extension.js +++ b/extension.js @@ -1,8 +1,7 @@ -const Lang = imports.lang const Meta = imports.gi.Meta -const Shell = imports.gi.Shell const Main = imports.ui.main const Mainloop = imports.mainloop; +const Gio = imports.gi.Gio; let _close = 50; var debug = false; @@ -11,57 +10,21 @@ var _log = function(){} if (debug) _log = log.bind(window.console); +const Config = imports.misc.config; +window.gsconnect = { + extdatadir: imports.misc.extensionUtils.getCurrentExtension().path, + shell_version: parseInt(Config.PACKAGE_VERSION.split('.')[1], 10) +}; +imports.searchPath.unshift(gsconnect.extdatadir); -const KeyManager = new Lang.Class({ - Name: 'MyKeyManager', - - _init: function() { - this.grabbers = new Map() - - global.display.connect( - 'accelerator-activated', - Lang.bind(this, function(display, action, deviceId, timestamp){ - _log('Accelerator Activated: [display={}, action={}, deviceId={}, timestamp={}]', - display, action, deviceId, timestamp) - this._onAccelerator(action) - })) - }, - - listenFor: function(accelerator, callback){ - _log('Trying to listen for hot key [accelerator={}]', accelerator) - let action = global.display.grab_accelerator(accelerator) - - if(action == Meta.KeyBindingAction.NONE) { - _log('Unable to grab accelerator [binding={}]', accelerator) - } else { - _log('Grabbed accelerator [action={}]', action) - let name = Meta.external_binding_name_for_action(action) - _log('Received binding name for action [name={}, action={}]', - name, action) - - _log('Requesting WM to allow binding [name={}]', name) - Main.wm.allowKeybinding(name, Shell.ActionMode.ALL) - - this.grabbers.set(action, { - name: name, - accelerator: accelerator, - callback: callback, - action: action - }) - } - - }, - - _onAccelerator: function(action) { - let grabber = this.grabbers.get(action) - - if(grabber) { - this.grabbers.get(action).callback() - } else { - _log('No listeners [action={}]', action) - } - } -}) +const KeyBindings = imports.keybindings +let keyManager = new KeyBindings.Manager(); +var oldbindings = { + unmaximize: [], + maximize: [], + toggle_tiled_left: [], + toggle_tiled_right: [] +} function isClose(a, b) { if (a <= b && a > b - _close) @@ -304,17 +267,48 @@ function requestMove(direction) { }); } -var enable = function() { - let modifier = ""; - let modifier2 = ""; - let keyManager = new KeyManager() - keyManager.listenFor(modifier+"left", function() { requestMove("left") }) - keyManager.listenFor(modifier+"right", function() { requestMove("right") }) - keyManager.listenFor(modifier+"up", function() { requestMove("up") }) - keyManager.listenFor(modifier+"down", function() { requestMove("down") }) - keyManager.listenFor(modifier2+"left", function() { requestMove("left") }) - keyManager.listenFor(modifier2+"right", function() { requestMove("right") }) - keyManager.listenFor(modifier2+"up", function() { requestMove("up") }) - keyManager.listenFor(modifier2+"down", function() { requestMove("down") }) +function changeBinding(settings, key, oldBinding, newBinding) { + var binding = oldbindings[key.replace(/-/g, '_')]; + var _newbindings = []; + for (var i = 0; i < binding.length; i++) { + let currentbinding = binding[i]; + if (currentbinding == oldBinding) + currentbinding = newBinding; + _newbindings.push(currentbinding) + } + settings.set_strv(key, _newbindings); } +function resetBinding(settings, key) { + var binding = oldbindings[key.replace(/-/g, '_')]; + settings.set_strv(key, binding); +} + +var enable = function() { + let desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.keybindings' }); + let mutterSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter.keybindings' }); + oldbindings['unmaximize'] = desktopSettings.get_strv('unmaximize'); + oldbindings['maximize'] = desktopSettings.get_strv('maximize'); + oldbindings['toggle_tiled_left'] = mutterSettings.get_strv('toggle-tiled-left'); + oldbindings['toggle_tiled_right'] = mutterSettings.get_strv('toggle-tiled-right'); + changeBinding(desktopSettings, 'unmaximize', 'Down', 'Down'); + changeBinding(desktopSettings, 'maximize', 'Up', 'Up'); + changeBinding(mutterSettings, 'toggle-tiled-left', 'Left', 'Left'); + changeBinding(mutterSettings, 'toggle-tiled-right', 'Right', 'Right'); + Mainloop.timeout_add(3000, function() { + keyManager.add("left", function() { requestMove("left") }) + keyManager.add("right", function() { requestMove("right") }) + keyManager.add("up", function() { requestMove("up") }) + keyManager.add("down", function() { requestMove("down") }) + }); +} + +var disable = function() { + keyManager.removeAll(); + let desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.keybindings' }); + let mutterSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter.keybindings' }); + resetBinding(desktopSettings, 'unmaximize'); + resetBinding(desktopSettings, 'maximize'); + resetBinding(mutterSettings, 'toggle-tiled-left'); + resetBinding(mutterSettings, 'toggle-tiled-right'); +} diff --git a/keybindings.js b/keybindings.js new file mode 100644 index 0000000..72c8532 --- /dev/null +++ b/keybindings.js @@ -0,0 +1,110 @@ +'use strict'; + +const Config = imports.misc.config; +const Main = imports.ui.main; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; + +const SHELL_VERSION_MINOR = parseInt(Config.PACKAGE_VERSION.split('.')[1]); + + +/** + * Keybindings.Manager is a simple convenience class for managing keyboard + * shortcuts in GNOME Shell. You bind a shortcut using add(), which on success + * will return a non-zero action id that can later be used with remove() to + * unbind the shortcut. + * + * Accelerators are accepted in the form returned by Gtk.accelerator_name() and + * callbacks are invoked directly, so should be complete closures. + * + * References: + * https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html + * https://developer.gnome.org/meta/stable/MetaDisplay.html + * https://developer.gnome.org/meta/stable/meta-MetaKeybinding.html + * https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/windowManager.js#L1093-1112 + */ +var Manager = class Manager { + + constructor() { + this._keybindings = new Map(); + + this._acceleratorActivatedId = global.display.connect( + 'accelerator-activated', + this._onAcceleratorActivated.bind(this) + ); + } + + _onAcceleratorActivated(display, action, deviceId, timestamp) { + try { + let binding = this._keybindings.get(action); + + if (binding !== undefined) { + binding.callback(); + } + } catch (e) { + logError(e); + } + } + + /** + * Add a keybinding with callback + * + * @param {String} accelerator - An accelerator in the form 'q' + * @param {Function} callback - A callback for the accelerator + * @return {Number} - A non-zero action id on success, or 0 on failure + */ + add(accelerator, callback) { + let action = Meta.KeyBindingAction.NONE; + + // A flags argument was added somewhere between 3.30-3.32 + if (SHELL_VERSION_MINOR > 30) { + action = global.display.grab_accelerator(accelerator, 0); + } else { + action = global.display.grab_accelerator(accelerator); + } + + if (action !== Meta.KeyBindingAction.NONE) { + let name = Meta.external_binding_name_for_action(action); + Main.wm.allowKeybinding(name, Shell.ActionMode.ALL); + this._keybindings.set(action, {name: name, callback: callback}); + } else { + logError(new Error(`Failed to add keybinding: '${accelerator}'`)); + } + + return action; + } + + /** + * Remove a keybinding + * + * @param {Number} accelerator - A non-zero action id returned by add() + */ + remove(action) { + try { + let binding = this._keybindings.get(action); + global.display.ungrab_accelerator(action); + Main.wm.allowKeybinding(binding.name, Shell.ActionMode.NONE); + this._keybindings.delete(action); + } catch (e) { + logError(new Error(`Failed to remove keybinding: ${e.message}`)); + } + } + + /** + * Remove all keybindings + */ + removeAll() { + for (let action of this._keybindings.keys()) { + this.remove(action); + } + } + + /** + * Destroy the keybinding manager and remove all keybindings + */ + destroy() { + global.display.disconnect(this._acceleratorActivatedId); + this.removeAll(); + } +}; + diff --git a/setHotKey.sh b/setHotKey.sh deleted file mode 100755 index 698b93c..0000000 --- a/setHotKey.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -if [ -z "$1" ]; then - echo "Usage: setHotKey.sh " -fi - -if [ "$1" = "ControlShiftSuper" ]; then - gsettings set org.gnome.desktop.wm.keybindings unmaximize "['Down', 'F5']" >/dev/null 2>&1 - gsettings set org.gnome.desktop.wm.keybindings maximize "['Up']" >/dev/null 2>&1 - gsettings set org.cinnamon.desktop.keybindings.wm push-tile-left "['Left']" >/dev/null 2>&1 - gsettings set org.gnome.mutter.keybindings toggle-tiled-left "['Left']" >/dev/null 2>&1 - gsettings set org.cinnamon.desktop.keybindings.wm push-tile-right "['Right']" >/dev/null 2>&1 - gsettings set org.gnome.mutter.keybindings toggle-tiled-right "['Right']" >/dev/null 2>&1 - echo -e "\nNow run Alt-F2 then R to restart GNOME.\n" -fi - -if [ "$1" = "Super" ]; then - gsettings set org.gnome.desktop.wm.keybindings unmaximize "['Down', 'F5']" >/dev/null 2>&1 - gsettings set org.gnome.desktop.wm.keybindings maximize "['Up']" >/dev/null 2>&1 - gsettings set org.cinnamon.desktop.keybindings.wm push-tile-left "['Left']" >/dev/null 2>&1 - gsettings set org.gnome.mutter.keybindings toggle-tiled-left "['Left']" >/dev/null 2>&1 - gsettings set org.cinnamon.desktop.keybindings.wm push-tile-right "['Right']" >/dev/null 2>&1 - gsettings set org.gnome.mutter.keybindings toggle-tiled-right "['Right']" >/dev/null 2>&1 - echo -e "\nNow run Alt-F2 then R to restart GNOME.\n" -fi