window-list: Option to show the window list on all monitors

A new setting "show-on-all-monitors" (false by default) is available to
show window lists on all connected monitors.
The Extension object monitors conditions that require the list of
windows to be rebuilt. The WindowList and Button classes have a new
"perMonitor" property that indicates they should handle windows on
their own monitor only.

https://bugzilla.gnome.org/show_bug.cgi?id=737486
This commit is contained in:
Sylvain Pasche
2014-12-19 15:15:44 +01:00
committed by Florian Müllner
parent e80b790b78
commit a3f352d0c3
3 changed files with 157 additions and 47 deletions
+136 -40
View File
@@ -53,7 +53,7 @@ function _onMenuStateChanged(menu, isOpen) {
let [x, y,] = global.get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
if (Me.stateObj.windowListContains(actor))
if (Me.stateObj.someWindowListContains(actor))
actor.sync_hover();
}
@@ -190,7 +190,10 @@ const BaseButton = new Lang.Class({
Name: 'BaseButton',
Abstract: true,
_init: function() {
_init: function(perMonitor, monitorIndex) {
this._perMonitor = perMonitor;
this._monitorIndex = monitorIndex;
this.actor = new St.Button({ style_class: 'window-button',
x_fill: true,
can_focus: true,
@@ -209,6 +212,15 @@ const BaseButton = new Lang.Class({
this._switchWorkspaceId =
global.window_manager.connect('switch-workspace',
Lang.bind(this, this._updateVisibility));
if (this._perMonitor) {
this._windowEnteredMonitorId =
global.screen.connect('window-entered-monitor',
Lang.bind(this, this._windowEnteredOrLeftMonitor));
this._windowLeftMonitorId =
global.screen.connect('window-left-monitor',
Lang.bind(this, this._windowEnteredOrLeftMonitor));
}
},
get active() {
@@ -247,10 +259,16 @@ const BaseButton = new Lang.Class({
this.actor.remove_style_class_name('focused');
},
_windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) {
throw new Error('Not implemented');
},
_isWindowVisible: function(window) {
let workspace = global.screen.get_active_workspace();
return !window.skip_taskbar && window.located_on_workspace(workspace);
return !window.skip_taskbar &&
window.located_on_workspace(workspace) &&
(!this._perMonitor || window.get_monitor() == this._monitorIndex);
},
_updateVisibility: function() {
@@ -272,6 +290,14 @@ const BaseButton = new Lang.Class({
_onDestroy: function() {
global.window_manager.disconnect(this._switchWorkspaceId);
if (this._windowEnteredMonitorId)
global.screen.disconnect(this._windowEnteredMonitorId);
this._windowEnteredMonitorId = 0;
if (this._windowLeftMonitorId)
global.screen.disconnect(this._windowLeftMonitorId);
this._windowLeftMonitorId = 0;
}
});
@@ -280,8 +306,8 @@ const WindowButton = new Lang.Class({
Name: 'WindowButton',
Extends: BaseButton,
_init: function(metaWindow) {
this.parent();
_init: function(metaWindow, perMonitor, monitorIndex) {
this.parent(perMonitor, monitorIndex);
this.metaWindow = metaWindow;
this._updateVisibility();
@@ -330,6 +356,11 @@ const WindowButton = new Lang.Class({
this.actor.remove_style_class_name('minimized');
},
_windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) {
if (monitorIndex == this._monitorIndex && metaWindow == this.metaWindow)
this._updateVisibility();
},
_updateVisibility: function() {
this.actor.visible = this._isWindowVisible(this.metaWindow);
},
@@ -422,8 +453,8 @@ const AppButton = new Lang.Class({
Name: 'AppButton',
Extends: BaseButton,
_init: function(app) {
this.parent();
_init: function(app, perMonitor, monitorIndex) {
this.parent(perMonitor, monitorIndex);
this.app = app;
this._updateVisibility();
@@ -476,9 +507,22 @@ const AppButton = new Lang.Class({
this._updateStyle();
},
_windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) {
if (this._windowTracker.get_window_app(metaWindow) == this.app &&
monitorIndex == this._monitorIndex) {
this._updateVisibility();
this._windowsChanged();
}
},
_updateVisibility: function() {
let workspace = global.screen.get_active_workspace();
this.actor.visible = this.app.is_on_workspace(workspace);
if (!this._perMonitor) {
// fast path: use ShellApp API to avoid iterating over all windows.
let workspace = global.screen.get_active_workspace();
this.actor.visible = this.app.is_on_workspace(workspace);
} else {
this.actor.visible = this.getWindowList().length >= 1;
}
},
_isFocused: function() {
@@ -767,7 +811,10 @@ const WorkspaceIndicator = new Lang.Class({
const WindowList = new Lang.Class({
Name: 'WindowList',
_init: function() {
_init: function(perMonitor, monitor) {
this._perMonitor = perMonitor;
this._monitor = monitor;
this.actor = new St.Widget({ name: 'panel',
style_class: 'bottom-panel',
reactive: true,
@@ -801,26 +848,35 @@ const WindowList = new Lang.Class({
this._workspaceIndicator = new WorkspaceIndicator();
indicatorsBox.add(this._workspaceIndicator.container, { expand: false, y_fill: true });
this._workspaceSettings = new Gio.Settings({ schema_id: 'org.gnome.shell.overrides' });
this._workspacesOnlyOnPrimaryChangedId =
this._workspaceSettings.connect('changed::workspaces-only-on-primary',
Lang.bind(this, this._updateWorkspaceIndicatorVisibility));
this._updateWorkspaceIndicatorVisibility();
this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menuManager.addMenu(this._workspaceIndicator.menu);
this._trayButton = new TrayButton();
indicatorsBox.add(this._trayButton.actor, { expand: false });
this._isOnBottomMonitor = this._monitor == Main.layoutManager.bottomMonitor;
if (this._isOnBottomMonitor) {
this._trayButton = new TrayButton();
indicatorsBox.add(this._trayButton.actor, { expand: false });
}
Main.layoutManager.addChrome(this.actor, { affectsStruts: true,
trackFullscreen: true });
Main.ctrlAltTabManager.addGroup(this.actor, _("Window List"), 'start-here-symbolic');
this.actor.width = this._monitor.width;
this.actor.set_position(this._monitor.x,
this._monitor.y + this._monitor.height - this.actor.height);
this._appSystem = Shell.AppSystem.get_default();
this._appStateChangedId =
this._appSystem.connect('app-state-changed',
Lang.bind(this, this._onAppStateChanged));
this._monitorsChangedId =
Main.layoutManager.connect('monitors-changed',
Lang.bind(this, this._updatePosition));
this._updatePosition();
this._keyboardVisiblechangedId =
Main.layoutManager.connect('keyboard-visible-changed',
Lang.bind(this, function(o, state) {
@@ -854,8 +910,6 @@ const WindowList = new Lang.Class({
this._updateMessageTrayAnchor();
}));
this._isOnBottomMonitor = Main.layoutManager.primaryIndex == Main.layoutManager.bottomIndex;
if (this._isOnBottomMonitor) {
let actor = this.actor;
this._bottomHoverChangedId =
@@ -925,6 +979,12 @@ const WindowList = new Lang.Class({
children[active].activate();
},
_updateWorkspaceIndicatorVisibility: function() {
this._workspaceIndicator.actor.visible =
this._monitor == Main.layoutManager.primaryMonitor ||
!this._workspaceSettings.get_boolean('workspaces-only-on-primary');
},
_getPreferredUngroupedWindowListWidth: function() {
if (this._windowList.get_n_children() == 0)
return this._windowList.get_preferred_width(-1)[1];
@@ -934,13 +994,21 @@ const WindowList = new Lang.Class({
let spacing = this._windowList.layout_manager.spacing;
let workspace = global.screen.get_active_workspace();
let nWindows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace).length;
let windows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace);
if (this._perMonitor) {
windows = windows.filter(Lang.bind(this, function(window) {
return window.get_monitor() == this._monitor.index;
}));
}
let nWindows = windows.length;
if (nWindows == 0)
return this._windowList.get_preferred_width(-1)[1];
return nWindows * childWidth + (nWindows - 1) * spacing;
},
_getMaxWindowListWidth: function() {
let indicatorsBox = this._trayButton.actor.get_parent();
let indicatorsBox = this._workspaceIndicator.actor.get_parent();
return this.actor.width - indicatorsBox.get_preferred_width(-1)[1];
},
@@ -991,12 +1059,6 @@ const WindowList = new Lang.Class({
}
},
_updatePosition: function() {
let monitor = Main.layoutManager.primaryMonitor;
this.actor.width = monitor.width;
this.actor.set_position(monitor.x, monitor.y + monitor.height - this.actor.height);
},
_updateKeyboardAnchor: function() {
if (!Main.keyboard.actor)
return;
@@ -1026,7 +1088,7 @@ const WindowList = new Lang.Class({
},
_addApp: function(app) {
let button = new AppButton(app);
let button = new AppButton(app, this._perMonitor, this._monitor.index);
this._windowList.layout_manager.pack(button.actor,
true, true, true,
Clutter.BoxAlignment.START,
@@ -1059,7 +1121,7 @@ const WindowList = new Lang.Class({
return;
}
let button = new WindowButton(win);
let button = new WindowButton(win, this._perMonitor, this._monitor.index);
this._windowList.layout_manager.pack(button.actor,
true, true, true,
Clutter.BoxAlignment.START,
@@ -1168,6 +1230,8 @@ const WindowList = new Lang.Class({
},
_onDestroy: function() {
this._workspaceSettings.disconnect(this._workspacesOnlyOnPrimaryChangedId);
this._workspaceIndicator.destroy();
Main.ctrlAltTabManager.removeGroup(this.actor);
@@ -1175,9 +1239,6 @@ const WindowList = new Lang.Class({
this._appSystem.disconnect(this._appStateChangedId);
this._appStateChangedId = 0;
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
Main.layoutManager.disconnect(this._keyboardVisiblechangedId);
this._keyboardVisiblechangedId = 0;
@@ -1224,34 +1285,69 @@ const Extension = new Lang.Class({
Name: 'Extension',
_init: function() {
this._windowList = null;
this._windowLists = null;
this._injections = {};
},
enable: function() {
this._windowList = new WindowList();
this._windowLists = [];
this._injections['_trayDwellTimeout'] =
MessageTray.MessageTray.prototype._trayDwellTimeout;
MessageTray.MessageTray.prototype._trayDwellTimeout = function() {
return false;
};
this._settings = Convenience.getSettings();
this._showOnAllMonitorsChangedId =
this._settings.connect('changed::show-on-all-monitors',
Lang.bind(this, this._buildWindowLists));
this._monitorsChangedId =
Main.layoutManager.connect('monitors-changed',
Lang.bind(this, this._buildWindowLists));
this._buildWindowLists();
},
_buildWindowLists: function() {
this._windowLists.forEach(function(windowList) {
windowList.actor.destroy();
});
this._windowLists = [];
let showOnAllMonitors = this._settings.get_boolean('show-on-all-monitors');
Main.layoutManager.monitors.forEach(Lang.bind(this, function(monitor) {
if (showOnAllMonitors || monitor == Main.layoutManager.primaryMonitor)
this._windowLists.push(new WindowList(showOnAllMonitors, monitor));
}));
},
disable: function() {
if (!this._windowList)
if (!this._windowLists)
return;
this._windowList.actor.hide();
this._windowList.actor.destroy();
this._windowList = null;
this._settings.disconnect(this._showOnAllMonitorsChangedId);
this._showOnAllMonitorsChangedId = 0;
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
this._windowLists.forEach(function(windowList) {
windowList.actor.hide();
windowList.actor.destroy();
});
this._windowLists = null;
for (let prop in this._injections)
MessageTray.MessageTray.prototype[prop] = this._injections[prop];
},
windowListContains: function(actor) {
return this._windowList.actor.contains(actor);
someWindowListContains: function(actor) {
return this._windowLists.some(function(windowList) {
return windowList.actor.contains(actor);
});
}
});
@@ -15,5 +15,13 @@
window list. Possible values are "never", "auto" and "always".
</_description>
</key>
<key name="show-on-all-monitors" type="b">
<default>false</default>
<_summary>Show the window list on all monitors</_summary>
<_description>
Whether to show the window list on all connected monitors or
only on the primary one.
</_description>
</key>
</schema>
</schemalist>
+13 -7
View File
@@ -1,5 +1,6 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
@@ -19,25 +20,25 @@ function init() {
const WindowListPrefsWidget = new GObject.Class({
Name: 'WindowList.Prefs.Widget',
GTypeName: 'WindowListPrefsWidget',
Extends: Gtk.Frame,
Extends: Gtk.Grid,
_init: function(params) {
this.parent(params);
this.shadow_type = Gtk.ShadowType.NONE;
this.margin = 24;
this.row_spacing = 6;
this.orientation = Gtk.Orientation.VERTICAL;
let title = '<b>' + _("Window Grouping") + '</b>';
let titleLabel = new Gtk.Label({ use_markup: true, label: title });
this.set_label_widget(titleLabel);
let groupingLabel = '<b>' + _("Window Grouping") + '</b>';
this.add(new Gtk.Label({ label: groupingLabel, use_markup: true,
halign: Gtk.Align.START }));
let align = new Gtk.Alignment({ left_padding: 12 });
this.add(align);
let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
row_spacing: 6,
column_spacing: 6,
margin_top: 6 });
column_spacing: 6 });
align.add(grid);
this._settings = Convenience.getSettings();
@@ -70,6 +71,11 @@ const WindowListPrefsWidget = new GObject.Class({
this._settings.set_string('grouping-mode', mode);
}));
}
let check = new Gtk.CheckButton({ label: _("Show on all monitors"),
margin_top: 6 });
this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT);
this.add(check);
}
});