Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@
<summary>Per monitor window switch</summary>
<description>Switch between windows on current monitor (monitor with the mouse cursor)</description>
</key>
<key type="b" name="isolate-current-monitor">
<default>false</default>
<summary>Leave other monitors unaffected</summary>
<description>Limit switcher previews, hidden windows, and background effects to the current monitor</description>
</key>
<key type="b" name="skip-minimized-windows">
<default>false</default>
<summary>Skip minimized windows</summary>
Expand Down
8 changes: 8 additions & 0 deletions src/coverflowSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 10 additions & 6 deletions src/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -281,4 +286,3 @@ export const Manager = class Manager {
}



19 changes: 16 additions & 3 deletions src/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand All @@ -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,
});
}
Expand Down
20 changes: 19 additions & 1 deletion src/prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 24 additions & 9 deletions src/switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
Expand Down
7 changes: 7 additions & 0 deletions src/timelineSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down