diff --git a/schemas/org.gnome.shell.extensions.coverflowalttab.gschema.xml b/schemas/org.gnome.shell.extensions.coverflowalttab.gschema.xml
index 236e767..9ed3740 100644
--- a/schemas/org.gnome.shell.extensions.coverflowalttab.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.coverflowalttab.gschema.xml
@@ -123,6 +123,11 @@
Per monitor window switch
Switch between windows on current monitor (monitor with the mouse cursor)
+
+ false
+ Leave other monitors unaffected
+ Limit switcher previews, hidden windows, and background effects to the current monitor
+
false
Skip minimized windows
diff --git a/src/coverflowSwitcher.js b/src/coverflowSwitcher.js
index e62fd37..ab8c495 100644
--- a/src/coverflowSwitcher.js
+++ b/src/coverflowSwitcher.js
@@ -55,6 +55,14 @@ export class CoverflowSwitcher extends BaseSwitcher {
for (let windowActor of global.get_window_actors()) {
let metaWin = windowActor.get_meta_window();
+ // Off-monitor clones can flash during the opening and closing
+ // animations even when their original window actors stay visible.
+ if (this._settings.switch_per_monitor
+ && this._settings.isolate_current_monitor
+ && metaWin.get_monitor() !== monitor.index) {
+ continue;
+ }
+
let texture = windowActor.get_texture();
let width, height, _;
if (texture.get_size) {
diff --git a/src/manager.js b/src/manager.js
index 4dc167a..621dc3d 100644
--- a/src/manager.js
+++ b/src/manager.js
@@ -216,11 +216,16 @@ export const Manager = class Manager {
break;
}
+ // Capture the active monitor once so filtering and the switcher UI
+ // cannot disagree if the pointer moves while the switcher starts.
+ let activeMonitor = this.platform.getSettings().enforce_primary_monitor
+ ? Main.layoutManager.primaryMonitor
+ : Main.layoutManager.currentMonitor;
+
// filter by windows existing on the active monitor
- if (this.platform.getSettings().switch_per_monitor)
- {
- windows = windows.filter ( (win) =>
- win.get_monitor() === Main.layoutManager.currentMonitor.index );
+ if (this.platform.getSettings().switch_per_monitor) {
+ windows = windows.filter(win =>
+ win.get_monitor() === activeMonitor.index);
}
if (this.platform.getSettings().skip_minimized_windows) {
@@ -230,7 +235,7 @@ export const Manager = class Manager {
if (windows.length) {
const currentIndex = 0;
let switcher_class = this.platform.getSettings().switcher_class;
- this.switcher = new switcher_class(windows, mask, currentIndex, this, null, isApplicationSwitcher, null, dBus);
+ this.switcher = new switcher_class(windows, mask, currentIndex, this, activeMonitor, isApplicationSwitcher, null, dBus);
}
}
@@ -281,4 +286,3 @@ export const Manager = class Manager {
}
-
diff --git a/src/platform.js b/src/platform.js
index cb1f304..f02d0b7 100644
--- a/src/platform.js
+++ b/src/platform.js
@@ -139,6 +139,7 @@ class AbstractPlatform {
easing_function: 'ease-out-cubic',
current_workspace_only: '1',
switch_per_monitor: false,
+ isolate_current_monitor: false,
skip_minimized_windows: false,
preview_to_monitor_ratio: 0.5,
coverflow_preview_scaling_factor: 0.75,
@@ -362,6 +363,7 @@ export class PlatformGnomeShell extends AbstractPlatform {
? TimelineSwitcher : CoverflowSwitcher,
current_workspace_only: settings.get_string("current-workspace-only"),
switch_per_monitor: settings.get_boolean("switch-per-monitor"),
+ isolate_current_monitor: settings.get_boolean("isolate-current-monitor"),
skip_minimized_windows: settings.get_boolean("skip-minimized-windows"),
preview_to_monitor_ratio: clamp(settings.get_double("preview-to-monitor-ratio"), 0, 1),
coverflow_preview_scaling_factor: clamp(settings.get_double("coverflow-preview-scaling-factor"), 0, 1),
@@ -527,12 +529,19 @@ export class PlatformGnomeShell extends AbstractPlatform {
actor.remove_all_transitions();
}
- initBackground() {
+ initBackground(monitor=null) {
this._backgroundGroup = new Meta.BackgroundGroup();
this._backgroundGroup.set_name("coverflow-alt-tab-background-group");
Main.uiGroup.add_child(this._backgroundGroup);
Main.uiGroup.set_child_above_sibling(this._backgroundGroup, global.window_group);
+ if (monitor !== null) {
+ // BackgroundManager positions its actor in stage coordinates, so
+ // clip the full-stage group instead of moving the background.
+ this._backgroundGroup.set_clip(
+ monitor.x, monitor.y, monitor.width, monitor.height);
+ }
+
this._backgroundShade = new Clutter.Actor({
opacity: 0,
reactive: false
@@ -552,10 +561,14 @@ export class PlatformGnomeShell extends AbstractPlatform {
this._backgroundGroup.set_child_above_sibling(this._backgroundShade, null);
this._backgroundGroup.opacity = 0;
this._backgroundGroup.hide();
- for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+ // A null monitor preserves the original all-monitor background.
+ let monitors = monitor !== null
+ ? [monitor]
+ : Main.layoutManager.monitors;
+ for (let currentMonitor of monitors) {
new Background.BackgroundManager({
container: this._backgroundGroup,
- monitorIndex: i,
+ monitorIndex: currentMonitor.index,
vignette: false,
});
}
diff --git a/src/prefs.js b/src/prefs.js
index 62328ff..af07aa7 100644
--- a/src/prefs.js
+++ b/src/prefs.js
@@ -396,7 +396,25 @@ export default class CoverflowAltTabPreferences extends ExtensionPreferences {
id: 'all-currentfirst', name: _("All workspaces, current first")
}];
window_size_pref_group.add(this.buildDropDownAdw("current-workspace-only", workspace_inclusion_options, _("Workspaces"), _("Switch between windows on current or on all workspaces.")));
- window_size_pref_group.add(this.buildSwitcherAdw("switch-per-monitor", [], [], _("Current Monitor"), _("Switch between windows on current monitor.")));
+ // Keep visual isolation subordinate to per-monitor window filtering;
+ // without Current Monitor enabled, there is no single monitor to isolate.
+ let current_monitor_row = new Adw.ExpanderRow({
+ title: _("Current Monitor"),
+ subtitle: _("Switch between windows on current monitor."),
+ show_enable_switch: true,
+ expanded: this.settings.get_boolean("switch-per-monitor"),
+ });
+ this.settings.bind("switch-per-monitor", current_monitor_row,
+ "enable-expansion", Gio.SettingsBindFlags.DEFAULT);
+ current_monitor_row.connect('notify::enable-expansion', row => {
+ if (row.enable_expansion)
+ row.expanded = true;
+ });
+ current_monitor_row.add_row(this.buildSwitcherAdw(
+ "isolate-current-monitor", [], [],
+ _("Leave Other Monitors Unaffected"),
+ _("Limit previews, hidden windows, and background effects to the current monitor.")));
+ window_size_pref_group.add(current_monitor_row);
window_size_pref_group.add(this.buildSwitcherAdw("skip-minimized-windows", [], [], _("Skip Minimized Windows"), _("Exclude minimized windows from the switcher.")));
coverflow_window_pref_group.add(switcher_looping_method_row);
timeline_window_pref_group.add(this.buildRangeAdw("timeline-preview-distance", [0, 1024, 1, [64, 128, 256, 512, 768, 1024]], _("Window Layout Distance"), _("Distance in pixels between timeline window's upper left corners."), true));
diff --git a/src/settings.js b/src/settings.js
index a540fd4..eed052e 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -14,6 +14,7 @@ export const ExtensionSettingKeys = [
"easing-function",
"current-workspace-only",
"switch-per-monitor",
+ "isolate-current-monitor",
"skip-minimized-windows",
"switcher-style",
"preview-to-monitor-ratio",
diff --git a/src/switcher.js b/src/switcher.js
index 40b85b3..2e530ce 100644
--- a/src/switcher.js
+++ b/src/switcher.js
@@ -63,6 +63,9 @@ export class Switcher {
this._windowManager = global.window_manager;
this._previews = [];
this._allPreviews = [];
+ // Track only actors hidden by this switcher so teardown does not show
+ // actors hidden by another Shell component or extension.
+ this._hiddenWindowActors = [];
this._numPreviewsComplete = 0;
this._isAppSwitcher = isAppSwitcher;
this._appWindowsMap = new Map();
@@ -101,7 +104,13 @@ export class Switcher {
this._dcid = this._windowManager.connect('destroy', this._windowDestroyed.bind(this));
this._mcid = this._windowManager.connect('map', this._activateSelected.bind(this));
manager.platform.switcher = this;
- if (this._parent === null) manager.platform.initBackground();
+ if (this._parent === null) {
+ // Passing null retains the legacy full-stage background. Isolation
+ // instead clips it to the monitor captured when switching began.
+ let backgroundMonitor = this._settings.switch_per_monitor
+ && this._settings.isolate_current_monitor ? monitor : null;
+ manager.platform.initBackground(backgroundMonitor);
+ }
// create a container for all our widgets
let widgetClass = manager.platform.getWidgetClass();
@@ -213,16 +222,21 @@ export class Switcher {
preview.connect('button-release-event', this._previewButtonReleaseEvent.bind(this));
}
- // hide windows and showcd Coverflow actors
+ // Hide real windows while their Coverflow clone actors are displayed.
// Only hide windows on the current workspace. Hiding (and later
// re-showing) windows from other workspaces makes them briefly render
// on the current workspace as non-interactive ghosts on GNOME 48+.
+ // In isolation mode, off-monitor actors remain visible and untouched.
if (this._parent === null) {
let currentWorkspace = this._manager.workspace_manager.get_active_workspace();
for (let child of global.window_group.get_children()) {
if (child !== global.window_group.get_first_child()
&& typeof child.get_meta_window === "function"
- && child.get_meta_window().get_workspace() === currentWorkspace) {
+ && child.get_meta_window().get_workspace() === currentWorkspace
+ && (!(this._settings.switch_per_monitor
+ && this._settings.isolate_current_monitor)
+ || child.get_meta_window().get_monitor() === this._activeMonitor.index)) {
+ this._hiddenWindowActors.push(child);
child.hide();
}
}
@@ -996,6 +1010,9 @@ export class Switcher {
_windowDestroyed(wm, actor) {
this._logger.debug('_windowDestroyed')
+ // A destroyed actor must not be revisited by the teardown restore loop.
+ this._hiddenWindowActors = this._hiddenWindowActors.filter(
+ windowActor => windowActor !== actor);
this._removeDestroyedWindow(actor.meta_window);
}
@@ -1173,18 +1190,16 @@ export class Switcher {
if (this._parent === null) this._manager.platform.removeBackground();
if (this._parent === null) {
- let currentWorkspace = this._manager.workspace_manager.get_active_workspace();
- for (let child of global.window_group.get_children()) {
+ // Restore exactly the live actors hidden when this switcher opened.
+ for (let child of this._hiddenWindowActors) {
if (typeof child.get_meta_window === "function") {
let metaWin = child.get_meta_window();
- // Only re-show windows that belong to the current workspace.
- // Re-showing windows from other workspaces is what left them
- // ghosting on the current workspace after a switch.
- if (!metaWin.minimized && metaWin.get_workspace() === currentWorkspace) {
+ if (metaWin !== null && !metaWin.minimized) {
child.show();
}
}
}
+ this._hiddenWindowActors = [];
}
this._disablePerspectiveCorrection();
diff --git a/src/timelineSwitcher.js b/src/timelineSwitcher.js
index ecacada..ed667c1 100644
--- a/src/timelineSwitcher.js
+++ b/src/timelineSwitcher.js
@@ -51,6 +51,13 @@ export class TimelineSwitcher extends Switcher {
for (let windowActor of global.get_window_actors()) {
let metaWin = windowActor.get_meta_window();
+ // Off-monitor clones can flash during the opening and closing
+ // animations even when their original window actors stay visible.
+ if (this._settings.switch_per_monitor
+ && this._settings.isolate_current_monitor
+ && metaWin.get_monitor() !== monitor.index) {
+ continue;
+ }
let texture = windowActor.get_texture();
let width, height;