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';