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;