From 05833ca853e5e661cf43f59734ca0a29219159fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 18 Jul 2019 00:39:49 +0200 Subject: [PATCH 01/32] apps-menu: Add drop-shadow to application icons ... to make sure they are readable on light backgrounds. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/168 --- extensions/apps-menu/extension.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js index 49a05c7..860cb77 100644 --- a/extensions/apps-menu/extension.js +++ b/extensions/apps-menu/extension.js @@ -112,7 +112,9 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { } _updateIcon() { - this._iconBin.set_child(this.getDragActor()); + let icon = this.getDragActor(); + icon.style_class = 'icon-dropshadow'; + this._iconBin.set_child(icon); } }; -- 2.26.2 From 4ace6d5da8e0edc590706af8afb8cfc843a06af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 29 May 2019 10:17:20 +0000 Subject: [PATCH 02/32] places-menu: Don't hardcode position The extension currently assumes that we have the "Activities" button at the left of the top bar. This is currently true, not only in the regular session, but also in GNOME classic where the button is hidden (but still present). However this is about to change: We will stop taking over the button from the apps-menu extension, and instead disable "Activities" from the session mode definition. Prepare for this by adding the places menu before the application menu instead of assuming a hardcoded position. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 --- extensions/places-menu/extension.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/places-menu/extension.js b/extensions/places-menu/extension.js index 429e81d..cdecb7b 100644 --- a/extensions/places-menu/extension.js +++ b/extensions/places-menu/extension.js @@ -133,9 +133,9 @@ let _indicator; function enable() { _indicator = new PlacesMenu; - let pos = 1; + let pos = Main.sessionMode.panel.left.indexOf('appMenu'); if ('apps-menu' in Main.panel.statusArea) - pos = 2; + pos++; Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left'); } -- 2.26.2 From 5985dd5d6867a305d80f1e3d8f7f4b22c30702f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 29 May 2019 08:32:03 +0000 Subject: [PATCH 03/32] apps-menu: Stop taking over Activities button We don't want the "Activities" button in GNOME Classic, but the current way of handling it is confusing: - the button is hidden, but the corresponding hot corner sometimes works (when the application menu isn't open) - the button is effectively moved inside the menu, although it's clearly not an app or category - the apps-menu can be used independent from classic mode, in which case removing the "Activities" button may not be wanted Address those points by removing any handling of the activities button from the apps-menu extension. We will remove it again from the classic session via a session mode tweak. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 --- extensions/apps-menu/extension.js | 63 +++---------------------------- 1 file changed, 6 insertions(+), 57 deletions(-) diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js index 860cb77..4bde0d5 100644 --- a/extensions/apps-menu/extension.js +++ b/extensions/apps-menu/extension.js @@ -30,22 +30,6 @@ const HORIZ_FACTOR = 5; const MENU_HEIGHT_OFFSET = 132; const NAVIGATION_REGION_OVERSHOOT = 50; -class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { - constructor(button) { - super(); - this._button = button; - let label = new St.Label({ text: _("Activities Overview") }); - this.actor.add_child(label); - this.actor.label_actor = label; - } - - activate(event) { - this._button.menu.toggle(); - Main.overview.toggle(); - super.activate(event); - } -}; - class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { constructor(button, app) { super(); @@ -244,21 +228,6 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { return false; } - open(animate) { - this._button.hotCorner.setBarrierSize(0); - if (this._button.hotCorner.actor) // fallback corner - this._button.hotCorner.actor.hide(); - super.open(animate); - } - - close(animate) { - let size = Main.layoutManager.panelBox.height; - this._button.hotCorner.setBarrierSize(size); - if (this._button.hotCorner.actor) // fallback corner - this._button.hotCorner.actor.show(); - super.close(animate); - } - toggle() { if (this.isOpen) { this._button.selectCategory(null, null); @@ -407,7 +376,7 @@ class DesktopTarget { Signals.addSignalMethods(DesktopTarget.prototype); class ApplicationsButton extends PanelMenu.Button { - constructor() { + constructor(includeIcon) { super(1.0, null, false); this.setMenu(new ApplicationsMenu(this.actor, 1.0, St.Side.TOP, this)); @@ -422,7 +391,8 @@ class ApplicationsButton extends PanelMenu.Button { let iconFile = Gio.File.new_for_path('/usr/share/icons/hicolor/scalable/apps/start-here.svg'); this._icon = new St.Icon({ gicon: new Gio.FileIcon({ file: iconFile }), - style_class: 'panel-logo-icon' }); + style_class: 'panel-logo-icon', + visible: includeIcon }); hbox.add_actor(this._icon); this._label = new St.Label({ text: _("Applications"), @@ -435,7 +405,6 @@ class ApplicationsButton extends PanelMenu.Button { this.actor.name = 'panelApplications'; this.actor.label_actor = this._label; - this.actor.connect('captured-event', this._onCapturedEvent.bind(this)); this.actor.connect('destroy', this._onDestroy.bind(this)); this._showingId = Main.overview.connect('showing', () => { @@ -479,10 +448,6 @@ class ApplicationsButton extends PanelMenu.Button { } } - get hotCorner() { - return Main.layoutManager.hotCorners[Main.layoutManager.primaryIndex]; - } - _createVertSeparator() { let separator = new St.DrawingArea({ style_class: 'calendar-vertical-separator', pseudo_class: 'highlighted' }); @@ -507,14 +472,6 @@ class ApplicationsButton extends PanelMenu.Button { this._desktopTarget.destroy(); } - _onCapturedEvent(actor, event) { - if (event.type() == Clutter.EventType.BUTTON_PRESS) { - if (!Main.overview.shouldToggleByCornerOrButton()) - return true; - } - return false; - } - _onMenuKeyPress(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { @@ -649,11 +606,6 @@ class ApplicationsButton extends PanelMenu.Button { x_fill: true, y_fill: true, y_align: St.Align.START }); - let activities = new ActivitiesMenuItem(this); - this.leftBox.add(activities.actor, { expand: false, - x_fill: true, y_fill: false, - y_align: St.Align.START }); - this.applicationsBox = new St.BoxLayout({ vertical: true }); this.applicationsScrollBox.add_actor(this.applicationsBox); this.categoriesBox = new St.BoxLayout({ vertical: true }); @@ -758,19 +710,16 @@ class ApplicationsButton extends PanelMenu.Button { }; let appsMenuButton; -let activitiesButton; function enable() { - activitiesButton = Main.panel.statusArea['activities']; - activitiesButton.container.hide(); - appsMenuButton = new ApplicationsButton(); - Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left'); + let index = Main.sessionMode.panel.left.indexOf('activities') + 1; + appsMenuButton = new ApplicationsButton(index == 0); + Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left'); } function disable() { Main.panel.menuManager.removeMenu(appsMenuButton.menu); appsMenuButton.destroy(); - activitiesButton.container.show(); } function init(metadata) { -- 2.26.2 From 25d6a4dbcd0ecbe5778ee097c681ef871bcd4064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 7 Jun 2019 14:30:16 +0000 Subject: [PATCH 04/32] apps-menu: Stop hiding the overview when toggled Now that the extension no longer doubles as the "Activities" button, that behavior is confusing. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 --- extensions/apps-menu/extension.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js index 4bde0d5..c7253fb 100644 --- a/extensions/apps-menu/extension.js +++ b/extensions/apps-menu/extension.js @@ -229,12 +229,8 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { } toggle() { - if (this.isOpen) { + if (this.isOpen) this._button.selectCategory(null, null); - } else { - if (Main.overview.visible) - Main.overview.hide(); - } super.toggle(); } }; -- 2.26.2 From b4f735a3b8c1d17a7248f57be3f8fd1837346f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 7 Jun 2019 20:07:19 +0000 Subject: [PATCH 05/32] apps-menu: Hide overview when launching app Now that we no longer hide the overview when the menu is opened, it is possible to activate menu entries from the overview. Start hiding the overview in that case, which is consistent with app launching elsewhere. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 --- extensions/apps-menu/extension.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js index c7253fb..4bf4fc2 100644 --- a/extensions/apps-menu/extension.js +++ b/extensions/apps-menu/extension.js @@ -75,6 +75,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { this._button.selectCategory(null, null); this._button.menu.toggle(); super.activate(event); + + Main.overview.hide(); } setActive(active, params) { -- 2.26.2 From a890a0b2aad2f4f1ce1638988c6bc34f7751f52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 29 May 2019 09:44:30 +0000 Subject: [PATCH 06/32] classic: Disable overview The overview is one of the defining features of GNOME 3, and thus almost by definition at odds with the classic session, which emulates a traditional GNOME 2 desktop. Even with the less prominent placement inside the application menu it never quite fit in - it doesn't help that besides the different UI paradigma, the overview keeps its "normal" styling which differs greatly with classic's normal mode. So besides removing the "Activities" button via the session mode definition, now that the apps-menu extension doesn't replace it anymore, disable the overview completely in the classic session. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 --- data/classic.json.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/classic.json.in b/data/classic.json.in index fdb3762..c1c0544 100644 --- a/data/classic.json.in +++ b/data/classic.json.in @@ -1,8 +1,9 @@ { "parentMode": "user", "stylesheetName": "gnome-classic.css", + "hasOverview": false, "enabledExtensions": [@CLASSIC_EXTENSIONS@], - "panel": { "left": ["activities", "appMenu"], + "panel": { "left": ["appMenu"], "center": [], "right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"] } -- 2.26.2 From 53f2eb6a89ae4b75aa604474cf9425d179a57d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 14 May 2019 19:51:22 +0200 Subject: [PATCH 07/32] window-list: Add window picker button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the latest changes, GNOME Classic has become so classic that it is bordering dull. Salvage at least a tiny piece of GNOME 3 in form of a window-pick button which toggles an exposé-like reduced overview. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/73 --- extensions/window-list/classic.css | 20 +- extensions/window-list/extension.js | 36 +++- extensions/window-list/meson.build | 2 +- extensions/window-list/stylesheet.css | 27 ++- extensions/window-list/windowPicker.js | 260 +++++++++++++++++++++++++ 5 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 extensions/window-list/windowPicker.js diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index f3c44a3..c506bea 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -6,14 +6,13 @@ height: 2.25em ; } - .bottom-panel .window-button > StWidget { + .bottom-panel .window-button > StWidget, + .bottom-panel .window-picker-toggle > StWidget { background-gradient-drection: vertical; background-color: #fff; background-gradient-start: #fff; background-gradient-end: #eee; color: #000; - -st-natural-width: 18.7em; - max-width: 18.75em; color: #2e3436; background-color: #eee; border-radius: 2px; @@ -22,7 +21,17 @@ text-shadow: 0 0 transparent; } - .bottom-panel .window-button:hover > StWidget { + .bottom-panel .window-button > StWidget { + -st-natural-width: 18.7em; + max-width: 18.75em; + } + + .bottom-panel .window-picker-toggle > StWidet { + border: 1px solid rgba(0,0,0,0.3); + } + + .bottom-panel .window-button:hover > StWidget, + .bottom-panel .window-picker-toggle:hover > StWidget { background-color: #f9f9f9; } @@ -31,7 +40,8 @@ box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); } - .bottom-panel .window-button.focused > StWidget { + .bottom-panel .window-button.focused > StWidget, + .bottom-panel .window-picker-toggle:checked > StWidget { background-color: #ddd; box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); } diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 716a324..f4610f9 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -8,12 +8,15 @@ const St = imports.gi.St; const DND = imports.ui.dnd; const Main = imports.ui.main; +const Overview = imports.ui.overview; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; +const Tweener = imports.ui.tweener; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; +const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; @@ -769,6 +772,12 @@ class WindowList { let box = new St.BoxLayout({ x_expand: true, y_expand: true }); this.actor.add_actor(box); + let toggle = new WindowPickerToggle(); + box.add_actor(toggle); + + toggle.connect('notify::checked', + this._updateWindowListVisibility.bind(this)); + let layout = new Clutter.BoxLayout({ homogeneous: true }); this._windowList = new St.Widget({ style_class: 'window-list', reactive: true, @@ -931,6 +940,19 @@ class WindowList { this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor; } + _updateWindowListVisibility() { + let visible = !Main.windowPicker.visible; + + Tweener.addTween(this._windowList, { + opacity: visible ? 255 : 0, + transition: 'ease-out-quad', + time: Overview.ANIMATION_TIME + }); + + this._windowList.reactive = visible; + this._windowList.get_children().forEach(c => c.reactive = visible); + } + _getPreferredUngroupedWindowListWidth() { if (this._windowList.get_n_children() == 0) return this._windowList.get_preferred_width(-1)[1]; @@ -1205,7 +1227,7 @@ class WindowList { class Extension { constructor() { this._windowLists = null; - this._injections = {}; + this._hideOverviewOrig = Main.overview.hide; } enable() { @@ -1220,6 +1242,13 @@ class Extension { Main.layoutManager.connect('monitors-changed', this._buildWindowLists.bind(this)); + Main.windowPicker = new WindowPicker(); + + Main.overview.hide = () => { + Main.windowPicker.close(); + this._hideOverviewOrig.call(Main.overview); + }; + this._buildWindowLists(); } @@ -1250,6 +1279,11 @@ class Extension { windowList.actor.destroy(); }); this._windowLists = null; + + Main.windowPicker.actor.destroy(); + delete Main.windowPicker; + + Main.overview.hide = this._hideOverviewOrig; } someWindowListContains(actor) { diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build index b4aa4db..5b1f5f5 100644 --- a/extensions/window-list/meson.build +++ b/extensions/window-list/meson.build @@ -4,7 +4,7 @@ extension_data += configure_file( configuration: metadata_conf ) -extension_sources += files('prefs.js') +extension_sources += files('prefs.js', 'windowPicker.js') extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') if classic_mode_enabled diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index f5285cb..91383ab 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -26,9 +26,8 @@ spacing: 4px; } -.window-button > StWidget { - -st-natural-width: 18.75em; - max-width: 18.75em; +.window-button > StWidget, +.window-picker-toggle > StWidget { color: #bbb; background-color: black; border-radius: 4px; @@ -37,7 +36,21 @@ text-shadow: 1px 1px 4px rgba(0,0,0,0.8); } -.window-button:hover > StWidget { +.window-picker-toggle { + padding: 3px; +} + +.window-picker-toggle > StWidet { + border: 1px solid rgba(255,255,255,0.3); +} + +.window-button > StWidget { + -st-natural-width: 18.75em; + max-width: 18.75em; +} + +.window-button:hover > StWidget, +.window-picker-toggle:hover > StWidget { color: white; background-color: #1f1f1f; } @@ -47,12 +60,14 @@ box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); } -.window-button.focused > StWidget { +.window-button.focused > StWidget, +.window-picker-toggle:checked > StWidget { color: white; box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7); } -.window-button.focused:active > StWidget { +.window-button.focused:active > StWidget, +.window-picker-toggle:checked:active > StWidget { box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); } diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js new file mode 100644 index 0000000..c134b98 --- /dev/null +++ b/extensions/window-list/windowPicker.js @@ -0,0 +1,260 @@ +/* exported WindowPicker, WindowPickerToggle */ +const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; +const Signals = imports.signals; + +const Layout = imports.ui.layout; +const Main = imports.ui.main; +const Overview = imports.ui.overview; +const { WorkspacesDisplay } = imports.ui.workspacesView; + +let MyWorkspacesDisplay = class extends WorkspacesDisplay { + constructor() { + super(); + + this.actor.add_constraint( + new Layout.MonitorConstraint({ + primary: true, + work_area: true + })); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + + this._workareasChangedId = global.screen.connect('workareas-changed', + this._onWorkAreasChanged.bind(this)); + this._onWorkAreasChanged(); + } + + show(...args) { + if (this._scrollEventId == 0) + this._scrollEventId = Main.windowPicker.connect('scroll-event', + this._onScrollEvent.bind(this)); + + super.show(...args); + } + + hide(...args) { + if (this._scrollEventId > 0) + Main.windowPicker.disconnect(this._scrollEventId); + this._scrollEventId = 0; + + super.hide(...args); + } + + _onWorkAreasChanged() { + let { primaryIndex } = Main.layoutManager; + let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex); + this.setWorkspacesFullGeometry(workarea); + } + + _updateWorkspacesViews() { + super._updateWorkspacesViews(); + + this._workspacesViews.forEach(v => { + Main.layoutManager.overviewGroup.remove_actor(v.actor); + Main.windowPicker.actor.add_actor(v.actor); + }); + } + + _onDestroy() { + if (this._workareasChangedId) + global.screen.disconnect(this._workareasChangedId); + this._workareasChangedId = 0; + } +}; + +var WindowPicker = class { + constructor() { + this._visible = false; + this._modal = false; + + this.actor = new Clutter.Actor(); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + + global.bind_property('screen-width', + this.actor, 'width', + GObject.BindingFlags.SYNC_CREATE); + global.bind_property('screen-height', + this.actor, 'height', + GObject.BindingFlags.SYNC_CREATE); + + this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true }); + this.actor.add_child(this._backgroundGroup); + + this._backgroundGroup.connect('scroll-event', (a, ev) => { + this.emit('scroll-event', ev); + }); + + // Trick WorkspacesDisplay constructor into adding actions here + let addActionOrig = Main.overview.addAction; + Main.overview.addAction = a => this._backgroundGroup.add_action(a); + + this._workspacesDisplay = new MyWorkspacesDisplay(); + this.actor.add_child(this._workspacesDisplay.actor); + + Main.overview.addAction = addActionOrig; + + this._bgManagers = []; + + this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', + this._updateBackgrounds.bind(this)); + this._updateBackgrounds(); + + Main.uiGroup.insert_child_below(this.actor, global.window_group); + } + + get visible() { + return this._visible; + } + + open() { + if (this._visible) + return; + + this._visible = true; + + if (!this._syncGrab()) + return; + + this._fakeOverviewVisible(true); + this._shadeBackgrounds(); + this._fakeOverviewAnimation(); + this._workspacesDisplay.show(false); + + this.emit('open-state-changed', this._visible); + } + + close() { + if (!this._visible) + return; + + this._visible = false; + + if (!this._syncGrab()) + return; + + this._workspacesDisplay.animateFromOverview(false); + this._unshadeBackgrounds(); + this._fakeOverviewAnimation(() => { + this._workspacesDisplay.hide(); + this._fakeOverviewVisible(false); + }); + + this.emit('open-state-changed', this._visible); + } + + _fakeOverviewAnimation(onComplete) { + Main.overview.animationInProgress = true; + GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + Overview.ANIMATION_TIME * 1000, + () => { + Main.overview.animationInProgress = false; + if (onComplete) + onComplete(); + }); + } + + _fakeOverviewVisible(visible) { + // Fake overview state for WorkspacesDisplay + Main.overview.visible = visible; + + // Hide real windows + Main.layoutManager._inOverview = visible; + Main.layoutManager._updateVisibility(); + } + + _syncGrab() { + if (this._visible) { + if (this._modal) + return true; + + this._modal = Main.pushModal(this.actor, { + actionMode: Shell.ActionMode.OVERVIEW + }); + + if (!this._modal) { + this.actor.hide(); + return false; + } + } else if (this._modal) { + Main.popModal(this.actor); + this._modal = false; + } + return true; + } + + _onDestroy() { + if (this._monitorsChangedId) + Main.layoutManager.disconnect(this._monitorsChangedId); + this._monitorsChangedId = 0; + } + + _updateBackgrounds() { + Main.overview._updateBackgrounds.call(this); + } + + _shadeBackgrounds() { + Main.overview._shadeBackgrounds.call(this); + } + + _unshadeBackgrounds() { + Main.overview._unshadeBackgrounds.call(this); + } +}; +Signals.addSignalMethods(WindowPicker.prototype); + +var WindowPickerToggle = GObject.registerClass( +class WindowPickerToggle extends St.Button { + _init() { + let iconBin = new St.Widget({ + layout_manager: new Clutter.BinLayout() + }); + iconBin.add_child(new St.Icon({ + icon_name: 'focus-windows-symbolic', + icon_size: 16, + x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER + })); + super._init({ + style_class: 'window-picker-toggle', + child: iconBin, + visible: !Main.sessionMode.hasOverview, + x_fill: true, + y_fill: true, + toggle_mode: true + }); + + this._overlayKeyId = 0; + + this.connect('destroy', this._onDestroy.bind(this)); + + this.connect('notify::checked', () => { + if (this.checked) + Main.windowPicker.open(); + else + Main.windowPicker.close(); + }); + + if (!Main.sessionMode.hasOverview) { + this._overlayKeyId = global.display.connect('overlay-key', () => { + if (!Main.windowPicker.visible) + Main.windowPicker.open(); + else + Main.windowPicker.close(); + }); + } + + Main.windowPicker.connect('open-state-changed', () => { + this.checked = Main.windowPicker.visible; + }); + } + + _onDestroy() { + if (this._overlayKeyId) + global.display.disconnect(this._overlayKeyId); + this._overlayKeyId == 0; + } +}); -- 2.26.2 From 05bb3a140706891bd81e368997c4464c309f84cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sat, 29 Jun 2019 02:52:45 +0200 Subject: [PATCH 08/32] window-list: Fix resetting handler ID This is embarrassing, although destroy() is expected to only run once, so the bug shouldn't have an effect in practice. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/78 --- extensions/window-list/windowPicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js index c134b98..6c8f5e8 100644 --- a/extensions/window-list/windowPicker.js +++ b/extensions/window-list/windowPicker.js @@ -255,6 +255,6 @@ class WindowPickerToggle extends St.Button { _onDestroy() { if (this._overlayKeyId) global.display.disconnect(this._overlayKeyId); - this._overlayKeyId == 0; + this._overlayKeyId = 0; } }); -- 2.26.2 From b77a8ed7be50501f500a13e9c2b838cac2ea86d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 2 Jul 2019 17:39:55 +0200 Subject: [PATCH 09/32] window-list: Move super-key handling into WindowPicker We have an option to put a window list on each monitor, so we may have more than one window picker toggle. We don't want each of those try to toggle the window picker simultanuously, so move handling of the super key directly into the picker. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80 --- extensions/window-list/windowPicker.js | 34 ++++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js index 6c8f5e8..c3c8391 100644 --- a/extensions/window-list/windowPicker.js +++ b/extensions/window-list/windowPicker.js @@ -67,6 +67,8 @@ var WindowPicker = class { this._visible = false; this._modal = false; + this._overlayKeyId = 0; + this.actor = new Clutter.Actor(); this.actor.connect('destroy', this._onDestroy.bind(this)); @@ -101,6 +103,15 @@ var WindowPicker = class { this._updateBackgrounds(); Main.uiGroup.insert_child_below(this.actor, global.window_group); + + if (!Main.sessionMode.hasOverview) { + this._overlayKeyId = global.display.connect('overlay-key', () => { + if (!this._visible) + this.open(); + else + this.close(); + }); + } } get visible() { @@ -188,6 +199,10 @@ var WindowPicker = class { if (this._monitorsChangedId) Main.layoutManager.disconnect(this._monitorsChangedId); this._monitorsChangedId = 0; + + if (this._overlayKeyId) + global.display.disconnect(this._overlayKeyId); + this._overlayKeyId = 0; } _updateBackgrounds() { @@ -227,10 +242,6 @@ class WindowPickerToggle extends St.Button { toggle_mode: true }); - this._overlayKeyId = 0; - - this.connect('destroy', this._onDestroy.bind(this)); - this.connect('notify::checked', () => { if (this.checked) Main.windowPicker.open(); @@ -238,23 +249,8 @@ class WindowPickerToggle extends St.Button { Main.windowPicker.close(); }); - if (!Main.sessionMode.hasOverview) { - this._overlayKeyId = global.display.connect('overlay-key', () => { - if (!Main.windowPicker.visible) - Main.windowPicker.open(); - else - Main.windowPicker.close(); - }); - } - Main.windowPicker.connect('open-state-changed', () => { this.checked = Main.windowPicker.visible; }); } - - _onDestroy() { - if (this._overlayKeyId) - global.display.disconnect(this._overlayKeyId); - this._overlayKeyId = 0; - } }); -- 2.26.2 From 0e53b834147b380fbd155738eb30244d96f18ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 2 Jul 2019 18:12:31 +0200 Subject: [PATCH 10/32] window-list: Handle closing window picker with Escape Just like the overview can be closed with Escape, it makes sense to allow the same for the window picker (in addition to pressing super repeatedly). https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80 --- extensions/window-list/windowPicker.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js index c3c8391..4895ae5 100644 --- a/extensions/window-list/windowPicker.js +++ b/extensions/window-list/windowPicker.js @@ -68,6 +68,7 @@ var WindowPicker = class { this._modal = false; this._overlayKeyId = 0; + this._stageKeyPressId = 0; this.actor = new Clutter.Actor(); @@ -132,6 +133,16 @@ var WindowPicker = class { this._fakeOverviewAnimation(); this._workspacesDisplay.show(false); + this._stageKeyPressId = global.stage.connect('key-press-event', + (a, event) => { + let sym = event.get_key_symbol(); + if (sym == Clutter.KEY_Escape) { + this.close(); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + }); + this.emit('open-state-changed', this._visible); } @@ -151,6 +162,9 @@ var WindowPicker = class { this._fakeOverviewVisible(false); }); + global.stage.disconnect(this._stageKeyPressId); + this._stageKeyPressId = 0; + this.emit('open-state-changed', this._visible); } @@ -203,6 +217,10 @@ var WindowPicker = class { if (this._overlayKeyId) global.display.disconnect(this._overlayKeyId); this._overlayKeyId = 0; + + if (this._stageKeyPressId) + global.stage.disconnect(this._stageKeyPressId); + this._stageKeyPressId = 0; } _updateBackgrounds() { -- 2.26.2 From aa92589bb24aa3e354cf7e4f262144de1a9e7c5a Mon Sep 17 00:00:00 2001 From: Jakub Steiner Date: Mon, 15 Jul 2019 23:40:09 +0200 Subject: [PATCH 11/32] classic: hover state for panel buttons - prelight before active - lighten up slightly, similar to what the default does (inverted) Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/169 --- data/gnome-classic.scss | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/data/gnome-classic.scss b/data/gnome-classic.scss index e8f4803..a231534 100644 --- a/data/gnome-classic.scss +++ b/data/gnome-classic.scss @@ -30,18 +30,20 @@ $variant: 'light'; font-weight: normal; color: $fg_color; text-shadow: none; + &:hover { + color: lighten($fg_color,10%); + text-shadow: none; + & .system-status-icon { icon-shadow: none; } + } &:active, &:overview, &:focus, &:checked { // Trick due to St limitations. It needs a background to draw // a box-shadow - background-color: $selected_bg_color !important; - color: $selected_fg_color !important; + background-color: $selected_bg_color; + color: $selected_fg_color; box-shadow: none; & > .system-status-icon { icon-shadow: none; } } - &:hover { - text-shadow: none; - & .system-status-icon { icon-shadow: none; } - } + .app-menu-icon { width: 0; height: 0; margin: 0; } // shell's display:none; :D .system-status-icon { -- 2.26.2 From f11b024ae46c2228cb43f9f4d3455b542ce55f9e Mon Sep 17 00:00:00 2001 From: Jakub Steiner Date: Mon, 15 Jul 2019 23:03:41 +0200 Subject: [PATCH 12/32] classic: Update window-list styling Make buttons flatter, rounder to match default styling. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/82 --- extensions/window-list/classic.css | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index c506bea..cc967e0 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -4,21 +4,18 @@ border-top-width: 1px; border-bottom-width: 0px; height: 2.25em ; + padding: 2px; } .bottom-panel .window-button > StWidget, .bottom-panel .window-picker-toggle > StWidget { - background-gradient-drection: vertical; - background-color: #fff; - background-gradient-start: #fff; - background-gradient-end: #eee; - color: #000; color: #2e3436; background-color: #eee; - border-radius: 2px; + border-radius: 3px; padding: 3px 6px 1px; - box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); - text-shadow: 0 0 transparent; + box-shadow: none; + text-shadow: none; + border: 1px solid rgba(0,0,0,0.2); } .bottom-panel .window-button > StWidget { @@ -26,10 +23,6 @@ max-width: 18.75em; } - .bottom-panel .window-picker-toggle > StWidet { - border: 1px solid rgba(0,0,0,0.3); - } - .bottom-panel .window-button:hover > StWidget, .bottom-panel .window-picker-toggle:hover > StWidget { background-color: #f9f9f9; @@ -37,13 +30,13 @@ .bottom-panel .window-button:active > StWidget, .bottom-panel .window-button:focus > StWidget { - box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); + box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .bottom-panel .window-button.focused > StWidget, .bottom-panel .window-picker-toggle:checked > StWidget { - background-color: #ddd; - box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); + background-color: #ccc; + box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .bottom-panel .window-button.focused:hover > StWidget { @@ -52,5 +45,5 @@ .bottom-panel .window-button.minimized > StWidget { color: #888; - box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); + box-shadow: none; } -- 2.26.2 From 94f82ca2d97583e4dd6ea246b148c008c4de72bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 00:23:13 +0000 Subject: [PATCH 13/32] window-list: Split out workspaceIndicator The extension has grown unwieldily big, so before starting to improve on the workspace indicator, move it to its own source file. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/extension.js | 115 +---------------- extensions/window-list/meson.build | 2 +- extensions/window-list/workspaceIndicator.js | 127 +++++++++++++++++++ 3 files changed, 129 insertions(+), 115 deletions(-) create mode 100644 extensions/window-list/workspaceIndicator.js diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index f4610f9..061421a 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -9,7 +9,6 @@ const St = imports.gi.St; const DND = imports.ui.dnd; const Main = imports.ui.main; const Overview = imports.ui.overview; -const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const Tweener = imports.ui.tweener; @@ -17,6 +16,7 @@ const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; +const { WorkspaceIndicator } = Me.imports.workspaceIndicator; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; @@ -644,119 +644,6 @@ class AppButton extends BaseButton { }; -class WorkspaceIndicator extends PanelMenu.Button { - constructor() { - super(0.0, _("Workspace Indicator"), true); - this.setMenu(new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.BOTTOM)); - this.actor.add_style_class_name('window-list-workspace-indicator'); - this.menu.actor.remove_style_class_name('panel-menu'); - - let container = new St.Widget({ layout_manager: new Clutter.BinLayout(), - x_expand: true, y_expand: true }); - this.actor.add_actor(container); - - this._currentWorkspace = global.screen.get_active_workspace().index(); - this.statusLabel = new St.Label({ text: this._getStatusText(), - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.CENTER }); - container.add_actor(this.statusLabel); - - this.workspacesItems = []; - - this._screenSignals = []; - this._screenSignals.push(global.screen.connect('notify::n-workspaces', - this._updateMenu.bind(this))); - this._screenSignals.push(global.screen.connect_after('workspace-switched', - this._updateIndicator.bind(this))); - - this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); - this._updateMenu(); - - this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); - this._settingsChangedId = - this._settings.connect('changed::workspace-names', - this._updateMenu.bind(this)); - } - - destroy() { - for (let i = 0; i < this._screenSignals.length; i++) - global.screen.disconnect(this._screenSignals[i]); - - if (this._settingsChangedId) { - this._settings.disconnect(this._settingsChangedId); - this._settingsChangedId = 0; - } - - super.destroy(); - } - - _updateIndicator() { - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); - this._currentWorkspace = global.screen.get_active_workspace().index(); - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); - - this.statusLabel.set_text(this._getStatusText()); - } - - _getStatusText() { - let current = global.screen.get_active_workspace().index(); - let total = global.screen.n_workspaces; - - return '%d / %d'.format(current + 1, total); - } - - _updateMenu() { - this.menu.removeAll(); - this.workspacesItems = []; - this._currentWorkspace = global.screen.get_active_workspace().index(); - - for(let i = 0; i < global.screen.n_workspaces; i++) { - let name = Meta.prefs_get_workspace_name(i); - let item = new PopupMenu.PopupMenuItem(name); - item.workspaceId = i; - - item.connect('activate', (item, event) => { - this._activate(item.workspaceId); - }); - - if (i == this._currentWorkspace) - item.setOrnament(PopupMenu.Ornament.DOT); - - this.menu.addMenuItem(item); - this.workspacesItems[i] = item; - } - - this.statusLabel.set_text(this._getStatusText()); - } - - _activate(index) { - if(index >= 0 && index < global.screen.n_workspaces) { - let metaWorkspace = global.screen.get_workspace_by_index(index); - metaWorkspace.activate(global.get_current_time()); - } - } - - _onScrollEvent(actor, event) { - let direction = event.get_scroll_direction(); - let diff = 0; - if (direction == Clutter.ScrollDirection.DOWN) { - diff = 1; - } else if (direction == Clutter.ScrollDirection.UP) { - diff = -1; - } else { - return; - } - - let newIndex = this._currentWorkspace + diff; - this._activate(newIndex); - } - - _allocate(actor, box, flags) { - if (actor.get_n_children() > 0) - actor.get_first_child().allocate(box, flags); - } -}; - class WindowList { constructor(perMonitor, monitor) { this._perMonitor = perMonitor; diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build index 5b1f5f5..34d7c3f 100644 --- a/extensions/window-list/meson.build +++ b/extensions/window-list/meson.build @@ -4,7 +4,7 @@ extension_data += configure_file( configuration: metadata_conf ) -extension_sources += files('prefs.js', 'windowPicker.js') +extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') if classic_mode_enabled diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js new file mode 100644 index 0000000..4127672 --- /dev/null +++ b/extensions/window-list/workspaceIndicator.js @@ -0,0 +1,127 @@ +/* exported WorkspaceIndicator */ +const { Clutter, Gio, GObject, Meta, St } = imports.gi; + +const PanelMenu = imports.ui.panelMenu; +const PopupMenu = imports.ui.popupMenu; + +const Gettext = imports.gettext.domain('gnome-shell-extensions'); +const _ = Gettext.gettext; + +var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + constructor() { + super(0.0, _('Workspace Indicator'), true); + this.setMenu(new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.BOTTOM)); + this.actor.add_style_class_name('window-list-workspace-indicator'); + this.menu.actor.remove_style_class_name('panel-menu'); + + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, + y_expand: true + }); + this.actor.add_actor(container); + + this._currentWorkspace = global.screen.get_active_workspace().index(); + this.statusLabel = new St.Label({ + text: this._getStatusText(), + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER + }); + container.add_actor(this.statusLabel); + + this.workspacesItems = []; + + this._screenSignals = [ + global.screen.connect('notify::n-workspaces', + this._updateMenu.bind(this)), + global.screen.connect_after('workspace-switched', + this._updateIndicator.bind(this)) + ]; + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + this._updateMenu(); + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( + 'changed::workspace-names', this._updateMenu.bind(this)); + } + + destroy() { + for (let i = 0; i < this._screenSignals.length; i++) + global.screen.disconnect(this._screenSignals[i]); + + if (this._settingsChangedId) { + this._settings.disconnect(this._settingsChangedId); + this._settingsChangedId = 0; + } + + super.destroy(); + } + + _updateIndicator() { + this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._currentWorkspace = global.screen.get_active_workspace().index(); + this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this.statusLabel.set_text(this._getStatusText()); + } + + _getStatusText() { + let current = global.screen.get_active_workspace().index(); + let total = global.screen.n_workspaces; + + return '%d / %d'.format(current + 1, total); + } + + _updateMenu() { + this.menu.removeAll(); + this.workspacesItems = []; + this._currentWorkspace = global.screen.get_active_workspace().index(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { + let name = Meta.prefs_get_workspace_name(i); + let item = new PopupMenu.PopupMenuItem(name); + item.workspaceId = i; + + item.connect('activate', (item, _event) => { + this._activate(item.workspaceId); + }); + + if (i == this._currentWorkspace) + item.setOrnament(PopupMenu.Ornament.DOT); + + this.menu.addMenuItem(item); + this.workspacesItems[i] = item; + } + + this.statusLabel.set_text(this._getStatusText()); + } + + _activate(index) { + if (index >= 0 && index < global.screen.n_workspaces) { + let metaWorkspace = global.screen.get_workspace_by_index(index); + metaWorkspace.activate(global.get_current_time()); + } + } + + _onScrollEvent(actor, event) { + let direction = event.get_scroll_direction(); + let diff = 0; + if (direction == Clutter.ScrollDirection.DOWN) { + diff = 1; + } else if (direction == Clutter.ScrollDirection.UP) { + diff = -1; + } else { + return; + } + + let newIndex = this._currentWorkspace + diff; + this._activate(newIndex); + } + + _allocate(actor, box, flags) { + if (actor.get_n_children() > 0) + actor.get_first_child().allocate(box, flags); + } +}; + -- 2.26.2 From 411547ad976c8917c964b58210be04d1f93f54ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 04:54:50 +0200 Subject: [PATCH 14/32] window-list: Make some properties private There's no reason why they should be public. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/workspaceIndicator.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 4127672..cd8c2d1 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -22,14 +22,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { this.actor.add_actor(container); this._currentWorkspace = global.screen.get_active_workspace().index(); - this.statusLabel = new St.Label({ + this._statusLabel = new St.Label({ text: this._getStatusText(), x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER }); - container.add_actor(this.statusLabel); + container.add_actor(this._statusLabel); - this.workspacesItems = []; + this._workspacesItems = []; this._screenSignals = [ global.screen.connect('notify::n-workspaces', @@ -59,11 +59,11 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { } _updateIndicator() { - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); this._currentWorkspace = global.screen.get_active_workspace().index(); - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); - this.statusLabel.set_text(this._getStatusText()); + this._statusLabel.set_text(this._getStatusText()); } _getStatusText() { @@ -75,7 +75,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { _updateMenu() { this.menu.removeAll(); - this.workspacesItems = []; + this._workspacesItems = []; this._currentWorkspace = global.screen.get_active_workspace().index(); for (let i = 0; i < global.screen.n_workspaces; i++) { @@ -91,10 +91,10 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { item.setOrnament(PopupMenu.Ornament.DOT); this.menu.addMenuItem(item); - this.workspacesItems[i] = item; + this._workspacesItems[i] = item; } - this.statusLabel.set_text(this._getStatusText()); + this._statusLabel.set_text(this._getStatusText()); } _activate(index) { -- 2.26.2 From 034582f6e53577ebd32f9152de6ecf290ebb4148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 04:57:39 +0200 Subject: [PATCH 15/32] window-list: Update workspace names in-place There's no good reason to rebuild the entire menu on workspace names changes, we can simply update the labels in-place. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/workspaceIndicator.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index cd8c2d1..8c360be 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -43,7 +43,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); this._settingsChangedId = this._settings.connect( - 'changed::workspace-names', this._updateMenu.bind(this)); + 'changed::workspace-names', this._updateMenuLabels.bind(this)); } destroy() { @@ -73,6 +73,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { return '%d / %d'.format(current + 1, total); } + _updateMenuLabels() { + for (let i = 0; i < this._workspacesItems.length; i++) { + let item = this._workspacesItems[i]; + let name = Meta.prefs_get_workspace_name(i); + item.label.text = name; + } + } + _updateMenu() { this.menu.removeAll(); this._workspacesItems = []; -- 2.26.2 From 99444dff49f34beb957cf6157a2dda0bacb3a09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 04:59:19 +0200 Subject: [PATCH 16/32] window-list: Minor cleanup Mutter has a dedicated method for getting the index of the active workspace, use that instead of getting first the active workspace and then its index. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/workspaceIndicator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 8c360be..9bb137b 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -21,7 +21,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { }); this.actor.add_actor(container); - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); this._statusLabel = new St.Label({ text: this._getStatusText(), x_align: Clutter.ActorAlign.CENTER, @@ -60,14 +60,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { _updateIndicator() { this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); this._statusLabel.set_text(this._getStatusText()); } _getStatusText() { - let current = global.screen.get_active_workspace().index(); + let current = global.screen.get_active_workspace_index(); let total = global.screen.n_workspaces; return '%d / %d'.format(current + 1, total); @@ -84,7 +84,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { _updateMenu() { this.menu.removeAll(); this._workspacesItems = []; - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); for (let i = 0; i < global.screen.n_workspaces; i++) { let name = Meta.prefs_get_workspace_name(i); -- 2.26.2 From a1d3da2bb958a1927130f7d7e9a7490924a1d5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 05:11:34 +0200 Subject: [PATCH 17/32] window-list: Improve workspace label styling The border currently looks off - it extends all the way vertically and leaves zero spacing to the label horizontally. Fix both issues by setting appropriate padding/margins. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/stylesheet.css | 8 +++----- extensions/window-list/workspaceIndicator.js | 13 ++++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index 91383ab..bab8f76 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -85,13 +85,11 @@ height: 24px; } -.window-list-workspace-indicator { - padding: 3px; -} - -.window-list-workspace-indicator > StWidget { +.window-list-workspace-indicator .status-label-bin { background-color: rgba(200, 200, 200, .3); border: 1px solid #cccccc; + padding: 0 3px; + margin: 3px 0; } .notification { diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 9bb137b..f7d0733 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -22,12 +22,15 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { this.actor.add_actor(container); this._currentWorkspace = global.screen.get_active_workspace_index(); - this._statusLabel = new St.Label({ - text: this._getStatusText(), - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.CENTER + this._statusLabel = new St.Label({ text: this._getStatusText() }); + + this._statusBin = new St.Bin({ + style_class: 'status-label-bin', + x_expand: true, + y_expand: true, + child: this._statusLabel }); - container.add_actor(this._statusLabel); + container.add_actor(this._statusBin); this._workspacesItems = []; -- 2.26.2 From 4790242fee236df68f21c9b5b178f0629f33fe4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 05:08:31 +0200 Subject: [PATCH 18/32] window-list: Refactor workspace signal handlers We are about to support a separate representation if horizontal workspaces are used. To prepare for that, rename the handlers to something more generic and split out menu-specific bits into a dedicated helper function. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/workspaceIndicator.js | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index f7d0733..a4ab011 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -36,9 +36,9 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { this._screenSignals = [ global.screen.connect('notify::n-workspaces', - this._updateMenu.bind(this)), + this._nWorkspacesChanged.bind(this)), global.screen.connect_after('workspace-switched', - this._updateIndicator.bind(this)) + this._onWorkspaceSwitched.bind(this)) ]; this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); @@ -61,14 +61,26 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { super.destroy(); } - _updateIndicator() { - this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + _onWorkspaceSwitched() { this._currentWorkspace = global.screen.get_active_workspace_index(); - this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._updateMenuOrnament(); this._statusLabel.set_text(this._getStatusText()); } + _nWorkspacesChanged() { + this._updateMenu(); + } + + _updateMenuOrnament() { + for (let i = 0; i < this._workspacesItems.length; i++) { + this._workspacesItems[i].setOrnament(i == this._currentWorkspace + ? PopupMenu.Ornament.DOT + : PopupMenu.Ornament.NONE); + } + } + _getStatusText() { let current = global.screen.get_active_workspace_index(); let total = global.screen.n_workspaces; -- 2.26.2 From 8d6b99d0a20ffed77a301d2a92584dac4f6a834d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 02:53:38 +0000 Subject: [PATCH 19/32] window-list: Support horizontal workspace layout Unlike in GNOME 2, the workspace indicator we display in the window list isn't a workspace switcher, but a menu button that allows switching workspaces via its menu. The reason for that is that a horizontal in-place switcher would be at odds with the vertical workspace layout used in GNOME 3. However that reasoning doesn't apply when the layout is changed to a horizontal one, so replace the button with a traditional workspace switcher in that case. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 --- extensions/window-list/classic.css | 9 +++ extensions/window-list/stylesheet.css | 29 +++++++++ extensions/window-list/workspaceIndicator.js | 63 +++++++++++++++++++- 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index cc967e0..c533473 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -47,3 +47,12 @@ color: #888; box-shadow: none; } + +/* workspace switcher */ +.window-list-workspace-indicator .workspace { + background-color: #ddd; +} + +.window-list-workspace-indicator .workspace.active { + background-color: #ccc; +} diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index bab8f76..ad5978a 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -92,6 +92,35 @@ margin: 3px 0; } +.window-list-workspace-indicator .workspaces-box { + spacing: 3px; + padding: 3px; +} + +.window-list-workspace-indicator .workspace { + border: 1px solid #cccccc; + width: 52px; +} + +.window-list-workspace-indicator .workspace:first-child:last-child:ltr, +.window-list-workspace-indicator .workspace:first-child:last-child:rtl { + border-radius: 4px; +} + +.window-list-workspace-indicator .workspace:first-child:ltr, +.window-list-workspace-indicator .workspace:last-child:rtl { + border-radius: 4px 0 0 4px; +} + +.window-list-workspace-indicator .workspace:first-child:rtl, +.window-list-workspace-indicator .workspace:last-child:ltr { + border-radius: 0 4px 4px 0; +} + +.window-list-workspace-indicator .workspace.active { + background-color: rgba(200, 200, 200, .3); +} + .notification { font-weight: normal; } diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index a4ab011..3519497 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -7,6 +7,24 @@ const PopupMenu = imports.ui.popupMenu; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; +let WorkspaceThumbnail = GObject.registerClass({ + GTypeName: 'WindowListWorkspaceThumbnail' +}, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ + style_class: 'workspace' + }); + + this._index = index; + } + + on_clicked() { + let ws = global.screen.get_workspace_by_index(this._index); + if (ws) + ws.activate(global.get_current_time()); + } +}); + var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { constructor() { super(0.0, _('Workspace Indicator'), true); @@ -32,17 +50,30 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { }); container.add_actor(this._statusBin); + this._thumbnailsBox = new St.BoxLayout({ + style_class: 'workspaces-box', + y_expand: true, + reactive: true + }); + this._thumbnailsBox.connect('scroll-event', + this._onScrollEvent.bind(this)); + container.add_actor(this._thumbnailsBox); + this._workspacesItems = []; this._screenSignals = [ global.screen.connect('notify::n-workspaces', this._nWorkspacesChanged.bind(this)), global.screen.connect_after('workspace-switched', - this._onWorkspaceSwitched.bind(this)) + this._onWorkspaceSwitched.bind(this)), + global.screen.connect('notify::layout-rows', + this._onWorkspaceOrientationChanged.bind(this)) ]; this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); this._updateMenu(); + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); this._settingsChangedId = this._settings.connect( @@ -61,16 +92,26 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { super.destroy(); } + _onWorkspaceOrientationChanged() { + let vertical = global.screen.layout_rows == -1; + this.reactive = vertical; + + this._statusBin.visible = vertical; + this._thumbnailsBox.visible = !vertical; + } + _onWorkspaceSwitched() { this._currentWorkspace = global.screen.get_active_workspace_index(); this._updateMenuOrnament(); + this._updateActiveThumbnail(); this._statusLabel.set_text(this._getStatusText()); } _nWorkspacesChanged() { this._updateMenu(); + this._updateThumbnails(); } _updateMenuOrnament() { @@ -81,6 +122,16 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { } } + _updateActiveThumbnail() { + let thumbs = this._thumbnailsBox.get_children(); + for (let i = 0; i < thumbs.length; i++) { + if (i == this._currentWorkspace) + thumbs[i].add_style_class_name('active'); + else + thumbs[i].remove_style_class_name('active'); + } + } + _getStatusText() { let current = global.screen.get_active_workspace_index(); let total = global.screen.n_workspaces; @@ -120,6 +171,16 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { this._statusLabel.set_text(this._getStatusText()); } + _updateThumbnails() { + this._thumbnailsBox.destroy_all_children(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { + let thumb = new WorkspaceThumbnail(i); + this._thumbnailsBox.add_actor(thumb); + } + this._updateActiveThumbnail(); + } + _activate(index) { if (index >= 0 && index < global.screen.n_workspaces) { let metaWorkspace = global.screen.get_workspace_by_index(index); -- 2.26.2 From 9b481c9ec7acf6bf681b8a4706a611cc63ec8660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 11 Jun 2019 23:01:20 +0000 Subject: [PATCH 20/32] window-list: Turn workspace thumbs into drop targets It makes some sense to allow using the workspace indicator for moving windows between workspaces as well as for workspace switching. This applies particularly in GNOME classic after we disabled the overview there, so that there is again a non-shortcut way of moving windows between workspaces. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 --- extensions/window-list/workspaceIndicator.js | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 3519497..771ff4e 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -1,6 +1,8 @@ /* exported WorkspaceIndicator */ const { Clutter, Gio, GObject, Meta, St } = imports.gi; +const DND = imports.ui.dnd; +const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; @@ -16,6 +18,31 @@ let WorkspaceThumbnail = GObject.registerClass({ }); this._index = index; + this._delegate = this; // needed for DND + } + + acceptDrop(source) { + if (!source.realWindow) + return false; + + let window = source.realWindow.get_meta_window(); + this._moveWindow(window); + return true; + } + + handleDragOver(source) { + if (source.realWindow) + return DND.DragMotionResult.MOVE_DROP; + else + return DND.DragMotionResult.CONTINUE; + } + + + _moveWindow(window) { + let monitorIndex = Main.layoutManager.findIndexForActor(this); + if (monitorIndex != window.get_monitor()) + window.move_to_monitor(monitorIndex); + window.change_workspace_by_index(this._index, false); } on_clicked() { -- 2.26.2 From f64c11d954309ef9eb286d88794a1738ed157e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 26 Jun 2019 23:55:58 +0000 Subject: [PATCH 21/32] window-list: Show previews in workspace switcher Currently the new horizontal workspace switcher only shows a series of buttons, with no indication of the workspaces' contents. Go full GNOME 2 and add tiny draggable preview rectangles that represent the windows on a particular workspace. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 --- extensions/window-list/classic.css | 10 ++ extensions/window-list/stylesheet.css | 10 ++ extensions/window-list/workspaceIndicator.js | 153 ++++++++++++++++++- 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css index c533473..7079d3e 100644 --- a/extensions/window-list/classic.css +++ b/extensions/window-list/classic.css @@ -56,3 +56,13 @@ .window-list-workspace-indicator .workspace.active { background-color: #ccc; } + +.window-list-window-preview { + background-color: #ededed; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #f6f5f4; + border: 2px solid #888; +} diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css index ad5978a..79d56ba 100644 --- a/extensions/window-list/stylesheet.css +++ b/extensions/window-list/stylesheet.css @@ -121,6 +121,16 @@ background-color: rgba(200, 200, 200, .3); } +.window-list-window-preview { + background-color: #252525; + border: 1px solid #ccc; +} + +.window-list-window-preview.active { + background-color: #353535; + border: 2px solid #ccc; +} + .notification { font-weight: normal; } diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 771ff4e..7caad52 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -9,16 +9,130 @@ const PopupMenu = imports.ui.popupMenu; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; +let WindowPreview = GObject.registerClass({ + GTypeName: 'WindowListWindowPreview' +}, class WindowPreview extends St.Button { + _init(window) { + super._init({ + style_class: 'window-list-window-preview' + }); + + this._delegate = this; + DND.makeDraggable(this, { restoreOnSuccess: true }); + + this._window = window; + + this.connect('destroy', this._onDestroy.bind(this)); + + this._sizeChangedId = this._window.connect('size-changed', + this._relayout.bind(this)); + this._positionChangedId = this._window.connect('position-changed', + this._relayout.bind(this)); + this._minimizedChangedId = this._window.connect('notify::minimized', + this._relayout.bind(this)); + this._monitorEnteredId = global.screen.connect('window-entered-monitor', + this._relayout.bind(this)); + this._monitorLeftId = global.screen.connect('window-left-monitor', + this._relayout.bind(this)); + + // Do initial layout when we get a parent + let id = this.connect('parent-set', () => { + this.disconnect(id); + if (!this.get_parent()) + return; + this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._laterId = 0; + this._relayout(); + return false; + }); + }); + + this._focusChangedId = global.display.connect('notify::focus-window', + this._onFocusChanged.bind(this)); + this._onFocusChanged(); + } + + // needed for DND + get realWindow() { + return this._window.get_compositor_private(); + } + + _onDestroy() { + this._window.disconnect(this._sizeChangedId); + this._window.disconnect(this._positionChangedId); + this._window.disconnect(this._minimizedChangedId); + global.screen.disconnect(this._monitorEnteredId); + global.screen.disconnect(this._monitorLeftId); + global.display.disconnect(this._focusChangedId); + if (this._laterId) + Meta.later_remove(this._laterId); + } + + _onFocusChanged() { + if (global.display.focus_window == this._window) + this.add_style_class_name('active'); + else + this.remove_style_class_name('active'); + } + + _relayout() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && + this._window.showing_on_its_workspace(); + + if (!this.visible) + return; + + let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); + let hscale = this.get_parent().allocation.get_width() / workArea.width; + let vscale = this.get_parent().allocation.get_height() / workArea.height; + + let frameRect = this._window.get_frame_rect(); + this.set_size( + Math.round(Math.min(frameRect.width, workArea.width) * hscale), + Math.round(Math.min(frameRect.height, workArea.height) * vscale)); + this.set_position( + Math.round(frameRect.x * hscale), + Math.round(frameRect.y * vscale)); + } +}); + let WorkspaceThumbnail = GObject.registerClass({ GTypeName: 'WindowListWorkspaceThumbnail' }, class WorkspaceThumbnail extends St.Button { _init(index) { super._init({ - style_class: 'workspace' + style_class: 'workspace', + child: new Clutter.Actor({ + layout_manager: new Clutter.BinLayout(), + clip_to_allocation: true + }), + x_fill: true, + y_fill: true }); + this.connect('destroy', this._onDestroy.bind(this)); + this._index = index; this._delegate = this; // needed for DND + + this._windowPreviews = new Map(); + + this._workspace = global.screen.get_workspace_by_index(index); + + this._windowAddedId = this._workspace.connect('window-added', + (ws, window) => { + this._addWindow(window); + }); + this._windowRemovedId = this._workspace.connect('window-removed', + (ws, window) => { + this._removeWindow(window); + }); + this._restackedId = global.screen.connect('restacked', + this._onRestacked.bind(this)); + + this._workspace.list_windows().forEach(w => this._addWindow(w)); + this._onRestacked(); } acceptDrop(source) { @@ -37,6 +151,37 @@ let WorkspaceThumbnail = GObject.registerClass({ return DND.DragMotionResult.CONTINUE; } + _addWindow(window) { + if (this._windowPreviews.has(window)) + return; + + let preview = new WindowPreview(window); + preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); + this._windowPreviews.set(window, preview); + this.child.add_child(preview); + } + + _removeWindow(window) { + let preview = this._windowPreviews.get(window); + if (!preview) + return; + + this._windowPreviews.delete(window); + preview.destroy(); + } + + _onRestacked() { + let lastPreview = null; + let windows = global.get_window_actors().map(a => a.meta_window); + for (let i = 0; i < windows.length; i++) { + let preview = this._windowPreviews.get(windows[i]); + if (!preview) + continue; + + this.child.set_child_above_sibling(preview, lastPreview); + lastPreview = preview; + } + } _moveWindow(window) { let monitorIndex = Main.layoutManager.findIndexForActor(this); @@ -50,6 +195,12 @@ let WorkspaceThumbnail = GObject.registerClass({ if (ws) ws.activate(global.get_current_time()); } + + _onDestroy() { + this._workspace.disconnect(this._windowAddedId); + this._workspace.disconnect(this._windowRemovedId); + global.screen.disconnect(this._restackedId); + } }); var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { -- 2.26.2 From 99380fb1c06f6539c69918fea513e59c327f0035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 5 Jun 2019 03:31:16 +0000 Subject: [PATCH 22/32] classic: Add 'horizontal-workspaces' extension Vertical workspaces are another defining characteristics of GNOME 3, and thus rather un-classic. That switch was driven by the overall layout of the overview, and now that we disable the overview in GNOME Classic, we can just return to the traditional workspace layout as well. Add a small extension that does just that. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/72 --- extensions/horizontal-workspaces/extension.js | 38 +++++++++++++++++++ extensions/horizontal-workspaces/meson.build | 5 +++ .../horizontal-workspaces/metadata.json.in | 10 +++++ .../horizontal-workspaces/stylesheet.css | 1 + meson.build | 1 + 5 files changed, 55 insertions(+) create mode 100644 extensions/horizontal-workspaces/extension.js create mode 100644 extensions/horizontal-workspaces/meson.build create mode 100644 extensions/horizontal-workspaces/metadata.json.in create mode 100644 extensions/horizontal-workspaces/stylesheet.css diff --git a/extensions/horizontal-workspaces/extension.js b/extensions/horizontal-workspaces/extension.js new file mode 100644 index 0000000..c9a3d13 --- /dev/null +++ b/extensions/horizontal-workspaces/extension.js @@ -0,0 +1,38 @@ +/* exported init */ +const { Meta } = imports.gi; + +const { ThumbnailsBox } = imports.ui.workspaceThumbnail; + +class Extension { + constructor() { + this._origUpdateSwitcherVisibility = + ThumbnailsBox.prototype._updateSwitcherVisibility; + } + + enable() { + global.screen.override_workspace_layout( + Meta.ScreenCorner.TOPLEFT, + false, + 1, + -1); + + ThumbnailsBox.prototype._updateSwitcherVisibility = function() { + this.actor.hide(); + }; + } + + disable() { + global.screen.override_workspace_layout( + Meta.ScreenCorner.TOPLEFT, + false, + -1, + 1); + + ThumbnailsBox.prototype._updateSwitcherVisibility = + this._origUpdateSwitcherVisibility; + } +} + +function init() { + return new Extension(); +} diff --git a/extensions/horizontal-workspaces/meson.build b/extensions/horizontal-workspaces/meson.build new file mode 100644 index 0000000..48504f6 --- /dev/null +++ b/extensions/horizontal-workspaces/meson.build @@ -0,0 +1,5 @@ +extension_data += configure_file( + input: metadata_name + '.in', + output: metadata_name, + configuration: metadata_conf +) diff --git a/extensions/horizontal-workspaces/metadata.json.in b/extensions/horizontal-workspaces/metadata.json.in new file mode 100644 index 0000000..f109e06 --- /dev/null +++ b/extensions/horizontal-workspaces/metadata.json.in @@ -0,0 +1,10 @@ +{ +"extension-id": "@extension_id@", +"uuid": "@uuid@", +"settings-schema": "@gschemaname@", +"gettext-domain": "@gettext_domain@", +"name": "Horizontal workspaces", +"description": "Use a horizontal workspace layout", +"shell-version": [ "@shell_current@" ], +"url": "@url@" +} diff --git a/extensions/horizontal-workspaces/stylesheet.css b/extensions/horizontal-workspaces/stylesheet.css new file mode 100644 index 0000000..25134b6 --- /dev/null +++ b/extensions/horizontal-workspaces/stylesheet.css @@ -0,0 +1 @@ +/* This extensions requires no special styling */ diff --git a/meson.build b/meson.build index fa4aa9c..c1581a5 100644 --- a/meson.build +++ b/meson.build @@ -36,6 +36,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' classic_extensions = [ 'alternate-tab', 'apps-menu', + 'horizontal-workspaces', 'places-menu', 'launch-new-instance', 'top-icons', -- 2.26.2 From 80f75e781e16405ae21fcb3135540c3157cbd1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sat, 29 Jun 2019 01:24:54 +0200 Subject: [PATCH 23/32] workspace-indicator: Fix whitespace error We only want a single space before and after operators, not at least one. Unfortunately eslint only enforces the latter ... https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index ace1703..f6d9912 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -106,7 +106,7 @@ class WorkspaceIndicator extends PanelMenu.Button { } _activate(index) { - if(index >= 0 && index < global.screen.n_workspaces) { + if(index >= 0 && index < global.screen.n_workspaces) { let metaWorkspace = global.screen.get_workspace_by_index(index); metaWorkspace.activate(global.get_current_time()); } -- 2.26.2 From a2636036fcb5f662edc520b4c0d51908c46c54f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 22:58:29 +0000 Subject: [PATCH 24/32] workspace-indicator: Make some properties private There's no reason why they should be public. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 35 +++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index f6d9912..67dec14 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -26,12 +26,14 @@ class WorkspaceIndicator extends PanelMenu.Button { super(0.0, _("Workspace Indicator")); this._currentWorkspace = global.screen.get_active_workspace().index(); - this.statusLabel = new St.Label({ y_align: Clutter.ActorAlign.CENTER, - text: this._labelText() }); + this._statusLabel = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() + }); - this.actor.add_actor(this.statusLabel); + this.add_actor(this._statusLabel); - this.workspacesItems = []; + this._workspacesItems = []; this._workspaceSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this._workspaceSection); @@ -46,7 +48,7 @@ class WorkspaceIndicator extends PanelMenu.Button { this._createWorkspacesSection(); //styling - this.statusLabel.add_style_class_name('panel-workspace-indicator'); + this._statusLabel.add_style_class_name('panel-workspace-indicator'); this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); this._settingsChangedId = @@ -67,11 +69,11 @@ class WorkspaceIndicator extends PanelMenu.Button { } _updateIndicator() { - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); this._currentWorkspace = global.screen.get_active_workspace().index(); - this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); - this.statusLabel.set_text(this._labelText()); + this._statusLabel.set_text(this._labelText()); } _labelText(workspaceIndex) { @@ -84,25 +86,24 @@ class WorkspaceIndicator extends PanelMenu.Button { _createWorkspacesSection() { this._workspaceSection.removeAll(); - this.workspacesItems = []; + this._workspacesItems = []; this._currentWorkspace = global.screen.get_active_workspace().index(); let i = 0; for(; i < global.screen.n_workspaces; i++) { - this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); - this._workspaceSection.addMenuItem(this.workspacesItems[i]); - this.workspacesItems[i].workspaceId = i; - this.workspacesItems[i].label_actor = this.statusLabel; - let self = this; - this.workspacesItems[i].connect('activate', (actor, event) => { + this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); + this._workspaceSection.addMenuItem(this._workspacesItems[i]); + this._workspacesItems[i].workspaceId = i; + this._workspacesItems[i].label_actor = this._statusLabel; + this._workspacesItems[i].connect('activate', (actor, _event) => { this._activate(actor.workspaceId); }); if (i == this._currentWorkspace) - this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); + this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); } - this.statusLabel.set_text(this._labelText()); + this._statusLabel.set_text(this._labelText()); } _activate(index) { -- 2.26.2 From d23f8f5dc246d9aa572287a78a49b6628bba38fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 23:03:55 +0000 Subject: [PATCH 25/32] workspace-indicator: Update workspace names in-place There's no good reason to rebuild the entire menu on workspace names changes, we can simply update the labels in-place. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 67dec14..3befe14 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -51,9 +51,9 @@ class WorkspaceIndicator extends PanelMenu.Button { this._statusLabel.add_style_class_name('panel-workspace-indicator'); this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); - this._settingsChangedId = - this._settings.connect('changed::' + WORKSPACE_KEY, - this._createWorkspacesSection.bind(this)); + this._settingsChangedId = this._settings.connect( + `changed::${WORKSPACE_KEY}`, + this._updateMenuLabels.bind(this)); } destroy() { @@ -84,6 +84,11 @@ class WorkspaceIndicator extends PanelMenu.Button { return Meta.prefs_get_workspace_name(workspaceIndex); } + _updateMenuLabels() { + for (let i = 0; i < this._workspacesItems.length; i++) + this._workspacesItems[i].label.text = this._labelText(i); + } + _createWorkspacesSection() { this._workspaceSection.removeAll(); this._workspacesItems = []; -- 2.26.2 From 2c24a724dade18dc55ae9ecf0966e6dc7c3ef067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 23:05:00 +0000 Subject: [PATCH 26/32] workspace-indicator: Minor cleanup Mutter has a dedicated method for getting the index of the active workspace, use that instead of getting first the active workspace and then its index. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 3befe14..d341b93 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -25,7 +25,7 @@ class WorkspaceIndicator extends PanelMenu.Button { constructor() { super(0.0, _("Workspace Indicator")); - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); this._statusLabel = new St.Label({ y_align: Clutter.ActorAlign.CENTER, text: this._labelText() @@ -70,7 +70,7 @@ class WorkspaceIndicator extends PanelMenu.Button { _updateIndicator() { this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); this._statusLabel.set_text(this._labelText()); @@ -92,7 +92,7 @@ class WorkspaceIndicator extends PanelMenu.Button { _createWorkspacesSection() { this._workspaceSection.removeAll(); this._workspacesItems = []; - this._currentWorkspace = global.screen.get_active_workspace().index(); + this._currentWorkspace = global.screen.get_active_workspace_index(); let i = 0; for(; i < global.screen.n_workspaces; i++) { @@ -129,7 +129,7 @@ class WorkspaceIndicator extends PanelMenu.Button { return; } - let newIndex = global.screen.get_active_workspace().index() + diff; + let newIndex = global.screen.get_active_workspace_index() + diff; this._activate(newIndex); } }; -- 2.26.2 From 416785e3be35ce922eabb9d358f13c95542b8f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 23:09:12 +0000 Subject: [PATCH 27/32] workspace-indicator: Refactor workspace signal handlers We are about to support a separate representation if horizontal workspaces are used. To prepare for that, rename the handlers to something more generic and split out menu-specific bits into a dedicated helper function. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 30 ++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index d341b93..58ac865 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -37,12 +37,12 @@ class WorkspaceIndicator extends PanelMenu.Button { this._workspaceSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this._workspaceSection); - this._screenSignals = []; - this._screenSignals.push(global.screen.connect_after('workspace-added', this._createWorkspacesSection.bind(this))); - this._screenSignals.push(global.screen.connect_after('workspace-removed', - this._createWorkspacesSection.bind(this))); - this._screenSignals.push(global.screen.connect_after('workspace-switched', - this._updateIndicator.bind(this))); + this._screenSignals = [ + global.screen.connect_after('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), + global.screen.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)) + ]; this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); this._createWorkspacesSection(); @@ -68,14 +68,26 @@ class WorkspaceIndicator extends PanelMenu.Button { super.destroy(); } - _updateIndicator() { - this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + _onWorkspaceSwitched() { this._currentWorkspace = global.screen.get_active_workspace_index(); - this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._updateMenuOrnament(); this._statusLabel.set_text(this._labelText()); } + _nWorkspacesChanged() { + this._createWorkspacesSection(); + } + + _updateMenuOrnament() { + for (let i = 0; i < this._workspacesItems.length; i++) { + this._workspacesItems[i].setOrnament(i == this._currentWorkspace + ? PopupMenu.Ornament.DOT + : PopupMenu.Ornament.NONE); + } + } + _labelText(workspaceIndex) { if(workspaceIndex == undefined) { workspaceIndex = this._currentWorkspace; -- 2.26.2 From 516d6d3e8e95f672388a0a807913fb116b70b79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 23:17:35 +0000 Subject: [PATCH 28/32] workspace-indicator: Minor cleanup Pass the style class at construction time instead of setting it later. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 58ac865..e6fffe4 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -27,6 +27,7 @@ class WorkspaceIndicator extends PanelMenu.Button { this._currentWorkspace = global.screen.get_active_workspace_index(); this._statusLabel = new St.Label({ + style_class: 'panel-workspace-indicator', y_align: Clutter.ActorAlign.CENTER, text: this._labelText() }); @@ -47,9 +48,6 @@ class WorkspaceIndicator extends PanelMenu.Button { this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); this._createWorkspacesSection(); - //styling - this._statusLabel.add_style_class_name('panel-workspace-indicator'); - this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); this._settingsChangedId = this._settings.connect( `changed::${WORKSPACE_KEY}`, -- 2.26.2 From 524166a7d4ece0e86f80190209387cc653c90b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 9 Jun 2019 23:45:24 +0000 Subject: [PATCH 29/32] workspace-indicator: Support horizontal workspace layout Just like we did for the workspace indicator in the window-list, improve the handling of horizontal workspace layouts by showing the switcher in-place instead of delegating the functionality to a menu. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 --- extensions/workspace-indicator/extension.js | 74 ++++++++++++++++++- extensions/workspace-indicator/stylesheet.css | 27 ++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index e6fffe4..32bb10f 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -1,6 +1,7 @@ // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- const Gio = imports.gi.Gio; +const GObject = imports.gi.GObject; const Meta = imports.gi.Meta; const Clutter = imports.gi.Clutter; const St = imports.gi.St; @@ -21,10 +22,36 @@ const Convenience = Me.imports.convenience; const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; const WORKSPACE_KEY = 'workspace-names'; +let WorkspaceThumbnail = GObject.registerClass({ + GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' +}, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ + style_class: 'workspace', + }); + + this._index = index; + } + + on_clicked() { + let ws = global.screen.get_workspace_by_index(this._index); + if (ws) + ws.activate(global.get_current_time()); + } +}); + + class WorkspaceIndicator extends PanelMenu.Button { constructor() { super(0.0, _("Workspace Indicator")); + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, + y_expand: true + }); + this.actor.add_actor(container); + this._currentWorkspace = global.screen.get_active_workspace_index(); this._statusLabel = new St.Label({ style_class: 'panel-workspace-indicator', @@ -32,7 +59,15 @@ class WorkspaceIndicator extends PanelMenu.Button { text: this._labelText() }); - this.add_actor(this._statusLabel); + container.add_actor(this._statusLabel); + + this._thumbnailsBox = new St.BoxLayout({ + style_class: 'panel-workspace-indicator-box', + y_expand: true, + reactive: true + }); + + container.add_actor(this._thumbnailsBox); this._workspacesItems = []; this._workspaceSection = new PopupMenu.PopupMenuSection(); @@ -42,11 +77,16 @@ class WorkspaceIndicator extends PanelMenu.Button { global.screen.connect_after('notify::n-workspaces', this._nWorkspacesChanged.bind(this)), global.screen.connect_after('workspace-switched', - this._onWorkspaceSwitched.bind(this)) + this._onWorkspaceSwitched.bind(this)), + global.screen.connect('notify::layout-rows', + this._onWorkspaceOrientationChanged.bind(this)) ]; this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); this._createWorkspacesSection(); + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); this._settingsChangedId = this._settings.connect( @@ -66,16 +106,26 @@ class WorkspaceIndicator extends PanelMenu.Button { super.destroy(); } + _onWorkspaceOrientationChanged() { + let vertical = global.screen.layout_rows == -1; + this.actor.reactive = vertical; + + this._statusLabel.visible = vertical; + this._thumbnailsBox.visible = !vertical; + } + _onWorkspaceSwitched() { this._currentWorkspace = global.screen.get_active_workspace_index(); this._updateMenuOrnament(); + this._updateActiveThumbnail(); this._statusLabel.set_text(this._labelText()); } _nWorkspacesChanged() { this._createWorkspacesSection(); + this._updateThumbnails(); } _updateMenuOrnament() { @@ -86,6 +136,16 @@ class WorkspaceIndicator extends PanelMenu.Button { } } + _updateActiveThumbnail() { + let thumbs = this._thumbnailsBox.get_children(); + for (let i = 0; i < thumbs.length; i++) { + if (i == this._currentWorkspace) + thumbs[i].add_style_class_name('active'); + else + thumbs[i].remove_style_class_name('active'); + } + } + _labelText(workspaceIndex) { if(workspaceIndex == undefined) { workspaceIndex = this._currentWorkspace; @@ -121,6 +181,16 @@ class WorkspaceIndicator extends PanelMenu.Button { this._statusLabel.set_text(this._labelText()); } + _updateThumbnails() { + this._thumbnailsBox.destroy_all_children(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { + let thumb = new WorkspaceThumbnail(i); + this._thumbnailsBox.add_actor(thumb); + } + this._updateActiveThumbnail(); + } + _activate(index) { if(index >= 0 && index < global.screen.n_workspaces) { let metaWorkspace = global.screen.get_workspace_by_index(index); diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css index 1271f1c..5118194 100644 --- a/extensions/workspace-indicator/stylesheet.css +++ b/extensions/workspace-indicator/stylesheet.css @@ -1,5 +1,30 @@ .panel-workspace-indicator { padding: 0 8px; - background-color: rgba(200, 200, 200, .5); +} + +.panel-workspace-indicator-box { + padding: 2px 0; +} + +.panel-workspace-indicator-box .workspace { + width: 40px; +} + +.panel-workspace-indicator, +.panel-workspace-indicator-box .workspace { border: 1px solid #cccccc; } + +.panel-workspace-indicator, +.panel-workspace-indicator-box .workspace.active { + background-color: rgba(200, 200, 200, .5); +} + +.panel-workspace-indicator-box .workspace { + background-color: rgba(200, 200, 200, .3); + border-left-width: 0; +} + +.panel-workspace-indicator-box .workspace:first-child { + border-left-width: 1px; +} -- 2.26.2 From 817f6f2dedc63fdeec2f1fa79c33b427272ddabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 28 Jun 2019 11:33:16 +0200 Subject: [PATCH 30/32] workspace-indicator: Show previews in workspace switcher Currently the new horizontal workspace switcher only shows a series of buttons, with no indication of the workspaces' contents. Go full GNOME 2 and add tiny draggable preview rectangles that represent the windows on a particular workspace. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/77 --- extensions/workspace-indicator/extension.js | 179 +++++++++++++++++- extensions/workspace-indicator/stylesheet.css | 10 + 2 files changed, 188 insertions(+), 1 deletion(-) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 32bb10f..6b656c8 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -6,6 +6,8 @@ const Meta = imports.gi.Meta; const Clutter = imports.gi.Clutter; const St = imports.gi.St; const Mainloop = imports.mainloop; + +const DND = imports.ui.dnd; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const Panel = imports.ui.panel; @@ -22,15 +24,185 @@ const Convenience = Me.imports.convenience; const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; const WORKSPACE_KEY = 'workspace-names'; +let WindowPreview = GObject.registerClass({ + GTypeName: 'WorkspaceIndicatorWindowPreview' +}, class WindowPreview extends St.Button { + _init(window) { + super._init({ + style_class: 'workspace-indicator-window-preview' + }); + + this._delegate = this; + DND.makeDraggable(this, { restoreOnSuccess: true }); + + this._window = window; + + this.connect('destroy', this._onDestroy.bind(this)); + + this._sizeChangedId = this._window.connect('size-changed', + this._relayout.bind(this)); + this._positionChangedId = this._window.connect('position-changed', + this._relayout.bind(this)); + this._minimizedChangedId = this._window.connect('notify::minimized', + this._relayout.bind(this)); + this._monitorEnteredId = global.screen.connect('window-entered-monitor', + this._relayout.bind(this)); + this._monitorLeftId = global.screen.connect('window-left-monitor', + this._relayout.bind(this)); + + // Do initial layout when we get a parent + let id = this.connect('parent-set', () => { + this.disconnect(id); + if (!this.get_parent()) + return; + this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._laterId = 0; + this._relayout(); + return false; + }); + }); + + this._focusChangedId = global.display.connect('notify::focus-window', + this._onFocusChanged.bind(this)); + this._onFocusChanged(); + } + + // needed for DND + get realWindow() { + return this._window.get_compositor_private(); + } + + _onDestroy() { + this._window.disconnect(this._sizeChangedId); + this._window.disconnect(this._positionChangedId); + this._window.disconnect(this._minimizedChangedId); + global.screen.disconnect(this._monitorEnteredId); + global.screen.disconnect(this._monitorLeftId); + global.display.disconnect(this._focusChangedId); + if (this._laterId) + Meta.later_remove(this._laterId); + } + + _onFocusChanged() { + if (global.display.focus_window == this._window) + this.add_style_class_name('active'); + else + this.remove_style_class_name('active'); + } + + _relayout() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && + this._window.showing_on_its_workspace(); + + if (!this.visible) + return; + + let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); + let hscale = this.get_parent().allocation.get_width() / workArea.width; + let vscale = this.get_parent().allocation.get_height() / workArea.height; + + let frameRect = this._window.get_frame_rect(); + this.set_size( + Math.round(Math.min(frameRect.width, workArea.width) * hscale), + Math.round(Math.min(frameRect.height, workArea.height) * vscale)); + this.set_position( + Math.round(frameRect.x * hscale), + Math.round(frameRect.y * vscale)); + } +}); + let WorkspaceThumbnail = GObject.registerClass({ GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' }, class WorkspaceThumbnail extends St.Button { _init(index) { super._init({ style_class: 'workspace', + child: new Clutter.Actor({ + layout_manager: new Clutter.BinLayout(), + clip_to_allocation: true + }), + x_fill: true, + y_fill: true }); + this.connect('destroy', this._onDestroy.bind(this)); + this._index = index; + this._delegate = this; // needed for DND + + this._windowPreviews = new Map(); + + this._workspace = global.screen.get_workspace_by_index(index); + + this._windowAddedId = this._workspace.connect('window-added', + (ws, window) => { + this._addWindow(window); + }); + this._windowRemovedId = this._workspace.connect('window-removed', + (ws, window) => { + this._removeWindow(window); + }); + this._restackedId = global.screen.connect('restacked', + this._onRestacked.bind(this)); + + this._workspace.list_windows().forEach(w => this._addWindow(w)); + this._onRestacked(); + } + + acceptDrop(source) { + if (!source.realWindow) + return false; + + let window = source.realWindow.get_meta_window(); + this._moveWindow(window); + return true; + } + + handleDragOver(source) { + if (source.realWindow) + return DND.DragMotionResult.MOVE_DROP; + else + return DND.DragMotionResult.CONTINUE; + } + + _addWindow(window) { + if (this._windowPreviews.has(window)) + return; + + let preview = new WindowPreview(window); + preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); + this._windowPreviews.set(window, preview); + this.child.add_child(preview); + } + + _removeWindow(window) { + let preview = this._windowPreviews.get(window); + if (!preview) + return; + + this._windowPreviews.delete(window); + preview.destroy(); + } + + _onRestacked() { + let lastPreview = null; + let windows = global.get_window_actors().map(a => a.meta_window); + for (let i = 0; i < windows.length; i++) { + let preview = this._windowPreviews.get(windows[i]); + if (!preview) + continue; + + this.child.set_child_above_sibling(preview, lastPreview); + lastPreview = preview; + } + } + + _moveWindow(window) { + let monitorIndex = Main.layoutManager.findIndexForActor(this); + if (monitorIndex != window.get_monitor()) + window.move_to_monitor(monitorIndex); + window.change_workspace_by_index(this._index, false); } on_clicked() { @@ -38,8 +210,13 @@ let WorkspaceThumbnail = GObject.registerClass({ if (ws) ws.activate(global.get_current_time()); } -}); + _onDestroy() { + this._workspace.disconnect(this._windowAddedId); + this._workspace.disconnect(this._windowRemovedId); + global.screen.disconnect(this._restackedId); + } +}); class WorkspaceIndicator extends PanelMenu.Button { constructor() { diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css index 5118194..8601c3e 100644 --- a/extensions/workspace-indicator/stylesheet.css +++ b/extensions/workspace-indicator/stylesheet.css @@ -28,3 +28,13 @@ .panel-workspace-indicator-box .workspace:first-child { border-left-width: 1px; } + +.workspace-indicator-window-preview { + background-color: #252525; + border: 1px solid #ccc; +} + +.workspace-indicator-window-preview.active { + background-color: #353535; + border: 2px solid #ccc; +} -- 2.26.2 From c4e44e23e945f299ba75b5509b6bf84fcc95d8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 6 Sep 2019 20:41:23 +0200 Subject: [PATCH 31/32] window-list: Exclude DESKTOP windows from window previews While nautilus removed its desktop support a while ago in favor of an extension, it's still possible that some external X11 desktop icon app is used. As DESKTOP windows cannot be moved between workspaces or stacked, and aren't perceived as regular windows, it doesn't make sense to show them as previews in the workspace switcher. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/93 --- extensions/window-list/workspaceIndicator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js index 7caad52..527c201 100644 --- a/extensions/window-list/workspaceIndicator.js +++ b/extensions/window-list/workspaceIndicator.js @@ -78,6 +78,7 @@ let WindowPreview = GObject.registerClass({ _relayout() { let monitor = Main.layoutManager.findIndexForActor(this); this.visible = monitor == this._window.get_monitor() && + this._window.window_type !== Meta.WindowType.DESKTOP && this._window.showing_on_its_workspace(); if (!this.visible) -- 2.26.2 From 0ea05d6ff2c926b4bb3fec4268ae0e84230d3e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 6 Sep 2019 20:47:56 +0200 Subject: [PATCH 32/32] workspace-indicator: Exclude DESKTOP windows from window previews While nautilus removed its desktop support a while ago in favor of an extension, it's still possible that some external X11 desktop icon app is used. As DESKTOP windows cannot be moved between workspaces or stacked, and aren't perceived as regular windows, it doesn't make sense to show them as previews in the workspace switcher. https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/93 --- extensions/workspace-indicator/extension.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js index 6b656c8..6b7947d 100644 --- a/extensions/workspace-indicator/extension.js +++ b/extensions/workspace-indicator/extension.js @@ -93,6 +93,7 @@ let WindowPreview = GObject.registerClass({ _relayout() { let monitor = Main.layoutManager.findIndexForActor(this); this.visible = monitor == this._window.get_monitor() && + this._window.window_type !== Meta.WindowType.DESKTOP && this._window.showing_on_its_workspace(); if (!this.visible) -- 2.26.2