From 10acbf6b7508d4d9c786f7256afd309d9751d8cf Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 16 May 2023 06:08:25 +0000 Subject: [PATCH] import gnome-shell-3.32.2-50.el8 --- .gitignore | 1 + .gnome-shell.metadata | 1 + ...-not-change-Wacom-LEDs-through-g-s-d.patch | 25 + ...0001-a11y-Change-HC-icon-theme-first.patch | 58 + ...intentional-loop-while-polkit-dialog.patch | 40 + ...-to-window-title-instead-of-WM_CLASS.patch | 28 + ...pDisplay-Show-full-app-name-on-hover.patch | 111 + ...und-refresh-after-suspend-on-wayland.patch | 29 + ...ll-process-working.svg-to-filesystem.patch | 37 + ...educe-calls-to-g_time_zone_new_local.patch | 65 + ...Handle-added-or-removed-sessionMode-.patch | 87 + ...ns-Add-a-SESSION_MODE-extension-type.patch | 42 + SOURCES/0001-gdm-add-AuthList-control.patch | 237 + ...able-keyboard-if-ClutterDeviceManage.patch | 39 + ...t-Initialize-regions-unconditionally.patch | 51 + ...-layout-Make-the-hot-corner-optional.patch | 56 + ...t-auth-prompt-on-vt-switch-before-fa.patch | 49 + ...oginDialog-make-info-messages-themed.patch | 32 + ...n-Dump-stack-on-segfaults-by-default.patch | 38 + ...ight-prevFocus-actor-after-the-focus.patch | 49 + ...orkAgent-add-support-for-SAE-secrets.patch | 29 + ...ry-action-labels-after-mode-switches.patch | 80 + ...-add-an-icon-to-the-ActivitiesButton.patch | 54 + ...andle-keypress-if-numlock-is-enabled.patch | 36 + ...eld-unblank-when-inserting-smartcard.patch | 33 + ...recording-when-screen-size-or-resour.patch | 253 ++ ...-workspace-from-startup-notification.patch | 78 + ...ll-recorder-Restore-cursor-recording.patch | 28 + ...DBus-method-to-load-a-single-extensi.patch | 66 + ...mine-if-password-entry-from-content-.patch | 91 + ...lEntry-Disconnect-handler-on-destroy.patch | 36 + ...st_bin_set_child-with-already-parent.patch | 59 + ...-Cancel-pending-requests-on-icon-the.patch | 94 + ...status-volume-Hide-sliders-initially.patch | 30 + ...01-theme-Update-window-preview-style.patch | 76 + ...-back-workspaces-submenu-for-static-.patch | 45 + ...1-workspace-Pass-device-to-startDrag.patch | 31 + ...ork-around-spurious-allocation-chang.patch | 37 + ...ld-background-not-just-animation-on-.patch | 31 + ...0002-environment-Fix-date-conversion.patch | 33 + ...Get-rid-of-_enabled-boolean-optimiza.patch | 237 + ...upport-for-GDM-s-ChoiceList-PAM-exte.patch | 267 ++ ...password-menu-item-text-when-it-s-cr.patch | 92 + ...Allow-extensions-to-run-on-the-login.patch | 393 ++ ...ystem-Monitor-for-icon-theme-changes.patch | 152 + ...e-password-item-from-dedication-func.patch | 119 + ...003-st-texture-cache-purge-on-resume.patch | 66 + ...sh-background-on-gl-video-memory-pur.patch | 115 + ...nc-to-worker-thread-when-saving-stat.patch | 124 + ...w-extensions-at-the-login-and-unlock.patch | 116 + ...rt-lockdown-of-Show-Text-menu-in-pas.patch | 116 + ...d-ShellAppCache-for-GAppInfo-caching.patch | 674 +++ ...y-Only-mask-text-in-password-entries.patch | 36 + ...-Always-use-AppSystem-to-lookup-apps.patch | 66 + .../allow-timed-login-with-no-user-list.patch | 159 + SOURCES/caps-lock-warning.patch | 488 +++ ...defend-against-corrupt-notifications.patch | 145 + .../disable-unlock-entry-until-question.patch | 167 + SOURCES/enforce-smartcard-at-unlock.patch | 114 + SOURCES/extension-updates.patch | 3810 +++++++++++++++++ SOURCES/fix-app-view-leaks.patch | 421 ++ SOURCES/fix-double-disposed-backgrounds.patch | 101 + SOURCES/fix-invalid-access-warnings.patch | 224 + SOURCES/fix-login-lock-screen.patch | 161 + SOURCES/fix-nm-device-settings.patch | 131 + SOURCES/fix-some-js-warnings.patch | 223 + SOURCES/gdm-networking.patch | 246 ++ .../gnome-shell-favourite-apps-firefox.patch | 38 + .../gnome-shell-favourite-apps-terminal.patch | 25 + SOURCES/gnome-shell-favourite-apps-yelp.patch | 26 + SOURCES/horizontal-workspace-support.patch | 399 ++ SOURCES/introspect-backports.patch | 643 +++ .../more-spurious-allocation-warnings.patch | 176 + SOURCES/osk-fixes.patch | 117 + SOURCES/perf-tool-wayland.patch | 399 ++ SOURCES/root-warning.patch | 71 + SOURCES/wake-up-on-deactivate.patch | 79 + SOURCES/warn-less.patch | 279 ++ SPECS/gnome-shell.spec | 1510 +++++++ 79 files changed, 15020 insertions(+) create mode 100644 .gitignore create mode 100644 .gnome-shell.metadata create mode 100644 SOURCES/0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch create mode 100644 SOURCES/0001-a11y-Change-HC-icon-theme-first.patch create mode 100644 SOURCES/0001-animation-fix-unintentional-loop-while-polkit-dialog.patch create mode 100644 SOURCES/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch create mode 100644 SOURCES/0001-appDisplay-Show-full-app-name-on-hover.patch create mode 100644 SOURCES/0001-background-refresh-after-suspend-on-wayland.patch create mode 100644 SOURCES/0001-data-install-process-working.svg-to-filesystem.patch create mode 100644 SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch create mode 100644 SOURCES/0001-extensionSystem-Handle-added-or-removed-sessionMode-.patch create mode 100644 SOURCES/0001-extensions-Add-a-SESSION_MODE-extension-type.patch create mode 100644 SOURCES/0001-gdm-add-AuthList-control.patch create mode 100644 SOURCES/0001-keyboard-Only-enable-keyboard-if-ClutterDeviceManage.patch create mode 100644 SOURCES/0001-layout-Initialize-regions-unconditionally.patch create mode 100644 SOURCES/0001-layout-Make-the-hot-corner-optional.patch create mode 100644 SOURCES/0001-loginDialog-Reset-auth-prompt-on-vt-switch-before-fa.patch create mode 100644 SOURCES/0001-loginDialog-make-info-messages-themed.patch create mode 100644 SOURCES/0001-main-Dump-stack-on-segfaults-by-default.patch create mode 100644 SOURCES/0001-main-Unset-the-right-prevFocus-actor-after-the-focus.patch create mode 100644 SOURCES/0001-networkAgent-add-support-for-SAE-secrets.patch create mode 100644 SOURCES/0001-padOsd-Re-query-action-labels-after-mode-switches.patch create mode 100644 SOURCES/0001-panel-add-an-icon-to-the-ActivitiesButton.patch create mode 100644 SOURCES/0001-popupMenu-Handle-keypress-if-numlock-is-enabled.patch create mode 100644 SOURCES/0001-screenShield-unblank-when-inserting-smartcard.patch create mode 100644 SOURCES/0001-screencast-Stop-recording-when-screen-size-or-resour.patch create mode 100644 SOURCES/0001-shell-app-Handle-workspace-from-startup-notification.patch create mode 100644 SOURCES/0001-shell-recorder-Restore-cursor-recording.patch create mode 100644 SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch create mode 100644 SOURCES/0001-shellEntry-Determine-if-password-entry-from-content-.patch create mode 100644 SOURCES/0001-shellEntry-Disconnect-handler-on-destroy.patch create mode 100644 SOURCES/0001-st-bin-Disallow-st_bin_set_child-with-already-parent.patch create mode 100644 SOURCES/0001-st-texture-cache-Cancel-pending-requests-on-icon-the.patch create mode 100644 SOURCES/0001-status-volume-Hide-sliders-initially.patch create mode 100644 SOURCES/0001-theme-Update-window-preview-style.patch create mode 100644 SOURCES/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch create mode 100644 SOURCES/0001-workspace-Pass-device-to-startDrag.patch create mode 100644 SOURCES/0001-workspacesView-Work-around-spurious-allocation-chang.patch create mode 100644 SOURCES/0002-background-rebuild-background-not-just-animation-on-.patch create mode 100644 SOURCES/0002-environment-Fix-date-conversion.patch create mode 100644 SOURCES/0002-extensionSystem-Get-rid-of-_enabled-boolean-optimiza.patch create mode 100644 SOURCES/0002-gdmUtil-enable-support-for-GDM-s-ChoiceList-PAM-exte.patch create mode 100644 SOURCES/0002-shellEntry-Give-password-menu-item-text-when-it-s-cr.patch create mode 100644 SOURCES/0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch create mode 100644 SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch create mode 100644 SOURCES/0003-shellEntry-Handle-password-item-from-dedication-func.patch create mode 100644 SOURCES/0003-st-texture-cache-purge-on-resume.patch create mode 100644 SOURCES/0004-background-refresh-background-on-gl-video-memory-pur.patch create mode 100644 SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch create mode 100644 SOURCES/0004-sessionMode-Allow-extensions-at-the-login-and-unlock.patch create mode 100644 SOURCES/0004-shellEntry-Support-lockdown-of-Show-Text-menu-in-pas.patch create mode 100644 SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch create mode 100644 SOURCES/0005-shellEntry-Only-mask-text-in-password-entries.patch create mode 100644 SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch create mode 100644 SOURCES/allow-timed-login-with-no-user-list.patch create mode 100644 SOURCES/caps-lock-warning.patch create mode 100644 SOURCES/defend-against-corrupt-notifications.patch create mode 100644 SOURCES/disable-unlock-entry-until-question.patch create mode 100644 SOURCES/enforce-smartcard-at-unlock.patch create mode 100644 SOURCES/extension-updates.patch create mode 100644 SOURCES/fix-app-view-leaks.patch create mode 100644 SOURCES/fix-double-disposed-backgrounds.patch create mode 100644 SOURCES/fix-invalid-access-warnings.patch create mode 100644 SOURCES/fix-login-lock-screen.patch create mode 100644 SOURCES/fix-nm-device-settings.patch create mode 100644 SOURCES/fix-some-js-warnings.patch create mode 100644 SOURCES/gdm-networking.patch create mode 100644 SOURCES/gnome-shell-favourite-apps-firefox.patch create mode 100644 SOURCES/gnome-shell-favourite-apps-terminal.patch create mode 100644 SOURCES/gnome-shell-favourite-apps-yelp.patch create mode 100644 SOURCES/horizontal-workspace-support.patch create mode 100644 SOURCES/introspect-backports.patch create mode 100644 SOURCES/more-spurious-allocation-warnings.patch create mode 100644 SOURCES/osk-fixes.patch create mode 100644 SOURCES/perf-tool-wayland.patch create mode 100644 SOURCES/root-warning.patch create mode 100644 SOURCES/wake-up-on-deactivate.patch create mode 100644 SOURCES/warn-less.patch create mode 100644 SPECS/gnome-shell.spec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e984bcc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-shell-3.32.2.tar.xz diff --git a/.gnome-shell.metadata b/.gnome-shell.metadata new file mode 100644 index 0000000..06e249f --- /dev/null +++ b/.gnome-shell.metadata @@ -0,0 +1 @@ +331e9cf71cd1d2a4e9238d87d216da4c6f3a400e SOURCES/gnome-shell-3.32.2.tar.xz diff --git a/SOURCES/0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch b/SOURCES/0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch new file mode 100644 index 0000000..9eb76be --- /dev/null +++ b/SOURCES/0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch @@ -0,0 +1,25 @@ +From f5ddd0fc02e99597e4b8506ac35523a6fa8ac22f Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Wed, 4 Mar 2020 16:08:31 +0100 +Subject: [PATCH] Do not change Wacom LEDs through g-s-d + +Let the wacom kernel driver sort it out by itself. +--- + js/ui/windowManager.js | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js +index dfe1b44..b2e938c 100644 +--- a/js/ui/windowManager.js ++++ b/js/ui/windowManager.js +@@ -1037,7 +1037,6 @@ var WindowManager = class { + + if (this._gsdWacomProxy) { + this._gsdWacomProxy.SetOLEDLabelsRemote(pad.get_device_node(), labels); +- this._gsdWacomProxy.SetGroupModeLEDRemote(pad.get_device_node(), group, mode); + } + }); + +-- +2.24.1 + diff --git a/SOURCES/0001-a11y-Change-HC-icon-theme-first.patch b/SOURCES/0001-a11y-Change-HC-icon-theme-first.patch new file mode 100644 index 0000000..f5eebd6 --- /dev/null +++ b/SOURCES/0001-a11y-Change-HC-icon-theme-first.patch @@ -0,0 +1,58 @@ +From a94260b4f2f72ea9328a0194b8656f1fb3e98675 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 14 Dec 2019 19:15:53 +0100 +Subject: [PATCH] a11y: Change HC icon theme first + +There are two ways for applications to provide a high contrast icon: + + 1. install an icon into the HighContrast theme + 2. install a symbolic icon into the default hicolor theme + +The latter is preferred nowadays, and implemented in the high-contrast +CSS variant by enforcing the symbolic icon style. + +However together with the way we currently enable/disable high-contrast, +this can lead to the following race: + 1. the GTK theme is changed from HighContrast + 2. we reload the default stylesheet + 3. the icon style changes to "regular", so we request a + new icon from the HighContrast icon theme + 4. the icon theme is changed from HighContrast + 5. we evict existing icons from the cache + 6. we reload icons for the new icon theme; however as we + find a pending request (from 3), we re-use it + 7. the request from 3 finishes, and we end up with a + wrong icon in the cache + +The simplest fix is to change the icon theme before the GTK theme: Unlike the +theme name, the icon style is encoded in the cache key, so we won't re-use +an old (and incorrect) request in that case. +--- + js/ui/status/accessibility.js | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/js/ui/status/accessibility.js b/js/ui/status/accessibility.js +index 10223ec84..90948d465 100644 +--- a/js/ui/status/accessibility.js ++++ b/js/ui/status/accessibility.js +@@ -154,14 +154,14 @@ class ATIndicator extends PanelMenu.Button { + interfaceSettings.is_writable(KEY_ICON_THEME), + enabled => { + if (enabled) { +- interfaceSettings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME); + interfaceSettings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME); ++ interfaceSettings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME); + } else if(!hasHC) { +- interfaceSettings.set_string(KEY_GTK_THEME, gtkTheme); + interfaceSettings.set_string(KEY_ICON_THEME, iconTheme); ++ interfaceSettings.set_string(KEY_GTK_THEME, gtkTheme); + } else { +- interfaceSettings.reset(KEY_GTK_THEME); + interfaceSettings.reset(KEY_ICON_THEME); ++ interfaceSettings.reset(KEY_GTK_THEME); + } + }); + return highContrast; +-- +2.23.0 + diff --git a/SOURCES/0001-animation-fix-unintentional-loop-while-polkit-dialog.patch b/SOURCES/0001-animation-fix-unintentional-loop-while-polkit-dialog.patch new file mode 100644 index 0000000..748b73f --- /dev/null +++ b/SOURCES/0001-animation-fix-unintentional-loop-while-polkit-dialog.patch @@ -0,0 +1,40 @@ +From 34e6bbeebef37ae688ca0527bde03fa26b143bb7 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 27 Jun 2019 14:27:34 -0400 +Subject: [PATCH] animation: fix unintentional loop while polkit dialog is + active + +The polkit password dialog has a spinner that gets displayed +while the users password is being verified. + +Unfortunately, the spinner stop method unintentionally calls +back into itself after the stop fade out animation is complete. +The stop method is called at startup, so the looping begins as +soon as the dialog is visible and continues until the dialog is +dismissed. + +This commit fixes the loop by having the stop method cease +calling itself, and instead having it call the stop method on the +superclass. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/602 +--- + js/ui/animation.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/animation.js b/js/ui/animation.js +index c21b22565..58d7f4018 100644 +--- a/js/ui/animation.js ++++ b/js/ui/animation.js +@@ -162,7 +162,7 @@ var Spinner = class extends AnimatedIcon { + time: SPINNER_ANIMATION_TIME, + transition: 'linear', + onComplete: () => { +- this.stop(false); ++ super.stop(); + } + }); + } else { +-- +2.21.0 + diff --git a/SOURCES/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch b/SOURCES/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch new file mode 100644 index 0000000..77f7cb1 --- /dev/null +++ b/SOURCES/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch @@ -0,0 +1,28 @@ +From 23755cc20f3c05b97f769e27553f2ab482d60137 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 20 May 2015 16:44:00 +0200 +Subject: [PATCH] app: Fall back to window title instead of WM_CLASS + +It's a bad fallback as it's clearly window-specific (rather than +app-specific), but it likely looks prettier when we fail to associate +a .desktop file ... +--- + src/shell-app.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shell-app.c b/src/shell-app.c +index 10efa9135..7d40186c9 100644 +--- a/src/shell-app.c ++++ b/src/shell-app.c +@@ -259,7 +259,7 @@ shell_app_get_name (ShellApp *app) + const char *name = NULL; + + if (window) +- name = meta_window_get_wm_class (window); ++ name = meta_window_get_title (window); + if (!name) + name = C_("program", "Unknown"); + return name; +-- +2.21.0 + diff --git a/SOURCES/0001-appDisplay-Show-full-app-name-on-hover.patch b/SOURCES/0001-appDisplay-Show-full-app-name-on-hover.patch new file mode 100644 index 0000000..ddd3d97 --- /dev/null +++ b/SOURCES/0001-appDisplay-Show-full-app-name-on-hover.patch @@ -0,0 +1,111 @@ +From a1c35ebb8f29103035526e6f48eba4ff37551964 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 21 Jun 2018 18:03:31 +0200 +Subject: [PATCH] appDisplay: Show full app name on hover + +--- + data/theme/gnome-shell-sass/_common.scss | 8 ++++ + js/ui/appDisplay.js | 48 ++++++++++++++++++++++++ + 2 files changed, 56 insertions(+) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 3b0d2bf04..293ea2ab9 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -1411,6 +1411,14 @@ StScrollBar { + + } + ++ .app-well-hover-text { ++ text-align: center; ++ color: $osd_fg_color; ++ background-color: $osd_bg_color; ++ border-radius: 5px; ++ padding: 3px; ++ } ++ + .app-well-app-running-dot { //running apps indicator + width: 10px; height: 3px; + background-color: $selected_bg_color; +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index adaefa7dd..a07db6573 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1478,6 +1478,20 @@ var AppIcon = class AppIcon { + this.actor.connect('clicked', this._onClicked.bind(this)); + this.actor.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); + ++ this._hoverText = null; ++ this._hoverTimeoutId = 0; ++ ++ if (this.icon.label) { ++ this._hoverText = new St.Label({ style_class: 'app-well-hover-text', ++ text: this.icon.label.text, ++ visible: false }); ++ this._hoverText.clutter_text.line_wrap = true; ++ Main.layoutManager.addChrome(this._hoverText); ++ ++ this.actor.connect('notify::hover', this._syncHoverText.bind(this)); ++ this.connect('sync-tooltip', this._syncHoverText.bind(this)); ++ } ++ + this._menu = null; + this._menuManager = new PopupMenu.PopupMenuManager(this); + +@@ -1509,12 +1523,39 @@ var AppIcon = class AppIcon { + this.app.disconnect(this._stateChangedId); + this._stateChangedId = 0; + this._removeMenuTimeout(); ++ this._removeHoverTimeout(); ++ if (this._hoverText) ++ this._hoverText.destroy(); ++ this._hoverText = null; + } + + _createIcon(iconSize) { + return this.app.create_icon_texture(iconSize); + } + ++ _syncHoverText() { ++ if (this.shouldShowTooltip()) { ++ if (this._hoverTimeoutId) ++ return; ++ ++ this._hoverTimeoutId = Mainloop.timeout_add(300, () => { ++ this._hoverText.style = `max-width: ${2 * this.icon.iconSize}px;`; ++ this._hoverText.ensure_style(); ++ ++ let [x, y] = this.icon.label.get_transformed_position(); ++ let offset = (this._hoverText.width - this.icon.label.width) / 2; ++ this._hoverText.set_position(Math.floor(x - offset), Math.floor(y)); ++ this._hoverText.show(); ++ ++ this._hoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ } else { ++ this._removeHoverTimeout(); ++ this._hoverText.hide(); ++ } ++ } ++ + _removeMenuTimeout() { + if (this._menuTimeoutId > 0) { + Mainloop.source_remove(this._menuTimeoutId); +@@ -1522,6 +1563,13 @@ var AppIcon = class AppIcon { + } + } + ++ _removeHoverTimeout() { ++ if (this._hoverTimeoutId > 0) { ++ Mainloop.source_remove(this._hoverTimeoutId); ++ this._hoverTimeoutId = 0; ++ } ++ } ++ + _updateRunningStyle() { + if (this.app.state != Shell.AppState.STOPPED) + this._dot.show(); +-- +2.21.0 + diff --git a/SOURCES/0001-background-refresh-after-suspend-on-wayland.patch b/SOURCES/0001-background-refresh-after-suspend-on-wayland.patch new file mode 100644 index 0000000..ffcbca0 --- /dev/null +++ b/SOURCES/0001-background-refresh-after-suspend-on-wayland.patch @@ -0,0 +1,29 @@ +From 165fc5147cd2c9bf4bc10a1c5a9a940ec4ddd8d9 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 15 Jan 2019 12:51:16 -0500 +Subject: [PATCH 1/4] background: refresh after suspend on wayland + +At the moment we only refresh after suspend on Xorg. + +We need to do it on wayland, too. +--- + src/shell-util.c | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/src/shell-util.c b/src/shell-util.c +index 31bb18e70..c6e5abed6 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -395,9 +395,6 @@ get_gl_vendor (void) + gboolean + shell_util_need_background_refresh (void) + { +- if (!clutter_check_windowing_backend (CLUTTER_WINDOWING_X11)) +- return FALSE; +- + if (g_strcmp0 (get_gl_vendor (), "NVIDIA Corporation") == 0) + return TRUE; + +-- +2.21.0 + diff --git a/SOURCES/0001-data-install-process-working.svg-to-filesystem.patch b/SOURCES/0001-data-install-process-working.svg-to-filesystem.patch new file mode 100644 index 0000000..9782372 --- /dev/null +++ b/SOURCES/0001-data-install-process-working.svg-to-filesystem.patch @@ -0,0 +1,37 @@ +From 1dcae7bbba222a1c8bdfc2d76a9f716e638b0334 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 8 Jun 2017 12:04:31 -0400 +Subject: [PATCH] data: install process-working.svg to filesystem + +This helps prevent unlock failure on inplace upgrades between +7.3 and 7.4 +--- + data/theme/meson.build | 2 ++ + meson.build | 1 + + 2 files changed, 3 insertions(+) + +diff --git a/data/theme/meson.build b/data/theme/meson.build +index 22bae3dd2..d5acb8d10 100644 +--- a/data/theme/meson.build ++++ b/data/theme/meson.build +@@ -23,3 +23,5 @@ foreach style: styles + ], + depend_files: theme_sources) + endforeach ++ ++install_data('process-working.svg', install_dir: themedir) +diff --git a/meson.build b/meson.build +index 21a80bcc8..0acaba705 100644 +--- a/meson.build ++++ b/meson.build +@@ -57,6 +57,7 @@ localedir = join_paths(datadir, 'locale') + portaldir = join_paths(datadir, 'xdg-desktop-portal', 'portals') + schemadir = join_paths(datadir, 'glib-2.0', 'schemas') + servicedir = join_paths(datadir, 'dbus-1', 'services') ++themedir = join_paths(pkgdatadir, 'theme') + + # XXX: Once https://github.com/systemd/systemd/issues/9595 is fixed and we can + # depend on this version, replace with something like: +-- +2.21.0 + diff --git a/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch b/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch new file mode 100644 index 0000000..3528e9d --- /dev/null +++ b/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch @@ -0,0 +1,65 @@ +From 6e80934456f0b4cc48da6a7201700dc4386a3474 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 27 Feb 2020 13:46:44 -0800 +Subject: [PATCH] environment: reduce calls to g_time_zone_new_local() + +Creating a new GTimeZone for the local timezone can be quite expensive if +done repeatedly. It requires an open(), mmap(), and parsing of +/etc/localtime. + +This patch was provided by Florian, and I've tested it as far back as +3.28.4 to ensure that we are really reducing the number of open() calls +on the compositor thread. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1051 + +Signed-off-by: Christian Hergert +--- + js/ui/environment.js | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/js/ui/environment.js b/js/ui/environment.js +index 9c125d3eb..809b48e45 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -11,6 +11,9 @@ imports.gi.versions.TelepathyLogger = '0.2'; + + const { Clutter, Gio, GLib, Shell, St } = imports.gi; + const Gettext = imports.gettext; ++const System = imports.system; ++ ++let _localTimeZone = null; + + // We can't import shell JS modules yet, because they may have + // variable initializations, etc, that depend on init() already having +@@ -117,9 +120,26 @@ function init() { + } + }; + ++ // Override to clear our own timezone cache as well ++ const origClearDateCaches = System.clearDateCaches; ++ System.clearDateCaches = function () { ++ _localTimeZone = null; ++ origClearDateCaches(); ++ }; ++ + // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783 + Date.prototype.toLocaleFormat = function(format) { +- return Shell.util_format_date(format, this.getTime()); ++ if (_localTimeZone === null) ++ _localTimeZone = GLib.TimeZone.new_local(); ++ ++ let dt = GLib.DateTime.new(_localTimeZone, ++ this.getYear(), ++ this.getMonth() + 1, ++ this.getDate(), ++ this.getHours(), ++ this.getMinutes(), ++ this.getSeconds()); ++ return dt ? dt.format(format) : ''; + }; + + let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR'); +-- +2.31.1 + diff --git a/SOURCES/0001-extensionSystem-Handle-added-or-removed-sessionMode-.patch b/SOURCES/0001-extensionSystem-Handle-added-or-removed-sessionMode-.patch new file mode 100644 index 0000000..2a8ff21 --- /dev/null +++ b/SOURCES/0001-extensionSystem-Handle-added-or-removed-sessionMode-.patch @@ -0,0 +1,87 @@ +From 1b6eb29ade832647510b36ddc13c9b88a25036df Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Sep 2019 20:18:20 +0200 +Subject: [PATCH 1/4] extensionSystem: Handle added or removed sessionMode + extensions + +Right now we're only handling added sessionMode extensions correctly on +sessionMode updates, also handle the other case and disable removed +sessionMode extensions on sessionMode updates. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/96 +--- + js/ui/extensionSystem.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 81804ea5e..77929f2a6 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -515,62 +515,62 @@ var ExtensionManager = class { + if (!this._initted) { + this._loadExtensions(); + this._initted = true; + } else { + this._enabledExtensions.forEach(uuid => { + this._callExtensionEnable(uuid); + }); + } + this._enabled = true; + } + + _disableAllExtensions() { + if (!this._enabled) + return; + + if (this._initted) { + this._extensionOrder.slice().reverse().forEach(uuid => { + this._callExtensionDisable(uuid); + }); + } + + this._enabled = false; + } + + _sessionUpdated() { + // For now sessionMode.allowExtensions controls extensions from both the + // 'enabled-extensions' preference and the sessionMode.enabledExtensions + // property; it might make sense to make enabledExtensions independent + // from allowExtensions in the future + if (Main.sessionMode.allowExtensions) { +- if (this._initted) +- this._enabledExtensions = this._getEnabledExtensions(); ++ // Take care of added or removed sessionMode extensions ++ this._onEnabledExtensionsChanged(); + this._enableAllExtensions(); + } else { + this._disableAllExtensions(); + } + } + }; + Signals.addSignalMethods(ExtensionManager.prototype); + + class ExtensionUpdateSource extends MessageTray.Source { + constructor() { + const appSys = Shell.AppSystem.get_default(); + this._app = appSys.lookup_app('gnome-shell-extension-prefs.desktop'); + + super(this._app.get_name()); + } + + getIcon() { + return this._app.app_info.get_icon(); + } + + _createPolicy() { + return new MessageTray.NotificationApplicationPolicy(this._app.id); + } + + open() { + this._app.activate(); + Main.overview.hide(); + Main.panel.closeCalendar(); + } + } +-- +2.27.0 + diff --git a/SOURCES/0001-extensions-Add-a-SESSION_MODE-extension-type.patch b/SOURCES/0001-extensions-Add-a-SESSION_MODE-extension-type.patch new file mode 100644 index 0000000..dc7619b --- /dev/null +++ b/SOURCES/0001-extensions-Add-a-SESSION_MODE-extension-type.patch @@ -0,0 +1,42 @@ +From 720eb83ba0b0e5e37185d7e7ed86fe9175cf18f4 Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Fri, 8 Nov 2013 13:58:09 +0100 +Subject: [PATCH] extensions: Add a SESSION_MODE extension type + +This allows e.g. gnome-tweak-tool to present these extensions in a +different way since they can't be disabled. +--- + js/misc/extensionUtils.js | 3 ++- + js/ui/extensionSystem.js | 2 ++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index cf308b31f..fb1e2b506 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -13,7 +13,8 @@ const FileUtils = imports.misc.fileUtils; + + var ExtensionType = { + SYSTEM: 1, +- PER_USER: 2 ++ PER_USER: 2, ++ SESSION_MODE: 3 + }; + + // Maps uuid -> metadata object +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 6244c39b4..9ffdb4f3d 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -322,6 +322,8 @@ function _loadExtensions() { + let finder = new ExtensionUtils.ExtensionFinder(); + finder.connect('extension-found', (finder, extension) => { + loadExtension(extension); ++ if (Main.sessionMode.enabledExtensions.indexOf(extension.uuid) != -1) ++ extension.type = ExtensionUtils.ExtensionType.SESSION_MODE; + }); + finder.scanExtensions(); + } +-- +2.21.0 + diff --git a/SOURCES/0001-gdm-add-AuthList-control.patch b/SOURCES/0001-gdm-add-AuthList-control.patch new file mode 100644 index 0000000..2bd503b --- /dev/null +++ b/SOURCES/0001-gdm-add-AuthList-control.patch @@ -0,0 +1,237 @@ +From 592bf9b4ba879a365375a7edcb6c48258386e413 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 18 Jul 2017 12:58:14 -0400 +Subject: [PATCH 1/2] gdm: add AuthList control + +Ultimately, we want to add support for GDM's new ChoiceList +PAM extension. That extension allows PAM modules to present +a list of choices to the user. Before we can support that +extension, however, we need to have a list control in the +login-screen/unlock screen. This commit adds that control. + +For the most part, it's a copy-and-paste of the gdm userlist, +but with less features. It lacks API specific to the users, +lacks the built in timed login indicator, etc. It does feature +a label heading. +--- + js/gdm/authList.js | 195 ++++++++++++++++++++++++++++++++++ + js/js-resources.gresource.xml | 1 + + 2 files changed, 196 insertions(+) + create mode 100644 js/gdm/authList.js + +diff --git a/js/gdm/authList.js b/js/gdm/authList.js +new file mode 100644 +index 000000000..fc1c3d6e4 +--- /dev/null ++++ b/js/gdm/authList.js +@@ -0,0 +1,195 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++/* ++ * Copyright 2017 Red Hat, Inc ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++const Clutter = imports.gi.Clutter; ++const GObject = imports.gi.GObject; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Signals = imports.signals; ++const St = imports.gi.St; ++ ++const Tweener = imports.ui.tweener; ++ ++const _SCROLL_ANIMATION_TIME = 0.5; ++ ++const AuthListItem = new Lang.Class({ ++ Name: 'AuthListItem', ++ ++ _init(key, text) { ++ this.key = key; ++ let label = new St.Label({ style_class: 'auth-list-item-label', ++ y_align: Clutter.ActorAlign.CENTER }); ++ label.text = text; ++ ++ this.actor = new St.Button({ style_class: 'login-dialog-user-list-item', ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ can_focus: true, ++ child: label, ++ reactive: true, ++ x_align: St.Align.START, ++ x_fill: true }); ++ ++ this.actor.connect('key-focus-in', () => { ++ this._setSelected(true); ++ }); ++ this.actor.connect('key-focus-out', () => { ++ this._setSelected(false); ++ }); ++ this.actor.connect('notify::hover', () => { ++ this._setSelected(this.actor.hover); ++ }); ++ ++ this.actor.connect('clicked', this._onClicked.bind(this)); ++ }, ++ ++ _onClicked() { ++ this.emit('activate'); ++ }, ++ ++ _setSelected(selected) { ++ if (selected) { ++ this.actor.add_style_pseudo_class('selected'); ++ this.actor.grab_key_focus(); ++ } else { ++ this.actor.remove_style_pseudo_class('selected'); ++ } ++ } ++}); ++Signals.addSignalMethods(AuthListItem.prototype); ++ ++var AuthList = new Lang.Class({ ++ Name: 'AuthList', ++ ++ _init() { ++ this.actor = new St.BoxLayout({ vertical: true, ++ style_class: 'login-dialog-auth-list-layout' }); ++ ++ this.label = new St.Label({ style_class: 'prompt-dialog-headline' }); ++ this.actor.add_actor(this.label); ++ ++ this._scrollView = new St.ScrollView({ style_class: 'login-dialog-user-list-view'}); ++ this._scrollView.set_policy(Gtk.PolicyType.NEVER, ++ Gtk.PolicyType.AUTOMATIC); ++ this.actor.add_actor(this._scrollView); ++ ++ this._box = new St.BoxLayout({ vertical: true, ++ style_class: 'login-dialog-user-list', ++ pseudo_class: 'expanded' }); ++ ++ this._scrollView.add_actor(this._box); ++ this._items = {}; ++ ++ this.actor.connect('key-focus-in', this._moveFocusToItems.bind(this)); ++ }, ++ ++ _moveFocusToItems() { ++ let hasItems = Object.keys(this._items).length > 0; ++ ++ if (!hasItems) ++ return; ++ ++ if (global.stage.get_key_focus() != this.actor) ++ return; ++ ++ let focusSet = this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ if (!focusSet) { ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this._moveFocusToItems(); ++ return false; ++ }); ++ } ++ }, ++ ++ _onItemActivated(activatedItem) { ++ this.emit('activate', activatedItem.key); ++ }, ++ ++ scrollToItem(item) { ++ let box = item.actor.get_allocation_box(); ++ ++ let adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); ++ ++ let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0); ++ Tweener.removeTweens(adjustment); ++ Tweener.addTween (adjustment, ++ { value: value, ++ time: _SCROLL_ANIMATION_TIME, ++ transition: 'easeOutQuad' }); ++ }, ++ ++ jumpToItem(item) { ++ let box = item.actor.get_allocation_box(); ++ ++ let adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); ++ ++ let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0); ++ ++ adjustment.set_value(value); ++ }, ++ ++ getItem(key) { ++ let item = this._items[key]; ++ ++ if (!item) ++ return null; ++ ++ return item; ++ }, ++ ++ addItem(key, text) { ++ this.removeItem(key); ++ ++ let item = new AuthListItem(key, text); ++ this._box.add(item.actor, { x_fill: true }); ++ ++ this._items[key] = item; ++ ++ item.connect('activate', ++ this._onItemActivated.bind(this)); ++ ++ // Try to keep the focused item front-and-center ++ item.actor.connect('key-focus-in', ++ () => { this.scrollToItem(item); }); ++ ++ this._moveFocusToItems(); ++ ++ this.emit('item-added', item); ++ }, ++ ++ removeItem(key) { ++ let item = this._items[key]; ++ ++ if (!item) ++ return; ++ ++ item.actor.destroy(); ++ delete this._items[key]; ++ }, ++ ++ numItems() { ++ return Object.keys(this._items).length; ++ }, ++ ++ clear() { ++ this.label.text = ""; ++ this._box.destroy_all_children(); ++ this._items = {}; ++ } ++}); ++Signals.addSignalMethods(AuthList.prototype); +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index 836d1c674..002b202f8 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -1,6 +1,7 @@ + + + ++ gdm/authList.js + gdm/authPrompt.js + gdm/batch.js + gdm/fingerprint.js +-- +2.21.0 + diff --git a/SOURCES/0001-keyboard-Only-enable-keyboard-if-ClutterDeviceManage.patch b/SOURCES/0001-keyboard-Only-enable-keyboard-if-ClutterDeviceManage.patch new file mode 100644 index 0000000..a781d43 --- /dev/null +++ b/SOURCES/0001-keyboard-Only-enable-keyboard-if-ClutterDeviceManage.patch @@ -0,0 +1,39 @@ +From 2acede02f30833c3fb891db8483f933f7b41508c Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Wed, 21 Oct 2020 21:32:03 +0200 +Subject: [PATCH] keyboard: Only enable keyboard if + ClutterDeviceManager::touch-mode is enabled + +--- + js/ui/keyboard.js | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js +index 94b5325..b1ee270 100644 +--- a/js/ui/keyboard.js ++++ b/js/ui/keyboard.js +@@ -1051,6 +1051,9 @@ var Keyboard = class Keyboard { + this._suggestions = null; + this._emojiKeyVisible = true; + ++ let manager = Clutter.DeviceManager.get_default(); ++ manager.connect('notify::touch-mode', this._syncEnabled.bind(this)); ++ + this._focusTracker = new FocusTracker(); + this._focusTracker.connect('position-changed', this._onFocusPositionChanged.bind(this)); + this._focusTracker.connect('reset', () => { +@@ -1120,8 +1123,10 @@ var Keyboard = class Keyboard { + + _syncEnabled() { + let wasEnabled = this._enabled; ++ let manager = Clutter.DeviceManager.get_default(); ++ let autoEnabled = manager.get_touch_mode() && this._lastDeviceIsTouchscreen(); + this._enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD); +- this._enabled = this._enableKeyboard || this._lastDeviceIsTouchscreen(); ++ this._enabled = this._enableKeyboard || autoEnabled; + if (!this._enabled && !this._keyboardController) + return; + +-- +2.26.2 + diff --git a/SOURCES/0001-layout-Initialize-regions-unconditionally.patch b/SOURCES/0001-layout-Initialize-regions-unconditionally.patch new file mode 100644 index 0000000..3f6f065 --- /dev/null +++ b/SOURCES/0001-layout-Initialize-regions-unconditionally.patch @@ -0,0 +1,51 @@ +From d2661753076a60a7981836e4a85e88c4588fb1b2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 17 Nov 2022 15:21:42 +0100 +Subject: [PATCH] layout: Initialize regions unconditionally + +We currently initialize regions in all code paths except for the +greeter. But while there are no windows on the login screen, the +work area can still be used for positioning, for example for +notifications. + +Part-of: + +--- + js/ui/layout.js | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index beb4c0a5d..bb51946b7 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -624,20 +624,19 @@ var LayoutManager = GObject.registerClass({ + reactive: true }); + this.addChrome(this._coverPane); + ++ // Force an update of the regions before we scale the UI group to ++ // get the correct allocation for the struts. ++ // Do this even when we don't animate on restart, so that maximized ++ // windows restore to the right size. ++ this._updateRegions(); ++ + if (Meta.is_restart()) { +- // On restart, we don't do an animation. Force an update of the +- // regions immediately so that maximized windows restore to the +- // right size taking struts into account. +- this._updateRegions(); ++ // On restart, we don't do an animation. + } else if (Main.sessionMode.isGreeter) { + this.panelBox.translation_y = -this.panelBox.height; + } else { + this._updateBackgrounds(); + +- // We need to force an update of the regions now before we scale +- // the UI group to get the correct allocation for the struts. +- this._updateRegions(); +- + this.keyboardBox.hide(); + + let monitor = this.primaryMonitor; +-- +2.38.1 + diff --git a/SOURCES/0001-layout-Make-the-hot-corner-optional.patch b/SOURCES/0001-layout-Make-the-hot-corner-optional.patch new file mode 100644 index 0000000..c77310c --- /dev/null +++ b/SOURCES/0001-layout-Make-the-hot-corner-optional.patch @@ -0,0 +1,56 @@ +From 35cbad572120125d3b823f37d2100b2beee4c1d8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 8 Jun 2017 17:07:56 +0200 +Subject: [PATCH] layout: Make the hot corner optional + +Whether people love or hate the hot corner depends in large extents +on hardware sensitivity and habits, which is hard to get right +universally. So bite the bullet and support an option to enable or +disable hot corners ... + +https://bugzilla.gnome.org/show_bug.cgi?id=688320 +--- + js/ui/layout.js | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 2b3bb7442..beb4c0a5d 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -1,6 +1,6 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; ++const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; + const Signals = imports.signals; + + const Background = imports.ui.background; +@@ -267,6 +267,13 @@ var LayoutManager = GObject.registerClass({ + this._backgroundGroup.lower_bottom(); + this._bgManagers = []; + ++ this._interfaceSettings = new Gio.Settings({ ++ schema_id: 'org.gnome.desktop.interface' ++ }); ++ ++ this._interfaceSettings.connect('changed::enable-hot-corners', ++ this._updateHotCorners.bind(this)); ++ + // Need to update struts on new workspaces when they are added + let workspaceManager = global.workspace_manager; + workspaceManager.connect('notify::n-workspaces', +@@ -358,6 +365,11 @@ var LayoutManager = GObject.registerClass({ + }); + this.hotCorners = []; + ++ if (!this._interfaceSettings.get_boolean('enable-hot-corners')) { ++ this.emit('hot-corners-changed'); ++ return; ++ } ++ + let size = this.panelBox.height; + + // build new hot corners +-- +2.21.0 + diff --git a/SOURCES/0001-loginDialog-Reset-auth-prompt-on-vt-switch-before-fa.patch b/SOURCES/0001-loginDialog-Reset-auth-prompt-on-vt-switch-before-fa.patch new file mode 100644 index 0000000..85cdbbb --- /dev/null +++ b/SOURCES/0001-loginDialog-Reset-auth-prompt-on-vt-switch-before-fa.patch @@ -0,0 +1,49 @@ +From 6d26b6f9f66e14843f175305441a2464dd255fd1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 27 Jul 2020 10:58:49 -0400 +Subject: [PATCH] loginDialog: Reset auth prompt on vt switch before fade in + +At the moment, if a user switches to the login screen vt, +the login screen fades in whatever was on screen prior, and +then does a reset. + +It makes more sense to reset first, so we fade in what the +user is going to interact with instead of what they interacted +with before. + +Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2997 +--- + js/gdm/loginDialog.js | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 214c2f512..eb6846d5c 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -923,6 +923,9 @@ var LoginDialog = GObject.registerClass({ + if (this.opacity == 255 && this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + return; + ++ if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.NOT_VERIFYING) ++ this._authPrompt.reset(); ++ + Tweener.addTween(this, + { opacity: 255, + time: _FADE_ANIMATION_TIME, +@@ -935,12 +938,7 @@ var LoginDialog = GObject.registerClass({ + children[i].opacity = this.opacity; + } + }, +- onUpdateScope: this, +- onComplete() { +- if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.NOT_VERIFYING) +- this._authPrompt.reset(); +- }, +- onCompleteScope: this }); ++ onUpdateScope: this }); + } + + _gotGreeterSessionProxy(proxy) { +-- +2.32.0 + diff --git a/SOURCES/0001-loginDialog-make-info-messages-themed.patch b/SOURCES/0001-loginDialog-make-info-messages-themed.patch new file mode 100644 index 0000000..483f615 --- /dev/null +++ b/SOURCES/0001-loginDialog-make-info-messages-themed.patch @@ -0,0 +1,32 @@ +From 9cfa56d4f3c5fe513630c58c09bd2421f3ca580b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 26 Jun 2017 14:35:05 -0400 +Subject: [PATCH] loginDialog: make info messages themed + +They were lacking a definition before leading them to +show up invisible. +--- + data/theme/gnome-shell-sass/_common.scss | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index c2df28279..a382ce561 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -1801,7 +1801,12 @@ StScrollBar { + .login-dialog-banner { color: darken($osd_fg_color,10%); } + .login-dialog-button-box { spacing: 5px; } + .login-dialog-message-warning { color: $warning_color; } +- .login-dialog-message-hint { padding-top: 0; padding-bottom: 20px; } ++ .login-dialog-message-hint, .login-dialog-message { ++ color: darken($osd_fg_color, 20%); ++ padding-top: 0; ++ padding-bottom: 20px; ++ min-height: 2.75em; ++ } + .login-dialog-user-selection-box { padding: 100px 0px; } + .login-dialog-not-listed-label { + padding-left: 2px; +-- +2.21.0 + diff --git a/SOURCES/0001-main-Dump-stack-on-segfaults-by-default.patch b/SOURCES/0001-main-Dump-stack-on-segfaults-by-default.patch new file mode 100644 index 0000000..75b9b1a --- /dev/null +++ b/SOURCES/0001-main-Dump-stack-on-segfaults-by-default.patch @@ -0,0 +1,38 @@ +From ba3ce64fbbce20192a55f9d438d1032c0bac0557 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 29 Oct 2020 18:21:06 +0100 +Subject: [PATCH] main: Dump stack on segfaults by default + +--- + src/main.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/main.c b/src/main.c +index 245837783..788309de7 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -39,6 +39,7 @@ static int caught_signal = 0; + #define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 + #define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 + ++#define DEFAULT_SHELL_DEBUG SHELL_DEBUG_BACKTRACE_SEGFAULTS + enum { + SHELL_DEBUG_BACKTRACE_WARNINGS = 1, + SHELL_DEBUG_BACKTRACE_SEGFAULTS = 2, +@@ -268,8 +269,11 @@ shell_init_debug (const char *debug_env) + { "backtrace-segfaults", SHELL_DEBUG_BACKTRACE_SEGFAULTS }, + }; + +- _shell_debug = g_parse_debug_string (debug_env, keys, +- G_N_ELEMENTS (keys)); ++ if (debug_env) ++ _shell_debug = g_parse_debug_string (debug_env, keys, ++ G_N_ELEMENTS (keys)); ++ else ++ _shell_debug = DEFAULT_SHELL_DEBUG; + } + + static void +-- +2.29.2 + diff --git a/SOURCES/0001-main-Unset-the-right-prevFocus-actor-after-the-focus.patch b/SOURCES/0001-main-Unset-the-right-prevFocus-actor-after-the-focus.patch new file mode 100644 index 0000000..c052297 --- /dev/null +++ b/SOURCES/0001-main-Unset-the-right-prevFocus-actor-after-the-focus.patch @@ -0,0 +1,49 @@ +From 0d95c2087aba7f0b07cb303c1f15d097b45f1b09 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 28 Apr 2020 23:26:11 +0200 +Subject: [PATCH] main: Unset the right prevFocus actor after the focus stack + got shifted +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When a modal that's not on top of the modalActorFocusStack gets popped, +we shift the focus stack as described in popModal() to ensure the chain +remains correct. That however destroys the association of a modal actor +and its prevFocus actor on the focus stack, because the prevFocus actors +are now moved to different entries of the stack. + +Now when a prevFocus actor gets destroyed, we don't handle that case +correctly and search for the modal actor that was associated with the +prevFocus actor before the stack was shifted, which means we end up +unsetting the wrong prevFocus actor. + +So fix that and search the stack for the prevFocus actor which is being +destroyed instead to unset the correct entry. + +Thanks to Florian Müllner for figuring out the actual issue and +proposing this fix. + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2446 +--- + js/ui/main.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/ui/main.js b/js/ui/main.js +index dd1d8463d..ca3dcaa3c 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -486,7 +486,9 @@ function pushModal(actor, params) { + let prevFocusDestroyId; + if (prevFocus != null) { + prevFocusDestroyId = prevFocus.connect('destroy', () => { +- let index = _findModal(actor); ++ const index = modalActorFocusStack.findIndex( ++ record => record.prevFocus === prevFocus); ++ + if (index >= 0) + modalActorFocusStack[index].prevFocus = null; + }); +-- +2.35.1 + diff --git a/SOURCES/0001-networkAgent-add-support-for-SAE-secrets.patch b/SOURCES/0001-networkAgent-add-support-for-SAE-secrets.patch new file mode 100644 index 0000000..dd05d56 --- /dev/null +++ b/SOURCES/0001-networkAgent-add-support-for-SAE-secrets.patch @@ -0,0 +1,29 @@ +From bd4a3186dc21f2c8d3e0f851cf262a34ddb6b625 Mon Sep 17 00:00:00 2001 +From: Lubomir Rintel +Date: Fri, 4 Oct 2019 14:21:25 +0200 +Subject: [PATCH] networkAgent: add support for SAE secrets + +NetworkManager supports "WPA3 Personal" networks for some time now, they +use the SAE authentication. Add support for it alongside other +password-based mechanisms. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/751 +--- + js/ui/components/networkAgent.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/components/networkAgent.js b/js/ui/components/networkAgent.js +index 32d40fb2b..3ff957bf6 100644 +--- a/js/ui/components/networkAgent.js ++++ b/js/ui/components/networkAgent.js +@@ -216,6 +216,7 @@ var NetworkSecretDialog = class extends ModalDialog.ModalDialog { + // First the easy ones + case 'wpa-none': + case 'wpa-psk': ++ case 'sae': + secrets.push({ label: _("Password: "), key: 'psk', + value: wirelessSecuritySetting.psk || '', + validate: this._validateWpaPsk, password: true }); +-- +2.32.0 + diff --git a/SOURCES/0001-padOsd-Re-query-action-labels-after-mode-switches.patch b/SOURCES/0001-padOsd-Re-query-action-labels-after-mode-switches.patch new file mode 100644 index 0000000..bdbfc65 --- /dev/null +++ b/SOURCES/0001-padOsd-Re-query-action-labels-after-mode-switches.patch @@ -0,0 +1,80 @@ +From 2bb826291c420dd1b601758c7a686ac48e1086a6 Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Mon, 16 Dec 2019 12:39:49 +0100 +Subject: [PATCH] padOsd: Re-query action labels after mode switches + +Do this so the pad OSD is able to update dynamically to mode changes, +showing immediately the new actions for the current mode(s). + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/898 +--- + js/ui/padOsd.js | 31 ++++++++++++++++++++++++++++--- + 1 file changed, 28 insertions(+), 3 deletions(-) + +diff --git a/js/ui/padOsd.js b/js/ui/padOsd.js +index a4af47297..b4b3fe453 100644 +--- a/js/ui/padOsd.js ++++ b/js/ui/padOsd.js +@@ -555,6 +555,14 @@ var PadDiagram = GObject.registerClass({ + this.add_actor(label); + } + ++ updateLabels(callback) { ++ for (let i = 0; i < this._labels.length; i++) { ++ let [label, action, idx, dir] = this._labels[i]; ++ let str = callback(action, idx, dir); ++ label.set_text(str); ++ } ++ } ++ + _applyLabel(label, action, idx, dir, str) { + if (str != null) { + label.set_text(str); +@@ -758,17 +766,29 @@ var PadOsd = class { + global.display.request_pad_osd(pad, editionMode); + } + +- _createLabel(type, number, dir) { ++ _getActionText(type, number) { + let str = global.display.get_pad_action_label(this.padDevice, type, number); +- let label = new St.Label({ text: str ? str : _("None") }); ++ return str ? str : _("None"); ++ } ++ ++ _createLabel(type, number, dir) { ++ let label = new St.Label({ text: this._getActionText(type, number) }); + this._padDiagram.addLabel(label, type, number, dir); + } + ++ _updateActionLabels() { ++ this._padDiagram.updateLabels(this._getActionText.bind(this)); ++ } ++ + _onCapturedEvent(actor, event) { ++ let isModeSwitch = ++ (event.type() == Clutter.EventType.PAD_BUTTON_PRESS || ++ event.type() == Clutter.EventType.PAD_BUTTON_RELEASE) && ++ this.padDevice.get_mode_switch_button_group(event.get_button()) >= 0; ++ + if (event.type() == Clutter.EventType.PAD_BUTTON_PRESS && + event.get_source_device() == this.padDevice) { + this._padDiagram.activateButton(event.get_button()); +- let isModeSwitch = this.padDevice.get_mode_switch_button_group(event.get_button()) >= 0; + + /* Buttons that switch between modes cannot be edited */ + if (this._editionMode && !isModeSwitch) +@@ -777,6 +797,11 @@ var PadOsd = class { + } else if (event.type() == Clutter.EventType.PAD_BUTTON_RELEASE && + event.get_source_device() == this.padDevice) { + this._padDiagram.deactivateButton(event.get_button()); ++ ++ if (isModeSwitch) { ++ this._endActionEdition(); ++ this._updateActionLabels(); ++ } + return Clutter.EVENT_STOP; + } else if (event.type() == Clutter.EventType.KEY_PRESS && + (!this._editionMode || event.get_key_symbol() == Clutter.Escape)) { +-- +2.24.0 + diff --git a/SOURCES/0001-panel-add-an-icon-to-the-ActivitiesButton.patch b/SOURCES/0001-panel-add-an-icon-to-the-ActivitiesButton.patch new file mode 100644 index 0000000..24c0d81 --- /dev/null +++ b/SOURCES/0001-panel-add-an-icon-to-the-ActivitiesButton.patch @@ -0,0 +1,54 @@ +From aadb0e19999c339ac1d6501a2e52b363e57e26ef Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 15 Jan 2014 16:45:34 -0500 +Subject: [PATCH] panel: add an icon to the ActivitiesButton + +Requested by brand +--- + data/theme/gnome-shell-sass/_common.scss | 5 +++++ + js/ui/panel.js | 9 ++++++++- + 2 files changed, 13 insertions(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index a382ce561..3b0d2bf04 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -769,6 +769,11 @@ StScrollBar { + //dimensions of the icon are hardcoded + } + ++ .panel-logo-icon { ++ padding-right: .4em; ++ icon-size: 1em; ++ } ++ + &:hover { + color: lighten($fg_color, 10%); + } +diff --git a/js/ui/panel.js b/js/ui/panel.js +index 16484850a..ede1c2b82 100644 +--- a/js/ui/panel.js ++++ b/js/ui/panel.js +@@ -465,11 +465,18 @@ class ActivitiesButton extends PanelMenu.Button { + + this.actor.name = 'panelActivities'; + ++ let box = new St.BoxLayout(); ++ this.actor.add_actor(box); ++ 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' }); ++ box.add_actor(this._icon); ++ + /* Translators: If there is no suitable word for "Activities" + in your language, you can use the word for "Overview". */ + this._label = new St.Label({ text: _("Activities"), + y_align: Clutter.ActorAlign.CENTER }); +- this.actor.add_actor(this._label); ++ box.add_actor(this._label); + + this.actor.label_actor = this._label; + +-- +2.21.0 + diff --git a/SOURCES/0001-popupMenu-Handle-keypress-if-numlock-is-enabled.patch b/SOURCES/0001-popupMenu-Handle-keypress-if-numlock-is-enabled.patch new file mode 100644 index 0000000..e6fed23 --- /dev/null +++ b/SOURCES/0001-popupMenu-Handle-keypress-if-numlock-is-enabled.patch @@ -0,0 +1,36 @@ +From fb0a9a60ab8f1c0dd96e789969ab9b6e48a9fce4 Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan +Date: Tue, 21 Jul 2020 16:33:04 +0200 +Subject: [PATCH] popupMenu: Handle keypress if numlock is enabled + +On Wayland, navigating menus with the keyboard would not open drop-down +menus when NumLock is enabled. + +That's old issue (gnome-shell#550) that was not completely fixed with +commit 88556226 because the lock mask needs to be filtered out in +_onKeyPress() as well. + +Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/550 +--- + js/ui/popupMenu.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js +index 6de081ce81..9835face19 100644 +--- a/js/ui/popupMenu.js ++++ b/js/ui/popupMenu.js +@@ -801,9 +801,10 @@ var PopupMenu = class extends PopupMenuBase { + + let state = event.get_state(); + +- // if user has a modifier down (except capslock) ++ // if user has a modifier down (except capslock and numlock) + // then don't handle the key press here + state &= ~Clutter.ModifierType.LOCK_MASK; ++ state &= ~Clutter.ModifierType.MOD2_MASK; + state &= Clutter.ModifierType.MODIFIER_MASK; + + if (state) +-- +2.26.2 + diff --git a/SOURCES/0001-screenShield-unblank-when-inserting-smartcard.patch b/SOURCES/0001-screenShield-unblank-when-inserting-smartcard.patch new file mode 100644 index 0000000..3dc3cb9 --- /dev/null +++ b/SOURCES/0001-screenShield-unblank-when-inserting-smartcard.patch @@ -0,0 +1,33 @@ +From cacce594f07295bb1b9e0685913a287e3cea2453 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 3 Jul 2015 13:54:36 -0400 +Subject: [PATCH] screenShield: unblank when inserting smartcard + +If a user inserts the smartcard when the screen is locked/blanked +we should ask them their pin right away. + +At the moment they have to wiggle the mouse or do some other +action to get the screen to unblank. +--- + js/ui/screenShield.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index a005a206b..cd38f11fc 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -513,8 +513,10 @@ var ScreenShield = class { + this._smartcardManager = SmartcardManager.getSmartcardManager(); + this._smartcardManager.connect('smartcard-inserted', + (manager, token) => { +- if (this._isLocked && token.UsedToLogin) ++ if (this._isLocked && token.UsedToLogin) { ++ this._wakeUpScreen(); + this._liftShield(true, 0); ++ } + }); + + this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager(); +-- +2.21.0 + diff --git a/SOURCES/0001-screencast-Stop-recording-when-screen-size-or-resour.patch b/SOURCES/0001-screencast-Stop-recording-when-screen-size-or-resour.patch new file mode 100644 index 0000000..582ff4e --- /dev/null +++ b/SOURCES/0001-screencast-Stop-recording-when-screen-size-or-resour.patch @@ -0,0 +1,253 @@ +From 67a4506d4d8a0cbbaca5df4adfc309e54e557aee Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 5 Jan 2021 12:04:23 +0100 +Subject: [PATCH] screencast: Stop recording when screen size or resource scale + change + +Video encoders don't really handle changing the size of the video, and if +we'd e.g. change resolution while recording, we would end up with a corrupt +video file. Handle this more gracefully by stopping the recording if the +conditions change. +--- + js/ui/screencast.js | 92 +++++++++++++++++++++++++++++++++++++++++--- + src/shell-recorder.c | 50 ++++++++++++------------ + src/shell-recorder.h | 1 + + 3 files changed, 114 insertions(+), 29 deletions(-) + +diff --git a/js/ui/screencast.js b/js/ui/screencast.js +index 0b0b14a8e..54f8fb5ae 100644 +--- a/js/ui/screencast.js ++++ b/js/ui/screencast.js +@@ -1,6 +1,6 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Gio, GLib, Shell } = imports.gi; ++const { Gio, GLib, Meta, Shell } = imports.gi; + const Signals = imports.signals; + + const Main = imports.ui.main; +@@ -53,16 +53,27 @@ var ScreencastService = class { + this._stopRecordingForSender(name); + } + +- _stopRecordingForSender(sender) { ++ _stopRecordingForSender(sender, closeNow=false) { + let recorder = this._recorders.get(sender); + if (!recorder) + return false; + + Gio.bus_unwatch_name(recorder._watchNameId); +- recorder.close(); ++ if (closeNow) ++ recorder.close_now(); ++ else ++ recorder.close(); + this._recorders.delete(sender); + this.emit('updated'); + ++ let connection = this._dbusImpl.get_connection(); ++ let info = this._dbusImpl.get_info(); ++ connection.emit_signal(sender, ++ this._dbusImpl.get_object_path(), ++ info ? info.name : null, ++ 'Stopped', ++ null); ++ + return true; + } + +@@ -78,6 +89,53 @@ var ScreencastService = class { + recorder.set_draw_cursor(options['draw-cursor']); + } + ++ _ensureResourceScaleChangedHandler() { ++ if (this._resourceScaleChangedHandlerId) ++ return; ++ ++ this._resourceScaleChangedHandlerId = ++ global.stage.connect('notify::resource-scale', ++ () => { ++ for (let sender of this._recorders.keys()) { ++ let recorder = this._recorders.get(sender); ++ ++ if (!recorder.is_recording()) ++ continue; ++ ++ this._stopRecordingForSender(sender, true); ++ } ++ }); ++ } ++ ++ _ensureMonitorsChangedHandler() { ++ if (this._monitorsChangedHandlerId) ++ return; ++ ++ this._monitorsChangedHandlerId = Main.layoutManager.connect('monitors-changed', ++ () => { ++ for (let sender of this._recorders.keys()) { ++ let recorder = this._recorders.get(sender); ++ ++ if (!recorder.is_recording()) ++ continue; ++ ++ let geometry = recorder._geometry; ++ let screenWidth = global.screen_width; ++ let screenHeight = global.screen_height; ++ ++ if (recorder._isAreaScreecast) { ++ if (geometry.x + geometry.width > screenWidth || ++ geometry.y + geometry.height > screenHeight) ++ this._stopRecordingForSender(sender, true); ++ } else { ++ if (geometry.width != screenWidth || ++ geometry.height != screenHeight) ++ this._stopRecordingForSender(sender, true); ++ } ++ } ++ }); ++ } ++ + ScreencastAsync(params, invocation) { + let returnValue = [false, '']; + if (!Main.sessionMode.allowScreencast || +@@ -95,8 +153,20 @@ var ScreencastService = class { + this._applyOptionalParameters(recorder, options); + let [success, fileName] = recorder.record(); + returnValue = [success, fileName ? fileName : '']; +- if (!success) ++ if (success) { ++ recorder._isAreaScreecast = false; ++ recorder._geometry = ++ new Meta.Rectangle({ ++ x: 0, ++ y: 0, ++ width: global.screen_width, ++ height: global.screen_height ++ }); ++ this._ensureResourceScaleChangedHandler(); ++ this._ensureMonitorsChangedHandler(); ++ } else { + this._stopRecordingForSender(sender); ++ } + } + + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); +@@ -131,8 +201,20 @@ var ScreencastService = class { + this._applyOptionalParameters(recorder, options); + let [success, fileName] = recorder.record(); + returnValue = [success, fileName ? fileName : '']; +- if (!success) ++ if (success) { ++ recorder._isAreaScreecast = true; ++ recorder._geometry = ++ new Meta.Rectangle({ ++ x: x, ++ y: y, ++ width: width, ++ height: height ++ }); ++ this._ensureResourceScaleChangedHandler(); ++ this._ensureMonitorsChangedHandler(); ++ } else { + this._stopRecordingForSender(sender); ++ } + } + + invocation.return_value(GLib.Variant.new('(bs)', returnValue)); +diff --git a/src/shell-recorder.c b/src/shell-recorder.c +index 0203ecf1c..e561a0152 100644 +--- a/src/shell-recorder.c ++++ b/src/shell-recorder.c +@@ -511,21 +511,6 @@ recorder_update_size (ShellRecorder *recorder) + } + } + +-static void +-recorder_on_stage_notify_size (GObject *object, +- GParamSpec *pspec, +- ShellRecorder *recorder) +-{ +- recorder_update_size (recorder); +- +- /* This breaks the recording but tweaking the GStreamer pipeline a bit +- * might make it work, at least if the codec can handle a stream where +- * the frame size changes in the middle. +- */ +- if (recorder->current_pipeline) +- recorder_pipeline_set_caps (recorder->current_pipeline); +-} +- + static gboolean + recorder_idle_redraw (gpointer data) + { +@@ -622,12 +607,6 @@ recorder_connect_stage_callbacks (ShellRecorder *recorder) + G_CALLBACK (recorder_on_stage_destroy), recorder); + g_signal_connect_after (recorder->stage, "paint", + G_CALLBACK (recorder_on_stage_paint), recorder); +- g_signal_connect (recorder->stage, "notify::width", +- G_CALLBACK (recorder_on_stage_notify_size), recorder); +- g_signal_connect (recorder->stage, "notify::height", +- G_CALLBACK (recorder_on_stage_notify_size), recorder); +- g_signal_connect (recorder->stage, "notify::resource-scale", +- G_CALLBACK (recorder_on_stage_notify_size), recorder); + } + + static void +@@ -639,9 +618,6 @@ recorder_disconnect_stage_callbacks (ShellRecorder *recorder) + g_signal_handlers_disconnect_by_func (recorder->stage, + (void *)recorder_on_stage_paint, + recorder); +- g_signal_handlers_disconnect_by_func (recorder->stage, +- (void *)recorder_on_stage_notify_size, +- recorder); + + /* We don't don't deselect for cursor changes in case someone else just + * happened to be selecting for cursor events on the same window; sending +@@ -1578,6 +1554,32 @@ shell_recorder_record (ShellRecorder *recorder, + return TRUE; + } + ++/** ++ * shell_recorder_close_now: ++ * @recorder: the #ShellRecorder ++ * ++ * Stops recording immediately. It's possible to call shell_recorder_record() ++ * again to reopen a new recording stream, but unless change the recording ++ * filename, this may result in the old recording being overwritten. ++ */ ++void ++shell_recorder_close_now (ShellRecorder *recorder) ++{ ++ g_return_if_fail (SHELL_IS_RECORDER (recorder)); ++ g_return_if_fail (recorder->state != RECORDER_STATE_CLOSED); ++ ++ recorder_remove_update_pointer_timeout (recorder); ++ recorder_close_pipeline (recorder); ++ ++ recorder->state = RECORDER_STATE_CLOSED; ++ ++ /* Reenable after the recording */ ++ meta_enable_unredirect_for_display (shell_global_get_display (shell_global_get ())); ++ ++ /* Release the refcount we took when we started recording */ ++ g_object_unref (recorder); ++} ++ + /** + * shell_recorder_close: + * @recorder: the #ShellRecorder +diff --git a/src/shell-recorder.h b/src/shell-recorder.h +index c1e0e6368..1c3e6aab4 100644 +--- a/src/shell-recorder.h ++++ b/src/shell-recorder.h +@@ -37,6 +37,7 @@ void shell_recorder_set_area (ShellRecorder *recorder, + gboolean shell_recorder_record (ShellRecorder *recorder, + char **filename_used); + void shell_recorder_close (ShellRecorder *recorder); ++void shell_recorder_close_now (ShellRecorder *recorder); + void shell_recorder_pause (ShellRecorder *recorder); + gboolean shell_recorder_is_recording (ShellRecorder *recorder); + +-- +2.27.0 + diff --git a/SOURCES/0001-shell-app-Handle-workspace-from-startup-notification.patch b/SOURCES/0001-shell-app-Handle-workspace-from-startup-notification.patch new file mode 100644 index 0000000..e7f0db8 --- /dev/null +++ b/SOURCES/0001-shell-app-Handle-workspace-from-startup-notification.patch @@ -0,0 +1,78 @@ +From 391f262aee82ac12fcf99951d6b2df362f734b31 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 15 Jun 2020 20:41:45 +0200 +Subject: [PATCH] shell/app: Handle workspace from startup notifications + +Launching applications on a particular workspace works through +launch contexts and startup notifications. While this is no +longer required by a launcher/WM split, in theory this allows +us to reliably identify the correct window to apply startup +properties to. + +However in practice we fail more often than not: Missing support in +toolkits, differences between display protocols, D-Bus activation +and single-instance applications all provide their own pitfalls. + +So instead, take advantage of the fact that launcher and WM live in +the same process, and go with the unsophisticated approach: Just +remember the last workspace that was requested when launching an +app, then move the next window that is associated with the app to +that workspace. + +This will break X11 applications that set an initial workspace, but +that's legacy functionality anyway (given that there's no wayland +protocol for that functionality), and seems a price worth paying +for making launching apps on workspaces more reliable. +--- + src/shell-app.c | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +diff --git a/src/shell-app.c b/src/shell-app.c +index 7d40186c9..f716bc5f8 100644 +--- a/src/shell-app.c ++++ b/src/shell-app.c +@@ -1067,6 +1067,10 @@ _shell_app_add_window (ShellApp *app, + if (!app->running_state) + create_running_state (app); + ++ if (app->started_on_workspace >= 0) ++ meta_window_change_workspace_by_index (window, app->started_on_workspace, FALSE); ++ app->started_on_workspace = -1; ++ + app->running_state->window_sort_stale = TRUE; + app->running_state->windows = g_slist_prepend (app->running_state->windows, g_object_ref (window)); + g_signal_connect_object (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app, 0); +@@ -1156,16 +1160,14 @@ _shell_app_handle_startup_sequence (ShellApp *app, + shell_app_state_transition (app, SHELL_APP_STATE_STARTING); + meta_x11_display_focus_the_no_focus_window (x11_display, + meta_startup_sequence_get_timestamp (sequence)); +- app->started_on_workspace = meta_startup_sequence_get_workspace (sequence); + } + +- if (!starting) +- { +- if (app->running_state && app->running_state->windows) +- shell_app_state_transition (app, SHELL_APP_STATE_RUNNING); +- else /* application have > 1 .desktop file */ +- shell_app_state_transition (app, SHELL_APP_STATE_STOPPED); +- } ++ if (starting) ++ app->started_on_workspace = meta_startup_sequence_get_workspace (sequence); ++ else if (app->running_state && app->running_state->windows) ++ shell_app_state_transition (app, SHELL_APP_STATE_RUNNING); ++ else /* application have > 1 .desktop file */ ++ shell_app_state_transition (app, SHELL_APP_STATE_STOPPED); + } + + /** +@@ -1473,6 +1475,7 @@ static void + shell_app_init (ShellApp *self) + { + self->state = SHELL_APP_STATE_STOPPED; ++ self->started_on_workspace = -1; + } + + static void +-- +2.29.2 + diff --git a/SOURCES/0001-shell-recorder-Restore-cursor-recording.patch b/SOURCES/0001-shell-recorder-Restore-cursor-recording.patch new file mode 100644 index 0000000..ffd31d1 --- /dev/null +++ b/SOURCES/0001-shell-recorder-Restore-cursor-recording.patch @@ -0,0 +1,28 @@ +From 3182ad73c8f88628cb51a96feba0fc32ce7f01c9 Mon Sep 17 00:00:00 2001 +From: Illya Klymov +Date: Mon, 8 Jul 2019 03:29:36 +0000 +Subject: [PATCH] shell-recorder: Restore cursor recording + +Due to changes introduced in 5357e0a1 cursor recording interaction with +magnifier was reversed. This fix restores original correct behavior +Related issue: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1208 +--- + src/shell-recorder.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shell-recorder.c b/src/shell-recorder.c +index 0203ecf1c..cf1cc336f 100644 +--- a/src/shell-recorder.c ++++ b/src/shell-recorder.c +@@ -465,7 +465,7 @@ recorder_record_frame (ShellRecorder *recorder, + + g_object_get (settings, "magnifier-active", &magnifier_active, NULL); + +- if (magnifier_active) ++ if (!magnifier_active) + recorder_draw_cursor (recorder, buffer); + } + +-- +2.35.1 + diff --git a/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch b/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch new file mode 100644 index 0000000..5649937 --- /dev/null +++ b/SOURCES/0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch @@ -0,0 +1,66 @@ +From 660ebe0125b591355116934ee57b08010e05246c Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Fri, 8 Nov 2013 11:36:04 +0100 +Subject: [PATCH] shellDBus: Add a DBus method to load a single extension + +This allows e.g. gnome-tweak-tool to install an extension from a zip +file and load it into the running shell. +--- + .../org.gnome.Shell.Extensions.xml | 13 +++++++++++++ + js/ui/shellDBus.js | 16 ++++++++++++++++ + 2 files changed, 29 insertions(+) + +diff --git a/data/dbus-interfaces/org.gnome.Shell.Extensions.xml b/data/dbus-interfaces/org.gnome.Shell.Extensions.xml +index 34a65af44..ce69439fc 100644 +--- a/data/dbus-interfaces/org.gnome.Shell.Extensions.xml ++++ b/data/dbus-interfaces/org.gnome.Shell.Extensions.xml +@@ -189,6 +189,19 @@ + --> + + ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index 19d07acce..112d60feb 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -341,6 +341,22 @@ var GnomeShellExtensions = class { + ExtensionDownloader.checkForUpdates(); + } + ++ LoadUserExtension(uuid) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ if (extension) ++ return true; ++ ++ let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); ++ try { ++ extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); ++ ExtensionSystem.loadExtension(extension); ++ } catch (e) { ++ log('Could not load user extension from %s'.format(dir.get_path())); ++ return false; ++ } ++ return true; ++ } ++ + get ShellVersion() { + return Config.PACKAGE_VERSION; + } +-- +2.21.0 + diff --git a/SOURCES/0001-shellEntry-Determine-if-password-entry-from-content-.patch b/SOURCES/0001-shellEntry-Determine-if-password-entry-from-content-.patch new file mode 100644 index 0000000..4d5bbe1 --- /dev/null +++ b/SOURCES/0001-shellEntry-Determine-if-password-entry-from-content-.patch @@ -0,0 +1,91 @@ +From e6cd96a9f6a89f77ca0fab72aff8c56354b59f38 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2019 15:01:34 -0400 +Subject: [PATCH 1/4] shellEntry: Determine if password entry from content + purpose not menu item + +Right now shellEntry decides whether or not it's a password entry based +on whether or not it has a "Show Text" context menu. + +That's a little roundabout, and gets in the way off providing lockdown +that disables the menu. + +This commit changes shellEntry to base whether or not it's a password +entry from it's input content purpose instead of from the presence +or absence of a context menu. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/687 +--- + js/ui/shellEntry.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 53bd1daa1..cac4ec9c2 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -14,61 +14,61 @@ var EntryMenu = class extends PopupMenu.PopupMenu { + + this._entry = entry; + this._clipboard = St.Clipboard.get_default(); + + // Populate menu + let item; + item = new PopupMenu.PopupMenuItem(_("Copy")); + item.connect('activate', this._onCopyActivated.bind(this)); + this.addMenuItem(item); + this._copyItem = item; + + item = new PopupMenu.PopupMenuItem(_("Paste")); + item.connect('activate', this._onPasteActivated.bind(this)); + this.addMenuItem(item); + this._pasteItem = item; + + this._passwordItem = null; + + Main.uiGroup.add_actor(this.actor); + this.actor.hide(); + } + + _makePasswordItem() { + let item = new PopupMenu.PopupMenuItem(''); + item.connect('activate', this._onPasswordActivated.bind(this)); + this.addMenuItem(item); + this._passwordItem = item; + } + + get isPassword() { +- return this._passwordItem != null; ++ return this._entry.input_purpose == Clutter.InputContentPurpose.PASSWORD; + } + + set isPassword(v) { + if (v == this.isPassword) + return; + + if (v) { + this._makePasswordItem(); + this._entry.input_purpose = Clutter.InputContentPurpose.PASSWORD; + } else { + this._passwordItem.destroy(); + this._passwordItem = null; + this._entry.input_purpose = Clutter.InputContentPurpose.NORMAL; + } + } + + open(animate) { + this._updatePasteItem(); + this._updateCopyItem(); + if (this._passwordItem) + this._updatePasswordItem(); + + super.open(animate); + this._entry.add_style_pseudo_class('focus'); + + let direction = St.DirectionType.TAB_FORWARD; + if (!this.actor.navigate_focus(null, direction, false)) + this.actor.grab_key_focus(); + } + +-- +2.27.0 + diff --git a/SOURCES/0001-shellEntry-Disconnect-handler-on-destroy.patch b/SOURCES/0001-shellEntry-Disconnect-handler-on-destroy.patch new file mode 100644 index 0000000..abf32bd --- /dev/null +++ b/SOURCES/0001-shellEntry-Disconnect-handler-on-destroy.patch @@ -0,0 +1,36 @@ +From fd8c4dc073b121b1093d68472cac3292d2c6605c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 14 Jun 2021 17:59:39 +0200 +Subject: [PATCH] shellEntry: Disconnect handler on destroy + +Actors will get unmapped on destroy, so unless we disconnect from +the notify::mapped signal, the handler will run one last time and +try to access methods/properties on the invalidated actor. +--- + js/ui/shellEntry.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 4a30b22f7..53bd1daa1 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -186,7 +186,7 @@ class CapsLockWarning extends St.Label { + this._keymap = Clutter.get_default_backend().get_keymap(); + this._stateChangedId = 0; + +- this.connect('notify::mapped', () => { ++ const mappedId = this.connect('notify::mapped', () => { + if (this.is_mapped()) { + this._stateChangedId = this._keymap.connect('state-changed', + () => this._sync(true)); +@@ -201,6 +201,7 @@ class CapsLockWarning extends St.Label { + this.connect('destroy', () => { + if (this._stateChangedId) + this._keymap.disconnect(this._stateChangedId); ++ this.disconnect(mappedId); + }); + } + +-- +2.31.1 + diff --git a/SOURCES/0001-st-bin-Disallow-st_bin_set_child-with-already-parent.patch b/SOURCES/0001-st-bin-Disallow-st_bin_set_child-with-already-parent.patch new file mode 100644 index 0000000..23c2606 --- /dev/null +++ b/SOURCES/0001-st-bin-Disallow-st_bin_set_child-with-already-parent.patch @@ -0,0 +1,59 @@ +From 96404287bc4269dea7b037e7b178e54ebf616d47 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Tue, 24 Nov 2020 17:34:08 +0800 +Subject: [PATCH] st-bin: Disallow st_bin_set_child with already-parented + children + +Not checking for this would result in `clutter_actor_add_child` +failing, but StBin keeping a copy in `priv->child`. So later on, +`st_bin_remove` would never be called on it and this assertion +would fail and crash the whole shell: + +``` +static void +st_bin_destroy (ClutterActor *actor) +{ + StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (actor)); + + if (priv->child) + clutter_actor_destroy (priv->child); + g_assert (priv->child == NULL); + +``` + +By disallowing spurious `st_bin_set_child` calls we now prevent StBin +from entering such a corrupt state and the above assertion won't fail +anymore. + +Part-of: +--- + src/st/st-bin.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/src/st/st-bin.c b/src/st/st-bin.c +index f013909e8..7959a4e95 100644 +--- a/src/st/st-bin.c ++++ b/src/st/st-bin.c +@@ -434,6 +434,19 @@ st_bin_set_child (StBin *bin, + if (priv->child == child) + return; + ++ if (child) ++ { ++ ClutterActor *parent = clutter_actor_get_parent (child); ++ ++ if (parent) ++ { ++ g_warning ("%s: The provided 'child' actor %p already has a " ++ "(different) parent %p and can't be made a child of %p.", ++ G_STRFUNC, child, parent, bin); ++ return; ++ } ++ } ++ + if (priv->child) + clutter_actor_remove_child (CLUTTER_ACTOR (bin), priv->child); + +-- +2.38.1 + diff --git a/SOURCES/0001-st-texture-cache-Cancel-pending-requests-on-icon-the.patch b/SOURCES/0001-st-texture-cache-Cancel-pending-requests-on-icon-the.patch new file mode 100644 index 0000000..a6f6fdc --- /dev/null +++ b/SOURCES/0001-st-texture-cache-Cancel-pending-requests-on-icon-the.patch @@ -0,0 +1,94 @@ +From 1bf28eea64056846547ec33d783c7f2e0dad78a4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 22 May 2020 22:53:39 +0200 +Subject: [PATCH] st/texture-cache: Cancel pending requests on icon-theme + changes + +As outlined in commit 36b8dcbe07, we can end up with wrong icons +if the icon theme changes right after a GTK theme change to/from +HighContrast triggered a theme reload. + +That's because when we reload icons for the new icon theme, there +are already pending requests due to the icon-style change; those +requests are simply re-used for the new icons, with the existing +icon infos from the old theme. + +The above commit applied a simple work-around by changing the +icon theme before the GTK theme, but that only works for the +HighContrast switch in our own UI. + +It turns out that Settings also uses the "wrong" order, so the +issue still reproduces with the Universal Access panel. + +So instead of relying on everything changing the settings in the +order we expect, cancel all ongoing requests on icon-theme changes. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1277 +--- + src/st/st-texture-cache.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c +index 35e9d036f..6dc351282 100644 +--- a/src/st/st-texture-cache.c ++++ b/src/st/st-texture-cache.c +@@ -48,6 +48,8 @@ struct _StTextureCachePrivate + + /* File monitors to evict cache data on changes */ + GHashTable *file_monitors; /* char * -> GFileMonitor * */ ++ ++ GCancellable *cancellable; + }; + + static void st_texture_cache_dispose (GObject *object); +@@ -152,6 +154,9 @@ on_icon_theme_changed (StSettings *settings, + { + g_autofree gchar *theme; + ++ g_cancellable_cancel (cache->priv->cancellable); ++ g_cancellable_reset (cache->priv->cancellable); ++ + st_texture_cache_evict_icons (cache); + + g_object_get (settings, "gtk-icon-theme", &theme, NULL); +@@ -186,6 +191,8 @@ st_texture_cache_init (StTextureCache *self) + self->priv->file_monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, g_object_unref); + ++ self->priv->cancellable = g_cancellable_new (); ++ + on_icon_theme_changed (settings, NULL, self); + } + +@@ -194,8 +201,11 @@ st_texture_cache_dispose (GObject *object) + { + StTextureCache *self = (StTextureCache*)object; + ++ g_cancellable_cancel (self->priv->cancellable); ++ + g_clear_object (&self->priv->settings); + g_clear_object (&self->priv->icon_theme); ++ g_clear_object (&self->priv->cancellable); + + g_clear_pointer (&self->priv->keyed_cache, g_hash_table_destroy); + g_clear_pointer (&self->priv->keyed_surface_cache, g_hash_table_destroy); +@@ -675,11 +685,14 @@ load_texture_async (StTextureCache *cache, + gtk_icon_info_load_symbolic_async (data->icon_info, + &foreground_color, &success_color, + &warning_color, &error_color, +- NULL, on_symbolic_icon_loaded, data); ++ cache->priv->cancellable, ++ on_symbolic_icon_loaded, data); + } + else + { +- gtk_icon_info_load_icon_async (data->icon_info, NULL, on_icon_loaded, data); ++ gtk_icon_info_load_icon_async (data->icon_info, ++ cache->priv->cancellable, ++ on_icon_loaded, data); + } + } + else +-- +2.26.2 + diff --git a/SOURCES/0001-status-volume-Hide-sliders-initially.patch b/SOURCES/0001-status-volume-Hide-sliders-initially.patch new file mode 100644 index 0000000..ab5a86e --- /dev/null +++ b/SOURCES/0001-status-volume-Hide-sliders-initially.patch @@ -0,0 +1,30 @@ +From 4e555e0efeb4b31918e199d29bee99b2a4ed1c8e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 11 May 2022 02:34:21 +0200 +Subject: [PATCH] status/volume: Hide sliders initially + +We update the visibility on state or stream changes, but those +changes may never happen if pipewire-pulse/pulseaudio isn't +available (for example when running as root). + +Hiding the sliders is preferable in that case to showing non-working +controls. +--- + js/ui/status/volume.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js +index d555b426e..ab5065683 100644 +--- a/js/ui/status/volume.js ++++ b/js/ui/status/volume.js +@@ -30,6 +30,7 @@ var StreamSlider = class { + this._control = control; + + this.item = new PopupMenu.PopupBaseMenuItem({ activate: false }); ++ this.item.actor.hide(); + + this._slider = new Slider.Slider(0); + +-- +2.35.1 + diff --git a/SOURCES/0001-theme-Update-window-preview-style.patch b/SOURCES/0001-theme-Update-window-preview-style.patch new file mode 100644 index 0000000..2871e60 --- /dev/null +++ b/SOURCES/0001-theme-Update-window-preview-style.patch @@ -0,0 +1,76 @@ +From c68fd3c94c6debdbf11020940c5a6aaee8bc230d Mon Sep 17 00:00:00 2001 +From: Feichtmeier +Date: Fri, 15 Mar 2019 14:41:55 +0100 +Subject: [PATCH] theme: Update window preview style + + - simplify the close button to use blue, lighter blue and darker blue + solid disks for normal, hover and active states + + - use a milky, transparent white border for the hover effect of the border + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/461 +--- + data/theme/gnome-shell-sass/_common.scss | 29 ++++++++++++------------ + 1 file changed, 14 insertions(+), 15 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 9e0751c8c..8bf368f6e 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -1164,25 +1164,23 @@ StScrollBar { + //close buttons + + .window-close { +- background-color: white; ++ background-color: $selected_bg_color; ++ color: white; + border-radius: 24px; +- border: 4px solid $selected_bg_color; +- box-shadow: inset 0 -4px 0 0 transparentize($selected_bg_color, 0.5); +- color: $selected_bg_color; ++ border: 2px solid $selected_bg_color; + height: 24px; + width: 24px; +- -shell-close-overlap: 14px; ++ -shell-close-overlap: 11px; ++ box-shadow: -1px 1px 5px 0px transparentize(black, 0.5); + + &:hover { +- background-color: $selected_bg_color; +- border-color: white; +- color: white; ++ background-color: lighten($selected_bg_color, 5%); ++ border-color: lighten($selected_bg_color, 5%); + } + + &:active { +- background-color: mix(white, $selected_bg_color, 75%); +- border-color: $selected_bg_color; +- color: $selected_bg_color; ++ background-color: darken($selected_bg_color, 5%); ++ border-color: darken($selected_bg_color, 5%); + } + } + +@@ -1247,13 +1245,14 @@ StScrollBar { + } + + .window-clone-border { +- border: 4px solid $selected_bg_color; +- border-radius: 4px; ++ $_bg: transparentize(white, 0.65); ++ border: 5px solid $_bg; ++ border-radius: 6px; + // For window decorations with round corners we can't match + // the exact shape when the window is scaled. So apply a shadow + // to fix that case +- box-shadow: inset 0px 0px 0px 1px $selected_bg_color; +- } ++ box-shadow: inset 0 0 0 1px $_bg; ++} + .window-caption { + spacing: 25px; + color: $selected_fg_color; +-- +2.31.1 + diff --git a/SOURCES/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch b/SOURCES/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch new file mode 100644 index 0000000..810a27a --- /dev/null +++ b/SOURCES/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch @@ -0,0 +1,45 @@ +From 20640a92f98e2145b9b6581209c978e9f6f78801 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 14 Mar 2017 17:04:36 +0100 +Subject: [PATCH] windowMenu: Bring back workspaces submenu for static + workspaces + +When the titlebar context menu was moved to the shell, the submenu for +moving to a specific workspace was intentionally left out; some people +are quite attached to it though, so bring it back when static workspaces +are used. +--- + js/ui/windowMenu.js | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js +index 628f145ea..f8eb4398c 100644 +--- a/js/ui/windowMenu.js ++++ b/js/ui/windowMenu.js +@@ -115,6 +115,23 @@ var WindowMenu = class extends PopupMenu.PopupMenu { + window.change_workspace(workspace.get_neighbor(dir)); + }); + } ++ ++ let { workspaceManager } = global; ++ let nWorkspaces = workspaceManager.n_workspaces; ++ if (nWorkspaces > 1 && !Meta.prefs_get_dynamic_workspaces()) { ++ item = new PopupMenu.PopupSubMenuMenuItem(_("Move to another workspace")); ++ this.addMenuItem(item); ++ ++ let currentIndex = workspaceManager.get_active_workspace_index(); ++ for (let i = 0; i < nWorkspaces; i++) { ++ let index = i; ++ let name = Meta.prefs_get_workspace_name(i); ++ let subitem = item.menu.addAction(name, () => { ++ window.change_workspace_by_index(index, false); ++ }); ++ subitem.setSensitive(currentIndex != i); ++ } ++ } + } + } + +-- +2.21.0 + diff --git a/SOURCES/0001-workspace-Pass-device-to-startDrag.patch b/SOURCES/0001-workspace-Pass-device-to-startDrag.patch new file mode 100644 index 0000000..7b4afba --- /dev/null +++ b/SOURCES/0001-workspace-Pass-device-to-startDrag.patch @@ -0,0 +1,31 @@ +From 9115f6e7962b97c3ee2fbef7b195b7116e62c070 Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Fri, 13 Dec 2019 18:14:51 +0100 +Subject: [PATCH] workspace: Pass device to startDrag() + +This is necessary to make DnD operations work from tablet devices on +wayland, as it's not the same onscreen pointer sprite than mice. Fixes +window DnD in the overview on tablet devices, no longer having them stick +to the wrong pointer. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/897 +--- + js/ui/workspace.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/workspace.js b/js/ui/workspace.js +index 1e9bedc28..d470f7f40 100644 +--- a/js/ui/workspace.js ++++ b/js/ui/workspace.js +@@ -431,7 +431,7 @@ var WindowClone = GObject.registerClass({ + return; + let [x, y] = action.get_coords(); + action.release(); +- this._draggable.startDrag(x, y, global.get_current_time(), this._dragTouchSequence); ++ this._draggable.startDrag(x, y, global.get_current_time(), this._dragTouchSequence, event.get_device()); + }); + } else { + this.emit('show-chrome'); +-- +2.23.0 + diff --git a/SOURCES/0001-workspacesView-Work-around-spurious-allocation-chang.patch b/SOURCES/0001-workspacesView-Work-around-spurious-allocation-chang.patch new file mode 100644 index 0000000..f51e8e2 --- /dev/null +++ b/SOURCES/0001-workspacesView-Work-around-spurious-allocation-chang.patch @@ -0,0 +1,37 @@ +From b69b404118852f7955f60d1814f5e19ad61ce449 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 12 Jul 2019 03:26:51 +0000 +Subject: [PATCH] workspacesView: Work around spurious allocation changes + +For some reason, people are still seeing those after commit d5ebd8c8. +While this is something we really should figure out, we can work around +the issue by keeping the view actors hidden until the update is complete. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1065 +--- + js/ui/workspacesView.js | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index 069937d5a..e302296a6 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -666,10 +666,15 @@ var WorkspacesDisplay = class { + this._scrollValueChanged.bind(this)); + } + ++ // HACK: Avoid spurious allocation changes while updating views ++ view.actor.hide(); ++ + this._workspacesViews.push(view); + Main.layoutManager.overviewGroup.add_actor(view.actor); + } + ++ this._workspacesViews.forEach(v => v.actor.show()); ++ + this._updateWorkspacesFullGeometry(); + this._updateWorkspacesActualGeometry(); + } +-- +2.21.0 + diff --git a/SOURCES/0002-background-rebuild-background-not-just-animation-on-.patch b/SOURCES/0002-background-rebuild-background-not-just-animation-on-.patch new file mode 100644 index 0000000..3b436cb --- /dev/null +++ b/SOURCES/0002-background-rebuild-background-not-just-animation-on-.patch @@ -0,0 +1,31 @@ +From f27c4224aa96975ae44641612f5fff3772f5c294 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 22 Aug 2022 13:06:05 +0200 +Subject: [PATCH] [PATCH 2/4] background: rebuild background, not just + animation on resume + +Previously, we would only refresh the animation on resume +(to handle clock skew). + +But we actually need to rebuild the background, too, on nvidia, +so we should just do a full background change. +--- + js/ui/background.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/background.js b/js/ui/background.js +index 2a404ae..dd11e3e 100644 +--- a/js/ui/background.js ++++ b/js/ui/background.js +@@ -254,7 +254,7 @@ var Background = class Background { + (lm, aboutToSuspend) => { + if (aboutToSuspend) + return; +- this._refreshAnimation(); ++ this.emit('changed'); + }); + + this._settingsChangedSignalId = +-- +2.35.3 + diff --git a/SOURCES/0002-environment-Fix-date-conversion.patch b/SOURCES/0002-environment-Fix-date-conversion.patch new file mode 100644 index 0000000..2f6b5bd --- /dev/null +++ b/SOURCES/0002-environment-Fix-date-conversion.patch @@ -0,0 +1,33 @@ +From 189add05c07fe9d9bed6c1399b30e51a4a934bd3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 2 Mar 2020 13:46:04 +0100 +Subject: [PATCH 2/6] environment: Fix date conversion + +This is a regression from commit 06b690ff21204: + +GLib.DateTime.new() expects the full four-digit year, so passing +the abbreviated year from Date() will result in a bogus datetime. + +Today is *not* Saturday March 2nd, 120 ... + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1061 +--- + js/ui/environment.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/environment.js b/js/ui/environment.js +index f3f2d17c7..a9cc16dee 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -126,7 +126,7 @@ function init() { + _localTimeZone = GLib.TimeZone.new_local(); + + let dt = GLib.DateTime.new(_localTimeZone, +- this.getYear(), ++ this.getFullYear(), + this.getMonth() + 1, + this.getDate(), + this.getHours(), +-- +2.26.2 + diff --git a/SOURCES/0002-extensionSystem-Get-rid-of-_enabled-boolean-optimiza.patch b/SOURCES/0002-extensionSystem-Get-rid-of-_enabled-boolean-optimiza.patch new file mode 100644 index 0000000..5dba07a --- /dev/null +++ b/SOURCES/0002-extensionSystem-Get-rid-of-_enabled-boolean-optimiza.patch @@ -0,0 +1,237 @@ +From b70cf463e08bff43b242b851fc7c79244f54e76b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 10 Aug 2021 13:25:57 -0400 +Subject: [PATCH 2/4] extensionSystem: Get rid of _enabled boolean optimization + +At the moment a session mode either allows extensions or it doesn't. +If it allows extensions, then the entire available list of +configured extensions get enabled as soon as the session mode is +entered. + +Since enabling or disabling extensions is an all or nothing situation, +the code tracks whether extensions are already enabled when entering +the session mode, and if so, avoids iterating through the extension list +needlessly. It does this using a boolean named _enabled. + +In the future, the extensions themselves will be given some say on +whether or not they should be enabled in a given session mode. This +means, the configured extension list may contain extensions that +shouldn't be enabled for a given session mode, and the _enabled boolean +will no longer be appropriated. + +This commit drops the _enabled boolean optimization. +--- + js/ui/extensionSystem.js | 13 ------------- + 1 file changed, 13 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 77929f2a6..05630ed54 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -1,53 +1,52 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const { GLib, Gio, GObject, St } = imports.gi; + const Signals = imports.signals; + + const ExtensionDownloader = imports.ui.extensionDownloader; + const ExtensionUtils = imports.misc.extensionUtils; + const FileUtils = imports.misc.fileUtils; + const Main = imports.ui.main; + const MessageTray = imports.ui.messageTray; + + const { ExtensionState, ExtensionType } = ExtensionUtils; + + const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; + const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions'; + const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation'; + + const UPDATE_CHECK_TIMEOUT = 24 * 60 * 60; // 1 day in seconds + + var ExtensionManager = class { + constructor() { + this._initted = false; +- this._enabled = false; + this._updateNotified = false; + + this._extensions = new Map(); + this._enabledExtensions = []; + this._extensionOrder = []; + + Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); + } + + init() { + this._installExtensionUpdates(); + this._sessionUpdated(); + + GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => { + ExtensionDownloader.checkForUpdates(); + return GLib.SOURCE_CONTINUE; + }); + ExtensionDownloader.checkForUpdates(); + } + + lookup(uuid) { + return this._extensions.get(uuid); + } + + getUuids() { + return [...this._extensions.keys()]; + } + + _callExtensionDisable(uuid) { + let extension = this.lookup(uuid); +@@ -375,63 +374,60 @@ var ExtensionManager = class { + let hasError = + extension.state == ExtensionState.ERROR || + extension.state == ExtensionState.OUT_OF_DATE; + + let isMode = this._getModeExtensions().includes(extension.uuid); + let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY); + + extension.canChange = + !hasError && + global.settings.is_writable(ENABLED_EXTENSIONS_KEY) && + (isMode || !modeOnly); + } + + _getEnabledExtensions() { + let extensions = this._getModeExtensions(); + + if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY)) + return extensions; + + return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY)); + } + + _onUserExtensionsEnabledChanged() { + this._onEnabledExtensionsChanged(); + this._onSettingsWritableChanged(); + } + + _onEnabledExtensionsChanged() { + let newEnabledExtensions = this._getEnabledExtensions(); + +- if (!this._enabled) +- return; +- + // Find and enable all the newly enabled extensions: UUIDs found in the + // new setting, but not in the old one. + newEnabledExtensions.filter( + uuid => !this._enabledExtensions.includes(uuid) + ).forEach(uuid => { + this._callExtensionEnable(uuid); + }); + + // Find and disable all the newly disabled extensions: UUIDs found in the + // old setting, but not in the new one. + this._enabledExtensions.filter( + item => !newEnabledExtensions.includes(item) + ).forEach(uuid => { + this._callExtensionDisable(uuid); + }); + + this._enabledExtensions = newEnabledExtensions; + } + + _onSettingsWritableChanged() { + for (let extension of this._extensions.values()) { + this._updateCanChange(extension); + this.emit('extension-state-changed', extension); + } + } + + _onVersionValidationChanged() { + // we want to reload all extensions, but only enable + // extensions when allowed by the sessionMode, so + // temporarily disable them all +@@ -482,85 +478,76 @@ var ExtensionManager = class { + + this._enabledExtensions = this._getEnabledExtensions(); + + let perUserDir = Gio.File.new_for_path(global.userdatadir); + FileUtils.collectFromDatadirs('extensions', true, (dir, info) => { + let fileType = info.get_file_type(); + if (fileType != Gio.FileType.DIRECTORY) + return; + let uuid = info.get_name(); + let existing = this.lookup(uuid); + if (existing) { + log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`); + return; + } + + let extension; + let type = dir.has_prefix(perUserDir) + ? ExtensionType.PER_USER + : ExtensionType.SYSTEM; + try { + extension = this.createExtensionObject(uuid, dir, type); + } catch (e) { + logError(e, `Could not load extension ${uuid}`); + return; + } + this.loadExtension(extension); + }); + } + + _enableAllExtensions() { +- if (this._enabled) +- return; +- + if (!this._initted) { + this._loadExtensions(); + this._initted = true; + } else { + this._enabledExtensions.forEach(uuid => { + this._callExtensionEnable(uuid); + }); + } +- this._enabled = true; + } + + _disableAllExtensions() { +- if (!this._enabled) +- return; +- + if (this._initted) { + this._extensionOrder.slice().reverse().forEach(uuid => { + this._callExtensionDisable(uuid); + }); + } +- +- this._enabled = false; + } + + _sessionUpdated() { + // For now sessionMode.allowExtensions controls extensions from both the + // 'enabled-extensions' preference and the sessionMode.enabledExtensions + // property; it might make sense to make enabledExtensions independent + // from allowExtensions in the future + if (Main.sessionMode.allowExtensions) { + // Take care of added or removed sessionMode extensions + this._onEnabledExtensionsChanged(); + this._enableAllExtensions(); + } else { + this._disableAllExtensions(); + } + } + }; + Signals.addSignalMethods(ExtensionManager.prototype); + + class ExtensionUpdateSource extends MessageTray.Source { + constructor() { + const appSys = Shell.AppSystem.get_default(); + this._app = appSys.lookup_app('gnome-shell-extension-prefs.desktop'); + + super(this._app.get_name()); + } + + getIcon() { + return this._app.app_info.get_icon(); + } + +-- +2.27.0 + diff --git a/SOURCES/0002-gdmUtil-enable-support-for-GDM-s-ChoiceList-PAM-exte.patch b/SOURCES/0002-gdmUtil-enable-support-for-GDM-s-ChoiceList-PAM-exte.patch new file mode 100644 index 0000000..b396e1c --- /dev/null +++ b/SOURCES/0002-gdmUtil-enable-support-for-GDM-s-ChoiceList-PAM-exte.patch @@ -0,0 +1,267 @@ +From c3ab03f8721ea96df6ac91c0393ed13ba750ab7e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 17 Jul 2017 16:48:03 -0400 +Subject: [PATCH 2/2] gdmUtil: enable support for GDM's ChoiceList PAM + extension + +This commit hooks up support for GDM's ChoiceList PAM extension. +--- + js/gdm/authPrompt.js | 74 ++++++++++++++++++++++++++++++++++++++++++- + js/gdm/loginDialog.js | 5 +++ + js/gdm/util.js | 28 ++++++++++++++++ + js/ui/unlockDialog.js | 9 +++++- + 4 files changed, 114 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index cf77b3f26..71069e93b 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -4,6 +4,7 @@ const { Clutter, GLib, Pango, Shell, St } = imports.gi; + const Signals = imports.signals; + + const Animation = imports.ui.animation; ++const AuthList = imports.gdm.authList; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; + const Meta = imports.gi.Meta; +@@ -54,6 +55,7 @@ var AuthPrompt = class { + + this._userVerifier.connect('ask-question', this._onAskQuestion.bind(this)); + this._userVerifier.connect('show-message', this._onShowMessage.bind(this)); ++ this._userVerifier.connect('show-choice-list', this._onShowChoiceList.bind(this)); + this._userVerifier.connect('verification-failed', this._onVerificationFailed.bind(this)); + this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this)); + this._userVerifier.connect('reset', this._onReset.bind(this)); +@@ -116,6 +118,28 @@ var AuthPrompt = class { + + this.actor.add(this._timedLoginIndicator); + ++ this._authList = new AuthList.AuthList(); ++ this._authList.connect('activate', (list, key) => { ++ this._authList.actor.reactive = false; ++ Tweener.addTween(this._authList.actor, ++ { opacity: 0, ++ time: MESSAGE_FADE_OUT_ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ this._authList.clear(); ++ this._authList.actor.hide(); ++ this._userVerifier.selectChoice(this._queryingService, key); ++ ++ } ++ }); ++ }); ++ this._authList.actor.hide(); ++ this.actor.add(this._authList.actor, ++ { expand: true, ++ x_fill: true, ++ y_fill: false, ++ x_align: St.Align.START }); ++ + this._message = new St.Label({ opacity: 0, + styleClass: 'login-dialog-message' }); + this._message.clutter_text.line_wrap = true; +@@ -258,6 +282,21 @@ var AuthPrompt = class { + this.emit('prompted'); + } + ++ _onShowChoiceList(userVerifier, serviceName, promptMessage, choiceList) { ++ if (this._queryingService) ++ this.clear(); ++ ++ this._queryingService = serviceName; ++ ++ if (this._preemptiveAnswer) ++ this._preemptiveAnswer = null; ++ ++ this.nextButton.label = _("Next"); ++ this.setChoiceList(promptMessage, choiceList); ++ this.updateSensitivity(true); ++ this.emit('prompted'); ++ } ++ + _onOVirtUserAuthenticated() { + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); +@@ -386,6 +425,8 @@ var AuthPrompt = class { + clear() { + this._entry.text = ''; + this.stopSpinning(); ++ this._authList.clear(); ++ this._authList.actor.hide(); + } + + setPasswordChar(passwordChar) { +@@ -401,12 +442,42 @@ var AuthPrompt = class { + + this._label.set_text(question); + ++ this._authList.actor.hide(); + this._label.show(); + this._entry.show(); + + this._entry.grab_key_focus(); + } + ++ _fadeInChoiceList() { ++ this._authList.actor.opacity = 0; ++ this._authList.actor.show(); ++ this._authList.actor.reactive = false; ++ Tweener.addTween(this._authList.actor, ++ { opacity: 255, ++ time: MESSAGE_FADE_OUT_ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ this._authList.actor.reactive = true; ++ } ++ }); ++ } ++ ++ setChoiceList(promptMessage, choiceList) { ++ this._authList.clear(); ++ this._authList.label.text = promptMessage; ++ for (let key in choiceList) { ++ let text = choiceList[key]; ++ this._authList.addItem(key, text); ++ } ++ ++ this._label.hide(); ++ this._entry.hide(); ++ if (this._message.text == "") ++ this._message.hide(); ++ this._fadeInChoiceList(); ++ } ++ + getAnswer() { + let text; + +@@ -442,6 +513,7 @@ var AuthPrompt = class { + else + this._message.remove_style_class_name('login-dialog-message-hint'); + ++ this._message.show(); + if (message) { + Tweener.removeTweens(this._message); + this._message.text = message; +@@ -457,7 +529,7 @@ var AuthPrompt = class { + } + + updateSensitivity(sensitive) { +- this._updateNextButtonSensitivity(sensitive && (this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING)); ++ this._updateNextButtonSensitivity(sensitive && !this._authList.actor.visible && (this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING)); + this._entry.reactive = sensitive; + this._entry.clutter_text.editable = sensitive; + } +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 9aaa013d8..942f5a0e5 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -406,6 +406,11 @@ var LoginDialog = GObject.registerClass({ + this._userManager = AccountsService.UserManager.get_default() + this._gdmClient = new Gdm.Client(); + ++ try { ++ this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]); ++ } catch(e) { ++ } ++ + this._settings = new Gio.Settings({ schema_id: GdmUtil.LOGIN_SCREEN_SCHEMA }); + + this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY, +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 6e940d2ab..9e249139d 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -192,6 +192,10 @@ var ShellUserVerifier = class { + if (this._userVerifier) { + this._userVerifier.run_dispose(); + this._userVerifier = null; ++ if (this._userVerifierChoiceList) { ++ this._userVerifierChoiceList.run_dispose(); ++ this._userVerifierChoiceList = null; ++ } + } + } + +@@ -219,6 +223,10 @@ var ShellUserVerifier = class { + this._oVirtCredentialsManager = null; + } + ++ selectChoice(serviceName, key) { ++ this._userVerifierChoiceList.call_select_choice(serviceName, key, this._cancellable, null); ++ } ++ + answerQuery(serviceName, answer) { + if (!this.hasPendingMessages) { + this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); +@@ -362,6 +370,11 @@ var ShellUserVerifier = class { + return; + } + ++ if (client.get_user_verifier_choice_list) ++ this._userVerifierChoiceList = client.get_user_verifier_choice_list(); ++ else ++ this._userVerifierChoiceList = null; ++ + this.reauthenticating = true; + this._connectSignals(); + this._beginVerification(); +@@ -379,6 +392,11 @@ var ShellUserVerifier = class { + return; + } + ++ if (client.get_user_verifier_choice_list) ++ this._userVerifierChoiceList = client.get_user_verifier_choice_list(); ++ else ++ this._userVerifierChoiceList = null; ++ + this._connectSignals(); + this._beginVerification(); + this._hold.release(); +@@ -392,6 +410,9 @@ var ShellUserVerifier = class { + this._userVerifier.connect('conversation-stopped', this._onConversationStopped.bind(this)); + this._userVerifier.connect('reset', this._onReset.bind(this)); + this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this)); ++ ++ if (this._userVerifierChoiceList) ++ this._userVerifierChoiceList.connect('choice-query', this._onChoiceListQuery.bind(this)); + } + + _getForegroundService() { +@@ -468,6 +489,13 @@ var ShellUserVerifier = class { + this._startService(FINGERPRINT_SERVICE_NAME); + } + ++ _onChoiceListQuery(client, serviceName, promptMessage, list) { ++ if (!this.serviceIsForeground(serviceName)) ++ return; ++ ++ this.emit('show-choice-list', serviceName, promptMessage, list.deep_unpack()); ++ } ++ + _onInfo(client, serviceName, info) { + if (this.serviceIsForeground(serviceName)) { + this._queueMessage(info, MessageType.INFO); +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 5c9d46021..4b0470f4b 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -33,7 +33,14 @@ var UnlockDialog = class { + y_expand: true }); + this.actor.add_child(this._promptBox); + +- this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY); ++ this._gdmClient = new Gdm.Client(); ++ ++ try { ++ this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]); ++ } catch(e) { ++ } ++ ++ this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_ONLY); + this._authPrompt.connect('failed', this._fail.bind(this)); + this._authPrompt.connect('cancelled', this._fail.bind(this)); + this._authPrompt.connect('reset', this._onReset.bind(this)); +-- +2.21.0 + diff --git a/SOURCES/0002-shellEntry-Give-password-menu-item-text-when-it-s-cr.patch b/SOURCES/0002-shellEntry-Give-password-menu-item-text-when-it-s-cr.patch new file mode 100644 index 0000000..55b3c6f --- /dev/null +++ b/SOURCES/0002-shellEntry-Give-password-menu-item-text-when-it-s-cr.patch @@ -0,0 +1,92 @@ +From de7df6c7248c39d7cce1c70485df72a398da92a3 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2019 15:48:33 -0400 +Subject: [PATCH 2/4] shellEntry: Give password menu item text when it's + created + +At the moment, the "Show Text" menu item is only given its text +at the time the menu is opened. This is because the text might +be "Hide Text" or "Show Text" depending on state, so the text +is set up lazily. + +That behavior means the menu item can't get added after the +menu is already shown, which is something we'ree going to need +in the future to support lockdown of the "Show Text" item. + +This commit ensures the menu item is given text when it's first +created, in addition to when the menu is opened. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/687 +--- + js/ui/shellEntry.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index cac4ec9c2..603a9c64a 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -11,60 +11,61 @@ const Tweener = imports.ui.tweener; + var EntryMenu = class extends PopupMenu.PopupMenu { + constructor(entry) { + super(entry, 0, St.Side.TOP); + + this._entry = entry; + this._clipboard = St.Clipboard.get_default(); + + // Populate menu + let item; + item = new PopupMenu.PopupMenuItem(_("Copy")); + item.connect('activate', this._onCopyActivated.bind(this)); + this.addMenuItem(item); + this._copyItem = item; + + item = new PopupMenu.PopupMenuItem(_("Paste")); + item.connect('activate', this._onPasteActivated.bind(this)); + this.addMenuItem(item); + this._pasteItem = item; + + this._passwordItem = null; + + Main.uiGroup.add_actor(this.actor); + this.actor.hide(); + } + + _makePasswordItem() { + let item = new PopupMenu.PopupMenuItem(''); + item.connect('activate', this._onPasswordActivated.bind(this)); + this.addMenuItem(item); + this._passwordItem = item; ++ this._updatePasswordItem(); + } + + get isPassword() { + return this._entry.input_purpose == Clutter.InputContentPurpose.PASSWORD; + } + + set isPassword(v) { + if (v == this.isPassword) + return; + + if (v) { + this._makePasswordItem(); + this._entry.input_purpose = Clutter.InputContentPurpose.PASSWORD; + } else { + this._passwordItem.destroy(); + this._passwordItem = null; + this._entry.input_purpose = Clutter.InputContentPurpose.NORMAL; + } + } + + open(animate) { + this._updatePasteItem(); + this._updateCopyItem(); + if (this._passwordItem) + this._updatePasswordItem(); + + super.open(animate); + this._entry.add_style_pseudo_class('focus'); + + let direction = St.DirectionType.TAB_FORWARD; +-- +2.27.0 + diff --git a/SOURCES/0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch b/SOURCES/0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch new file mode 100644 index 0000000..c2dcedb --- /dev/null +++ b/SOURCES/0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch @@ -0,0 +1,393 @@ +From 7300ae2eac743fa06f40f6459ac8fbf739ab28ea Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 10 Aug 2021 15:03:50 -0400 +Subject: [PATCH 3/4] extensionSystem: Allow extensions to run on the login + screen + +At the moment it's not realy possible to extend the login screen to do +things it doesn't have built-in support for. This means in order +to support niche use cases, those cases have to change the main +code base. For instance, oVirt and Vmware deployments want to be able +to automaticaly log in guest VMs when a user pre-authenticates through a +console on a management host. To support those use cases, we added +code to the login screen directly, even though most machines will never +be associated with oVirt or Vmware management hosts. + +We also get requests from e.g. government users that need certain features +at the login screen that wouldn't get used much outside of government +deployments. For instance, we've gotten requests that a machine contains +prominently displays that it has "Top Secret" information. + +All of these use cases seem like they would better handled via +extensions that could be installed in the specific deployments. The +problem is extensions only run in the user session, and get +disabled at the login screen automatically. + +This commit changes that. Now extensions can specify in their metadata +via a new sessionModes property, which modes that want to run in. For +backward compatibility, if an extension doesn't specify which session +modes it works in, its assumed the extension only works in the user +session. +--- + js/ui/extensionSystem.js | 43 ++++++++++++++++++++++++++++++++++++---- + 1 file changed, 39 insertions(+), 4 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 05630ed54..dfe82821e 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -21,119 +21,147 @@ var ExtensionManager = class { + constructor() { + this._initted = false; + this._updateNotified = false; + + this._extensions = new Map(); + this._enabledExtensions = []; + this._extensionOrder = []; + + Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); + } + + init() { + this._installExtensionUpdates(); + this._sessionUpdated(); + + GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => { + ExtensionDownloader.checkForUpdates(); + return GLib.SOURCE_CONTINUE; + }); + ExtensionDownloader.checkForUpdates(); + } + + lookup(uuid) { + return this._extensions.get(uuid); + } + + getUuids() { + return [...this._extensions.keys()]; + } + ++ _extensionSupportsSessionMode(uuid) { ++ let extension = this.lookup(uuid); ++ ++ if (!extension) ++ return false; ++ ++ if (extension.sessionModes.includes(Main.sessionMode.currentMode)) ++ return true; ++ ++ if (extension.sessionModes.includes(Main.sessionMode.parentMode)) ++ return true; ++ ++ return false; ++ } ++ ++ _sessionModeCanUseExtension(uuid) { ++ if (!Main.sessionMode.allowExtensions) ++ return false; ++ ++ if (!this._extensionSupportsSessionMode(uuid)) ++ return false; ++ ++ return true; ++ } ++ + _callExtensionDisable(uuid) { + let extension = this.lookup(uuid); + if (!extension) + return; + + if (extension.state != ExtensionState.ENABLED) + return; + + // "Rebase" the extension order by disabling and then enabling extensions + // in order to help prevent conflicts. + + // Example: + // order = [A, B, C, D, E] + // user disables C + // this should: disable E, disable D, disable C, enable D, enable E + + let orderIdx = this._extensionOrder.indexOf(uuid); + let order = this._extensionOrder.slice(orderIdx + 1); + let orderReversed = order.slice().reverse(); + + for (let i = 0; i < orderReversed.length; i++) { + let uuid = orderReversed[i]; + try { + this.lookup(uuid).stateObj.disable(); + } catch (e) { + this.logExtensionError(uuid, e); + } + } + + if (extension.stylesheet) { + let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); + theme.unload_stylesheet(extension.stylesheet); + delete extension.stylesheet; + } + + try { + extension.stateObj.disable(); + } catch(e) { + this.logExtensionError(uuid, e); + } + + for (let i = 0; i < order.length; i++) { + let uuid = order[i]; + try { + this.lookup(uuid).stateObj.enable(); + } catch (e) { + this.logExtensionError(uuid, e); + } + } + + this._extensionOrder.splice(orderIdx, 1); + + if (extension.state != ExtensionState.ERROR) { + extension.state = ExtensionState.DISABLED; + this.emit('extension-state-changed', extension); + } + } + + _callExtensionEnable(uuid) { ++ if (!this._sessionModeCanUseExtension(uuid)) ++ return; ++ + let extension = this.lookup(uuid); + if (!extension) + return; + + if (extension.state == ExtensionState.INITIALIZED) + this._callExtensionInit(uuid); + + if (extension.state != ExtensionState.DISABLED) + return; + + this._extensionOrder.push(uuid); + + let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css']; + let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); + for (let i = 0; i < stylesheetNames.length; i++) { + try { + let stylesheetFile = extension.dir.get_child(stylesheetNames[i]); + theme.load_stylesheet(stylesheetFile); + extension.stylesheet = stylesheetFile; + break; + } catch (e) { + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) + continue; // not an error + log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`); + return; + } + } + + try { + extension.stateObj.enable(); +@@ -231,61 +259,62 @@ var ExtensionManager = class { + throw new Error(`Failed to load metadata.json: ${e}`); + } + let meta; + try { + meta = JSON.parse(metadataContents); + } catch (e) { + throw new Error(`Failed to parse metadata.json: ${e}`); + } + + let requiredProperties = ['uuid', 'name', 'description', 'shell-version']; + for (let i = 0; i < requiredProperties.length; i++) { + let prop = requiredProperties[i]; + if (!meta[prop]) { + throw new Error(`missing "${prop}" property in metadata.json`); + } + } + + if (uuid != meta.uuid) { + throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`); + } + + let extension = { + metadata: meta, + uuid: meta.uuid, + type, + dir, + path: dir.get_path(), + error: '', + hasPrefs: dir.get_child('prefs.js').query_exists(null), + hasUpdate: false, +- canChange: false ++ canChange: false, ++ sessionModes: meta['session-modes'] ? meta['session-modes'] : [ 'user' ], + }; + this._extensions.set(uuid, extension); + + return extension; + } + + loadExtension(extension) { + // Default to error, we set success as the last step + extension.state = ExtensionState.ERROR; + + let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); + + if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { + extension.state = ExtensionState.OUT_OF_DATE; + } else { + let enabled = this._enabledExtensions.includes(extension.uuid); + if (enabled) { + if (!this._callExtensionInit(extension.uuid)) + return; + if (extension.state == ExtensionState.DISABLED) + this._callExtensionEnable(extension.uuid); + } else { + extension.state = ExtensionState.INITIALIZED; + } + } + + this._updateCanChange(extension); + this.emit('extension-state-changed', extension); + } + +@@ -296,60 +325,63 @@ var ExtensionManager = class { + this._callExtensionDisable(extension.uuid); + + extension.state = ExtensionState.UNINSTALLED; + this.emit('extension-state-changed', extension); + + this._extensions.delete(extension.uuid); + return true; + } + + reloadExtension(oldExtension) { + // Grab the things we'll need to pass to createExtensionObject + // to reload it. + let { uuid: uuid, dir: dir, type: type } = oldExtension; + + // Then unload the old extension. + this.unloadExtension(oldExtension); + + // Now, recreate the extension and load it. + let newExtension; + try { + newExtension = this.createExtensionObject(uuid, dir, type); + } catch (e) { + this.logExtensionError(uuid, e); + return; + } + + this.loadExtension(newExtension); + } + + _callExtensionInit(uuid) { ++ if (!this._sessionModeCanUseExtension(uuid)) ++ return false; ++ + let extension = this.lookup(uuid); + let dir = extension.dir; + + if (!extension) + throw new Error("Extension was not properly created. Call loadExtension first"); + + let extensionJs = dir.get_child('extension.js'); + if (!extensionJs.query_exists(null)) { + this.logExtensionError(uuid, new Error('Missing extension.js')); + return false; + } + + let extensionModule; + let extensionState = null; + + ExtensionUtils.installImporter(extension); + try { + extensionModule = extension.imports.extension; + } catch(e) { + this.logExtensionError(uuid, e); + return false; + } + + if (extensionModule.init) { + try { + extensionState = extensionModule.init(extension); + } catch (e) { + this.logExtensionError(uuid, e); + return false; + } +@@ -377,69 +409,72 @@ var ExtensionManager = class { + + let isMode = this._getModeExtensions().includes(extension.uuid); + let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY); + + extension.canChange = + !hasError && + global.settings.is_writable(ENABLED_EXTENSIONS_KEY) && + (isMode || !modeOnly); + } + + _getEnabledExtensions() { + let extensions = this._getModeExtensions(); + + if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY)) + return extensions; + + return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY)); + } + + _onUserExtensionsEnabledChanged() { + this._onEnabledExtensionsChanged(); + this._onSettingsWritableChanged(); + } + + _onEnabledExtensionsChanged() { + let newEnabledExtensions = this._getEnabledExtensions(); + + // Find and enable all the newly enabled extensions: UUIDs found in the + // new setting, but not in the old one. + newEnabledExtensions.filter( +- uuid => !this._enabledExtensions.includes(uuid) ++ uuid => !this._enabledExtensions.includes(uuid) && ++ this._extensionSupportsSessionMode(uuid) + ).forEach(uuid => { + this._callExtensionEnable(uuid); + }); + + // Find and disable all the newly disabled extensions: UUIDs found in the +- // old setting, but not in the new one. ++ // old setting, but not in the new one, and extensions that don't work with ++ // the current session mode. + this._enabledExtensions.filter( +- item => !newEnabledExtensions.includes(item) ++ item => !newEnabledExtensions.includes(item) || ++ !this._extensionSupportsSessionMode(item) + ).forEach(uuid => { + this._callExtensionDisable(uuid); + }); + + this._enabledExtensions = newEnabledExtensions; + } + + _onSettingsWritableChanged() { + for (let extension of this._extensions.values()) { + this._updateCanChange(extension); + this.emit('extension-state-changed', extension); + } + } + + _onVersionValidationChanged() { + // we want to reload all extensions, but only enable + // extensions when allowed by the sessionMode, so + // temporarily disable them all + this._enabledExtensions = []; + + // The loop modifies the extensions map, so iterate over a copy + let extensions = [...this._extensions.values()]; + for (let extension of extensions) + this.reloadExtension(extension); + this._enabledExtensions = this._getEnabledExtensions(); + + if (Main.sessionMode.allowExtensions) { + this._enabledExtensions.forEach(uuid => { + this._callExtensionEnable(uuid); + }); +-- +2.27.0 + diff --git a/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch b/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch new file mode 100644 index 0000000..89a3a80 --- /dev/null +++ b/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch @@ -0,0 +1,152 @@ +From c9277326055c96185a80b68d4228eee360bb0e7c Mon Sep 17 00:00:00 2001 +From: Georges Basile Stavracas Neto +Date: Thu, 1 Aug 2019 20:58:20 -0300 +Subject: [PATCH 3/6] shell/app-system: Monitor for icon theme changes + +Whenever an app is installed, the usual routine is +to run 'gtk-update-icon-cache' after installing all +of the app's files. + +The side effect of that is that the .desktop file of +the application is installed before the icon theme +is updated. By the time GAppInfoMonitor emits the +'changed' signal, the icon theme is not yet updated, +leading to StIcon use the fallback icon. + +Under some circumstances (e.g. on very slow spinning +disks) the app icon is never actually loaded, and we +see the fallback icon forever. + +Monitor the icon theme for changes when an app is +installed. Try as many as 6 times before giving up +on detecting an icon theme update. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/661 +--- + src/shell-app-system.c | 54 +++++++++++++++++++++++++++++++++++++++ + src/st/st-texture-cache.c | 8 ++++++ + src/st/st-texture-cache.h | 2 ++ + 3 files changed, 64 insertions(+) + +diff --git a/src/shell-app-system.c b/src/shell-app-system.c +index f632cbe54..127f29ef0 100644 +--- a/src/shell-app-system.c ++++ b/src/shell-app-system.c +@@ -14,6 +14,14 @@ + #include "shell-app-system-private.h" + #include "shell-global.h" + #include "shell-util.h" ++#include "st.h" ++ ++/* Rescan for at most RESCAN_TIMEOUT_MS * MAX_RESCAN_RETRIES. That ++ * should be plenty of time for even a slow spinning drive to update ++ * the icon cache. ++ */ ++#define RESCAN_TIMEOUT_MS 2500 ++#define MAX_RESCAN_RETRIES 6 + + /* Vendor prefixes are something that can be preprended to a .desktop + * file name. Undo this. +@@ -51,6 +59,9 @@ struct _ShellAppSystemPrivate { + GHashTable *id_to_app; + GHashTable *startup_wm_class_to_id; + GList *installed_apps; ++ ++ guint rescan_icons_timeout_id; ++ guint n_rescan_retries; + }; + + static void shell_app_system_finalize (GObject *object); +@@ -157,12 +168,54 @@ stale_app_remove_func (gpointer key, + return app_is_stale (value); + } + ++static gboolean ++rescan_icon_theme_cb (gpointer user_data) ++{ ++ ShellAppSystemPrivate *priv; ++ ShellAppSystem *self; ++ StTextureCache *texture_cache; ++ gboolean rescanned; ++ ++ self = (ShellAppSystem *) user_data; ++ priv = self->priv; ++ ++ texture_cache = st_texture_cache_get_default (); ++ rescanned = st_texture_cache_rescan_icon_theme (texture_cache); ++ ++ priv->n_rescan_retries++; ++ ++ if (rescanned || priv->n_rescan_retries >= MAX_RESCAN_RETRIES) ++ { ++ priv->n_rescan_retries = 0; ++ priv->rescan_icons_timeout_id = 0; ++ return G_SOURCE_REMOVE; ++ } ++ ++ return G_SOURCE_CONTINUE; ++} ++ ++static void ++rescan_icon_theme (ShellAppSystem *self) ++{ ++ ShellAppSystemPrivate *priv = self->priv; ++ ++ priv->n_rescan_retries = 0; ++ ++ if (priv->rescan_icons_timeout_id > 0) ++ return; ++ ++ priv->rescan_icons_timeout_id = g_timeout_add (RESCAN_TIMEOUT_MS, ++ rescan_icon_theme_cb, ++ self); ++} ++ + static void + installed_changed (GAppInfoMonitor *monitor, + gpointer user_data) + { + ShellAppSystem *self = user_data; + ++ rescan_icon_theme (self); + scan_startup_wm_class_to_id (self); + + g_hash_table_foreach_remove (self->priv->id_to_app, stale_app_remove_func, NULL); +@@ -200,6 +253,7 @@ shell_app_system_finalize (GObject *object) + g_hash_table_destroy (priv->id_to_app); + g_hash_table_destroy (priv->startup_wm_class_to_id); + g_list_free_full (priv->installed_apps, g_object_unref); ++ g_clear_handle_id (&priv->rescan_icons_timeout_id, g_source_remove); + + G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object); + } +diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c +index c1331747f..4d0d617c4 100644 +--- a/src/st/st-texture-cache.c ++++ b/src/st/st-texture-cache.c +@@ -1554,3 +1554,11 @@ st_texture_cache_get_default (void) + instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL); + return instance; + } ++ ++gboolean ++st_texture_cache_rescan_icon_theme (StTextureCache *cache) ++{ ++ StTextureCachePrivate *priv = cache->priv; ++ ++ return gtk_icon_theme_rescan_if_needed (priv->icon_theme); ++} +diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h +index 11d1c4e64..a99316da8 100644 +--- a/src/st/st-texture-cache.h ++++ b/src/st/st-texture-cache.h +@@ -113,4 +113,6 @@ CoglTexture * st_texture_cache_load (StTextureCache *cache, + void *data, + GError **error); + ++gboolean st_texture_cache_rescan_icon_theme (StTextureCache *cache); ++ + #endif /* __ST_TEXTURE_CACHE_H__ */ +-- +2.26.2 + diff --git a/SOURCES/0003-shellEntry-Handle-password-item-from-dedication-func.patch b/SOURCES/0003-shellEntry-Handle-password-item-from-dedication-func.patch new file mode 100644 index 0000000..46afc47 --- /dev/null +++ b/SOURCES/0003-shellEntry-Handle-password-item-from-dedication-func.patch @@ -0,0 +1,119 @@ +From 39cf97176e2a92506081ee151ea546e2c6cf213a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2019 15:06:46 -0400 +Subject: [PATCH 3/4] shellEntry: Handle password item from dedication function + +At the moment, shellEntry handles creating and destroying its +"Show Text" password menu item directly from its isPassword +setter function. + +This commit moves that handling to a dedicated _resetPasswordItem +function, as prep work for adding lockdown support of the "Show Text" +menu item. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/687 +--- + js/ui/shellEntry.js | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 603a9c64a..765cede06 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -14,76 +14,87 @@ var EntryMenu = class extends PopupMenu.PopupMenu { + + this._entry = entry; + this._clipboard = St.Clipboard.get_default(); + + // Populate menu + let item; + item = new PopupMenu.PopupMenuItem(_("Copy")); + item.connect('activate', this._onCopyActivated.bind(this)); + this.addMenuItem(item); + this._copyItem = item; + + item = new PopupMenu.PopupMenuItem(_("Paste")); + item.connect('activate', this._onPasteActivated.bind(this)); + this.addMenuItem(item); + this._pasteItem = item; + + this._passwordItem = null; + + Main.uiGroup.add_actor(this.actor); + this.actor.hide(); + } + + _makePasswordItem() { + let item = new PopupMenu.PopupMenuItem(''); + item.connect('activate', this._onPasswordActivated.bind(this)); + this.addMenuItem(item); + this._passwordItem = item; + this._updatePasswordItem(); + } + ++ _resetPasswordItem() { ++ if (!this.isPassword) { ++ if (this._passwordItem) { ++ this._passwordItem.destroy(); ++ this._passwordItem = null; ++ } ++ this._entry.clutter_text.set_password_char('\u25cf'); ++ } else { ++ if (!this._passwordItem) ++ this._makePasswordItem(); ++ } ++ } ++ + get isPassword() { + return this._entry.input_purpose == Clutter.InputContentPurpose.PASSWORD; + } + + set isPassword(v) { + if (v == this.isPassword) + return; + +- if (v) { +- this._makePasswordItem(); ++ if (v) + this._entry.input_purpose = Clutter.InputContentPurpose.PASSWORD; +- } else { +- this._passwordItem.destroy(); +- this._passwordItem = null; ++ else + this._entry.input_purpose = Clutter.InputContentPurpose.NORMAL; +- } ++ ++ this._resetPasswordItem(); + } + + open(animate) { + this._updatePasteItem(); + this._updateCopyItem(); + if (this._passwordItem) + this._updatePasswordItem(); + + super.open(animate); + this._entry.add_style_pseudo_class('focus'); + + let direction = St.DirectionType.TAB_FORWARD; + if (!this.actor.navigate_focus(null, direction, false)) + this.actor.grab_key_focus(); + } + + _updateCopyItem() { + let selection = this._entry.clutter_text.get_selection(); + this._copyItem.setSensitive(!this._entry.clutter_text.password_char && + selection && selection != ''); + } + + _updatePasteItem() { + this._clipboard.get_text(St.ClipboardType.CLIPBOARD, + (clipboard, text) => { + this._pasteItem.setSensitive(text && text != ''); + }); + } + + _updatePasswordItem() { +-- +2.27.0 + diff --git a/SOURCES/0003-st-texture-cache-purge-on-resume.patch b/SOURCES/0003-st-texture-cache-purge-on-resume.patch new file mode 100644 index 0000000..41c288d --- /dev/null +++ b/SOURCES/0003-st-texture-cache-purge-on-resume.patch @@ -0,0 +1,66 @@ +From 2ebeda3385fb679df4bc13ba4b80bdeba5e2ad13 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 15 Jan 2019 12:54:32 -0500 +Subject: [PATCH 3/4] st-texture-cache: purge on resume + +With the proprietary nvidia driver, textures get garbled on suspend, +so the texture cache needs to evict all textures in that situation. +--- + js/ui/main.js | 6 +++++- + src/st/st-texture-cache.c | 10 ++++++++++ + src/st/st-texture-cache.h | 1 + + 3 files changed, 16 insertions(+), 1 deletion(-) + +diff --git a/js/ui/main.js b/js/ui/main.js +index 061303cf3..8d1755cf1 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -200,7 +200,11 @@ function _initializeUI() { + return true; + }); + +- global.display.connect('gl-video-memory-purged', loadTheme); ++ global.display.connect('gl-video-memory-purged', () => { ++ let cache = St.TextureCache.get_default(); ++ cache.clear(); ++ loadTheme(); ++ }); + + // Provide the bus object for gnome-session to + // initiate logouts. +diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c +index cbe3afaba..40a11dd6d 100644 +--- a/src/st/st-texture-cache.c ++++ b/src/st/st-texture-cache.c +@@ -113,6 +113,16 @@ st_texture_cache_class_init (StTextureCacheClass *klass) + G_TYPE_NONE, 1, G_TYPE_FILE); + } + ++/* Evicts all cached textures */ ++void ++st_texture_cache_clear (StTextureCache *cache) ++{ ++ g_return_if_fail (ST_IS_TEXTURE_CACHE (cache)); ++ ++ g_hash_table_remove_all (cache->priv->keyed_cache); ++ g_signal_emit (cache, signals[ICON_THEME_CHANGED], 0); ++} ++ + /* Evicts all cached textures for named icons */ + static void + st_texture_cache_evict_icons (StTextureCache *cache) +diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h +index 11d1c4e64..9079d1fda 100644 +--- a/src/st/st-texture-cache.h ++++ b/src/st/st-texture-cache.h +@@ -53,6 +53,7 @@ typedef enum { + } StTextureCachePolicy; + + StTextureCache* st_texture_cache_get_default (void); ++void st_texture_cache_clear (StTextureCache *cache); + + ClutterActor * + st_texture_cache_load_sliced_image (StTextureCache *cache, +-- +2.21.0 + diff --git a/SOURCES/0004-background-refresh-background-on-gl-video-memory-pur.patch b/SOURCES/0004-background-refresh-background-on-gl-video-memory-pur.patch new file mode 100644 index 0000000..0f81f47 --- /dev/null +++ b/SOURCES/0004-background-refresh-background-on-gl-video-memory-pur.patch @@ -0,0 +1,115 @@ +From 055bc14c70af66fe1893dcd4c42c65662ae1f9d0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 21 Jan 2019 15:07:15 -0500 +Subject: [PATCH 4/4] background: refresh background on gl-video-memory-purged + signal + +Right now we refresh the background when resuming and when NVIDIA. +But mutter has a signal to tell us specifically when to refresh, +and the signal is only emitted for NVIDIA, so use that instead. +--- + js/ui/background.js | 9 +++++++-- + js/ui/layout.js | 12 ------------ + src/shell-util.c | 27 --------------------------- + src/shell-util.h | 2 -- + 4 files changed, 7 insertions(+), 43 deletions(-) + +diff --git a/js/ui/background.js b/js/ui/background.js +index 75b76a57e..466cc4de7 100644 +--- a/js/ui/background.js ++++ b/js/ui/background.js +@@ -527,10 +527,15 @@ var BackgroundSource = class BackgroundSource { + let monitorManager = Meta.MonitorManager.get(); + this._monitorsChangedId = + monitorManager.connect('monitors-changed', +- this._onMonitorsChanged.bind(this)); ++ this._refresh.bind(this)); ++ ++ global.display.connect('gl-video-memory-purged', () => { ++ Meta.Background.refresh_all(); ++ this._refresh(); ++ }); + } + +- _onMonitorsChanged() { ++ _refresh() { + for (let monitorIndex in this._backgrounds) { + let background = this._backgrounds[monitorIndex]; + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 30e750dc5..2b3bb7442 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -282,18 +282,6 @@ var LayoutManager = GObject.registerClass({ + monitorManager.connect('monitors-changed', + this._monitorsChanged.bind(this)); + this._monitorsChanged(); +- +- // NVIDIA drivers don't preserve FBO contents across +- // suspend/resume, see +- // https://bugzilla.gnome.org/show_bug.cgi?id=739178 +- if (Shell.util_need_background_refresh()) { +- LoginManager.getLoginManager().connect('prepare-for-sleep', +- (lm, suspending) => { +- if (suspending) +- return; +- Meta.Background.refresh_all(); +- }); +- } + } + + // This is called by Main after everything else is constructed +diff --git a/src/shell-util.c b/src/shell-util.c +index c6e5abed6..9c25643c6 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -374,33 +374,6 @@ shell_util_create_pixbuf_from_data (const guchar *data, + (GdkPixbufDestroyNotify) g_free, NULL); + } + +-typedef const gchar *(*ShellGLGetString) (GLenum); +- +-static const gchar * +-get_gl_vendor (void) +-{ +- static const gchar *vendor = NULL; +- +- if (!vendor) +- { +- ShellGLGetString gl_get_string; +- gl_get_string = (ShellGLGetString) cogl_get_proc_address ("glGetString"); +- if (gl_get_string) +- vendor = gl_get_string (GL_VENDOR); +- } +- +- return vendor; +-} +- +-gboolean +-shell_util_need_background_refresh (void) +-{ +- if (g_strcmp0 (get_gl_vendor (), "NVIDIA Corporation") == 0) +- return TRUE; +- +- return FALSE; +-} +- + static gboolean + canvas_draw_cb (ClutterContent *content, + cairo_t *cr, +diff --git a/src/shell-util.h b/src/shell-util.h +index 6904f43bc..049c3fe18 100644 +--- a/src/shell-util.h ++++ b/src/shell-util.h +@@ -44,8 +44,6 @@ GdkPixbuf *shell_util_create_pixbuf_from_data (const guchar *data, + int height, + int rowstride); + +-gboolean shell_util_need_background_refresh (void); +- + ClutterContent * shell_util_get_content_for_window_actor (MetaWindowActor *window_actor, + MetaRectangle *window_rect); + +-- +2.21.0 + diff --git a/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch b/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch new file mode 100644 index 0000000..2271311 --- /dev/null +++ b/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch @@ -0,0 +1,124 @@ +From 2a4f33df723d4b9ce68e5948b568a89675d37411 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 26 Feb 2020 14:46:20 -0800 +Subject: [PATCH 4/6] global: force fsync() to worker thread when saving state + +The g_file_replace_contents_async() API can potentially call fsync() from +the thread calling into it upon completion. This can have disasterous +effects when run from the compositor main thread such as complete stalls. + +This is a followup to 86a00b6872375a266449beee1ea6d5e94f1ebbcb which +assumed (like the rest of us) that the fsync() would be performed on the +thread that was doing the I/O operations. + +You can verify this with an strace -e fsync and cause terminal to display +a command completed notification (eg: from a backdrop window). + +This also fixes a lifecycle bug for the variant, as +g_file_replace_contents_async() does not copy the data during the operation +as that is the responsibility of the caller. Instead, we just use a GBytes +variant and reference the variant there. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1050 +--- + src/shell-global.c | 70 +++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 63 insertions(+), 7 deletions(-) + +diff --git a/src/shell-global.c b/src/shell-global.c +index df84b6b0d..4b33778e0 100644 +--- a/src/shell-global.c ++++ b/src/shell-global.c +@@ -1572,6 +1572,55 @@ delete_variant_cb (GObject *object, + g_hash_table_remove (global->save_ops, object); + } + ++static void ++replace_contents_worker (GTask *task, ++ gpointer source_object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ GFile *file = source_object; ++ GBytes *bytes = task_data; ++ GError *error = NULL; ++ const gchar *data; ++ gsize len; ++ ++ data = g_bytes_get_data (bytes, &len); ++ ++ if (!g_file_replace_contents (file, data, len, NULL, FALSE, ++ G_FILE_CREATE_REPLACE_DESTINATION, ++ NULL, cancellable, &error)) ++ g_task_return_error (task, g_steal_pointer (&error)); ++ else ++ g_task_return_boolean (task, TRUE); ++} ++ ++static void ++replace_contents_async (GFile *path, ++ GBytes *bytes, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_assert (G_IS_FILE (path)); ++ g_assert (bytes != NULL); ++ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (path, cancellable, callback, user_data); ++ g_task_set_source_tag (task, replace_contents_async); ++ g_task_set_task_data (task, g_bytes_ref (bytes), (GDestroyNotify)g_bytes_unref); ++ g_task_run_in_thread (task, replace_contents_worker); ++} ++ ++static gboolean ++replace_contents_finish (GFile *file, ++ GAsyncResult *result, ++ GError **error) ++{ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ + static void + replace_variant_cb (GObject *object, + GAsyncResult *result, +@@ -1580,7 +1629,7 @@ replace_variant_cb (GObject *object, + ShellGlobal *global = user_data; + GError *error = NULL; + +- if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error)) ++ if (!replace_contents_finish (G_FILE (object), result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { +@@ -1616,12 +1665,19 @@ save_variant (ShellGlobal *global, + } + else + { +- g_file_replace_contents_async (path, +- g_variant_get_data (variant), +- g_variant_get_size (variant), +- NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, +- cancellable, replace_variant_cb, global); ++ g_autoptr(GBytes) bytes = NULL; ++ ++ bytes = g_bytes_new_with_free_func (g_variant_get_data (variant), ++ g_variant_get_size (variant), ++ (GDestroyNotify)g_variant_unref, ++ g_variant_ref (variant)); ++ /* g_file_replace_contents_async() can potentially fsync() from the ++ * calling thread when completing the asynchronous task. Instead, we ++ * want to force that fsync() to a thread to avoid blocking the ++ * compository main loop. Using our own replace_contents_async() ++ * simply executes the operation synchronously from a thread. ++ */ ++ replace_contents_async (path, bytes, cancellable, replace_variant_cb, global); + } + + g_object_unref (path); +-- +2.26.2 + diff --git a/SOURCES/0004-sessionMode-Allow-extensions-at-the-login-and-unlock.patch b/SOURCES/0004-sessionMode-Allow-extensions-at-the-login-and-unlock.patch new file mode 100644 index 0000000..f19fecd --- /dev/null +++ b/SOURCES/0004-sessionMode-Allow-extensions-at-the-login-and-unlock.patch @@ -0,0 +1,116 @@ +From 5fad989ca773f9e0ff6fdbeb0cb7c9cb70cc6148 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 10 Aug 2021 15:31:00 -0400 +Subject: [PATCH 4/4] sessionMode: Allow extensions at the login and unlock + screens + +Now extensions can specify which session modes they work in, +but specifying the login screen or unlock screen session modes in +an extensions metadata still won't work, because those session +modes disallow extensions. + +This commit fixes that. +--- + js/ui/sessionMode.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js +index fa7f83416..8d8ce1a64 100644 +--- a/js/ui/sessionMode.js ++++ b/js/ui/sessionMode.js +@@ -12,89 +12,92 @@ const Config = imports.misc.config; + const DEFAULT_MODE = 'restrictive'; + + const _modes = { + 'restrictive': { + parentMode: null, + stylesheetName: 'gnome-shell.css', + hasOverview: false, + showCalendarEvents: false, + allowSettings: false, + allowExtensions: false, + allowScreencast: false, + enabledExtensions: [], + hasRunDialog: false, + hasWorkspaces: false, + hasWindows: false, + hasNotifications: false, + isLocked: false, + isGreeter: false, + isPrimary: false, + unlockDialog: null, + components: [], + panel: { + left: [], + center: [], + right: [] + }, + panelStyle: null + }, + + 'gdm': { ++ allowExtensions: true, + hasNotifications: true, + isGreeter: true, + isPrimary: true, + unlockDialog: imports.gdm.loginDialog.LoginDialog, + components: Config.HAVE_NETWORKMANAGER + ? ['networkAgent', 'polkitAgent'] + : ['polkitAgent'], + panel: { + left: [], + center: ['dateMenu'], + right: ['a11y', 'keyboard', 'aggregateMenu'] + }, + panelStyle: 'login-screen' + }, + + 'lock-screen': { ++ allowExtensions: true, + isLocked: true, + isGreeter: undefined, + unlockDialog: undefined, + components: ['polkitAgent', 'telepathyClient'], + panel: { + left: [], + center: [], + right: ['aggregateMenu'] + }, + panelStyle: 'lock-screen' + }, + + 'unlock-dialog': { ++ allowExtensions: true, + isLocked: true, + unlockDialog: undefined, + components: ['polkitAgent', 'telepathyClient'], + panel: { + left: [], + center: [], + right: ['a11y', 'keyboard', 'aggregateMenu'] + }, + panelStyle: 'unlock-screen' + }, + + 'user': { + hasOverview: true, + showCalendarEvents: true, + allowSettings: true, + allowExtensions: true, + allowScreencast: true, + hasRunDialog: true, + hasWorkspaces: true, + hasWindows: true, + hasNotifications: true, + isLocked: false, + isPrimary: true, + unlockDialog: imports.ui.unlockDialog.UnlockDialog, + components: Config.HAVE_NETWORKMANAGER ? + ['networkAgent', 'polkitAgent', 'telepathyClient', + 'keyring', 'autorunManager', 'automountManager'] : + ['polkitAgent', 'telepathyClient', + 'keyring', 'autorunManager', 'automountManager'], + +-- +2.27.0 + diff --git a/SOURCES/0004-shellEntry-Support-lockdown-of-Show-Text-menu-in-pas.patch b/SOURCES/0004-shellEntry-Support-lockdown-of-Show-Text-menu-in-pas.patch new file mode 100644 index 0000000..8d8a7d7 --- /dev/null +++ b/SOURCES/0004-shellEntry-Support-lockdown-of-Show-Text-menu-in-pas.patch @@ -0,0 +1,116 @@ +From ee64cd773bdeef845d02dc84063f926d77090dec Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 21 Aug 2019 15:06:46 -0400 +Subject: [PATCH 4/4] shellEntry: Support lockdown of "Show Text" menu in + password entries + +Some deployments require being able to prevent users from showing +the password they're currently typing. + +This commit adds support for that kind of lockdown. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/687 +--- + js/ui/shellEntry.js | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 765cede06..c45e4545a 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -1,81 +1,89 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Clutter, GObject, Pango, Shell, St } = imports.gi; ++const { Clutter, Gio, GObject, Pango, Shell, St } = imports.gi; + + const BoxPointer = imports.ui.boxpointer; + const Main = imports.ui.main; + const Params = imports.misc.params; + const PopupMenu = imports.ui.popupMenu; + const Tweener = imports.ui.tweener; + ++const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; ++const DISABLE_SHOW_PASSWORD_KEY = 'disable-show-password'; ++ + var EntryMenu = class extends PopupMenu.PopupMenu { + constructor(entry) { + super(entry, 0, St.Side.TOP); + ++ this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA }); ++ this._lockdownSettings.connect('changed::' + DISABLE_SHOW_PASSWORD_KEY, this._resetPasswordItem.bind(this)); ++ + this._entry = entry; + this._clipboard = St.Clipboard.get_default(); + + // Populate menu + let item; + item = new PopupMenu.PopupMenuItem(_("Copy")); + item.connect('activate', this._onCopyActivated.bind(this)); + this.addMenuItem(item); + this._copyItem = item; + + item = new PopupMenu.PopupMenuItem(_("Paste")); + item.connect('activate', this._onPasteActivated.bind(this)); + this.addMenuItem(item); + this._pasteItem = item; + + this._passwordItem = null; + + Main.uiGroup.add_actor(this.actor); + this.actor.hide(); + } + + _makePasswordItem() { + let item = new PopupMenu.PopupMenuItem(''); + item.connect('activate', this._onPasswordActivated.bind(this)); + this.addMenuItem(item); + this._passwordItem = item; + this._updatePasswordItem(); + } + + _resetPasswordItem() { +- if (!this.isPassword) { ++ let passwordDisabled = this._lockdownSettings.get_boolean(DISABLE_SHOW_PASSWORD_KEY); ++ ++ if (!this.isPassword || passwordDisabled) { + if (this._passwordItem) { + this._passwordItem.destroy(); + this._passwordItem = null; + } + this._entry.clutter_text.set_password_char('\u25cf'); +- } else { ++ } else if (this.isPassword && !passwordDisabled) { + if (!this._passwordItem) + this._makePasswordItem(); + } + } + + get isPassword() { + return this._entry.input_purpose == Clutter.InputContentPurpose.PASSWORD; + } + + set isPassword(v) { + if (v == this.isPassword) + return; + + if (v) + this._entry.input_purpose = Clutter.InputContentPurpose.PASSWORD; + else + this._entry.input_purpose = Clutter.InputContentPurpose.NORMAL; + + this._resetPasswordItem(); + } + + open(animate) { + this._updatePasteItem(); + this._updateCopyItem(); + if (this._passwordItem) + this._updatePasswordItem(); + + super.open(animate); + this._entry.add_style_pseudo_class('focus'); + +-- +2.27.0 + diff --git a/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch b/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch new file mode 100644 index 0000000..1f19cb0 --- /dev/null +++ b/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch @@ -0,0 +1,674 @@ +From a3fc35a2b452855d004549afbec57d1b4f36c917 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Thu, 27 Feb 2020 19:36:14 -0800 +Subject: [PATCH 5/6] app-cache: add ShellAppCache for GAppInfo caching + +This caches GAppInfo so that the compositor thread does not have to perform +costly disk access to load them. Instead, they are loaded from a worker +thread and the ShellAppCache notifies of changes. + +To simplify maintenance, ShellAppCache manages this directly and the +existing ShellAppSystem wraps the cache. We may want to graft these +together in the future, but now it provides the easiest way to backport +changes to older Shell releases. + +Another source of compositor thread disk access was in determining the +name for an application directory. Translations are provided via GKeyFile +installed in "desktop-directories". Each time we would build the name +for a label (or update it) we would have to load all of these files. + +Instead, the ShellAppCache caches that information and updates the cache +in bulk when those change. We can reduce this in the future to do less +work, but chances are these will come together anyway so that is probably +worth fixing if we ever come across it. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2282 +--- + js/ui/appDisplay.js | 12 +- + src/meson.build | 5 +- + src/shell-app-cache-private.h | 19 ++ + src/shell-app-cache.c | 404 ++++++++++++++++++++++++++++++++++ + src/shell-app-system.c | 34 ++- + src/shell-util.c | 16 ++ + src/shell-util.h | 2 + + 7 files changed, 463 insertions(+), 29 deletions(-) + create mode 100644 src/shell-app-cache-private.h + create mode 100644 src/shell-app-cache.c + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index 7fad02cd0..a2d691085 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -78,15 +78,9 @@ function _getFolderName(folder) { + let name = folder.get_string('name'); + + if (folder.get_boolean('translate')) { +- let keyfile = new GLib.KeyFile(); +- let path = 'desktop-directories/' + name; +- +- try { +- keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE); +- name = keyfile.get_locale_string('Desktop Entry', 'Name', null); +- } catch(e) { +- return name; +- } ++ let translated = Shell.util_get_translated_folder_name(name); ++ if (translated !== null) ++ return translated; + } + + return name; +diff --git a/src/meson.build b/src/meson.build +index 97a5a796c..2b911d347 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -108,6 +108,7 @@ endif + + libshell_private_headers = [ + 'shell-app-private.h', ++ 'shell-app-cache-private.h', + 'shell-app-system-private.h', + 'shell-global-private.h', + 'shell-window-tracker-private.h', +@@ -146,7 +147,9 @@ if have_networkmanager + libshell_sources += 'shell-network-agent.c' + endif + +-libshell_private_sources = [] ++libshell_private_sources = [ ++ 'shell-app-cache.c', ++] + + if enable_recorder + libshell_sources += ['shell-recorder.c'] +diff --git a/src/shell-app-cache-private.h b/src/shell-app-cache-private.h +new file mode 100644 +index 000000000..b73094ab1 +--- /dev/null ++++ b/src/shell-app-cache-private.h +@@ -0,0 +1,19 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++#ifndef __SHELL_APP_CACHE_PRIVATE_H__ ++#define __SHELL_APP_CACHE_PRIVATE_H__ ++ ++#include ++#include ++ ++#define SHELL_TYPE_APP_CACHE (shell_app_cache_get_type()) ++ ++G_DECLARE_FINAL_TYPE (ShellAppCache, shell_app_cache, SHELL, APP_CACHE, GObject) ++ ++ShellAppCache *shell_app_cache_get_default (void); ++GList *shell_app_cache_get_all (ShellAppCache *cache); ++GDesktopAppInfo *shell_app_cache_get_info (ShellAppCache *cache, ++ const char *id); ++char *shell_app_cache_translate_folder (ShellAppCache *cache, ++ const char *name); ++ ++#endif /* __SHELL_APP_CACHE_PRIVATE_H__ */ +diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c +new file mode 100644 +index 000000000..15d4734d0 +--- /dev/null ++++ b/src/shell-app-cache.c +@@ -0,0 +1,404 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "shell-app-cache-private.h" ++ ++/** ++ * SECTION:shell-app-cache ++ * @title: ShellAppCache ++ * @short_description: application information cache ++ * ++ * The #ShellAppCache is responsible for caching information about #GAppInfo ++ * to ensure that the compositor thread never needs to perform disk reads to ++ * access them. All of the work is done off-thread. When the new data has ++ * been loaded, a #ShellAppCache::changed signal is emitted. ++ * ++ * Additionally, the #ShellAppCache caches information about translations for ++ * directories. This allows translation provided in [Desktop Entry] GKeyFiles ++ * to be available when building StLabel and other elements without performing ++ * costly disk reads. ++ * ++ * Various monitors are used to keep this information up to date while the ++ * Shell is running. ++ */ ++ ++#define DEFAULT_TIMEOUT_SECONDS 5 ++ ++struct _ShellAppCache ++{ ++ GObject parent_instance; ++ ++ GAppInfoMonitor *monitor; ++ GPtrArray *dir_monitors; ++ GHashTable *folders; ++ GCancellable *cancellable; ++ GList *app_infos; ++ ++ guint queued_update; ++}; ++ ++typedef struct ++{ ++ GList *app_infos; ++ GHashTable *folders; ++} CacheState; ++ ++G_DEFINE_TYPE (ShellAppCache, shell_app_cache, G_TYPE_OBJECT) ++ ++enum { ++ CHANGED, ++ N_SIGNALS ++}; ++ ++static guint signals [N_SIGNALS]; ++ ++static void ++cache_state_free (CacheState *state) ++{ ++ g_clear_pointer (&state->folders, g_hash_table_unref); ++ g_list_free_full (state->app_infos, g_object_unref); ++ g_slice_free (CacheState, state); ++} ++ ++static CacheState * ++cache_state_new (void) ++{ ++ CacheState *state; ++ ++ state = g_slice_new0 (CacheState); ++ state->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ++ ++ return g_steal_pointer (&state); ++} ++ ++/** ++ * shell_app_cache_get_default: ++ * ++ * Gets the default #ShellAppCache. ++ * ++ * Returns: (transfer none): a #ShellAppCache ++ */ ++ShellAppCache * ++shell_app_cache_get_default (void) ++{ ++ static ShellAppCache *instance; ++ ++ if (instance == NULL) ++ { ++ instance = g_object_new (SHELL_TYPE_APP_CACHE, NULL); ++ g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance); ++ } ++ ++ return instance; ++} ++ ++static void ++load_folder (GHashTable *folders, ++ const char *path) ++{ ++ g_autoptr(GDir) dir = NULL; ++ const char *name; ++ ++ g_assert (folders != NULL); ++ g_assert (path != NULL); ++ ++ dir = g_dir_open (path, 0, NULL); ++ if (dir == NULL) ++ return; ++ ++ while ((name = g_dir_read_name (dir))) ++ { ++ g_autofree gchar *filename = NULL; ++ g_autoptr(GKeyFile) keyfile = NULL; ++ ++ /* First added wins */ ++ if (g_hash_table_contains (folders, name)) ++ continue; ++ ++ filename = g_build_filename (path, name, NULL); ++ keyfile = g_key_file_new (); ++ ++ if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL)) ++ { ++ gchar *translated; ++ ++ translated = g_key_file_get_locale_string (keyfile, ++ "Desktop Entry", "Name", ++ NULL, NULL); ++ ++ if (translated != NULL) ++ g_hash_table_insert (folders, g_strdup (name), translated); ++ } ++ } ++} ++ ++static void ++load_folders (GHashTable *folders) ++{ ++ const char * const *dirs; ++ g_autofree gchar *userdir = NULL; ++ guint i; ++ ++ g_assert (folders != NULL); ++ ++ userdir = g_build_filename (g_get_user_data_dir (), "desktop-directories", NULL); ++ load_folder (folders, userdir); ++ ++ dirs = g_get_system_data_dirs (); ++ for (i = 0; dirs[i] != NULL; i++) ++ { ++ g_autofree gchar *sysdir = g_build_filename (dirs[i], "desktop-directories", NULL); ++ load_folder (folders, sysdir); ++ } ++} ++ ++static void ++shell_app_cache_worker (GTask *task, ++ gpointer source_object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ CacheState *state; ++ ++ g_assert (G_IS_TASK (task)); ++ g_assert (SHELL_IS_APP_CACHE (source_object)); ++ ++ state = cache_state_new (); ++ state->app_infos = g_app_info_get_all (); ++ load_folders (state->folders); ++ ++ g_task_return_pointer (task, state, (GDestroyNotify) cache_state_free); ++} ++ ++static void ++apply_update_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ ShellAppCache *cache = (ShellAppCache *)object; ++ g_autoptr(GError) error = NULL; ++ CacheState *state; ++ ++ g_assert (SHELL_IS_APP_CACHE (cache)); ++ g_assert (G_IS_TASK (result)); ++ g_assert (user_data == NULL); ++ ++ state = g_task_propagate_pointer (G_TASK (result), &error); ++ ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return; ++ ++ g_list_free_full (cache->app_infos, g_object_unref); ++ cache->app_infos = g_steal_pointer (&state->app_infos); ++ ++ g_clear_pointer (&cache->folders, g_hash_table_unref); ++ cache->folders = g_steal_pointer (&state->folders); ++ ++ g_signal_emit (cache, signals[CHANGED], 0); ++ ++ cache_state_free (state); ++} ++ ++static gboolean ++shell_app_cache_do_update (gpointer user_data) ++{ ++ ShellAppCache *cache = user_data; ++ g_autoptr(GTask) task = NULL; ++ ++ cache->queued_update = 0; ++ ++ /* Reset the cancellable state so we don't race with ++ * two updates coming back overlapped and applying the ++ * information in the wrong order. ++ */ ++ g_cancellable_cancel (cache->cancellable); ++ g_clear_object (&cache->cancellable); ++ cache->cancellable = g_cancellable_new (); ++ ++ task = g_task_new (cache, cache->cancellable, apply_update_cb, NULL); ++ g_task_set_source_tag (task, shell_app_cache_do_update); ++ g_task_run_in_thread (task, shell_app_cache_worker); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++shell_app_cache_queue_update (ShellAppCache *self) ++{ ++ g_assert (SHELL_IS_APP_CACHE (self)); ++ ++ if (self->queued_update != 0) ++ g_source_remove (self->queued_update); ++ ++ self->queued_update = g_timeout_add_seconds (DEFAULT_TIMEOUT_SECONDS, ++ shell_app_cache_do_update, ++ self); ++} ++ ++static void ++monitor_desktop_directories_for_data_dir (ShellAppCache *self, ++ const gchar *directory) ++{ ++ g_autofree gchar *subdir = NULL; ++ g_autoptr(GFile) file = NULL; ++ g_autoptr(GFileMonitor) monitor = NULL; ++ ++ g_assert (SHELL_IS_APP_CACHE (self)); ++ ++ if (directory == NULL) ++ return; ++ ++ subdir = g_build_filename (directory, "desktop-directories", NULL); ++ file = g_file_new_for_path (subdir); ++ monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); ++ ++ if (monitor != NULL) ++ { ++ g_file_monitor_set_rate_limit (monitor, DEFAULT_TIMEOUT_SECONDS * 1000); ++ g_signal_connect_object (monitor, ++ "changed", ++ G_CALLBACK (shell_app_cache_queue_update), ++ self, ++ G_CONNECT_SWAPPED); ++ g_ptr_array_add (self->dir_monitors, g_steal_pointer (&monitor)); ++ } ++} ++ ++static void ++shell_app_cache_finalize (GObject *object) ++{ ++ ShellAppCache *self = (ShellAppCache *)object; ++ ++ g_clear_object (&self->monitor); ++ ++ if (self->queued_update) ++ { ++ g_source_remove (self->queued_update); ++ self->queued_update = 0; ++ } ++ ++ g_clear_pointer (&self->dir_monitors, g_ptr_array_unref); ++ g_clear_pointer (&self->folders, g_hash_table_unref); ++ g_list_free_full (self->app_infos, g_object_unref); ++ ++ G_OBJECT_CLASS (shell_app_cache_parent_class)->finalize (object); ++} ++ ++static void ++shell_app_cache_class_init (ShellAppCacheClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = shell_app_cache_finalize; ++ ++ /** ++ * ShellAppCache::changed: ++ * ++ * The "changed" signal is emitted when the cache has updated ++ * information about installed applications. ++ */ ++ signals [CHANGED] = ++ g_signal_new ("changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_NONE, 0); ++} ++ ++static void ++shell_app_cache_init (ShellAppCache *self) ++{ ++ const gchar * const *sysdirs; ++ guint i; ++ ++ /* Monitor directories for translation changes */ ++ self->dir_monitors = g_ptr_array_new_with_free_func (g_object_unref); ++ monitor_desktop_directories_for_data_dir (self, g_get_user_data_dir ()); ++ sysdirs = g_get_system_data_dirs (); ++ for (i = 0; sysdirs[i] != NULL; i++) ++ monitor_desktop_directories_for_data_dir (self, sysdirs[i]); ++ ++ /* Load translated directory names immediately */ ++ self->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ++ load_folders (self->folders); ++ ++ /* Setup AppMonitor to track changes */ ++ self->monitor = g_app_info_monitor_get (); ++ g_signal_connect_object (self->monitor, ++ "changed", ++ G_CALLBACK (shell_app_cache_queue_update), ++ self, ++ G_CONNECT_SWAPPED); ++ self->app_infos = g_app_info_get_all (); ++} ++ ++/** ++ * shell_app_cache_get_all: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * ++ * Like g_app_info_get_all() but always returns a ++ * cached set of application info so the caller can be ++ * sure that I/O will not happen on the current thread. ++ * ++ * Returns: (transfer none) (element-type GAppInfo): ++ * a #GList of references to #GAppInfo. ++ */ ++GList * ++shell_app_cache_get_all (ShellAppCache *cache) ++{ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ return cache->app_infos; ++} ++ ++/** ++ * shell_app_cache_get_info: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * @id: the application id ++ * ++ * A replacement for g_desktop_app_info_new() that will lookup the ++ * information from the cache instead of (re)loading from disk. ++ * ++ * Returns: (nullable) (transfer none): a #GDesktopAppInfo or %NULL ++ */ ++GDesktopAppInfo * ++shell_app_cache_get_info (ShellAppCache *cache, ++ const char *id) ++{ ++ const GList *iter; ++ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ for (iter = cache->app_infos; iter != NULL; iter = iter->next) ++ { ++ GAppInfo *info = iter->data; ++ ++ if (g_strcmp0 (id, g_app_info_get_id (info)) == 0) ++ return G_DESKTOP_APP_INFO (info); ++ } ++ ++ return NULL; ++} ++ ++/** ++ * shell_app_cache_translate_folder: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * @name: the folder name ++ * ++ * Gets the translated folder name for @name if any exists. ++ * ++ * Returns: (nullable): the translated string or %NULL if there is no ++ * translation. ++ */ ++char * ++shell_app_cache_translate_folder (ShellAppCache *cache, ++ const char *name) ++{ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ if (name == NULL) ++ return NULL; ++ ++ return g_strdup (g_hash_table_lookup (cache->folders, name)); ++} +diff --git a/src/shell-app-system.c b/src/shell-app-system.c +index 127f29ef0..828fa726a 100644 +--- a/src/shell-app-system.c ++++ b/src/shell-app-system.c +@@ -9,6 +9,7 @@ + #include + #include + ++#include "shell-app-cache-private.h" + #include "shell-app-private.h" + #include "shell-window-tracker-private.h" + #include "shell-app-system-private.h" +@@ -94,14 +95,14 @@ static void + scan_startup_wm_class_to_id (ShellAppSystem *self) + { + ShellAppSystemPrivate *priv = self->priv; +- GList *l; ++ const GList *l; ++ GList *all; + + g_hash_table_remove_all (priv->startup_wm_class_to_id); + +- g_list_free_full (priv->installed_apps, g_object_unref); +- priv->installed_apps = g_app_info_get_all (); ++ all = shell_app_cache_get_all (shell_app_cache_get_default ()); + +- for (l = priv->installed_apps; l != NULL; l = l->next) ++ for (l = all; l != NULL; l = l->next) + { + GAppInfo *info = l->data; + const char *startup_wm_class, *id, *old_id; +@@ -131,7 +132,8 @@ app_is_stale (ShellApp *app) + if (shell_app_is_window_backed (app)) + return FALSE; + +- info = g_desktop_app_info_new (shell_app_get_id (app)); ++ info = shell_app_cache_get_info (shell_app_cache_get_default (), ++ shell_app_get_id (app)); + if (!info) + return TRUE; + +@@ -156,7 +158,6 @@ app_is_stale (ShellApp *app) + g_icon_equal (g_app_info_get_icon (old_info), + g_app_info_get_icon (new_info)); + +- g_object_unref (info); + return !is_unchanged; + } + +@@ -210,11 +211,9 @@ rescan_icon_theme (ShellAppSystem *self) + } + + static void +-installed_changed (GAppInfoMonitor *monitor, +- gpointer user_data) ++installed_changed (ShellAppCache *cache, ++ ShellAppSystem *self) + { +- ShellAppSystem *self = user_data; +- + rescan_icon_theme (self); + scan_startup_wm_class_to_id (self); + +@@ -227,7 +226,7 @@ static void + shell_app_system_init (ShellAppSystem *self) + { + ShellAppSystemPrivate *priv; +- GAppInfoMonitor *monitor; ++ ShellAppCache *cache; + + self->priv = priv = shell_app_system_get_instance_private (self); + +@@ -238,9 +237,9 @@ shell_app_system_init (ShellAppSystem *self) + + priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + +- monitor = g_app_info_monitor_get (); +- g_signal_connect (monitor, "changed", G_CALLBACK (installed_changed), self); +- installed_changed (monitor, self); ++ cache = shell_app_cache_get_default (); ++ g_signal_connect (cache, "changed", G_CALLBACK (installed_changed), self); ++ installed_changed (cache, self); + } + + static void +@@ -293,13 +292,12 @@ shell_app_system_lookup_app (ShellAppSystem *self, + if (app) + return app; + +- info = g_desktop_app_info_new (id); ++ info = shell_app_cache_get_info (shell_app_cache_get_default (), id); + if (!info) + return NULL; + + app = _shell_app_new (info); + g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app); +- g_object_unref (info); + return app; + } + +@@ -506,7 +504,5 @@ shell_app_system_search (const char *search_string) + GList * + shell_app_system_get_installed (ShellAppSystem *self) + { +- ShellAppSystemPrivate *priv = self->priv; +- +- return priv->installed_apps; ++ return shell_app_cache_get_all (shell_app_cache_get_default ()); + } +diff --git a/src/shell-util.c b/src/shell-util.c +index fa3fc08c8..370784523 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -16,6 +16,7 @@ + #include + #include + ++#include "shell-app-cache-private.h" + #include "shell-util.h" + #include + #include +@@ -639,3 +640,18 @@ shell_util_has_x11_display_extension (MetaDisplay *display, + xdisplay = meta_x11_display_get_xdisplay (x11_display); + return XQueryExtension (xdisplay, extension, &op, &event, &error); + } ++ ++/** ++ * shell_util_get_translated_folder_name: ++ * @name: the untranslated folder name ++ * ++ * Attempts to translate the folder @name using translations provided ++ * by .directory files. ++ * ++ * Returns: (nullable): a translated string or %NULL ++ */ ++char * ++shell_util_get_translated_folder_name (const char *name) ++{ ++ return shell_app_cache_translate_folder (shell_app_cache_get_default (), name); ++} +diff --git a/src/shell-util.h b/src/shell-util.h +index 02b8404e9..843a1253d 100644 +--- a/src/shell-util.h ++++ b/src/shell-util.h +@@ -62,6 +62,8 @@ void shell_util_check_cloexec_fds (void); + gboolean shell_util_has_x11_display_extension (MetaDisplay *display, + const char *extension); + ++char *shell_util_get_translated_folder_name (const char *name); ++ + G_END_DECLS + + #endif /* __SHELL_UTIL_H__ */ +-- +2.26.2 + diff --git a/SOURCES/0005-shellEntry-Only-mask-text-in-password-entries.patch b/SOURCES/0005-shellEntry-Only-mask-text-in-password-entries.patch new file mode 100644 index 0000000..a87a327 --- /dev/null +++ b/SOURCES/0005-shellEntry-Only-mask-text-in-password-entries.patch @@ -0,0 +1,36 @@ +From 7a264550c5f3a98b1786b1a75cff01cde1d084eb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 29 Jul 2021 17:17:43 +0200 +Subject: [PATCH 5/5] shellEntry: Only mask text in password entries + +When "Show Text" is locked down, we not only remove the corresponding +menu item, but also make sure the password is masked. + +Except that the current code is too eager, and masks the text in +any entries. +--- + js/ui/shellEntry.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index c45e4545a..64b389050 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -55,11 +55,13 @@ var EntryMenu = class extends PopupMenu.PopupMenu { + this._passwordItem.destroy(); + this._passwordItem = null; + } +- this._entry.clutter_text.set_password_char('\u25cf'); + } else if (this.isPassword && !passwordDisabled) { + if (!this._passwordItem) + this._makePasswordItem(); + } ++ ++ if (this.isPassword && passwordDisabled) ++ this._entry.clutter_text.set_password_char('\u25cf'); + } + + get isPassword() { +-- +2.31.1 + diff --git a/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch b/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch new file mode 100644 index 0000000..73080e8 --- /dev/null +++ b/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch @@ -0,0 +1,66 @@ +From a0df79f8de4c13c36ed3b22cfdbb78e324424ef1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 14 Mar 2020 14:45:42 +0100 +Subject: [PATCH 6/6] js: Always use AppSystem to lookup apps + +There is no good reason for bypassing the application cache in +AppSystem and loading .desktop files again. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1093 +--- + js/ui/appDisplay.js | 4 ++-- + js/ui/calendar.js | 16 ++++++++++------ + 2 files changed, 12 insertions(+), 8 deletions(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index a2d691085..cb2be7d3c 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1001,8 +1001,8 @@ var AppSearchProvider = class AppSearchProvider { + let results = []; + groups.forEach(group => { + group = group.filter(appID => { +- let app = Gio.DesktopAppInfo.new(appID); +- return app && app.should_show(); ++ const app = this._appSys.lookup_app(appID); ++ return app && app.app_info.should_show(); + }); + results = results.concat(group.sort( + (a, b) => usage.compare(a, b) +diff --git a/js/ui/calendar.js b/js/ui/calendar.js +index cd3e879c4..3ae2e44f8 100644 +--- a/js/ui/calendar.js ++++ b/js/ui/calendar.js +@@ -791,8 +791,9 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection { + this._title.connect('clicked', this._onTitleClicked.bind(this)); + this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this)); + +- Shell.AppSystem.get_default().connect('installed-changed', +- this._appInstalledChanged.bind(this)); ++ this._appSys = Shell.AppSystem.get_default(); ++ this._appSys.connect('installed-changed', ++ this._appInstalledChanged.bind(this)); + this._appInstalledChanged(); + } + +@@ -883,10 +884,13 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection { + Main.overview.hide(); + Main.panel.closeCalendar(); + +- let app = this._getCalendarApp(); +- if (app.get_id() == 'evolution.desktop') +- app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); +- app.launch([], global.create_app_launch_context(0, -1)); ++ let appInfo = this._getCalendarApp(); ++ if (app.get_id() == 'evolution.desktop') { ++ let app = this._appSys.lookup_app('evolution-calendar.desktop'); ++ if (app) ++ appInfo = app.app_info; ++ } ++ appInfo.launch([], global.create_app_launch_context(0, -1)); + } + + setDate(date) { +-- +2.26.2 + diff --git a/SOURCES/allow-timed-login-with-no-user-list.patch b/SOURCES/allow-timed-login-with-no-user-list.patch new file mode 100644 index 0000000..040e507 --- /dev/null +++ b/SOURCES/allow-timed-login-with-no-user-list.patch @@ -0,0 +1,159 @@ +From 3252f05b8745a5d3118986474793fe3ecc2b041c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 19 Apr 2016 13:12:46 -0400 +Subject: [PATCH] loginDialog: allow timed login with disabled user list + +At the moment the timed login feature is implemented in the user list. +If there's no user list, we don't show the indicator anywhere and +don't proceed with timed login. + +This commit allows timed login to work when the user list is disabled. +It accomplishes this by putting the timed login indicator on the +auth prompt, in that scenario. +--- + data/theme/gnome-shell-sass/_common.scss | 4 +++ + js/gdm/authPrompt.js | 41 +++++++++++++++++++++++- + js/gdm/loginDialog.js | 25 +++++++++++++-- + 3 files changed, 67 insertions(+), 3 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index a6357baad..c2df28279 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -1856,6 +1856,10 @@ StScrollBar { + padding-bottom: 12px; + spacing: 8px; + width: 23em; ++ .login-dialog-timed-login-indicator { ++ height: 2px; ++ background-color: darken($fg_color,40%); ++ } + } + + .login-dialog-prompt-label { +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 27eb31a89..cf77b3f26 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -1,6 +1,6 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Clutter, Pango, Shell, St } = imports.gi; ++const { Clutter, GLib, Pango, Shell, St } = imports.gi; + const Signals = imports.signals; + + const Animation = imports.ui.animation; +@@ -111,6 +111,11 @@ var AuthPrompt = class { + + this._entry.grab_key_focus(); + ++ this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator', ++ scale_x: 0 }); ++ ++ this.actor.add(this._timedLoginIndicator); ++ + this._message = new St.Label({ opacity: 0, + styleClass: 'login-dialog-message' }); + this._message.clutter_text.line_wrap = true; +@@ -135,6 +140,40 @@ var AuthPrompt = class { + this._defaultButtonWell.add_child(this._spinner.actor); + } + ++ showTimedLoginIndicator(time) { ++ let hold = new Batch.Hold(); ++ ++ this.hideTimedLoginIndicator(); ++ ++ let startTime = GLib.get_monotonic_time(); ++ ++ this._timedLoginTimeoutId = GLib.timeout_add (GLib.PRIORITY_DEFAULT, 33, ++ () => { ++ let currentTime = GLib.get_monotonic_time(); ++ let elapsedTime = (currentTime - startTime) / GLib.USEC_PER_SEC; ++ this._timedLoginIndicator.scale_x = elapsedTime / time; ++ if (elapsedTime >= time) { ++ this._timedLoginTimeoutId = 0; ++ hold.release(); ++ return GLib.SOURCE_REMOVE; ++ } ++ ++ return GLib.SOURCE_CONTINUE; ++ }); ++ ++ GLib.Source.set_name_by_id(this._timedLoginTimeoutId, '[gnome-shell] this._timedLoginTimeoutId'); ++ ++ return hold; ++ } ++ ++ hideTimedLoginIndicator() { ++ if (this._timedLoginTimeoutId) { ++ GLib.source_remove(this._timedLoginTimeoutId); ++ this._timedLoginTimeoutId = 0; ++ } ++ this._timedLoginIndicator.scale_x = 0.; ++ } ++ + _onDestroy() { + if (this._preemptiveAnswerWatchId) { + this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 6c4d1357d..b4df6e959 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -734,6 +734,9 @@ var LoginDialog = GObject.registerClass({ + + if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING) + this._authPrompt.reset(); ++ ++ if (this._disableUserList && this._timedLoginUserListHold) ++ this._timedLoginUserListHold.release(); + } + } + +@@ -1020,16 +1023,31 @@ var LoginDialog = GObject.registerClass({ + let loginItem = null; + let animationTime; + +- let tasks = [() => this._waitForItemForUser(userName), ++ let tasks = [() => { ++ if (this._disableUserList) ++ return; ++ ++ this._timedLoginUserListHold = this._waitForItemForUser(userName); ++ ++ return this._timedLoginUserListHold; ++ }, + + () => { +- loginItem = this._userList.getItemFromUserName(userName); ++ this._timedLoginUserListHold = null; ++ ++ ++ loginItem = this._disableUserList ++ ? this._authPrompt ++ : this._userList.getItemFromUserName(userName); + + // If there is an animation running on the item, reset it. + loginItem.hideTimedLoginIndicator(); + }, + + () => { ++ if (this._disableUserList) ++ return; ++ + // If we're just starting out, start on the right item. + if (!this._userManager.is_loaded) { + this._userList.jumpToItem(loginItem); +@@ -1051,6 +1069,9 @@ var LoginDialog = GObject.registerClass({ + }, + + () => { ++ if (this._disableUserList) ++ return; ++ + // If idle timeout is done, make sure the timed login indicator is shown + if (delay > _TIMED_LOGIN_IDLE_THRESHOLD && + this._authPrompt.actor.visible) +-- +2.26.2 + diff --git a/SOURCES/caps-lock-warning.patch b/SOURCES/caps-lock-warning.patch new file mode 100644 index 0000000..d3c2daa --- /dev/null +++ b/SOURCES/caps-lock-warning.patch @@ -0,0 +1,488 @@ +From 7b514e637837e00372e20fa52f841e993966b734 Mon Sep 17 00:00:00 2001 +From: Umang Jain +Date: Fri, 13 Dec 2019 13:36:14 +0530 +Subject: [PATCH 1/7] shellEntry: Add CapsLockWarning class + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619 +--- + data/theme/gnome-shell-sass/_common.scss | 5 +++ + js/ui/shellEntry.js | 39 +++++++++++++++++++++++- + 2 files changed, 43 insertions(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index b1eeb0ce97..19a736ab7d 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -391,6 +391,11 @@ StScrollBar { + padding-bottom: 8px; + } + ++ .prompt-dialog-caps-lock-warning { ++ @extend .prompt-dialog-error-label; ++ padding-left: 6.2em; ++ } ++ + .prompt-dialog-info-label { + font-size: 10pt; + padding-bottom: 8px; +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 79f1aad3e7..c1738c4064 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -1,6 +1,6 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Clutter, Shell, St } = imports.gi; ++const { Clutter, GObject, Pango, Shell, St } = imports.gi; + + const BoxPointer = imports.ui.boxpointer; + const Main = imports.ui.main; +@@ -170,3 +170,40 @@ function addContextMenu(entry, params) { + entry._menuManager = null; + }); + } ++ ++var CapsLockWarning = GObject.registerClass( ++class CapsLockWarning extends St.Label { ++ _init(params) { ++ let defaultParams = { style_class: 'prompt-dialog-error-label' }; ++ super._init(Object.assign(defaultParams, params)); ++ ++ this.text = _('Caps lock is on.'); ++ ++ this._keymap = Clutter.get_default_backend().get_keymap(); ++ ++ this.connect('notify::mapped', () => { ++ if (this.is_mapped()) { ++ this.stateChangedId = this._keymap.connect('state-changed', ++ this._updateCapsLockWarningOpacity.bind(this)); ++ } else { ++ this._keymap.disconnect(this.stateChangedId); ++ this.stateChangedId = 0; ++ } ++ ++ this._updateCapsLockWarningOpacity(); ++ }); ++ ++ this.connect('destroy', () => { ++ if (this.stateChangedId > 0) ++ this._keymap.disconnect(this.stateChangedId); ++ }); ++ ++ this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ this.clutter_text.line_wrap = true; ++ } ++ ++ _updateCapsLockWarningOpacity() { ++ let capsLockOn = this._keymap.get_caps_lock_state(); ++ this.opacity = capsLockOn ? 255 : 0; ++ } ++}); +-- +2.21.1 + + +From aa4938f261454f85c782e59e40d4e5a9e1a01dbc Mon Sep 17 00:00:00 2001 +From: Umang Jain +Date: Wed, 18 Dec 2019 01:33:45 +0530 +Subject: [PATCH 2/7] js: Add caps-lock Warning to the dialogs + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/619 +--- + js/gdm/authPrompt.js | 4 ++++ + js/ui/components/keyring.js | 6 ++++++ + js/ui/components/networkAgent.js | 8 ++++++++ + js/ui/components/polkitAgent.js | 2 ++ + js/ui/shellMountOperation.js | 3 +++ + 5 files changed, 23 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 71069e93b8..3ce9fd0d01 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -113,6 +113,9 @@ var AuthPrompt = class { + + this._entry.grab_key_focus(); + ++ this._capsLockWarningLabel = new ShellEntry.CapsLockWarning(); ++ this.actor.add_child(this._capsLockWarningLabel); ++ + this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator', + scale_x: 0 }); + +@@ -432,6 +435,7 @@ var AuthPrompt = class { + setPasswordChar(passwordChar) { + this._entry.clutter_text.set_password_char(passwordChar); + this._entry.menu.isPassword = passwordChar != ''; ++ this._capsLockWarningLabel.visible = passwordChar !== ''; + } + + setQuestion(question) { +diff --git a/js/ui/components/keyring.js b/js/ui/components/keyring.js +index 0d9f1e4663..3512fb63b1 100644 +--- a/js/ui/components/keyring.js ++++ b/js/ui/components/keyring.js +@@ -128,6 +128,12 @@ var KeyringDialog = class extends ModalDialog.ModalDialog { + this.prompt.set_password_actor(this._passwordEntry ? this._passwordEntry.clutter_text : null); + this.prompt.set_confirm_actor(this._confirmEntry ? this._confirmEntry.clutter_text : null); + ++ if (this._passwordEntry || this._confirmEntry) { ++ this._capsLockWarningLabel = new ShellEntry.CapsLockWarning(); ++ layout.attach(this._capsLockWarningLabel, 1, row, 1, 1); ++ row++; ++ } ++ + if (this.prompt.choice_visible) { + let choice = new CheckBox.CheckBox(); + this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE); +diff --git a/js/ui/components/networkAgent.js b/js/ui/components/networkAgent.js +index f871c732d9..32d40fb2b9 100644 +--- a/js/ui/components/networkAgent.js ++++ b/js/ui/components/networkAgent.js +@@ -95,6 +95,14 @@ var NetworkSecretDialog = class extends ModalDialog.ModalDialog { + secret.entry.clutter_text.set_password_char('\u25cf'); + } + ++ if (this._content.secrets.some(s => s.password)) { ++ this._capsLockWarningLabel = new ShellEntry.CapsLockWarning(); ++ if (rtl) ++ layout.attach(this._capsLockWarningLabel, 0, pos, 1, 1); ++ else ++ layout.attach(this._capsLockWarningLabel, 1, pos, 1, 1); ++ } ++ + contentBox.messageBox.add(secretTable); + + if (flags & NM.SecretAgentGetSecretsFlags.WPS_PBC_ACTIVE) { +diff --git a/js/ui/components/polkitAgent.js b/js/ui/components/polkitAgent.js +index 21feb40903..734a217335 100644 +--- a/js/ui/components/polkitAgent.js ++++ b/js/ui/components/polkitAgent.js +@@ -108,6 +108,8 @@ var AuthenticationDialog = class extends ModalDialog.ModalDialog { + + this.setInitialKeyFocus(this._passwordEntry); + this._passwordBox.hide(); ++ this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({ style_class: 'prompt-dialog-caps-lock-warning' }); ++ content.messageBox.add(this._capsLockWarningLabel); + + this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' }); + this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js +index f976f400f4..3a2377ddaf 100644 +--- a/js/ui/shellMountOperation.js ++++ b/js/ui/shellMountOperation.js +@@ -305,6 +305,9 @@ var ShellMountPasswordDialog = class extends ModalDialog.ModalDialog { + this._passwordBox.add(this._passwordEntry, {expand: true }); + this.setInitialKeyFocus(this._passwordEntry); + ++ this._capsLockWarningLabel = new ShellEntry.CapsLockWarning(); ++ content.messageBox.add(this._capsLockWarningLabel); ++ + this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label', + text: _("Sorry, that didn’t work. Please try again.") }); + this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +-- +2.21.1 + + +From 016cbd971711665844d40ec678d2779c160f791b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 23 Jan 2020 22:37:06 +0100 +Subject: [PATCH 3/7] shellEntry: Make signal id variable private + +Signal connection IDs should be private variables, so make this one +private. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/952 +--- + js/ui/shellEntry.js | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index c1738c4064..cd7c9a6c88 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -183,19 +183,19 @@ class CapsLockWarning extends St.Label { + + this.connect('notify::mapped', () => { + if (this.is_mapped()) { +- this.stateChangedId = this._keymap.connect('state-changed', ++ this._stateChangedId = this._keymap.connect('state-changed', + this._updateCapsLockWarningOpacity.bind(this)); + } else { +- this._keymap.disconnect(this.stateChangedId); +- this.stateChangedId = 0; ++ this._keymap.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; + } + + this._updateCapsLockWarningOpacity(); + }); + + this.connect('destroy', () => { +- if (this.stateChangedId > 0) +- this._keymap.disconnect(this.stateChangedId); ++ if (this._stateChangedId > 0) ++ this._keymap.disconnect(this._stateChangedId); + }); + + this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +-- +2.21.1 + + +From ba65f9066d72731e345a5aced61f35d39c1c1376 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 23 Jan 2020 23:26:45 +0100 +Subject: [PATCH 4/7] theme: Move caps-lock warning to entry widget stylesheet + +The caps-lock warning is more related to entries than dialogs and is +also used in gdm, which is not realated to dialogs at all. Rename the +css class to caps-lock-warning-label and move it to the entry +stylesheet. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/952 +--- + data/theme/gnome-shell-sass/_common.scss | 12 +++++++----- + js/ui/shellEntry.js | 2 +- + 2 files changed, 8 insertions(+), 6 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 19a736ab7d..4661533de2 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -94,6 +94,13 @@ StEntry { + } + } + ++.caps-lock-warning-label { ++ padding-left: 6.2em; ++ @include fontsize($font-size - 1); ++ color: $warning_color; ++} ++ ++ + + /* Scrollbars */ + +@@ -391,11 +398,6 @@ StScrollBar { + padding-bottom: 8px; + } + +- .prompt-dialog-caps-lock-warning { +- @extend .prompt-dialog-error-label; +- padding-left: 6.2em; +- } +- + .prompt-dialog-info-label { + font-size: 10pt; + padding-bottom: 8px; +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index cd7c9a6c88..46eba88d54 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -174,7 +174,7 @@ function addContextMenu(entry, params) { + var CapsLockWarning = GObject.registerClass( + class CapsLockWarning extends St.Label { + _init(params) { +- let defaultParams = { style_class: 'prompt-dialog-error-label' }; ++ let defaultParams = { style_class: 'caps-lock-warning-label' }; + super._init(Object.assign(defaultParams, params)); + + this.text = _('Caps lock is on.'); +-- +2.21.1 + + +From afd764c82febe21aec70bdfc19d256f3401530e1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 23 Jan 2020 22:36:09 +0100 +Subject: [PATCH 5/7] shellEntry: Hide caps lock warning and use animation to + show it + +Since the caps-lock warning adds a lot of spacing to dialogs and the +lock screen, hide it by default and only show it when necessary. To make +the transition smooth instead of just showing the label, animate it in +using the height and opacity. + +Also add some bottom padding to the label so we can show or hide that +padding, too. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/952 +--- + data/theme/gnome-shell-sass/_common.scss | 1 + + js/ui/shellEntry.js | 33 ++++++++++++++++++------ + 2 files changed, 26 insertions(+), 8 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 4661533de2..9e0751c8c5 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -95,6 +95,7 @@ StEntry { + } + + .caps-lock-warning-label { ++ padding-bottom: 8px; + padding-left: 6.2em; + @include fontsize($font-size - 1); + color: $warning_color; +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 46eba88d54..fc8ee37a9a 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -6,6 +6,7 @@ const BoxPointer = imports.ui.boxpointer; + const Main = imports.ui.main; + const Params = imports.misc.params; + const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; + + var EntryMenu = class extends PopupMenu.PopupMenu { + constructor(entry) { +@@ -179,31 +180,47 @@ class CapsLockWarning extends St.Label { + + this.text = _('Caps lock is on.'); + ++ this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ this.clutter_text.line_wrap = true; ++ + this._keymap = Clutter.get_default_backend().get_keymap(); + + this.connect('notify::mapped', () => { + if (this.is_mapped()) { + this._stateChangedId = this._keymap.connect('state-changed', +- this._updateCapsLockWarningOpacity.bind(this)); ++ () => this._sync(true)); + } else { + this._keymap.disconnect(this._stateChangedId); + this._stateChangedId = 0; + } + +- this._updateCapsLockWarningOpacity(); ++ this._sync(false); + }); + + this.connect('destroy', () => { +- if (this._stateChangedId > 0) ++ if (this._stateChangedId) + this._keymap.disconnect(this._stateChangedId); + }); +- +- this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; +- this.clutter_text.line_wrap = true; + } + +- _updateCapsLockWarningOpacity() { ++ _sync(animate) { + let capsLockOn = this._keymap.get_caps_lock_state(); +- this.opacity = capsLockOn ? 255 : 0; ++ ++ Tweener.removeTweens(this); ++ ++ this.natural_height_set = false; ++ let [, height] = this.get_preferred_height(-1); ++ this.natural_height_set = true; ++ ++ Tweener.addTween(this, { ++ height: capsLockOn ? height : 0, ++ opacity: capsLockOn ? 255 : 0, ++ time: animate ? 0.2 : 0, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ if (capsLockOn) ++ this.height = -1; ++ }, ++ }); + } + }); +-- +2.21.1 + + +From 1ef3dafb51da380c54635d0565dc098e40bbb3e1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 Jan 2020 17:48:57 +0100 +Subject: [PATCH 6/7] js: Initialize some properties + +Otherwise those can result in the (harmless) "reference to undefined +property" warnings. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/970 +--- + js/ui/overview.js | 1 + + js/ui/shellEntry.js | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/js/ui/overview.js b/js/ui/overview.js +index dc6ad1821b..5bad4cbd62 100644 +--- a/js/ui/overview.js ++++ b/js/ui/overview.js +@@ -80,6 +80,7 @@ var Overview = class { + constructor() { + this._overviewCreated = false; + this._initCalled = false; ++ this._visible = false; + + Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); + this._sessionUpdated(); +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index fc8ee37a9a..55267e7c87 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -184,6 +184,7 @@ class CapsLockWarning extends St.Label { + this.clutter_text.line_wrap = true; + + this._keymap = Clutter.get_default_backend().get_keymap(); ++ this._stateChangedId = 0; + + this.connect('notify::mapped', () => { + if (this.is_mapped()) { +-- +2.21.1 + + +From 273f7adb43cfee907342d017e1454ea90d42d262 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 21 Feb 2020 19:38:53 +0100 +Subject: [PATCH 7/7] shellEntry: Restore natural-height-set instead of forcing + it + +If we are transitioning the label from 0 to its natural height, we +must set natural-height-set again after querying the preferred height, +otherwise Clutter would skip the transition. + +However when transitioning in the opposite direction, setting the +property to true can go horribly wrong: +If the actor hasn't been allocated before, it will store a fixed +natural height of 0. But as there is no fixed min-height, we can +end up with min-height > natural-height, which is a fatal error. + +(This isn't an issue when *actually* setting a fixed height, as +that will set both natural and minimum height) + +So instead of always setting natural-height-set to true, restore +its previous value to fix the issue. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2255 +--- + js/ui/shellEntry.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/shellEntry.js b/js/ui/shellEntry.js +index 55267e7c87..4a30b22f7a 100644 +--- a/js/ui/shellEntry.js ++++ b/js/ui/shellEntry.js +@@ -209,9 +209,10 @@ class CapsLockWarning extends St.Label { + + Tweener.removeTweens(this); + ++ const naturalHeightSet = this.natural_height_set; + this.natural_height_set = false; + let [, height] = this.get_preferred_height(-1); +- this.natural_height_set = true; ++ this.natural_height_set = naturalHeightSet; + + Tweener.addTween(this, { + height: capsLockOn ? height : 0, +-- +2.21.1 + diff --git a/SOURCES/defend-against-corrupt-notifications.patch b/SOURCES/defend-against-corrupt-notifications.patch new file mode 100644 index 0000000..9957a7b --- /dev/null +++ b/SOURCES/defend-against-corrupt-notifications.patch @@ -0,0 +1,145 @@ +From 7bdd1962213a37f6218fe15ea1a4062dd318672a Mon Sep 17 00:00:00 2001 +From: Will Thompson +Date: Wed, 28 Aug 2019 15:39:44 +0100 +Subject: [PATCH 1/2] global: Don't trust persistent/runtime state data + +An Endless OS system was found in the wild with a malformed +.local/share/gnome-shell/notifications. When deserialized in Python, +after passing trusted=True to g_variant_new_from_bytes(), the first +element of the first struct in the array looks like this: + + In [41]: _38.get_child_value(0).get_child_value(0) + Out[41]: GLib.Variant('s', '\Uffffffff\Uffffffff\Uffffffff\Uffffffff\Uffffffff') + +When deserialised in GJS, we get: + + gjs> v.get_child_value(0).get_child_value(0) + [object variant of type "s"] + gjs> v.get_child_value(0).get_child_value(0).get_string() + typein:43:1 malformed UTF-8 character sequence at offset 0 + @typein:43:1 + @:1:34 + +While g_variant_new_from_bytes() doesn't have much to say about its +'trusted' parameter, g_variant_new_from_data() does: + +> If data is trusted to be serialised data in normal form then trusted +> should be TRUE. This applies to serialised data created within this +> process or read from a trusted location on the disk (such as a file +> installed in /usr/lib alongside your application). You should set +> trusted to FALSE if data is read from the network, a file in the +> user's home directory, etc. + +Persistent state is read from the user's home directory, so it should +not be trusted. With trusted=False, the string value above comes out as +"". + +I don't have an explanation for how this file ended up being malformed. +I also don't have an explanation for when this started crashing: my +guess is that recent GJS became stricter about validating UTF-8 but I +could be wrong! + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1552 +--- + src/shell-global.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shell-global.c b/src/shell-global.c +index 4b33778e0..33046f614 100644 +--- a/src/shell-global.c ++++ b/src/shell-global.c +@@ -1707,7 +1707,7 @@ load_variant (GFile *dir, + else + { + GBytes *bytes = g_mapped_file_get_bytes (mfile); +- res = g_variant_new_from_bytes (G_VARIANT_TYPE (property_type), bytes, TRUE); ++ res = g_variant_new_from_bytes (G_VARIANT_TYPE (property_type), bytes, FALSE); + g_bytes_unref (bytes); + g_mapped_file_unref (mfile); + } +-- +2.35.1 + + +From 13dcb3e4400b92a0d2f548e88b70b358240d462c Mon Sep 17 00:00:00 2001 +From: Will Thompson +Date: Wed, 28 Aug 2019 15:38:03 +0100 +Subject: [PATCH 2/2] notificationDaemon: Catch exceptions while loading + notifications + +An Endless OS system was found in the wild with a malformed +.local/share/gnome-shell/notifications which causes _loadNotifications() +to raise an exception. This exception was not previously handled and +bubbles all the way out to gnome_shell_plugin_start(), whereupon the +shell exit(1)s. The user could no longer log into their computer. + +Handle exceptions from _loadNotifications(), log them, and attempt to +continue. Ensure that this._isLoading is set to 'false' even on error, +so that future calls to _saveNotifications() can overwrite the (corrupt) +state file. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1552 +--- + js/ui/notificationDaemon.js | 42 ++++++++++++++++++++----------------- + 1 file changed, 23 insertions(+), 19 deletions(-) + +diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js +index 4bdede841..dbe673b88 100644 +--- a/js/ui/notificationDaemon.js ++++ b/js/ui/notificationDaemon.js +@@ -749,29 +749,33 @@ var GtkNotificationDaemon = class GtkNotificationDaemon { + _loadNotifications() { + this._isLoading = true; + +- let value = global.get_persistent_state('a(sa(sv))', 'notifications'); +- if (value) { +- let sources = value.deep_unpack(); +- sources.forEach(([appId, notifications]) => { +- if (notifications.length == 0) +- return; +- +- let source; +- try { +- source = this._ensureAppSource(appId); +- } catch(e) { +- if (e instanceof InvalidAppError) ++ try { ++ let value = global.get_persistent_state('a(sa(sv))', 'notifications'); ++ if (value) { ++ let sources = value.deep_unpack(); ++ sources.forEach(([appId, notifications]) => { ++ if (notifications.length == 0) + return; +- throw e; +- } + +- notifications.forEach(([notificationId, notification]) => { +- source.addNotification(notificationId, notification.deep_unpack(), false); ++ let source; ++ try { ++ source = this._ensureAppSource(appId); ++ } catch (e) { ++ if (e instanceof InvalidAppError) ++ return; ++ throw e; ++ } ++ ++ notifications.forEach(([notificationId, notification]) => { ++ source.addNotification(notificationId, notification.deep_unpack(), false); ++ }); + }); +- }); ++ } ++ } catch (e) { ++ logError(e, 'Failed to load saved notifications'); ++ } finally { ++ this._isLoading = false; + } +- +- this._isLoading = false; + } + + _saveNotifications() { +-- +2.35.1 + diff --git a/SOURCES/disable-unlock-entry-until-question.patch b/SOURCES/disable-unlock-entry-until-question.patch new file mode 100644 index 0000000..d238ae8 --- /dev/null +++ b/SOURCES/disable-unlock-entry-until-question.patch @@ -0,0 +1,167 @@ +From a57132816ac7bd93d6875fee0a6c5b273177ac8d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 30 Sep 2015 12:51:24 -0400 +Subject: [PATCH 1/3] authPrompt: don't fade out auth messages if user types + password up front + +Right now we fade out any stale auth messages as soon as the user starts +typing. This behavior doesn't really make sense if the user is typing up +front, before a password is asked. +--- + js/gdm/authPrompt.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d7f53a92e..d421a8856 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -169,7 +169,7 @@ var AuthPrompt = class { + this._updateNextButtonSensitivity(this._entry.text.length > 0); + + this._entry.clutter_text.connect('text-changed', () => { +- if (!this._userVerifier.hasPendingMessages) ++ if (!this._userVerifier.hasPendingMessages && this._queryingService && !this._preemptiveAnswer) + this._fadeOutMessage(); + + this._updateNextButtonSensitivity(this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING); +-- +2.21.0 + + +From 50af703ea95f2b73733c38e66c9c251663a51744 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 30 Sep 2015 14:36:33 -0400 +Subject: [PATCH 2/3] authPrompt: don't spin unless answering question + +--- + js/gdm/authPrompt.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d421a8856..62c5bd078 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -60,8 +60,8 @@ var AuthPrompt = class { + + this.connect('next', () => { + this.updateSensitivity(false); +- this.startSpinning(); + if (this._queryingService) { ++ this.startSpinning(); + this._userVerifier.answerQuery(this._queryingService, this._entry.text); + } else { + this._preemptiveAnswer = this._entry.text; +-- +2.21.0 + + +From b89be880936ad9dd145eb43890ac72d03c37785d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 5 Oct 2015 15:26:18 -0400 +Subject: [PATCH 3/3] authPrompt: stop accepting preemptive answer if user + stops typing + +We only want to allow the user to type the preemptive password in +one smooth motion. If they start to type, and then stop typing, +we should discard their preemptive password as expired. + +Typing ahead the password is just a convenience for users who don't +want to manually lift the shift before typing their passwords, after +all. +--- + js/gdm/authPrompt.js | 37 +++++++++++++++++++++++++++++++++++++ + 1 file changed, 37 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 62c5bd078..27eb31a89 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -6,6 +6,7 @@ const Signals = imports.signals; + const Animation = imports.ui.animation; + const Batch = imports.gdm.batch; + const GdmUtil = imports.gdm.util; ++const Meta = imports.gi.Meta; + const Params = imports.misc.params; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; +@@ -41,6 +42,8 @@ var AuthPrompt = class { + this._gdmClient = gdmClient; + this._mode = mode; + ++ this._idleMonitor = Meta.IdleMonitor.get_core(); ++ + let reauthenticationOnly; + if (this._mode == AuthPromptMode.UNLOCK_ONLY) + reauthenticationOnly = true; +@@ -65,6 +68,11 @@ var AuthPrompt = class { + this._userVerifier.answerQuery(this._queryingService, this._entry.text); + } else { + this._preemptiveAnswer = this._entry.text; ++ ++ if (this._preemptiveAnswerWatchId) { ++ this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); ++ this._preemptiveAnswerWatchId = 0; ++ } + } + }); + +@@ -128,6 +136,11 @@ var AuthPrompt = class { + } + + _onDestroy() { ++ if (this._preemptiveAnswerWatchId) { ++ this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); ++ this._preemptiveAnswerWatchId = 0; ++ } ++ + this._userVerifier.destroy(); + this._userVerifier = null; + } +@@ -342,6 +355,11 @@ var AuthPrompt = class { + } + + setQuestion(question) { ++ if (this._preemptiveAnswerWatchId) { ++ this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); ++ this._preemptiveAnswerWatchId = 0; ++ } ++ + this._label.set_text(question); + + this._label.show(); +@@ -427,6 +445,19 @@ var AuthPrompt = class { + } + } + ++ _onUserStoppedTypePreemptiveAnswer() { ++ if (!this._preemptiveAnswerWatchId || ++ this._preemptiveAnswer || ++ this._queryingService) ++ return; ++ ++ this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); ++ this._preemptiveAnswerWatchId = 0; ++ ++ this._entry.text = ''; ++ this.updateSensitivity(false); ++ } ++ + reset() { + let oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; +@@ -434,6 +465,12 @@ var AuthPrompt = class { + this.nextButton.label = _("Next"); + this._preemptiveAnswer = null; + ++ if (this._preemptiveAnswerWatchId) { ++ this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); ++ } ++ this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch (500, ++ this._onUserStoppedTypePreemptiveAnswer.bind(this)); ++ + if (this._userVerifier) + this._userVerifier.cancel(); + +-- +2.21.0 + diff --git a/SOURCES/enforce-smartcard-at-unlock.patch b/SOURCES/enforce-smartcard-at-unlock.patch new file mode 100644 index 0000000..60274e5 --- /dev/null +++ b/SOURCES/enforce-smartcard-at-unlock.patch @@ -0,0 +1,114 @@ +From 8ce91c85fe052d1a9f4fed0743bceae7d9654aa0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 28 Sep 2015 10:57:02 -0400 +Subject: [PATCH 1/3] smartcardManager: add way to detect if user logged using + (any) token + +If a user uses a token at login time, we need to make sure they continue +to use the token at unlock time. + +As a prerequisite for addressing that problem we need to know up front +if a user logged in with a token at all. + +This commit adds the necessary api to detect that case. +--- + js/misc/smartcardManager.js | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/js/misc/smartcardManager.js b/js/misc/smartcardManager.js +index fda782d1e..bb43c96e7 100644 +--- a/js/misc/smartcardManager.js ++++ b/js/misc/smartcardManager.js +@@ -112,5 +112,12 @@ var SmartcardManager = class { + return true; + } + ++ loggedInWithToken() { ++ if (this._loginToken) ++ return true; ++ ++ return false; ++ } ++ + }; + Signals.addSignalMethods(SmartcardManager.prototype); +-- +2.21.0 + + +From 6decf5560d309579760e10048533d3bd9bc56c3c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 28 Sep 2015 19:56:53 -0400 +Subject: [PATCH 2/3] gdm: only unlock with smartcard, if smartcard used for + login + +If a smartcard is used for login, we need to make sure the smartcard +gets used for unlock, too. +--- + js/gdm/util.js | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 2e9935250..2b80e1dd9 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -126,7 +126,6 @@ var ShellUserVerifier = class { + this._settings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA }); + this._settings.connect('changed', + this._updateDefaultService.bind(this)); +- this._updateDefaultService(); + + this._fprintManager = Fprint.FprintManager(); + this._smartcardManager = SmartcardManager.getSmartcardManager(); +@@ -138,6 +137,8 @@ var ShellUserVerifier = class { + this.smartcardDetected = false; + this._checkForSmartcard(); + ++ this._updateDefaultService(); ++ + this._smartcardInsertedId = this._smartcardManager.connect('smartcard-inserted', + this._checkForSmartcard.bind(this)); + this._smartcardRemovedId = this._smartcardManager.connect('smartcard-removed', +@@ -407,7 +408,9 @@ var ShellUserVerifier = class { + } + + _updateDefaultService() { +- if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) ++ if (this._smartcardManager.loggedInWithToken()) ++ this._defaultService = SMARTCARD_SERVICE_NAME; ++ else if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) + this._defaultService = PASSWORD_SERVICE_NAME; + else if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) + this._defaultService = SMARTCARD_SERVICE_NAME; +-- +2.21.0 + + +From dd844c98c3450dd1b21bcc580b51162c1b00ed2a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 28 Sep 2015 19:57:36 -0400 +Subject: [PATCH 3/3] gdm: update default service when smartcard inserted + +Early on at start up we may not know if a smartcard is +available. Make sure we reupdate the default service +after we get a smartcard insertion event. +--- + js/gdm/util.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 2b80e1dd9..6e940d2ab 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -327,6 +327,8 @@ var ShellUserVerifier = class { + else if (this._preemptingService == SMARTCARD_SERVICE_NAME) + this._preemptingService = null; + ++ this._updateDefaultService(); ++ + this.emit('smartcard-status-changed'); + } + } +-- +2.21.0 + diff --git a/SOURCES/extension-updates.patch b/SOURCES/extension-updates.patch new file mode 100644 index 0000000..acc7826 --- /dev/null +++ b/SOURCES/extension-updates.patch @@ -0,0 +1,3810 @@ +From ab3a275e20c36cc21e529bb2c4328ea36024ecba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 6 Jul 2019 15:31:57 +0200 +Subject: [PATCH 01/26] extensionUtils: Move ExtensionState definition here + +It makes sense to keep extension-related enums in the same module instead +of spreading them between ExtensionSystem and ExtensionUtils. + +More importantly, this will make the type available to the extensions-prefs +tool (which runs in a different process and therefore only has access to +a limited set of modules). + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/misc/extensionUtils.js | 13 +++++++++++++ + js/ui/extensionSystem.js | 13 +------------ + js/ui/lookingGlass.js | 14 ++++++++------ + 3 files changed, 22 insertions(+), 18 deletions(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index fb1e2b506..dc6e74cf8 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -17,6 +17,19 @@ var ExtensionType = { + SESSION_MODE: 3 + }; + ++var ExtensionState = { ++ ENABLED: 1, ++ DISABLED: 2, ++ ERROR: 3, ++ OUT_OF_DATE: 4, ++ DOWNLOADING: 5, ++ INITIALIZED: 6, ++ ++ // Used as an error state for operations on unknown extensions, ++ // should never be in a real extensionMeta object. ++ UNINSTALLED: 99 ++}; ++ + // Maps uuid -> metadata object + var extensions = {}; + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 9ffdb4f3d..3091af2ba 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -6,18 +6,7 @@ const Signals = imports.signals; + const ExtensionUtils = imports.misc.extensionUtils; + const Main = imports.ui.main; + +-var ExtensionState = { +- ENABLED: 1, +- DISABLED: 2, +- ERROR: 3, +- OUT_OF_DATE: 4, +- DOWNLOADING: 5, +- INITIALIZED: 6, +- +- // Used as an error state for operations on unknown extensions, +- // should never be in a real extensionMeta object. +- UNINSTALLED: 99 +-}; ++const { ExtensionState } = ExtensionUtils; + + // Arrays of uuids + var enabledExtensions; +diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js +index 958211df0..fefb3f731 100644 +--- a/js/ui/lookingGlass.js ++++ b/js/ui/lookingGlass.js +@@ -14,6 +14,8 @@ const Tweener = imports.ui.tweener; + const Main = imports.ui.main; + const JsParse = imports.misc.jsParse; + ++const { ExtensionState } = ExtensionUtils; ++ + const CHEVRON = '>>> '; + + /* Imports...feel free to add here as needed */ +@@ -684,16 +686,16 @@ var Extensions = class Extensions { + + _stateToString(extensionState) { + switch (extensionState) { +- case ExtensionSystem.ExtensionState.ENABLED: ++ case ExtensionState.ENABLED: + return _("Enabled"); +- case ExtensionSystem.ExtensionState.DISABLED: +- case ExtensionSystem.ExtensionState.INITIALIZED: ++ case ExtensionState.DISABLED: ++ case ExtensionState.INITIALIZED: + return _("Disabled"); +- case ExtensionSystem.ExtensionState.ERROR: ++ case ExtensionState.ERROR: + return _("Error"); +- case ExtensionSystem.ExtensionState.OUT_OF_DATE: ++ case ExtensionState.OUT_OF_DATE: + return _("Out of date"); +- case ExtensionSystem.ExtensionState.DOWNLOADING: ++ case ExtensionState.DOWNLOADING: + return _("Downloading"); + } + return 'Unknown'; // Not translated, shouldn't appear +-- +2.29.2 + + +From c16d1589d093dac4e0efe21c7f1aeb635afabd0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 7 Mar 2019 01:45:45 +0100 +Subject: [PATCH 02/26] extensionSystem: Turn into a class + +The extension system started out as a set of simple functions, but +gained more state later, and even some hacks to emit signals without +having an object to emit them on. + +There is no good reason for that weirdness, so rather than imitating an +object, wrap the existing system into a real ExtensionManager object. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/ui/extensionDownloader.js | 17 +- + js/ui/extensionSystem.js | 569 +++++++++++++++++------------------ + js/ui/lookingGlass.js | 5 +- + js/ui/main.js | 3 +- + js/ui/shellDBus.js | 7 +- + 5 files changed, 297 insertions(+), 304 deletions(-) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index 9aed29c69..fe37463f2 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -6,6 +6,7 @@ const Config = imports.misc.config; + const ExtensionUtils = imports.misc.extensionUtils; + const ExtensionSystem = imports.ui.extensionSystem; + const FileUtils = imports.misc.fileUtils; ++const Main = imports.ui.main; + const ModalDialog = imports.ui.modalDialog; + + const _signals = ExtensionSystem._signals; +@@ -25,7 +26,7 @@ function installExtension(uuid, invocation) { + + _httpSession.queue_message(message, (session, message) => { + if (message.status_code != Soup.KnownStatusCode.OK) { +- ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code); ++ Main.extensionManager.logExtensionError(uuid, 'downloading info: ' + message.status_code); + invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString()); + return; + } +@@ -34,7 +35,7 @@ function installExtension(uuid, invocation) { + try { + info = JSON.parse(message.response_body.data); + } catch (e) { +- ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e); ++ Main.extensionManager.logExtensionError(uuid, 'parsing info: ' + e); + invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString()); + return; + } +@@ -53,7 +54,7 @@ function uninstallExtension(uuid) { + if (extension.type != ExtensionUtils.ExtensionType.PER_USER) + return false; + +- if (!ExtensionSystem.unloadExtension(extension)) ++ if (!Main.extensionManager.unloadExtension(extension)) + return false; + + FileUtils.recursivelyDeleteDir(extension.dir, true); +@@ -117,7 +118,7 @@ function updateExtension(uuid) { + let oldExtension = ExtensionUtils.extensions[uuid]; + let extensionDir = oldExtension.dir; + +- if (!ExtensionSystem.unloadExtension(oldExtension)) ++ if (!Main.extensionManager.unloadExtension(oldExtension)) + return; + + FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir); +@@ -127,10 +128,10 @@ function updateExtension(uuid) { + + try { + extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); +- ExtensionSystem.loadExtension(extension); ++ Main.extensionManager.loadExtension(extension); + } catch(e) { + if (extension) +- ExtensionSystem.unloadExtension(extension); ++ Main.extensionManager.unloadExtension(extension); + + logError(e, 'Error loading extension %s'.format(uuid)); + +@@ -139,7 +140,7 @@ function updateExtension(uuid) { + + // Restore what was there before. We can't do much if we + // fail here. +- ExtensionSystem.loadExtension(oldExtension); ++ Main.extensionManager.loadExtension(oldExtension); + return; + } + +@@ -239,7 +240,7 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog { + + try { + let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); +- ExtensionSystem.loadExtension(extension); ++ Main.extensionManager.loadExtension(extension); + } catch(e) { + uninstallExtension(uuid); + errback('LoadExtensionError', e); +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 3091af2ba..b7e908223 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -8,358 +8,351 @@ const Main = imports.ui.main; + + const { ExtensionState } = ExtensionUtils; + +-// Arrays of uuids +-var enabledExtensions; +-// Contains the order that extensions were enabled in. +-var extensionOrder = []; +- +-// We don't really have a class to add signals on. So, create +-// a simple dummy object, add the signal methods, and export those +-// publically. +-var _signals = {}; +-Signals.addSignalMethods(_signals); +- +-var connect = _signals.connect.bind(_signals); +-var disconnect = _signals.disconnect.bind(_signals); +- + const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; + const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions'; + const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation'; + +-var initted = false; +-var enabled; ++var ExtensionManager = class { ++ constructor() { ++ this._initted = false; ++ this._enabled = false; + +-function disableExtension(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; +- if (!extension) +- return; ++ this._enabledExtensions = []; ++ this._extensionOrder = []; + +- if (extension.state != ExtensionState.ENABLED) +- return; ++ Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); ++ this._sessionUpdated(); ++ } + +- // "Rebase" the extension order by disabling and then enabling extensions +- // in order to help prevent conflicts. ++ disableExtension(uuid) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ if (!extension) ++ return; + +- // Example: +- // order = [A, B, C, D, E] +- // user disables C +- // this should: disable E, disable D, disable C, enable D, enable E ++ if (extension.state != ExtensionState.ENABLED) ++ return; + +- let orderIdx = extensionOrder.indexOf(uuid); +- let order = extensionOrder.slice(orderIdx + 1); +- let orderReversed = order.slice().reverse(); ++ // "Rebase" the extension order by disabling and then enabling extensions ++ // in order to help prevent conflicts. ++ ++ // Example: ++ // order = [A, B, C, D, E] ++ // user disables C ++ // this should: disable E, disable D, disable C, enable D, enable E ++ ++ let orderIdx = this._extensionOrder.indexOf(uuid); ++ let order = this._extensionOrder.slice(orderIdx + 1); ++ let orderReversed = order.slice().reverse(); ++ ++ for (let i = 0; i < orderReversed.length; i++) { ++ let uuid = orderReversed[i]; ++ try { ++ ExtensionUtils.extensions[uuid].stateObj.disable(); ++ } catch (e) { ++ this.logExtensionError(uuid, e); ++ } ++ } ++ ++ if (extension.stylesheet) { ++ let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); ++ theme.unload_stylesheet(extension.stylesheet); ++ delete extension.stylesheet; ++ } + +- for (let i = 0; i < orderReversed.length; i++) { +- let uuid = orderReversed[i]; + try { +- ExtensionUtils.extensions[uuid].stateObj.disable(); ++ extension.stateObj.disable(); + } catch(e) { +- logExtensionError(uuid, e); ++ this.logExtensionError(uuid, e); + } +- } + +- if (extension.stylesheet) { +- let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); +- theme.unload_stylesheet(extension.stylesheet); +- delete extension.stylesheet; +- } ++ for (let i = 0; i < order.length; i++) { ++ let uuid = order[i]; ++ try { ++ ExtensionUtils.extensions[uuid].stateObj.enable(); ++ } catch (e) { ++ this.logExtensionError(uuid, e); ++ } ++ } + +- try { +- extension.stateObj.disable(); +- } catch(e) { +- logExtensionError(uuid, e); +- } ++ this._extensionOrder.splice(orderIdx, 1); + +- for (let i = 0; i < order.length; i++) { +- let uuid = order[i]; +- try { +- ExtensionUtils.extensions[uuid].stateObj.enable(); +- } catch(e) { +- logExtensionError(uuid, e); ++ if (extension.state != ExtensionState.ERROR) { ++ extension.state = ExtensionState.DISABLED; ++ this.emit('extension-state-changed', extension); + } + } + +- extensionOrder.splice(orderIdx, 1); +- +- if ( extension.state != ExtensionState.ERROR ) { +- extension.state = ExtensionState.DISABLED; +- _signals.emit('extension-state-changed', extension); +- } +-} ++ enableExtension(uuid) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ if (!extension) ++ return; + +-function enableExtension(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; +- if (!extension) +- return; ++ if (extension.state == ExtensionState.INITIALIZED) ++ this.initExtension(uuid); + +- if (extension.state == ExtensionState.INITIALIZED) +- initExtension(uuid); ++ if (extension.state != ExtensionState.DISABLED) ++ return; + +- if (extension.state != ExtensionState.DISABLED) +- return; ++ this._extensionOrder.push(uuid); + +- extensionOrder.push(uuid); ++ let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css']; ++ let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); ++ for (let i = 0; i < stylesheetNames.length; i++) { ++ try { ++ let stylesheetFile = extension.dir.get_child(stylesheetNames[i]); ++ theme.load_stylesheet(stylesheetFile); ++ extension.stylesheet = stylesheetFile; ++ break; ++ } catch (e) { ++ if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) ++ continue; // not an error ++ log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`); ++ return; ++ } ++ } + +- let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css']; +- let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); +- for (let i = 0; i < stylesheetNames.length; i++) { + try { +- let stylesheetFile = extension.dir.get_child(stylesheetNames[i]); +- theme.load_stylesheet(stylesheetFile); +- extension.stylesheet = stylesheetFile; +- break; ++ extension.stateObj.enable(); ++ extension.state = ExtensionState.ENABLED; ++ this.emit('extension-state-changed', extension); ++ return; + } catch (e) { +- if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) +- continue; // not an error +- log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`); ++ if (extension.stylesheet) { ++ theme.unload_stylesheet(extension.stylesheet); ++ delete extension.stylesheet; ++ } ++ this.logExtensionError(uuid, e); + return; + } + } + +- try { +- extension.stateObj.enable(); +- extension.state = ExtensionState.ENABLED; +- _signals.emit('extension-state-changed', extension); +- return; +- } catch(e) { +- if (extension.stylesheet) { +- theme.unload_stylesheet(extension.stylesheet); +- delete extension.stylesheet; +- } +- logExtensionError(uuid, e); +- return; +- } +-} +- +-function logExtensionError(uuid, error) { +- let extension = ExtensionUtils.extensions[uuid]; +- if (!extension) +- return; ++ logExtensionError(uuid, error) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ if (!extension) ++ return; + +- let message = '' + error; ++ let message = '' + error; + +- extension.state = ExtensionState.ERROR; +- if (!extension.errors) +- extension.errors = []; +- extension.errors.push(message); ++ extension.state = ExtensionState.ERROR; ++ if (!extension.errors) ++ extension.errors = []; ++ extension.errors.push(message); + +- log('Extension "%s" had error: %s'.format(uuid, message)); +- _signals.emit('extension-state-changed', { uuid: uuid, ++ log('Extension "%s" had error: %s'.format(uuid, message)); ++ this.emit('extension-state-changed', { uuid: uuid, + error: message, + state: extension.state }); +-} ++ } + +-function loadExtension(extension) { +- // Default to error, we set success as the last step +- extension.state = ExtensionState.ERROR; ++ loadExtension(extension) { ++ // Default to error, we set success as the last step ++ extension.state = ExtensionState.ERROR; + +- let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); ++ let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY); + +- if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { +- extension.state = ExtensionState.OUT_OF_DATE; +- } else { +- let enabled = enabledExtensions.indexOf(extension.uuid) != -1; +- if (enabled) { +- if (!initExtension(extension.uuid)) +- return; +- if (extension.state == ExtensionState.DISABLED) +- enableExtension(extension.uuid); ++ if (checkVersion && ExtensionUtils.isOutOfDate(extension)) { ++ extension.state = ExtensionState.OUT_OF_DATE; + } else { +- extension.state = ExtensionState.INITIALIZED; ++ let enabled = this._enabledExtensions.includes(extension.uuid); ++ if (enabled) { ++ if (!this.initExtension(extension.uuid)) ++ return; ++ if (extension.state == ExtensionState.DISABLED) ++ this.enableExtension(extension.uuid); ++ } else { ++ extension.state = ExtensionState.INITIALIZED; ++ } + } ++ ++ this.emit('extension-state-changed', extension); + } + +- _signals.emit('extension-state-changed', extension); +-} +- +-function unloadExtension(extension) { +- // Try to disable it -- if it's ERROR'd, we can't guarantee that, +- // but it will be removed on next reboot, and hopefully nothing +- // broke too much. +- disableExtension(extension.uuid); +- +- extension.state = ExtensionState.UNINSTALLED; +- _signals.emit('extension-state-changed', extension); +- +- delete ExtensionUtils.extensions[extension.uuid]; +- return true; +-} +- +-function reloadExtension(oldExtension) { +- // Grab the things we'll need to pass to createExtensionObject +- // to reload it. +- let { uuid: uuid, dir: dir, type: type } = oldExtension; +- +- // Then unload the old extension. +- unloadExtension(oldExtension); +- +- // Now, recreate the extension and load it. +- let newExtension; +- try { +- newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type); +- } catch(e) { +- logExtensionError(uuid, e); +- return; ++ unloadExtension(extension) { ++ // Try to disable it -- if it's ERROR'd, we can't guarantee that, ++ // but it will be removed on next reboot, and hopefully nothing ++ // broke too much. ++ this.disableExtension(extension.uuid); ++ ++ extension.state = ExtensionState.UNINSTALLED; ++ this.emit('extension-state-changed', extension); ++ ++ delete ExtensionUtils.extensions[extension.uuid]; ++ return true; + } + +- loadExtension(newExtension); +-} ++ reloadExtension(oldExtension) { ++ // Grab the things we'll need to pass to createExtensionObject ++ // to reload it. ++ let { uuid: uuid, dir: dir, type: type } = oldExtension; + +-function initExtension(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; +- let dir = extension.dir; ++ // Then unload the old extension. ++ this.unloadExtension(oldExtension); + +- if (!extension) +- throw new Error("Extension was not properly created. Call loadExtension first"); ++ // Now, recreate the extension and load it. ++ let newExtension; ++ try { ++ newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type); ++ } catch (e) { ++ this.logExtensionError(uuid, e); ++ return; ++ } + +- let extensionJs = dir.get_child('extension.js'); +- if (!extensionJs.query_exists(null)) { +- logExtensionError(uuid, new Error('Missing extension.js')); +- return false; ++ this.loadExtension(newExtension); + } + +- let extensionModule; +- let extensionState = null; ++ initExtension(uuid) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ let dir = extension.dir; + +- ExtensionUtils.installImporter(extension); +- try { +- extensionModule = extension.imports.extension; +- } catch(e) { +- logExtensionError(uuid, e); +- return false; +- } ++ if (!extension) ++ throw new Error("Extension was not properly created. Call loadExtension first"); ++ ++ let extensionJs = dir.get_child('extension.js'); ++ if (!extensionJs.query_exists(null)) { ++ this.logExtensionError(uuid, new Error('Missing extension.js')); ++ return false; ++ } ++ ++ let extensionModule; ++ let extensionState = null; + +- if (extensionModule.init) { ++ ExtensionUtils.installImporter(extension); + try { +- extensionState = extensionModule.init(extension); ++ extensionModule = extension.imports.extension; + } catch(e) { +- logExtensionError(uuid, e); ++ this.logExtensionError(uuid, e); + return false; + } ++ ++ if (extensionModule.init) { ++ try { ++ extensionState = extensionModule.init(extension); ++ } catch (e) { ++ this.logExtensionError(uuid, e); ++ return false; ++ } ++ } ++ ++ if (!extensionState) ++ extensionState = extensionModule; ++ extension.stateObj = extensionState; ++ ++ extension.state = ExtensionState.DISABLED; ++ this.emit('extension-loaded', uuid); ++ return true; + } + +- if (!extensionState) +- extensionState = extensionModule; +- extension.stateObj = extensionState; +- +- extension.state = ExtensionState.DISABLED; +- _signals.emit('extension-loaded', uuid); +- return true; +-} +- +-function getEnabledExtensions() { +- let extensions; +- if (Array.isArray(Main.sessionMode.enabledExtensions)) +- extensions = Main.sessionMode.enabledExtensions; +- else +- extensions = []; +- +- if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY)) +- return extensions; +- +- return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY)); +-} +- +-function onEnabledExtensionsChanged() { +- let newEnabledExtensions = getEnabledExtensions(); +- +- if (!enabled) +- return; +- +- // Find and enable all the newly enabled extensions: UUIDs found in the +- // new setting, but not in the old one. +- newEnabledExtensions.filter( +- uuid => !enabledExtensions.includes(uuid) +- ).forEach(uuid => { +- enableExtension(uuid); +- }); +- +- // Find and disable all the newly disabled extensions: UUIDs found in the +- // old setting, but not in the new one. +- enabledExtensions.filter( +- item => !newEnabledExtensions.includes(item) +- ).forEach(uuid => { +- disableExtension(uuid); +- }); +- +- enabledExtensions = newEnabledExtensions; +-} +- +-function _onVersionValidationChanged() { +- // we want to reload all extensions, but only enable +- // extensions when allowed by the sessionMode, so +- // temporarily disable them all +- enabledExtensions = []; +- for (let uuid in ExtensionUtils.extensions) +- reloadExtension(ExtensionUtils.extensions[uuid]); +- enabledExtensions = getEnabledExtensions(); +- +- if (Main.sessionMode.allowExtensions) { +- enabledExtensions.forEach(uuid => { +- enableExtension(uuid); +- }); ++ _getEnabledExtensions() { ++ let extensions; ++ if (Array.isArray(Main.sessionMode.enabledExtensions)) ++ extensions = Main.sessionMode.enabledExtensions; ++ else ++ extensions = []; ++ ++ if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY)) ++ return extensions; ++ ++ return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY)); + } +-} +- +-function _loadExtensions() { +- global.settings.connect('changed::' + ENABLED_EXTENSIONS_KEY, onEnabledExtensionsChanged); +- global.settings.connect('changed::' + DISABLE_USER_EXTENSIONS_KEY, onEnabledExtensionsChanged); +- global.settings.connect('changed::' + EXTENSION_DISABLE_VERSION_CHECK_KEY, _onVersionValidationChanged); +- +- enabledExtensions = getEnabledExtensions(); +- +- let finder = new ExtensionUtils.ExtensionFinder(); +- finder.connect('extension-found', (finder, extension) => { +- loadExtension(extension); +- if (Main.sessionMode.enabledExtensions.indexOf(extension.uuid) != -1) +- extension.type = ExtensionUtils.ExtensionType.SESSION_MODE; +- }); +- finder.scanExtensions(); +-} +- +-function enableAllExtensions() { +- if (enabled) +- return; +- +- if (!initted) { +- _loadExtensions(); +- initted = true; +- } else { +- enabledExtensions.forEach(uuid => { +- enableExtension(uuid); ++ ++ _onEnabledExtensionsChanged() { ++ let newEnabledExtensions = this._getEnabledExtensions(); ++ ++ if (!this._enabled) ++ return; ++ ++ // Find and enable all the newly enabled extensions: UUIDs found in the ++ // new setting, but not in the old one. ++ newEnabledExtensions.filter( ++ uuid => !this._enabledExtensions.includes(uuid) ++ ).forEach(uuid => { ++ this.enableExtension(uuid); + }); ++ ++ // Find and disable all the newly disabled extensions: UUIDs found in the ++ // old setting, but not in the new one. ++ this._enabledExtensions.filter( ++ item => !newEnabledExtensions.includes(item) ++ ).forEach(uuid => { ++ this.disableExtension(uuid); ++ }); ++ ++ this._enabledExtensions = newEnabledExtensions; ++ } ++ ++ _onVersionValidationChanged() { ++ // we want to reload all extensions, but only enable ++ // extensions when allowed by the sessionMode, so ++ // temporarily disable them all ++ this._enabledExtensions = []; ++ for (let uuid in ExtensionUtils.extensions) ++ this.reloadExtension(ExtensionUtils.extensions[uuid]); ++ this._enabledExtensions = this._getEnabledExtensions(); ++ ++ if (Main.sessionMode.allowExtensions) { ++ this._enabledExtensions.forEach(uuid => { ++ this.enableExtension(uuid); ++ }); ++ } + } +- enabled = true; +-} + +-function disableAllExtensions() { +- if (!enabled) +- return; ++ _loadExtensions() { ++ global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, ++ this._onEnabledExtensionsChanged.bind(this)); ++ global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, ++ this._onEnabledExtensionsChanged.bind(this)); ++ global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, ++ this._onVersionValidationChanged.bind(this)); ++ ++ this._enabledExtensions = this._getEnabledExtensions(); + +- if (initted) { +- extensionOrder.slice().reverse().forEach(uuid => { +- disableExtension(uuid); ++ let finder = new ExtensionUtils.ExtensionFinder(); ++ finder.connect('extension-found', (finder, extension) => { ++ this.loadExtension(extension); + }); ++ finder.scanExtensions(); ++ } ++ ++ enableAllExtensions() { ++ if (this._enabled) ++ return; ++ ++ if (!this._initted) { ++ this._loadExtensions(); ++ this._initted = true; ++ } else { ++ this._enabledExtensions.forEach(uuid => { ++ this.enableExtension(uuid); ++ }); ++ } ++ this._enabled = true; + } + +- enabled = false; +-} +- +-function _sessionUpdated() { +- // For now sessionMode.allowExtensions controls extensions from both the +- // 'enabled-extensions' preference and the sessionMode.enabledExtensions +- // property; it might make sense to make enabledExtensions independent +- // from allowExtensions in the future +- if (Main.sessionMode.allowExtensions) { +- if (initted) +- enabledExtensions = getEnabledExtensions(); +- enableAllExtensions(); +- } else { +- disableAllExtensions(); ++ disableAllExtensions() { ++ if (!this._enabled) ++ return; ++ ++ if (this._initted) { ++ this._extensionOrder.slice().reverse().forEach(uuid => { ++ this.disableExtension(uuid); ++ }); ++ } ++ ++ this._enabled = false; + } +-} + +-function init() { +- Main.sessionMode.connect('updated', _sessionUpdated); +- _sessionUpdated(); +-} ++ _sessionUpdated() { ++ // For now sessionMode.allowExtensions controls extensions from both the ++ // 'enabled-extensions' preference and the sessionMode.enabledExtensions ++ // property; it might make sense to make enabledExtensions independent ++ // from allowExtensions in the future ++ if (Main.sessionMode.allowExtensions) { ++ if (this._initted) ++ this._enabledExtensions = this._getEnabledExtensions(); ++ this.enableAllExtensions(); ++ } else { ++ this.disableAllExtensions(); ++ } ++ } ++}; ++Signals.addSignalMethods(ExtensionManager.prototype); +diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js +index fefb3f731..e947574f2 100644 +--- a/js/ui/lookingGlass.js ++++ b/js/ui/lookingGlass.js +@@ -7,7 +7,6 @@ const Signals = imports.signals; + const System = imports.system; + + const History = imports.misc.history; +-const ExtensionSystem = imports.ui.extensionSystem; + const ExtensionUtils = imports.misc.extensionUtils; + const ShellEntry = imports.ui.shellEntry; + const Tweener = imports.ui.tweener; +@@ -624,8 +623,8 @@ var Extensions = class Extensions { + for (let uuid in ExtensionUtils.extensions) + this._loadExtension(null, uuid); + +- ExtensionSystem.connect('extension-loaded', +- this._loadExtension.bind(this)); ++ Main.extensionManager.connect('extension-loaded', ++ this._loadExtension.bind(this)); + } + + _loadExtension(o, uuid) { +diff --git a/js/ui/main.js b/js/ui/main.js +index 8dde95bf9..7bfbce497 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -43,6 +43,7 @@ const STICKY_KEYS_ENABLE = 'stickykeys-enable'; + const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a'; + + var componentManager = null; ++var extensionManager = null; + var panel = null; + var overview = null; + var runDialog = null; +@@ -218,7 +219,7 @@ function _initializeUI() { + _startDate = new Date(); + + ExtensionDownloader.init(); +- ExtensionSystem.init(); ++ extensionManager = new ExtensionSystem.ExtensionManager(); + + if (sessionMode.isGreeter && screenShield) { + layoutManager.connect('startup-prepared', () => { +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index 112d60feb..4b04e68ac 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -4,7 +4,6 @@ const { Gio, GLib, Meta, Shell } = imports.gi; + const Lang = imports.lang; + + const Config = imports.misc.config; +-const ExtensionSystem = imports.ui.extensionSystem; + const ExtensionDownloader = imports.ui.extensionDownloader; + const ExtensionUtils = imports.misc.extensionUtils; + const Main = imports.ui.main; +@@ -250,8 +249,8 @@ var GnomeShellExtensions = class { + constructor() { + this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this); + this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell'); +- ExtensionSystem.connect('extension-state-changed', +- this._extensionStateChanged.bind(this)); ++ Main.extensionManager.connect('extension-state-changed', ++ this._extensionStateChanged.bind(this)); + } + + ListExtensions() { +@@ -334,7 +333,7 @@ var GnomeShellExtensions = class { + if (!extension) + return; + +- ExtensionSystem.reloadExtension(extension); ++ Main.extensionManager.reloadExtension(extension); + } + + CheckForUpdates() { +-- +2.29.2 + + +From 7419ee28b5b568dd4478db7f3c890ff01678637a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 8 Jul 2019 02:53:32 +0200 +Subject: [PATCH 03/26] extensionSystem: Make methods to call extension + functions private + +While public methods to enable/disable extensions make sense for an +extension manager, the existing ones are only used internally. Make +them private and rename them, so that we can re-use the current +names for more useful public methods. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/ui/extensionSystem.js | 32 ++++++++++++++++---------------- + 1 file changed, 16 insertions(+), 16 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index b7e908223..c5fb007ae 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -24,7 +24,7 @@ var ExtensionManager = class { + this._sessionUpdated(); + } + +- disableExtension(uuid) { ++ _callExtensionDisable(uuid) { + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) + return; +@@ -82,13 +82,13 @@ var ExtensionManager = class { + } + } + +- enableExtension(uuid) { ++ _callExtensionEnable(uuid) { + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) + return; + + if (extension.state == ExtensionState.INITIALIZED) +- this.initExtension(uuid); ++ this._callExtensionInit(uuid); + + if (extension.state != ExtensionState.DISABLED) + return; +@@ -155,10 +155,10 @@ var ExtensionManager = class { + } else { + let enabled = this._enabledExtensions.includes(extension.uuid); + if (enabled) { +- if (!this.initExtension(extension.uuid)) ++ if (!this._callExtensionInit(extension.uuid)) + return; + if (extension.state == ExtensionState.DISABLED) +- this.enableExtension(extension.uuid); ++ this._callExtensionEnable(extension.uuid); + } else { + extension.state = ExtensionState.INITIALIZED; + } +@@ -171,7 +171,7 @@ var ExtensionManager = class { + // Try to disable it -- if it's ERROR'd, we can't guarantee that, + // but it will be removed on next reboot, and hopefully nothing + // broke too much. +- this.disableExtension(extension.uuid); ++ this._callExtensionDisable(extension.uuid); + + extension.state = ExtensionState.UNINSTALLED; + this.emit('extension-state-changed', extension); +@@ -200,7 +200,7 @@ var ExtensionManager = class { + this.loadExtension(newExtension); + } + +- initExtension(uuid) { ++ _callExtensionInit(uuid) { + let extension = ExtensionUtils.extensions[uuid]; + let dir = extension.dir; + +@@ -266,7 +266,7 @@ var ExtensionManager = class { + newEnabledExtensions.filter( + uuid => !this._enabledExtensions.includes(uuid) + ).forEach(uuid => { +- this.enableExtension(uuid); ++ this._callExtensionEnable(uuid); + }); + + // Find and disable all the newly disabled extensions: UUIDs found in the +@@ -274,7 +274,7 @@ var ExtensionManager = class { + this._enabledExtensions.filter( + item => !newEnabledExtensions.includes(item) + ).forEach(uuid => { +- this.disableExtension(uuid); ++ this._callExtensionDisable(uuid); + }); + + this._enabledExtensions = newEnabledExtensions; +@@ -291,7 +291,7 @@ var ExtensionManager = class { + + if (Main.sessionMode.allowExtensions) { + this._enabledExtensions.forEach(uuid => { +- this.enableExtension(uuid); ++ this._callExtensionEnable(uuid); + }); + } + } +@@ -313,7 +313,7 @@ var ExtensionManager = class { + finder.scanExtensions(); + } + +- enableAllExtensions() { ++ _enableAllExtensions() { + if (this._enabled) + return; + +@@ -322,19 +322,19 @@ var ExtensionManager = class { + this._initted = true; + } else { + this._enabledExtensions.forEach(uuid => { +- this.enableExtension(uuid); ++ this._callExtensionEnable(uuid); + }); + } + this._enabled = true; + } + +- disableAllExtensions() { ++ _disableAllExtensions() { + if (!this._enabled) + return; + + if (this._initted) { + this._extensionOrder.slice().reverse().forEach(uuid => { +- this.disableExtension(uuid); ++ this._callExtensionDisable(uuid); + }); + } + +@@ -349,9 +349,9 @@ var ExtensionManager = class { + if (Main.sessionMode.allowExtensions) { + if (this._initted) + this._enabledExtensions = this._getEnabledExtensions(); +- this.enableAllExtensions(); ++ this._enableAllExtensions(); + } else { +- this.disableAllExtensions(); ++ this._disableAllExtensions(); + } + } + }; +-- +2.29.2 + + +From e88419278531b136984f9f05a8c056145d03edba Mon Sep 17 00:00:00 2001 +From: Didier Roche +Date: Wed, 17 Jan 2018 13:43:11 +0100 +Subject: [PATCH 04/26] extensionSystem: Add methods to enable/disable + extensions + +Extensions are currently enabled or disabled by directly changing the +list in the 'enabled-extensions' GSettings key. As we will soon add +an overriding 'disabled-extensions' key as well, it makes sense to +offer explicit API for enabling/disabling to avoid duplicating the +logic. + +For the corresponding D-Bus API, the methods were even mentioned in +the GSettings schema, albeit unimplemented until now. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + .../org.gnome.Shell.Extensions.xml | 24 +++++++++++++++++ + js/ui/extensionDownloader.js | 12 ++------- + js/ui/extensionSystem.js | 26 +++++++++++++++++++ + js/ui/shellDBus.js | 8 ++++++ + 4 files changed, 60 insertions(+), 10 deletions(-) + +diff --git a/data/dbus-interfaces/org.gnome.Shell.Extensions.xml b/data/dbus-interfaces/org.gnome.Shell.Extensions.xml +index ce69439fc..22273f889 100644 +--- a/data/dbus-interfaces/org.gnome.Shell.Extensions.xml ++++ b/data/dbus-interfaces/org.gnome.Shell.Extensions.xml +@@ -173,6 +173,30 @@ + + + ++ ++ \ ++ \ ++ \ ++ \ ++ ++ ++ \ ++ \ ++ \ ++ \ ++ + + + + +diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js +index 43efa95e9..2b4ce5753 100644 +--- a/js/extensionPrefs/main.js ++++ b/js/extensionPrefs/main.js +@@ -241,7 +241,7 @@ var Application = class { + this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder'); + + this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell'); +- this._shellProxy.connectSignal('ExtensionStatusChanged', (proxy, senderName, [uuid, state, error]) => { ++ this._shellProxy.connectSignal('ExtensionStateChanged', (proxy, senderName, [uuid, state]) => { + if (ExtensionUtils.extensions[uuid] !== undefined) + this._scanExtensions(); + }); +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 8ff0fa56f..98eaf5259 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -159,15 +159,14 @@ var ExtensionManager = class { + + let message = '' + error; + ++ extension.error = message; + extension.state = ExtensionState.ERROR; + if (!extension.errors) + extension.errors = []; + extension.errors.push(message); + + log('Extension "%s" had error: %s'.format(uuid, message)); +- this.emit('extension-state-changed', { uuid: uuid, +- error: message, +- state: extension.state }); ++ this.emit('extension-state-changed', extension); + } + + loadExtension(extension) { +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index af5889789..23274c0a3 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -335,6 +335,10 @@ var GnomeShellExtensions = class { + } + + _extensionStateChanged(_, newState) { ++ let state = ExtensionUtils.serializeExtension(newState); ++ this._dbusImpl.emit_signal('ExtensionStateChanged', ++ new GLib.Variant('(sa{sv})', [newState.uuid, state])); ++ + this._dbusImpl.emit_signal('ExtensionStatusChanged', + GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error])); + } +-- +2.29.2 + + +From 47d185f8964f9e04430f4ef97d6d712faaf32078 Mon Sep 17 00:00:00 2001 +From: Didier Roche +Date: Tue, 4 Dec 2018 09:31:27 +0100 +Subject: [PATCH 07/26] extensionSystem: Add canChange property to extensions + +Whether or not an extension can be enabled/disabled depends on various +factors: Whether the extension is in error state, whether user extensions +are disabled and whether the underlying GSettings keys are writable. + +This is complex enough to share the logic, so add it to the extension +properties that are exposed over D-Bus. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/misc/extensionUtils.js | 3 ++- + js/ui/extensionSystem.js | 44 +++++++++++++++++++++++++++++++++------ + 2 files changed, 40 insertions(+), 7 deletions(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index bc9c36f4e..025cd042e 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -31,7 +31,7 @@ var ExtensionState = { + UNINSTALLED: 99 + }; + +-const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs']; ++const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange']; + + // Maps uuid -> metadata object + var extensions = {}; +@@ -222,6 +222,7 @@ function createExtensionObject(uuid, dir, type) { + extension.path = dir.get_path(); + extension.error = ''; + extension.hasPrefs = dir.get_child('prefs.js').query_exists(null); ++ extension.canChange = false; + + extensions[uuid] = extension; + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 98eaf5259..a83e53c83 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -189,6 +189,7 @@ var ExtensionManager = class { + } + } + ++ this._updateCanChange(extension); + this.emit('extension-state-changed', extension); + } + +@@ -267,12 +268,28 @@ var ExtensionManager = class { + return true; + } + +- _getEnabledExtensions() { +- let extensions; ++ _getModeExtensions() { + if (Array.isArray(Main.sessionMode.enabledExtensions)) +- extensions = Main.sessionMode.enabledExtensions; +- else +- extensions = []; ++ return Main.sessionMode.enabledExtensions; ++ return []; ++ } ++ ++ _updateCanChange(extension) { ++ let hasError = ++ extension.state == ExtensionState.ERROR || ++ extension.state == ExtensionState.OUT_OF_DATE; ++ ++ let isMode = this._getModeExtensions().includes(extension.uuid); ++ let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY); ++ ++ extension.canChange = ++ !hasError && ++ global.settings.is_writable(ENABLED_EXTENSIONS_KEY) && ++ (isMode || !modeOnly); ++ } ++ ++ _getEnabledExtensions() { ++ let extensions = this._getModeExtensions(); + + if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY)) + return extensions; +@@ -280,6 +297,11 @@ var ExtensionManager = class { + return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY)); + } + ++ _onUserExtensionsEnabledChanged() { ++ this._onEnabledExtensionsChanged(); ++ this._onSettingsWritableChanged(); ++ } ++ + _onEnabledExtensionsChanged() { + let newEnabledExtensions = this._getEnabledExtensions(); + +@@ -305,6 +327,14 @@ var ExtensionManager = class { + this._enabledExtensions = newEnabledExtensions; + } + ++ _onSettingsWritableChanged() { ++ for (let uuid in ExtensionUtils.extensions) { ++ let extension = ExtensionUtils.extensions[uuid]; ++ this._updateCanChange(extension); ++ this.emit('extension-state-changed', extension); ++ } ++ } ++ + _onVersionValidationChanged() { + // we want to reload all extensions, but only enable + // extensions when allowed by the sessionMode, so +@@ -325,9 +355,11 @@ var ExtensionManager = class { + global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, + this._onEnabledExtensionsChanged.bind(this)); + global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, +- this._onEnabledExtensionsChanged.bind(this)); ++ this._onUserExtensionsEnabledChanged.bind(this)); + global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, + this._onVersionValidationChanged.bind(this)); ++ global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`, ++ this._onSettingsWritableChanged.bind(this)); + + this._enabledExtensions = this._getEnabledExtensions(); + +-- +2.29.2 + + +From ce14c00ca0707c78dc920ede3a157b7c9d55fff5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Tue, 28 May 2019 23:22:37 +0200 +Subject: [PATCH 08/26] extensionPrefs: Inherit from Gtk.Application + +Extension preferences Application class is just a container for a GtkApplication +so instead of using composition we can inherit from the base GObject class. + +Also replace signal connections with vfunc's. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/631 +--- + js/extensionPrefs/main.js | 35 +++++++++++++++++------------------ + 1 file changed, 17 insertions(+), 18 deletions(-) + +diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js +index 2b4ce5753..94a5b12fe 100644 +--- a/js/extensionPrefs/main.js ++++ b/js/extensionPrefs/main.js +@@ -17,18 +17,16 @@ function stripPrefix(string, prefix) { + return string; + } + +-var Application = class { +- constructor() { ++var Application = GObject.registerClass({ ++ GTypeName: 'ExtensionPrefs_Application' ++}, class Application extends Gtk.Application { ++ _init() { + GLib.set_prgname('gnome-shell-extension-prefs'); +- this.application = new Gtk.Application({ ++ super._init({ + application_id: 'org.gnome.shell.ExtensionPrefs', + flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE + }); + +- this.application.connect('activate', this._onActivate.bind(this)); +- this.application.connect('command-line', this._onCommandLine.bind(this)); +- this.application.connect('startup', this._onStartup.bind(this)); +- + this._extensionPrefsModules = {}; + + this._startupUuid = null; +@@ -84,7 +82,7 @@ var Application = class { + visible: true })); + + if (this._skipMainWindow) { +- this.application.add_window(dialog); ++ this.add_window(dialog); + if (this._window) + this._window.destroy(); + this._window = dialog; +@@ -206,8 +204,8 @@ var Application = class { + return scroll; + } + +- _buildUI(app) { +- this._window = new Gtk.ApplicationWindow({ application: app, ++ _buildUI() { ++ this._window = new Gtk.ApplicationWindow({ application: this, + window_position: Gtk.WindowPosition.CENTER }); + + this._window.set_default_size(800, 500); +@@ -295,17 +293,19 @@ var Application = class { + this._loaded = true; + } + +- _onActivate() { ++ vfunc_activate() { + this._window.present(); + } + +- _onStartup(app) { +- this._buildUI(app); ++ vfunc_startup() { ++ super.vfunc_startup(); ++ ++ this._buildUI(); + this._scanExtensions(); + } + +- _onCommandLine(app, commandLine) { +- app.activate(); ++ vfunc_command_line(commandLine) { ++ this.activate(); + let args = commandLine.get_arguments(); + + if (args.length) { +@@ -325,7 +325,7 @@ var Application = class { + } + return 0; + } +-}; ++}); + + var Expander = GObject.registerClass({ + Properties: { +@@ -631,6 +631,5 @@ function main(argv) { + Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR); + Gettext.textdomain(Config.GETTEXT_PACKAGE); + +- let app = new Application(); +- app.application.run(argv); ++ new Application().run(argv); + } +-- +2.29.2 + + +From ea202d21b65c027db03e773a51bd8fecf4a2fb0a Mon Sep 17 00:00:00 2001 +From: Didier Roche +Date: Thu, 1 Nov 2018 13:50:30 +0100 +Subject: [PATCH 09/26] extensionPrefs: Attach extension object to each row + +Each row represents an extension, so it makes sense to associate the +rows with the actual extensions instead of linking rows and extensions +by looking up the UUID in the external extensions map in ExtensionUtils. + +This will also make it much easier to stop using the shared extension +loading / map in favor of the extension D-Bus API. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/extensionPrefs/main.js | 120 +++++++++++++++++++------------------- + 1 file changed, 60 insertions(+), 60 deletions(-) + +diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js +index 94a5b12fe..7e7b2dcc7 100644 +--- a/js/extensionPrefs/main.js ++++ b/js/extensionPrefs/main.js +@@ -34,52 +34,31 @@ var Application = GObject.registerClass({ + this._skipMainWindow = false; + } + +- _extensionAvailable(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; +- +- if (!extension) +- return false; ++ _showPrefs(uuid) { ++ let row = this._extensionSelector.get_children().find(c => { ++ return c.uuid === uuid && c.hasPrefs; ++ }); + +- if (!extension.dir.get_child('prefs.js').query_exists(null)) ++ if (!row) + return false; + +- return true; +- } +- +- _getExtensionPrefsModule(extension) { +- let uuid = extension.metadata.uuid; +- +- if (this._extensionPrefsModules.hasOwnProperty(uuid)) +- return this._extensionPrefsModules[uuid]; +- +- ExtensionUtils.installImporter(extension); +- +- let prefsModule = extension.imports.prefs; +- prefsModule.init(extension.metadata); +- +- this._extensionPrefsModules[uuid] = prefsModule; +- return prefsModule; +- } +- +- _selectExtension(uuid) { +- if (!this._extensionAvailable(uuid)) +- return; +- +- let extension = ExtensionUtils.extensions[uuid]; + let widget; + + try { +- let prefsModule = this._getExtensionPrefsModule(extension); +- widget = prefsModule.buildPrefsWidget(); ++ widget = row.prefsModule.buildPrefsWidget(); + } catch (e) { +- widget = this._buildErrorUI(extension, e); ++ widget = this._buildErrorUI(row, e); + } + +- let dialog = new Gtk.Window({ modal: !this._skipMainWindow, +- type_hint: Gdk.WindowTypeHint.DIALOG }); +- dialog.set_titlebar(new Gtk.HeaderBar({ show_close_button: true, +- title: extension.metadata.name, +- visible: true })); ++ let dialog = new Gtk.Window({ ++ modal: !this._skipMainWindow, ++ type_hint: Gdk.WindowTypeHint.DIALOG ++ }); ++ dialog.set_titlebar(new Gtk.HeaderBar({ ++ show_close_button: true, ++ title: row.name, ++ visible: true ++ })); + + if (this._skipMainWindow) { + this.add_window(dialog); +@@ -96,7 +75,7 @@ var Application = GObject.registerClass({ + dialog.show(); + } + +- _buildErrorUI(extension, exc) { ++ _buildErrorUI(row, exc) { + let scroll = new Gtk.ScrolledWindow({ + hscrollbar_policy: Gtk.PolicyType.NEVER, + propagate_natural_height: true +@@ -183,13 +162,13 @@ var Application = GObject.registerClass({ + label: _("Homepage"), + tooltip_text: _("Visit extension homepage"), + no_show_all: true, +- visible: extension.metadata.url != null ++ visible: row.url != null + }); + toolbar.add(urlButton); + + urlButton.connect('clicked', w => { + let context = w.get_display().get_app_launch_context(); +- Gio.AppInfo.launch_default_for_uri(extension.metadata.url, context); ++ Gio.AppInfo.launch_default_for_uri(row.url, context); + }); + + let expandedBox = new Gtk.Box({ +@@ -248,9 +227,7 @@ var Application = GObject.registerClass({ + } + + _sortList(row1, row2) { +- let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name; +- let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name; +- return name1.localeCompare(name2); ++ return row1.name.localeCompare(row2.name); + } + + _updateHeader(row, before) { +@@ -269,11 +246,10 @@ var Application = GObject.registerClass({ + } + + _extensionFound(finder, extension) { +- let row = new ExtensionRow(extension.uuid); ++ let row = new ExtensionRow(extension); + +- row.prefsButton.visible = this._extensionAvailable(row.uuid); + row.prefsButton.connect('clicked', () => { +- this._selectExtension(row.uuid); ++ this._showPrefs(row.uuid); + }); + + row.show_all(); +@@ -286,8 +262,8 @@ var Application = GObject.registerClass({ + else + this._mainStack.visible_child_name = 'placeholder'; + +- if (this._startupUuid && this._extensionAvailable(this._startupUuid)) +- this._selectExtension(this._startupUuid); ++ if (this._startupUuid) ++ this._showPrefs(this._startupUuid); + this._startupUuid = null; + this._skipMainWindow = false; + this._loaded = true; +@@ -316,11 +292,9 @@ var Application = GObject.registerClass({ + // Strip off "extension:///" prefix which fakes a URI, if it exists + uuid = stripPrefix(uuid, "extension:///"); + +- if (this._extensionAvailable(uuid)) +- this._selectExtension(uuid); +- else if (!this._loaded) ++ if (!this._loaded) + this._startupUuid = uuid; +- else ++ else if (!this._showPrefs(uuid)) + this._skipMainWindow = false; + } + return 0; +@@ -504,10 +478,11 @@ class DescriptionLabel extends Gtk.Label { + + var ExtensionRow = GObject.registerClass( + class ExtensionRow extends Gtk.ListBoxRow { +- _init(uuid) { ++ _init(extension) { + super._init(); + +- this.uuid = uuid; ++ this._extension = extension; ++ this._prefsModule = null; + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); + this._settings.connect('changed::enabled-extensions', () => { +@@ -525,9 +500,23 @@ class ExtensionRow extends Gtk.ListBoxRow { + this._buildUI(); + } + +- _buildUI() { +- let extension = ExtensionUtils.extensions[this.uuid]; ++ get uuid() { ++ return this._extension.uuid; ++ } ++ ++ get name() { ++ return this._extension.metadata.name; ++ } ++ ++ get hasPrefs() { ++ return this._extension.hasPrefs; ++ } + ++ get url() { ++ return this._extension.metadata.url; ++ } ++ ++ _buildUI() { + let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, + hexpand: true, margin_end: 24, spacing: 24, + margin: 12 }); +@@ -537,19 +526,20 @@ class ExtensionRow extends Gtk.ListBoxRow { + spacing: 6, hexpand: true }); + hbox.add(vbox); + +- let name = GLib.markup_escape_text(extension.metadata.name, -1); ++ let name = GLib.markup_escape_text(this.name, -1); + let label = new Gtk.Label({ label: '' + name + '', + use_markup: true, + halign: Gtk.Align.START }); + vbox.add(label); + +- let desc = extension.metadata.description.split('\n')[0]; ++ let desc = this._extension.metadata.description.split('\n')[0]; + label = new DescriptionLabel({ label: desc, wrap: true, lines: 2, + ellipsize: Pango.EllipsizeMode.END, + xalign: 0, yalign: 0 }); + vbox.add(label); + + let button = new Gtk.Button({ valign: Gtk.Align.CENTER, ++ visible: this.hasPrefs, + no_show_all: true }); + button.set_image(new Gtk.Image({ icon_name: 'emblem-system-symbolic', + icon_size: Gtk.IconSize.BUTTON, +@@ -573,11 +563,10 @@ class ExtensionRow extends Gtk.ListBoxRow { + } + + _canEnable() { +- let extension = ExtensionUtils.extensions[this.uuid]; + let checkVersion = !this._settings.get_boolean('disable-extension-version-validation'); + + return !this._settings.get_boolean('disable-user-extensions') && +- !(checkVersion && ExtensionUtils.isOutOfDate(extension)); ++ !(checkVersion && ExtensionUtils.isOutOfDate(this._extension)); + } + + _isEnabled() { +@@ -605,6 +594,17 @@ class ExtensionRow extends Gtk.ListBoxRow { + } while (pos != -1); + this._settings.set_strv('enabled-extensions', extensions); + } ++ ++ get prefsModule() { ++ if (!this._prefsModule) { ++ ExtensionUtils.installImporter(this._extension); ++ ++ this._prefsModule = this._extension.imports.prefs; ++ this._prefsModule.init(this._extension.metadata); ++ } ++ ++ return this._prefsModule; ++ } + }); + + function initEnvironment() { +-- +2.29.2 + + +From 8d80e6667ded38dac53fe245a10191b5d4a3150b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 6 Jul 2019 01:48:05 +0200 +Subject: [PATCH 10/26] extensionPrefs: Override getCurrentExtension() for + extensions + +Extensions are used to calling the getCurrentExtension() utility function, +both from the extension itself and from its preferences. For the latter, +that relies on the extensions map in ExtensionUtils being populated from +the separated extension-prefs process just like from gnome-shell. + +This won't be the case anymore when we switch to the extensions D-Bus API, +but as we know which extension we are showing the prefs dialog for, we +can patch in a simple replacement that gives extensions the expected API. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/extensionPrefs/main.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js +index 7e7b2dcc7..29de8202a 100644 +--- a/js/extensionPrefs/main.js ++++ b/js/extensionPrefs/main.js +@@ -599,6 +599,9 @@ class ExtensionRow extends Gtk.ListBoxRow { + if (!this._prefsModule) { + ExtensionUtils.installImporter(this._extension); + ++ // give extension prefs access to their own extension object ++ ExtensionUtils.getCurrentExtension = () => this._extension; ++ + this._prefsModule = this._extension.imports.prefs; + this._prefsModule.init(this._extension.metadata); + } +-- +2.29.2 + + +From e03a21b1f768405050bbfda1eb2bbf2ffcf7b4ca Mon Sep 17 00:00:00 2001 +From: Didier Roche +Date: Thu, 1 Nov 2018 13:55:17 +0100 +Subject: [PATCH 11/26] extensionPrefs: Switch to D-Bus API to get extension + live state + +By direclty using the underlying GSetting, whether or not an extension +appears as enabled or disabled currently depends only on whether it is +included in the 'enabled-extensions' list or not. + +However this doesn't necessarily reflect the real extension state, as an +extension may be in error state, or enabled via the session mode. + +Switch to the extensions D-Bus API to ensure that the list of extensions +and each extension's state correctly reflects the state in gnome-shell. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/extensionPrefs/main.js | 166 +++++++++++++++++++++++++------------- + 1 file changed, 110 insertions(+), 56 deletions(-) + +diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js +index 29de8202a..f1b732e85 100644 +--- a/js/extensionPrefs/main.js ++++ b/js/extensionPrefs/main.js +@@ -8,6 +8,8 @@ const Config = imports.misc.config; + const ExtensionUtils = imports.misc.extensionUtils; + const { loadInterfaceXML } = imports.misc.fileUtils; + ++const { ExtensionState } = ExtensionUtils; ++ + const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions'); + const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface); + +@@ -32,6 +34,11 @@ var Application = GObject.registerClass({ + this._startupUuid = null; + this._loaded = false; + this._skipMainWindow = false; ++ this._shellProxy = null; ++ } ++ ++ get shellProxy() { ++ return this._shellProxy; + } + + _showPrefs(uuid) { +@@ -218,10 +225,8 @@ var Application = GObject.registerClass({ + this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder'); + + this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell'); +- this._shellProxy.connectSignal('ExtensionStateChanged', (proxy, senderName, [uuid, state]) => { +- if (ExtensionUtils.extensions[uuid] !== undefined) +- this._scanExtensions(); +- }); ++ this._shellProxy.connectSignal('ExtensionStateChanged', ++ this._onExtensionStateChanged.bind(this)); + + this._window.show_all(); + } +@@ -238,14 +243,51 @@ var Application = GObject.registerClass({ + row.set_header(sep); + } + ++ _findExtensionRow(uuid) { ++ return this._extensionSelector.get_children().find(c => c.uuid === uuid); ++ } ++ ++ _onExtensionStateChanged(proxy, senderName, [uuid, newState]) { ++ let row = this._findExtensionRow(uuid); ++ if (row) { ++ let { state } = ExtensionUtils.deserializeExtension(newState); ++ if (state == ExtensionState.UNINSTALLED) ++ row.destroy(); ++ return; // we only deal with new and deleted extensions here ++ } ++ ++ this._shellProxy.GetExtensionInfoRemote(uuid, ([serialized]) => { ++ let extension = ExtensionUtils.deserializeExtension(serialized); ++ if (!extension) ++ return; ++ // check the extension wasn't added in between ++ if (this._findExtensionRow(uuid) != null) ++ return; ++ this._addExtensionRow(extension); ++ }); ++ } ++ + _scanExtensions() { +- let finder = new ExtensionUtils.ExtensionFinder(); +- finder.connect('extension-found', this._extensionFound.bind(this)); +- finder.scanExtensions(); +- this._extensionsLoaded(); ++ this._shellProxy.ListExtensionsRemote(([extensionsMap], e) => { ++ if (e) { ++ if (e instanceof Gio.DBusError) { ++ log(`Failed to connect to shell proxy: ${e}`); ++ this._mainStack.add_named(new NoShellPlaceholder(), 'noshell'); ++ this._mainStack.visible_child_name = 'noshell'; ++ } else ++ throw e; ++ return; ++ } ++ ++ for (let uuid in extensionsMap) { ++ let extension = ExtensionUtils.deserializeExtension(extensionsMap[uuid]); ++ this._addExtensionRow(extension); ++ } ++ this._extensionsLoaded(); ++ }); + } + +- _extensionFound(finder, extension) { ++ _addExtensionRow(extension) { + let row = new ExtensionRow(extension); + + row.prefsButton.connect('clicked', () => { +@@ -466,6 +508,35 @@ class EmptyPlaceholder extends Gtk.Box { + } + }); + ++var NoShellPlaceholder = GObject.registerClass( ++class NoShellPlaceholder extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 12, ++ margin: 100, ++ margin_bottom: 60 ++ }); ++ ++ let label = new Gtk.Label({ ++ label: '%s'.format( ++ _("Something’s gone wrong")), ++ use_markup: true ++ }); ++ label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); ++ this.add(label); ++ ++ label = new Gtk.Label({ ++ label: _("We’re very sorry, but it was not possible to get the list of installed extensions. Make sure you are logged into GNOME and try again."), ++ justify: Gtk.Justification.CENTER, ++ wrap: true ++ }); ++ this.add(label); ++ ++ this.show_all(); ++ } ++}); ++ + var DescriptionLabel = GObject.registerClass( + class DescriptionLabel extends Gtk.Label { + vfunc_get_preferred_height_for_width(width) { +@@ -481,22 +552,23 @@ class ExtensionRow extends Gtk.ListBoxRow { + _init(extension) { + super._init(); + ++ this._app = Gio.Application.get_default(); + this._extension = extension; + this._prefsModule = null; + +- this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); +- this._settings.connect('changed::enabled-extensions', () => { +- this._switch.state = this._isEnabled(); +- }); +- this._settings.connect('changed::disable-extension-version-validation', +- () => { +- this._switch.sensitive = this._canEnable(); +- }); +- this._settings.connect('changed::disable-user-extensions', +- () => { +- this._switch.sensitive = this._canEnable(); ++ this._extensionStateChangedId = this._app.shellProxy.connectSignal( ++ 'ExtensionStateChanged', (p, sender, [uuid, newState]) => { ++ if (this.uuid !== uuid) ++ return; ++ ++ this._extension = ExtensionUtils.deserializeExtension(newState); ++ let state = (this._extension.state == ExtensionState.ENABLED); ++ this._switch.state = state; ++ this._switch.sensitive = this._canToggle(); + }); + ++ this.connect('destroy', this._onDestroy.bind(this)); ++ + this._buildUI(); + } + +@@ -516,6 +588,15 @@ class ExtensionRow extends Gtk.ListBoxRow { + return this._extension.metadata.url; + } + ++ _onDestroy() { ++ if (!this._app.shellProxy) ++ return; ++ ++ if (this._extensionStateChangedId) ++ this._app.shellProxy.disconnectSignal(this._extensionStateChangedId); ++ this._extensionStateChangedId = 0; ++ } ++ + _buildUI() { + let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, + hexpand: true, margin_end: 24, spacing: 24, +@@ -549,50 +630,23 @@ class ExtensionRow extends Gtk.ListBoxRow { + + this.prefsButton = button; + +- this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER, +- sensitive: this._canEnable(), +- state: this._isEnabled() }); ++ this._switch = new Gtk.Switch({ ++ valign: Gtk.Align.CENTER, ++ sensitive: this._canToggle(), ++ state: this._extension.state === ExtensionState.ENABLED ++ }); + this._switch.connect('notify::active', () => { + if (this._switch.active) +- this._enable(); ++ this._app.shellProxy.EnableExtensionRemote(this.uuid); + else +- this._disable(); ++ this._app.shellProxy.DisableExtensionRemote(this.uuid); + }); + this._switch.connect('state-set', () => true); + hbox.add(this._switch); + } + +- _canEnable() { +- let checkVersion = !this._settings.get_boolean('disable-extension-version-validation'); +- +- return !this._settings.get_boolean('disable-user-extensions') && +- !(checkVersion && ExtensionUtils.isOutOfDate(this._extension)); +- } +- +- _isEnabled() { +- let extensions = this._settings.get_strv('enabled-extensions'); +- return extensions.indexOf(this.uuid) != -1; +- } +- +- _enable() { +- let extensions = this._settings.get_strv('enabled-extensions'); +- if (extensions.indexOf(this.uuid) != -1) +- return; +- +- extensions.push(this.uuid); +- this._settings.set_strv('enabled-extensions', extensions); +- } +- +- _disable() { +- let extensions = this._settings.get_strv('enabled-extensions'); +- let pos = extensions.indexOf(this.uuid); +- if (pos == -1) +- return; +- do { +- extensions.splice(pos, 1); +- pos = extensions.indexOf(this.uuid); +- } while (pos != -1); +- this._settings.set_strv('enabled-extensions', extensions); ++ _canToggle() { ++ return this._extension.canChange; + } + + get prefsModule() { +-- +2.29.2 + + +From 9baf77dcae765618902d958549801276156f1255 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 7 Jul 2019 23:38:27 +0200 +Subject: [PATCH 12/26] extensionSystem: Move extension loading into + ExtensionManager + +Now that extension loading and the extensions map are no longer shared +between the gnome-shell and gnome-shell-extension-prefs processes, we +can move both into the ExtensionManager which makes much more sense +conceptually. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/misc/extensionUtils.js | 95 ++---------------------------- + js/ui/extensionDownloader.js | 12 ++-- + js/ui/extensionSystem.js | 110 +++++++++++++++++++++++++++++------ + js/ui/lookingGlass.js | 4 +- + js/ui/main.js | 1 + + js/ui/shellDBus.js | 8 +-- + 6 files changed, 111 insertions(+), 119 deletions(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index 025cd042e..c513ebc06 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -7,10 +7,8 @@ const { Gio, GLib } = imports.gi; + + const Gettext = imports.gettext; + const Lang = imports.lang; +-const Signals = imports.signals; + + const Config = imports.misc.config; +-const FileUtils = imports.misc.fileUtils; + + var ExtensionType = { + SYSTEM: 1, +@@ -33,9 +31,6 @@ var ExtensionState = { + + const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange']; + +-// Maps uuid -> metadata object +-var extensions = {}; +- + /** + * getCurrentExtension: + * +@@ -66,13 +61,17 @@ function getCurrentExtension() { + if (!match) + return null; + ++ // local import, as the module is used from outside the gnome-shell process ++ // as well (not this function though) ++ let extensionManager = imports.ui.main.extensionManager; ++ + let path = match[1]; + let file = Gio.File.new_for_path(path); + + // Walk up the directory tree, looking for an extension with + // the same UUID as a directory name. + while (file != null) { +- let extension = extensions[file.get_basename()]; ++ let extension = extensionManager.extensions[file.get_basename()]; + if (extension !== undefined) + return extension; + file = file.get_parent(); +@@ -178,57 +177,6 @@ function isOutOfDate(extension) { + return false; + } + +-function createExtensionObject(uuid, dir, type) { +- let info; +- +- let metadataFile = dir.get_child('metadata.json'); +- if (!metadataFile.query_exists(null)) { +- throw new Error('Missing metadata.json'); +- } +- +- let metadataContents, success, tag; +- try { +- [success, metadataContents, tag] = metadataFile.load_contents(null); +- if (metadataContents instanceof Uint8Array) +- metadataContents = imports.byteArray.toString(metadataContents); +- } catch (e) { +- throw new Error('Failed to load metadata.json: ' + e); +- } +- let meta; +- try { +- meta = JSON.parse(metadataContents); +- } catch (e) { +- throw new Error('Failed to parse metadata.json: ' + e); +- } +- +- let requiredProperties = ['uuid', 'name', 'description', 'shell-version']; +- for (let i = 0; i < requiredProperties.length; i++) { +- let prop = requiredProperties[i]; +- if (!meta[prop]) { +- throw new Error('missing "' + prop + '" property in metadata.json'); +- } +- } +- +- if (uuid != meta.uuid) { +- throw new Error('uuid "' + meta.uuid + '" from metadata.json does not match directory name "' + uuid + '"'); +- } +- +- let extension = {}; +- +- extension.metadata = meta; +- extension.uuid = meta.uuid; +- extension.type = type; +- extension.dir = dir; +- extension.path = dir.get_path(); +- extension.error = ''; +- extension.hasPrefs = dir.get_child('prefs.js').query_exists(null); +- extension.canChange = false; +- +- extensions[uuid] = extension; +- +- return extension; +-} +- + function serializeExtension(extension) { + let obj = {}; + Lang.copyProperties(extension.metadata, obj); +@@ -283,36 +231,3 @@ function installImporter(extension) { + extension.imports = imports[extension.uuid]; + imports.searchPath = oldSearchPath; + } +- +-var ExtensionFinder = class { +- _loadExtension(extensionDir, info, perUserDir) { +- let fileType = info.get_file_type(); +- if (fileType != Gio.FileType.DIRECTORY) +- return; +- let uuid = info.get_name(); +- let existing = extensions[uuid]; +- if (existing) { +- log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path())); +- return; +- } +- +- let extension; +- let type = extensionDir.has_prefix(perUserDir) ? ExtensionType.PER_USER +- : ExtensionType.SYSTEM; +- try { +- extension = createExtensionObject(uuid, extensionDir, type); +- } catch(e) { +- logError(e, 'Could not load extension %s'.format(uuid)); +- return; +- } +- this.emit('extension-found', extension); +- } +- +- scanExtensions() { +- let perUserDir = Gio.File.new_for_path(global.userdatadir); +- FileUtils.collectFromDatadirs('extensions', true, (dir, info) => { +- this._loadExtension(dir, info, perUserDir); +- }); +- } +-}; +-Signals.addSignalMethods(ExtensionFinder.prototype); +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index de52edfa6..1d92a5740 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -43,7 +43,7 @@ function installExtension(uuid, invocation) { + } + + function uninstallExtension(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = Main.extensionManager.extensions[uuid]; + if (!extension) + return false; + +@@ -112,7 +112,7 @@ function updateExtension(uuid) { + + _httpSession.queue_message(message, (session, message) => { + gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => { +- let oldExtension = ExtensionUtils.extensions[uuid]; ++ let oldExtension = Main.extensionManager.extensions[uuid]; + let extensionDir = oldExtension.dir; + + if (!Main.extensionManager.unloadExtension(oldExtension)) +@@ -124,7 +124,7 @@ function updateExtension(uuid) { + let extension = null; + + try { +- extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); ++ extension = Main.extensionManager.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); + Main.extensionManager.loadExtension(extension); + } catch(e) { + if (extension) +@@ -150,8 +150,8 @@ function updateExtension(uuid) { + + function checkForUpdates() { + let metadatas = {}; +- for (let uuid in ExtensionUtils.extensions) { +- metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata; ++ for (let uuid in Main.extensionManager.extensions) { ++ metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata; + } + + let params = { shell_version: Config.PACKAGE_VERSION, +@@ -229,7 +229,7 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog { + + function callback() { + try { +- let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); ++ let extension = Main.extensionManager.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); + Main.extensionManager.loadExtension(extension); + if (!Main.extensionManager.enableExtension(uuid)) + throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`); +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index a83e53c83..0fd49c5ca 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -4,9 +4,10 @@ const { Gio, St } = imports.gi; + const Signals = imports.signals; + + const ExtensionUtils = imports.misc.extensionUtils; ++const FileUtils = imports.misc.fileUtils; + const Main = imports.ui.main; + +-const { ExtensionState } = ExtensionUtils; ++const { ExtensionState, ExtensionType } = ExtensionUtils; + + const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; + const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions'; +@@ -17,15 +18,23 @@ var ExtensionManager = class { + this._initted = false; + this._enabled = false; + ++ this._extensions = {}; + this._enabledExtensions = []; + this._extensionOrder = []; + + Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); ++ } ++ ++ init() { + this._sessionUpdated(); + } + ++ get extensions() { ++ return this._extensions; ++ } ++ + _callExtensionDisable(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = this._extensions[uuid]; + if (!extension) + return; + +@@ -47,7 +56,7 @@ var ExtensionManager = class { + for (let i = 0; i < orderReversed.length; i++) { + let uuid = orderReversed[i]; + try { +- ExtensionUtils.extensions[uuid].stateObj.disable(); ++ this._extensions[uuid].stateObj.disable(); + } catch (e) { + this.logExtensionError(uuid, e); + } +@@ -68,7 +77,7 @@ var ExtensionManager = class { + for (let i = 0; i < order.length; i++) { + let uuid = order[i]; + try { +- ExtensionUtils.extensions[uuid].stateObj.enable(); ++ this._extensions[uuid].stateObj.enable(); + } catch (e) { + this.logExtensionError(uuid, e); + } +@@ -83,7 +92,7 @@ var ExtensionManager = class { + } + + _callExtensionEnable(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = this._extensions[uuid]; + if (!extension) + return; + +@@ -127,7 +136,7 @@ var ExtensionManager = class { + } + + enableExtension(uuid) { +- if (!ExtensionUtils.extensions[uuid]) ++ if (!this._extensions[uuid]) + return false; + + let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); +@@ -140,7 +149,7 @@ var ExtensionManager = class { + } + + disableExtension(uuid) { +- if (!ExtensionUtils.extensions[uuid]) ++ if (!this._extensions[uuid]) + return false; + + let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); +@@ -153,7 +162,7 @@ var ExtensionManager = class { + } + + logExtensionError(uuid, error) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = this._extensions[uuid]; + if (!extension) + return; + +@@ -169,6 +178,54 @@ var ExtensionManager = class { + this.emit('extension-state-changed', extension); + } + ++ createExtensionObject(uuid, dir, type) { ++ let metadataFile = dir.get_child('metadata.json'); ++ if (!metadataFile.query_exists(null)) { ++ throw new Error('Missing metadata.json'); ++ } ++ ++ let metadataContents, success; ++ try { ++ [success, metadataContents] = metadataFile.load_contents(null); ++ if (metadataContents instanceof Uint8Array) ++ metadataContents = imports.byteArray.toString(metadataContents); ++ } catch (e) { ++ throw new Error(`Failed to load metadata.json: ${e}`); ++ } ++ let meta; ++ try { ++ meta = JSON.parse(metadataContents); ++ } catch (e) { ++ throw new Error(`Failed to parse metadata.json: ${e}`); ++ } ++ ++ let requiredProperties = ['uuid', 'name', 'description', 'shell-version']; ++ for (let i = 0; i < requiredProperties.length; i++) { ++ let prop = requiredProperties[i]; ++ if (!meta[prop]) { ++ throw new Error(`missing "${prop}" property in metadata.json`); ++ } ++ } ++ ++ if (uuid != meta.uuid) { ++ throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`); ++ } ++ ++ let extension = { ++ metadata: meta, ++ uuid: meta.uuid, ++ type, ++ dir, ++ path: dir.get_path(), ++ error: '', ++ hasPrefs: dir.get_child('prefs.js').query_exists(null), ++ canChange: false ++ }; ++ this._extensions[uuid] = extension; ++ ++ return extension; ++ } ++ + loadExtension(extension) { + // Default to error, we set success as the last step + extension.state = ExtensionState.ERROR; +@@ -202,7 +259,7 @@ var ExtensionManager = class { + extension.state = ExtensionState.UNINSTALLED; + this.emit('extension-state-changed', extension); + +- delete ExtensionUtils.extensions[extension.uuid]; ++ delete this._extensions[extension.uuid]; + return true; + } + +@@ -217,7 +274,7 @@ var ExtensionManager = class { + // Now, recreate the extension and load it. + let newExtension; + try { +- newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type); ++ newExtension = this.createExtensionObject(uuid, dir, type); + } catch (e) { + this.logExtensionError(uuid, e); + return; +@@ -227,7 +284,7 @@ var ExtensionManager = class { + } + + _callExtensionInit(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = this._extensions[uuid]; + let dir = extension.dir; + + if (!extension) +@@ -328,7 +385,7 @@ var ExtensionManager = class { + } + + _onSettingsWritableChanged() { +- for (let uuid in ExtensionUtils.extensions) { ++ for (let uuid in this._extensions) { + let extension = ExtensionUtils.extensions[uuid]; + this._updateCanChange(extension); + this.emit('extension-state-changed', extension); +@@ -340,8 +397,8 @@ var ExtensionManager = class { + // extensions when allowed by the sessionMode, so + // temporarily disable them all + this._enabledExtensions = []; +- for (let uuid in ExtensionUtils.extensions) +- this.reloadExtension(ExtensionUtils.extensions[uuid]); ++ for (let uuid in this._extensions) ++ this.reloadExtension(this._extensions[uuid]); + this._enabledExtensions = this._getEnabledExtensions(); + + if (Main.sessionMode.allowExtensions) { +@@ -363,11 +420,30 @@ var ExtensionManager = class { + + this._enabledExtensions = this._getEnabledExtensions(); + +- let finder = new ExtensionUtils.ExtensionFinder(); +- finder.connect('extension-found', (finder, extension) => { ++ let perUserDir = Gio.File.new_for_path(global.userdatadir); ++ FileUtils.collectFromDatadirs('extensions', true, (dir, info) => { ++ let fileType = info.get_file_type(); ++ if (fileType != Gio.FileType.DIRECTORY) ++ return; ++ let uuid = info.get_name(); ++ let existing = this._extensions[uuid]; ++ if (existing) { ++ log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`); ++ return; ++ } ++ ++ let extension; ++ let type = dir.has_prefix(perUserDir) ++ ? ExtensionType.PER_USER ++ : ExtensionType.SYSTEM; ++ try { ++ extension = this.createExtensionObject(uuid, dir, type); ++ } catch (e) { ++ logError(e, `Could not load extension ${uuid}`); ++ return; ++ } + this.loadExtension(extension); + }); +- finder.scanExtensions(); + } + + _enableAllExtensions() { +diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js +index e947574f2..b8f8b14c9 100644 +--- a/js/ui/lookingGlass.js ++++ b/js/ui/lookingGlass.js +@@ -620,7 +620,7 @@ var Extensions = class Extensions { + this._extensionsList.add(this._noExtensions); + this.actor.add(this._extensionsList); + +- for (let uuid in ExtensionUtils.extensions) ++ for (let uuid in Main.extensionManager.extensions) + this._loadExtension(null, uuid); + + Main.extensionManager.connect('extension-loaded', +@@ -628,7 +628,7 @@ var Extensions = class Extensions { + } + + _loadExtension(o, uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = Main.extensionManager.extensions[uuid]; + // There can be cases where we create dummy extension metadata + // that's not really a proper extension. Don't bother with these. + if (!extension.metadata.name) +diff --git a/js/ui/main.js b/js/ui/main.js +index 7bfbce497..5fa5a8077 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -220,6 +220,7 @@ function _initializeUI() { + + ExtensionDownloader.init(); + extensionManager = new ExtensionSystem.ExtensionManager(); ++ extensionManager.init(); + + if (sessionMode.isGreeter && screenShield) { + layoutManager.connect('startup-prepared', () => { +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index 23274c0a3..dc3a61df6 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -254,7 +254,7 @@ var GnomeShellExtensions = class { + + ListExtensions() { + let out = {}; +- for (let uuid in ExtensionUtils.extensions) { ++ for (let uuid in Main.extensionManager.extensions) { + let dbusObj = this.GetExtensionInfo(uuid); + out[uuid] = dbusObj; + } +@@ -262,12 +262,12 @@ var GnomeShellExtensions = class { + } + + GetExtensionInfo(uuid) { +- let extension = ExtensionUtils.extensions[uuid] || {}; ++ let extension = Main.extensionManager.extensions[uuid] || {}; + return ExtensionUtils.serializeExtension(extension); + } + + GetExtensionErrors(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = Main.extensionManager.extensions[uuid]; + if (!extension) + return []; + +@@ -303,7 +303,7 @@ var GnomeShellExtensions = class { + } + + ReloadExtension(uuid) { +- let extension = ExtensionUtils.extensions[uuid]; ++ let extension = Main.extensionManager.extensions[uuid]; + if (!extension) + return; + +-- +2.29.2 + + +From 776b82542aa705ea527dfbdd1a6d3fb1588092e2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 8 Jul 2019 00:01:11 +0200 +Subject: [PATCH 13/26] extensionSystem: Store extensions in a Map + +After making the extensions map private to the ExtensionManager, we can +switch it to a proper hash table which is more appropriate. + +https://bugzilla.gnome.org/show_bug.cgi?id=789852 +--- + js/misc/extensionUtils.js | 2 +- + js/ui/extensionDownloader.js | 8 +++---- + js/ui/extensionSystem.js | 42 ++++++++++++++++++++---------------- + js/ui/lookingGlass.js | 5 +++-- + js/ui/shellDBus.js | 10 ++++----- + 5 files changed, 37 insertions(+), 30 deletions(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index c513ebc06..62b25d46c 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -71,7 +71,7 @@ function getCurrentExtension() { + // Walk up the directory tree, looking for an extension with + // the same UUID as a directory name. + while (file != null) { +- let extension = extensionManager.extensions[file.get_basename()]; ++ let extension = extensionManager.lookup(file.get_basename()); + if (extension !== undefined) + return extension; + file = file.get_parent(); +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index 1d92a5740..77d013ffb 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -43,7 +43,7 @@ function installExtension(uuid, invocation) { + } + + function uninstallExtension(uuid) { +- let extension = Main.extensionManager.extensions[uuid]; ++ let extension = Main.extensionManager.lookup(uuid); + if (!extension) + return false; + +@@ -112,7 +112,7 @@ function updateExtension(uuid) { + + _httpSession.queue_message(message, (session, message) => { + gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => { +- let oldExtension = Main.extensionManager.extensions[uuid]; ++ let oldExtension = Main.extensionManager.lookup(uuid); + let extensionDir = oldExtension.dir; + + if (!Main.extensionManager.unloadExtension(oldExtension)) +@@ -150,9 +150,9 @@ function updateExtension(uuid) { + + function checkForUpdates() { + let metadatas = {}; +- for (let uuid in Main.extensionManager.extensions) { ++ Main.extensionManager.getUuids().forEach(uuid => { + metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata; +- } ++ }); + + let params = { shell_version: Config.PACKAGE_VERSION, + installed: JSON.stringify(metadatas) }; +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 0fd49c5ca..cd3e78301 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -18,7 +18,7 @@ var ExtensionManager = class { + this._initted = false; + this._enabled = false; + +- this._extensions = {}; ++ this._extensions = new Map(); + this._enabledExtensions = []; + this._extensionOrder = []; + +@@ -29,12 +29,16 @@ var ExtensionManager = class { + this._sessionUpdated(); + } + +- get extensions() { +- return this._extensions; ++ lookup(uuid) { ++ return this._extensions.get(uuid); ++ } ++ ++ getUuids() { ++ return [...this._extensions.keys()]; + } + + _callExtensionDisable(uuid) { +- let extension = this._extensions[uuid]; ++ let extension = this.lookup(uuid); + if (!extension) + return; + +@@ -56,7 +60,7 @@ var ExtensionManager = class { + for (let i = 0; i < orderReversed.length; i++) { + let uuid = orderReversed[i]; + try { +- this._extensions[uuid].stateObj.disable(); ++ this.lookup(uuid).stateObj.disable(); + } catch (e) { + this.logExtensionError(uuid, e); + } +@@ -77,7 +81,7 @@ var ExtensionManager = class { + for (let i = 0; i < order.length; i++) { + let uuid = order[i]; + try { +- this._extensions[uuid].stateObj.enable(); ++ this.lookup(uuid).stateObj.enable(); + } catch (e) { + this.logExtensionError(uuid, e); + } +@@ -92,7 +96,7 @@ var ExtensionManager = class { + } + + _callExtensionEnable(uuid) { +- let extension = this._extensions[uuid]; ++ let extension = this.lookup(uuid); + if (!extension) + return; + +@@ -136,7 +140,7 @@ var ExtensionManager = class { + } + + enableExtension(uuid) { +- if (!this._extensions[uuid]) ++ if (!this._extensions.has(uuid)) + return false; + + let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); +@@ -149,7 +153,7 @@ var ExtensionManager = class { + } + + disableExtension(uuid) { +- if (!this._extensions[uuid]) ++ if (!this._extensions.has(uuid)) + return false; + + let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); +@@ -162,7 +166,7 @@ var ExtensionManager = class { + } + + logExtensionError(uuid, error) { +- let extension = this._extensions[uuid]; ++ let extension = this.lookup(uuid); + if (!extension) + return; + +@@ -221,7 +225,7 @@ var ExtensionManager = class { + hasPrefs: dir.get_child('prefs.js').query_exists(null), + canChange: false + }; +- this._extensions[uuid] = extension; ++ this._extensions.set(uuid, extension); + + return extension; + } +@@ -259,7 +263,7 @@ var ExtensionManager = class { + extension.state = ExtensionState.UNINSTALLED; + this.emit('extension-state-changed', extension); + +- delete this._extensions[extension.uuid]; ++ this._extensions.delete(extension.uuid); + return true; + } + +@@ -284,7 +288,7 @@ var ExtensionManager = class { + } + + _callExtensionInit(uuid) { +- let extension = this._extensions[uuid]; ++ let extension = this.lookup(uuid); + let dir = extension.dir; + + if (!extension) +@@ -385,8 +389,7 @@ var ExtensionManager = class { + } + + _onSettingsWritableChanged() { +- for (let uuid in this._extensions) { +- let extension = ExtensionUtils.extensions[uuid]; ++ for (let extension of this._extensions.values()) { + this._updateCanChange(extension); + this.emit('extension-state-changed', extension); + } +@@ -397,8 +400,11 @@ var ExtensionManager = class { + // extensions when allowed by the sessionMode, so + // temporarily disable them all + this._enabledExtensions = []; +- for (let uuid in this._extensions) +- this.reloadExtension(this._extensions[uuid]); ++ ++ // The loop modifies the extensions map, so iterate over a copy ++ let extensions = [...this._extensions.values()]; ++ for (let extension of extensions) ++ this.reloadExtension(extension); + this._enabledExtensions = this._getEnabledExtensions(); + + if (Main.sessionMode.allowExtensions) { +@@ -426,7 +432,7 @@ var ExtensionManager = class { + if (fileType != Gio.FileType.DIRECTORY) + return; + let uuid = info.get_name(); +- let existing = this._extensions[uuid]; ++ let existing = this.lookup(uuid); + if (existing) { + log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`); + return; +diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js +index b8f8b14c9..9196959bd 100644 +--- a/js/ui/lookingGlass.js ++++ b/js/ui/lookingGlass.js +@@ -620,15 +620,16 @@ var Extensions = class Extensions { + this._extensionsList.add(this._noExtensions); + this.actor.add(this._extensionsList); + +- for (let uuid in Main.extensionManager.extensions) ++ Main.extensionManager.getUuids().forEach(uuid => { + this._loadExtension(null, uuid); ++ }); + + Main.extensionManager.connect('extension-loaded', + this._loadExtension.bind(this)); + } + + _loadExtension(o, uuid) { +- let extension = Main.extensionManager.extensions[uuid]; ++ let extension = Main.extensionManager.lookup(uuid); + // There can be cases where we create dummy extension metadata + // that's not really a proper extension. Don't bother with these. + if (!extension.metadata.name) +diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js +index dc3a61df6..be9b10491 100644 +--- a/js/ui/shellDBus.js ++++ b/js/ui/shellDBus.js +@@ -254,20 +254,20 @@ var GnomeShellExtensions = class { + + ListExtensions() { + let out = {}; +- for (let uuid in Main.extensionManager.extensions) { ++ Main.extensionManager.getUuids().forEach(uuid => { + let dbusObj = this.GetExtensionInfo(uuid); + out[uuid] = dbusObj; +- } ++ }); + return out; + } + + GetExtensionInfo(uuid) { +- let extension = Main.extensionManager.extensions[uuid] || {}; ++ let extension = Main.extensionManager.lookup(uuid) || {}; + return ExtensionUtils.serializeExtension(extension); + } + + GetExtensionErrors(uuid) { +- let extension = Main.extensionManager.extensions[uuid]; ++ let extension = Main.extensionManager.lookup(uuid); + if (!extension) + return []; + +@@ -303,7 +303,7 @@ var GnomeShellExtensions = class { + } + + ReloadExtension(uuid) { +- let extension = Main.extensionManager.extensions[uuid]; ++ let extension = Main.extensionManager.lookup(uuid); + if (!extension) + return; + +-- +2.29.2 + + +From 2cc95ba7c93c04bae0006b7d018928600d9cbb13 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 22 Jan 2020 14:45:15 +0100 +Subject: [PATCH 14/26] extensionSystem: Add hasUpdate state + +The current support for extension updates is half-baked at best. +We are about to change that, and implement offline updates similar +to gnome-software. + +As a first step, add a hasUpdate property to the extension state +which will communicate available updates. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/misc/extensionUtils.js | 10 +++++++++- + js/ui/extensionSystem.js | 10 ++++++++++ + 2 files changed, 19 insertions(+), 1 deletion(-) + +diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js +index 62b25d46c..a812acdb1 100644 +--- a/js/misc/extensionUtils.js ++++ b/js/misc/extensionUtils.js +@@ -29,7 +29,15 @@ var ExtensionState = { + UNINSTALLED: 99 + }; + +-const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange']; ++const SERIALIZED_PROPERTIES = [ ++ 'type', ++ 'state', ++ 'path', ++ 'error', ++ 'hasPrefs', ++ 'hasUpdate', ++ 'canChange', ++]; + + /** + * getCurrentExtension: +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index cd3e78301..93faf48d4 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -165,6 +165,15 @@ var ExtensionManager = class { + return true; + } + ++ notifyExtensionUpdate(uuid) { ++ let extension = this.lookup(uuid); ++ if (!extension) ++ return; ++ ++ extension.hasUpdate = true; ++ this.emit('extension-state-changed', extension); ++ } ++ + logExtensionError(uuid, error) { + let extension = this.lookup(uuid); + if (!extension) +@@ -223,6 +232,7 @@ var ExtensionManager = class { + path: dir.get_path(), + error: '', + hasPrefs: dir.get_child('prefs.js').query_exists(null), ++ hasUpdate: false, + canChange: false + }; + this._extensions.set(uuid, extension); +-- +2.29.2 + + +From 07330eaac64fc115851ec9d5a0969bd046599e12 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 22 Jan 2020 15:07:45 +0100 +Subject: [PATCH 15/26] extensionDownloader: Make checkForUpdates() check for + updates + +Currently the method installs updates instead of merely checking for +them (or it would do, if it actually worked). + +This is not just surprising considering the method name, the whole idea +of live updates is problematic and will not work properly more often +than not: + - imports are cached, so any local modules will stay at their + original version until a shell restart + - GTypes cannot be unregistered + +So change the method to only download available updates, and set the +extensions' hasUpdate state accordingly. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/ui/extensionDownloader.js | 51 +++++++----------------------------- + 1 file changed, 9 insertions(+), 42 deletions(-) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index 77d013ffb..66cb13d56 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -97,53 +97,20 @@ function gotExtensionZipFile(session, message, uuid, dir, callback, errback) { + }); + } + +-function updateExtension(uuid) { +- // This gets a bit tricky. We want the update to be seamless - +- // if we have any error during downloading or extracting, we +- // want to not unload the current version. +- +- let oldExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension'); +- let newExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension'); ++function downloadExtensionUpdate(uuid) { ++ let dir = Gio.File.new_for_path( ++ GLib.build_filenamev([global.userdatadir, 'extension-updates', uuid])); + + let params = { shell_version: Config.PACKAGE_VERSION }; + + let url = REPOSITORY_URL_DOWNLOAD.format(uuid); + let message = Soup.form_request_new_from_hash('GET', url, params); + +- _httpSession.queue_message(message, (session, message) => { +- gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => { +- let oldExtension = Main.extensionManager.lookup(uuid); +- let extensionDir = oldExtension.dir; +- +- if (!Main.extensionManager.unloadExtension(oldExtension)) +- return; +- +- FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir); +- FileUtils.recursivelyMoveDir(newExtensionTmpDir, extensionDir); +- +- let extension = null; +- +- try { +- extension = Main.extensionManager.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); +- Main.extensionManager.loadExtension(extension); +- } catch(e) { +- if (extension) +- Main.extensionManager.unloadExtension(extension); +- +- logError(e, 'Error loading extension %s'.format(uuid)); +- +- FileUtils.recursivelyDeleteDir(extensionDir, false); +- FileUtils.recursivelyMoveDir(oldExtensionTmpDir, extensionDir); +- +- // Restore what was there before. We can't do much if we +- // fail here. +- Main.extensionManager.loadExtension(oldExtension); +- return; +- } +- +- FileUtils.recursivelyDeleteDir(oldExtensionTmpDir, true); +- }, (code, message) => { +- log('Error while updating extension %s: %s (%s)'.format(uuid, code, message ? message : '')); ++ _httpSession.queue_message(message, session => { ++ gotExtensionZipFile(session, message, uuid, dir, () => { ++ Main.extensionManager.notifyExtensionUpdate(uuid); ++ }, (code, msg) => { ++ log(`Error while downloading update for extension ${uuid}: ${code} (${msg})`); + }); + }); + } +@@ -169,7 +136,7 @@ function checkForUpdates() { + if (operation == 'blacklist') + uninstallExtension(uuid); + else if (operation == 'upgrade' || operation == 'downgrade') +- updateExtension(uuid); ++ downloadExtensionUpdate(uuid); + } + }); + } +-- +2.29.2 + + +From 49eaf28202787f0802663aa609ee9f87eb548b03 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 22 Jan 2020 15:42:06 +0100 +Subject: [PATCH 16/26] extensionDownloader: Only check updates for user + extensions + +System extensions cannot be updated through the website, so don't +even try. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/ui/extensionDownloader.js | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index 66cb13d56..c8f6735c5 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -118,7 +118,10 @@ function downloadExtensionUpdate(uuid) { + function checkForUpdates() { + let metadatas = {}; + Main.extensionManager.getUuids().forEach(uuid => { +- metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata; ++ let extension = Main.extensionManager.lookup(uuid); ++ if (extension.type !== ExtensionUtils.ExtensionType.PER_USER) ++ return; ++ metadatas[uuid] = extension.metadata; + }); + + let params = { shell_version: Config.PACKAGE_VERSION, +-- +2.29.2 + + +From 67d709de14b083a013b3b1160e5cc451cf96bfde Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 27 Jan 2020 01:13:49 +0100 +Subject: [PATCH 17/26] extensionDownloader: Exclude extensions with pending + updates from check + +While it is possible that an extension has a newer version available +than the previously downloaded update, it's more likely that we end up +downloading the same archive again. That would be a bit silly despite +the usually small size, so we can either use the metadata from the +update, or exclude the extension from the check. + +The latter is much easier, so let's go with that for now. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/ui/extensionDownloader.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index c8f6735c5..ede276c37 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -121,6 +121,8 @@ function checkForUpdates() { + let extension = Main.extensionManager.lookup(uuid); + if (extension.type !== ExtensionUtils.ExtensionType.PER_USER) + return; ++ if (extension.hasUpdate) ++ return; + metadatas[uuid] = extension.metadata; + }); + +-- +2.29.2 + + +From 2fa19162c71787fbb9aa9af1d35e0e9cab11c1d1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 22 Jan 2020 16:53:32 +0100 +Subject: [PATCH 18/26] extensionDownloader: Include version validation in + update check + +The extensions website will consider the setting to find the best suitable +extension version, so we should transmit the parameter for better results. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/ui/extensionDownloader.js | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index ede276c37..f957c6c62 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -126,8 +126,13 @@ function checkForUpdates() { + metadatas[uuid] = extension.metadata; + }); + +- let params = { shell_version: Config.PACKAGE_VERSION, +- installed: JSON.stringify(metadatas) }; ++ let versionCheck = global.settings.get_boolean( ++ 'disable-extension-version-validation'); ++ let params = { ++ shell_version: Config.PACKAGE_VERSION, ++ installed: JSON.stringify(metadatas), ++ disable_version_validation: `${versionCheck}`, ++ }; + + let url = REPOSITORY_URL_UPDATE; + let message = Soup.form_request_new_from_hash('GET', url, params); +-- +2.29.2 + + +From ccb0095b1981233ca980d44c260c0d36eef910bd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 22 Jan 2020 15:09:05 +0100 +Subject: [PATCH 19/26] extensionSystem: Install pending updates on startup + +Now that we have a way to check for updates and download them, we +should actually apply them as well. Do this on startup before any +extensions are initialized, to make sure we don't run into any +conflicts with a previously loaded version. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/945 +--- + js/ui/extensionSystem.js | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 93faf48d4..36a248dc1 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -1,6 +1,6 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { Gio, St } = imports.gi; ++const { GLib, Gio, St } = imports.gi; + const Signals = imports.signals; + + const ExtensionUtils = imports.misc.extensionUtils; +@@ -26,6 +26,7 @@ var ExtensionManager = class { + } + + init() { ++ this._installExtensionUpdates(); + this._sessionUpdated(); + } + +@@ -424,6 +425,21 @@ var ExtensionManager = class { + } + } + ++ _installExtensionUpdates() { ++ FileUtils.collectFromDatadirs('extension-updates', true, (dir, info) => { ++ let fileType = info.get_file_type(); ++ if (fileType !== Gio.FileType.DIRECTORY) ++ return; ++ let uuid = info.get_name(); ++ let extensionDir = Gio.File.new_for_path( ++ GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); ++ ++ FileUtils.recursivelyDeleteDir(extensionDir, false); ++ FileUtils.recursivelyMoveDir(dir, extensionDir); ++ FileUtils.recursivelyDeleteDir(dir, true); ++ }); ++ } ++ + _loadExtensions() { + global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, + this._onEnabledExtensionsChanged.bind(this)); +-- +2.29.2 + + +From e377b16ffb667be40a850ff03e092f2f9dfe8fe8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 24 Jan 2020 18:09:34 +0100 +Subject: [PATCH 20/26] extensionPrefs: Add application icon + +We are about to make the tool a user-visible application, so we +need an icon. Add one (plus its symbolic variant). + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1968 +--- + data/gnome-shell-extension-prefs.desktop.in.in | 1 + + data/icons/hicolor/scalable/apps/org.gnome.Extensions.svg | 7 +++++++ + .../symbolic/apps/org.gnome.Extensions-symbolic.svg | 1 + + data/icons/meson.build | 1 + + data/meson.build | 1 + + meson.build | 1 + + 6 files changed, 12 insertions(+) + create mode 100644 data/icons/hicolor/scalable/apps/org.gnome.Extensions.svg + create mode 100644 data/icons/hicolor/symbolic/apps/org.gnome.Extensions-symbolic.svg + create mode 100644 data/icons/meson.build + +diff --git a/data/gnome-shell-extension-prefs.desktop.in.in b/data/gnome-shell-extension-prefs.desktop.in.in +index 1b144c5bd..1b58c424e 100644 +--- a/data/gnome-shell-extension-prefs.desktop.in.in ++++ b/data/gnome-shell-extension-prefs.desktop.in.in +@@ -1,6 +1,7 @@ + [Desktop Entry] + Type=Application + Name=Shell Extensions ++Icon=org.gnome.Extensions + Comment=Configure GNOME Shell Extensions + Exec=@bindir@/gnome-shell-extension-prefs %u + X-GNOME-Bugzilla-Bugzilla=GNOME +diff --git a/data/icons/hicolor/scalable/apps/org.gnome.Extensions.svg b/data/icons/hicolor/scalable/apps/org.gnome.Extensions.svg +new file mode 100644 +index 000000000..49d63888b +--- /dev/null ++++ b/data/icons/hicolor/scalable/apps/org.gnome.Extensions.svg +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ ++ +diff --git a/data/icons/hicolor/symbolic/apps/org.gnome.Extensions-symbolic.svg b/data/icons/hicolor/symbolic/apps/org.gnome.Extensions-symbolic.svg +new file mode 100644 +index 000000000..43786ff4a +--- /dev/null ++++ b/data/icons/hicolor/symbolic/apps/org.gnome.Extensions-symbolic.svg +@@ -0,0 +1 @@ ++ +\ No newline at end of file +diff --git a/data/icons/meson.build b/data/icons/meson.build +new file mode 100644 +index 000000000..eff6e4b53 +--- /dev/null ++++ b/data/icons/meson.build +@@ -0,0 +1 @@ ++install_subdir('hicolor', install_dir: icondir) +diff --git a/data/meson.build b/data/meson.build +index 31ac4514e..33edb58c4 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -42,6 +42,7 @@ endforeach + + + subdir('dbus-interfaces') ++subdir('icons') + subdir('theme') + + data_resources = [ +diff --git a/meson.build b/meson.build +index 0acaba705..2dd1bbc7a 100644 +--- a/meson.build ++++ b/meson.build +@@ -52,6 +52,7 @@ pkglibdir = join_paths(libdir, meson.project_name()) + autostartdir = join_paths(sysconfdir, 'xdg', 'autostart') + convertdir = join_paths(datadir, 'GConf', 'gsettings') + desktopdir = join_paths(datadir, 'applications') ++icondir = join_paths(datadir, 'icons') + ifacedir = join_paths(datadir, 'dbus-1', 'interfaces') + localedir = join_paths(datadir, 'locale') + portaldir = join_paths(datadir, 'xdg-desktop-portal', 'portals') +-- +2.29.2 + + +From 6b3fa1549f9682f54f55cdd963a242cd279ff17c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 26 Jan 2020 23:47:24 +0100 +Subject: [PATCH 21/26] extensionSystem: Show notification when updates are + available + +Now that the extensions app has the ability to handle updates, we +can use it as source of updates notifications. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1968 +--- + js/ui/extensionSystem.js | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 36a248dc1..805e08cae 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -1,11 +1,12 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +-const { GLib, Gio, St } = imports.gi; ++const { GLib, Gio, GObject, St } = imports.gi; + const Signals = imports.signals; + + const ExtensionUtils = imports.misc.extensionUtils; + const FileUtils = imports.misc.fileUtils; + const Main = imports.ui.main; ++const MessageTray = imports.ui.messageTray; + + const { ExtensionState, ExtensionType } = ExtensionUtils; + +@@ -17,6 +18,7 @@ var ExtensionManager = class { + constructor() { + this._initted = false; + this._enabled = false; ++ this._updateNotified = false; + + this._extensions = new Map(); + this._enabledExtensions = []; +@@ -173,6 +175,18 @@ var ExtensionManager = class { + + extension.hasUpdate = true; + this.emit('extension-state-changed', extension); ++ ++ if (!this._updateNotified) { ++ this._updateNotified = true; ++ ++ let source = new ExtensionUpdateSource(); ++ Main.messageTray.add(source); ++ ++ let notification = new MessageTray.Notification(source, ++ _('Extension Updates Available'), ++ _('Extension updates are ready to be installed.')); ++ source.notify(notification); ++ } + } + + logExtensionError(uuid, error) { +@@ -521,3 +535,26 @@ var ExtensionManager = class { + } + }; + Signals.addSignalMethods(ExtensionManager.prototype); ++ ++class ExtensionUpdateSource extends MessageTray.Source { ++ constructor() { ++ const appSys = Shell.AppSystem.get_default(); ++ this._app = appSys.lookup_app('gnome-shell-extension-prefs.desktop'); ++ ++ super(this._app.get_name()); ++ } ++ ++ getIcon() { ++ return this._app.app_info.get_icon(); ++ } ++ ++ _createPolicy() { ++ return new MessageTray.NotificationApplicationPolicy(this._app.id); ++ } ++ ++ open() { ++ this._app.activate(); ++ Main.overview.hide(); ++ Main.panel.closeCalendar(); ++ } ++} +-- +2.29.2 + + +From f6a5e2731f487d7a0ac088aff53ca1e76006c118 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 27 Jan 2020 00:59:19 +0100 +Subject: [PATCH 22/26] extensionSystem: Periodically check for extension + updates + +Now that we can download, apply and display extension updates, it is time +to actually check for updates. Schedule an update check right on startup, +then every 24 hours. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1968 +--- + js/ui/extensionSystem.js | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 805e08cae..914abb309 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -3,6 +3,7 @@ + const { GLib, Gio, GObject, St } = imports.gi; + const Signals = imports.signals; + ++const ExtensionDownloader = imports.ui.extensionDownloader; + const ExtensionUtils = imports.misc.extensionUtils; + const FileUtils = imports.misc.fileUtils; + const Main = imports.ui.main; +@@ -14,6 +15,8 @@ const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; + const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions'; + const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation'; + ++const UPDATE_CHECK_TIMEOUT = 24 * 60 * 60; // 1 day in seconds ++ + var ExtensionManager = class { + constructor() { + this._initted = false; +@@ -30,6 +33,12 @@ var ExtensionManager = class { + init() { + this._installExtensionUpdates(); + this._sessionUpdated(); ++ ++ GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => { ++ ExtensionDownloader.checkForUpdates(); ++ return GLib.SOURCE_CONTINUE; ++ }); ++ ExtensionDownloader.checkForUpdates(); + } + + lookup(uuid) { +-- +2.29.2 + + +From cb3ed33a72fea3ae6b8df031abca48b99dba75a5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 9 Mar 2020 16:49:34 +0100 +Subject: [PATCH 23/26] extensionDownloader: Remove pending updates with + extension + +When an extension is uninstalled, there is no point in keeping +a pending update: If the update didn't fail (which it currently +does), we would end up sneakily reinstalling the extension. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2343 +--- + js/ui/extensionDownloader.js | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index f957c6c62..0bd77e125 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -55,6 +55,15 @@ function uninstallExtension(uuid) { + return false; + + FileUtils.recursivelyDeleteDir(extension.dir, true); ++ ++ try { ++ const updatesDir = Gio.File.new_for_path(GLib.build_filenamev( ++ [global.userdatadir, 'extension-updates', extension.uuid])); ++ FileUtils.recursivelyDeleteDir(updatesDir, true); ++ } catch (e) { ++ // not an error ++ } ++ + return true; + } + +-- +2.29.2 + + +From 6abb5a189a7c97de8c0ed28c40f34fb625363223 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 9 Mar 2020 16:45:22 +0100 +Subject: [PATCH 24/26] extensionSystem: Catch errors when updating extensions + +Extension updates are installed at startup, so any errors that bubble +up uncaught will prevent the startup to complete. + +While the most likely error reason was addressed in the previous commit +(pending update for a no-longer exitent extension), it makes sense to +catch any kind of corrupt updates to not interfere with shell startup. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2343 +--- + js/ui/extensionSystem.js | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 914abb309..320af54e4 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -457,9 +457,14 @@ var ExtensionManager = class { + let extensionDir = Gio.File.new_for_path( + GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); + +- FileUtils.recursivelyDeleteDir(extensionDir, false); +- FileUtils.recursivelyMoveDir(dir, extensionDir); +- FileUtils.recursivelyDeleteDir(dir, true); ++ try { ++ FileUtils.recursivelyDeleteDir(extensionDir, false); ++ FileUtils.recursivelyMoveDir(dir, extensionDir); ++ } catch (e) { ++ log('Failed to install extension updates for %s'.format(uuid)); ++ } finally { ++ FileUtils.recursivelyDeleteDir(dir, true); ++ } + }); + } + +-- +2.29.2 + + +From 405897a9930362dad590eb8bd425c130dc636083 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 26 Jan 2021 17:12:04 +0100 +Subject: [PATCH 25/26] extensionSystem: Fix opening Extensions app from + notification + +Launching the app is implemented by the source's open() method, but +only external notifications are hooked up to call into the source +when no default action was provided. +--- + js/ui/extensionSystem.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js +index 320af54e4..81804ea5e 100644 +--- a/js/ui/extensionSystem.js ++++ b/js/ui/extensionSystem.js +@@ -194,6 +194,8 @@ var ExtensionManager = class { + let notification = new MessageTray.Notification(source, + _('Extension Updates Available'), + _('Extension updates are ready to be installed.')); ++ notification.connect('activated', ++ () => source.open()); + source.notify(notification); + } + } +-- +2.29.2 + + +From 8d50b96701eefa7f9bff4af8c855087eee35739a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 1 Feb 2021 18:26:00 +0100 +Subject: [PATCH 26/26] extensionDownloader: Refuse to override system + extensions + +The website allows to "update" system extensions by installing the +upstream version into the user's home directory. + +Prevent that by refusing to download and install extensions that are +already installed system-wide. +--- + js/ui/extensionDownloader.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js +index 0bd77e125..1e6f5340a 100644 +--- a/js/ui/extensionDownloader.js ++++ b/js/ui/extensionDownloader.js +@@ -16,6 +16,14 @@ var REPOSITORY_URL_UPDATE = REPOSITORY_URL_BASE + '/update-info/'; + let _httpSession; + + function installExtension(uuid, invocation) { ++ const oldExt = Main.extensionManager.lookup(uuid); ++ if (oldExt && oldExt.type === ExtensionUtils.ExtensionType.SYSTEM) { ++ log('extensionDownloader: Trying to replace system extension %s'.format(uuid)); ++ invocation.return_dbus_error('org.gnome.Shell.InstallError', ++ 'System extensions cannot be replaced'); ++ return; ++ } ++ + let params = { uuid: uuid, + shell_version: Config.PACKAGE_VERSION }; + +-- +2.29.2 diff --git a/SOURCES/fix-app-view-leaks.patch b/SOURCES/fix-app-view-leaks.patch new file mode 100644 index 0000000..b74b9b2 --- /dev/null +++ b/SOURCES/fix-app-view-leaks.patch @@ -0,0 +1,421 @@ +From a518c9f57e5fe9c6b5ece5c6cb0534a83f0b2f2d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 15 Jul 2019 13:52:58 -0400 +Subject: [PATCH 1/8] appDisplay: Don't leak duplicate items in AppView + +If an icon already exists in an app view with the same id, the +duplicate is not added on a call to addItem. Unfortunately, +since it's not added, the icon actor gets orphaned and leaked. + +This commit address the problem by introducing a new hasItem +method and disallowing callers to call addItem with a duplicate +in the first place. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 15 ++++++++++++--- + 1 file changed, 12 insertions(+), 3 deletions(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index a07db6573..fa22f47e0 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -143,10 +143,14 @@ class BaseAppView { + return this._allItems; + } + ++ hasItem(id) { ++ return this._items[id] !== undefined; ++ } ++ + addItem(icon) { + let id = icon.id; +- if (this._items[id] !== undefined) +- return; ++ if (this.hasItem(id)) ++ throw new Error(`icon with id ${id} already added to view`) + + this._allItems.push(icon); + this._items[id] = icon; +@@ -386,6 +390,8 @@ var AllView = class AllView extends BaseAppView { + + let folders = this._folderSettings.get_strv('folder-children'); + folders.forEach(id => { ++ if (this.hasItem(id)) ++ return; + let path = this._folderSettings.path + 'folders/' + id + '/'; + let icon = new FolderIcon(id, path, this); + icon.connect('name-changed', this._itemNameChanged.bind(this)); +@@ -1165,7 +1171,10 @@ var FolderIcon = class FolderIcon { + let excludedApps = this._folder.get_strv('excluded-apps'); + let appSys = Shell.AppSystem.get_default(); + let addAppId = appId => { +- if (excludedApps.indexOf(appId) >= 0) ++ if (this.view.hasItem(appId)) ++ return; ++ ++ if (excludedApps.includes(appId)) + return; + + let app = appSys.lookup_app(appId); +-- +2.23.0 + + +From 2b6aa9aed98c4854c2ad015879ddcb8d2bf91e9e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jul 2019 11:06:30 -0400 +Subject: [PATCH 2/8] iconGrid: Clear meta_later callback on destruction + +The IconGrid code sometimes sets up a callback to be invoked +later right before being destroyed. + +This commit adds a destroy handler to cancel the callback. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/iconGrid.js | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js +index d51a443e8..1f05e67f3 100644 +--- a/js/ui/iconGrid.js ++++ b/js/ui/iconGrid.js +@@ -210,6 +210,8 @@ var IconGrid = GObject.registerClass({ + this.rightPadding = 0; + this.leftPadding = 0; + ++ this._updateIconSizesLaterId = 0; ++ + this._items = []; + this._clonesAnimating = []; + // Pulled from CSS, but hardcode some defaults here +@@ -227,6 +229,14 @@ var IconGrid = GObject.registerClass({ + + this.connect('actor-added', this._childAdded.bind(this)); + this.connect('actor-removed', this._childRemoved.bind(this)); ++ this.connect('destroy', this._onDestroy.bind(this)); ++ } ++ ++ _onDestroy() { ++ if (this._updateIconSizesLaterId) { ++ Meta.later_remove (this._updateIconSizesLaterId); ++ this._updateIconSizesLaterId = 0; ++ } + } + + _keyFocusIn(actor) { +@@ -757,12 +767,14 @@ var IconGrid = GObject.registerClass({ + + this._updateSpacingForSize(availWidth, availHeight); + } +- Meta.later_add(Meta.LaterType.BEFORE_REDRAW, +- this._updateIconSizes.bind(this)); ++ if (!this._updateIconSizesLaterId) ++ this._updateIconSizesLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, ++ this._updateIconSizes.bind(this)); + } + + // Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up + _updateIconSizes() { ++ this._updateIconSizesLaterId = 0; + let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize); + let newIconSize = Math.floor(ICON_SIZE * scale); + for (let i in this._items) { +-- +2.23.0 + + +From 14a2650548a5104d6a3ec7a1174a23264d79030a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jul 2019 11:02:10 -0400 +Subject: [PATCH 3/8] appDisplay: Add AppFolderPopup destroy handler + +At the moment AppFolderPopup calls popdown on destruction, +which leads to open-state-changed getting emitted after +the actor associated with the popup is destroyed. + +This commit handles ungrabbing and closing from an +actor destroy handler to side-step the open-state-changed +signal. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index fa22f47e0..b75d095d5 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1329,6 +1329,15 @@ var AppFolderPopup = class AppFolderPopup { + }); + this._grabHelper.addActor(Main.layoutManager.overviewGroup); + this.actor.connect('key-press-event', this._onKeyPress.bind(this)); ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ } ++ ++ _onDestroy() { ++ if (this._isOpen) { ++ this._isOpen = false; ++ this._grabHelper.ungrab({ actor: this.actor }); ++ this._grabHelper = null; ++ } + } + + _onKeyPress(actor, event) { +-- +2.23.0 + + +From c9fcb2d23141694ffa2182df20ba75687b01dacc Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2019 10:06:38 -0400 +Subject: [PATCH 4/8] appDisplay: Clear AllView reference to current popup when + destroyed + +AllView contains a reference to the current popup that lingers after +the popup is destroyed. + +This commit fixes that, by explicitly nullifying when appropriate. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index b75d095d5..dabf63bfd 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -300,6 +300,7 @@ var AllView = class AllView extends BaseAppView { + this._eventBlocker.add_action(this._clickAction); + + this._displayingPopup = false; ++ this._currentPopupDestroyId = 0; + + this._availWidth = 0; + this._availHeight = 0; +@@ -589,7 +590,22 @@ var AllView = class AllView extends BaseAppView { + this._stack.add_actor(popup.actor); + popup.connect('open-state-changed', (popup, isOpen) => { + this._eventBlocker.reactive = isOpen; +- this._currentPopup = isOpen ? popup : null; ++ ++ if (this._currentPopup) { ++ this._currentPopup.actor.disconnect(this._currentPopupDestroyId); ++ this._currentPopupDestroyId = 0; ++ } ++ ++ this._currentPopup = null; ++ ++ if (isOpen) { ++ this._currentPopup = popup; ++ this._currentPopupDestroyId = popup.actor.connect('destroy', () => { ++ this._currentPopup = null; ++ this._currentPopupDestroyId = 0; ++ this._eventBlocker.reactive = false; ++ }); ++ } + this._updateIconOpacities(isOpen); + if(!isOpen) + this._closeSpaceForPopup(); +-- +2.23.0 + + +From b7a3fd7fa4527ba9411dcd18debe6ccf88c34dc0 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jul 2019 10:57:57 -0400 +Subject: [PATCH 5/8] appDisplay: Add destroy handler for FolderIcon + +It is important that the FolderView of a FolderIcon always +gets destroyed before the AppFolderPopup, since the view +may or may not be in the popup, and the view should +get cleaned up exactly once in either case. + +This commit adds a destroy handler on FolderIcon to ensure +things get taken down in the right order, and to make sure +the view isn't leaked if it's not yet part of the popup. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index dabf63bfd..5a8f4f1bf 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1156,6 +1156,7 @@ var FolderIcon = class FolderIcon { + this.view.actor.vscroll.adjustment.value = 0; + this._openSpaceForPopup(); + }); ++ this.actor.connect('destroy', this.onDestroy.bind(this)); + this.actor.connect('notify::mapped', () => { + if (!this.actor.mapped && this._popup) + this._popup.popdown(); +@@ -1165,6 +1166,13 @@ var FolderIcon = class FolderIcon { + this._redisplay(); + } + ++ onDestroy() { ++ this.view.actor.destroy(); ++ ++ if (this._popup) ++ this._popup.actor.destroy(); ++ } ++ + getAppIds() { + return this.view.getAllItems().map(item => item.id); + } +-- +2.23.0 + + +From a90d7a97d21ffa596747cc8ecd0e3f500cb8a77c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2019 14:49:30 -0400 +Subject: [PATCH 6/8] appDisplay: Stop watching FolderIcon parent view when + destroyed + +When a FolderIcon is opened, it asks the parent view to allocate +space for it, which takes time. Eventually, the space-ready +signal is emitted on the view and the icon can make use of the new +space with its popup. If the icon gets destroyed in the +interim, though, space-ready signal handler still fires. + +This commit disconnects the signal handler so it doesn't get called +on a destroyed icon. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index 5a8f4f1bf..062ff222c 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1169,6 +1169,11 @@ var FolderIcon = class FolderIcon { + onDestroy() { + this.view.actor.destroy(); + ++ if (this._spaceReadySignalId) { ++ this._parentView.disconnect(this._spaceReadySignalId); ++ this._spaceReadySignalId = 0; ++ } ++ + if (this._popup) + this._popup.actor.destroy(); + } +@@ -1240,8 +1245,9 @@ var FolderIcon = class FolderIcon { + } + + _openSpaceForPopup() { +- let id = this._parentView.connect('space-ready', () => { +- this._parentView.disconnect(id); ++ this._spaceReadySignalId = this._parentView.connect('space-ready', () => { ++ this._parentView.disconnect(this._spaceReadySignalId); ++ this._spaceReadySignalId = 0; + this._popup.popup(); + this._updatePopupPosition(); + }); +-- +2.23.0 + + +From b57ab33dadf0f31c5bf2c800806593e94784050c Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2019 10:19:13 -0400 +Subject: [PATCH 7/8] appDisplay: Add open method to FolderIcon + +At the moment the only way to open a folder icon is to click on it; +there's no API to open the icon programmatically. + +This commits adds an open method and makes the click handler use +it. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index 062ff222c..c0c6e3663 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1151,11 +1151,7 @@ var FolderIcon = class FolderIcon { + + this.view = new FolderView(); + +- this.actor.connect('clicked', () => { +- this._ensurePopup(); +- this.view.actor.vscroll.adjustment.value = 0; +- this._openSpaceForPopup(); +- }); ++ this.actor.connect('clicked', this.open.bind(this)); + this.actor.connect('destroy', this.onDestroy.bind(this)); + this.actor.connect('notify::mapped', () => { + if (!this.actor.mapped && this._popup) +@@ -1178,6 +1174,12 @@ var FolderIcon = class FolderIcon { + this._popup.actor.destroy(); + } + ++ open() { ++ this._ensurePopup(); ++ this.view.actor.vscroll.adjustment.value = 0; ++ this._openSpaceForPopup(); ++ } ++ + getAppIds() { + return this.view.getAllItems().map(item => item.id); + } +-- +2.23.0 + + +From baacab7922a56957d041aa59944c419b82e7a7e1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 18 Jul 2019 11:13:27 -0400 +Subject: [PATCH 8/8] appDisplay: Keep popup open on refresh + +If the list of applications is refreshed we currently close +the open app folder. + +This commit adds logic to reopen the app folder on reload. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/628 +--- + js/ui/appDisplay.js | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index c0c6e3663..7fad02cd0 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -345,6 +345,21 @@ var AllView = class AllView extends BaseAppView { + super.removeAll(); + } + ++ _redisplay() { ++ let openFolderId = null; ++ if (this._displayingPopup && this._currentPopup) ++ openFolderId = this._currentPopup._source.id; ++ ++ super._redisplay(); ++ ++ if (openFolderId) { ++ let [folderToReopen] = this.folderIcons.filter(folder => folder.id == openFolderId); ++ ++ if (folderToReopen) ++ folderToReopen.open(); ++ } ++ } ++ + _itemNameChanged(item) { + // If an item's name changed, we can pluck it out of where it's + // supposed to be and reinsert it where it's sorted. +-- +2.23.0 + diff --git a/SOURCES/fix-double-disposed-backgrounds.patch b/SOURCES/fix-double-disposed-backgrounds.patch new file mode 100644 index 0000000..7eebf77 --- /dev/null +++ b/SOURCES/fix-double-disposed-backgrounds.patch @@ -0,0 +1,101 @@ +From 49d066234f9f528122bb40c5144b40d8b19a0071 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 22 Aug 2022 12:52:19 +0200 +Subject: [PATCH] Background: Avoid double dispose and actors recreations + +Subject: [PATCH 1/2] background: Use Garbage Collector to dispose background: + +The same Meta.Background could be used by multiple instances of background +actors, and so should not be disposed when the actor using it is destroyed. + +Instead of calling `run_dispose` directly on it, just nullify the reference +on destroy method, leaving the job of doing the proper disposition to the +gabage collector that keeps the proper reference count on the Meta.Background. + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/501 + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/558 + +Subject: [PATCH 2/2] background: Group 'changed' signal emission + +Background is monitoring the whole `org.gnome.desktop.background` gsettings keys +for changes connecting to the non-specialized 'changed' signal and re-emitting +this as-is. +This means that when the background is changed via control-center, we get +multiple 'changed' signal events from GSettings, and for each one of this we +recreate a Background and a BackgroundActor. + +Avoid this by using an idle to delay the emission of the 'changed' signal +grouping the events. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/558 +--- + js/ui/background.js | 26 +++++++++++++++++++++----- + 1 file changed, 21 insertions(+), 5 deletions(-) + +diff --git a/js/ui/background.js b/js/ui/background.js +index 06e0388..2a404ae 100644 +--- a/js/ui/background.js ++++ b/js/ui/background.js +@@ -257,14 +257,15 @@ var Background = class Background { + this._refreshAnimation(); + }); + +- this._settingsChangedSignalId = this._settings.connect('changed', () => { +- this.emit('changed'); +- }); ++ this._settingsChangedSignalId = ++ this._settings.connect('changed', this._emitChangedSignal.bind(this)); + + this._load(); + } + + destroy() { ++ this.background = null; ++ + this._cancellable.cancel(); + this._removeAnimationTimeout(); + +@@ -288,6 +289,22 @@ var Background = class Background { + if (this._settingsChangedSignalId != 0) + this._settings.disconnect(this._settingsChangedSignalId); + this._settingsChangedSignalId = 0; ++ ++ if (this._changedIdleId) { ++ GLib.source_remove(this._changedIdleId); ++ this._changedIdleId = 0; ++ } ++ } ++ ++ _emitChangedSignal() { ++ if (this._changedIdleId) ++ return; ++ ++ this._changedIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { ++ this._changedIdleId = 0; ++ this.emit('changed'); ++ return GLib.SOURCE_REMOVE; ++ }); + } + + updateResolution() { +@@ -343,7 +360,7 @@ var Background = class Background { + if (changedFile.equal(file)) { + let imageCache = Meta.BackgroundImageCache.get_default(); + imageCache.purge(changedFile); +- this.emit('changed'); ++ this._emitChangedSignal(); + } + }); + this._fileWatches[key] = signalId; +@@ -699,7 +716,6 @@ var BackgroundManager = class BackgroundManager { + time: FADE_ANIMATION_TIME, + transition: 'easeOutQuad', + onComplete() { +- oldBackgroundActor.background.run_dispose(); + oldBackgroundActor.destroy(); + } + }); +-- +2.35.3 + diff --git a/SOURCES/fix-invalid-access-warnings.patch b/SOURCES/fix-invalid-access-warnings.patch new file mode 100644 index 0000000..8fbdb93 --- /dev/null +++ b/SOURCES/fix-invalid-access-warnings.patch @@ -0,0 +1,224 @@ +From 76eebb42ed4c76970a9debfc0cd41537923eccde Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Tue, 5 Dec 2017 02:41:50 +0100 +Subject: [PATCH 1/2] tweener: Save handlers on target and remove them on + destroy + +Saving handlers we had using the wrapper as a property of the object and delete +them when resetting the object state. +Without doing this an handler could be called on a destroyed target when this +happens on the onComplete callback. + +https://bugzilla.gnome.org/show_bug.cgi?id=791233 +--- + js/ui/tweener.js | 63 ++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 50 insertions(+), 13 deletions(-) + +diff --git a/js/ui/tweener.js b/js/ui/tweener.js +index bb9ea557c..c04cede25 100644 +--- a/js/ui/tweener.js ++++ b/js/ui/tweener.js +@@ -63,30 +63,67 @@ function _getTweenState(target) { + return target.__ShellTweenerState; + } + ++function _ensureHandlers(target) { ++ if (!target.__ShellTweenerHandlers) ++ target.__ShellTweenerHandlers = {}; ++ return target.__ShellTweenerHandlers; ++} ++ + function _resetTweenState(target) { + let state = target.__ShellTweenerState; + + if (state) { +- if (state.destroyedId) ++ if (state.destroyedId) { + state.actor.disconnect(state.destroyedId); ++ delete state.destroyedId; ++ } + } + ++ _removeHandler(target, 'onComplete', _tweenCompleted); + target.__ShellTweenerState = {}; + } + + function _addHandler(target, params, name, handler) { +- if (params[name]) { +- let oldHandler = params[name]; +- let oldScope = params[name + 'Scope']; +- let oldParams = params[name + 'Params']; +- let eventScope = oldScope ? oldScope : target; +- +- params[name] = () => { +- oldHandler.apply(eventScope, oldParams); +- handler(target); +- }; +- } else +- params[name] = () => { handler(target); }; ++ let wrapperNeeded = false; ++ let tweenerHandlers = _ensureHandlers(target); ++ ++ if (!(name in tweenerHandlers)) { ++ tweenerHandlers[name] = []; ++ wrapperNeeded = true; ++ } ++ ++ let handlers = tweenerHandlers[name]; ++ handlers.push(handler); ++ ++ if (wrapperNeeded) { ++ if (params[name]) { ++ let oldHandler = params[name]; ++ let oldScope = params[name + 'Scope']; ++ let oldParams = params[name + 'Params']; ++ let eventScope = oldScope ? oldScope : target; ++ ++ params[name] = () => { ++ oldHandler.apply(eventScope, oldParams); ++ handlers.forEach((h) => h(target)); ++ }; ++ } else { ++ params[name] = () => { handlers.forEach((h) => h(target)); }; ++ } ++ } ++} ++ ++function _removeHandler(target, name, handler) { ++ let tweenerHandlers = _ensureHandlers(target); ++ ++ if (name in tweenerHandlers) { ++ let handlers = tweenerHandlers[name]; ++ let handlerIndex = handlers.indexOf(handler); ++ ++ while (handlerIndex > -1) { ++ handlers.splice(handlerIndex, 1); ++ handlerIndex = handlers.indexOf(handler); ++ } ++ } + } + + function _actorDestroyed(target) { +-- +2.21.0 + + +From 730f6f7d708a0cbcfcc75e4a1fba8512ac7c4c82 Mon Sep 17 00:00:00 2001 +From: Cosimo Cecchi +Date: Sun, 26 May 2019 08:31:07 -0700 +Subject: [PATCH 2/2] windowAttentionHandler: disconnect signals before + destruction +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The 'destroy' signal is emitted at the end of the destroy() method. +However the implementation of destroy() can end up emitting one of the +signals we connect to on the window, causing us to re-enter destroy +from its callback. +That will in turn lead to some objects getting disposed twice, which +produces a stack trace like the following one. + +This commit fixes the issue by overriding the destroy() method instead +of connecting to the signal, which allows us to disconnect the signal +handlers from the window at an earlier time and avoid re-entrancy. + +-- + +gnome-shell[1082]: Object Gio.Settings (0x7f0af8143f00), has been already deallocated — impossible to access it. This might be caused by the object having been destroyed from C code using something such as destroy(), dispose(), or remove() vfuncs. +org.gnome.Shell.desktop[1082]: == Stack trace for context 0x5627f7d1e220 == +org.gnome.Shell.desktop[1082]: #0 5627f9e801a8 i resource:///org/gnome/shell/ui/messageTray.js:238 (7f0aefa9eca0 @ 22) +org.gnome.Shell.desktop[1082]: #1 5627f9e80108 i resource:///org/gnome/shell/ui/messageTray.js:802 (7f0aefaa2ee0 @ 28) +org.gnome.Shell.desktop[1082]: #2 5627f9e80070 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:79 (7f0aef7b29d0 @ 62) +org.gnome.Shell.desktop[1082]: #3 7fffa69fbfc0 b self-hosted:979 (7f0aefa515e0 @ 440) +org.gnome.Shell.desktop[1082]: #4 5627f9e7ffe0 i resource:///org/gnome/shell/ui/messageTray.js:121 (7f0aefa9e1f0 @ 71) +org.gnome.Shell.desktop[1082]: #5 5627f9e7ff38 i resource:///org/gnome/shell/ui/messageTray.js:1408 (7f0aefaa58b0 @ 22) +org.gnome.Shell.desktop[1082]: #6 5627f9e7fe80 i resource:///org/gnome/shell/ui/messageTray.js:1237 (7f0aefaa51f0 @ 729) +org.gnome.Shell.desktop[1082]: #7 5627f9e7fde8 i resource:///org/gnome/shell/ui/messageTray.js:1055 (7f0aefaa3d30 @ 124) +org.gnome.Shell.desktop[1082]: #8 7fffa69ff8e0 b self-hosted:979 (7f0aefa515e0 @ 440) +org.gnome.Shell.desktop[1082]: #9 7fffa69ff9d0 b resource:///org/gnome/gjs/modules/signals.js:142 (7f0aefccb670 @ 386) +org.gnome.Shell.desktop[1082]: #10 5627f9e7fd58 i resource:///org/gnome/shell/ui/messageTray.js:479 (7f0aefaa0940 @ 50) +org.gnome.Shell.desktop[1082]: #11 5627f9e7fcb8 i resource:///org/gnome/shell/ui/messageTray.js:808 (7f0aefaa2ee0 @ 99) +org.gnome.Shell.desktop[1082]: #12 5627f9e7fc28 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:69 (7f0aef7b28b0 @ 13) +org.gnome.Shell.desktop[1082]: #13 5627f9e7fb80 i resource:///org/gnome/shell/ui/main.js:566 (7f0aefcd8820 @ 216) +org.gnome.Shell.desktop[1082]: #14 5627f9e7fad0 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:103 (7f0aef7b2c10 @ 27) +org.gnome.Shell.desktop[1082]: #15 5627f9e7fa58 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:43 (7f0aef7b2700 @ 17) +org.gnome.Shell.desktop[1082]: #16 7fffa6a03350 b resource:///org/gnome/gjs/modules/signals.js:142 (7f0aefccb670 @ 386) +org.gnome.Shell.desktop[1082]: #17 5627f9e7f9d0 i resource:///org/gnome/shell/ui/messageTray.js:471 (7f0aefaa08b0 @ 22) +org.gnome.Shell.desktop[1082]: #18 5627f9e7f950 i resource:///org/gnome/shell/ui/calendar.js:752 (7f0aefaabdc0 @ 22) +org.gnome.Shell.desktop[1082]: #19 7fffa6a048f0 b self-hosted:979 (7f0aefa515e0 @ 440) +org.gnome.Shell.desktop[1082]: == Stack trace for context 0x5627f7d1e220 == +org.gnome.Shell.desktop[1082]: #0 5627f9e801a8 i resource:///org/gnome/shell/ui/messageTray.js:239 (7f0aefa9eca0 @ 42) +org.gnome.Shell.desktop[1082]: #1 5627f9e80108 i resource:///org/gnome/shell/ui/messageTray.js:802 (7f0aefaa2ee0 @ 28) +org.gnome.Shell.desktop[1082]: #2 5627f9e80070 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:79 (7f0aef7b29d0 @ 62) +org.gnome.Shell.desktop[1082]: #3 7fffa69fbfc0 b self-hosted:979 (7f0aefa515e0 @ 440) +org.gnome.Shell.desktop[1082]: #4 5627f9e7ffe0 i resource:///org/gnome/shell/ui/messageTray.js:121 (7f0aefa9e1f0 @ 71) +org.gnome.Shell.desktop[1082]: #5 5627f9e7ff38 i resource:///org/gnome/shell/ui/messageTray.js:1408 (7f0aefaa58b0 @ 22) +org.gnome.Shell.desktop[1082]: #6 5627f9e7fe80 i resource:///org/gnome/shell/ui/messageTray.js:1237 (7f0aefaa51f0 @ 729) +org.gnome.Shell.desktop[1082]: #7 5627f9e7fde8 i resource:///org/gnome/shell/ui/messageTray.js:1055 (7f0aefaa3d30 @ 124) +org.gnome.Shell.desktop[1082]: #8 7fffa69ff8e0 b self-hosted:979 (7f0aefa515e0 @ 440) +org.gnome.Shell.desktop[1082]: #9 7fffa69ff9d0 b resource:///org/gnome/gjs/modules/signals.js:142 (7f0aefccb670 @ 386) +org.gnome.Shell.desktop[1082]: #10 5627f9e7fd58 i resource:///org/gnome/shell/ui/messageTray.js:479 (7f0aefaa0940 @ 50) +org.gnome.Shell.desktop[1082]: #11 5627f9e7fcb8 i resource:///org/gnome/shell/ui/messageTray.js:808 (7f0aefaa2ee0 @ 99) +org.gnome.Shell.desktop[1082]: #12 5627f9e7fc28 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:69 (7f0aef7b28b0 @ 13) +org.gnome.Shell.desktop[1082]: #13 5627f9e7fb80 i resource:///org/gnome/shell/ui/main.js:566 (7f0aefcd8820 @ 216) +org.gnome.Shell.desktop[1082]: #14 5627f9e7fad0 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:103 (7f0aef7b2c10 @ 27) +org.gnome.Shell.desktop[1082]: #15 5627f9e7fa58 i resource:///org/gnome/shell/ui/windowAttentionHandler.js:43 (7f0aef7b2700 @ 17) +org.gnome.Shell.desktop[1082]: #16 7fffa6a03350 b resource:///org/gnome/gjs/modules/signals.js:142 (7f0aefccb670 @ 386) +org.gnome.Shell.desktop[1082]: #17 5627f9e7f9d0 i resource:///org/gnome/shell/ui/messageTray.js:471 (7f0aefaa08b0 @ 22) +org.gnome.Shell.desktop[1082]: #18 5627f9e7f950 i resource:///org/gnome/shell/ui/calendar.js:752 (7f0aefaabdc0 @ 22) +org.gnome.Shell.desktop[1082]: #19 7fffa6a048f0 b self-hosted:979 (7f0aefa515e0 @ 440) +gnome-shell[1082]: g_object_run_dispose: assertion 'G_IS_OBJECT (object)' failed +gnome-shell[1082]: Object Gio.Settings (0x7f0af8161750), has been already deallocated — impossible to access it. This might be caused by the object having been destroyed from C code using something such as destroy(), dispose(), or remove() vfuncs. +gnome-shell[1082]: g_object_run_dispose: assertion 'G_IS_OBJECT (object)' failed + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/555 +--- + js/ui/windowAttentionHandler.js | 17 ++++++++--------- + 1 file changed, 8 insertions(+), 9 deletions(-) + +diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js +index abdb8a444..a9a7111ba 100644 +--- a/js/ui/windowAttentionHandler.js ++++ b/js/ui/windowAttentionHandler.js +@@ -69,8 +69,6 @@ var Source = class WindowAttentionSource extends MessageTray.Source { + () => { this.destroy(); })); + this.signalIDs.push(this._window.connect('unmanaged', + () => { this.destroy(); })); +- +- this.connect('destroy', this._onDestroy.bind(this)); + } + + _sync() { +@@ -79,13 +77,6 @@ var Source = class WindowAttentionSource extends MessageTray.Source { + this.destroy(); + } + +- _onDestroy() { +- for(let i = 0; i < this.signalIDs.length; i++) { +- this._window.disconnect(this.signalIDs[i]); +- } +- this.signalIDs = []; +- } +- + _createPolicy() { + if (this._app && this._app.get_app_info()) { + let id = this._app.get_id().replace(/\.desktop$/,''); +@@ -99,6 +90,14 @@ var Source = class WindowAttentionSource extends MessageTray.Source { + return this._app.create_icon_texture(size); + } + ++ destroy(params) { ++ for (let i = 0; i < this.signalIDs.length; i++) ++ this._window.disconnect(this.signalIDs[i]); ++ this.signalIDs = []; ++ ++ super.destroy(params); ++ } ++ + open() { + Main.activateWindow(this._window); + } +-- +2.21.0 + diff --git a/SOURCES/fix-login-lock-screen.patch b/SOURCES/fix-login-lock-screen.patch new file mode 100644 index 0000000..f714a38 --- /dev/null +++ b/SOURCES/fix-login-lock-screen.patch @@ -0,0 +1,161 @@ +From 214c4f390faa40199c03a80594313760ffe9c5a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 20 Sep 2019 13:17:40 +0200 +Subject: [PATCH 1/2] unlockDialog: Use inheritance instead of composition + +The screen shield creates the unlock dialog based on the session mode. + +However since commit 0c0d76f7d6990 turned LoginDialog into an actor +subclass (while UnlockDialog kept using the delegate pattern), it is +no longer possible to handle both objects the same way without warnings. + +Allow this again by turning UnlockDialog into an actor subclass as well. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/736 +--- + js/ui/unlockDialog.js | 46 ++++++++++++++++++++++++------------------- + 1 file changed, 26 insertions(+), 20 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 4b0470f4b..55abb652d 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -1,8 +1,7 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + const { AccountsService, Atk, Clutter, +- Gdm, Gio, GLib, Meta, Shell, St } = imports.gi; +-const Signals = imports.signals; ++ Gdm, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; + + const Layout = imports.ui.layout; + const Main = imports.ui.main; +@@ -12,15 +11,19 @@ const AuthPrompt = imports.gdm.authPrompt; + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + +-var UnlockDialog = class { +- constructor(parentActor) { +- this.actor = new St.Widget({ accessible_role: Atk.Role.WINDOW, +- style_class: 'login-dialog', +- layout_manager: new Clutter.BoxLayout(), +- visible: false }); ++var UnlockDialog = GObject.registerClass({ ++ Signals: { 'failed': {} }, ++}, class UnlockDialog extends St.Widget { ++ _init(parentActor) { ++ super._init({ ++ accessible_role: Atk.Role.WINDOW, ++ style_class: 'login-dialog', ++ layout_manager: new Clutter.BoxLayout(), ++ visible: false, ++ }); + +- this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); +- parentActor.add_child(this.actor); ++ this.add_constraint(new Layout.MonitorConstraint({ primary: true })); ++ parentActor.add_child(this); + + this._userManager = AccountsService.UserManager.get_default(); + this._userName = GLib.get_user_name(); +@@ -31,7 +34,7 @@ var UnlockDialog = class { + y_align: Clutter.ActorAlign.CENTER, + x_expand: true, + y_expand: true }); +- this.actor.add_child(this._promptBox); ++ this.add_child(this._promptBox); + + this._gdmClient = new Gdm.Client(); + +@@ -70,10 +73,12 @@ var UnlockDialog = class { + this._authPrompt.reset(); + this._updateSensitivity(true); + +- Main.ctrlAltTabManager.addGroup(this.actor, _("Unlock Window"), 'dialog-password-symbolic'); ++ Main.ctrlAltTabManager.addGroup(this, _("Unlock Window"), 'dialog-password-symbolic'); + + this._idleMonitor = Meta.IdleMonitor.get_core(); + this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, this._escape.bind(this)); ++ ++ this.connect('destroy', this._onDestroy.bind(this)); + } + + _updateSensitivity(sensitive) { +@@ -112,9 +117,8 @@ var UnlockDialog = class { + this._authPrompt.cancel(); + } + +- destroy() { ++ _onDestroy() { + this.popModal(); +- this.actor.destroy(); + + if (this._idleWatchId) { + this._idleMonitor.remove_watch(this._idleWatchId); +@@ -137,13 +141,16 @@ var UnlockDialog = class { + } + + open(timestamp) { +- this.actor.show(); ++ this.show(); + + if (this._isModal) + return true; + +- if (!Main.pushModal(this.actor, { timestamp: timestamp, +- actionMode: Shell.ActionMode.UNLOCK_SCREEN })) ++ let modalParams = { ++ timestamp, ++ actionMode: Shell.ActionMode.UNLOCK_SCREEN, ++ }; ++ if (!Main.pushModal(this, modalParams)) + return false; + + this._isModal = true; +@@ -153,9 +160,8 @@ var UnlockDialog = class { + + popModal(timestamp) { + if (this._isModal) { +- Main.popModal(this.actor, timestamp); ++ Main.popModal(this, timestamp); + this._isModal = false; + } + } +-}; +-Signals.addSignalMethods(UnlockDialog.prototype); ++}); +-- +2.31.1 + + +From cddeb2f4e38928e0d5e0f3a852961f639536aff3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 20 Sep 2019 13:14:40 +0200 +Subject: [PATCH 2/2] screenShield: Stop using deprecated actor property + +Both LoginDialog and UnlockDialog are now actor subclasses, so stop +using the deprecated actor delegate that will trigger a warning. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/736 +--- + js/ui/screenShield.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 2d0a429be..f97a9288a 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -917,8 +917,8 @@ var ScreenShield = class { + this._lockScreenGroup.hide(); + + if (this._dialog) { +- this._dialog.actor.grab_key_focus(); +- this._dialog.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false); ++ this._dialog.grab_key_focus(); ++ this._dialog.navigate_focus(null, St.DirectionType.TAB_FORWARD, false); + } + } + +-- +2.31.1 + diff --git a/SOURCES/fix-nm-device-settings.patch b/SOURCES/fix-nm-device-settings.patch new file mode 100644 index 0000000..db9d80e --- /dev/null +++ b/SOURCES/fix-nm-device-settings.patch @@ -0,0 +1,131 @@ +From e2a1b737156804e2647e5de938c3d170c11b6ba4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 31 Jul 2020 20:40:36 +0200 +Subject: [PATCH 1/2] status/network: Use D-Bus to launch Settings panels + +For more obscure network configurations, we need to launch the +corresponding Settings panel with additional parameters, so we +cannot simply launch the .desktop file. + +However we can do better than spawning a command line: Control center +exposes an application action we can use instead, so the process is +launched with the appropriate activation environment and startup +notification support. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1385 +--- + js/ui/status/network.js | 33 +++++++++++++++++++++++++++++---- + 1 file changed, 29 insertions(+), 4 deletions(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index f8991d02f..0e7e82ce0 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -15,6 +15,8 @@ const Util = imports.misc.util; + + const { loadInterfaceXML } = imports.misc.fileUtils; + ++Gio._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish'); ++ + const NMConnectionCategory = { + INVALID: 'invalid', + WIRED: 'wired', +@@ -75,6 +77,30 @@ function ensureActiveConnectionProps(active, client) { + } + } + ++function launchSettingsPanel(panel, ...args) { ++ const param = new GLib.Variant('(sav)', ++ [panel, args.map(s => new GLib.Variant('s', s))]); ++ const platformData = { ++ 'desktop-startup-id': new GLib.Variant('s', ++ '_TIME%s'.format(global.get_current_time())), ++ }; ++ try { ++ Gio.DBus.session.call( ++ 'org.gnome.ControlCenter', ++ '/org/gnome/ControlCenter', ++ 'org.freedesktop.Application', ++ 'ActivateAction', ++ new GLib.Variant('(sava{sv})', ++ ['launch-panel', [param], platformData]), ++ null, ++ Gio.DBusCallFlags.NONE, ++ -1, ++ null); ++ } catch (e) { ++ log('Failed to launch Settings panel: %s'.format(e.message)); ++ } ++} ++ + var NMConnectionItem = class { + constructor(section, connection) { + this._section = section; +@@ -534,8 +560,7 @@ var NMDeviceModem = class extends NMConnectionDevice { + } + + _autoConnect() { +- Util.spawn(['gnome-control-center', 'network', +- 'connect-3g', this._device.get_path()]); ++ launchSettingsPanel('network', 'connect-3g', this._device.get_path()); + } + + _sessionUpdated() { +@@ -920,8 +945,8 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog { + || (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) { + // 802.1x-enabled APs require further configuration, so they're + // handled in gnome-control-center +- Util.spawn(['gnome-control-center', 'wifi', 'connect-8021x-wifi', +- this._device.get_path(), accessPoints[0].get_path()]); ++ launchSettingsPanel('wifi', 'connect-8021x-wifi', ++ this._device.get_path(), accessPoints[0].get_path()); + } else { + let connection = new NM.SimpleConnection(); + this._client.add_and_activate_connection_async(connection, this._device, accessPoints[0].get_path(), null, null) +-- +2.38.1 + + +From 9ca1989fcc73157685742470c25f538d01d8df44 Mon Sep 17 00:00:00 2001 +From: Xiaoguang Wang +Date: Mon, 21 Feb 2022 09:11:23 +0800 +Subject: [PATCH 2/2] network: Get dbus path from NMDevice + +In the NetworkManager new version the NMDevice.get_path returns pci +path, we need to use NM prototype to get device dbus path. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/4565 + +Part-of: +--- + js/ui/status/network.js | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index 0e7e82ce0..9d6a83b73 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -946,7 +946,7 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog { + // 802.1x-enabled APs require further configuration, so they're + // handled in gnome-control-center + launchSettingsPanel('wifi', 'connect-8021x-wifi', +- this._device.get_path(), accessPoints[0].get_path()); ++ this._getDeviceDBusPath(), accessPoints[0].get_path()); + } else { + let connection = new NM.SimpleConnection(); + this._client.add_and_activate_connection_async(connection, this._device, accessPoints[0].get_path(), null, null) +@@ -956,6 +956,11 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog { + this.close(); + } + ++ _getDeviceDBusPath() { ++ // nm_object_get_path() is shadowed by nm_device_get_path() ++ return NM.Object.prototype.get_path.call(this._device); ++ } ++ + _notifySsidCb(accessPoint) { + if (accessPoint.get_ssid() != null) { + accessPoint.disconnect(accessPoint._notifySsidId); +-- +2.38.1 + diff --git a/SOURCES/fix-some-js-warnings.patch b/SOURCES/fix-some-js-warnings.patch new file mode 100644 index 0000000..3faa329 --- /dev/null +++ b/SOURCES/fix-some-js-warnings.patch @@ -0,0 +1,223 @@ +From 530964cc6e5db02633434853debd96069dc2b8d8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Thu, 23 May 2019 06:12:56 +0200 +Subject: [PATCH 1/6] realmd: Set login format to null on start and update if + invalid + +We were checking an undefined property but that would lead to a a warning. +Instead we can consider the login format unset until is null, and in case +update it. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/700 +--- + js/gdm/realmd.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/realmd.js b/js/gdm/realmd.js +index 50f3c5899..04cd99787 100644 +--- a/js/gdm/realmd.js ++++ b/js/gdm/realmd.js +@@ -21,6 +21,7 @@ var Manager = class { + '/org/freedesktop/realmd', + this._reloadRealms.bind(this)) + this._realms = {}; ++ this._loginFormat = null; + + this._signalId = this._aggregateProvider.connect('g-properties-changed', + (proxy, properties) => { +@@ -86,7 +87,7 @@ var Manager = class { + } + + get loginFormat() { +- if (this._loginFormat !== undefined) ++ if (this._loginFormat) + return this._loginFormat; + + this._updateLoginFormat(); +-- +2.33.1 + + +From 988e4b58d64fbf87f0c497315ff2506b269ff7c9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 9 Jun 2020 19:42:21 +0200 +Subject: [PATCH 2/6] popupMenu: Guard against non-menu-item children + +This avoid a harmless but annoying warning. +--- + js/ui/popupMenu.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js +index 44818533a..b5115d7f7 100644 +--- a/js/ui/popupMenu.js ++++ b/js/ui/popupMenu.js +@@ -696,7 +696,8 @@ var PopupMenuBase = class { + } + + _getMenuItems() { +- return this.box.get_children().map(a => a._delegate).filter(item => { ++ const children = this.box.get_children().filter(a => a._delegate !== undefined); ++ return children.map(a => a._delegate).filter(item => { + return item instanceof PopupBaseMenuItem || item instanceof PopupMenuSection; + }); + } +-- +2.33.1 + + +From 609a8e22e67b63da1e35167d8511400f22641368 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 9 Jun 2020 19:48:06 +0200 +Subject: [PATCH 3/6] st/shadow: Check pipeline when painting + +We shouldn't simply assume that st_shadow_helper_update() has been +called before paint() or that the pipeline was created successfully. +--- + src/st/st-shadow.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/st/st-shadow.c b/src/st/st-shadow.c +index f3a22f034..7665de755 100644 +--- a/src/st/st-shadow.c ++++ b/src/st/st-shadow.c +@@ -289,9 +289,10 @@ st_shadow_helper_paint (StShadowHelper *helper, + ClutterActorBox *actor_box, + guint8 paint_opacity) + { +- _st_paint_shadow_with_opacity (helper->shadow, +- framebuffer, +- helper->pipeline, +- actor_box, +- paint_opacity); ++ if (helper->pipeline != NULL) ++ _st_paint_shadow_with_opacity (helper->shadow, ++ framebuffer, ++ helper->pipeline, ++ actor_box, ++ paint_opacity); + } +-- +2.33.1 + + +From b57d6efccbeb139d6c7c1894f83caa7a26fd6bad Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 5 Jan 2021 21:42:24 +0100 +Subject: [PATCH 4/6] viewSelector: Don't set page parent during construction + +gjs now aggressively garbage-collects objects that fall out of scope, +sometimes too aggressively: + + - we pass a child as construct property to StBin + - as a result, the child's ::parent-set handler runs + - when calling clutter_actor_get_parent() from that + handler, the returned object is garbage-collected + *before* the constructor returns (and thus the + assignment that would keep it alive) + +This is a bug on the gjs side that should be fixed, but we can easily +work around the issue by setting the child after constructing the +parent. +--- + js/ui/viewSelector.js | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js +index 77146552d..6529ac9a5 100644 +--- a/js/ui/viewSelector.js ++++ b/js/ui/viewSelector.js +@@ -301,11 +301,13 @@ var ViewSelector = class { + _addPage(actor, name, a11yIcon, params) { + params = Params.parse(params, { a11yFocus: null }); + +- let page = new St.Bin({ child: actor, +- x_align: St.Align.START, +- y_align: St.Align.START, +- x_fill: true, +- y_fill: true }); ++ let page = new St.Bin({ ++ x_align: St.Align.START, ++ y_align: St.Align.START, ++ x_fill: true, ++ y_fill: true, ++ }); ++ page.set_child(actor); + if (params.a11yFocus) + Main.ctrlAltTabManager.addGroup(params.a11yFocus, name, a11yIcon); + else +-- +2.33.1 + + +From 0c76c91c3d16c8386a242daf367d66057364a5d1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Oct 2020 23:44:48 +0200 +Subject: [PATCH 5/6] workspacesView: Don't set up MetaLater when unparented + +We already do the check in the later handler, but if we got +unparented because the actor is destroyed, then the call to +get_parent() itself will trigger a (harmless but annoying) +warning. +--- + js/ui/workspacesView.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index e302296a6..3270900b2 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -715,6 +715,9 @@ var WorkspacesDisplay = class { + oldParent.disconnect(this._notifyOpacityId); + this._notifyOpacityId = 0; + ++ if (!this.actor.get_parent()) ++ return; ++ + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + let newParent = this.actor.get_parent(); + if (!newParent) +-- +2.33.1 + + +From 4ba01f2fdada7e4b059a0f57a99dc3ff2ddfa8f8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 26 Nov 2021 17:28:54 +0100 +Subject: [PATCH 6/6] workspacesView: Remove later on destroy + +We are careful not to schedule the later when the actor is destroyed, +however it is possible that one is still pending at that point (namely +if the actor was never shown). +--- + js/ui/workspacesView.js | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index 3270900b2..9dc05fca7 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -449,6 +449,11 @@ var WorkspacesDisplay = class { + this.actor._delegate = this; + this.actor.connect('notify::allocation', this._updateWorkspacesActualGeometry.bind(this)); + this.actor.connect('parent-set', this._parentSet.bind(this)); ++ this.actor.connect('destroy', () => { ++ if (this._laterId) ++ Meta.later_remove(this._laterId); ++ this._laterId = 0; ++ }); + + let clickAction = new Clutter.ClickAction(); + clickAction.connect('clicked', action => { +@@ -718,7 +723,7 @@ var WorkspacesDisplay = class { + if (!this.actor.get_parent()) + return; + +- Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + let newParent = this.actor.get_parent(); + if (!newParent) + return; +-- +2.33.1 + diff --git a/SOURCES/gdm-networking.patch b/SOURCES/gdm-networking.patch new file mode 100644 index 0000000..4f5e2fc --- /dev/null +++ b/SOURCES/gdm-networking.patch @@ -0,0 +1,246 @@ +From ed0699886f49e5dd8d6ca9ffb60ba17cd76a810f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Jun 2021 17:49:57 +0200 +Subject: [PATCH 1/5] status/network: Disable modem connection when windows + aren't allowed + +The item launches the corresponding Settings panel when activated, which +doesn't work when windows are disabled by the session mode. Rather than +failing silently, turn the item insensitive. +--- + js/ui/status/network.js | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index b3bb7589c..3ad7b04dd 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -514,6 +514,10 @@ var NMDeviceModem = class extends NMConnectionDevice { + this._iconChanged(); + }); + } ++ ++ this._sessionUpdatedId = ++ Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); ++ this._sessionUpdated(); + } + + get category() { +@@ -525,6 +529,10 @@ var NMDeviceModem = class extends NMConnectionDevice { + 'connect-3g', this._device.get_path()]); + } + ++ _sessionUpdated() { ++ this._autoConnectItem.sensitive = Main.sessionMode.hasWindows; ++ } ++ + destroy() { + if (this._operatorNameId) { + this._mobileDevice.disconnect(this._operatorNameId); +@@ -534,6 +542,10 @@ var NMDeviceModem = class extends NMConnectionDevice { + this._mobileDevice.disconnect(this._signalQualityId); + this._signalQualityId = 0; + } ++ if (this._sessionUpdatedId) { ++ Main.sessionMode.disconnect(this._sessionUpdatedId); ++ this._sessionUpdatedId = 0; ++ } + + super.destroy(); + } +-- +2.31.1 + + +From 59d52e1591e1522fff22320c657496ca978a7926 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Jun 2021 18:28:32 +0200 +Subject: [PATCH 2/5] status/network: Only list wifi networks that can be + activated + +Setting up a connection for an Enterprise WPA(2) encrypted wireless +network requires Settings. That's not available when windows are +disabled via the session mode, so filter out affected entries. +--- + js/ui/status/network.js | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index 3ad7b04dd..c023022a7 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -1,5 +1,5 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +-const { Clutter, Gio, GLib, GObject, NM, St } = imports.gi; ++const { Clutter, Gio, GLib, GObject, Meta, NM, St } = imports.gi; + const Mainloop = imports.mainloop; + const Signals = imports.signals; + +@@ -751,6 +751,11 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog { + this._scanTimeoutId = 0; + } + ++ if (this._syncVisibilityId) { ++ Meta.later_remove(this._syncVisibilityId); ++ this._syncVisibilityId = 0; ++ } ++ + super.destroy(); + } + +@@ -1081,9 +1086,31 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog { + this._itemBox.insert_child_at_index(network.item.actor, newPos); + } + ++ this._queueSyncItemVisibility(); + this._syncView(); + } + ++ _queueSyncItemVisibility() { ++ if (this._syncVisibilityId) ++ return; ++ ++ this._syncVisibilityId = Meta.later_add( ++ Meta.LaterType.BEFORE_REDRAW, ++ () => { ++ const { hasWindows } = Main.sessionMode; ++ const { WPA2_ENT, WPA_ENT } = NMAccessPointSecurity; ++ ++ for (const network of this._networks) { ++ const [firstAp] = network.accessPoints; ++ network.item.visible = ++ hasWindows || ++ network.connections.length > 0 || ++ (firstAp._secType !== WPA2_ENT && firstAp._secType !== WPA_ENT); ++ } ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ + _accessPointRemoved(device, accessPoint) { + let res = this._findExistingNetwork(accessPoint); + +-- +2.31.1 + + +From 9d204cdb38bcfee214dbe0b0bf9c2073dc50fe93 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 8 Jun 2021 00:17:48 +0200 +Subject: [PATCH 3/5] status/network: Consider network-control action + +NetworkManager installs a `network-control` polkit action that can +be used to disallow network configuration, except that we happily +ignore it. Add it to the conditions that turn a network section +insensitive. +--- + js/ui/status/network.js | 20 +++++++++++++++++--- + 1 file changed, 17 insertions(+), 3 deletions(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index c023022a7..79729e01b 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -1,5 +1,5 @@ + // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +-const { Clutter, Gio, GLib, GObject, Meta, NM, St } = imports.gi; ++const { Clutter, Gio, GLib, GObject, Meta, NM, Polkit, St } = imports.gi; + const Mainloop = imports.mainloop; + const Signals = imports.signals; + +@@ -1683,11 +1683,25 @@ var NMApplet = class extends PanelMenu.SystemIndicator { + this._client.connect('connection-removed', this._connectionRemoved.bind(this)); + + Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); +- this._sessionUpdated(); ++ ++ this._configPermission = null; ++ Polkit.Permission.new( ++ 'org.freedesktop.NetworkManager.network-control', null, null, ++ (o, res) => { ++ try { ++ this._configPermission = Polkit.Permission.new_finish(res); ++ } catch (e) { ++ log('No permission to control network connections: %s'.format(e.toString())); ++ } ++ this._sessionUpdated(); ++ }); + } + + _sessionUpdated() { +- let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; ++ const sensitive = ++ !Main.sessionMode.isLocked && ++ !Main.sessionMode.isGreeter && ++ this._configPermission && this._configPermission.allowed; + this.menu.setSensitive(sensitive); + } + +-- +2.31.1 + + +From 7d2c8aabb86b9942c99ae9b7157dbffb875acde9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 10 Jun 2021 23:12:27 +0200 +Subject: [PATCH 4/5] sessionMode: Enable networkAgent on login screen + +We will soon enable the network sections in the status menu on the +login screen, so enable the network agent to handle authentication +requests (like wifi/VPN passwords). +--- + js/ui/sessionMode.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js +index 25aa75a3d..fa7f83416 100644 +--- a/js/ui/sessionMode.js ++++ b/js/ui/sessionMode.js +@@ -43,7 +43,9 @@ const _modes = { + isGreeter: true, + isPrimary: true, + unlockDialog: imports.gdm.loginDialog.LoginDialog, +- components: ['polkitAgent'], ++ components: Config.HAVE_NETWORKMANAGER ++ ? ['networkAgent', 'polkitAgent'] ++ : ['polkitAgent'], + panel: { + left: [], + center: ['dateMenu'], +-- +2.31.1 + + +From 07ce899bcb9d30991262d6c484508e6c5fa14c85 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 8 Jun 2021 00:19:26 +0200 +Subject: [PATCH 5/5] status/network: Do not disable on login screen + +We currently disable all network items on both the lock- and login +screen. While it makes sense to be very restrictive on the lock screen, +there are some (fringe) use cases for being more permissive on the +login screen (like remote home directories only accessible via VPN). + +There's precedence with the power-off/restart actions to be less +restrictive on the login screen, and since we started respecting +the `network-control` polkit action, it's possible to restore the +old behavior if desired. +--- + js/ui/status/network.js | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/js/ui/status/network.js b/js/ui/status/network.js +index 79729e01b..914dbbd99 100644 +--- a/js/ui/status/network.js ++++ b/js/ui/status/network.js +@@ -1700,7 +1700,6 @@ var NMApplet = class extends PanelMenu.SystemIndicator { + _sessionUpdated() { + const sensitive = + !Main.sessionMode.isLocked && +- !Main.sessionMode.isGreeter && + this._configPermission && this._configPermission.allowed; + this.menu.setSensitive(sensitive); + } +-- +2.31.1 + diff --git a/SOURCES/gnome-shell-favourite-apps-firefox.patch b/SOURCES/gnome-shell-favourite-apps-firefox.patch new file mode 100644 index 0000000..888491e --- /dev/null +++ b/SOURCES/gnome-shell-favourite-apps-firefox.patch @@ -0,0 +1,38 @@ +From 87104647f061892525236a71f304b63609960626 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 9 Mar 2017 14:43:30 +0100 +Subject: [PATCH] appFavorites: Make firefox the default browser + +--- + data/org.gnome.shell.gschema.xml.in | 2 +- + js/ui/appFavorites.js | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in +index 24e2a75b0..2f50036d0 100644 +--- a/data/org.gnome.shell.gschema.xml.in ++++ b/data/org.gnome.shell.gschema.xml.in +@@ -39,7 +39,7 @@ + + + +- [ 'epiphany.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ] ++ [ 'firefox.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ] + List of desktop file IDs for favorite applications + + The applications corresponding to these identifiers +diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js +index 657e15965..1e44a1655 100644 +--- a/js/ui/appFavorites.js ++++ b/js/ui/appFavorites.js +@@ -49,6 +49,7 @@ const RENAMED_DESKTOP_IDS = { + 'gnotski.desktop': 'org.gnome.Klotski.desktop', + 'gtali.desktop': 'org.gnome.Tali.desktop', + 'iagno.desktop': 'org.gnome.Reversi.desktop', ++ 'mozilla-firefox.desktop': 'firefox.desktop', + 'nautilus.desktop': 'org.gnome.Nautilus.desktop', + 'org.gnome.gnome-2048.desktop': 'org.gnome.TwentyFortyEight.desktop', + 'org.gnome.taquin.desktop': 'org.gnome.Taquin.desktop', +-- +2.21.0 + diff --git a/SOURCES/gnome-shell-favourite-apps-terminal.patch b/SOURCES/gnome-shell-favourite-apps-terminal.patch new file mode 100644 index 0000000..d3f0377 --- /dev/null +++ b/SOURCES/gnome-shell-favourite-apps-terminal.patch @@ -0,0 +1,25 @@ +From d15a92aeaa075230f711921f4bcd929c49bfc97d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 9 Mar 2017 14:44:32 +0100 +Subject: [PATCH] appFavorites: Add terminal + +--- + data/org.gnome.shell.gschema.xml.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in +index 40526187e..9d7e011fc 100644 +--- a/data/org.gnome.shell.gschema.xml.in ++++ b/data/org.gnome.shell.gschema.xml.in +@@ -39,7 +39,7 @@ + + + +- [ 'firefox.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop', 'yelp.desktop' ] ++ [ 'firefox.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop', 'yelp.desktop', 'gnome-terminal.desktop' ] + List of desktop file IDs for favorite applications + + The applications corresponding to these identifiers +-- +2.21.0 + diff --git a/SOURCES/gnome-shell-favourite-apps-yelp.patch b/SOURCES/gnome-shell-favourite-apps-yelp.patch new file mode 100644 index 0000000..d96597a --- /dev/null +++ b/SOURCES/gnome-shell-favourite-apps-yelp.patch @@ -0,0 +1,26 @@ +From 53eba56c29c2c3f25bdfc4b73d1b9ce74ce2504b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 9 Mar 2017 14:44:03 +0100 +Subject: [PATCH] Add 'yelp' to default favorites + +Help should be easily available, so add it to the default favorites. +--- + data/org.gnome.shell.gschema.xml.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in +index 2f50036d0..40526187e 100644 +--- a/data/org.gnome.shell.gschema.xml.in ++++ b/data/org.gnome.shell.gschema.xml.in +@@ -39,7 +39,7 @@ + + + +- [ 'firefox.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ] ++ [ 'firefox.desktop', 'evolution.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop', 'yelp.desktop' ] + List of desktop file IDs for favorite applications + + The applications corresponding to these identifiers +-- +2.21.0 + diff --git a/SOURCES/horizontal-workspace-support.patch b/SOURCES/horizontal-workspace-support.patch new file mode 100644 index 0000000..8605e1d --- /dev/null +++ b/SOURCES/horizontal-workspace-support.patch @@ -0,0 +1,399 @@ +From b42dd3f87ad5fb6c7ee139cb0de22e0fbb393ba2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 4 Jun 2019 19:22:26 +0000 +Subject: [PATCH 1/2] workspaceSwitcherPopup: Support horizontal layout + +While mutter supports a variety of different grid layouts (n columns/rows, +growing vertically or horizontally from any of the four corners), we +hardcode a fixed vertical layout of a single column. + +Now that mutter exposes the actual layout to us, add support for a more +traditional horizontal layout as well. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/575 +--- + data/theme/gnome-shell-sass/_common.scss | 3 +- + js/ui/windowManager.js | 36 ++++++++-- + js/ui/workspaceSwitcherPopup.js | 86 ++++++++++++++++++------ + 3 files changed, 98 insertions(+), 27 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 293ea2ab9..b1eeb0ce9 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -680,7 +680,8 @@ StScrollBar { + spacing: 8px; + } + +- .ws-switcher-active-up, .ws-switcher-active-down { ++ .ws-switcher-active-up, .ws-switcher-active-down, ++ .ws-switcher-active-left, .ws-switcher-active-right { + height: 50px; + background-color: $selected_bg_color; + color: $selected_fg_color; +diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js +index b9f5fef46..dfe1b4460 100644 +--- a/js/ui/windowManager.js ++++ b/js/ui/windowManager.js +@@ -2145,6 +2145,8 @@ var WindowManager = class { + let [action,,,target] = binding.get_name().split('-'); + let newWs; + let direction; ++ let vertical = workspaceManager.layout_rows == -1; ++ let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + + if (action == 'move') { + // "Moving" a window to another workspace doesn't make sense when +@@ -2157,7 +2159,12 @@ var WindowManager = class { + } + + if (target == 'last') { +- direction = Meta.MotionDirection.DOWN; ++ if (vertical) ++ direction = Meta.MotionDirection.DOWN; ++ else if (rtl) ++ direction = Meta.MotionDirection.LEFT; ++ else ++ direction = Meta.MotionDirection.RIGHT; + newWs = workspaceManager.get_workspace_by_index(workspaceManager.n_workspaces - 1); + } else if (isNaN(target)) { + // Prepend a new workspace dynamically +@@ -2173,16 +2180,33 @@ var WindowManager = class { + target--; + newWs = workspaceManager.get_workspace_by_index(target); + +- if (workspaceManager.get_active_workspace().index() > target) +- direction = Meta.MotionDirection.UP; +- else +- direction = Meta.MotionDirection.DOWN; ++ if (workspaceManager.get_active_workspace().index() > target) { ++ if (vertical) ++ direction = Meta.MotionDirection.UP; ++ else if (rtl) ++ direction = Meta.MotionDirection.RIGHT; ++ else ++ direction = Meta.MotionDirection.LEFT; ++ } else { ++ if (vertical) ++ direction = Meta.MotionDirection.DOWN; ++ else if (rtl) ++ direction = Meta.MotionDirection.LEFT; ++ else ++ direction = Meta.MotionDirection.RIGHT; ++ } + } + +- if (direction != Meta.MotionDirection.UP && ++ if (workspaceManager.layout_rows == -1 && ++ direction != Meta.MotionDirection.UP && + direction != Meta.MotionDirection.DOWN) + return; + ++ if (workspaceManager.layout_columns == -1 && ++ direction != Meta.MotionDirection.LEFT && ++ direction != Meta.MotionDirection.RIGHT) ++ return; ++ + if (action == 'switch') + this.actionMoveWorkspace(newWs); + else +diff --git a/js/ui/workspaceSwitcherPopup.js b/js/ui/workspaceSwitcherPopup.js +index 26404eaab..d21c5de4d 100644 +--- a/js/ui/workspaceSwitcherPopup.js ++++ b/js/ui/workspaceSwitcherPopup.js +@@ -17,41 +17,75 @@ class WorkspaceSwitcherPopupList extends St.Widget { + this._itemSpacing = 0; + this._childHeight = 0; + this._childWidth = 0; ++ this._orientation = global.workspace_manager.layout_rows == -1 ++ ? Clutter.Orientation.VERTICAL ++ : Clutter.Orientation.HORIZONTAL; + + this.connect('style-changed', () => { + this._itemSpacing = this.get_theme_node().get_length('spacing'); + }); + } + +- vfunc_get_preferred_height(forWidth) { ++ _getPreferredSizeForOrientation(forSize) { + let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); + let themeNode = this.get_theme_node(); + +- let availHeight = workArea.height; +- availHeight -= themeNode.get_vertical_padding(); ++ let availSize; ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) ++ availSize = workArea.width - themeNode.get_horizontal_padding(); ++ else ++ availSize = workArea.height - themeNode.get_vertical_padding(); + +- let height = 0; ++ let size = 0; + for (let child of this.get_children()) { + let [childMinHeight, childNaturalHeight] = child.get_preferred_height(-1); +- let [childMinWidth, childNaturalWidth] = child.get_preferred_width(childNaturalHeight); +- height += childNaturalHeight * workArea.width / workArea.height; ++ let height = childNaturalHeight * workArea.width / workArea.height; ++ ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) { ++ size += height * workArea.width / workArea.height; ++ } else { ++ size += height; ++ } + } + + let workspaceManager = global.workspace_manager; + let spacing = this._itemSpacing * (workspaceManager.n_workspaces - 1); +- height += spacing; +- height = Math.min(height, availHeight); ++ size += spacing; ++ size = Math.min(size, availSize); ++ ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) { ++ this._childWidth = (size - spacing) / workspaceManager.n_workspaces; ++ return themeNode.adjust_preferred_width(size, size); ++ } else { ++ this._childHeight = (size - spacing) / workspaceManager.n_workspaces; ++ return themeNode.adjust_preferred_height(size, size); ++ } ++ } ++ ++ _getSizeForOppositeOrientation() { ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); + +- this._childHeight = (height - spacing) / workspaceManager.n_workspaces; ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) { ++ this._childHeight = Math.round(this._childWidth * workArea.height / workArea.width); ++ return [this._childHeight, this._childHeight]; ++ } else { ++ this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height); ++ return [this._childWidth, this._childWidth]; ++ } ++ } + +- return themeNode.adjust_preferred_height(height, height); ++ vfunc_get_preferred_height(forWidth) { ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) ++ return this._getSizeForOppositeOrientation(); ++ else ++ return this._getPreferredSizeForOrientation(forWidth); + } + + vfunc_get_preferred_width(forHeight) { +- let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); +- this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height); +- +- return [this._childWidth, this._childWidth]; ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) ++ return this._getPreferredSizeForOrientation(forHeight); ++ else ++ return this._getSizeForOppositeOrientation(); + } + + vfunc_allocate(box, flags) { +@@ -62,15 +96,23 @@ class WorkspaceSwitcherPopupList extends St.Widget { + + let childBox = new Clutter.ActorBox(); + ++ let rtl = this.text_direction == Clutter.TextDirection.RTL; ++ let x = rtl ? box.x2 - this._childWidth : box.x1; + let y = box.y1; +- let prevChildBoxY2 = box.y1 - this._itemSpacing; + for (let child of this.get_children()) { +- childBox.x1 = box.x1; +- childBox.x2 = box.x1 + this._childWidth; +- childBox.y1 = prevChildBoxY2 + this._itemSpacing; ++ childBox.x1 = Math.round(x); ++ childBox.x2 = Math.round(x + this._childWidth); ++ childBox.y1 = Math.round(y); + childBox.y2 = Math.round(y + this._childHeight); +- y += this._childHeight + this._itemSpacing; +- prevChildBoxY2 = childBox.y2; ++ ++ if (this._orientation == Clutter.Orientation.HORIZONTAL) { ++ if (rtl) ++ x -= this._childWidth + this._itemSpacing; ++ else ++ x += this._childWidth + this._itemSpacing; ++ } else { ++ y += this._childHeight + this._itemSpacing; ++ } + child.allocate(childBox, flags); + } + } +@@ -123,6 +165,10 @@ class WorkspaceSwitcherPopup extends St.Widget { + indicator = new St.Bin({ style_class: 'ws-switcher-active-up' }); + else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.DOWN) + indicator = new St.Bin({ style_class: 'ws-switcher-active-down' }); ++ else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.LEFT) ++ indicator = new St.Bin({ style_class: 'ws-switcher-active-left' }); ++ else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.RIGHT) ++ indicator = new St.Bin({ style_class: 'ws-switcher-active-right' }); + else + indicator = new St.Bin({ style_class: 'ws-switcher-box' }); + +-- +2.21.0 + + +From 813976ff69b15ab884d44f5f6a56ae66f407acfd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 4 Jun 2019 19:49:23 +0000 +Subject: [PATCH 2/2] workspacesView: Support horizontal layout + +Just as we did for the workspace switcher popup, support workspaces +being laid out in a single row in the window picker. + +Note that this takes care of the various workspace switch actions in +the overview (scrolling, panning, touch(pad) gestures) as well as the +switch animation, but not of the overview's workspace switcher component. + +There are currently no plans to support other layouts there, as the +component is inherently vertical (in fact, it was the whole reason for +switching the layout in the first place). + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/575 +--- + js/ui/workspacesView.js | 81 ++++++++++++++++++++++++++++++----------- + 1 file changed, 60 insertions(+), 21 deletions(-) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index fe06d9dae..069937d5a 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -181,26 +181,32 @@ var WorkspacesView = class extends WorkspacesViewBase { + + Tweener.removeTweens(workspace.actor); + +- let y = (w - active) * this._fullGeometry.height; ++ let params = {}; ++ if (workspaceManager.layout_rows == -1) ++ params.y = (w - active) * this._fullGeometry.height; ++ else if (this.actor.text_direction == Clutter.TextDirection.RTL) ++ params.x = (active - w) * this._fullGeometry.width; ++ else ++ params.x = (w - active) * this._fullGeometry.width; + + if (showAnimation) { +- let params = { y: y, +- time: WORKSPACE_SWITCH_TIME, +- transition: 'easeOutQuad' +- }; ++ let tweenParams = Object.assign(params, { ++ time: WORKSPACE_SWITCH_TIME, ++ transition: 'easeOutQuad' ++ }); + // we have to call _updateVisibility() once before the + // animation and once afterwards - it does not really + // matter which tween we use, so we pick the first one ... + if (w == 0) { + this._updateVisibility(); +- params.onComplete = () => { ++ tweenParams.onComplete = () => { + this._animating = false; + this._updateVisibility(); + }; + } +- Tweener.addTween(workspace.actor, params); ++ Tweener.addTween(workspace.actor, tweenParams); + } else { +- workspace.actor.set_position(0, y); ++ workspace.actor.set(params); + if (w == 0) + this._updateVisibility(); + } +@@ -338,22 +344,39 @@ var WorkspacesView = class extends WorkspacesViewBase { + metaWorkspace.activate(global.get_current_time()); + } + +- let last = this._workspaces.length - 1; +- let firstWorkspaceY = this._workspaces[0].actor.y; +- let lastWorkspaceY = this._workspaces[last].actor.y; +- let workspacesHeight = lastWorkspaceY - firstWorkspaceY; +- + if (adj.upper == 1) + return; + +- let currentY = firstWorkspaceY; +- let newY = - adj.value / (adj.upper - 1) * workspacesHeight; ++ let last = this._workspaces.length - 1; ++ ++ if (workspaceManager.layout_rows == -1) { ++ let firstWorkspaceY = this._workspaces[0].actor.y; ++ let lastWorkspaceY = this._workspaces[last].actor.y; ++ let workspacesHeight = lastWorkspaceY - firstWorkspaceY; ++ ++ let currentY = firstWorkspaceY; ++ let newY = -adj.value / (adj.upper - 1) * workspacesHeight; + +- let dy = newY - currentY; ++ let dy = newY - currentY; ++ ++ for (let i = 0; i < this._workspaces.length; i++) { ++ this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1; ++ this._workspaces[i].actor.y += dy; ++ } ++ } else { ++ let firstWorkspaceX = this._workspaces[0].actor.x; ++ let lastWorkspaceX = this._workspaces[last].actor.x; ++ let workspacesWidth = lastWorkspaceX - firstWorkspaceX; + +- for (let i = 0; i < this._workspaces.length; i++) { +- this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1; +- this._workspaces[i].actor.y += dy; ++ let currentX = firstWorkspaceX; ++ let newX = -adj.value / (adj.upper - 1) * workspacesWidth; ++ ++ let dx = newX - currentX; ++ ++ for (let i = 0; i < this._workspaces.length; i++) { ++ this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1; ++ this._workspaces[i].actor.x += dx; ++ } + } + } + }; +@@ -504,7 +527,12 @@ var WorkspacesDisplay = class { + _onPan(action) { + let [dist, dx, dy] = action.get_motion_delta(0); + let adjustment = this._scrollAdjustment; +- adjustment.value -= (dy / this.actor.height) * adjustment.page_size; ++ if (global.workspace_manager.layout_rows == -1) ++ adjustment.value -= (dy / this.actor.height) * adjustment.page_size; ++ else if (this.actor.text_direction == Clutter.TextDirection.RTL) ++ adjustment.value += (dx / this.actor.width) * adjustment.page_size; ++ else ++ adjustment.value -= (dx / this.actor.width) * adjustment.page_size; + return false; + } + +@@ -536,7 +564,12 @@ var WorkspacesDisplay = class { + let workspaceManager = global.workspace_manager; + let active = workspaceManager.get_active_workspace_index(); + let adjustment = this._scrollAdjustment; +- adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size; ++ if (workspaceManager.layout_rows == -1) ++ adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size; ++ else if (this.actor.text_direction == Clutter.TextDirection.RTL) ++ adjustment.value = (active + xRel / this.actor.width) * adjustment.page_size; ++ else ++ adjustment.value = (active - xRel / this.actor.width) * adjustment.page_size; + } + + _onSwitchWorkspaceActivated(action, direction) { +@@ -755,6 +788,12 @@ var WorkspacesDisplay = class { + case Clutter.ScrollDirection.DOWN: + ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN); + break; ++ case Clutter.ScrollDirection.LEFT: ++ ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT); ++ break; ++ case Clutter.ScrollDirection.RIGHT: ++ ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT); ++ break; + default: + return Clutter.EVENT_PROPAGATE; + } +-- +2.21.0 + diff --git a/SOURCES/introspect-backports.patch b/SOURCES/introspect-backports.patch new file mode 100644 index 0000000..b93532c --- /dev/null +++ b/SOURCES/introspect-backports.patch @@ -0,0 +1,643 @@ +From 781dfcf6ce7168c6b116d58df5f1c67291a7b513 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 16 May 2019 00:57:27 +0200 +Subject: [PATCH 01/11] introspect: Include `sandboxed-app-id` as well + +App IDs in gnome-shell don't match AppStream, Flatpak or Snap IDs. For the +desktop portal, the latter two are more relevant, so include it in the +returned information. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1289 +--- + js/misc/introspect.js | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index f7a7f2fe6..1e8300d0a 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -55,6 +55,11 @@ var IntrospectService = class { + return APP_WHITELIST.includes(sender); + } + ++ _getSandboxedAppId(app) { ++ let ids = app.get_windows().map(w => w.get_sandboxed_app_id()); ++ return ids.find(id => id != null); ++ } ++ + _syncRunningApplications() { + let tracker = Shell.WindowTracker.get_default(); + let apps = this._appSystem.get_running(); +@@ -76,6 +81,10 @@ var IntrospectService = class { + newActiveApplication = app.get_id(); + } + ++ let sandboxedAppId = this._getSandboxedAppId(app); ++ if (sandboxedAppId) ++ appInfo['sandboxed-app-id'] = new GLib.Variant('s', sandboxedAppId); ++ + newRunningApplications[app.get_id()] = appInfo; + } + +@@ -137,6 +146,7 @@ var IntrospectService = class { + let frameRect = window.get_frame_rect(); + let title = window.get_title(); + let wmClass = window.get_wm_class(); ++ let sandboxedAppId = window.get_sandboxed_app_id(); + + windowsList[windowId] = { + 'app-id': GLib.Variant.new('s', app.get_id()), +@@ -153,6 +163,10 @@ var IntrospectService = class { + + if (wmClass != null) + windowsList[windowId]['wm-class'] = GLib.Variant.new('s', wmClass); ++ ++ if (sandboxedAppId != null) ++ windowsList[windowId]['sandboxed-app-id'] = ++ GLib.Variant.new('s', sandboxedAppId); + } + } + invocation.return_value(new GLib.Variant('(a{ta{sv}})', [windowsList])); +-- +2.26.2 + + +From b0b4fb82c058722e2171d24902ba3855ffe243f3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 18 Sep 2019 14:57:48 +0200 +Subject: [PATCH 02/11] introspect: Check whitelist also for + GetRunningWindows() + +Otherwise the xdg-desktop-portal-gtk screen cast widget won't work. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/732 +--- + js/misc/introspect.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index 1e8300d0a..cee6409a8 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -128,7 +128,8 @@ var IntrospectService = class { + let apps = this._appSystem.get_running(); + let windowsList = {}; + +- if (!this._isIntrospectEnabled()) { ++ if (!this._isIntrospectEnabled() && ++ !this._isSenderWhitelisted(invocation.get_sender())) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'App introspection not allowed'); +-- +2.26.2 + + +From 23556e03db3743ddf478a3c1bbb64946c687afdf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 25 Nov 2019 19:44:10 +0100 +Subject: [PATCH 03/11] introspect: Fix whitelist check + +The whitelist is a list of well-known D-Bus names, which we then search +for the unique name we get from the method invocation - unsuccesfully. + +Fix this by watching the bus for any name in the whitelist in order +to maintain a map from wel-known to unique name that we can use for +matching. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/1916 +--- + js/misc/introspect.js | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index cee6409a8..f14eabfad 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -39,6 +39,15 @@ var IntrospectService = class { + }); + + this._syncRunningApplications(); ++ ++ this._whitelistMap = new Map(); ++ APP_WHITELIST.forEach(appName => { ++ Gio.DBus.watch_name(Gio.BusType.SESSION, ++ appName, ++ Gio.BusNameWatcherFlags.NONE, ++ (conn, name, owner) => this._whitelistMap.set(name, owner), ++ (conn, name) => this._whitelistMap.delete(name)); ++ }); + } + + _isStandaloneApp(app) { +@@ -52,7 +61,7 @@ var IntrospectService = class { + } + + _isSenderWhitelisted(sender) { +- return APP_WHITELIST.includes(sender); ++ return [...this._whitelistMap.values()].includes(sender); + } + + _getSandboxedAppId(app) { +-- +2.26.2 + + +From 1a6275add6d214df958ed8a06c097445bef021bc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 25 Sep 2019 20:36:28 +0200 +Subject: [PATCH 04/11] introspect: Add helper to check method call permission + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + js/misc/introspect.js | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index f14eabfad..6186754cd 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -120,9 +120,18 @@ var IntrospectService = class { + type == Meta.WindowType.UTILITY); + } + ++ _isInvocationAllowed(invocation) { ++ if (this._isIntrospectEnabled()) ++ return true; ++ ++ if (this._isSenderWhitelisted(invocation.get_sender())) ++ return true; ++ ++ return false; ++ } ++ + GetRunningApplicationsAsync(params, invocation) { +- if (!this._isIntrospectEnabled() && +- !this._isSenderWhitelisted(invocation.get_sender())) { ++ if (!this._isInvocationAllowed(invocation)) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'App introspection not allowed'); +@@ -137,8 +146,7 @@ var IntrospectService = class { + let apps = this._appSystem.get_running(); + let windowsList = {}; + +- if (!this._isIntrospectEnabled() && +- !this._isSenderWhitelisted(invocation.get_sender())) { ++ if (!this._isInvocationAllowed(invocation)) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'App introspection not allowed'); +-- +2.26.2 + + +From f578dc01cf774faa4504a4d258cc0e82060d988b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 11:55:33 +0200 +Subject: [PATCH 05/11] shell-util: Add API to check for X11 extensions + +Will be used to disable animations when running inside Xvnc. This was +done in gsd-xsettings before. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + src/shell-util.c | 26 ++++++++++++++++++++++++++ + src/shell-util.h | 3 +++ + 2 files changed, 29 insertions(+) + +diff --git a/src/shell-util.c b/src/shell-util.c +index 31bb18e70..fa3fc08c8 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -21,6 +21,8 @@ + #include + #include + #include ++#include ++#include + + #include + #ifdef HAVE__NL_TIME_FIRST_WEEKDAY +@@ -613,3 +615,27 @@ shell_util_check_cloexec_fds (void) + fdwalk (check_cloexec, NULL); + g_info ("Open fd CLOEXEC check complete"); + } ++ ++/** ++ * shell_util_has_x11_display_extension: ++ * @display: A #MetaDisplay ++ * @extension: An X11 extension ++ * ++ * If the corresponding X11 display provides the passed extension, return %TRUE, ++ * otherwise %FALSE. If there is no X11 display, %FALSE is passed. ++ */ ++gboolean ++shell_util_has_x11_display_extension (MetaDisplay *display, ++ const char *extension) ++{ ++ MetaX11Display *x11_display; ++ Display *xdisplay; ++ int op, event, error; ++ ++ x11_display = meta_display_get_x11_display (display); ++ if (!x11_display) ++ return FALSE; ++ ++ xdisplay = meta_x11_display_get_xdisplay (x11_display); ++ return XQueryExtension (xdisplay, extension, &op, &event, &error); ++} +diff --git a/src/shell-util.h b/src/shell-util.h +index 6904f43bc..02b8404e9 100644 +--- a/src/shell-util.h ++++ b/src/shell-util.h +@@ -59,6 +59,9 @@ cairo_surface_t * shell_util_composite_capture_images (ClutterCapture *captures + + void shell_util_check_cloexec_fds (void); + ++gboolean shell_util_has_x11_display_extension (MetaDisplay *display, ++ const char *extension); ++ + G_END_DECLS + + #endif /* __SHELL_UTIL_H__ */ +-- +2.26.2 + + +From 48ee79bb7b48c7e93e77e35629f21bbdbabc253f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 11:56:34 +0200 +Subject: [PATCH 06/11] st/settings: Add API to inhibit animations + +There may be situations where we shouldn't enable animations. Make it +possible for the Shell to decide when there are such situations and in +when needed inhibit animations. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + src/st/st-settings.c | 38 +++++++++++++++++++++++++++++++++++++- + src/st/st-settings.h | 4 ++++ + 2 files changed, 41 insertions(+), 1 deletion(-) + +diff --git a/src/st/st-settings.c b/src/st/st-settings.c +index 17f2c466e..ebfd28480 100644 +--- a/src/st/st-settings.c ++++ b/src/st/st-settings.c +@@ -54,6 +54,7 @@ struct _StSettings + + gchar *gtk_theme; + gchar *gtk_icon_theme; ++ int inhibit_animations_count; + gboolean enable_animations; + gboolean primary_paste; + gboolean magnifier_active; +@@ -62,6 +63,41 @@ struct _StSettings + + G_DEFINE_TYPE (StSettings, st_settings, G_TYPE_OBJECT) + ++static gboolean ++get_enable_animations (StSettings *settings) ++{ ++ if (settings->inhibit_animations_count > 0) ++ return FALSE; ++ else ++ return settings->enable_animations; ++} ++ ++void ++st_settings_inhibit_animations (StSettings *settings) ++{ ++ gboolean enable_animations; ++ ++ enable_animations = get_enable_animations (settings); ++ settings->inhibit_animations_count++; ++ ++ if (enable_animations != get_enable_animations (settings)) ++ g_object_notify_by_pspec (G_OBJECT (settings), ++ props[PROP_ENABLE_ANIMATIONS]); ++} ++ ++void ++st_settings_uninhibit_animations (StSettings *settings) ++{ ++ gboolean enable_animations; ++ ++ enable_animations = get_enable_animations (settings); ++ settings->inhibit_animations_count--; ++ ++ if (enable_animations != get_enable_animations (settings)) ++ g_object_notify_by_pspec (G_OBJECT (settings), ++ props[PROP_ENABLE_ANIMATIONS]); ++} ++ + static void + st_settings_finalize (GObject *object) + { +@@ -95,7 +131,7 @@ st_settings_get_property (GObject *object, + switch (prop_id) + { + case PROP_ENABLE_ANIMATIONS: +- g_value_set_boolean (value, settings->enable_animations); ++ g_value_set_boolean (value, get_enable_animations (settings)); + break; + case PROP_PRIMARY_PASTE: + g_value_set_boolean (value, settings->primary_paste); +diff --git a/src/st/st-settings.h b/src/st/st-settings.h +index c2c4fa23e..8b2549469 100644 +--- a/src/st/st-settings.h ++++ b/src/st/st-settings.h +@@ -33,6 +33,10 @@ G_DECLARE_FINAL_TYPE (StSettings, st_settings, ST, SETTINGS, GObject) + + StSettings * st_settings_get (void); + ++void st_settings_inhibit_animations (StSettings *settings); ++ ++void st_settings_uninhibit_animations (StSettings *settings); ++ + G_END_DECLS + + #endif /* __ST_SETTINGS_H__ */ +-- +2.26.2 + + +From 80025388c44296b629c8f24ea673d77ffc4efc67 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 12:02:31 +0200 +Subject: [PATCH 07/11] main: Inhibit animations when software rendered + +This was previously decided by gsd-xsettings. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + js/ui/main.js | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index 978f83c3f..c3230ff03 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -147,6 +147,8 @@ function _initializeUI() { + _loadOskLayouts(); + _loadDefaultStylesheet(); + ++ new AnimationsSettings(); ++ + // Setup the stage hierarchy early + layoutManager = new Layout.LayoutManager(); + +@@ -723,3 +725,13 @@ function showRestartMessage(message) { + let restartMessage = new RestartMessage(message); + restartMessage.open(); + } ++ ++var AnimationsSettings = class { ++ constructor() { ++ let backend = Meta.get_backend(); ++ if (!backend.is_rendering_hardware_accelerated()) { ++ St.Settings.get().inhibit_animations(); ++ return; ++ } ++ } ++}; +-- +2.26.2 + + +From 788ecb60e35d8a369f0747813f37e8b1ca27cb87 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 12:03:52 +0200 +Subject: [PATCH 08/11] main: Inhibit animations if X server advertises + VNC-EXTENSION + +This was previously done by gsd-xsettings to disable animations when +running in Xvnc. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + js/ui/main.js | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index c3230ff03..ae7c3ffd0 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -733,5 +733,12 @@ var AnimationsSettings = class { + St.Settings.get().inhibit_animations(); + return; + } ++ ++ let isXvnc = Shell.util_has_x11_display_extension( ++ global.display, 'VNC-EXTENSION'); ++ if (isXvnc) { ++ St.Settings.get().inhibit_animations(); ++ return; ++ } + } + }; +-- +2.26.2 + + +From 1da5a7ce4cf0b95b96dd50b62ac6c1380fd88cb1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 12:04:52 +0200 +Subject: [PATCH 09/11] main: Inhibit animations when there is a remote desktop + session + +If a remote desktop session asks for animations to be disabled, inhibit +animations while the session is active. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + js/ui/main.js | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index ae7c3ffd0..1203b3c39 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -740,5 +740,31 @@ var AnimationsSettings = class { + St.Settings.get().inhibit_animations(); + return; + } ++ ++ let remoteAccessController = backend.get_remote_access_controller(); ++ if (!remoteAccessController) ++ return; ++ ++ this._handles = new Set(); ++ remoteAccessController.connect('new-handle', ++ (_, handle) => this._onNewRemoteAccessHandle(handle)); ++ } ++ ++ _onRemoteAccessHandleStopped(handle) { ++ let settings = St.Settings.get(); ++ ++ settings.uninhibit_animations(); ++ this._handles.delete(handle); ++ } ++ ++ _onNewRemoteAccessHandle(handle) { ++ if (!handle.get_disable_animations()) ++ return; ++ ++ let settings = St.Settings.get(); ++ ++ settings.inhibit_animations(); ++ this._handles.add(handle); ++ handle.connect('stopped', this._onRemoteAccessHandleStopped.bind(this)); + } + }; +-- +2.26.2 + + +From ebfd46341a2d7a6338386e4be4a2807a6bc6e63c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 12:06:13 +0200 +Subject: [PATCH 10/11] introspect: Rename variable + +It was too generic, and would conflict with a StSettings variable. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + js/misc/introspect.js | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index 6186754cd..8e68a7e4f 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -29,7 +29,9 @@ var IntrospectService = class { + this._syncRunningApplications(); + }); + +- this._settings = new Gio.Settings({ schema_id: INTROSPECT_SCHEMA }); ++ this._introspectSettings = new Gio.Settings({ ++ schema_id: INTROSPECT_SCHEMA, ++ }); + + let tracker = Shell.WindowTracker.get_default(); + tracker.connect('notify::focus-app', +@@ -57,7 +59,7 @@ var IntrospectService = class { + } + + _isIntrospectEnabled() { +- return this._settings.get_boolean(INTROSPECT_KEY); ++ return this._introspectSettings.get_boolean(INTROSPECT_KEY); + } + + _isSenderWhitelisted(sender) { +-- +2.26.2 + + +From 343e7792fc84c296b331c3fcb142ed79d2ce9bd5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 1 Oct 2019 12:07:03 +0200 +Subject: [PATCH 11/11] introspect: Add AnimationsEnabled property + +While the gsetting is available for all who needs it, the Shell might +override it given various hueristics. Expose the decision made by the +Shell via a new property. + +Intended to be used by gsd-xsettings as well as xdg-desktop-portal-gtk. + +This also add a version property to the API, so that semi external +services (xdg-desktop-portal-gtk) can detect what API is expected to be +present. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/757 +--- + .../org.gnome.Shell.Introspect.xml | 14 ++++++++++ + js/misc/introspect.js | 27 ++++++++++++++++++- + 2 files changed, 40 insertions(+), 1 deletion(-) + +diff --git a/data/dbus-interfaces/org.gnome.Shell.Introspect.xml b/data/dbus-interfaces/org.gnome.Shell.Introspect.xml +index 9508681af..d71f2414b 100644 +--- a/data/dbus-interfaces/org.gnome.Shell.Introspect.xml ++++ b/data/dbus-interfaces/org.gnome.Shell.Introspect.xml +@@ -57,5 +57,19 @@ + + + ++ ++ ++ ++ ++ + + +diff --git a/js/misc/introspect.js b/js/misc/introspect.js +index 8e68a7e4f..7c62113e5 100644 +--- a/js/misc/introspect.js ++++ b/js/misc/introspect.js +@@ -1,9 +1,11 @@ +-const { Gio, GLib, Meta, Shell } = imports.gi; ++const { Gio, GLib, Meta, Shell, St } = imports.gi; + + const INTROSPECT_SCHEMA = 'org.gnome.shell'; + const INTROSPECT_KEY = 'introspect'; + const APP_WHITELIST = ['org.freedesktop.impl.portal.desktop.gtk']; + ++const INTROSPECT_DBUS_API_VERSION = 2; ++ + const { loadInterfaceXML } = imports.misc.fileUtils; + + const IntrospectDBusIface = loadInterfaceXML('org.gnome.Shell.Introspect'); +@@ -21,6 +23,7 @@ var IntrospectService = class { + this._runningApplicationsDirty = true; + this._activeApplication = null; + this._activeApplicationDirty = true; ++ this._animationsEnabled = true; + + this._appSystem = Shell.AppSystem.get_default(); + this._appSystem.connect('app-state-changed', +@@ -50,6 +53,11 @@ var IntrospectService = class { + (conn, name, owner) => this._whitelistMap.set(name, owner), + (conn, name) => this._whitelistMap.delete(name)); + }); ++ ++ this._settings = St.Settings.get(); ++ this._settings.connect('notify::enable-animations', ++ this._syncAnimationsEnabled.bind(this)); ++ this._syncAnimationsEnabled(); + } + + _isStandaloneApp(app) { +@@ -191,4 +199,21 @@ var IntrospectService = class { + } + invocation.return_value(new GLib.Variant('(a{ta{sv}})', [windowsList])); + } ++ ++ _syncAnimationsEnabled() { ++ let wasAnimationsEnabled = this._animationsEnabled; ++ this._animationsEnabled = this._settings.enable_animations; ++ if (wasAnimationsEnabled !== this._animationsEnabled) { ++ let variant = new GLib.Variant('b', this._animationsEnabled); ++ this._dbusImpl.emit_property_changed('AnimationsEnabled', variant); ++ } ++ } ++ ++ get AnimationsEnabled() { ++ return this._animationsEnabled; ++ } ++ ++ get version() { ++ return INTROSPECT_DBUS_API_VERSION; ++ } + }; +-- +2.26.2 + diff --git a/SOURCES/more-spurious-allocation-warnings.patch b/SOURCES/more-spurious-allocation-warnings.patch new file mode 100644 index 0000000..16acc8f --- /dev/null +++ b/SOURCES/more-spurious-allocation-warnings.patch @@ -0,0 +1,176 @@ +From 4926a9b8f958617d67d603622b1382c17fe4037c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 20 May 2020 12:05:04 +0200 +Subject: [PATCH 1/2] workspacesView: Avoid setting invalid geometries on views + +The fullGeometry and the actualGeometry of the WorkspacesDisplay are set +from the allocation of the overviews ControlsManager and the +WorkspacesDisplay, that means they're only valid after those actors got +their allocations during Clutters allocation cycle. + +Since WorkspacesDisplay._updateWorkspacesViews() is already called while +showing/mapping the WorkspacesDisplay, that allocation cycle didn't +happen yet and we end up either setting the geometries of the views to +null (in case of the fullGeometry) or to something wrong (a 0-sized +allocation in case of the actualGeometry). + +So avoid setting invalid geometries on the views by initializing both +the fullGeometry and the actualGeometry to null, and then only updating +the geometries of the views after they're set to a correct value. + +Note that this means we won't correctly animate the overview the first +time we open it since the animation depends on the geometries being set, +but is being started from show(), which means no allocations have +happened yet. In practice this introduces no regression though since +before this change we simply used incorrect geometries (see the 0-sized +allocation mentioned above) on the initial opening and the animation +didn't work either. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1119 +--- + js/ui/workspacesView.js | 28 +++++++++++++++++----------- + 1 file changed, 17 insertions(+), 11 deletions(-) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index e302296a6..02baddc6e 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -521,6 +521,7 @@ var WorkspacesDisplay = class { + this._scrollEventId = 0; + this._keyPressEventId = 0; + ++ this._actualGeometry = null; + this._fullGeometry = null; + } + +@@ -675,8 +676,10 @@ var WorkspacesDisplay = class { + + this._workspacesViews.forEach(v => v.actor.show()); + +- this._updateWorkspacesFullGeometry(); +- this._updateWorkspacesActualGeometry(); ++ if (this._fullGeometry) ++ this._syncWorkspacesFullGeometry(); ++ if (this._actualGeometry) ++ this._syncWorkspacesActualGeometry(); + } + + _scrollValueChanged() { +@@ -739,10 +742,10 @@ var WorkspacesDisplay = class { + // the sliding controls were never slid in at all. + setWorkspacesFullGeometry(geom) { + this._fullGeometry = geom; +- this._updateWorkspacesFullGeometry(); ++ this._syncWorkspacesFullGeometry(); + } + +- _updateWorkspacesFullGeometry() { ++ _syncWorkspacesFullGeometry() { + if (!this._workspacesViews.length) + return; + +@@ -754,18 +757,21 @@ var WorkspacesDisplay = class { + } + + _updateWorkspacesActualGeometry() { ++ const [x, y] = this.actor.get_transformed_position(); ++ const width = this.actor.allocation.get_width(); ++ const height = this.actor.allocation.get_height(); ++ ++ this._actualGeometry = { x, y, width, height }; ++ this._syncWorkspacesActualGeometry(); ++ } ++ ++ _syncWorkspacesActualGeometry() { + if (!this._workspacesViews.length) + return; + +- let [x, y] = this.actor.get_transformed_position(); +- let allocation = this.actor.allocation; +- let width = allocation.x2 - allocation.x1; +- let height = allocation.y2 - allocation.y1; +- let primaryGeometry = { x: x, y: y, width: width, height: height }; +- + let monitors = Main.layoutManager.monitors; + for (let i = 0; i < monitors.length; i++) { +- let geometry = (i == this._primaryIndex) ? primaryGeometry : monitors[i]; ++ let geometry = i === this._primaryIndex ? this._actualGeometry : monitors[i]; + this._workspacesViews[i].setActualGeometry(geometry); + } + } +-- +2.26.2 + + +From 4671eebccf4e6afce8c0a869d63095b39aa7e163 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 20 May 2020 13:39:11 +0200 +Subject: [PATCH 2/2] workspacesView: Only animate on show() when geometries + are already set + +Animating the window clones of the overview requires the fullGeometry +and the actualGeometry to be set, which they won't be when showing the +overview for the first time. So don't even try to animate the window +clones in that case because the geometries will still be null and +accessing them in workspace.js will throw errors. + +The workspace views will still get the correct layout as soon as the +allocations happen because syncing the geometries will trigger updating +the window positions. Since animations are disabled for position changes +when syncing the geometry though, we won't get an animation and the +clones will jump into place. That's not a regression though since before +this change we also didn't animate in that case because the geometries +used were simply wrong (the actualGeometry was 0-sized as explained in +the last commit). + +If we wanted to fix the initial animation of the overview, we'd have to +always enable animations of the window clones when syncing geometries, +but that would break the animation of the workspace when hovering the +workspaceThumbnail slider, because right now those animations are "glued +together" using the actualGeometry, so they would get out of sync. + +The reason there are no errors happening in workspace.js with the +existing code is that due to a bug in Clutter the fullGeometry of +WorkspacesDisplay gets set very early while mapping the WorkspacesViews +(because the overviews ControlsManager gets an allocation during the +resource scale calculation of a ClutterClone, see +https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1181), so it +won't be set to null anymore when calling +WorkspacesView.animateToOverview(). + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1119 +--- + js/ui/workspacesView.js | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js +index 02baddc6e..3e9d77655 100644 +--- a/js/ui/workspacesView.js ++++ b/js/ui/workspacesView.js +@@ -589,13 +589,16 @@ var WorkspacesDisplay = class { + + show(fadeOnPrimary) { + this._updateWorkspacesViews(); +- for (let i = 0; i < this._workspacesViews.length; i++) { +- let animationType; +- if (fadeOnPrimary && i == this._primaryIndex) +- animationType = AnimationType.FADE; +- else +- animationType = AnimationType.ZOOM; +- this._workspacesViews[i].animateToOverview(animationType); ++ ++ if (this._actualGeometry && this._fullGeometry) { ++ for (let i = 0; i < this._workspacesViews.length; i++) { ++ let animationType; ++ if (fadeOnPrimary && i == this._primaryIndex) ++ animationType = AnimationType.FADE; ++ else ++ animationType = AnimationType.ZOOM; ++ this._workspacesViews[i].animateToOverview(animationType); ++ } + } + + this._restackedNotifyId = +-- +2.26.2 + diff --git a/SOURCES/osk-fixes.patch b/SOURCES/osk-fixes.patch new file mode 100644 index 0000000..6c5648d --- /dev/null +++ b/SOURCES/osk-fixes.patch @@ -0,0 +1,117 @@ +From 96ccb155bbe6ce570832a9f3d27a0a08698127ea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sat, 28 Mar 2020 14:15:09 +0100 +Subject: [PATCH 1/3] keyboard: Don't include keyboard devices when updating + lastDevice + +We're dealing with attached keyboards now using the touch_mode property +of ClutterSeat: If a device has a keyboard attached, the touch-mode is +FALSE and we won't automatically show the OSK on touches, also the +touch-mode gets set to FALSE when an external keyboard is being plugged +in, so that also hides the OSK automatically. + +With that, we can now ignore keyboard devices when updating the last +used device and no longer have to special-case our own virtual devices. + +Because there was no special-case for the virtual device we use on +Wayland now, this fixes a bug where the keyboard disappeared after +touching keys like Enter or Backspace. + +Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2287 + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1142 +--- + js/ui/keyboard.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js +index c4ac72d..94b5325 100644 +--- a/js/ui/keyboard.js ++++ b/js/ui/keyboard.js +@@ -1075,6 +1075,9 @@ var Keyboard = class Keyboard { + let device = manager.get_device(deviceId); + + if (device.get_device_name().indexOf('XTEST') < 0) { ++ if (device.device_type == Clutter.InputDeviceType.KEYBOARD_DEVICE) ++ return; ++ + this._lastDeviceId = deviceId; + this._syncEnabled(); + } +-- +2.26.2 + + +From 3106746ae424287d8644643a2ef46d565e4cd7ed Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sat, 28 Mar 2020 14:34:24 +0100 +Subject: [PATCH 2/3] layout: Use translation_y of 0 to hide keyboard + +Since we show the keyboard using a translation_y of -keyboardHeight, the +keyboard will be moved down far enough to be out of sight by setting +translation_y to 0. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1142 +--- + js/ui/layout.js | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index beb4c0a..4382f6e 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -719,7 +719,7 @@ var LayoutManager = GObject.registerClass({ + showKeyboard() { + this.keyboardBox.show(); + Tweener.addTween(this.keyboardBox, +- { anchor_y: this.keyboardBox.height, ++ { translation_y: -this.keyboardBox.height, + opacity: 255, + time: KEYBOARD_ANIMATION_TIME, + transition: 'easeOutQuad', +@@ -735,7 +735,7 @@ var LayoutManager = GObject.registerClass({ + this._updateRegions(); + + this._keyboardHeightNotifyId = this.keyboardBox.connect('notify::height', () => { +- this.keyboardBox.anchor_y = this.keyboardBox.height; ++ this.keyboardBox.translation_y = -this.keyboardBox.height; + }); + } + +@@ -745,7 +745,7 @@ var LayoutManager = GObject.registerClass({ + this._keyboardHeightNotifyId = 0; + } + Tweener.addTween(this.keyboardBox, +- { anchor_y: 0, ++ { translation_y: 0, + opacity: 0, + time: immediate ? 0 : KEYBOARD_ANIMATION_TIME, + transition: 'easeInQuad', +-- +2.26.2 + + +From 642822308a72be6a47f4eb285f32539499f0d3e4 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Wed, 21 Oct 2020 20:29:34 +0200 +Subject: [PATCH 3/3] layout: queue redraw after hiding keyboard + +--- + js/ui/layout.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/layout.js b/js/ui/layout.js +index 4382f6e..1824313 100644 +--- a/js/ui/layout.js ++++ b/js/ui/layout.js +@@ -759,6 +759,7 @@ var LayoutManager = GObject.registerClass({ + _hideKeyboardComplete() { + this.keyboardBox.hide(); + this._updateRegions(); ++ global.stage.queue_redraw(); + } + + // setDummyCursorGeometry: +-- +2.26.2 + diff --git a/SOURCES/perf-tool-wayland.patch b/SOURCES/perf-tool-wayland.patch new file mode 100644 index 0000000..be4b625 --- /dev/null +++ b/SOURCES/perf-tool-wayland.patch @@ -0,0 +1,399 @@ +From 119ec213b8f9a9e55ca340dbde10b0d19becab41 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 5 Dec 2019 14:12:47 +0100 +Subject: [PATCH 1/4] perf-helper: Add content for custom drawing + +Drawing windows got a lot more involved with the advent of client-side +decorations. Instead of accounting for visible and invisible borders, +titlebar and shadows when necessary, just add an empty child for the +custom drawing. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/887 +--- + src/shell-perf-helper.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/src/shell-perf-helper.c b/src/shell-perf-helper.c +index e5eab208b..55bdbef02 100644 +--- a/src/shell-perf-helper.c ++++ b/src/shell-perf-helper.c +@@ -120,9 +120,9 @@ on_window_map_event (GtkWidget *window, + } + + static gboolean +-on_window_draw (GtkWidget *window, +- cairo_t *cr, +- WindowInfo *info) ++on_child_draw (GtkWidget *window, ++ cairo_t *cr, ++ WindowInfo *info) + { + cairo_rectangle_int_t allocation; + double x_offset, y_offset; +@@ -204,6 +204,7 @@ create_window (int width, + gboolean redraws) + { + WindowInfo *info; ++ GtkWidget *child; + + info = g_new0 (WindowInfo, 1); + info->width = width; +@@ -219,10 +220,13 @@ create_window (int width, + info->pending = TRUE; + info->start_time = -1; + ++ child = g_object_new (GTK_TYPE_BOX, "visible", TRUE, "app-paintable", TRUE, NULL); ++ gtk_container_add (GTK_CONTAINER (info->window), child); ++ + gtk_widget_set_size_request (info->window, width, height); + gtk_widget_set_app_paintable (info->window, TRUE); + g_signal_connect (info->window, "map-event", G_CALLBACK (on_window_map_event), info); +- g_signal_connect (info->window, "draw", G_CALLBACK (on_window_draw), info); ++ g_signal_connect (child, "draw", G_CALLBACK (on_child_draw), info); + gtk_widget_show (info->window); + + if (info->redraws) +-- +2.26.2 + + +From bb4c2acaef4d8fdea50915030c221e1190f704a4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 5 Dec 2019 13:29:38 +0100 +Subject: [PATCH 2/4] perf-helper: Remove unused atoms + +Those aren't used for anything, but make the helper dependent on X11. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/887 +--- + src/shell-perf-helper.c | 18 ------------------ + 1 file changed, 18 deletions(-) + +diff --git a/src/shell-perf-helper.c b/src/shell-perf-helper.c +index 55bdbef02..d3280de96 100644 +--- a/src/shell-perf-helper.c ++++ b/src/shell-perf-helper.c +@@ -12,7 +12,6 @@ + #include + + #include +-#include + + #define BUS_NAME "org.gnome.Shell.PerfHelper" + +@@ -60,12 +59,6 @@ static GOptionEntry opt_entries[] = + { NULL } + }; + +-static Display *xdisplay; +-static Window xroot; +-static Atom atom_wm_state; +-static Atom atom__net_wm_name; +-static Atom atom_utf8_string; +- + static guint timeout_id; + static GList *our_windows; + static GList *wait_windows_invocations; +@@ -351,8 +344,6 @@ on_name_lost (GDBusConnection *connection, + int + main (int argc, char **argv) + { +- GdkDisplay *display; +- GdkScreen *screen; + GOptionContext *context; + GError *error = NULL; + +@@ -368,15 +359,6 @@ main (int argc, char **argv) + return 1; + } + +- display = gdk_display_get_default (); +- screen = gdk_screen_get_default (); +- +- xdisplay = gdk_x11_display_get_xdisplay (display); +- xroot = gdk_x11_window_get_xid (gdk_screen_get_root_window (screen)); +- atom_wm_state = gdk_x11_get_xatom_by_name_for_display (display, "WM_STATE"); +- atom__net_wm_name = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"); +- atom_utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); +- + g_bus_own_name (G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | +-- +2.26.2 + + +From d8b4d72b89340dab46bdcb92ee54bde18dbb9ba9 Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan +Date: Fri, 24 Jan 2020 10:59:31 +0100 +Subject: [PATCH 3/4] perf-tool: Spawn perf-tool-helper from gnome-shell + +On Wayland, the display server is the Wayland compositor, i.e. +`gnome-shell` itself. + +As a result, we cannot spawn `gnome-shell-perf-helper` before +`gnome-shell` is started, as `gnome-shell-perf-helper` needs to connect +to the display server. + +So, instead of spawning `gnome-shell-perf-helper` from the perf tool, +start it from `gnome-shell` itself. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/941 +--- + js/ui/scripting.js | 51 ++++++++++++++++++++++------------ + src/gnome-shell-perf-tool.in | 53 ------------------------------------ + 2 files changed, 34 insertions(+), 70 deletions(-) + +diff --git a/js/ui/scripting.js b/js/ui/scripting.js +index d227b9ef4..eef8f3887 100644 +--- a/js/ui/scripting.js ++++ b/js/ui/scripting.js +@@ -3,8 +3,10 @@ + const { Gio, GLib, Meta, Shell } = imports.gi; + const Mainloop = imports.mainloop; + ++const Config = imports.misc.config; + const Main = imports.ui.main; + const Params = imports.misc.params; ++const Util = imports.misc.util; + + const { loadInterfaceXML } = imports.misc.fileUtils; + +@@ -73,6 +75,12 @@ function _getPerfHelper() { + return _perfHelper; + } + ++function _spawnPerfHelper() { ++ let path = Config.LIBEXECDIR; ++ let command = `${path}/gnome-shell-perf-helper`; ++ Util.trySpawnCommandLine(command); ++} ++ + function _callRemote(obj, method, ...args) { + return new Promise((resolve, reject) => { + args.push((result, excp) => { +@@ -270,6 +278,25 @@ function _collect(scriptModule, outputFile) { + } + } + ++async function _runPerfScript(scriptModule, outputFile) { ++ for (let step of scriptModule.run()) { ++ try { ++ await step; // eslint-disable-line no-await-in-loop ++ } catch (err) { ++ log(`Script failed: ${err}\n${err.stack}`); ++ Meta.exit(Meta.ExitCode.ERROR); ++ } ++ } ++ ++ try { ++ _collect(scriptModule, outputFile); ++ } catch (err) { ++ log(`Script failed: ${err}\n${err.stack}`); ++ Meta.exit(Meta.ExitCode.ERROR); ++ } ++ Meta.exit(Meta.ExitCode.SUCCESS); ++} ++ + /** + * runPerfScript + * @scriptModule: module object with run and finish functions +@@ -310,23 +337,13 @@ function _collect(scriptModule, outputFile) { + * After running the script and collecting statistics from the + * event log, GNOME Shell will exit. + **/ +-async function runPerfScript(scriptModule, outputFile) { ++function runPerfScript(scriptModule, outputFile) { + Shell.PerfLog.get_default().set_enabled(true); ++ _spawnPerfHelper(); + +- for (let step of scriptModule.run()) { +- try { +- await step; +- } catch (err) { +- log(`Script failed: ${err}\n${err.stack}`); +- Meta.exit(Meta.ExitCode.ERROR); +- } +- } +- +- try { +- _collect(scriptModule, outputFile); +- } catch (err) { +- log(`Script failed: ${err}\n${err.stack}`); +- Meta.exit(Meta.ExitCode.ERROR); +- } +- Meta.exit(Meta.ExitCode.SUCCESS); ++ Gio.bus_watch_name(Gio.BusType.SESSION, ++ 'org.gnome.Shell.PerfHelper', ++ Gio.BusNameWatcherFlags.NONE, ++ () => _runPerfScript(scriptModule, outputFile), ++ null); + } +diff --git a/src/gnome-shell-perf-tool.in b/src/gnome-shell-perf-tool.in +index f4b48f730..050c66b30 100755 +--- a/src/gnome-shell-perf-tool.in ++++ b/src/gnome-shell-perf-tool.in +@@ -24,52 +24,6 @@ def show_version(option, opt_str, value, parser): + print("GNOME Shell Performance Test @VERSION@") + sys.exit() + +-def wait_for_dbus_name(wait_name): +- loop = GLib.MainLoop() +- +- def on_name_appeared(connection, name, new_owner, *args): +- if not (name == wait_name and new_owner != ''): +- return +- loop.quit() +- return +- +- watch_id = Gio.bus_watch_name(Gio.BusType.SESSION, +- wait_name, +- Gio.BusNameWatcherFlags.NONE, +- on_name_appeared, +- None) +- +- def on_timeout(): +- print("\nFailed to start %s: timed out" % (wait_name,)) +- sys.exit(1) +- GLib.timeout_add_seconds(7, on_timeout) +- +- loop.run() +- Gio.bus_unwatch_name(watch_id) +- +-PERF_HELPER_NAME = "org.gnome.Shell.PerfHelper" +-PERF_HELPER_IFACE = "org.gnome.Shell.PerfHelper" +-PERF_HELPER_PATH = "/org/gnome/Shell/PerfHelper" +- +-def start_perf_helper(): +- self_dir = os.path.dirname(os.path.abspath(sys.argv[0])) +- perf_helper_path = "@libexecdir@/gnome-shell-perf-helper" +- +- subprocess.Popen([perf_helper_path]) +- wait_for_dbus_name (PERF_HELPER_NAME) +- +-def stop_perf_helper(): +- bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) +- +- proxy = Gio.DBusProxy.new_sync(bus, +- Gio.DBusProxyFlags.NONE, +- None, +- PERF_HELPER_NAME, +- PERF_HELPER_PATH, +- PERF_HELPER_IFACE, +- None) +- proxy.Exit() +- + def start_shell(perf_output=None): + # Set up environment + env = dict(os.environ) +@@ -204,8 +158,6 @@ def run_performance_test(): + logs = [] + metric_summaries = {} + +- start_perf_helper() +- + for i in range(0, iters): + # We create an empty temporary file that the shell will overwrite + # with the contents. +@@ -217,14 +169,12 @@ def run_performance_test(): + try: + normal_exit = run_shell(perf_output=output_file) + except: +- stop_perf_helper() + raise + finally: + if not normal_exit: + os.remove(output_file) + + if not normal_exit: +- stop_perf_helper() + return False + + try: +@@ -232,7 +182,6 @@ def run_performance_test(): + output = json.load(f) + f.close() + except: +- stop_perf_helper() + raise + finally: + os.remove(output_file) +@@ -260,8 +209,6 @@ def run_performance_test(): + + logs.append(output['log']) + +- stop_perf_helper() +- + if options.perf_output or options.perf_upload: + # Write a complete report, formatted as JSON. The Javascript/C code that + # generates the individual reports we are summarizing here is very careful +-- +2.26.2 + + +From 8090db0f29dc72e602be341d43b3113373404b21 Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan +Date: Tue, 21 Jan 2020 11:05:58 +0100 +Subject: [PATCH 4/4] perf-tool: Allow to run as a Wayland compositor + +`gnome-shell-perf-tool` is initially designed to run on X11, using the +`--replace` option which does not work when gnome-shell is a Wayland +compositor. + +A solution would be to run `gnome-shell-perf-tool` in place of just +`gnome-shell` to run the entire perf session under Wayland, but the +script `gnome-shell-perf-tool` does not spawn `gnome-shell` as a Wayladn +compositor, so that fails as well. + +Add a `--wayland` option to `gnome-shell-perf-tool` so that it can +optionally spawn gnome-shell as a Wayland compositor so the whole perf +tool can be starred from a console with: + +``` + $ dbus-run-session -- gnome-shell-perf-tool --wayland +``` + +Alternatively, for testing purposes, it can also be started nested with: + +``` + $ dbus-run-session -- gnome-shell-perf-tool --nested +``` + +Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/2139 +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/941 +--- + src/gnome-shell-perf-tool.in | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/gnome-shell-perf-tool.in b/src/gnome-shell-perf-tool.in +index 050c66b30..04072c4cd 100755 +--- a/src/gnome-shell-perf-tool.in ++++ b/src/gnome-shell-perf-tool.in +@@ -45,6 +45,13 @@ def start_shell(perf_output=None): + if options.replace: + args.append('--replace') + ++ if options.wayland or options.nested: ++ args.append('--wayland') ++ if options.nested: ++ args.append('--nested') ++ else: ++ args.append('--display-server') ++ + return subprocess.Popen(args, env=env) + + def run_shell(perf_output=None): +@@ -284,6 +291,10 @@ parser.add_option("", "--version", action="callback", callback=show_version, + + parser.add_option("-r", "--replace", action="store_true", + help="Replace the running window manager") ++parser.add_option("-w", "--wayland", action="store_true", ++ help="Run as a Wayland compositor") ++parser.add_option("-n", "--nested", action="store_true", ++ help="Run as a Wayland nested compositor") + + options, args = parser.parse_args() + +-- +2.26.2 + diff --git a/SOURCES/root-warning.patch b/SOURCES/root-warning.patch new file mode 100644 index 0000000..35b2c44 --- /dev/null +++ b/SOURCES/root-warning.patch @@ -0,0 +1,71 @@ +From 45ddeeaa317fb0ffd045600d9e4b95143c9ca8b8 Mon Sep 17 00:00:00 2001 +From: Matthias Clasen +Date: Sat, 8 Jun 2013 13:32:35 -0400 +Subject: [PATCH 1/2] main: Show a warning when running as root + +gnome-session used to show a dialog in this case, but a +notification is more natural nowadays. Doing it in gnome-shell +avoids complicated synchronization between gnome-session and +gnome-shell. + +https://bugzilla.gnome.org/show_bug.cgi?id=701212 +--- + js/ui/main.js | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index 8d1755cf1..abf8a8765 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -237,6 +237,12 @@ function _initializeUI() { + ['MESSAGE_ID=' + GNOMESHELL_STARTED_MESSAGE_ID]); + } + ++ let credentials = new Gio.Credentials(); ++ if (credentials.get_unix_user() === 0) { ++ notify(_('Logged in as a privileged user'), ++ _('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.')); ++ } ++ + let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); + if (perfModuleName) { + let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT"); +-- +2.23.0 + + +From 8e82907909b6a2e5af5da3f93b087df4b7eb48b5 Mon Sep 17 00:00:00 2001 +From: Matthias Clasen +Date: Sat, 8 Jun 2013 13:33:58 -0400 +Subject: [PATCH 2/2] main: Show a warning when gdm is missing + +If we are not running under gdm, some functionaliy (such as +the lock screen) does not work, and we should inform the +user about this. + +https://bugzilla.gnome.org/show_bug.cgi?id=701212 +--- + js/ui/main.js | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index abf8a8765..be49c750e 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -243,6 +243,13 @@ function _initializeUI() { + _('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.')); + } + ++ if (sessionMode.currentMode !== 'gdm' && ++ sessionMode.currentMode !== 'initial-setup' && ++ screenShield === null) { ++ notify(_('Screen Lock disabled'), ++ _('Screen Locking requires the GNOME display manager.')); ++ } ++ + let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); + if (perfModuleName) { + let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT"); +-- +2.23.0 + diff --git a/SOURCES/wake-up-on-deactivate.patch b/SOURCES/wake-up-on-deactivate.patch new file mode 100644 index 0000000..b20cbbd --- /dev/null +++ b/SOURCES/wake-up-on-deactivate.patch @@ -0,0 +1,79 @@ +From d6ead50fe230df58ddab822966d69760b00ec920 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 1 Apr 2020 14:48:10 +0200 +Subject: [PATCH 1/2] screenShield: Switch lightboxes off before unlock + transition + +There is no point in animating a transition with fullscreen black +rectangles stacked on top, so switch them off before rather than +after the transition. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1158 +--- + js/ui/screenShield.js | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index cd38f11fc8..282f29fa30 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1221,6 +1221,9 @@ var ScreenShield = class { + this._isModal = false; + } + ++ this._longLightbox.hide(); ++ this._shortLightbox.hide(); ++ + Tweener.addTween(this._lockDialogGroup, { + scale_x: 0, + scale_y: 0, +@@ -1237,8 +1240,6 @@ var ScreenShield = class { + this._dialog = null; + } + +- this._longLightbox.hide(); +- this._shortLightbox.hide(); + this.actor.hide(); + + if (this._becameActiveId != 0) { +-- +2.28.0 + + +From 39ac7cad68d8c00d98c900b35add637b01eddbbf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 31 Mar 2020 21:07:59 +0200 +Subject: [PATCH 2/2] screenShield: Wake up on deactivate() + +Usually the screen is woken up before the shield is deactivated, but +it is also possible to unlock the session programmatically via the +org.gnome.ScreenSaver D-Bus API. + +The intention is very likely not to unlock a turned off screen in +that case. Nor does it seem like a good idea to change the lock +state without any indication. + +Waking up the screen is more likely to meet expectations and is +more reasonable too, so do that. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1158 +--- + js/ui/screenShield.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js +index 282f29fa30..2d0a429bee 100644 +--- a/js/ui/screenShield.js ++++ b/js/ui/screenShield.js +@@ -1200,6 +1200,8 @@ var ScreenShield = class { + if (Main.sessionMode.currentMode == 'unlock-dialog') + Main.sessionMode.popMode('unlock-dialog'); + ++ this.emit('wake-up-screen'); ++ + if (this._isGreeter) { + // We don't want to "deactivate" any more than + // this. In particular, we don't want to drop +-- +2.28.0 + diff --git a/SOURCES/warn-less.patch b/SOURCES/warn-less.patch new file mode 100644 index 0000000..f5dd837 --- /dev/null +++ b/SOURCES/warn-less.patch @@ -0,0 +1,279 @@ +From 37bbb9175bbd061d4ae14e86c35e4211602dbeaa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 23 Mar 2020 17:57:38 +0100 +Subject: [PATCH 1/4] shell/util: Add touch_file_async() helper + +Add a small helper method to asynchronously "touch" a file and return +whether the file was created or not. + +As g_file_make_directory_with_parents() doesn't have an async variant, +we need a C helper to make the entire operation non-blocking. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2432 +--- + src/shell-util.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ + src/shell-util.h | 7 ++++++ + 2 files changed, 69 insertions(+) + +diff --git a/src/shell-util.c b/src/shell-util.c +index fa3fc08c8..eec67f3d7 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -323,6 +323,68 @@ shell_get_file_contents_utf8_sync (const char *path, + return contents; + } + ++static void ++touch_file (GTask *task, ++ gpointer object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ GFile *file = object; ++ g_autoptr (GFile) parent = NULL; ++ g_autoptr (GFileOutputStream) stream = NULL; ++ GError *error = NULL; ++ ++ parent = g_file_get_parent (file); ++ g_file_make_directory_with_parents (parent, cancellable, &error); ++ ++ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ g_clear_error (&error); ++ ++ stream = g_file_create (file, G_FILE_CREATE_NONE, cancellable, &error); ++ ++ if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) ++ { ++ g_task_return_error (task, error); ++ return; ++ } ++ g_clear_error (&error); ++ ++ if (stream) ++ g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL); ++ ++ g_task_return_boolean (task, stream != NULL); ++} ++ ++void ++shell_util_touch_file_async (GFile *file, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr (GTask) task = NULL; ++ ++ g_return_if_fail (G_IS_FILE (file)); ++ ++ task = g_task_new (file, NULL, callback, user_data); ++ g_task_set_source_tag (task, shell_util_touch_file_async); ++ ++ g_task_run_in_thread (task, touch_file); ++} ++ ++gboolean ++shell_util_touch_file_finish (GFile *file, ++ GAsyncResult *res, ++ GError **error) ++{ ++ g_return_val_if_fail (G_IS_FILE (file), FALSE); ++ g_return_val_if_fail (G_IS_TASK (res), FALSE); ++ ++ return g_task_propagate_boolean (G_TASK (res), error); ++} ++ + /** + * shell_util_wifexited: + * @status: the status returned by wait() or waitpid() +diff --git a/src/shell-util.h b/src/shell-util.h +index 02b8404e9..bedf516ba 100644 +--- a/src/shell-util.h ++++ b/src/shell-util.h +@@ -32,6 +32,13 @@ gboolean shell_write_string_to_stream (GOutputStream *stream, + char *shell_get_file_contents_utf8_sync (const char *path, + GError **error); + ++void shell_util_touch_file_async (GFile *file, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean shell_util_touch_file_finish (GFile *file, ++ GAsyncResult *res, ++ GError **error); ++ + gboolean shell_util_wifexited (int status, + int *exit); + +-- +2.31.1 + + +From 1f75494bea1ef7017d50d77cf5c7ad6b9668d4f5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 23 Mar 2020 18:00:27 +0100 +Subject: [PATCH 2/4] environment: Hook up touch_file to GFile prototype + +We don't usually extend introspected types with our own API, but in +this case it's too tempting to make the helper functions usable with +Gio._promisify() ... + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2432 +--- + js/ui/environment.js | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/js/ui/environment.js b/js/ui/environment.js +index e22ec7402..9c125d3eb 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -9,7 +9,7 @@ imports.gi.versions.Gtk = '3.0'; + imports.gi.versions.TelepathyGLib = '0.12'; + imports.gi.versions.TelepathyLogger = '0.2'; + +-const { Clutter, GLib, Shell, St } = imports.gi; ++const { Clutter, Gio, GLib, Shell, St } = imports.gi; + const Gettext = imports.gettext; + + // We can't import shell JS modules yet, because they may have +@@ -97,6 +97,13 @@ function init() { + return St.describe_actor(this); + }; + ++ Gio._LocalFilePrototype.touch_async = function (callback) { ++ Shell.util_touch_file_async(this, callback); ++ }; ++ Gio._LocalFilePrototype.touch_finish = function (result) { ++ return Shell.util_touch_file_finish(this, result); ++ }; ++ + let origToString = Object.prototype.toString; + Object.prototype.toString = function() { + let base = origToString.call(this); +-- +2.31.1 + + +From 4bef23c7176a43f4dcf146e70bbb8aaa701b8cd2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 20 Mar 2020 12:42:04 +0100 +Subject: [PATCH 3/4] main: Do not warn about missing GDM on each login + +We now warn on startup if screen locking isn't available, however for +users who choose not to use GDM or logind, repeating the warning on +each login is more annoying than helpful. + +Instead, limit the warning to the first login on which the screen lock +became unavailable. That way the notification will still serve the +intended purpose of informing the user, but without being perceived +as nagging. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2432 +--- + js/ui/main.js | 36 +++++++++++++++++++++++++++++++----- + 1 file changed, 31 insertions(+), 5 deletions(-) + +diff --git a/js/ui/main.js b/js/ui/main.js +index 1203b3c39..a3fad158c 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -81,6 +81,9 @@ let _a11ySettings = null; + let _themeResource = null; + let _oskResource = null; + ++Gio._promisify(Gio._LocalFilePrototype, 'delete_async', 'delete_finish'); ++Gio._promisify(Gio._LocalFilePrototype, 'touch_async', 'touch_finish'); ++ + function _sessionUpdated() { + if (sessionMode.isPrimary) + _loadDefaultStylesheet(); +@@ -242,11 +245,8 @@ function _initializeUI() { + } + + if (sessionMode.currentMode !== 'gdm' && +- sessionMode.currentMode !== 'initial-setup' && +- screenShield === null) { +- notify(_('Screen Lock disabled'), +- _('Screen Locking requires the GNOME display manager.')); +- } ++ sessionMode.currentMode !== 'initial-setup') ++ _handleLockScreenWarning(); + + let perfModuleName = GLib.getenv("SHELL_PERF_MODULE"); + if (perfModuleName) { +@@ -257,6 +257,32 @@ function _initializeUI() { + }); + } + ++async function _handleLockScreenWarning() { ++ const path = '%s/lock-warning-shown'.format(global.userdatadir); ++ const file = Gio.File.new_for_path(path); ++ ++ const hasLockScreen = screenShield !== null; ++ if (hasLockScreen) { ++ try { ++ await file.delete_async(0, null); ++ } catch (e) { ++ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) ++ logError(e); ++ } ++ } else { ++ try { ++ if (!await file.touch_async()) ++ return; ++ } catch (e) { ++ logError(e); ++ } ++ ++ notify( ++ _('Screen Lock disabled'), ++ _('Screen Locking requires the GNOME display manager.')); ++ } ++} ++ + function _getStylesheet(name) { + let stylesheet; + +-- +2.31.1 + + +From c3f34e786826d0ed1af4150190159fed50d9fb87 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 Oct 2020 20:11:14 +0200 +Subject: [PATCH 4/4] messageTray: Default to generic policy + +How and if notifications are shown is controlled by NotificationPolicy +objects. But ever since 098bd45, only notification daemon sources or +notifications associated with an app are hooked up to GSettings. + +The hardcoded default policy for built-in notifications (including +those provided by extensions) arguably made sense back then, but +now that the main setting has been rebranded as "Do Not Disturb" +and is exposed prominently in the calendar drop-down, following +GSettings is a better default. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3291 + +Part-of: +--- + js/ui/messageTray.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js +index 8f8130451..f6bdae8e4 100644 +--- a/js/ui/messageTray.js ++++ b/js/ui/messageTray.js +@@ -731,7 +731,7 @@ var Source = class Source { + } + + _createPolicy() { +- return new NotificationPolicy(); ++ return new NotificationGenericPolicy(); + } + + get narrowestPrivacyScope() { +-- +2.31.1 + diff --git a/SPECS/gnome-shell.spec b/SPECS/gnome-shell.spec new file mode 100644 index 0000000..9a12512 --- /dev/null +++ b/SPECS/gnome-shell.spec @@ -0,0 +1,1510 @@ +Name: gnome-shell +Version: 3.32.2 +Release: 50%{?dist} +Summary: Window management and application launching for GNOME + +Group: User Interface/Desktops +License: GPLv2+ +Provides: desktop-notification-daemon +URL: https://wiki.gnome.org/Projects/GnomeShell +#VCS: git:git://git.gnome.org/gnome-shell +Source0: http://download.gnome.org/sources/gnome-shell/3.32/%{name}-%{version}.tar.xz + +# Replace Epiphany with Firefox in the default favourite apps list +Patch1: gnome-shell-favourite-apps-firefox.patch +Patch2: gnome-shell-favourite-apps-yelp.patch +Patch3: gnome-shell-favourite-apps-terminal.patch + +# GDM/Lock stuff +Patch12: 0001-screenShield-unblank-when-inserting-smartcard.patch +Patch13: enforce-smartcard-at-unlock.patch +Patch14: disable-unlock-entry-until-question.patch +Patch15: allow-timed-login-with-no-user-list.patch +Patch16: 0001-data-install-process-working.svg-to-filesystem.patch +Patch17: 0001-loginDialog-make-info-messages-themed.patch +Patch18: 0001-gdm-add-AuthList-control.patch +Patch19: 0002-gdmUtil-enable-support-for-GDM-s-ChoiceList-PAM-exte.patch +Patch20: wake-up-on-deactivate.patch +Patch21: caps-lock-warning.patch +Patch22: gdm-networking.patch +Patch23: 0001-shellEntry-Disconnect-handler-on-destroy.patch +Patch24: fix-login-lock-screen.patch +Patch25: 0001-shellEntry-Determine-if-password-entry-from-content-.patch +Patch26: 0002-shellEntry-Give-password-menu-item-text-when-it-s-cr.patch +Patch27: 0003-shellEntry-Handle-password-item-from-dedication-func.patch +Patch28: 0004-shellEntry-Support-lockdown-of-Show-Text-menu-in-pas.patch +Patch29: 0005-shellEntry-Only-mask-text-in-password-entries.patch + +# Misc. +Patch30: 0001-shellDBus-Add-a-DBus-method-to-load-a-single-extensi.patch +Patch31: 0001-extensions-Add-a-SESSION_MODE-extension-type.patch +Patch32: extension-updates.patch +Patch33: 0001-panel-add-an-icon-to-the-ActivitiesButton.patch +Patch34: 0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch +Patch35: 0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch +Patch36: 0001-shell-app-Handle-workspace-from-startup-notification.patch +Patch37: 0001-main-Dump-stack-on-segfaults-by-default.patch +Patch38: 0001-appDisplay-Show-full-app-name-on-hover.patch +Patch39: horizontal-workspace-support.patch +Patch40: 0001-animation-fix-unintentional-loop-while-polkit-dialog.patch +Patch41: 0001-workspacesView-Work-around-spurious-allocation-chang.patch +Patch42: 0001-layout-Make-the-hot-corner-optional.patch +Patch43: fix-app-view-leaks.patch +Patch44: root-warning.patch +Patch45: 0001-workspace-Pass-device-to-startDrag.patch +Patch46: 0001-a11y-Change-HC-icon-theme-first.patch +Patch47: perf-tool-wayland.patch +Patch48: 0001-padOsd-Re-query-action-labels-after-mode-switches.patch +Patch49: 0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch +Patch50: 0001-st-texture-cache-Cancel-pending-requests-on-icon-the.patch +Patch51: introspect-backports.patch +Patch52: 0001-popupMenu-Handle-keypress-if-numlock-is-enabled.patch +Patch53: 0001-theme-Update-window-preview-style.patch +Patch54: warn-less.patch +Patch55: 0001-networkAgent-add-support-for-SAE-secrets.patch +Patch56: 0001-main-Unset-the-right-prevFocus-actor-after-the-focus.patch +Patch57: defend-against-corrupt-notifications.patch +Patch58: 0001-status-volume-Hide-sliders-initially.patch +Patch59: 0001-shell-recorder-Restore-cursor-recording.patch +Patch60: 0001-st-bin-Disallow-st_bin_set_child-with-already-parent.patch +Patch61: 0001-layout-Initialize-regions-unconditionally.patch +Patch62: fix-nm-device-settings.patch + +# Backport JS invalid access warnings (#1651894, #1663171, #1642482, #1637622) +Patch70: fix-invalid-access-warnings.patch +Patch71: more-spurious-allocation-warnings.patch +Patch72: fix-some-js-warnings.patch +Patch73: fix-double-disposed-backgrounds.patch + +# Backport performance fixes under load (#1820760) +Patch80: 0001-environment-reduce-calls-to-g_time_zone_new_local.patch +Patch81: 0002-environment-Fix-date-conversion.patch +Patch82: 0003-shell-app-system-Monitor-for-icon-theme-changes.patch +Patch83: 0004-global-force-fsync-to-worker-thread-when-saving-stat.patch +Patch84: 0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch +Patch85: 0006-js-Always-use-AppSystem-to-lookup-apps.patch + +# Stop screen recording on monitor changes (#1705392) +Patch90: 0001-screencast-Stop-recording-when-screen-size-or-resour.patch + +# Backport OSK fixes (#1871041) +Patch95: osk-fixes.patch +Patch96: 0001-keyboard-Only-enable-keyboard-if-ClutterDeviceManage.patch + +# suspend/resume fix on nvidia (#1663440) +Patch10001: 0001-background-refresh-after-suspend-on-wayland.patch +Patch10002: 0002-background-rebuild-background-not-just-animation-on-.patch +Patch10003: 0003-st-texture-cache-purge-on-resume.patch +Patch10004: 0004-background-refresh-background-on-gl-video-memory-pur.patch + +# Allow login screen extensions (#1651378) +Patch20001: 0001-extensionSystem-Handle-added-or-removed-sessionMode-.patch +Patch20002: 0002-extensionSystem-Get-rid-of-_enabled-boolean-optimiza.patch +Patch20003: 0003-extensionSystem-Allow-extensions-to-run-on-the-login.patch +Patch20004: 0004-sessionMode-Allow-extensions-at-the-login-and-unlock.patch + +# CVE-2020-17489 +Patch30001: 0001-loginDialog-Reset-auth-prompt-on-vt-switch-before-fa.patch + +%define libcroco_version 0.6.8 +%define eds_version 3.17.2 +%define gnome_desktop_version 3.7.90 +%define glib2_version 2.56.0 +%define gobject_introspection_version 1.49.1 +%define gjs_version 1.54.0 +%define gtk3_version 3.15.0 +%define mutter_version 3.32.2-57 +%define polkit_version 0.100 +%define gsettings_desktop_schemas_version 3.32.0-6 +%define ibus_version 1.5.2 +%define gnome_bluetooth_version 1:3.9.0 +%define gstreamer_version 1.4.5 + +BuildRequires: gcc +BuildRequires: meson +BuildRequires: git +BuildRequires: ibus-devel >= %{ibus_version} +BuildRequires: chrpath +BuildRequires: desktop-file-utils +BuildRequires: evolution-data-server-devel >= %{eds_version} +BuildRequires: gcr-devel +BuildRequires: gjs-devel >= %{gjs_version} +BuildRequires: glib2-devel >= %{glib2_version} +BuildRequires: gobject-introspection >= %{gobject_introspection_version} +BuildRequires: mesa-libGL-devel +BuildRequires: mesa-libEGL-devel +BuildRequires: NetworkManager-libnm-devel +BuildRequires: polkit-devel >= %{polkit_version} +BuildRequires: startup-notification-devel +# for theme generation +BuildRequires: sassc +# for screencast recorder functionality +BuildRequires: gstreamer1-devel >= %{gstreamer_version} +BuildRequires: gtk3-devel >= %{gtk3_version} +BuildRequires: gettext >= 0.19.6 +BuildRequires: libcanberra-devel +BuildRequires: libcroco-devel >= %{libcroco_version} +BuildRequires: pkgconfig(libsystemd) +BuildRequires: python3 + +# for barriers +BuildRequires: libXfixes-devel >= 5.0 +# used in unused BigThemeImage +BuildRequires: librsvg2-devel +BuildRequires: mutter-devel >= %{mutter_version} +BuildRequires: pulseaudio-libs-devel +%ifnarch s390 s390x ppc ppc64 ppc64p7 +BuildRequires: gnome-bluetooth-libs-devel >= %{gnome_bluetooth_version} +%endif +#BuildRequires: control-center +# Bootstrap requirements +BuildRequires: gtk-doc +%ifnarch s390 s390x +Requires: gnome-bluetooth%{?_isa} >= %{gnome_bluetooth_version} +%endif +Requires: gnome-desktop3%{?_isa} >= %{gnome_desktop_version} +%if 0%{?rhel} != 7 +# Disabled on RHEL 7 to allow logging into KDE session by default +Requires: gnome-session-xsession +%endif +# wrapper script uses to restart old GNOME session if run --replace +# from the command line +Requires: gobject-introspection%{?_isa} >= %{gobject_introspection_version} +Requires: gjs%{?_isa} >= %{gjs_version} +Requires: gtk3%{?_isa} >= %{gtk3_version} +Requires: libnma%{?_isa} +# needed for loading SVG's via gdk-pixbuf +Requires: librsvg2%{?_isa} +Requires: mutter%{?_isa} >= %{mutter_version} +Requires: upower%{?_isa} +Requires: polkit%{?_isa} >= %{polkit_version} +Requires: gnome-desktop3%{?_isa} >= %{gnome_desktop_version} +Requires: glib2%{?_isa} >= %{glib2_version} +Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} +Requires: libcroco%{?_isa} >= %{libcroco_version} +Requires: gstreamer1%{?_isa} >= %{gstreamer_version} +# needed for schemas +Requires: at-spi2-atk%{?_isa} +# needed for on-screen keyboard +Requires: ibus%{?_isa} >= %{ibus_version} +# needed for the user menu +Requires: accountsservice-libs%{?_isa} +Requires: gdm-libs%{?_isa} +# needed for settings items in menus +Requires: control-center +# needed by some utilities +Requires: python3%{_isa} +# needed for the dual-GPU launch menu +Requires: switcheroo-control +# needed for clocks/weather integration +Requires: geoclue2-libs%{?_isa} +Requires: libgweather%{?_isa} +# needed for thunderbolt support +Requires: bolt%{?_isa} +# Needed for launching flatpak apps etc +Requires: xdg-desktop-portal-gtk + +Provides: PolicyKit-authentication-agent = %{version}-%{release} + +%description +GNOME Shell provides core user interface functions for the GNOME 3 desktop, +like switching to windows and launching applications. GNOME Shell takes +advantage of the capabilities of modern graphics hardware and introduces +innovative user interface concepts to provide a visually attractive and +easy to use experience. + +%prep +%autosetup -S git + +%build +%meson +%meson_build + +%install +%meson_install + +# Create empty directories where other packages can drop extensions +mkdir -p %{buildroot}%{_datadir}/gnome-shell/extensions +mkdir -p %{buildroot}%{_datadir}/gnome-shell/search-providers + +%find_lang %{name} + +%check +desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/gnome-shell-extension-prefs.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/evolution-calendar.desktop + +%files -f %{name}.lang +%license COPYING +%doc README.md +%{_bindir}/gnome-shell +%{_bindir}/gnome-shell-extension-tool +%{_bindir}/gnome-shell-perf-tool +%{_bindir}/gnome-shell-extension-prefs +%{_datadir}/glib-2.0/schemas/*.xml +%{_datadir}/glib-2.0/schemas/00_org.gnome.shell.gschema.override +%{_datadir}/applications/org.gnome.Shell.desktop +%{_datadir}/applications/gnome-shell-extension-prefs.desktop +%{_datadir}/applications/evolution-calendar.desktop +%{_datadir}/applications/org.gnome.Shell.PortalHelper.desktop +%{_datadir}/gnome-control-center/keybindings/50-gnome-shell-system.xml +%{_datadir}/gnome-shell/ +%{_datadir}/dbus-1/services/org.gnome.Shell.CalendarServer.service +%{_datadir}/dbus-1/services/org.gnome.Shell.HotplugSniffer.service +%{_datadir}/dbus-1/services/org.gnome.Shell.PortalHelper.service +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Extensions.xml +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Introspect.xml +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.PadOsd.xml +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Screencast.xml +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Screenshot.xml +%{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider.xml +%{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider2.xml +%{_datadir}/icons/hicolor/scalable/apps/org.gnome.Extensions.svg +%{_datadir}/icons/hicolor/symbolic/apps/org.gnome.Extensions-symbolic.svg +%{_userunitdir}/gnome-shell.service +%{_userunitdir}/gnome-shell-wayland.target +%{_userunitdir}/gnome-shell-x11.target +%{_sysconfdir}/xdg/autostart/gnome-shell-overrides-migration.desktop +# Co own directory instead of pulling in xdg-desktop-portal - we +# are providing a backend to the portal, not depending on it +%dir %{_datadir}/xdg-desktop-portal/portals/ +%{_datadir}/xdg-desktop-portal/portals/gnome-shell.portal +%{_libdir}/gnome-shell/ +%{_libexecdir}/gnome-shell-calendar-server +%{_libexecdir}/gnome-shell-perf-helper +%{_libexecdir}/gnome-shell-hotplug-sniffer +%{_libexecdir}/gnome-shell-portal-helper +%{_libexecdir}/gnome-shell-overrides-migration.sh +# Co own these directories instead of pulling in GConf +# after all, we are trying to get rid of GConf with these files +%dir %{_datadir}/GConf +%dir %{_datadir}/GConf/gsettings +%{_datadir}/GConf/gsettings/gnome-shell-overrides.convert +%{_mandir}/man1/%{name}.1.gz + +%changelog +* Thu Dec 01 2022 Florian Müllner - 3.32.2-50 +- Fix struts on login screen + Resolves: #2138941 +- Fix launching network device settings + Resolves: #1879405 + +* Mon Nov 21 2022 Florian Müllner - 3.32.2-49 +- Fix assert durin StBin destruction + Resolves: #2130131 + +* Fri Aug 12 2022 Florian Müllner - 3.32.2-48 +- Fix warnings on double-disposed backgrounds + Resolves: #2116555 + +* Wed May 11 2022 Phil Wyett - 3.32.2-47 +- Restore missing cursor to screencast recordings + Resolves: #1993420 + +* Wed May 11 2022 Florian Müllner - 3.32.2-46 +- Hide volume sliders initially + Resolves: #1982779 + +* Thu Apr 21 2022 Florian Müllner - 3.32.2-45 +- Fix lock up when previous focus actor is destroyed during modal + Resolves: #2075231 +- Defend against corrupt notifications file + Resolves: #2078564 + +* Fri Nov 26 2021 Florian Müllner - 3.32.2-44 +- Fix more JS warnings + Resolves: #2025940 + +* Thu Oct 21 2021 Florian Müllner - 3.32.2-43 +- Backport fix for CVE-2020-17489 + Resolves: #1874259 + +* Wed Oct 20 2021 Florian Müllner - 3.32.2-42 +- Backport WPA3 support + Resolves: #1924593 + +* Tue Aug 31 2021 Ray Strode - 3.32.2-41 +- Add bugs introduced in backport for #1651378 + Related: #2000918 +- Tidy up patch list a bit + +* Wed Aug 25 2021 Ray Strode - 3.32.2-39 +- Allow extensions on the login screen + Related: #1651378 + +* Thu Jul 29 2021 Florian Müllner - 3.32.2-38 +- Only mask text in password entries + Resolves: #1987233 + +* Wed Jul 28 2021 Florian Müllner - 3.32.2-37 +- Only warn once when not running under GDM + Resolves: #1980661 + +* Tue Jul 20 2021 Ray Strode - 3.32.2-36 +- Add ability to lock down password showing + Resolves: #1770302 +- Add requires on newer mutter version + Related: #1937866 + +* Tue Jul 13 2021 Florian Müllner - 3.32.2-35 +- Improve style of window preview close buttons + Resolves: #1981420 + +* Mon Jul 12 2021 Florian Müllner - 3.32.2-34 +- Add PolicyKit-authentication-agent virtual provides + Resolves: #1978287 + +* Mon Jun 14 2021 Florian Müllner - 3.32.2-33 +- Fix warnings on unlock + Resolves: #1971534 +- Fix gdm lock screen + Resolves: #1971507 + +* Thu Jun 10 2021 Florian Müllner - 3.32.2-32 +- Fix network secret requests on login screen + Related: #1935261 + +* Wed Jun 09 2021 Florian Müllner - 3.32.2-31 +- Backport of touch mode + Resolves: #1937866 + +* Tue Jun 08 2021 Florian Müllner - 3.32.2-30 +- Do not disable network actions on login screen + Resolves: #1935261 + +* Mon Feb 01 2021 Florian Müllner - 3.32.2-29 +- Refuse to override system extensions + Related: #1802105 + +* Mon Jan 25 2021 Florian Müllner - 3.32.2-28 +- Backport extension updates support + Related: #1802105 + +* Thu Jan 14 2021 Florian Müllner - 3.32.2-27 +- Default to printing JS backtrace on segfaults + Resolves: #1883868 + +* Wed Jan 13 2021 Carlos Garnacho - 3.32.2-26 +- Backport OSK fixes + Resolves: #1871041 + +* Tue Jan 12 2021 Jonas Ådahl - 3.32.2-25 +- Stop screen recording on monitor changes + Resolves: #1705392 + +* Thu Jan 07 2021 Florian Müllner - 3.32.2-24 +- Handle workspace from startup notification + Resolves: #1671761 + +* Tue Jan 05 2021 Florian Müllner - 3.32.2-23 +- Work around aggressive garbage collection + Related: #1881312 + +* Fri Oct 23 2020 Florian Müllner - 3.32.2-22 +- Wake up lock screen when deactivated programmatically + Resolves: #1854290 +- Backport better caps-lock warning + Resolves: #1861357 +- Fix more (harmless) JS warnings + Resolves: #1881312 + +* Thu Oct 01 2020 Michael Catanzaro - 3.32.2-21 +- Fix JS warning in AuthList downstream patch + Resolves: #1860946 + +* Thu Jul 30 2020 Florian Müllner - 3.32.2-20 +- Fix popupMenu keynav when NumLock is active + Resolves: #1840080 + +* Mon Jun 15 2020 Florian Müllner - 3.32.2-19 +- Fix last backport + Resolves: #1847051 + +* Sun Jun 14 2020 Florian Müllner - 3.32.2-18 +- Fix more spurious allocation warnings + Resolves: #1715845 + +* Fri May 22 2020 Florian Müllner - 3.32.2-17 +- Really allow using perf-tool on wayland + Resolves: #1652178 +- Fix timed login without user list + Resolves: #1668895 +- Fix HighContrast/symbolic icon mixup + Resolves: #1794045 +- Backport introspect API changes + Resolves: #1837413 + +* Fri Apr 17 2020 Florian Müllner - 3.32.2-16 +- Drop bad upstream patch + Resolves: #1820760 + +* Wed Apr 08 2020 Florian Müllner - 3.32.2-15 +- Improve performance under load + Resolves: #1820760 + +* Wed Mar 04 2020 Carlos Garnacho - 3.32.2-14 +- Do not set Wacom LEDs through gnome-settings-daemon, rely on kernel driver + Resolves: #1687979 + +* Mon Dec 16 2019 Carlos Garnacho - 3.32.2-13 +- Update pad OSD on mode switching + Resolves: #1716774 + +* Fri Dec 13 2019 Carlos Garnacho - 3.32.2-12 +- Fix window dragging with tablets in the overview + Resolves: #1716767 +- Fix high-contrast/symbolic race + Resolves: #1730612 +- Make perf-tool usable on wayland + Resolves: #1652178 + +* Mon Dec 02 2019 Florian Müllner - 3.32.2-11 +- Warn when logging in as root + Resolves: #1746327 + +* Wed Nov 27 2019 Florian Müllner - 3.32.2-10 +- Fix leaks in app picker + Related: #1719819 + +* Thu Aug 15 2019 Jonas Ådahl - 3.32.2-9 +- Depend on correct gsettings-desktop-schemas version + Related: #1704355 + +* Wed Aug 14 2019 Jonas Ådahl - 3.32.2-8 +- Depend on required gsettings-desktop-schemas version + Related: #1704355 + +* Tue Jul 23 2019 Florian Müllner - 3.32.2-7 +- Make the hot corner optional + Resolves: #1704355 + +* Fri Jul 12 2019 Florian Müllner - 3.32.2-6 +- Fix warnings triggered by spurious allocations + Resolves: #1719279 + +* Wed Jul 10 2019 Florian Müllner - 3.32.2-5 +- Fix infinite loop in spinner animation + Resolves: #1725555 + +* Tue Jun 18 2019 Florian Müllner - 3.32.2-4 +- Adjust more shortcut handlers for horizontal workspaces + Related: #1704360 + +* Wed Jun 12 2019 Florian Müllner - 3.32.2-3 +- Support horizontal workspaces in gestures/keybindings/animations + Related: #1704360 + +* Fri May 31 2019 Florian Müllner - 3.32.2-2 +- Adjust downstream patch to mutter changes + Resolves: #1715738 + +* Thu May 23 2019 Florian Müllner - 3.32.2-1 +- Update to 3.32.2 + Resolves: #1698520 + +* Mon Feb 11 2019 Florian Müllner - 3.28.3-10 +- Backport another IM fix (#1668979) + +* Mon Feb 11 2019 Florian Müllner - 3.28.3-9 +- Fix JS "invalid access" warnings (#1642482, #1637622) +- Fix new input sources only working after re-login (#1628154) +- Backport IM support fixes (#1668979) + +* Fri Feb 08 2019 Florian Müllner - 3.28.3-8 +- Re-add dropped downstream patches (rhbz#1668884) + +* Tue Feb 05 2019 Jonas Ådahl - 3.28.3-7 +- Backport dnd crash fix (#1663171) + +* Thu Jan 31 2019 Ray Strode - 3.28.3-6 +- Fix suspend and resume corruption on NVidia + Resolves: #1663440 + +* Wed Jan 16 2019 Jonas Ådahl - 3.28.3-5 +- Backport dnd fix (#1651894) + +* Fri Jan 11 2019 Olivier Fourdan - 3.28.3-4 +- Backport Introspection D-BUS API (rhbz#1658967) + +* Tue Sep 18 2018 David King - 3.28.3-3 +- Improve Python 3 dependency (#1630148) + +* Fri Aug 10 2018 Jonas Ådahl - 3.28.3-2 +- Backport remote access control UI (rhbz#1613749) + +* Fri Aug 10 2018 Kalev Lember - 3.28.3-1 +- Update to 3.28.3 + +* Mon Aug 06 2018 Charalampos Stratakis - 3.28.1-6 +- Remove telepathy-logger and telepathy-glib runtime dependencies + +* Wed Aug 01 2018 Stef Walter - 3.28.1-5 +- Remove unneeded libgnome-keyring dependency (#1589078) + +* Wed Aug 01 2018 Charalampos Stratakis - 3.28.1-4 +- BuildRequire python3-devel + +* Sun Apr 29 2018 Adam Williamson - 3.28.1-3 +- Backport fix for password entry modifier key issues (#1569211) + +* Tue Apr 24 2018 Ray Strode - 3.28.1-2 +- pull polkit cancel lock up from upstream + Resolves: #1568213 + +* Fri Apr 13 2018 Florian Müllner - 3.28.1-1 +- Update to 3.28.1 + +* Mon Mar 12 2018 Florian Müllner - 3.28.0-1 +- Update to 3.28.0 + +* Mon Mar 05 2018 Florian Müllner - 3.27.92-1 +- Update to 3.27.92 + +* Thu Feb 22 2018 Lubomir Rintel - 3.27.91-2 +- Replace libnm-gtk with libnma + +* Wed Feb 21 2018 Florian Müllner - 3.27.91-1 +- Update to 3.27.91 + +* Wed Feb 07 2018 Kalev Lember - 3.27.1-5 +- Rebuilt for evolution-data-server soname bump + +* Mon Jan 22 2018 Adam Williamson - 3.27.1-4 +- Backport fix for crasher bug BGO #788931 (#1469129) + +* Tue Dec 19 2017 Kalev Lember - 3.27.1-3 +- Explicitly require libnm-gtk (#1509496) + +* Wed Nov 08 2017 Milan Crha - 3.27.1-2 +- Rebuild for newer libical + +* Tue Oct 17 2017 Florian Müllner - 3.27.1-1 +- Update to 3.27.1 + +* Wed Oct 04 2017 Florian Müllner - 3.26.1-1 +- Update to 3.26.1 + +* Thu Sep 21 2017 Florian Müllner - 3.26.0-2 +- Fix crash on fast status icon remapping + +* Tue Sep 12 2017 Florian Müllner - 3.26.0-1 +- Update to 3.26.0 + +* Tue Aug 22 2017 Florian Müllner - 3.25.91-1 +- Update to 3.25.91 + +* Fri Aug 11 2017 Kevin Fenzi - 3.25.90-2 +- Rebuild with older working rpm + +* Thu Aug 10 2017 Florian Müllner - 3.25.90-1 +- Update to 3.25.90 + +* Wed Aug 02 2017 Fedora Release Engineering - 3.25.4-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 3.25.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Thu Jul 20 2017 Florian Müllner - 3.25.4-1 +- Update to 3.25.4 + +* Wed Jun 21 2017 Florian Müllner - 3.25.3-1 +- Update to 3.25.3 + +* Thu May 25 2017 Florian Müllner - 3.25.2-1 +- Update to 3.25.2 + +* Thu Apr 27 2017 Florian Müllner - 3.25.1-1 +- Update to 3.25.1 + +* Tue Apr 11 2017 Florian Müllner - 3.24.1-1 +- Update to 3.24.1 + +* Mon Mar 20 2017 Florian Müllner - 3.24.0-1 +- Update to 3.24.0 + +* Thu Mar 16 2017 Igor Gnatenko - 3.23.92-2 +- Fix wrong runtime requirements + +* Tue Mar 14 2017 Florian Müllner - 3.23.92-1 +- Update to 3.23.92 + +* Wed Mar 01 2017 Florian Müllner - 3.23.91-1 +- Update to 3.23.91 + +* Thu Feb 16 2017 Florian Müllner - 3.23.90-1 +- Update to 3.23.90 + +* Tue Feb 14 2017 Richard Hughes - 3.23.3-1 +- Update to 3.23.3 + +* Fri Feb 10 2017 Fedora Release Engineering - 3.23.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Dec 19 2016 Miro Hrončok - 3.23.2-3 +- Rebuild for Python 3.6 + +* Tue Dec 6 2016 Rui Matos - 3.23.2-2 +- Tighten mutter version dependency for plugin API changes + Resolves: #1401886 + +* Wed Nov 23 2016 Florian Müllner - 3.23.2-1 +- Update to 3.23.2 + +* Sun Oct 30 2016 Florian Müllner - 3.23.1-1 +- Update to 3.23.1 + +* Fri Oct 21 2016 Bastien Nocera - 3.22.1-2 +- Add patches to allow launching on discrete GPU when available + +* Tue Oct 11 2016 Florian Müllner - 3.22.1 +- Update to 3.22.1 + +* Mon Sep 19 2016 Florian Müllner - 3.22.0 +- Update to 3.22.0 + +* Tue Sep 13 2016 Florian Müllner - 3.21.92 +- Update to 3.21.92 + +* Fri Sep 09 2016 Kalev Lember - 3.21.91-2 +- Drop libgsystem dependency + +* Tue Aug 30 2016 Florian Müllner - 3.21.91 +- Update to 3.21.91 + +* Sat Aug 20 2016 Florian Müllner - 3.21.90.1-1 +- Update to 3.21.90.1 + (Fixes a corrupt .desktop file that made it from the build directory into + the 3.21.90 tarball) + +* Fri Aug 19 2016 Florian Müllner - 3.21.90-1 +- Update to 3.21.90 + +* Wed Jul 20 2016 Florian Müllner - 3.21.4-1 +- Update to 3.21.4 + +* Mon Jul 18 2016 Milan Crha - 3.21.3-2 +- Rebuild for newer evolution-data-server + +* Tue Jun 21 2016 Florian Müllner - 3.21.3-1 +- Update to 3.21.3 + +* Tue Jun 21 2016 Milan Crha - 3.21.2-2 +- Rebuild for newer evolution-data-server + +* Thu May 26 2016 Florian Müllner - 3.21.2-1 +- Update to 3.21.2 + +* Fri Apr 29 2016 Florian Müllner - 3.21.1-1 +- Update to 3.21.1 + +* Fri Apr 15 2016 David Tardon - 3.20.1-2 +- rebuild for ICU 57.1 + +* Wed Apr 13 2016 Florian Müllner - 3.20.1-1 +- Update to 3.20.1 + +* Tue Mar 22 2016 Florian Müllner - 3.20.0-1 +- Update to 3.20.0 + +* Wed Mar 16 2016 Florian Müllner - 3.19.92-1 +- Update to 3.19.92 + +* Thu Mar 03 2016 Florian Müllner - 3.19.91-1 +- Update to 3.19.91 + +* Fri Feb 19 2016 Florian Müllner - 3.19.90-1 +- Update to 3.19.90 + +* Tue Feb 16 2016 Milan Crha - 3.19.4-3 +- Rebuild for newer evolution-data-server + +* Wed Feb 03 2016 Fedora Release Engineering - 3.19.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Jan 21 2016 Florian Müllner - 3.19.4-1 +- Update to 3.19.4 + +* Mon Jan 18 2016 David Tardon - 3.19.3-2 +- rebuild for libical 2.0.0 + +* Thu Dec 17 2015 Florian Müllner - 3.19.3-1 +- Update to 3.19.3 + +* Tue Dec 01 2015 Kalev Lember - 3.19.2-2 +- Bump gsettings-desktop-schemas dep to 3.19.2 + +* Wed Nov 25 2015 Florian Müllner - 3.19.2-1 +- Update to 3.19.2 + +* Tue Nov 10 2015 Ray Strode 3.19.1-3.20151110 +- Update to git snapshot + +* Sun Nov 01 2015 Kalev Lember - 3.19.1-2 +- Fix gnome-shell crashing in gdm mode (#1276833) + +* Thu Oct 29 2015 Florian Müllner - 3.19.1-1 +- Update to 3.19.1 + +* Thu Oct 15 2015 Florian Müllner - 3.18.1-1 +- Update to 3.18.1 + +* Mon Sep 21 2015 Florian Müllner - 3.18.0-1 +- Update to 3.18.0 + +* Wed Sep 16 2015 Florian Müllner - 3.17.92-1 +- Update to 3.17.92 + +* Thu Sep 03 2015 Florian Müllner - 3.17.91-1 +- Update to 3.17.91 + +* Thu Aug 20 2015 Florian Müllner - 3.17.90-1 +- Update to 3.17.90 + +* Wed Aug 19 2015 Kalev Lember - 3.17.4-2 +- Create empty directories for extensions and search providers +- Move desktop file validation to %%check section +- Use make_install macro + +* Thu Jul 23 2015 Florian Müllner - 3.17.4-1 +- Update to 3.17.4 + +* Wed Jul 22 2015 Milan Crha - 3.17.3-3 +- Rebuild for newer evolution-data-server + +* Sat Jul 04 2015 Kalev Lember - 3.17.3-2 +- Require gobject-introspection 1.45.3 + +* Thu Jul 02 2015 Florian Müllner - 3.17.3-1 +- Update to 3.17.3 + +* Wed Jun 17 2015 Fedora Release Engineering - 3.17.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Wed May 27 2015 Florian Müllner - 3.17.2-1 +- Update to 3.17.2 + +* Thu Apr 30 2015 Florian Müllner - 3.17.1-1 +- Update to 3.17.1 + +* Tue Apr 28 2015 Milan Crha - 3.16.1-2 +- Rebuild for newer evolution-data-server + +* Tue Apr 14 2015 Florian Müllner - 3.16.1-1 +- Update to 3.16.1 + +* Mon Mar 23 2015 Florian Müllner - 3.16.0-1 +- Update to 3.16.0 + +* Tue Mar 17 2015 Kalev Lember - 3.15.92-2 +- Update minimum dep versions +- Use license macro for the COPYING file + +* Tue Mar 17 2015 Florian Müllner - 3.15.92-1 +- Update to 3.15.92 + +* Tue Mar 17 2015 Ray Strode 3.15.91-2 +- Drop dep on NetworkManager-config-connectivity-fedora + It's already required by fedora-release-workstation + +* Wed Mar 04 2015 Florian Müllner - 3.15.91-1 +- Update to 3.15.91 + +* Fri Feb 20 2015 Florian Müllner - 3.15.90-1 +- Update to 3.15.90 + +* Tue Feb 17 2015 Milan Crha - 3.15.4-2 +- Rebuild against newer evolution-data-server + +* Wed Jan 21 2015 Florian Müllner - 3.15.4-1 +- Update to 3.15.4 + +* Fri Dec 19 2014 Florian Müllner - 3.15.3-1 +- Update to 3.15.3 + +* Thu Nov 27 2014 Florian Müllner - 3.15.2-1 +- Update to 3.15.2 + +* Thu Oct 30 2014 Florian Müllner - 3.15.1-1 +- Update to 3.15.1 + +* Tue Oct 14 2014 Florian Müllner - 3.14.1-1 +- Update to 3.14.1 + +* Tue Sep 23 2014 Kalev Lember - 3.14.0-2 +- Drop unused gnome-menus dependency + +* Mon Sep 22 2014 Florian Müllner - 3.14.0-1 +- Update to 3.14.0 + +* Wed Sep 17 2014 Florian Müllner - 3.13.92-1 +- Update to 3.13.92 + +* Wed Sep 03 2014 Florian Müllner - 3.13.91-1 +- Update to 3.13.91 + +* Wed Aug 20 2014 Florian Müllner - 3.13.90-1 +- Update to 3.13.90 + +* Sat Aug 16 2014 Fedora Release Engineering - 3.13.4-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Thu Jul 31 2014 Milan Crha - 3.13.4-3 +- Rebuild against newer evolution-data-server + +* Mon Jul 28 2014 Adel Gadllah - 3.13.4-2 +- Require NetworkManager-config-connectivity-fedora + +* Wed Jul 23 2014 Florian Müllner - 3.13.4-1 +- Update to 3.13.4 + +* Tue Jul 22 2014 Kalev Lember - 3.13.3-2 +- Rebuilt for gobject-introspection 1.41.4 + +* Fri Jun 27 2014 Florian Müllner - 3.13.3-1 +- New gobject-introspection has been built, drop the last patch again + +* Wed Jun 25 2014 Florian Müllner - 3.13.3-1 +- Revert annotation updates until we get a new gobject-introspection build + +* Wed Jun 25 2014 Florian Müllner - 3.13.3-1 +- Update to 3.13.3 + +* Sat Jun 07 2014 Fedora Release Engineering - 3.13.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Tue May 27 2014 Florian Müllner - 3.13.2-1 +- Update to 3.13.2 + +* Thu May 01 2014 Kalev Lember - 3.13.1-2 +- Pull in libgsystem + +* Wed Apr 30 2014 Florian Müllner - 3.13.1-1 +- Update to 3.13.1 + +* Tue Apr 15 2014 Florian Müllner - 3.12.1-1 +- Update to 3.12.1 + +* Sat Apr 05 2014 Kalev Lember - 3.12.0-2 +- Update dep versions + +* Tue Mar 25 2014 Florian Müllner - 3.12.0-1 +- Update to 3.12.0 + +* Wed Mar 19 2014 Florian Müllner - 3.11.92-1 +- Update to 3.11.92 + +* Wed Mar 12 2014 Adam Williamson - 3.11.91-2 +- update to final revision of background bug fix from upstream (BGO #722149) + +* Thu Mar 06 2014 Florian Müllner - 3.11.91-1 +- Update to 3.11.91 + +* Mon Mar 03 2014 Adam Williamson - 3.11.90-5 +- backport fixes to fix drag-and-drop workspace creation (BGO #724686) + +* Wed Feb 26 2014 Adam Williamson - 3.11.90-4 +- backport a couple of bugfixes from BGO for things that annoy me + +* Sat Feb 22 2014 Florian Müllner - 3.11.90-3 +- Add dependency on gnome-control-center - several panels are referenced + by a number of menu items + +* Thu Feb 20 2014 Kalev Lember - 3.11.90-2 +- Rebuilt for cogl soname bump + +* Thu Feb 20 2014 Florian Müllner - 3.11.90-1 +- Update to 3.11.90 + +* Mon Feb 10 2014 Peter Hutterer - 3.11.5-3 +- Rebuild for libevdev soname bump + +* Wed Feb 05 2014 Adam Williamson - 3.11.5-2 +- build against new gjs (and hence mozjs24) + +* Wed Feb 05 2014 Richard Hughes - 3.11.5-1 +- Update to 3.11.5 + +* Mon Feb 03 2014 Milan Crha - 3.11.4-2 +- Rebuild against newer evolution-data-server + +* Thu Jan 16 2014 Florian Müllner - 3.11.4-1 +- Update to 3.11.4 + +* Tue Jan 14 2014 Milan Crha - 3.11.3-2 +- Rebuild against newer evolution-data-server + +* Fri Dec 20 2013 Florian Müllner - 3.11.3-1 +- Update to 3.11.3 + +* Thu Nov 21 2013 Milan Crha - 3.11.2-3 +- Rebuild for new libical (RH bug #1023020) + +* Tue Nov 19 2013 Milan Crha - 3.11.2-2 +- Rebuild against newer evolution-data-server + +* Wed Nov 13 2013 Florian Müllner - 3.11.2-1 +- Update to 3.11.2 + +* Wed Oct 30 2013 Florian Müllner - 3.11.1-1 +- Update to 3.11.1 + +* Fri Oct 25 2013 Florian Müllner - 3.10.1-2 +- Rebuild for new e-d-s + +* Tue Oct 15 2013 Florian Müllner - 3.10.1-1 +- Update to 3.10.1 + +* Wed Sep 25 2013 Kalev Lember - 3.10.0.1-1 +- Update to 3.10.0.1 + +* Tue Sep 24 2013 Florian Müllner - 3.10.0-1 +- Update to 3.10.0 + +* Wed Sep 18 2013 Matthias Clasen - 3.9.92-3 +- Build against mutter-wayland + +* Tue Sep 17 2013 Florian Müllner - 3.9.92-1 +- Update to 3.9.92 + +* Tue Sep 03 2013 Florian Müllner - 3.9.91-1 +- Update to 3.9.91 + +* Thu Aug 22 2013 Florian Müllner - 3.9.90-1 +- Update to 3.9.90 + +* Mon Aug 19 2013 Adam Williamson - 3.9.5-3 +- Rebuild for new e-d-s + +* Sat Aug 10 2013 Kalev Lember - 3.9.5-2 +- Drop the bluez revert patch as we now have new enough gnome-bluetooth + +* Tue Jul 30 2013 Florian Müllner - 3.9.5 +- Update to 3.9.5 + +* Mon Jul 29 2013 Adam Williamson - 3.9.4-2 +- rebuild against updated evolution-data-server + +* Wed Jul 10 2013 Florian Müllner - 3.9.4-1 +- Update to 3.9.4 + +* Wed Jul 10 2013 Milan Crha - 3.9.3-3 +- Rebuild against newer evolution-data-server + +* Wed Jul 10 2013 Kalev Lember - 3.9.3-2 +- Add a downstream patch to revert back to bluez 4 + +* Tue Jun 18 2013 Florian Müllner - 3.9.3-1 +- Update to 3.9.3 + +* Tue May 28 2013 Florian Müllner - 3.9.2-1 +- Update to 3.9.2 + +* Sat May 25 2013 Rex Dieter 3.9.1-3 +- rebuild (libical) + +* Wed May 01 2013 Kalev Lember - 3.9.1-2 +- Add missing telepathy-logger runtime dep +- Depend on gnome-session-xsession so that it gets pulled in for + typical GNOME installs + +* Wed May 01 2013 Florian Müllner - 3.9.1-1 +- Update to 3.9.1 + +* Tue Apr 16 2013 Florian Müllner - 3.8.1-1 +- Update to 3.8.1 + +* Thu Mar 28 2013 Adel Gadllah - 3.8.0.1-2 +- Ship the perf tool + +* Wed Mar 27 2013 Ray Strode - 3.8.0.1-1 +- Update to 3.8.0.1 + +* Tue Mar 26 2013 Florian Müllner - 3.8.0-1 +- Update to 3.8.0 + +* Tue Mar 19 2013 Florian Müllner - 3.7.92-1 +- Update to 3.7.92 + +* Tue Mar 05 2013 Florian Müllner - 3.7.91-1 +- Update to 3.7.91 + +* Wed Feb 20 2013 Florian Müllner - 3.7.90-1 +- Update to 3.7.90 + +* Wed Feb 06 2013 Kalev Lember - 3.7.5-2 +- Rebuilt for libgcr soname bump + +* Wed Feb 06 2013 Florian Müllner - 3.7.5-1 +- Update to 3.7.5 + +* Fri Jan 25 2013 Peter Robinson 3.7.4.1-2 +- Rebuild for new cogl + +* Thu Jan 17 2013 Florian Müllner - 3.7.4.1-1 +- Update to 3.7.4.1 + +* Tue Jan 15 2013 Florian Müllner - 3.7.4-1 +- Update to 3.7.4 + +* Wed Jan 09 2013 Richard Hughes - 3.7.3.1-1 +- Update to 3.7.3.1 + +* Tue Dec 18 2012 Florian Müllner 3.7.3-1 +- Update to 3.7.3 + +* Mon Dec 17 2012 Adam Jackson 3.7.2-3 +- Also don't mangle rpath on power + +* Mon Dec 10 2012 Adam Jackson 3.7.2-2 +- Disable bluetooth on power + +* Mon Nov 19 2012 Florian Müllner - 3.7.2-1 +- Update to 3.7.2 + +* Tue Nov 13 2012 Dan Horák - 3.7.1-2 +- don't Require: gnome-bluetooth on s390(x) + +* Fri Nov 09 2012 Kalev Lember - 3.7.1-1 +- Update to 3.7.1 + +* Wed Oct 31 2012 Brian Pepple - 3.6.1-5 +- Rebuild against latest telepathy-logger + +* Thu Oct 25 2012 Milan Crha - 3.6.1-4 +- Rebuild against newer evolution-data-server + +* Sat Oct 20 2012 Dan Horák - 3.6.1-3 +- explicit BR: control-center as it isn't brought in indirectly on s390(x) + +* Thu Oct 18 2012 Florian Müllner - 3.6.1-2 +- Remove avoid-redhat-menus patch + + The standard way of supporting a desktop-specific menu layout is + to set XDG_MENU_PREFIX (which we made gnome-session do now). + +* Mon Oct 15 2012 Florian Müllner - 3.6.1-1 +- Update to 3.6.1 + +* Tue Sep 25 2012 Florian Müllner - 3.6.0-1 +- Update to 3.6.0 + +* Wed Sep 19 2012 Florian Müllner - 3.5.92-1 +- Update to 3.5.92 + +* Tue Sep 11 2012 Florian Müllner - 3.5.91-1 +- Update dependencies + +* Tue Sep 04 2012 Richard Hughes - 3.5.91-1 +- Update to 3.5.91 + +* Tue Aug 28 2012 Matthias Clasen - 3.5.90-3 +- Rebuild against new cogl/clutter + +* Mon Aug 27 2012 Debarshi Ray - 3.5.90-2 +- Rebuild for new libcamel and synchronize gnome-bluetooth Requires with + BuildRequires. + +* Wed Aug 22 2012 Richard Hughes - 3.5.90-1 +- Update to 3.5.90 + +* Tue Aug 14 2012 Debarshi Ray - 3.5.5-2 +- Add Requires: gnome-bluetooth >= 3.5.5 + +* Mon Aug 13 2012 Debarshi Ray - 3.5.5-1 +- Update to 3.5.5 + +* Fri Jul 27 2012 Fedora Release Engineering - 3.5.4-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Sat Jul 21 2012 Kalev Lember - 3.5.4-4 +- Tighten runtime requires + +* Thu Jul 19 2012 Matthias Clasen - 3.5.4-3 +- Add a gdm-libs dependency + +* Wed Jul 18 2012 Colin Walters - 3.5.4-2 +- Bump release + +* Wed Jul 18 2012 Ray Strode 3.5.4-1 +- Update to 3.5.4 + +* Tue Jun 26 2012 Matthias Clasen - 3.5.3-2 +- Rebuild against new e-d-s + +* Tue Jun 26 2012 Matthias Clasen - 3.5.3-1 +- Update to 3.5.3 + +* Thu Jun 07 2012 Richard Hughes - 3.5.2-2 +- Remove upstreamed patch + +* Thu Jun 07 2012 Richard Hughes - 3.5.2-1 +- Update to 3.5.2 + +* Mon May 28 2012 Peter Robinson - 3.4.1-6 +- Cherry pick F17 changes, bump build for new evo soname + +* Wed May 16 2012 Owen Taylor - 3.4.1-5 +- New version of unmount notification + +* Tue May 15 2012 Owen Taylor - 3.4.1-4 +- Add a patch to display a notification until it's safe to remove a drive (#819492) + +* Fri Apr 20 2012 Owen Taylor - 3.4.1-3 +- Add a patch from upstream to avoid a crash when Evolution is not installed (#814401) + +* Wed Apr 18 2012 Kalev Lember - 3.4.1-2 +- Silence glib-compile-schemas scriplets + +* Wed Apr 18 2012 Kalev Lember - 3.4.1-1 +- Update to 3.4.1 + +* Thu Apr 5 2012 Owen Taylor - 3.4.0-2 +- Change gnome-shell-favourite-apps-firefox.patch to also patch the JS code + to handle the transition from mozilla-firefox.desktop to firefox.desktop. + (#808894, reported by Jonathan Kamens) + +* Tue Mar 27 2012 Richard Hughes - 3.4.0-1 +- Update to 3.4.0 + +* Wed Mar 21 2012 Matthias Clasen - 3.3.92-1 +- Update to 3.3.92 + +* Sat Mar 10 2012 Matthias Clasen - 3.3.90-2 +- Rebuild for new cogl + +* Sat Feb 25 2012 Matthias Clasen - 3.3.90-1 +- Update to 3.3.90 + +* Thu Feb 9 2012 Matthias Clasen - 3.3.5-2 +- Depend on accountsservice-libs (#755112) + +* Tue Feb 7 2012 Matthias Clasen - 3.3.5-1 +- Update to 3.3.5 + +* Fri Jan 20 2012 Matthias Clasen - 3.3.4-1 +- Update to 3.3.4 + +* Thu Jan 19 2012 Matthias Clasen - 3.3.3-2 +- Rebuild for new cogl + +* Thu Jan 5 2012 Matthias Clasen - 3.3.3-1 +- Update to 3.3.3 + +* Sun Nov 27 2011 Peter Robinson - 3.3.2-2 +- Rebuild for new clutter and e-d-s + +* Wed Nov 23 2011 Matthias Clasen - 3.3.2-1 +- Update to 3.3.2 + +* Wed Nov 09 2011 Kalev Lember - 3.2.1-6 +- Adapt to firefox desktop file name change in F17 + +* Thu Nov 03 2011 Adam Jackson 3.2.1-5 +- Build with -Wno-error=disabled-declarations for the moment + +* Wed Nov 02 2011 Brian Pepple - 3.2.1-4 +- Rebuld against tp-logger. + +* Sun Oct 30 2011 Bruno Wolff III - 3.2.1-3 +- Rebuild for new evolution-data-server + +* Wed Oct 26 2011 Fedora Release Engineering - 3.2.1-2 +- Rebuilt for glibc bug#747377 + +* Wed Oct 19 2011 Matthias Clasen - 3.2.1-1 +- Update to 3.2.1 + +* Wed Sep 28 2011 Ray Strode 3.2.0-2 +- rebuild + +* Mon Sep 26 2011 Owen Taylor - 3.2.0-1 +- Update to 3.2.0 + +* Tue Sep 20 2011 Matthias Clasen - 3.1.92-1 +- Update to 3.1.92 + +* Fri Sep 16 2011 Kalev Lember - 3.1.91.1-2 +- Tighten dependencies by specifying the required arch (#739130) + +* Wed Sep 14 2011 Owen Taylor - 3.1.91.1-1 +- Update to 3.1.91.1 (adds browser plugin) + Update Requires + +* Thu Sep 08 2011 Dan Horák - 3.1.91-3 +- workaround a chrpath issue on s390(x) + +* Wed Sep 07 2011 Kalev Lember - 3.1.91-2 +- Replace Epiphany with Firefox in the default favourite apps + +* Wed Sep 7 2011 Matthias Clasen - 3.1.91-1 +- Update to 3.1.91 + +* Thu Sep 1 2011 Matthias Clasen - 3.1.90.1-2 +- Require caribou + +* Wed Aug 31 2011 Matthias Clasen - 3.1.90.1-1 +- Update to 3.1.90.1 + +* Wed Aug 31 2011 Adam Williamson - 3.1.4-3.gite7b9933 +- rebuild against e-d-s + +* Fri Aug 19 2011 Matthias Clasen - 3.1.4-2.gite7b9933 +- git snapshot that builds against gnome-menus 3.1.5 + +* Thu Aug 18 2011 Matthew Barnes - 3.1.5-1 +- Rebuild against newer eds libraries. + +* Wed Jul 27 2011 Matthias Clasen - 3.1.4-1 +- Update to 3.1.4 + +* Wed Jul 27 2011 Matthias Clasen - 3.1.3-4 +- Rebuild + +* Tue Jul 26 2011 Matthias Clasen - 3.1.3-3 +- Add necessary requires + +* Mon Jul 25 2011 Matthias Clasen - 3.1.3-2 +- Rebuild + +* Tue Jul 5 2011 Peter Robinson - 3.1.3-1 +- Upstream 3.1.3 dev release + +* Mon Jun 27 2011 Adam Williamson - 3.0.2-4 +- add fixes from f15 branch (gjs dep and rpath) + +* Wed Jun 22 2011 Owen Taylor - 3.0.2-3 +- Add a patch from upstream to avoid g_file_get_contents() + +* Fri Jun 17 2011 Tomas Bzatek - 3.0.2-2 +- Rebuilt for new gtk3 and gnome-desktop3 + +* Wed May 25 2011 Owen Taylor - 3.0.2-1 +- Update to 3.0.2 + +* Tue May 10 2011 Dan Williams - 3.0.1-4 +- Fix initial connections to WPA Enterprise access points (#699014) +- Fix initial connections to mobile broadband networks + +* Thu Apr 28 2011 Dan Horák - 3.0.1-3 +- no bluetooth on s390(x) + +* Wed Apr 27 2011 Owen Taylor - 3.0.1-2 +- Add a patch from upstream to fix duplicate applications in application display + +* Mon Apr 25 2011 Owen Taylor - 3.0.1-1 +- Update to 3.0.1 + +* Mon Apr 11 2011 Colin Walters - 3.0.0.2-2 +- We want to use the GNOME menus which has the designed categories, + not the legacy redhat-menus. + +* Fri Apr 08 2011 Nils Philippsen - 3.0.0.2-1 +- Update to 3.0.0.2 (fixes missing import that was preventing extensions from + loading.) +- Update source URL + +* Tue Apr 5 2011 Owen Taylor - 3.0.0.1-1 +- Update to 3.0.0.1 (fixes bug where network menu could leave + Clutter event handling stuck.) + +* Mon Apr 4 2011 Owen Taylor - 3.0.0-1 +- Update to 3.0.0 + +* Tue Mar 29 2011 Brian Pepple - 2.91.93-3 +- Bump + +* Tue Mar 29 2011 Brian Pepple - 2.91.93-2 +- Rebuild for new tp-logger + +* Mon Mar 28 2011 Owen Taylor - 2.91.93-1 +- Update to 2.91.93. + +* Fri Mar 25 2011 Ray Strode 2.91.92-3 +- Adjustments for More nm-client api changes. +- Fix VPN indicator + +* Thu Mar 24 2011 Christopher Aillon - 2.91.92-2 +- Make activating vpn connections work from the shell indicator + +* Wed Mar 23 2011 Matthias Clasen - 2.91.92-1 +- Update to 2.91.92 + +* Wed Mar 16 2011 Michel Salim - 2.91.91-2 +- Fix alt-tab behavior on when primary display is not leftmost (# 683932) + +* Tue Mar 8 2011 Owen Taylor - 2.91.91-1 +- Update to 2.91.91 + +* Tue Feb 22 2011 Matthias Clasen - 2.91.90-2 +- Require upower and polkit at runtime + +* Tue Feb 22 2011 Matthias Clasen - 2.91.90-1 +- Update to 2.91.90 + +* Thu Feb 10 2011 Matthias Clasen - 2.91.6-6 +- Rebuild against newer gtk + +* Tue Feb 08 2011 Fedora Release Engineering - 2.91.6-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Thu Feb 3 2011 Bill Nottingham - 2.91.6-4 +- buildrequire gnome-bluetooth to fix bluetooth status icon (#674874) + +* Wed Feb 2 2011 Matthias Clasen - 2.91.6-3 +- Rebuild against newer gtk + +* Tue Feb 1 2011 Owen Taylor - 2.91.6-2 +- Build-requires evolution-data-server-devel + +* Tue Feb 1 2011 Owen Taylor - 2.91.6-1 +- Update to 2.91.6 + +* Thu Jan 13 2011 Mattihas Clasen - 2.91.5-3 +- Drop desktop-effects dependency + +* Wed Jan 12 2011 Colin Walters - 2.91.5-2 +- BR latest g-i, handles flags as arguments better + +* Tue Jan 11 2011 Matthias Clasen - 2.91.5-1 +- Update to 2.91.5 + +* Sat Jan 8 2011 Matthias Clasen - 2.91.4-1 +- Update to 2.91.4 +- Rebuild against new gtk + +* Fri Dec 3 2010 Matthias Clasen - 2.91.3-2 +- Rebuild aginst new gtk + +* Mon Nov 29 2010 Owen Taylor - 2.91.2-1 +- Update to 2.91.3 + +* Thu Nov 18 2010 Owen Taylor - 2.91.2-3 +- Add another memory-management crasher fix from upstream + +* Mon Nov 15 2010 Owen Taylor - 2.91.2-2 +- Add a patch from upstream fixing a memory-management crasher + +* Tue Nov 9 2010 Owen Taylor - 2.91.2-1 +- Update to 2.91.2 + +* Mon Nov 1 2010 Owen Taylor - 2.91.1-1 +- Update to 2.91.1 +- Add libcroco-devel to BuildRequires, apparently it was getting + pulled in indirectly before +- Add libcanberra-devel and pulseaudio-libs-devel BuildRequires + +* Mon Oct 4 2010 Owen Taylor - 2.91.0-1 +- Update to 2.91.0 +- Remove patch to disable VBlank syncing + +* Thu Aug 12 2010 Colin Walters - 2.31.5-7 +- Add patch to disable vblank syncing + +* Tue Jul 13 2010 Colin Walters - 2.31.5-5 +- Run glib-compile-schemas + +* Tue Jul 13 2010 Colin Walters - 2.31.5-4 +- Bless stuff in files section + +* Tue Jul 13 2010 Colin Walters - 2.31.5-3 +- Axe gnome-desktop-devel + +* Tue Jul 13 2010 Adel Gadllah - 2.31.5-2 +- BuildRequire gnome-desktop3-devel, gtk3 + +* Mon Jul 12 2010 Colin Walters - 2.31.5-1 +- New upstream version +- Drop rpath goop, shouldn't be necessary any more + +* Fri Jun 25 2010 Colin Walters - 2.31.2-3 +- Drop gir-repository-devel build dependency + +* Fri May 28 2010 Adam Miller - 2.31.2-2 +- Added new version requirements for dependencies based on upstream releases +- Added new file listings for gnome-shell-clock-preferences binary and .desktop +- Added gnome-shell man page file listing + +* Wed May 26 2010 Adam Miller - 2.31.2-1 +- New upstream release + +* Fri Mar 26 2010 Colin Walters - 2.29.1-3 +- Specify V=1 for build, readd smp_mflags since parallel is fixed upstream + +* Thu Mar 25 2010 Adam Miller - 2.29.1-2 +- Bumped for new version of mutter and clutter +- Added version requirement to gjs-devel because of dependency of build + +* Wed Mar 24 2010 Adam Miller - 2.29.1-1 +- Update to latest version 2.29.1 + +* Sun Feb 21 2010 Bastien Nocera 2.28.1-0.2.20100128git +- Require json-glib +- Rebuild for new clutter with json split out +- Fix deprecation in COGL + +* Thu Jan 28 2010 Adam Miller - 2.28.1-0.1.20100128git +- New git snapshot +- Fixed Version for alphatag use + +* Fri Jan 15 2010 Adam Miller - 2.28.0.20101015git-1 +- Added dependency on a git build of gobject-introspect to solve some breakage +- Also went ahead and made a new git tarball + +* Tue Jan 12 2010 Adam Miller - 2.28.0.20100112git-1 +- New git snapshot + +* Mon Dec 07 2009 Adam Miller - 2.28.0.20091206git-5 +- Added libtool, glib-gettext for the libtoolize dep of git snapshot + +* Mon Dec 07 2009 Adam Miller - 2.28.0.20091206git-4 +- Added gnome-common needed by autogen.sh in git snapshot build + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-3 +- Added the autotools needed to build the git snapshot to the build requires + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-2 +- Fixed the setup naming issue with the git snapshot directory naming + +* Sun Dec 06 2009 Adam Miller - 2.28.0.20091206git-1 +- Update to git snapshot on 20091206 + +* Wed Oct 7 2009 Owen Taylor - 2.28.0-2 +- Update to 2.28.0 + +* Tue Sep 15 2009 Owen Taylor - 2.27.3-1 +- Update to 2.27.3 + +* Fri Sep 4 2009 Owen Taylor - 2.27.2-2 +- Test for gobject-introspection version should be >= not > + +* Fri Sep 4 2009 Owen Taylor - 2.27.2-1 +- Update to 2.27.2 +- Add an explicit dep on gobject-introspection 0.6.5 which is required + for the new version + +* Sat Aug 29 2009 Owen Taylor - 2.27.1-4 +- Fix GConf %%preun script to properly be for package removal + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-3 +- Replace libgnomeui with gnome-desktop in BuildRequires + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-2 +- BuildRequire intltool +- Add find_lang + +* Fri Aug 28 2009 Owen Taylor - 2.27.1-1 +- Update to 2.27.1 +- Update Requires, add desktop-effects + +* Wed Aug 12 2009 Owen Taylor - 2.27.0-4 +- Add an explicit dependency on GConf2 for pre/post + +* Tue Aug 11 2009 Owen Taylor - 2.27.0-3 +- Add missing BuildRequires on gir-repository-devel + +* Tue Aug 11 2009 Owen Taylor - 2.27.0-2 +- Temporarily use a non-parallel-build until gnome-shell is fixed + +* Mon Aug 10 2009 Owen Taylor - 2.27.0-1 +- Initial version