diff --git a/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml b/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml index 7b441e47..0fbf6670 100644 --- a/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml +++ b/resources/schemas/org.gnome.shell.extensions.tilingshell.gschema.xml @@ -257,6 +257,14 @@ Focus the window prior to the current focused window + + + Switch next window with focused window + + + + Switch previous window with focused window + Minimize all the other windows and show only the focused window diff --git a/src/extension.ts b/src/extension.ts index 3d064125..a79194c5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -289,6 +289,17 @@ export default class TilingShellExtension extends Extension { this._onKeyboardFocusWinDirection(dp, dir); }, ); + this._signals.connect( + this._keybindings, + 'shift-focus-window', + ( + kb: KeyBindings, + dp: Meta.Display, + dir: FocusSwitchDirection, + ) => { + this._onKeyboardFocusSwitchWin(dp, dir); + }, + ); this._signals.connect( this._keybindings, 'highlight-current-window', @@ -649,7 +660,7 @@ export default class TilingShellExtension extends Extension { bestWindow.activate(global.get_current_time()); } - private _onKeyboardFocusWin( + private _getWindowsAndFocusAdjacent( display: Meta.Display, direction: FocusSwitchDirection, ) { @@ -661,36 +672,107 @@ export default class TilingShellExtension extends Extension { (focus_window.get_wm_class() && focus_window.get_wm_class() === 'gjs') ) - return; + return {}; const windowList = filterUnfocusableWindows( - focus_window.get_workspace().list_windows(), + focus_window + .get_workspace() + .list_windows() + .sort((a, b) => { + // goal is to have a stable ordering based on windows placement + // This is "clock-like" -- not perfect, but roughly quadrant-ordered + const aRect = a.get_frame_rect(); + const bRect = b.get_frame_rect(); + return ( + bRect.y - aRect.y || + aRect.x - bRect.x || // reversed + bRect.width - aRect.width || + bRect.height - aRect.height || + b.get_stable_sequence() - a.get_stable_sequence() + ); + }), ); + if (windowList.length < 2) return {}; + const focusParent = focus_window.get_transient_for() || focus_window; const focusedIdx = windowList.findIndex((win) => { // in case we are iterating over a modal dialog for our focused window return win === focusParent; }); - let nextIndex = -1; + // default to staying same place + let nextIdx = focusedIdx; switch (direction) { case FocusSwitchDirection.PREV: - if (focusedIdx === 0 && Settings.WRAPAROUND_FOCUS) { - windowList[windowList.length - 1].activate( - global.get_current_time(), - ); + if (focusedIdx === 0) { + if (Settings.WRAPAROUND_FOCUS) + nextIdx = windowList.length - 1; } else { - windowList[focusedIdx - 1].activate( - global.get_current_time(), - ); + nextIdx = focusedIdx - 1; } break; case FocusSwitchDirection.NEXT: - nextIndex = (focusedIdx + 1) % windowList.length; - if (nextIndex > 0 || Settings.WRAPAROUND_FOCUS) - windowList[nextIndex].activate(global.get_current_time()); + nextIdx = (focusedIdx + 1) % windowList.length; + if (nextIdx === 0 && !Settings.WRAPAROUND_FOCUS) + nextIdx = focusedIdx; break; } + if (focusedIdx === nextIdx) return {}; + + return { + focus_window, + windowList, + focusedIdx, + nextIdx, + }; + } + + private _onKeyboardFocusWin( + display: Meta.Display, + direction: FocusSwitchDirection, + ) { + const focusWindowsContext = this._getWindowsAndFocusAdjacent( + display, + direction, + ); + if (!focusWindowsContext.focus_window) return; + + focusWindowsContext.windowList[focusWindowsContext.nextIdx].activate( + global.get_current_time(), + ); + } + + private _onKeyboardFocusSwitchWin( + display: Meta.Display, + direction: FocusSwitchDirection, + ) { + const focusWindowsContext = this._getWindowsAndFocusAdjacent( + display, + direction, + ); + if (!focusWindowsContext.focus_window) return; + + const nextWin = + focusWindowsContext.windowList[focusWindowsContext.nextIdx]; + const focusRect = focusWindowsContext.focus_window.get_frame_rect(); + const nextRect = nextWin.get_frame_rect(); + + nextWin.move_resize_frame( + false, + focusRect.x, + focusRect.y, + focusRect.width, + focusRect.height, + ); + focusWindowsContext.focus_window.move_resize_frame( + true, // based on user action + nextRect.x, + nextRect.y, + nextRect.width, + nextRect.height, + ); + + focusWindowsContext.focus_window.activate(global.get_current_time()); } private _onKeyboardUntileWindow(kb: KeyBindings, display: Meta.Display) { diff --git a/src/keybindings.ts b/src/keybindings.ts index 42628901..579669c1 100644 --- a/src/keybindings.ts +++ b/src/keybindings.ts @@ -47,6 +47,9 @@ export default class KeyBindings extends GObject.Object { 'focus-window': { param_types: [Meta.Display.$gtype, GObject.TYPE_INT], // Meta.Display, FocusSwitchDirection }, + 'shift-focus-window': { + param_types: [Meta.Display.$gtype, GObject.TYPE_INT], // Meta.Display, FocusSwitchDirection + }, 'highlight-current-window': { param_types: [Meta.Display.$gtype], // Meta.Display }, @@ -232,6 +235,34 @@ export default class KeyBindings extends GObject.Object { }, ); + Main.wm.addKeybinding( + Settings.SETTING_SHIFT_FOCUS_WINDOW_NEXT, + extensionSettings, + Meta.KeyBindingFlags.NONE, + Shell.ActionMode.NORMAL, + (display: Meta.Display) => { + this.emit( + 'shift-focus-window', + display, + FocusSwitchDirection.NEXT, + ); + }, + ); + + Main.wm.addKeybinding( + Settings.SETTING_SHIFT_FOCUS_WINDOW_PREV, + extensionSettings, + Meta.KeyBindingFlags.NONE, + Shell.ActionMode.NORMAL, + (display: Meta.Display) => { + this.emit( + 'shift-focus-window', + display, + FocusSwitchDirection.PREV, + ); + }, + ); + Main.wm.addKeybinding( Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW, extensionSettings, @@ -347,7 +378,11 @@ export default class KeyBindings extends GObject.Object { Main.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_RIGHT); Main.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_NEXT); Main.wm.removeKeybinding(Settings.SETTING_FOCUS_WINDOW_PREV); + Main.wm.removeKeybinding(Settings.SETTING_SHIFT_FOCUS_WINDOW_NEXT); + Main.wm.removeKeybinding(Settings.SETTING_SHIFT_FOCUS_WINDOW_PREV); + Main.wm.removeKeybinding(Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW); + Main.wm.removeKeybinding(Settings.SETTING_CYCLE_LAYOUTS); } diff --git a/src/prefs.ts b/src/prefs.ts index 98f16d54..40e08356 100644 --- a/src/prefs.ts +++ b/src/prefs.ts @@ -683,6 +683,20 @@ export default class TilingShellExtensionPreferences extends ExtensionPreference false, false, ], + [ + Settings.SETTING_SHIFT_FOCUS_WINDOW_NEXT, + _('Switch next window with focused window'), + _('Flip the next window with focused window'), + false, + false, + ], + [ + Settings.SETTING_SHIFT_FOCUS_WINDOW_PREV, + _('Switch previous window with focused window'), + _('Flip the previous window with focused window'), + false, + false, + ], [ Settings.SETTING_HIGHLIGHT_CURRENT_WINDOW, _('Highlight focused window'), diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 3a890449..49948041 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -139,6 +139,8 @@ export default class Settings { static SETTING_FOCUS_WINDOW_DOWN = 'focus-window-down'; static SETTING_FOCUS_WINDOW_NEXT = 'focus-window-next'; static SETTING_FOCUS_WINDOW_PREV = 'focus-window-prev'; + static SETTING_SHIFT_FOCUS_WINDOW_NEXT = 'shift-focus-window-next'; + static SETTING_SHIFT_FOCUS_WINDOW_PREV = 'shift-focus-window-prev'; static SETTING_HIGHLIGHT_CURRENT_WINDOW = 'highlight-current-window'; static SETTING_CYCLE_LAYOUTS = 'cycle-layouts';