From 55e4312ca7c2d493952bcbac7928bb0de828fac2 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Fri, 22 May 2026 10:30:35 +0300 Subject: [PATCH] Import CS --- .gitignore | 2 +- ...js-1.81.2-for-build-because-Intl.Seg.patch | 27 + ...rt-data-Drop-org.gnome.Shell.desktop.patch | 79 + ...yboard-Limit-the-input-method-indica.patch | 45 + ...web-login-and-unified-auth-mechanism.patch | 9185 +++++++++++++++++ ...-to-window-title-instead-of-WM_CLASS.patch | 8 +- ...settings-desktop-schemas-requirement.patch | 27 + 0001-data-Update-generated-stylesheets.patch | 2692 ++++- ...der-Refuse-to-override-system-extens.patch | 8 +- ...Work-around-failing-fingerprint-auth.patch | 106 +- ...n-Dump-stack-on-segfaults-by-default.patch | 10 +- ...Register-session-with-GDM-on-startup.patch | 46 + ...el-Use-branding-in-activities-button.patch | 12 +- ...eld-unblank-when-inserting-smartcard.patch | 8 +- 0001-st-texture-cache-purge-on-resume.patch | 18 +- ...tionally-allow-restart-shutdown-on-l.patch | 64 - 0001-theme-Welcome-Illustration.patch | 6 +- ...-back-workspaces-submenu-for-static-.patch | 8 +- ...otify-gnome-session-when-we-re-ready.patch | 46 + disable-unlock-entry-until-question.patch | 38 +- enforce-smartcard-at-unlock.patch | 20 +- fix-some-js-warnings.patch | 16 +- gdm-support-banner-message-file.patch | 232 - ...-enabled-extensions-background-logos.patch | 6 +- gnome-shell-favourite-apps-firefox.patch | 25 +- gnome-shell-favourite-apps-terminal.patch | 12 +- gnome-shell.spec | 121 +- revert-gir-2.0-port.patch | 199 + screenshot-tool.patch | 453 + sources | 2 +- tweak-app-defaults.patch | 384 - 31 files changed, 13051 insertions(+), 854 deletions(-) create mode 100644 0001-Revert-Require-gjs-1.81.2-for-build-because-Intl.Seg.patch create mode 100644 0001-Revert-data-Drop-org.gnome.Shell.desktop.patch create mode 100644 0001-Revert-status-keyboard-Limit-the-input-method-indica.patch create mode 100644 0001-Support-for-web-login-and-unified-auth-mechanism.patch create mode 100644 0001-build-Lower-gsettings-desktop-schemas-requirement.patch create mode 100644 0001-main-Register-session-with-GDM-on-startup.patch delete mode 100644 0001-systemActions-Optionally-allow-restart-shutdown-on-l.patch create mode 100644 0002-Reapply-main-Notify-gnome-session-when-we-re-ready.patch delete mode 100644 gdm-support-banner-message-file.patch create mode 100644 revert-gir-2.0-port.patch create mode 100644 screenshot-tool.patch delete mode 100644 tweak-app-defaults.patch diff --git a/.gitignore b/.gitignore index 0547dec..031b9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -gnome-shell-47.4.tar.xz +gnome-shell-49.4.tar.xz diff --git a/0001-Revert-Require-gjs-1.81.2-for-build-because-Intl.Seg.patch b/0001-Revert-Require-gjs-1.81.2-for-build-because-Intl.Seg.patch new file mode 100644 index 0000000..b67e36d --- /dev/null +++ b/0001-Revert-Require-gjs-1.81.2-for-build-because-Intl.Seg.patch @@ -0,0 +1,27 @@ +From 67bb91b741b5a30624c02cbc65c9f2a1bfe4ac6f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 27 Aug 2025 13:25:01 +0200 +Subject: [PATCH] Revert "Require gjs >= 1.81.2 for build because + Intl.Segmenter is needed" + +This reverts commit 9ce0cf036e4b22b6933e5425a0604fd0135d8b31. +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 327e5f0b7e..f1df40f702 100644 +--- a/meson.build ++++ b/meson.build +@@ -23,7 +23,7 @@ eds_req = '>= 3.33.1' + gcr_req = '>= 3.90.0' + gio_req = '>= 2.79.2' + gi_req = '>= 1.49.1' +-gjs_req = '>= 1.81.2' ++gjs_req = '>= 1.73.1' + gtk_req = '>= 4.0' + mutter_req = '>= 49.0' + polkit_req = '>= 0.100' +-- +2.51.1 + diff --git a/0001-Revert-data-Drop-org.gnome.Shell.desktop.patch b/0001-Revert-data-Drop-org.gnome.Shell.desktop.patch new file mode 100644 index 0000000..bcea6b2 --- /dev/null +++ b/0001-Revert-data-Drop-org.gnome.Shell.desktop.patch @@ -0,0 +1,79 @@ +From 622ee57d6b53011d11e7e22f741766af6424d398 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 13 Nov 2025 12:57:55 +0100 +Subject: [PATCH 1/2] Revert "data: Drop org.gnome.Shell.desktop" + +This reverts commit f3911151da7b0e3c630011cb26bd85f719e531d2. +--- + data/meson.build | 13 ++++++++++++- + ....in => org.gnome.Shell.Extensions.desktop.in.in} | 0 + ...n => org.gnome.Shell.PortalHelper.desktop.in.in} | 0 + data/org.gnome.Shell.desktop.in.in | 13 +++++++++++++ + 4 files changed, 25 insertions(+), 1 deletion(-) + rename data/{org.gnome.Shell.Extensions.desktop.in => org.gnome.Shell.Extensions.desktop.in.in} (100%) + rename data/{org.gnome.Shell.PortalHelper.desktop.in => org.gnome.Shell.PortalHelper.desktop.in.in} (100%) + create mode 100644 data/org.gnome.Shell.desktop.in.in + +diff --git a/data/meson.build b/data/meson.build +index cc7b5e2a1f..76a32ca59f 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -1,6 +1,7 @@ + data_builddir = meson.current_build_dir() + + desktop_files = [ ++ 'org.gnome.Shell.desktop', + 'org.gnome.Shell.Extensions.desktop', + ] + service_files = [] +@@ -10,9 +11,19 @@ if have_portal_helper + service_files += 'org.gnome.Shell.PortalHelper.service' + endif + ++desktopconf = configuration_data() ++# We substitute in bindir so it works as an autostart ++# file when built in a non-system prefix ++desktopconf.set('bindir', bindir) ++desktopconf.set('systemd_hidden', have_systemd ? 'true' : 'false') ++ + foreach desktop_file : desktop_files + i18n.merge_file( +- input: desktop_file + '.in', ++ input: configure_file( ++ input: desktop_file + '.in.in', ++ output: desktop_file + '.in', ++ configuration: desktopconf ++ ), + output: desktop_file, + po_dir: po_dir, + install: true, +diff --git a/data/org.gnome.Shell.Extensions.desktop.in b/data/org.gnome.Shell.Extensions.desktop.in.in +similarity index 100% +rename from data/org.gnome.Shell.Extensions.desktop.in +rename to data/org.gnome.Shell.Extensions.desktop.in.in +diff --git a/data/org.gnome.Shell.PortalHelper.desktop.in b/data/org.gnome.Shell.PortalHelper.desktop.in.in +similarity index 100% +rename from data/org.gnome.Shell.PortalHelper.desktop.in +rename to data/org.gnome.Shell.PortalHelper.desktop.in.in +diff --git a/data/org.gnome.Shell.desktop.in.in b/data/org.gnome.Shell.desktop.in.in +new file mode 100644 +index 0000000000..59d1cd2fe9 +--- /dev/null ++++ b/data/org.gnome.Shell.desktop.in.in +@@ -0,0 +1,13 @@ ++[Desktop Entry] ++Type=Application ++Name=GNOME Shell ++Comment=Window management and application launching ++Exec=@bindir@/gnome-shell ++Categories=GNOME;GTK;Core; ++OnlyShowIn=GNOME; ++NoDisplay=true ++X-GNOME-Autostart-Phase=DisplayServer ++X-GNOME-Provides=panel;windowmanager; ++X-GNOME-Autostart-Notify=true ++X-GNOME-AutoRestart=false ++X-GNOME-HiddenUnderSystemd=@systemd_hidden@ +-- +2.51.1 + diff --git a/0001-Revert-status-keyboard-Limit-the-input-method-indica.patch b/0001-Revert-status-keyboard-Limit-the-input-method-indica.patch new file mode 100644 index 0000000..655f3f0 --- /dev/null +++ b/0001-Revert-status-keyboard-Limit-the-input-method-indica.patch @@ -0,0 +1,45 @@ +From df7e3d8e4e4f5d6d875d1c1a48d23475dc98dc80 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 7 Nov 2025 19:57:25 +0100 +Subject: [PATCH] Revert "status/keyboard: Limit the input method indicator to + < 3 grapheme clusters" + +The change requires support for Intl.Segmenter that was introduced +in mozjs-125. RHEL still includes an older gjs release based on +mozjs-115, so revert the change. + +This reverts commit f2246442a0ade263a1f9e775f02818bbd2862ae8. +--- + js/ui/status/keyboard.js | 9 +-------- + 1 file changed, 1 insertion(+), 8 deletions(-) + +diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js +index 62f1e731db..7f00e289e3 100644 +--- a/js/ui/status/keyboard.js ++++ b/js/ui/status/keyboard.js +@@ -1005,12 +1005,6 @@ class InputSourceIndicator extends PanelMenu.Button { + } + } + +- _getGraphemeClusters(text = '') { +- const segmenter = new Intl.Segmenter(undefined, {granularity: 'grapheme'}); +- const segments = [...segmenter.segment(text)].map(o => o.segment); +- return segments; +- } +- + _buildPropSubMenu(menu, props) { + if (!props) + return; +@@ -1034,8 +1028,7 @@ class InputSourceIndicator extends PanelMenu.Button { + let currentSource = this._inputSourceManager.currentSource; + if (currentSource) { + let indicatorLabel = this._indicatorLabels[currentSource.index]; +- const graphemeClusters = this._getGraphemeClusters(text); +- if (graphemeClusters.length > 0 && graphemeClusters.length < 3) ++ if (text && text.length > 0 && text.length < 3) + indicatorLabel.set_text(text); + } + } +-- +2.51.1 + diff --git a/0001-Support-for-web-login-and-unified-auth-mechanism.patch b/0001-Support-for-web-login-and-unified-auth-mechanism.patch new file mode 100644 index 0000000..273c3a1 --- /dev/null +++ b/0001-Support-for-web-login-and-unified-auth-mechanism.patch @@ -0,0 +1,9185 @@ +From 3c1861b42dfcefafa240bae0270702c7d9c9a2fd Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 2 Oct 2025 10:59:57 +0200 +Subject: [PATCH 01/42] style: Add common login dialog button styles to avoid + duplication + +This will be used in next commits, when new login buttons are added. + +Also, add a missing insensitive_button_bg_color on lockscren buttons, +without it the button was being dark when insensitive. +--- + data/theme/gnome-shell-sass/_common.scss | 11 +++++++++++ + data/theme/gnome-shell-sass/_drawing.scss | 1 + + .../gnome-shell-sass/widgets/_login-lock.scss | 16 ++-------------- + 3 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss +index 846427e8e0..1f14286f9f 100644 +--- a/data/theme/gnome-shell-sass/_common.scss ++++ b/data/theme/gnome-shell-sass/_common.scss +@@ -378,3 +378,14 @@ stage { + StLabel.hint-text { color: transparentize($system_fg_color, 0.3);} + } + ++/* Login Dialog Elements */ ++ ++@mixin login_dialog_item_button($style: normal) { ++ @extend %button_common; ++ @include button(normal, $tc:$system_fg_color, $c:$system_base_color, $style: $style, $always_dark: true); ++ &:selected, ++ &:focus { @include button(focus, $tc:$system_fg_color, $c:$system_base_color, $style: $style, $always_dark: true);} ++ &:hover { @include button(hover, $tc:$system_fg_color, $c:$system_base_color, $style: $style, $always_dark: true);} ++ &:active { @include button(active, $tc:$system_fg_color, $c:$system_base_color, $style: $style, $always_dark: true);} ++ &:insensitive { @include button(insensitive, $tc:$system_fg_color, $c:$system_base_color, $style: $style, $always_dark: true);} ++} +diff --git a/data/theme/gnome-shell-sass/_drawing.scss b/data/theme/gnome-shell-sass/_drawing.scss +index d98cb49397..f1e82176a4 100644 +--- a/data/theme/gnome-shell-sass/_drawing.scss ++++ b/data/theme/gnome-shell-sass/_drawing.scss +@@ -219,6 +219,7 @@ + $hover_button_bg_color: transparentize($tc, .87); + $active_button_bg_color: transparentize($tc, .84); + $active_hover_button_bg_color: transparentize($tc, .81); ++ $insensitive_button_bg_color: transparentize($tc, .9); + } + + // background color overrides for notification style +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index b661e93c8d..6cca1e28e9 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -137,12 +137,7 @@ $_gdm_dialog_width: 25em; + + .login-dialog { + .login-dialog-auth-list-item { +- @extend %button_common; +- @include button(normal, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true); +- &:selected, +- &:focus { @include button(focus, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} +- &:hover { @include button(hover, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} +- &:active { @include button(active, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} ++ @include login_dialog_item_button(); + + border-radius: $modal_radius * 0.6; + padding: $base_padding * 1.2; +@@ -175,14 +170,7 @@ $_gdm_dialog_width: 25em; + spacing: $base_padding * 2; + + .login-dialog-user-list-item { +- // use button styling +- @extend %button_common; +- @include button(normal, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true); +- &:selected, +- &:focus { @include button(focus, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} +- &:hover { @include button(hover, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} +- &:active { @include button(active, $tc:$_gdm_fg, $c:$system_base_color, $always_dark: true);} +- ++ @include login_dialog_item_button(); + border-radius: $modal_radius; + padding: $base_padding * 1.5; + +-- +2.53.0 + + +From bfcebf4a5a60c64e5cbec78dbb8b646b69b571da Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 16:37:08 +0100 +Subject: [PATCH 02/42] unlockDialog: Vertically center dialog using fixed + height + +Use a fixed estimated height for centering so the position stays +stable regardless of actual content height changes during user +interaction. +--- + js/ui/unlockDialog.js | 20 +++++++++++++++----- + 1 file changed, 15 insertions(+), 5 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 63ba591eec..5d14cb9555 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -33,6 +33,8 @@ const FADE_OUT_SCALE = 0.3; + const BLUR_BRIGHTNESS = 0.65; + const BLUR_RADIUS = 90; + ++const FIXED_PROMPT_HEIGHT = 400; ++ + const NotificationsBox = GObject.registerClass({ + Signals: {'wake-up-screen': {}}, + }, class NotificationsBox extends St.BoxLayout { +@@ -449,8 +451,8 @@ class UnlockDialogLayout extends Clutter.LayoutManager { + vfunc_allocate(container, box) { + let [width, height] = box.get_size(); + +- let tenthOfHeight = height / 10.0; +- let thirdOfHeight = height / 3.0; ++ const tenthOfHeight = height / 10.0; ++ const centerY = height / 2.0; + + let [, , stackWidth, stackHeight] = + this._stack.get_preferred_size(); +@@ -476,9 +478,17 @@ class UnlockDialogLayout extends Clutter.LayoutManager { + this._notifications.allocate(actorBox); + + // Authentication Box +- let stackY = Math.min( +- thirdOfHeight, +- height - stackHeight - maxNotificationsHeight); ++ const dialog = container.get_parent(); ++ let stackY; ++ if (dialog._activePage === dialog._clock) { ++ stackY = Math.min( ++ Math.floor(centerY - stackHeight / 2.0), ++ height - stackHeight - maxNotificationsHeight); ++ } else { ++ stackY = Math.min( ++ Math.floor(centerY - FIXED_PROMPT_HEIGHT / 2.0), ++ height - stackHeight - maxNotificationsHeight); ++ } + + actorBox.x1 = columnX1; + actorBox.y1 = stackY; +-- +2.53.0 + + +From 6873ad575042d38fb5ab0663728694110db60c4a Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 16:38:04 +0100 +Subject: [PATCH 03/42] unlockDialog: Fix username reuse on reset + +The condition was checking for PROVIDE_USERNAME specifically, but +should also handle REUSE_USERNAME. Check for not DONT_PROVIDE_USERNAME +instead to correctly reuse the username when requested. +--- + js/ui/unlockDialog.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 5d14cb9555..b6d71cd47f 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -848,7 +848,7 @@ export const UnlockDialog = GObject.registerClass({ + + _onReset(authPrompt, beginRequest) { + let userName; +- if (beginRequest === AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { ++ if (beginRequest !== AuthPrompt.BeginRequestType.DONT_PROVIDE_USERNAME) { + this._authPrompt.setUser(this._user); + userName = this._userName; + } else { +-- +2.53.0 + + +From c3467f84952ae46add711482d121969c9c55633d Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 16:38:54 +0100 +Subject: [PATCH 04/42] unlockDialog: Wait for authPrompt destruction before + switching VT + +When switching to another user, wait until authPrompt is destroyed +and the clock transition animation completes before switching to +the login session VT. +--- + js/ui/unlockDialog.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index b6d71cd47f..02c302ce7a 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -898,8 +898,8 @@ export const UnlockDialog = GObject.registerClass({ + } + + _otherUserClicked() { +- Gdm.goto_login_session_sync(null); +- ++ this._authPrompt.connectObject('destroy', () => ++ Gdm.goto_login_session_sync(null)); + this._authPrompt.cancel(); + } + +-- +2.53.0 + + +From 0c642b409fab1374dd466a64b450d4a67e151452 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 5 Feb 2026 18:55:38 +0100 +Subject: [PATCH 05/42] authPrompt: Use destructured object for + updateSensitivity + +Replace the boolean parameter with a destructured object to make +call sites self-documenting. +--- + js/gdm/authPrompt.js | 18 +++++++++--------- + js/gdm/loginDialog.js | 6 +++--- + js/ui/unlockDialog.js | 2 +- + 3 files changed, 13 insertions(+), 13 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 3b4a2f7988..526dcdbaa0 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -287,7 +287,7 @@ export const AuthPrompt = GObject.registerClass({ + + _activateNext(shouldSpin) { + this.verificationStatus = AuthPromptStatus.VERIFICATION_IN_PROGRESS; +- this.updateSensitivity(false); ++ this.updateSensitivity({sensitive: false}); + + if (this._queryingService) { + if (shouldSpin) +@@ -350,7 +350,7 @@ export const AuthPrompt = GObject.registerClass({ + else + this.setQuestion(question.replace(/[::] *$/, '').trim()); + +- this.updateSensitivity(true); ++ this.updateSensitivity({sensitive: true}); + this.emit('prompted'); + } + +@@ -364,7 +364,7 @@ export const AuthPrompt = GObject.registerClass({ + this._preemptiveAnswer = null; + + this.setChoiceList(promptMessage, choiceList); +- this.updateSensitivity(true); ++ this.updateSensitivity({sensitive: true}); + this.emit('prompted'); + } + +@@ -419,7 +419,7 @@ export const AuthPrompt = GObject.registerClass({ + this.clear(); + } + +- this.updateSensitivity(canRetry); ++ this.updateSensitivity({sensitive: canRetry}); + this.setActorInDefaultButtonWell(null); + + if (!canRetry) +@@ -540,12 +540,12 @@ export const AuthPrompt = GObject.registerClass({ + opacity: 0, + visible: true, + }); +- this.updateSensitivity(false); ++ this.updateSensitivity({sensitive: false}); + this._authList.ease({ + opacity: 255, + duration: MESSAGE_FADE_OUT_ANIMATION_TIME, + transition: Clutter.AnimationMode.EASE_OUT_QUAD, +- onComplete: () => this.updateSensitivity(true), ++ onComplete: () => this.updateSensitivity({sensitive: true}), + }); + } + +@@ -611,7 +611,7 @@ export const AuthPrompt = GObject.registerClass({ + wiggle(this._message, wiggleParameters); + } + +- updateSensitivity(sensitive) { ++ updateSensitivity({sensitive}) { + let authWidget; + + if (this._authList.visible) +@@ -641,7 +641,7 @@ export const AuthPrompt = GObject.registerClass({ + + this.setUser(null); + +- this.updateSensitivity(true); ++ this.updateSensitivity({sensitive: true}); + this._entry.set_text(''); + } + +@@ -734,7 +734,7 @@ export const AuthPrompt = GObject.registerClass({ + hold: null, + }); + +- this.updateSensitivity(false); ++ this.updateSensitivity({sensitive: false}); + + let hold = params.hold; + if (!hold) +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 5aca896db9..9f76464ad5 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1148,8 +1148,8 @@ export const LoginDialog = GObject.registerClass({ + () => { + this._authPrompt.disconnect(this._nextSignalId); + this._nextSignalId = 0; +- this._authPrompt.updateSensitivity(false); +- let answer = this._authPrompt.getAnswer(); ++ this._authPrompt.updateSensitivity({sensitive: false}); ++ const answer = this._authPrompt.getAnswer(); + this._user = this._userManager.get_user(answer); + this._authPrompt.clear(); + this._authPrompt.begin({userName: answer}); +@@ -1158,7 +1158,7 @@ export const LoginDialog = GObject.registerClass({ + this._updateCancelButton(); + + this._sessionMenuButton.updateSensitivity(false); +- this._authPrompt.updateSensitivity(true); ++ this._authPrompt.updateSensitivity({sensitive: true}); + this._showPrompt(); + } + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 02c302ce7a..7f4082b92a 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -765,7 +765,7 @@ export const UnlockDialog = GObject.registerClass({ + case AuthPromptStatus.VERIFICATION_FAILED: + this._authPrompt.reset(); + this._authPrompt.updateSensitivity( +- verificationStatus === AuthPromptStatus.NOT_VERIFYING); ++ {sensitive: verificationStatus === AuthPromptStatus.NOT_VERIFYING}); + } + } + +-- +2.53.0 + + +From f30150c922a3aa4fd97101f9a2b0362ba84a029a Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 16:47:22 +0100 +Subject: [PATCH 06/42] authPrompt: Use array-based widget lookup in + updateSensitivity + +Replace the if/else widget selection with array-based lookup to +prepare for additional auth widgets in upcoming commits. +--- + js/gdm/authPrompt.js | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 526dcdbaa0..7967b60a81 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -612,12 +612,9 @@ export const AuthPrompt = GObject.registerClass({ + } + + updateSensitivity({sensitive}) { +- let authWidget; +- +- if (this._authList.visible) +- authWidget = this._authList; +- else +- authWidget = this._entry; ++ const authWidget = [ ++ this._authList, ++ ].find(widget => widget.visible) ?? this._entry; + + if (authWidget.reactive === sensitive) + return; +-- +2.53.0 + + +From 356968d5a09cca83d0cc7ac53774fd42092ae042 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 9 Feb 2024 09:02:25 -0500 +Subject: [PATCH 07/42] authPrompt: Fade out input buttons/entry after + verification + +It's nice to just see the user image and post login messages +once the user is done with the prompt. The buttons and +to some extent the password entry can disrupt the natural +login flow. + +Also, make _fadeInElement more abstract instead of specific for +authList. It'll be used in next commits. +--- + js/gdm/authPrompt.js | 25 +++++++++++++++++++------ + js/gdm/loginDialog.js | 11 +++++++++++ + 2 files changed, 30 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 7967b60a81..33d46feba8 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -51,6 +51,7 @@ export const AuthPrompt = GObject.registerClass({ + 'next': {}, + 'prompted': {}, + 'reset': {param_types: [GObject.TYPE_UINT]}, ++ 'verification-complete': {}, + }, + }, class AuthPrompt extends St.BoxLayout { + _init(gdmClient, mode) { +@@ -432,8 +433,16 @@ export const AuthPrompt = GObject.registerClass({ + _onVerificationComplete() { + this.setActorInDefaultButtonWell(null); + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; +- this.cancelButton.reactive = false; +- this.cancelButton.can_focus = false; ++ ++ this._mainBox.reactive = false; ++ this._mainBox.can_focus = false; ++ this._mainBox.ease({ ++ opacity: 0, ++ duration: MESSAGE_FADE_OUT_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ }); ++ ++ this.emit('verification-complete'); + } + + _onReset() { +@@ -520,6 +529,10 @@ export const AuthPrompt = GObject.registerClass({ + this.stopSpinning(); + this._authList.clear(); + this._authList.hide(); ++ ++ this._mainBox.opacity = 255; ++ this._mainBox.reactive = true; ++ this._mainBox.can_focus = true; + } + + setQuestion(question) { +@@ -535,13 +548,13 @@ export const AuthPrompt = GObject.registerClass({ + this._entry.grab_key_focus(); + } + +- _fadeInChoiceList() { +- this._authList.set({ ++ _fadeInElement(element) { ++ element.set({ + opacity: 0, + visible: true, + }); + this.updateSensitivity({sensitive: false}); +- this._authList.ease({ ++ element.ease({ + opacity: 255, + duration: MESSAGE_FADE_OUT_ANIMATION_TIME, + transition: Clutter.AnimationMode.EASE_OUT_QUAD, +@@ -560,7 +573,7 @@ export const AuthPrompt = GObject.registerClass({ + this._entry.hide(); + if (this._message.text === '') + this._message.hide(); +- this._fadeInChoiceList(); ++ this._fadeInElement(this._authList); + } + + getAnswer() { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 9f76464ad5..f75497065c 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -590,6 +590,7 @@ export const LoginDialog = GObject.registerClass({ + this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN); + this._authPrompt.connect('prompted', this._onPrompted.bind(this)); + this._authPrompt.connect('reset', this._onReset.bind(this)); ++ this._authPrompt.connect('verification-complete', this._onVerificationComplete.bind(this)); + this._authPrompt.hide(); + this.add_child(this._authPrompt); + +@@ -1091,6 +1092,16 @@ export const LoginDialog = GObject.registerClass({ + } + } + ++ _onVerificationComplete() { ++ this._bottomButtonGroup.reactive = false; ++ this._bottomButtonGroup.can_focus = false; ++ this._bottomButtonGroup.ease({ ++ opacity: 0, ++ duration: _FADE_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ }); ++ } ++ + _onDefaultSessionChanged(client, sessionId) { + this._sessionMenuButton.setActiveSession(sessionId); + } +-- +2.53.0 + + +From 34e3c9dc1d829860eccb5a75e34b6d0c9d6c35dc Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 21 Jan 2026 14:23:34 +0100 +Subject: [PATCH 08/42] authPrompt: Don't reset preemptiveAnswer when + VERIFICATION_IN_PROGRESS + +PreemptiveAnswer wasn't being used in the case where verification is in +progress and the smartcard is inserted, triggering a reset. + +This change ensures a preemptive answer will be used once smartcard +service asks for the PIN. +--- + js/gdm/authPrompt.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 33d46feba8..ec3c2ec546 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -685,7 +685,8 @@ export const AuthPrompt = GObject.registerClass({ + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.cancelButton.reactive = this._hasCancelButton; + this.cancelButton.can_focus = this._hasCancelButton; +- this._preemptiveAnswer = null; ++ if (oldStatus !== AuthPromptStatus.VERIFICATION_IN_PROGRESS) ++ this._preemptiveAnswer = null; + + if (this._preemptiveAnswerWatchId) + this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); +-- +2.53.0 + + +From fdd96fe191abfaea4fd29242fd7df8e78200a17e Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 17:05:32 +0100 +Subject: [PATCH 09/42] style: Increase hint-text left margin + +The cursor was overlapping the hint-text, making it difficult to read. +Increase the left margin to ensure proper readability. +--- + data/theme/gnome-shell-sass/widgets/_entries.scss | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/theme/gnome-shell-sass/widgets/_entries.scss b/data/theme/gnome-shell-sass/widgets/_entries.scss +index 41e10f7663..5a45e86d6d 100644 +--- a/data/theme/gnome-shell-sass/widgets/_entries.scss ++++ b/data/theme/gnome-shell-sass/widgets/_entries.scss +@@ -15,6 +15,6 @@ StEntry { + } + + StLabel.hint-text { +- margin-left: $base_margin * 0.5; ++ margin-left: $base_margin * 2; + } + } +-- +2.53.0 + + +From 2532bcfc85315b588f4c5c6ddaee7a3287fa8911 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 16 Feb 2026 16:05:17 +0100 +Subject: [PATCH 10/42] authPrompt: Use connectObject for userVerifier signals + +This allows cleanly disconnecting all signals at once when the +authPrompt is destroyed, preventing potential issues from stale +signal handlers. +--- + js/gdm/authPrompt.js | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index ec3c2ec546..5a62e8897f 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -80,14 +80,16 @@ export const AuthPrompt = GObject.registerClass({ + + this._userVerifier = this._createUserVerifier(this._gdmClient, {reauthenticationOnly}); + +- 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)); +- this._userVerifier.connect('smartcard-status-changed', this._onSmartcardStatusChanged.bind(this)); +- this._userVerifier.connect('credential-manager-authenticated', this._onCredentialManagerAuthenticated.bind(this)); ++ this._userVerifier.connectObject( ++ 'ask-question', this._onAskQuestion.bind(this), ++ 'show-message', this._onShowMessage.bind(this), ++ 'show-choice-list', this._onShowChoiceList.bind(this), ++ 'verification-failed', this._onVerificationFailed.bind(this), ++ 'verification-complete', this._onVerificationComplete.bind(this), ++ 'reset', this._onReset.bind(this), ++ 'smartcard-status-changed', this._onSmartcardStatusChanged.bind(this), ++ 'credential-manager-authenticated', this._onCredentialManagerAuthenticated.bind(this), ++ this); + this.smartcardDetected = this._userVerifier.smartcardDetected; + + this.connect('destroy', this._onDestroy.bind(this)); +@@ -141,6 +143,7 @@ export const AuthPrompt = GObject.registerClass({ + this._inactiveEntry.destroy(); + this._inactiveEntry = null; + ++ this._userVerifier.disconnectObject(this); + this._userVerifier.destroy(); + this._userVerifier = null; + this._entry = null; +-- +2.53.0 + + +From f634f1e7ff47de5a3a042b0da4d7af6b06f6831f Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 17:20:34 +0100 +Subject: [PATCH 11/42] authPrompt: Redesign entry area layout + +Restructure the authentication prompt layout: +- Make the entry larger and more rounded. +- Add a new next button, it's inside _defaultButtonWell. +- Wrap _entry and _defaultButtonWell in a new _entryArea container. +- _defaultButtonWell now is overlaying _entryArea aligned on the right. +- Anchor cancelButton to _entry using a constraint. +--- + .../gnome-shell-sass/widgets/_login-lock.scss | 24 ++++++- + js/gdm/authPrompt.js | 68 ++++++++++++++----- + 2 files changed, 72 insertions(+), 20 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index 6cca1e28e9..93dbe617b7 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -16,6 +16,19 @@ $_gdm_dialog_width: 25em; + width: $_gdm_dialog_width; + spacing: $base_padding * 1.5; + } ++ ++ .login-dialog-prompt-entry-area { ++ margin: 0.5em $base_margin * 5; ++ } ++ ++ .login-dialog-prompt-entry { ++ border-radius: $base_border_radius * 1.5; ++ padding-right: 3em; // Make room for button-well inside entry ++ } ++ ++ .login-dialog-default-button-well { ++ margin-right: 1em; ++ } + } + + // GDM Login Dialog +@@ -33,7 +46,7 @@ $_gdm_dialog_width: 25em; + + // buttons on login screen + .login-dialog-button { +- ++ &.next-button, + &.a11y-button, + &.cancel-button, + &.switch-user-button, +@@ -44,13 +57,18 @@ $_gdm_dialog_width: 25em; + padding: to_em(16px); + } + ++ &.next-button { ++ background-color: transparent !important; ++ padding: 0; ++ } ++ + &.cancel-button { +- padding: $base_padding * 1.5; ++ padding: $base_padding * 2; + } + } + + .login-dialog-button-box { +- spacing: $base_padding * 2; ++ height: 4em; + } + + .conflicting-session-dialog-content { +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 5a62e8897f..474b6ecd9c 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -2,6 +2,7 @@ import Clutter from 'gi://Clutter'; + import GLib from 'gi://GLib'; + import Atk from 'gi://Atk'; + import GObject from 'gi://GObject'; ++import Graphene from 'gi://Graphene'; + import Pango from 'gi://Pango'; + import Shell from 'gi://Shell'; + import St from 'gi://St'; +@@ -158,9 +159,11 @@ export const AuthPrompt = GObject.registerClass({ + } + + _initInputRow() { +- this._mainBox = new St.BoxLayout({ ++ this._mainBox = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), + style_class: 'login-dialog-button-box', +- orientation: Clutter.Orientation.HORIZONTAL, ++ x_expand: true, ++ y_expand: false, + }); + this.add_child(this._mainBox); + +@@ -170,10 +173,17 @@ export const AuthPrompt = GObject.registerClass({ + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: this._hasCancelButton, + can_focus: this._hasCancelButton, ++ x_expand: true, + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.CENTER, + icon_name: 'go-previous-symbolic', + }); ++ this.cancelButton.add_constraint(new Clutter.AlignConstraint({ ++ source: this._mainBox, ++ align_axis: Clutter.AlignAxis.X_AXIS, ++ pivot_point: new Graphene.Point({x: 1, y: 0}), ++ })); ++ + if (this._hasCancelButton) + this.cancelButton.connect('clicked', () => this.cancel()); + else +@@ -201,10 +211,20 @@ export const AuthPrompt = GObject.registerClass({ + }); + this._mainBox.add_child(this._authList); + +- let entryParams = { ++ this._entryArea = new St.Widget({ ++ style_class: 'login-dialog-prompt-entry-area', ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true, ++ visible: false, ++ }); ++ this._mainBox.add_child(this._entryArea); ++ ++ const entryParams = { + style_class: 'login-dialog-prompt-entry', + can_focus: true, + x_expand: true, ++ y_expand: true, + }; + + this._entry = null; +@@ -216,8 +236,7 @@ export const AuthPrompt = GObject.registerClass({ + ShellEntry.addContextMenu(this._passwordEntry, {actionMode: Shell.ActionMode.NONE}); + + this._entry = this._passwordEntry; +- this._mainBox.add_child(this._entry); +- this._entry.grab_key_focus(); ++ this._entryArea.add_child(this._entry); + this._inactiveEntry = this._textEntry; + + this._timedLoginIndicator = new St.Bin({ +@@ -242,17 +261,28 @@ export const AuthPrompt = GObject.registerClass({ + + this._defaultButtonWell = new St.Widget({ + layout_manager: new Clutter.BinLayout(), ++ style_class: 'login-dialog-default-button-well', ++ x_expand: true, + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.CENTER, + }); +- this._defaultButtonWell.add_constraint(new Clutter.BindConstraint({ +- source: this.cancelButton, +- coordinate: Clutter.BindCoordinate.WIDTH, +- })); +- this._mainBox.add_child(this._defaultButtonWell); ++ this._entryArea.add_child(this._defaultButtonWell); ++ ++ this._nextButton = new St.Button({ ++ style_class: 'login-dialog-button next-button', ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ can_focus: false, ++ icon_name: 'go-next-symbolic', ++ }); ++ this._nextButton.connect('clicked', () => this._activateNext()); ++ this._nextButton.add_style_pseudo_class('default'); ++ this._defaultButtonWell.add_child(this._nextButton); + + this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE); + this._defaultButtonWell.add_child(this._spinner); ++ ++ this.setActorInDefaultButtonWell(this._nextButton); + } + + showTimedLoginIndicator(time) { +@@ -322,7 +352,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + if (newEntry) { +- this._mainBox.replace_child(this._entry, newEntry); ++ this._entryArea.replace_child(this._entry, newEntry); + this._entry = newEntry; + this._inactiveEntry = inactiveEntry; + +@@ -424,17 +454,17 @@ export const AuthPrompt = GObject.registerClass({ + } + + this.updateSensitivity({sensitive: canRetry}); +- this.setActorInDefaultButtonWell(null); ++ this.setActorInDefaultButtonWell(this._nextButton); + + if (!canRetry) + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; + + if (wasQueryingService) +- wiggle(this._entry); ++ wiggle(this._entryArea); + } + + _onVerificationComplete() { +- this.setActorInDefaultButtonWell(null); ++ this.setActorInDefaultButtonWell(this._nextButton, true); + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + + this._mainBox.reactive = false; +@@ -547,7 +577,8 @@ export const AuthPrompt = GObject.registerClass({ + this._entry.hint_text = question; + + this._authList.hide(); +- this._entry.show(); ++ ++ this._entryArea.show(); + this._entry.grab_key_focus(); + } + +@@ -573,7 +604,7 @@ export const AuthPrompt = GObject.registerClass({ + this._authList.addItem(key, text); + } + +- this._entry.hide(); ++ this._entryArea.hide(); + if (this._message.text === '') + this._message.hide(); + this._fadeInElement(this._authList); +@@ -635,6 +666,9 @@ export const AuthPrompt = GObject.registerClass({ + if (authWidget.reactive === sensitive) + return; + ++ if (authWidget === this._entry) ++ this._nextButton.reactive = sensitive; ++ + authWidget.reactive = sensitive; + + if (sensitive) { +@@ -648,7 +682,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + vfunc_hide() { +- this.setActorInDefaultButtonWell(null, true); ++ this.setActorInDefaultButtonWell(this._nextButton, true); + super.vfunc_hide(); + this._message.opacity = 0; + +-- +2.53.0 + + +From e44919241a35ba1a0e515cf8b05f181dd0506688 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 17:41:48 +0100 +Subject: [PATCH 12/42] authPrompt: Now by default the entry area fades in + +This means when clear it's hidden. Keep it visible on verification failed, +it'll be cleared later on reset. +--- + js/gdm/authPrompt.js | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 474b6ecd9c..d639d574e4 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -384,7 +384,6 @@ export const AuthPrompt = GObject.registerClass({ + else + this.setQuestion(question.replace(/[::] *$/, '').trim()); + +- this.updateSensitivity({sensitive: true}); + this.emit('prompted'); + } + +@@ -448,10 +447,8 @@ export const AuthPrompt = GObject.registerClass({ + _onVerificationFailed(userVerifier, serviceName, canRetry) { + const wasQueryingService = this._queryingService === serviceName; + +- if (wasQueryingService) { ++ if (wasQueryingService) + this._queryingService = null; +- this.clear(); +- } + + this.updateSensitivity({sensitive: canRetry}); + this.setActorInDefaultButtonWell(this._nextButton); +@@ -557,6 +554,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + clear() { ++ this._entryArea.hide(); + this._entry.text = ''; + this._inactiveEntry.text = ''; + this.stopSpinning(); +@@ -578,8 +576,7 @@ export const AuthPrompt = GObject.registerClass({ + + this._authList.hide(); + +- this._entryArea.show(); +- this._entry.grab_key_focus(); ++ this._fadeInElement(this._entryArea); + } + + _fadeInElement(element) { +-- +2.53.0 + + +From 67209dda9cfc1be9a87b2765406a5a8e7bd11af8 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 17:36:08 +0100 +Subject: [PATCH 13/42] authPrompt: Show entry area when displaying message + +Since entryArea is hidden by default, we must explicitly make it +visible when showing a message if no other widgets are visible. +This allows getting a preemptive answer. +--- + js/gdm/authPrompt.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d639d574e4..d876a3acf5 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -441,6 +441,14 @@ export const AuthPrompt = GObject.registerClass({ + } + + this.setMessage(message, type, wiggleParameters); ++ ++ // If we're showing a message and no auth widget is currently visible, ++ // show the entry area to allow getting a preemptive answer ++ if (message && ++ !this._entryArea.visible && ++ !this._authList.visible) ++ this._fadeInElement(this._entryArea); ++ + this.emit('prompted'); + } + +-- +2.53.0 + + +From ae7d20481b14b922d45c20e3d02585ca0526e390 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 17:28:54 +0100 +Subject: [PATCH 14/42] authPrompt: Refactor button-well animation and add + loading signal + +- Simplify setActorInDefaultButtonWell animation logic using start/stop + spinning. +- Simplify _activateNext checking internally reactive state and + passwordEntry. +- Add 'loading' signal emitted on start/stop spinning (used in upcoming + commits). +- Simplify setActorInDefaultButtonWell and remove unused + DEFAULT_BUTTON_WELL_ANIMATION_DELAY constant. +--- + js/gdm/authPrompt.js | 105 +++++++++++++++++++------------------------ + 1 file changed, 46 insertions(+), 59 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d876a3acf5..1f78b70bd4 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -17,7 +17,6 @@ import * as UserWidget from '../ui/userWidget.js'; + import {wiggle} from '../misc/animationUtils.js'; + + const DEFAULT_BUTTON_WELL_ICON_SIZE = 16; +-const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; + const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300; + + const MESSAGE_FADE_OUT_ANIMATION_TIME = 500; +@@ -53,6 +52,7 @@ export const AuthPrompt = GObject.registerClass({ + 'prompted': {}, + 'reset': {param_types: [GObject.TYPE_UINT]}, + 'verification-complete': {}, ++ 'loading': {param_types: [GObject.TYPE_BOOLEAN]}, + }, + }, class AuthPrompt extends St.BoxLayout { + _init(gdmClient, mode) { +@@ -252,11 +252,7 @@ export const AuthPrompt = GObject.registerClass({ + this._fadeOutMessage(); + }); + +- entry.clutter_text.connect('activate', () => { +- let shouldSpin = entry === this._passwordEntry; +- if (entry.reactive) +- this._activateNext(shouldSpin); +- }); ++ entry.clutter_text.connect('activate', () => this._activateNext()); + }); + + this._defaultButtonWell = new St.Widget({ +@@ -319,12 +315,15 @@ export const AuthPrompt = GObject.registerClass({ + this._timedLoginIndicator.scale_x = 0.; + } + +- _activateNext(shouldSpin) { ++ _activateNext() { ++ if (!this._entry.reactive) ++ return; ++ + this.verificationStatus = AuthPromptStatus.VERIFICATION_IN_PROGRESS; + this.updateSensitivity({sensitive: false}); + + if (this._queryingService) { +- if (shouldSpin) ++ if (this._entry === this._passwordEntry) + this.startSpinning(); + + this._userVerifier.answerQuery(this._queryingService, this._entry.text); +@@ -459,7 +458,7 @@ export const AuthPrompt = GObject.registerClass({ + this._queryingService = null; + + this.updateSensitivity({sensitive: canRetry}); +- this.setActorInDefaultButtonWell(this._nextButton); ++ this.stopSpinning(); + + if (!canRetry) + this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; +@@ -469,7 +468,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + _onVerificationComplete() { +- this.setActorInDefaultButtonWell(this._nextButton, true); ++ this.stopSpinning(true); + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + + this._mainBox.reactive = false; +@@ -489,76 +488,64 @@ export const AuthPrompt = GObject.registerClass({ + } + + setActorInDefaultButtonWell(actor, animate) { +- if (!this._defaultButtonWellActor && +- !actor) ++ if (!this._defaultButtonWellActor && !actor) + return; + +- let oldActor = this._defaultButtonWellActor; ++ const oldActor = this._defaultButtonWellActor; ++ const wasSpinner = oldActor === this._spinner; + + if (oldActor) + oldActor.remove_all_transitions(); + +- let wasSpinner; +- if (oldActor === this._spinner) +- wasSpinner = true; +- else +- wasSpinner = false; +- +- let isSpinner; + if (actor === this._spinner) +- isSpinner = true; +- else +- isSpinner = false; ++ this._spinner.play(); + +- if (this._defaultButtonWellActor !== actor && oldActor) { +- if (!animate) { ++ if (!animate) { ++ if (oldActor) { + oldActor.opacity = 0; +- +- if (wasSpinner) { +- if (this._spinner) +- this._spinner.stop(); +- } +- } else { +- oldActor.ease({ +- opacity: 0, +- duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME, +- delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, +- mode: Clutter.AnimationMode.LINEAR, +- onComplete: () => { +- if (wasSpinner) { +- if (this._spinner) +- this._spinner.stop(); +- } +- }, +- }); ++ if (wasSpinner) ++ this._spinner.stop(); + } ++ if (actor) ++ actor.opacity = 255; ++ ++ this._defaultButtonWellActor = actor; ++ return; + } + ++ if (oldActor) { ++ oldActor.opacity = 255; ++ oldActor.ease({ ++ opacity: 0, ++ duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.LINEAR, ++ onComplete: () => { ++ if (wasSpinner) ++ this._spinner.stop(); ++ }, ++ }); ++ } + if (actor) { +- if (isSpinner) +- this._spinner.play(); +- +- if (!animate) { +- actor.opacity = 255; +- } else { +- actor.ease({ +- opacity: 255, +- duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME, +- delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY, +- mode: Clutter.AnimationMode.LINEAR, +- }); +- } ++ actor.opacity = 0; ++ actor.ease({ ++ opacity: 255, ++ duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME, ++ delay: oldActor ? DEFAULT_BUTTON_WELL_ANIMATION_TIME : 0, ++ mode: Clutter.AnimationMode.LINEAR, ++ }); + } + + this._defaultButtonWellActor = actor; + } + + startSpinning() { ++ this.emit('loading', true); + this.setActorInDefaultButtonWell(this._spinner, true); + } + +- stopSpinning() { +- this.setActorInDefaultButtonWell(null, false); ++ stopSpinning(animate) { ++ this.emit('loading', false); ++ this.setActorInDefaultButtonWell(this._nextButton, animate); + } + + clear() { +@@ -687,7 +674,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + vfunc_hide() { +- this.setActorInDefaultButtonWell(this._nextButton, true); ++ this.stopSpinning(); + super.vfunc_hide(); + this._message.opacity = 0; + +-- +2.53.0 + + +From b502b7c6e06a77c17ba225e91539f60118e39328 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 13:06:32 -0500 +Subject: [PATCH 15/42] authPrompt: Parameterize reset function + +In the future, userVerifier will request a partial reset where +some state is carried over or explicitly specified. + +This commit prepares for that by allowing the request type and +reusing entry text to be specified at reset time. +--- + js/gdm/authPrompt.js | 35 +++++++++++++++++++++++++---------- + 1 file changed, 25 insertions(+), 10 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 1f78b70bd4..2614b82410 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -482,9 +482,11 @@ export const AuthPrompt = GObject.registerClass({ + this.emit('verification-complete'); + } + +- _onReset() { +- this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; +- this.reset(); ++ _onReset(_, resetParams) { ++ if (this.verificationStatus === AuthPromptStatus.VERIFICATION_SUCCEEDED) ++ return; ++ ++ this.reset(resetParams); + } + + setActorInDefaultButtonWell(actor, animate) { +@@ -548,10 +550,17 @@ export const AuthPrompt = GObject.registerClass({ + this.setActorInDefaultButtonWell(this._nextButton, animate); + } + +- clear() { ++ clear(params) { ++ const {reuseEntryText} = Params.parse(params, { ++ reuseEntryText: false, ++ }); ++ ++ if (!reuseEntryText) { ++ this._entry.text = ''; ++ this._inactiveEntry.text = ''; ++ } ++ + this._entryArea.hide(); +- this._entry.text = ''; +- this._inactiveEntry.text = ''; + this.stopSpinning(); + this._authList.clear(); + this._authList.hide(); +@@ -709,8 +718,13 @@ export const AuthPrompt = GObject.registerClass({ + this.updateSensitivity(false); + } + +- reset() { +- let oldStatus = this.verificationStatus; ++ reset(params) { ++ const {reuseEntryText, softReset} = Params.parse(params, { ++ reuseEntryText: false, ++ softReset: false, ++ }); ++ ++ const oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; + this.cancelButton.reactive = this._hasCancelButton; + this.cancelButton.can_focus = this._hasCancelButton; +@@ -726,7 +740,7 @@ export const AuthPrompt = GObject.registerClass({ + this._userVerifier.cancel(); + + this._queryingService = null; +- this.clear(); ++ this.clear({reuseEntryText}); + this._message.opacity = 0; + this.setUser(null); + this._updateEntry(true); +@@ -749,7 +763,8 @@ export const AuthPrompt = GObject.registerClass({ + // We don't need to know the username if the user preempted the login screen + // with a smartcard or with preauthenticated oVirt credentials + beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; +- } else if (oldStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) { ++ } else if (oldStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS || ++ softReset) { + // We're going back to retry with current user + beginRequestType = BeginRequestType.REUSE_USERNAME; + } else { +-- +2.53.0 + + +From 090803f771f3c1b800ac256c0b91b50f07c12813 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 12 Feb 2026 16:56:48 +0100 +Subject: [PATCH 16/42] authPrompt: Rename BeginRequestType to ResetType + +The enum is emitted with the 'reset' signal and describes the type +of reset being performed, so ResetType is a clearer name. +--- + js/gdm/authPrompt.js | 14 +++++++------- + js/gdm/loginDialog.js | 6 +++--- + js/ui/unlockDialog.js | 4 ++-- + 3 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 2614b82410..ea09f470b4 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -38,7 +38,7 @@ export const AuthPromptStatus = { + }; + + /** @enum {number} */ +-export const BeginRequestType = { ++export const ResetType = { + PROVIDE_USERNAME: 0, + DONT_PROVIDE_USERNAME: 1, + REUSE_USERNAME: 2, +@@ -751,28 +751,28 @@ export const AuthPrompt = GObject.registerClass({ + else if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED) + this.emit('cancelled'); + +- let beginRequestType; ++ let resetType; + + if (this._mode === AuthPromptMode.UNLOCK_ONLY) { + // The user is constant at the unlock screen, so it will immediately + // respond to the request with the username + if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED) + return; +- beginRequestType = BeginRequestType.PROVIDE_USERNAME; ++ resetType = ResetType.PROVIDE_USERNAME; + } else if (this._userVerifier.foregroundServiceDeterminesUsername()) { + // We don't need to know the username if the user preempted the login screen + // with a smartcard or with preauthenticated oVirt credentials +- beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; ++ resetType = ResetType.DONT_PROVIDE_USERNAME; + } else if (oldStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS || + softReset) { + // We're going back to retry with current user +- beginRequestType = BeginRequestType.REUSE_USERNAME; ++ resetType = ResetType.REUSE_USERNAME; + } else { + // In all other cases, we should get the username up front. +- beginRequestType = BeginRequestType.PROVIDE_USERNAME; ++ resetType = ResetType.PROVIDE_USERNAME; + } + +- this.emit('reset', beginRequestType); ++ this.emit('reset', resetType); + } + + addCharacter(unichar) { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index f75497065c..f38de0c914 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1066,7 +1066,7 @@ export const LoginDialog = GObject.registerClass({ + } + } + +- _onReset(authPrompt, beginRequest) { ++ _onReset(authPrompt, resetType) { + this._ensureGreeterProxy(); + this._sessionMenuButton.updateSensitivity(true); + +@@ -1078,11 +1078,11 @@ export const LoginDialog = GObject.registerClass({ + this._nextSignalId = 0; + } + +- if (previousUser && beginRequest === AuthPrompt.BeginRequestType.REUSE_USERNAME) { ++ if (previousUser && resetType === AuthPrompt.ResetType.REUSE_USERNAME) { + this._user = previousUser; + this._authPrompt.setUser(this._user); + this._authPrompt.begin({userName: previousUser.get_user_name()}); +- } else if (beginRequest === AuthPrompt.BeginRequestType.PROVIDE_USERNAME) { ++ } else if (resetType === AuthPrompt.ResetType.PROVIDE_USERNAME) { + if (!this._disableUserList) + this._showUserList(); + else +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 7f4082b92a..c52abc4d7e 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -846,9 +846,9 @@ export const UnlockDialog = GObject.registerClass({ + this.emit('failed'); + } + +- _onReset(authPrompt, beginRequest) { ++ _onReset(authPrompt, resetType) { + let userName; +- if (beginRequest !== AuthPrompt.BeginRequestType.DONT_PROVIDE_USERNAME) { ++ if (resetType !== AuthPrompt.ResetType.DONT_PROVIDE_USERNAME) { + this._authPrompt.setUser(this._user); + userName = this._userName; + } else { +-- +2.53.0 + + +From f0da5c72c6ee44eee36a572aa8f97ce48f742480 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 11 Feb 2026 16:11:59 +0100 +Subject: [PATCH 17/42] unlockDialog: Use isprint instead of isgraph for + preemptive input + +isgraph returns true for printable characters except space, while +isprint includes space as a valid character. This allows users to +type passwords containing spaces during preemptive input. +--- + js/ui/unlockDialog.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index c52abc4d7e..a648243a24 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -686,7 +686,7 @@ export const UnlockDialog = GObject.registerClass({ + + this._showPrompt(); + +- if (GLib.unichar_isgraph(unichar)) ++ if (GLib.unichar_isprint(unichar)) + this._authPrompt.addCharacter(unichar); + + return Clutter.EVENT_PROPAGATE; +-- +2.53.0 + + +From 30b8c4d9febced8814d37c9f55835950a438d2ac Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 21 Jan 2026 14:34:11 +0100 +Subject: [PATCH 18/42] authPrompt: Capture preemptive input before entry is + sensitive + +The previous changes made the entry invisible and insensitive until +askQuestion is called. During the lock screen, when the user starts +typing before the PAM service is ready, keystrokes were being lost. + +Replace addCharacter() with startPreemptiveInput() which buffers +keystrokes and handles Enter key presses while waiting for the entry +to become sensitive. + +Also, remove unsused addCharacter() in loginDialog.js. +--- + js/gdm/authPrompt.js | 39 +++++++++++++++++++++++++++++++-------- + js/gdm/loginDialog.js | 4 ---- + js/ui/unlockDialog.js | 2 +- + 3 files changed, 32 insertions(+), 13 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index ea09f470b4..5a09c3063b 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -155,6 +155,19 @@ export const AuthPrompt = GObject.registerClass({ + this.cancel(); + return Clutter.EVENT_STOP; + } ++ ++ if (this._preemptiveInput && !this._pendingActivate) { ++ const unichar = event.get_key_unicode(); ++ if (event.get_key_symbol() === Clutter.KEY_Return && ++ this._entry.clutter_text.text) { ++ this._pendingActivate = true; ++ } else if (GLib.unichar_isprint(unichar)) { ++ this._entry.clutter_text.insert_text(unichar, ++ this._entry.clutter_text.cursor_position); ++ } ++ return Clutter.EVENT_STOP; ++ } ++ + return Clutter.EVENT_PROPAGATE; + } + +@@ -593,10 +606,20 @@ export const AuthPrompt = GObject.registerClass({ + opacity: 255, + duration: MESSAGE_FADE_OUT_ANIMATION_TIME, + transition: Clutter.AnimationMode.EASE_OUT_QUAD, +- onComplete: () => this.updateSensitivity({sensitive: true}), ++ onComplete: () => { ++ this.updateSensitivity({sensitive: true}); ++ this._completePreemptiveInput(element); ++ }, + }); + } + ++ _completePreemptiveInput(element) { ++ if (element === this._entryArea && this._pendingActivate) ++ this._activateNext(); ++ this._preemptiveInput = false; ++ this._pendingActivate = false; ++ } ++ + setChoiceList(promptMessage, choiceList) { + this._authList.clear(); + this._authList.label.text = promptMessage; +@@ -719,7 +742,7 @@ export const AuthPrompt = GObject.registerClass({ + } + + reset(params) { +- const {reuseEntryText, softReset} = Params.parse(params, { ++ let {reuseEntryText, softReset} = Params.parse(params, { + reuseEntryText: false, + softReset: false, + }); +@@ -739,6 +762,8 @@ export const AuthPrompt = GObject.registerClass({ + if (this._userVerifier) + this._userVerifier.cancel(); + ++ reuseEntryText = reuseEntryText || this._preemptiveInput; ++ + this._queryingService = null; + this.clear({reuseEntryText}); + this._message.opacity = 0; +@@ -775,12 +800,10 @@ export const AuthPrompt = GObject.registerClass({ + this.emit('reset', resetType); + } + +- addCharacter(unichar) { +- if (!this._entry.visible) +- return; +- +- this._entry.grab_key_focus(); +- this._entry.clutter_text.insert_unichar(unichar); ++ startPreemptiveInput(unichar) { ++ this._preemptiveInput = true; ++ this._entry.clutter_text.insert_text(unichar, this._entry.clutter_text.cursor_position); ++ this.grab_key_focus(); + } + + begin(params) { +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index f38de0c914..09e8073c65 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -1593,10 +1593,6 @@ export const LoginDialog = GObject.registerClass({ + this._authPrompt.cancel(); + } + +- addCharacter(_unichar) { +- // Don't allow type ahead at the login screen +- } +- + finish(onComplete) { + this._authPrompt.finish(onComplete); + } +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index a648243a24..fffc58e07c 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -687,7 +687,7 @@ export const UnlockDialog = GObject.registerClass({ + this._showPrompt(); + + if (GLib.unichar_isprint(unichar)) +- this._authPrompt.addCharacter(unichar); ++ this._authPrompt.startPreemptiveInput(unichar); + + return Clutter.EVENT_PROPAGATE; + } +-- +2.53.0 + + +From 7a086c8fb3c08377c554e96202c0e183a5968646 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 10 Feb 2026 18:51:41 +0100 +Subject: [PATCH 19/42] authPrompt: On verificationFailed ensure input + sensitivity is disabled + +There's a time window between the verification failing and a new +verification request being started. + +Input is not accepted during this time, so disable input sensitivity +when verification fails. UserVerifier will be in charge of restarting the +verification process, reenabling sensitivity. +--- + 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 5a09c3063b..a383208ecc 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -470,7 +470,7 @@ export const AuthPrompt = GObject.registerClass({ + if (wasQueryingService) + this._queryingService = null; + +- this.updateSensitivity({sensitive: canRetry}); ++ this.updateSensitivity({sensitive: false}); + this.stopSpinning(); + + if (!canRetry) +-- +2.53.0 + + +From f4faa89270778bd0280c911cd5524498444205ef Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 30 Sep 2025 17:43:22 +0200 +Subject: [PATCH 20/42] authPrompt: Update authList style + +Make the AuthListItem buttons a bit bigger and more rounded. + +AuthListItem now accepts a content object with title, subtitle, iconName, +iconTitle, and iconSubtitle properties. This allows displaying richer +item content. The icon button shows a popup with additional details. + +Now the title is an insensitive button that is in _mainBox to ensure +back button is visible and properly aligned. + +Use accessible_name. + +Register classes with the new style. +--- + .../gnome-shell-sass/widgets/_login-lock.scss | 95 ++++++++-- + js/gdm/authList.js | 168 +++++++++++++++--- + js/gdm/authPrompt.js | 28 ++- + js/gdm/util.js | 6 +- + 4 files changed, 249 insertions(+), 48 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index 93dbe617b7..5a31725b1d 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -141,42 +141,107 @@ $_gdm_dialog_width: 25em; + // Authentication methods list + .login-dialog-auth-list-view { + -st-vfade-offset: 3em; ++ max-height: 13em; + } + + .login-dialog-auth-list { +- spacing: $base_padding; +- margin-left: 2em; +-} +- +-.login-dialog-auth-list-title { +- margin-left: 2em; +- padding-bottom: $base_padding; ++ spacing: $base_padding * 1.5; + } + + .login-dialog { ++ .login-dialog-auth-list-title, + .login-dialog-auth-list-item { +- @include login_dialog_item_button(); ++ @include login_dialog_item_button($style:card); ++ border-radius: $base_border_radius * 1.5; ++ } + +- border-radius: $modal_radius * 0.6; +- padding: $base_padding * 1.2; ++ .login-dialog-auth-list-title { ++ background-color: transparentize($_gdm_fg, .98) !important; ++ color: $_gdm_fg !important; ++ margin: 0.5em $base_margin * 5; ++ } ++ ++ .login-dialog-auth-list-item { ++ min-height: 3em; ++ padding: $base_padding * 1.5; ++ margin: 0 $base_margin * 5; ++ margin-bottom: $base_margin; + } + } + + .unlock-dialog { + .login-dialog-auth-list-item { + @extend %lockscreen_button; ++ border-radius: $base_border_radius * 1.5; ++ min-height: 3em; ++ padding: $base_padding * 1.5; ++ margin: 0 $base_margin * 5; ++ margin-bottom: $base_margin; ++ } + +- border-radius: $modal_radius * 0.6; +- padding: $base_padding * 1.2; ++ .login-dialog-auth-list-title { ++ @extend %lockscreen_button; ++ background-color: transparent !important; ++ color: $system_fg_color !important; ++ padding: 0; ++ margin: 0; + } + } + +-.login-dialog-auth-list-label { ++.login-dialog-auth-list-title-label { + @extend %title_4; +- &:ltr { padding-left: $base_padding * 2.5; text-align: left; } +- &:rtl { padding-right: $base_padding * 2.5; text-align: right; } ++ padding: $base_padding; ++ text-align: center; ++} ++ ++.login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle { ++ @extend %heading; ++ text-align: center; ++ padding: $base_padding * 0.3 0; ++} ++ ++.login-dialog-auth-list-item-title { ++ color: $_gdm_fg; + } + ++.login-dialog-auth-list-item-subtitle { ++ color: darken($_gdm_fg, 20%); ++ font-weight: 500; ++} ++ ++.login-dialog-item-icon { ++ width: 1.3em; ++ height: 1.3em; ++ color: $_gdm_fg; ++ padding: $base_padding * 0.7 $base_padding; ++ border-radius: $base_border_radius; ++ ++ &:hover { ++ background-color: $system_bg_color; ++ } ++ ++ &-popup { ++ &.popup-menu { ++ min-width: 0; ++ } ++ ++ &-box &-labels { ++ spacing: $base_padding * 0.5; ++ @extend %heading; ++ text-align: center; ++ ++ & > :first-child { ++ color: darken($fg_color, 30%); ++ font-weight: 500; ++ } ++ ++ & > :last-child { ++ color: $fg_color; ++ } ++ } ++ } ++} + + // User list + .login-dialog-user-list-view { +diff --git a/js/gdm/authList.js b/js/gdm/authList.js +index 4873a05c58..3ce30efc29 100644 +--- a/js/gdm/authList.js ++++ b/js/gdm/authList.js +@@ -18,30 +18,141 @@ + import Clutter from 'gi://Clutter'; + import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; ++import Graphene from 'gi://Graphene'; + import Meta from 'gi://Meta'; ++import Shell from 'gi://Shell'; + import St from 'gi://St'; + ++import * as Main from '../ui/main.js'; ++import * as PopupMenu from '../ui/popupMenu.js'; ++ + const SCROLL_ANIMATION_TIME = 500; + +-const AuthListItem = GObject.registerClass({ +- Signals: {'activate': {}}, +-}, class AuthListItem extends St.Button { +- _init(key, text) { +- this.key = key; +- const label = new St.Label({ +- text, +- style_class: 'login-dialog-auth-list-label', +- y_align: Clutter.ActorAlign.CENTER, +- x_expand: true, ++const ItemIconPopup = class extends PopupMenu.PopupMenu { ++ constructor(sourceActor, title, subtitle) { ++ super(sourceActor, 0.5, St.Side.TOP); ++ ++ this.box.add_style_class_name('login-dialog-item-icon-popup-box'); ++ this.actor.add_style_class_name('login-dialog-item-icon-popup'); ++ ++ const labels = new St.BoxLayout({ ++ orientation: Clutter.Orientation.VERTICAL, ++ style_class: 'login-dialog-item-icon-popup-labels', ++ }); ++ labels.add_child(new St.Label({text: title})); ++ labels.add_child(new St.Label({text: subtitle})); ++ ++ const item = new PopupMenu.PopupBaseMenuItem({ ++ reactive: false, ++ can_focus: false, ++ }); ++ item.add_child(labels); ++ this.addMenuItem(item); ++ ++ sourceActor.connect('clicked', () => this.toggle()); ++ sourceActor.connect('destroy', () => this.destroy()); ++ ++ this.actor.hide(); ++ ++ this._menuManager = new PopupMenu.PopupMenuManager(sourceActor, { ++ actionMode: Shell.ActionMode.NONE, + }); ++ this._menuManager.addMenu(this); ++ } ++}; ++ ++class ItemIcon extends St.Button { ++ static [GObject.GTypeName] = 'ItemIcon'; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(iconName, iconTitle, iconSubtitle) { ++ super({ ++ style_class: 'login-dialog-item-icon', ++ child: new St.Icon({icon_name: iconName}), ++ }); ++ ++ this._popup = new ItemIconPopup(this, iconTitle, iconSubtitle); ++ Main.uiGroup.add_child(this._popup.actor); ++ } ++} ++ ++class AuthListItem extends St.Button { ++ static [GObject.GTypeName] = 'AuthListItem'; ++ ++ static [GObject.signals] = { ++ 'activate': {}, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } + +- super._init({ ++ constructor(key, content) { ++ const {title, subtitle, iconName, iconTitle, iconSubtitle} = content; ++ ++ super({ + style_class: 'login-dialog-auth-list-item', + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + can_focus: true, +- child: label, + reactive: true, ++ accessible_name: [title, subtitle, iconTitle, iconSubtitle] ++ .filter(p => p) ++ .join(', '), ++ }); ++ ++ this.key = key; ++ ++ this._container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, + }); ++ this._labelBox = new St.BoxLayout({ ++ orientation: Clutter.Orientation.VERTICAL, ++ y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ this._container.add_child(this._labelBox); ++ ++ if (title) { ++ const label = new St.Label({ ++ text: title, ++ style_class: 'login-dialog-auth-list-item-title', ++ y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ this._labelBox.add_child(label); ++ } ++ ++ if (subtitle) { ++ const label = new St.Label({ ++ text: subtitle, ++ style_class: 'login-dialog-auth-list-item-subtitle', ++ y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ this._labelBox.add_child(label); ++ } ++ ++ if (iconName && iconTitle && iconSubtitle) { ++ const icon = new ItemIcon(iconName, iconTitle, iconSubtitle); ++ icon.add_constraint(new Clutter.AlignConstraint({ ++ source: this._container, ++ align_axis: Clutter.AlignAxis.X_AXIS, ++ factor: 0.5, ++ })); ++ icon.add_constraint(new Clutter.AlignConstraint({ ++ source: this._container, ++ align_axis: Clutter.AlignAxis.Y_AXIS, ++ factor: 0.0, ++ pivot_point: new Graphene.Point({x: 0.0, y: 0.35}), ++ })); ++ this._container.add_child(icon); ++ } ++ ++ this.set_child(this._container); + + this.connect('key-focus-in', + () => this._setSelected(true)); +@@ -65,25 +176,29 @@ const AuthListItem = GObject.registerClass({ + this.remove_style_pseudo_class('selected'); + } + } +-}); ++} + +-export const AuthList = GObject.registerClass({ +- Signals: { ++export class AuthList extends St.BoxLayout { ++ static [GObject.GTypeName] = 'AuthList'; ++ ++ static [GObject.signals] = { + 'activate': {param_types: [GObject.TYPE_STRING]}, + 'item-added': {param_types: [AuthListItem.$gtype]}, +- }, +-}, class AuthList extends St.BoxLayout { +- _init() { +- super._init({ ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor() { ++ super({ + orientation: Clutter.Orientation.VERTICAL, + style_class: 'login-dialog-auth-list-layout', +- x_align: Clutter.ActorAlign.START, ++ x_align: Clutter.ActorAlign.FILL, + y_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, + }); + +- this.label = new St.Label({style_class: 'login-dialog-auth-list-title'}); +- this.add_child(this.label); +- + this._box = new St.BoxLayout({ + orientation: Clutter.Orientation.VERTICAL, + style_class: 'login-dialog-auth-list', +@@ -136,10 +251,10 @@ export const AuthList = GObject.registerClass({ + }); + } + +- addItem(key, text) { ++ addItem(key, content) { + this.removeItem(key); + +- let item = new AuthListItem(key, text); ++ const item = new AuthListItem(key, content); + this._box.add_child(item); + + this._items.set(key, item); +@@ -170,8 +285,7 @@ export const AuthList = GObject.registerClass({ + } + + clear() { +- this.label.text = ''; + this._box.destroy_all_children(); + this._items.clear(); + } +-}); ++} +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index a383208ecc..8d7dfb9bae 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -216,13 +216,30 @@ export const AuthPrompt = GObject.registerClass({ + duration: MESSAGE_FADE_OUT_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { ++ this._authListTitle.child.text = ''; + this._authList.clear(); + this._authList.hide(); + this._userVerifier.selectChoice(this._queryingService, key); + }, + }); + }); +- this._mainBox.add_child(this._authList); ++ this.add_child(this._authList); ++ ++ this._authListTitle = new St.Button({ ++ style_class: 'login-dialog-auth-list-title', ++ x_expand: true, ++ y_expand: true, ++ child: new St.Label({style_class: 'login-dialog-auth-list-title-label'}), ++ reactive: false, ++ can_focus: false, ++ }); ++ this._authList.bind_property('visible', ++ this._authListTitle, 'visible', ++ GObject.BindingFlags.SYNC_CREATE); ++ this._authList.bind_property('opacity', ++ this._authListTitle, 'opacity', ++ GObject.BindingFlags.SYNC_CREATE); ++ this._mainBox.add_child(this._authListTitle); + + this._entryArea = new St.Widget({ + style_class: 'login-dialog-prompt-entry-area', +@@ -575,6 +592,7 @@ export const AuthPrompt = GObject.registerClass({ + + this._entryArea.hide(); + this.stopSpinning(); ++ this._authListTitle.child.text = ''; + this._authList.clear(); + this._authList.hide(); + +@@ -622,10 +640,10 @@ export const AuthPrompt = GObject.registerClass({ + + 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._authListTitle.child.text = promptMessage; ++ for (const key in choiceList) { ++ const content = choiceList[key]; ++ this._authList.addItem(key, content); + } + + this._entryArea.hide(); +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 4b0f763f0a..b71251458e 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -723,7 +723,11 @@ export class ShellUserVerifier extends Signals.EventEmitter { + if (!this.serviceIsForeground(serviceName)) + return; + +- this.emit('show-choice-list', serviceName, promptMessage, list.deepUnpack()); ++ const choiceList = {}; ++ for (const [key, value] of Object.entries(list.deepUnpack())) ++ choiceList[key] = {title: value}; ++ ++ this.emit('show-choice-list', serviceName, promptMessage, choiceList); + } + + _onInfo(client, serviceName, info) { +-- +2.53.0 + + +From 199fec44007815189b8b23fad04b199a40982768 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Oct 2025 18:48:07 +0200 +Subject: [PATCH 21/42] authPrompt: Let back button go back to step 1 instead + of full reset + +There can be some auth methods that would require multiple steps. In the +original behaviour when pressing back button, it would do a full reset +and display the user list. Now if we're in a multi-step flow (step > 1), +go back to step 1 instead of full reset. + +Show backButton in unlockDialog for these cases. +--- + js/gdm/authPrompt.js | 38 ++++++++++++++++++++++++++++---------- + 1 file changed, 28 insertions(+), 10 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 8d7dfb9bae..3326096a2d 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -70,6 +70,7 @@ export const AuthPrompt = GObject.registerClass({ + this._mode = mode; + this._defaultButtonWellActor = null; + this._cancelledRetries = 0; ++ this._promptStep = 0; + + this._idleMonitor = global.backend.get_core_idle_monitor(); + +@@ -101,8 +102,6 @@ export const AuthPrompt = GObject.registerClass({ + }); + this.add_child(this._userWell); + +- this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN; +- + this._initInputRow(); + + let capsLockPlaceholder = new St.Label(); +@@ -184,8 +183,8 @@ export const AuthPrompt = GObject.registerClass({ + style_class: 'login-dialog-button cancel-button', + accessible_name: _('Cancel'), + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, +- reactive: this._hasCancelButton, +- can_focus: this._hasCancelButton, ++ reactive: true, ++ can_focus: true, + x_expand: true, + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.CENTER, +@@ -197,10 +196,8 @@ export const AuthPrompt = GObject.registerClass({ + pivot_point: new Graphene.Point({x: 1, y: 0}), + })); + +- if (this._hasCancelButton) +- this.cancelButton.connect('clicked', () => this.cancel()); +- else +- this.cancelButton.opacity = 0; ++ this.cancelButton.connect('clicked', () => this.cancel()); ++ this._updateCancelButton(); + this._mainBox.add_child(this.cancelButton); + + this._authList = new AuthList.AuthList(); +@@ -311,6 +308,16 @@ export const AuthPrompt = GObject.registerClass({ + this.setActorInDefaultButtonWell(this._nextButton); + } + ++ _updateCancelButton() { ++ if (this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN) ++ return; ++ ++ const cancelVisible = this._promptStep > 1; ++ this.cancelButton.visible = cancelVisible; ++ this.cancelButton.reactive = cancelVisible; ++ this.cancelButton.can_focus = cancelVisible; ++ } ++ + showTimedLoginIndicator(time) { + let hold = new Batch.Hold(); + +@@ -397,6 +404,9 @@ export const AuthPrompt = GObject.registerClass({ + this.clear(); + + this._queryingService = serviceName; ++ this._promptStep++; ++ this._updateCancelButton(); ++ + if (this._preemptiveAnswer) { + this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer); + this._preemptiveAnswer = null; +@@ -421,6 +431,8 @@ export const AuthPrompt = GObject.registerClass({ + this.clear(); + + this._queryingService = serviceName; ++ this._promptStep++; ++ this._updateCancelButton(); + + if (this._preemptiveAnswer) + this._preemptiveAnswer = null; +@@ -767,10 +779,10 @@ export const AuthPrompt = GObject.registerClass({ + + const oldStatus = this.verificationStatus; + this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; +- this.cancelButton.reactive = this._hasCancelButton; +- this.cancelButton.can_focus = this._hasCancelButton; + if (oldStatus !== AuthPromptStatus.VERIFICATION_IN_PROGRESS) + this._preemptiveAnswer = null; ++ this._promptStep = 0; ++ this._updateCancelButton(); + + if (this._preemptiveAnswerWatchId) + this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); +@@ -858,6 +870,12 @@ export const AuthPrompt = GObject.registerClass({ + if (this.verificationStatus === AuthPromptStatus.VERIFICATION_SUCCEEDED) + return; + ++ // If we're in a multi-step flow (step > 1), go back to step 1 instead of full reset ++ if (this._promptStep > 1) { ++ this.reset({softReset: true}); ++ return; ++ } ++ + if (this.verificationStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) { + this._cancelledRetries++; + if (this._cancelledRetries > this._userVerifier.allowedFailures) +-- +2.53.0 + + +From 09d762ac05a793f5df5fd51be2e674fd0929252d Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 20 Oct 2025 17:24:40 +0200 +Subject: [PATCH 22/42] loginDialog: Vertically center authPrompt using fixed + height + +Use a fixed estimated height for centering so the position stays +stable regardless of actual content height changes during user +interaction. +--- + js/gdm/loginDialog.js | 25 ++++++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 09e8073c65..3a22bf161b 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -45,6 +45,7 @@ import * as A11y from '../ui/status/accessibility.js'; + + const _FADE_ANIMATION_TIME = 250; + const _SCROLL_ANIMATION_TIME = 500; ++const _FIXED_TOP_ACTOR_HEIGHT = 400; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60; + +@@ -732,6 +733,28 @@ export const LoginDialog = GObject.registerClass({ + return actorBox; + } + ++ _getFixedTopActorAllocation(dialogBox, actor) { ++ const actorBox = new Clutter.ActorBox(); ++ ++ let [, , natWidth, natHeight] = actor.get_preferred_size(); ++ const dialogWidth = dialogBox.x2 - dialogBox.x1; ++ const dialogHeight = dialogBox.y2 - dialogBox.y1; ++ const centerX = dialogBox.x1 + dialogWidth / 2; ++ const centerY = dialogBox.y1 + dialogHeight / 2; ++ ++ const top = centerY - _FIXED_TOP_ACTOR_HEIGHT / 2; ++ ++ natWidth = Math.min(natWidth, dialogWidth); ++ natHeight = Math.min(natHeight, dialogHeight); ++ ++ actorBox.x1 = Math.floor(centerX - natWidth / 2); ++ actorBox.y1 = Math.floor(top); ++ actorBox.x2 = actorBox.x1 + natWidth; ++ actorBox.y2 = actorBox.y1 + natHeight; ++ ++ return actorBox; ++ } ++ + _getCenterActorAllocation(dialogBox, actor) { + let actorBox = new Clutter.ActorBox(); + +@@ -770,7 +793,7 @@ export const LoginDialog = GObject.registerClass({ + let authPromptAllocation = null; + let authPromptWidth = 0; + if (this._authPrompt.visible) { +- authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt); ++ authPromptAllocation = this._getFixedTopActorAllocation(dialogBox, this._authPrompt); + authPromptWidth = authPromptAllocation.x2 - authPromptAllocation.x1; + } + +-- +2.53.0 + + +From cfec9c4e34f698ad5693d5e81daf1d31a8972ccb Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 12 Nov 2024 14:26:30 -0500 +Subject: [PATCH 23/42] data: Add fingerprint and vcard icons + +Fingerprint icon will be used to inform when it's being run un the +background. + +Vcard icon will be used to inform on smartcard certificates list when they +have an Organization field in its subject. +--- + data/gnome-shell-icons.gresource.xml | 2 ++ + .../status/fingerprint-auth-symbolic.svg | 28 +++++++++++++++++++ + data/icons/scalable/status/vcard-symbolic.svg | 9 ++++++ + 3 files changed, 39 insertions(+) + create mode 100644 data/icons/scalable/status/fingerprint-auth-symbolic.svg + create mode 100644 data/icons/scalable/status/vcard-symbolic.svg + +diff --git a/data/gnome-shell-icons.gresource.xml b/data/gnome-shell-icons.gresource.xml +index 4c9a3446b5..11a5afa3fd 100644 +--- a/data/gnome-shell-icons.gresource.xml ++++ b/data/gnome-shell-icons.gresource.xml +@@ -51,6 +51,7 @@ + scalable/actions/shell-focus-windows-symbolic.svg + scalable/status/background-app-ghost-symbolic.svg + scalable/status/check-symbolic.svg ++ scalable/status/fingerprint-auth-symbolic.svg + scalable/status/keyboard-brightness-high-symbolic.svg + scalable/status/keyboard-brightness-medium-symbolic.svg + scalable/status/keyboard-brightness-off-symbolic.svg +@@ -68,6 +69,7 @@ + scalable/status/screen-privacy-symbolic.svg + scalable/status/switch-off-symbolic.svg + scalable/status/switch-on-symbolic.svg ++ scalable/status/vcard-symbolic.svg + scalable/status/wellbeing-symbolic.svg + + +diff --git a/data/icons/scalable/status/fingerprint-auth-symbolic.svg b/data/icons/scalable/status/fingerprint-auth-symbolic.svg +new file mode 100644 +index 0000000000..67cce39bc1 +--- /dev/null ++++ b/data/icons/scalable/status/fingerprint-auth-symbolic.svg +@@ -0,0 +1,28 @@ ++ ++ ++ ++image/svg+xmlauth-fingerprint +diff --git a/data/icons/scalable/status/vcard-symbolic.svg b/data/icons/scalable/status/vcard-symbolic.svg +new file mode 100644 +index 0000000000..1694f23645 +--- /dev/null ++++ b/data/icons/scalable/status/vcard-symbolic.svg +@@ -0,0 +1,9 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +-- +2.53.0 + + +From 4df4492dfd42af8f9449249ad2f5778b63e9f4d7 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 18 Aug 2025 12:30:50 +0200 +Subject: [PATCH 24/42] gdm: Extract authentication service and role constants + to const.js + +Create a dedicated const.js module to centralize GDM authentication-related +constants that are shared across multiple files. + +Also, in utils, add isSelectable() and getIconName() to get mechanisms metadata. + +This prepares the codebase for upcoming commits that will use these +constants. +--- + js/gdm/authPrompt.js | 3 +- + js/gdm/const.js | 10 ++++++ + js/gdm/util.js | 64 +++++++++++++++++++++++++---------- + js/js-resources.gresource.xml | 1 + + 4 files changed, 59 insertions(+), 19 deletions(-) + create mode 100644 js/gdm/const.js + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 3326096a2d..7b3aa8558f 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -10,6 +10,7 @@ import St from 'gi://St'; + import * as Animation from '../ui/animation.js'; + import * as AuthList from './authList.js'; + import * as Batch from './batch.js'; ++import * as Const from './const.js'; + import * as GdmUtil from './util.js'; + import * as Params from '../misc/params.js'; + import * as ShellEntry from '../ui/shellEntry.js'; +@@ -457,7 +458,7 @@ export const AuthPrompt = GObject.registerClass({ + // with a smartcard + // 2) Don't reset if we've already succeeded at verification and + // the user is getting logged in. +- if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && ++ if (this._userVerifier.serviceIsDefault(Const.SMARTCARD_SERVICE_NAME) && + (this.verificationStatus === AuthPromptStatus.VERIFYING || + this.verificationStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) && + this.smartcardDetected) +diff --git a/js/gdm/const.js b/js/gdm/const.js +new file mode 100644 +index 0000000000..2f37446c8a +--- /dev/null ++++ b/js/gdm/const.js +@@ -0,0 +1,10 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++export const PASSWORD_ROLE_NAME = 'password'; ++export const SMARTCARD_ROLE_NAME = 'smartcard'; ++export const FINGERPRINT_ROLE_NAME = 'fingerprint'; ++ ++export const PASSWORD_SERVICE_NAME = 'gdm-password'; ++export const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; ++export const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; ++export const SWITCHABLE_AUTH_SERVICE_NAME = 'gdm-switchable-auth'; +diff --git a/js/gdm/util.js b/js/gdm/util.js +index b71251458e..660c6e4ddc 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -24,9 +24,6 @@ Gio._promisify(Gdm.UserVerifierProxy.prototype, + 'call_begin_verification_for_user'); + Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification'); + +-export const PASSWORD_SERVICE_NAME = 'gdm-password'; +-export const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; +-export const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; + const CLONE_FADE_ANIMATION_TIME = 250; + + export const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; +@@ -101,6 +98,37 @@ export function cloneAndFadeOutActor(actor) { + return hold; + } + ++/** ++ * @param {object} mechanism ++ * @returns {boolean} ++ */ ++export function isSelectable(mechanism) { ++ switch (mechanism.role) { ++ case Const.PASSWORD_ROLE_NAME: ++ case Const.SMARTCARD_ROLE_NAME: ++ return true; ++ case Const.FINGERPRINT_ROLE_NAME: ++ return false; ++ default: ++ throw new Error(`Failed checking mechanism is selectable: ${mechanism.role}`); ++ } ++} ++ ++/** ++ * @param {object} mechanism ++ * @returns {string} ++ */ ++export function getIconName(mechanism) { ++ // This is only used for non selectable mechanisms. ++ // Currently only fingerprint is non selectable ++ switch (mechanism.role) { ++ case Const.FINGERPRINT_ROLE_NAME: ++ return 'fingerprint-auth-symbolic'; ++ default: ++ throw new Error(`Failed getting mechanism icon: ${mechanism.role}`); ++ } ++} ++ + export class ShellUserVerifier extends Signals.EventEmitter { + constructor(client, params) { + super(); +@@ -431,7 +459,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._updateDefaultService(); + + if (this._userVerifier && +- !this._activeServices.has(FINGERPRINT_SERVICE_NAME)) { ++ !this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) { + if (!this._hold?.isAcquired()) + this._hold = new Batch.Hold(); + await this._maybeStartFingerprintVerification(); +@@ -485,8 +513,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this.smartcardDetected = smartcardDetected; + + if (this.smartcardDetected) +- this._preemptingService = SMARTCARD_SERVICE_NAME; +- else if (this._preemptingService === SMARTCARD_SERVICE_NAME) ++ this._preemptingService = Const.SMARTCARD_SERVICE_NAME; ++ else if (this._preemptingService === Const.SMARTCARD_SERVICE_NAME) + this._preemptingService = null; + + this._updateDefaultService(); +@@ -605,7 +633,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + return true; + } + +- return this.serviceIsForeground(SMARTCARD_SERVICE_NAME); ++ return this.serviceIsForeground(Const.SMARTCARD_SERVICE_NAME); + } + + serviceIsDefault(serviceName) { +@@ -614,7 +642,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + serviceIsFingerprint(serviceName) { + return this._fingerprintReaderType !== FingerprintReaderType.NONE && +- serviceName === FINGERPRINT_SERVICE_NAME; ++ serviceName === Const.FINGERPRINT_SERVICE_NAME; + } + + _onSettingsChanged() { +@@ -631,7 +659,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._fingerprintManager = null; + this._fingerprintReaderType = FingerprintReaderType.NONE; + +- if (this._activeServices.has(FINGERPRINT_SERVICE_NAME)) ++ if (this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) + needsReset = true; + } + +@@ -641,7 +669,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._smartcardManager.disconnectObject(this); + this._smartcardManager = null; + +- if (this._activeServices.has(SMARTCARD_SERVICE_NAME)) ++ if (this._activeServices.has(Const.SMARTCARD_SERVICE_NAME)) + needsReset = true; + } + +@@ -651,13 +679,13 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + _getDetectedDefaultService() { + if (this._smartcardManager?.loggedInWithToken()) +- return SMARTCARD_SERVICE_NAME; ++ return Const.SMARTCARD_SERVICE_NAME; + else if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) +- return PASSWORD_SERVICE_NAME; ++ return Const.PASSWORD_SERVICE_NAME; + else if (this._smartcardManager) +- return SMARTCARD_SERVICE_NAME; ++ return Const.SMARTCARD_SERVICE_NAME; + else if (this._fingerprintReaderType !== FingerprintReaderType.NONE) +- return FINGERPRINT_SERVICE_NAME; ++ return Const.FINGERPRINT_SERVICE_NAME; + return null; + } + +@@ -667,7 +695,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + if (!this._defaultService) { + log('no authentication service is enabled, using password authentication'); +- this._defaultService = PASSWORD_SERVICE_NAME; ++ this._defaultService = Const.PASSWORD_SERVICE_NAME; + } + + if (oldDefaultService && +@@ -715,8 +743,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + async _maybeStartFingerprintVerification() { + if (this._userName && + this._fingerprintReaderType !== FingerprintReaderType.NONE && +- !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME)) +- await this._startService(FINGERPRINT_SERVICE_NAME); ++ !this.serviceIsForeground(Const.FINGERPRINT_SERVICE_NAME)) ++ await this._startService(Const.FINGERPRINT_SERVICE_NAME); + } + + _onChoiceListQuery(client, serviceName, promptMessage, list) { +@@ -848,7 +876,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + async _verificationFailed(serviceName, shouldRetry) { +- if (serviceName === FINGERPRINT_SERVICE_NAME) { ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME) { + if (this._fingerprintFailedId) + GLib.source_remove(this._fingerprintFailedId); + } +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index e5e6167f15..ee6b77d322 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -4,6 +4,7 @@ + gdm/authList.js + gdm/authPrompt.js + gdm/batch.js ++ gdm/const.js + gdm/credentialManager.js + gdm/loginDialog.js + gdm/oVirt.js +-- +2.53.0 + + +From 90848a72fd7a322d83a7f0319d31bf6a498e01d1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 11:04:20 -0500 +Subject: [PATCH 25/42] gdm: Add new AuthMenuButton control + +The latest login screen designs show a new "Login Options" menu in the +corner for session selection and login methods. + +As a first step toward acheiving that goal, this commit adds a new +AuthMenuButton class. It's a fairly generic control derived from +the code used to show the sessions menu button. One key difference +is it allows a multi-valued, partial key for its entries. This provides +flexibility that we'll need to leverage later. + +Also add AuthMenuButtonIndicator which derives from AuthMenuButton to +display icons to inform about non selectable background authentication +methods in use, it's non-interactive. + +Nothing uses these new classes yet. A subsequent commit will change the +sessions menu button code over to use it, and a commit after that will +use it for Login Options. +--- + .../gnome-shell-sass/widgets/_login-lock.scss | 42 ++ + js/gdm/authMenuButton.js | 613 ++++++++++++++++++ + js/js-resources.gresource.xml | 1 + + js/ui/popupMenu.js | 22 + + 4 files changed, 678 insertions(+) + create mode 100644 js/gdm/authMenuButton.js + +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index 5a31725b1d..45d47f0652 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -67,6 +67,48 @@ $_gdm_dialog_width: 25em; + } + } + ++.login-dialog-auth-menu-button-popup { ++ padding: $base_padding * 3; ++ margin-right: $base_padding * 2; ++ ++ .login-dialog-auth-menu-header { ++ @include fontsize($base_font_size - 1); ++ text-align: center; ++ font-weight: bold; ++ padding-top: $base_padding * 3; ++ padding-bottom: $base_padding; ++ ++ &:first-child { ++ padding-top: $base_padding; ++ } ++ } ++ ++ .login-dialog-auth-menu-item-indicator { ++ spacing: $base_padding * .5; ++ ++ .login-dialog-auth-menu-item-indicator-name { ++ @include fontsize($base_font_size + 1.75); ++ font-weight: bold; ++ } ++ ++ .login-dialog-auth-menu-item-indicator-description { ++ @include fontsize($base_font_size - .25); ++ } ++ } ++} ++ ++.login-dialog-auth-menu-button-indicator { ++ background-color: transparent !important; ++ ++ .login-dialog-auth-menu-button-indicator-icons { ++ spacing: $base_padding * 3; ++ ++ .login-dialog-auth-menu-button-indicator-icon { ++ icon-size: 2em; ++ } ++ } ++} ++ + .login-dialog-button-box { + height: 4em; + } +diff --git a/js/gdm/authMenuButton.js b/js/gdm/authMenuButton.js +new file mode 100644 +index 0000000000..b5018d8382 +--- /dev/null ++++ b/js/gdm/authMenuButton.js +@@ -0,0 +1,613 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++/* ++ * Copyright 2024 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 . ++ */ ++ ++/* ++ * AuthMenuButton implements a menu button that is used to manage login options ++ * (authentication methods, session) ++ * ++ * Item objects are mapped to menu items internally using the item objects ++ * themselves as their own key. ++ * Each item can have arbitrary properties, but certain properties have special ++ * meaning: ++ * - name: (required) Display text for the item ++ * - iconName: Icon to show for the item and optionally in the button ++ * - description: Additional descriptive text shown below the name ++ * - sectionName: Groups items into labeled sections ++ * ++ * The button supports searching for items using partial criteria objects. Any ++ * item matching all non-null properties in the criteria will be returned. ++ * For example: ++ * {sectionName: 'foo'} matches all items in section 'foo' ++ * {name: 'bar', sectionName: 'foo'} matches the item named 'bar' in ++ * section 'foo' ++ * ++ * Items within sections can be ordered using the sectionOrder parameter during ++ * initialization. Sections not in sectionOrder appear at the end. ++ * ++ * Only one item per section can be active at a time. Setting a new active item ++ * deactivates any other active item in the same section. ++ */ ++ ++import Atk from 'gi://Atk'; ++import Clutter from 'gi://Clutter'; ++import GObject from 'gi://GObject'; ++import Shell from 'gi://Shell'; ++import St from 'gi://St'; ++ ++import * as BoxPointer from '../ui/boxpointer.js'; ++import * as Main from '../ui/main.js'; ++import * as PopupMenu from '../ui/popupMenu.js'; ++ ++const VISIBILITY_ANIMATION_TIME = 200; ++ ++class AuthMenuItem extends PopupMenu.PopupImageMenuItem { ++ static [GObject.GTypeName] = 'AuthMenuItem'; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(item, params) { ++ super(item.name, item.iconName || '', params); ++ ++ // Move ornament to the left ++ this.set_child_at_index(this._ornamentIcon, 0); ++ } ++ ++ updateLabelActor(labelActor) { ++ this.insert_child_below(labelActor, this.label_actor); ++ this.remove_child(this.label_actor); ++ ++ this.label_actor.destroy(); ++ this.label_actor = labelActor; ++ } ++} ++ ++class AuthMenuItemIndicator extends AuthMenuItem { ++ static [GObject.GTypeName] = 'AuthMenuItemIndicator'; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(item, params) { ++ super(item, params); ++ ++ if (item.description) { ++ const box = new St.BoxLayout({ ++ vertical: true, ++ style_class: 'login-dialog-auth-menu-item-indicator', ++ }); ++ ++ const nameLabel = new St.Label({ ++ text: item.name, ++ style_class: 'login-dialog-auth-menu-item-indicator-name', ++ }); ++ ++ const descriptionLabel = new St.Label({ ++ text: item.description, ++ style_class: 'login-dialog-auth-menu-item-indicator-description', ++ }); ++ ++ box.add_child(nameLabel); ++ box.add_child(descriptionLabel); ++ ++ this.updateLabelActor(box); ++ } ++ } ++} ++ ++export class AuthMenuButton extends St.Bin { ++ static [GObject.GTypeName] = 'AuthMenuButton'; ++ ++ static [GObject.properties] = { ++ 'title': GObject.ParamSpec.string( ++ 'title', null, null, ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ++ ''), ++ 'icon-name': GObject.ParamSpec.string( ++ 'icon-name', null, null, ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ++ ''), ++ 'read-only': GObject.ParamSpec.boolean( ++ 'read-only', null, null, ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ++ false), ++ 'section-order': GObject.ParamSpec.jsobject( ++ 'section-order', null, null, ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY), ++ 'animate-visibility': GObject.ParamSpec.boolean( ++ 'animate-visibility', null, null, ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ++ false), ++ }; ++ ++ static [GObject.signals] = { ++ 'active-item-changed': {param_types: [GObject.TYPE_STRING]}, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(params) { ++ params.sectionOrder ??= []; ++ super(params); ++ ++ const button = new St.Button({ ++ style_class: 'login-dialog-button login-dialog-auth-menu-button', ++ child: new St.Icon({icon_name: this.iconName}), ++ reactive: true, ++ track_hover: true, ++ can_focus: true, ++ accessible_name: this.title, ++ accessible_role: Atk.Role.MENU, ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ ++ this.child = button; ++ ++ this._button = button; ++ ++ this._menu = new PopupMenu.PopupMenu(this, 0, St.Side.BOTTOM); ++ this._menu.box.add_style_class_name('login-dialog-auth-menu-button-popup'); ++ Main.uiGroup.add_child(this._menu.actor); ++ this._menu.actor.hide(); ++ ++ this._menu.connect('open-state-changed', (_menu, isOpen) => { ++ if (this.readOnly) { ++ if (isOpen) ++ this._addMenuShield(); ++ else ++ this._removeMenuShield(); ++ } ++ }); ++ ++ this._manager = new PopupMenu.PopupMenuManager(this._button, ++ {actionMode: Shell.ActionMode.NONE}); ++ this._manager.addMenu(this._menu); ++ ++ this._button.connect('clicked', () => { ++ if (this.readOnly && this._getVisibleItemsCount() === 1) ++ return; ++ this._menu.toggle(); ++ }); ++ ++ this._items = new Map(); ++ this._activeItems = new Set(); ++ this._headers = new Map(); ++ this.updateSensitivity(true); ++ } ++ ++ _addMenuShield() { ++ if (this._menuShield) ++ return; ++ ++ this._menuShield = new St.Widget({ ++ reactive: true, ++ opacity: 0, ++ }); ++ ++ Main.uiGroup.add_child(this._menuShield); ++ ++ this._menuShield.add_constraint(new Clutter.BindConstraint({ ++ source: this._menu.actor, ++ coordinate: Clutter.BindCoordinate.ALL, ++ })); ++ } ++ ++ _removeMenuShield() { ++ if (this._menuShield) { ++ this._menuShield.destroy(); ++ this._menuShield = null; ++ } ++ } ++ ++ _getMenuItem(item) { ++ if (!item) ++ return null; ++ ++ return this._items.get(JSON.stringify(item)); ++ } ++ ++ _getVisibleItemsCount() { ++ return Array.from(this._items.values()).filter(item => item.visible).length; ++ } ++ ++ updateReactive(reactive) { ++ this._button.reactive = reactive; ++ this._button.can_focus = reactive; ++ } ++ ++ updateSensitivity(sensitive) { ++ this._sensitive = sensitive; ++ ++ const visibleItems = this._getVisibleItemsCount(); ++ if (visibleItems === 0 || (visibleItems <= 1 && !this.readOnly)) ++ sensitive = false; ++ ++ this._button.reactive = sensitive; ++ this._button.can_focus = sensitive; ++ this._menu.close(BoxPointer.PopupAnimation.NONE); ++ ++ if (this.animateVisibility) { ++ if (sensitive) { ++ this.opacity = 0; ++ this.visible = true; ++ this.ease({ ++ opacity: 255, ++ duration: VISIBILITY_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ }); ++ } else { ++ this.ease({ ++ opacity: 0, ++ duration: VISIBILITY_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ onComplete: () => { ++ this.visible = false; ++ }, ++ }); ++ } ++ } else { ++ this.opacity = sensitive ? 255 : 0; ++ this.visible = sensitive; ++ } ++ } ++ ++ _updateOrnament() { ++ for (const menuItem of this._items.values()) ++ menuItem.setOrnament(PopupMenu.Ornament.NO_DOT); ++ ++ for (const itemKey of this._activeItems) { ++ const menuItem = this._getMenuItem(JSON.parse(itemKey)); ++ if (menuItem) ++ menuItem.setOrnament(PopupMenu.Ornament.DOT); ++ } ++ } ++ ++ getSections() { ++ const sectionsSet = new Set(); ++ for (const itemKey of this._items.keys()) { ++ const item = JSON.parse(itemKey); ++ if (item.sectionName) ++ sectionsSet.add(item.sectionName); ++ } ++ ++ const sections = Array.from(sectionsSet); ++ sections.sort((a, b) => { ++ const indexA = this.sectionOrder.indexOf(a); ++ const indexB = this.sectionOrder.indexOf(b); ++ ++ if (indexA !== -1 && indexB !== -1) ++ return indexA - indexB; ++ if (indexA !== -1) ++ return -1; ++ if (indexB !== -1) ++ return 1; ++ return 0; ++ }); ++ ++ return sections; ++ } ++ ++ _deepEquals(a, b) { ++ if (a === b) ++ return true; ++ ++ if (a == null || b == null) ++ return false; ++ ++ const keysA = Object.keys(a); ++ const keysB = Object.keys(b); ++ ++ if (keysA.length !== keysB.length) ++ return false; ++ ++ for (const key of keysA) { ++ if (!keysB.includes(key)) ++ return false; ++ ++ if (!this._deepEquals(a[key], b[key])) ++ return false; ++ } ++ ++ return true; ++ } ++ ++ _findItems(searchCriteria) { ++ const items = []; ++ for (const itemKey of this._items.keys()) { ++ const item = JSON.parse(itemKey); ++ ++ let criteriaMismatch = false; ++ for (const key of Object.keys(searchCriteria)) { ++ if (!searchCriteria[key]) ++ continue; ++ ++ if (this._deepEquals(item[key], searchCriteria[key])) ++ continue; ++ ++ criteriaMismatch = true; ++ break; ++ } ++ ++ if (criteriaMismatch) ++ continue; ++ ++ items.push(itemKey); ++ } ++ ++ return items; ++ } ++ ++ getItems(searchCriteria = {}) { ++ return this._findItems(searchCriteria).map(itemKey => JSON.parse(itemKey)); ++ } ++ ++ clearItems(searchCriteria = {}) { ++ const sections = this.getSections(); ++ ++ this._findItems(searchCriteria).forEach(itemKey => { ++ const menuItem = this._items.get(itemKey); ++ this._activeItems.delete(itemKey); ++ menuItem.destroy(); ++ this._items.delete(itemKey); ++ }); ++ ++ sections.forEach(sectionName => { ++ const itemsInSection = this._findItems({sectionName}); ++ if (itemsInSection.length === 0) { ++ const header = this._headers.get(sectionName); ++ if (header) { ++ header.destroy(); ++ this._headers.delete(sectionName); ++ } ++ } ++ }); ++ ++ this._updateVisibility(); ++ this.updateSensitivity(this._sensitive); ++ } ++ ++ addItem(item) { ++ const itemKey = JSON.stringify(item); ++ if (this._items.has(itemKey)) ++ throw new Error(`Duplicate item ${itemKey}`); ++ ++ if (!item.name) ++ throw new Error(`item ${itemKey} lacks name`); ++ ++ const sectionName = item.sectionName ?? null; ++ if (sectionName && !this._headers.has(sectionName)) { ++ const header = new St.Label({ ++ text: sectionName, ++ style_class: 'login-dialog-auth-menu-header', ++ y_align: Clutter.ActorAlign.START, ++ y_expand: true, ++ }); ++ ++ let insertIndex = 0; ++ const orderIndex = this.sectionOrder.indexOf(sectionName); ++ ++ if (orderIndex === -1) { ++ insertIndex = -1; ++ } else { ++ for (const existingSectionName of this._headers.keys()) { ++ const existingIndex = this.sectionOrder.indexOf(existingSectionName); ++ if (existingIndex === -1 || existingIndex > orderIndex) ++ break; ++ insertIndex++; ++ } ++ } ++ ++ this._menu.box.insert_child_at_index(header, insertIndex); ++ this._headers.set(sectionName, header); ++ } ++ ++ const menuItem = this._createMenuItem(item); ++ menuItem.setOrnament(PopupMenu.Ornament.HIDDEN); ++ ++ menuItem.connect('activate', () => { ++ this.setActiveItem(item); ++ }); ++ ++ if (sectionName) { ++ const children = this._menu.box.get_children(); ++ const header = this._headers.get(sectionName); ++ const headerIndex = children.indexOf(header); ++ ++ let insertIndex = headerIndex + 1; ++ while (insertIndex < children.length && children[insertIndex] instanceof AuthMenuItem) ++ insertIndex++; ++ ++ this._menu.box.insert_child_at_index(menuItem, insertIndex); ++ } else { ++ this._menu.box.add_child(menuItem); ++ } ++ ++ this._items.set(itemKey, menuItem); ++ this._updateVisibility(); ++ this.updateSensitivity(this._sensitive); ++ } ++ ++ _createMenuItem(item) { ++ return new AuthMenuItem(item); ++ } ++ ++ _updateVisibility() { ++ const visibleSections = this._getVisibleSections(); ++ ++ for (const [sectionName, header] of this._headers) { ++ const showThisSection = visibleSections.includes(sectionName); ++ ++ header.visible = showThisSection; ++ ++ const sectionItems = this._findItems({sectionName}); ++ for (const itemKey of sectionItems) { ++ const menuItem = this._items.get(itemKey); ++ menuItem.visible = showThisSection; ++ } ++ } ++ ++ this._button.visible = visibleSections.length > 0; ++ } ++ ++ _getVisibleSections() { ++ return Array.from(this._headers.keys()).filter(sectionName => ++ this._getSectionItemCount(sectionName) > 1 ++ ); ++ } ++ ++ _getSectionItemCount(sectionName) { ++ return this._findItems({sectionName}).length; ++ } ++ ++ _resolveItem(searchCriteria) { ++ const itemKeys = this._findItems(searchCriteria); ++ ++ if (!itemKeys.length) ++ throw new Error(`Unknown item ${JSON.stringify(searchCriteria)}`); ++ ++ if (itemKeys.length > 1) ++ throw new Error(`Matched multiple items with criteria ${JSON.stringify(searchCriteria)}`); ++ ++ const item = JSON.parse(itemKeys[0]); ++ const menuItem = this._items.get(itemKeys[0]); ++ return {item, menuItem}; ++ } ++ ++ setActiveItem(searchCriteria) { ++ const {item} = this._resolveItem(searchCriteria); ++ const itemKey = JSON.stringify(item); ++ ++ if (this._activeItems.has(itemKey)) ++ return; ++ ++ const sectionName = item.sectionName ?? null; ++ const activeInSection = this._findItems({sectionName}) ++ .filter(key => this._activeItems.has(key)); ++ ++ for (const key of activeInSection) ++ this._activeItems.delete(key); ++ ++ this._activeItems.add(itemKey); ++ this._updateOrnament(); ++ this.emit('active-item-changed', sectionName); ++ } ++ ++ getActiveItem(searchCriteria = {}) { ++ const activeKeys = this._findItems(searchCriteria) ++ .filter(key => this._activeItems.has(key)); ++ ++ if (activeKeys.length === 0) ++ return null; ++ ++ if (activeKeys.length > 1) ++ throw new Error(`Multiple active items found with criteria ${JSON.stringify(searchCriteria)}`); ++ ++ return JSON.parse(activeKeys[0]); ++ } ++ ++ close() { ++ this._menu.close(); ++ } ++} ++ ++export class AuthMenuButtonIndicator extends AuthMenuButton { ++ static [GObject.GTypeName] = 'AuthMenuButtonIndicator'; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(params) { ++ params.readOnly = true; ++ super(params); ++ ++ this._button.add_style_class_name('login-dialog-auth-menu-button-indicator'); ++ ++ const container = new St.BoxLayout({ ++ x_align: Clutter.ActorAlign.START, ++ }); ++ ++ this._iconsBox = new St.BoxLayout({ ++ style_class: 'login-dialog-auth-menu-button-indicator-icons', ++ x_expand: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ }); ++ this._button.child = this._iconsBox; ++ ++ this.remove_child(this._button); ++ container.add_child(this._button); ++ ++ this._descriptionLabel = new St.Label({ ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this._descriptionLabel.bind_property_full('text', ++ this._descriptionLabel, 'visible', ++ GObject.BindingFlags.SYNC_CREATE, ++ (bind, source) => [true, !!source], ++ null); ++ container.add_child(this._descriptionLabel); ++ ++ this.child = container; ++ ++ this._menu.setSourceActor(this._button); ++ } ++ ++ addItem(item) { ++ if (item.iconName) { ++ const icon = new St.Icon({ ++ icon_name: item.iconName, ++ style_class: 'login-dialog-auth-menu-button-indicator-icon', ++ }); ++ this._iconsBox.add_child(icon); ++ } ++ ++ super.addItem(item); ++ } ++ ++ _createMenuItem(item) { ++ return new AuthMenuItemIndicator(item); ++ } ++ ++ clearItems(searchCriteria = {}) { ++ this._findItems(searchCriteria).forEach(itemKey => { ++ const item = JSON.parse(itemKey); ++ if (item.iconName) { ++ this._iconsBox.get_children() ++ .find(icon => icon.icon_name === item.iconName) ++ ?.destroy(); ++ } ++ }); ++ ++ super.clearItems(searchCriteria); ++ this.updateDescriptionLabel(); ++ } ++ ++ updateDescriptionLabel() { ++ const [item] = this._items.size === 1 ? this.getItems() : []; ++ this._descriptionLabel.text = item?.description ?? ''; ++ } ++ ++ // Override to force visibility even when there's only one item ++ _updateVisibility() { ++ } ++} +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index ee6b77d322..254d2af958 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -2,6 +2,7 @@ + + + gdm/authList.js ++ gdm/authMenuButton.js + gdm/authPrompt.js + gdm/batch.js + gdm/const.js +diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js +index 0426ce5452..b719b1bc0c 100644 +--- a/js/ui/popupMenu.js ++++ b/js/ui/popupMenu.js +@@ -1104,6 +1104,28 @@ export class PopupMenu extends PopupMenuBase { + this._boxPointer.setSourceAlignment(alignment); + } + ++ setSourceActor(sourceActor) { ++ if (this.sourceActor === sourceActor) ++ return; ++ ++ this.sourceActor?.disconnectObject(this); ++ ++ this.sourceActor = sourceActor; ++ this.focusActor = sourceActor; ++ ++ if (this.sourceActor) { ++ this.sourceActor.connectObject( ++ 'key-press-event', this._onKeyPress.bind(this), ++ 'notify::mapped', () => { ++ if (!this.sourceActor.mapped) ++ this.close(); ++ }, this); ++ } ++ ++ if (this.isOpen) ++ this._boxPointer.setPosition(this.sourceActor, this._arrowAlignment); ++ } ++ + open(animate) { + if (this.isOpen) + return; +-- +2.53.0 + + +From a4272f28a818a1cb49b51e62a42198bb49c8fa42 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 11:11:32 -0500 +Subject: [PATCH 26/42] loginDialog: Port sessions menu over to AuthMenuButton + +Now that AuthMenuButton exists, we should use it. + +This commit changes the session menu over to use the new +control. +--- + .../gnome-shell-sass/widgets/_login-lock.scss | 1 + + js/gdm/loginDialog.js | 170 ++++++------------ + 2 files changed, 56 insertions(+), 115 deletions(-) + +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index 45d47f0652..c18a6bb8a8 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -50,6 +50,7 @@ $_gdm_dialog_width: 25em; + &.a11y-button, + &.cancel-button, + &.switch-user-button, ++ &.login-dialog-auth-menu-button, + &.login-dialog-session-list-button { + @extend .icon-button; + @extend %system_button; +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 3a22bf161b..46e2fbc126 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -27,9 +27,9 @@ import Pango from 'gi://Pango'; + import Shell from 'gi://Shell'; + import St from 'gi://St'; + ++import * as AuthMenuButton from './authMenuButton.js'; + import * as AuthPrompt from './authPrompt.js'; + import * as Batch from './batch.js'; +-import * as BoxPointer from '../ui/boxpointer.js'; + import * as CtrlAltTab from '../ui/ctrlAltTab.js'; + import * as GdmUtil from './util.js'; + import * as Layout from '../ui/layout.js'; +@@ -48,6 +48,7 @@ const _SCROLL_ANIMATION_TIME = 500; + const _FIXED_TOP_ACTOR_HEIGHT = 400; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60; ++const _SESSION_TYPE_SECTION_NAME = _('Session Type'); + + const N_A11Y_MENU_COLUMNS = 2; + +@@ -315,103 +316,6 @@ const UserList = GObject.registerClass({ + } + }); + +-const SessionMenuButton = GObject.registerClass({ +- Signals: {'session-activated': {param_types: [GObject.TYPE_STRING]}}, +-}, class SessionMenuButton extends St.Bin { +- _init() { +- let button = new St.Button({ +- style_class: 'login-dialog-button login-dialog-session-list-button', +- icon_name: 'cog-wheel-symbolic', +- reactive: true, +- track_hover: true, +- can_focus: true, +- accessible_name: _('Choose Session'), +- accessible_role: Atk.Role.MENU, +- x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER, +- }); +- +- super._init({child: button}); +- this._button = button; +- +- this._menu = new PopupMenu.PopupMenu(this._button, 0, St.Side.BOTTOM); +- Main.uiGroup.add_child(this._menu.actor); +- this._menu.actor.hide(); +- +- this._menu.connect('open-state-changed', (menu, isOpen) => { +- if (isOpen) +- this._button.add_style_pseudo_class('active'); +- else +- this._button.remove_style_pseudo_class('active'); +- }); +- +- this._manager = new PopupMenu.PopupMenuManager(this._button, +- {actionMode: Shell.ActionMode.NONE}); +- this._manager.addMenu(this._menu); +- +- this._button.connect('clicked', () => this._menu.toggle()); +- +- this._items = new Map(); +- this._activeSessionId = null; +- this._populate(); +- } +- +- updateSensitivity(sensitive) { +- this._button.reactive = sensitive; +- this._button.can_focus = sensitive; +- this.opacity = sensitive ? 255 : 0; +- this._menu.close(BoxPointer.PopupAnimation.NONE); +- } +- +- _updateOrnament() { +- for (const itemId of this._items.keys()) { +- if (itemId === this._activeSessionId) +- this._items.get(itemId).setOrnament(PopupMenu.Ornament.DOT); +- else +- this._items.get(itemId).setOrnament(PopupMenu.Ornament.NO_DOT); +- } +- } +- +- setActiveSession(sessionId) { +- if (sessionId === this._activeSessionId) +- return; +- +- this._activeSessionId = sessionId; +- this._updateOrnament(); +- } +- +- close() { +- this._menu.close(); +- } +- +- _populate() { +- let ids = Gdm.get_session_ids(); +- +- if (ids.length <= 1) { +- this._button.hide(); +- return; +- } +- +- const sessions = ids.map(id => { +- const [sessionName] = Gdm.get_session_name_and_description(id); +- return {id, sessionName}; +- }); +- +- sessions.sort((a, b) => a.sessionName.localeCompare(b.sessionName)); +- +- for (const {id, sessionName} of sessions) { +- let item = new PopupMenu.PopupMenuItem(sessionName); +- this._menu.addMenuItem(item); +- this._items.set(id, item); +- +- item.connect('activate', () => { +- this.setActiveSession(id); +- this.emit('session-activated', this._activeSessionId); +- }); +- } +- } +-}); +- + const A11yMenuButton = GObject.registerClass( + class A11yMenuButton extends St.Button { + constructor() { +@@ -592,6 +496,7 @@ export const LoginDialog = GObject.registerClass({ + this._authPrompt.connect('prompted', this._onPrompted.bind(this)); + this._authPrompt.connect('reset', this._onReset.bind(this)); + this._authPrompt.connect('verification-complete', this._onVerificationComplete.bind(this)); ++ this._authPrompt.connect('loading', this._onLoading.bind(this)); + this._authPrompt.hide(); + this.add_child(this._authPrompt); + +@@ -645,14 +550,7 @@ export const LoginDialog = GObject.registerClass({ + }); + this.add_child(this._bottomButtonGroup); + +- this._sessionMenuButton = new SessionMenuButton(); +- this._sessionMenuButton.connect('session-activated', +- (list, sessionId) => { +- this._greeter.call_select_session_sync(sessionId, null); +- }); +- this._sessionMenuButton.opacity = 0; +- this._sessionMenuButton.show(); +- this._bottomButtonGroup.add_child(this._sessionMenuButton); ++ this._createAuthMenuButton(); + + this._a11yMenuButton = new A11yMenuButton(); + this._bottomButtonGroup.add_child(this._a11yMenuButton); +@@ -688,6 +586,45 @@ export const LoginDialog = GObject.registerClass({ + this._updateDisableUserList.bind(this), this); + } + ++ _createAuthMenuButton() { ++ this._authMenuButton = new AuthMenuButton.AuthMenuButton({ ++ title: _('Login Options'), ++ iconName: 'cog-wheel-symbolic', ++ sectionOrder: [_SESSION_TYPE_SECTION_NAME], ++ }); ++ this._authMenuButton.updateSensitivity(false); ++ ++ const ids = Gdm.get_session_ids(); ++ ids.sort(); ++ ++ if (ids.length <= 1) { ++ this._button.hide(); ++ return; ++ } ++ ++ for (const id of ids) { ++ const [sessionName, _] = Gdm.get_session_name_and_description(id); ++ ++ this._authMenuButton.addItem({ ++ sectionName: _SESSION_TYPE_SECTION_NAME, ++ name: sessionName, ++ id, ++ }); ++ } ++ ++ this._authMenuButton.connect('active-item-changed', (_button, sectionName) => { ++ const item = this._authMenuButton.getActiveItem({sectionName}); ++ if (!item) ++ return; ++ ++ if (sectionName === _SESSION_TYPE_SECTION_NAME) ++ this._greeter.call_select_session_sync(item.id, null); ++ ++ this._authMenuButton.close(); ++ }); ++ this._bottomButtonGroup.add_child(this._authMenuButton); ++ } ++ + _getBannerAllocation(dialogBox) { + let actorBox = new Clutter.ActorBox(); + +@@ -1061,10 +998,8 @@ export const LoginDialog = GObject.registerClass({ + } + + _onPrompted() { +- const showSessionMenu = this._shouldShowSessionMenuButton(); ++ this._authMenuButton.updateSensitivity(this._shouldShowAuthMenu()); + +- this._sessionMenuButton.updateSensitivity(showSessionMenu); +- this._sessionMenuButton.visible = showSessionMenu; + this._showPrompt(); + } + +@@ -1091,7 +1026,6 @@ export const LoginDialog = GObject.registerClass({ + + _onReset(authPrompt, resetType) { + this._ensureGreeterProxy(); +- this._sessionMenuButton.updateSensitivity(true); + + const previousUser = this._user; + this._user = null; +@@ -1125,11 +1059,18 @@ export const LoginDialog = GObject.registerClass({ + }); + } + ++ _onLoading(_authPrompt, isLoading) { ++ this._authMenuButton.updateReactive(!isLoading); ++ } ++ + _onDefaultSessionChanged(client, sessionId) { +- this._sessionMenuButton.setActiveSession(sessionId); ++ this._authMenuButton.setActiveItem({ ++ sectionName: _SESSION_TYPE_SECTION_NAME, ++ id: sessionId, ++ }); + } + +- _shouldShowSessionMenuButton() { ++ _shouldShowAuthMenu() { + const visibleStatuses = [ + AuthPrompt.AuthPromptStatus.VERIFYING, + AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED, +@@ -1191,7 +1132,7 @@ export const LoginDialog = GObject.registerClass({ + }); + this._updateCancelButton(); + +- this._sessionMenuButton.updateSensitivity(false); ++ this._authMenuButton.updateSensitivity(false); + this._authPrompt.updateSensitivity({sensitive: true}); + this._showPrompt(); + } +@@ -1505,8 +1446,7 @@ export const LoginDialog = GObject.registerClass({ + this._ensureUserListLoaded(); + this._authPrompt.hide(); + this._hideBannerView(); +- this._sessionMenuButton.close(); +- this._sessionMenuButton.hide(); ++ this._authMenuButton.updateSensitivity(false); + this._setUserListExpanded(true); + this._notListedButton.show(); + this._userList.grab_key_focus(); +-- +2.53.0 + + +From c4d9263b2270b43fb6f538243277f90559fb8f57 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 13:40:26 -0500 +Subject: [PATCH 27/42] loginDialog: Add login options menu to AuthMenuButton +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Now the authMenuButton allows selecting a session type and a login +mechanism. + +authPrompt is in charge of: + * Informing of the available login mechanisms via the "mechanisms-changed" + signal, it also provides one between them that is selected by default. + * Getting the selected login mechanism with "selectMechanism" method. + +In future commits it will be implemented how UserVerifier emits +"mechanisms-changed". + +There are some mechanisms that are not selectable but run in the +background, e.g. fingerprint. These ones won't be at _authMenuButton but +at the new _authIndicatorButton to inform when they are enabled. + +Co-authored-by: Marco Trevisan (Treviño) +--- + js/gdm/authPrompt.js | 13 ++++++++++++ + js/gdm/loginDialog.js | 48 ++++++++++++++++++++++++++++++++++++------- + 2 files changed, 54 insertions(+), 7 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 7b3aa8558f..11d90a4de3 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -51,6 +51,7 @@ export const AuthPrompt = GObject.registerClass({ + 'failed': {}, + 'next': {}, + 'prompted': {}, ++ 'mechanisms-changed': {param_types: [GObject.TYPE_JSOBJECT, GObject.TYPE_JSOBJECT]}, + 'reset': {param_types: [GObject.TYPE_UINT]}, + 'verification-complete': {}, + 'loading': {param_types: [GObject.TYPE_BOOLEAN]}, +@@ -87,6 +88,7 @@ export const AuthPrompt = GObject.registerClass({ + 'ask-question', this._onAskQuestion.bind(this), + 'show-message', this._onShowMessage.bind(this), + 'show-choice-list', this._onShowChoiceList.bind(this), ++ 'mechanisms-changed', (_, ...args) => this.emit('mechanisms-changed', ...args), + 'verification-failed', this._onVerificationFailed.bind(this), + 'verification-complete', this._onVerificationComplete.bind(this), + 'reset', this._onReset.bind(this), +@@ -772,6 +774,17 @@ export const AuthPrompt = GObject.registerClass({ + this.updateSensitivity(false); + } + ++ selectMechanism(mechanism) { ++ const invalidStatus = [ ++ AuthPromptStatus.VERIFICATION_SUCCEEDED, ++ AuthPromptStatus.VERIFICATION_IN_PROGRESS, ++ ]; ++ if (invalidStatus.includes(this.verificationStatus)) ++ return false; ++ ++ return true; ++ } ++ + reset(params) { + let {reuseEntryText, softReset} = Params.parse(params, { + reuseEntryText: false, +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 46e2fbc126..ddb7e603b2 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -48,6 +48,7 @@ const _SCROLL_ANIMATION_TIME = 500; + const _FIXED_TOP_ACTOR_HEIGHT = 400; + const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; + const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60; ++const _PRIMARY_LOGIN_METHOD_SECTION_NAME = _('Login Options'); + const _SESSION_TYPE_SECTION_NAME = _('Session Type'); + + const N_A11Y_MENU_COLUMNS = 2; +@@ -497,6 +498,7 @@ export const LoginDialog = GObject.registerClass({ + this._authPrompt.connect('reset', this._onReset.bind(this)); + this._authPrompt.connect('verification-complete', this._onVerificationComplete.bind(this)); + this._authPrompt.connect('loading', this._onLoading.bind(this)); ++ this._authPrompt.connect('mechanisms-changed', this._onMechanismsChanged.bind(this)); + this._authPrompt.hide(); + this.add_child(this._authPrompt); + +@@ -590,18 +592,13 @@ export const LoginDialog = GObject.registerClass({ + this._authMenuButton = new AuthMenuButton.AuthMenuButton({ + title: _('Login Options'), + iconName: 'cog-wheel-symbolic', +- sectionOrder: [_SESSION_TYPE_SECTION_NAME], ++ sectionOrder: [_PRIMARY_LOGIN_METHOD_SECTION_NAME, _SESSION_TYPE_SECTION_NAME], + }); + this._authMenuButton.updateSensitivity(false); + + const ids = Gdm.get_session_ids(); + ids.sort(); + +- if (ids.length <= 1) { +- this._button.hide(); +- return; +- } +- + for (const id of ids) { + const [sessionName, _] = Gdm.get_session_name_and_description(id); + +@@ -617,7 +614,9 @@ export const LoginDialog = GObject.registerClass({ + if (!item) + return; + +- if (sectionName === _SESSION_TYPE_SECTION_NAME) ++ if (sectionName === _PRIMARY_LOGIN_METHOD_SECTION_NAME) ++ this._selectAuthMechanism(item); ++ else if (sectionName === _SESSION_TYPE_SECTION_NAME) + this._greeter.call_select_session_sync(item.id, null); + + this._authMenuButton.close(); +@@ -625,6 +624,20 @@ export const LoginDialog = GObject.registerClass({ + this._bottomButtonGroup.add_child(this._authMenuButton); + } + ++ _selectAuthMechanism(authMechanism) { ++ const oldMechanism = this._selectedAuthMechanism; ++ ++ if (authMechanism === oldMechanism) ++ return; ++ ++ if (!this._authPrompt.selectMechanism(authMechanism)) { ++ this._authMenuButton.setActiveItem(oldMechanism); ++ return; ++ } ++ ++ this._selectedAuthMechanism = authMechanism; ++ } ++ + _getBannerAllocation(dialogBox) { + let actorBox = new Clutter.ActorBox(); + +@@ -1063,6 +1076,27 @@ export const LoginDialog = GObject.registerClass({ + this._authMenuButton.updateReactive(!isLoading); + } + ++ _onMechanismsChanged(_authPrompt, mechanisms, selectedMechanism) { ++ this._authMenuButton.clearItems({ ++ sectionName: _PRIMARY_LOGIN_METHOD_SECTION_NAME, ++ }); ++ ++ if (mechanisms.length === 0) ++ return; ++ ++ for (const m of mechanisms) { ++ if (GdmUtil.isSelectable(m)) { ++ this._authMenuButton.addItem({ ++ sectionName: _PRIMARY_LOGIN_METHOD_SECTION_NAME, ++ ...m, ++ }); ++ } ++ } ++ ++ if (Object.keys(selectedMechanism).length > 0) ++ this._authMenuButton.setActiveItem(selectedMechanism); ++ } ++ + _onDefaultSessionChanged(client, sessionId) { + this._authMenuButton.setActiveItem({ + sectionName: _SESSION_TYPE_SECTION_NAME, +-- +2.53.0 + + +From 25db638e4f11984258ed9efebb5ead49f01045cb Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 13:41:39 -0500 +Subject: [PATCH 28/42] unlockDialog: Add _authMenuButton and + _authIndicatorButton + +_authMenuButton is used to select an available auth mechanism from the +unlock screen. It's in the bottom right corner of the screen. + +_authIndicatorButton is used to show indicators for background (non +selectable) auth mechanisms. It's in the bottom left corner of the screen. + +Make _otherUserButton written out, instead of using an icon. And move it to +the bottom right corner of the screen, with _authMenuButton. +Only show _otherUserButton when clock is dismissed and authentication +prompt visible. +--- + js/ui/unlockDialog.js | 158 +++++++++++++++++++++++++++++++++++++----- + 1 file changed, 141 insertions(+), 17 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index fffc58e07c..af5a238d87 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -15,11 +15,16 @@ import * as Main from './main.js'; + import * as MessageTray from './messageTray.js'; + import * as SwipeTracker from './swipeTracker.js'; + import {formatDateWithCFormatString} from '../misc/dateUtils.js'; ++import * as AuthMenuButton from '../gdm/authMenuButton.js'; + import * as AuthPrompt from '../gdm/authPrompt.js'; ++import * as GdmConst from '../gdm/const.js'; ++import * as GdmUtil from '../gdm/util.js'; + import {AuthPromptStatus} from '../gdm/authPrompt.js'; + import {MprisSource} from './mpris.js'; + import {MediaMessage} from './messageList.js'; + ++const PRIMARY_UNLOCK_METHOD_SECTION_NAME = _('Unlock Options'); ++ + // The timeout before going back automatically to the lock screen (in seconds) + const IDLE_TIMEOUT = 2 * 60; + +@@ -432,12 +437,13 @@ class UnlockDialogClock extends St.BoxLayout { + + const UnlockDialogLayout = GObject.registerClass( + class UnlockDialogLayout extends Clutter.LayoutManager { +- _init(stack, notifications, switchUserButton) { ++ _init(stack, notifications, authIndicatorButton, bottomButtonGroup) { + super._init(); + + this._stack = stack; + this._notifications = notifications; +- this._switchUserButton = switchUserButton; ++ this._authIndicatorButton = authIndicatorButton; ++ this._bottomButtonGroup = bottomButtonGroup; + } + + vfunc_get_preferred_width(container, forHeight) { +@@ -497,22 +503,40 @@ class UnlockDialogLayout extends Clutter.LayoutManager { + + this._stack.allocate(actorBox); + +- // Switch User button +- if (this._switchUserButton.visible) { +- let [, , natWidth, natHeight] = +- this._switchUserButton.get_preferred_size(); ++ // Auth Indicator button (left bottom) ++ if (this._authIndicatorButton.visible) { ++ const [, , natWidth, natHeight] = ++ this._authIndicatorButton.get_preferred_size(); + +- const textDirection = this._switchUserButton.get_text_direction(); ++ const textDirection = this._authIndicatorButton.get_text_direction(); + if (textDirection === Clutter.TextDirection.RTL) +- actorBox.x1 = box.x1 + natWidth; ++ actorBox.x1 = box.x2 - natWidth; + else +- actorBox.x1 = box.x2 - (natWidth * 2); ++ actorBox.x1 = box.x1; + +- actorBox.y1 = box.y2 - (natHeight * 2); ++ actorBox.y1 = box.y2 - natHeight; + actorBox.x2 = actorBox.x1 + natWidth; + actorBox.y2 = actorBox.y1 + natHeight; + +- this._switchUserButton.allocate(actorBox); ++ this._authIndicatorButton.allocate(actorBox); ++ } ++ ++ // bottom button group, (has login options and switch user buttons) (right bottom) ++ if (this._bottomButtonGroup.visible) { ++ const [, , natWidth, natHeight] = ++ this._bottomButtonGroup.get_preferred_size(); ++ ++ const textDirection = this._bottomButtonGroup.get_text_direction(); ++ if (textDirection === Clutter.TextDirection.RTL) ++ actorBox.x1 = box.x1; ++ else ++ actorBox.x1 = box.x2 - natWidth; ++ ++ actorBox.y1 = box.y2 - natHeight; ++ actorBox.x2 = actorBox.x1 + natWidth; ++ actorBox.y2 = actorBox.y1 + natHeight; ++ ++ this._bottomButtonGroup.allocate(actorBox); + } + } + }); +@@ -622,19 +646,48 @@ export const UnlockDialog = GObject.registerClass({ + this._notificationsBox = new NotificationsBox(); + this._notificationsBox.connect('wake-up-screen', () => this.emit('wake-up-screen')); + ++ this._bottomButtonGroup = new St.BoxLayout({ ++ style_class: 'login-dialog-bottom-button-group', ++ }); ++ + // Switch User button + this._otherUserButton = new St.Button({ + style_class: 'login-dialog-button switch-user-button', + accessible_name: _('Log in as another user'), + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + reactive: false, +- opacity: 0, + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.END, +- icon_name: 'system-users-symbolic', ++ label: _('Switch User…'), + }); + this._otherUserButton.set_pivot_point(0.5, 0.5); + this._otherUserButton.connect('clicked', this._otherUserClicked.bind(this)); ++ this._bottomButtonGroup.add_child(this._otherUserButton); ++ ++ // Login Options button ++ this._authMenuButton = new AuthMenuButton.AuthMenuButton({ ++ title: _('Login Options'), ++ iconName: 'cog-wheel-symbolic', ++ }); ++ this._authMenuButton.connect('active-item-changed', () => { ++ const authMechanism = this._authMenuButton.getActiveItem(); ++ if (!authMechanism) ++ return; ++ ++ this._selectAuthMechanism(authMechanism); ++ this._authMenuButton.close(); ++ }); ++ this._authMenuButton.updateSensitivity(true); ++ this._bottomButtonGroup.add_child(this._authMenuButton); ++ ++ // Auth Indicators ++ this._authIndicatorButton = new AuthMenuButton.AuthMenuButtonIndicator({ ++ title: _('Background Authentication Methods'), ++ animateVisibility: true, ++ }); ++ this._authIndicatorButton.add_style_class_name('login-dialog-bottom-button-group'); ++ this._authIndicatorButton.set_pivot_point(0.5, 0.5); ++ this._authIndicatorButton.updateSensitivity(true); + + this._screenSaverSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.screensaver'}); + +@@ -657,11 +710,13 @@ export const UnlockDialog = GObject.registerClass({ + mainBox.add_constraint(new Layout.MonitorConstraint({primary: true})); + mainBox.add_child(this._stack); + mainBox.add_child(this._notificationsBox); +- mainBox.add_child(this._otherUserButton); ++ mainBox.add_child(this._authIndicatorButton); ++ mainBox.add_child(this._bottomButtonGroup); + mainBox.layout_manager = new UnlockDialogLayout( + this._stack, + this._notificationsBox, +- this._otherUserButton); ++ this._authIndicatorButton, ++ this._bottomButtonGroup); + this.add_child(mainBox); + + this._idleMonitor = global.backend.get_core_idle_monitor(); +@@ -699,6 +754,20 @@ export const UnlockDialog = GObject.registerClass({ + return Clutter.EVENT_PROPAGATE; + } + ++ _selectAuthMechanism(authMechanism) { ++ const oldMechanism = this._selectedAuthMechanism; ++ ++ if (authMechanism === oldMechanism) ++ return; ++ ++ if (!this._authPrompt.selectMechanism(authMechanism)) { ++ this._authMenuButton.setActiveItem(oldMechanism); ++ return; ++ } ++ ++ this._selectedAuthMechanism = authMechanism; ++ } ++ + _createBackground(monitorIndex) { + let monitor = Main.layoutManager.monitors[monitorIndex]; + let widget = new St.Widget({ +@@ -755,6 +824,8 @@ export const UnlockDialog = GObject.registerClass({ + this._authPrompt.connect('failed', this._fail.bind(this)); + this._authPrompt.connect('cancelled', this._fail.bind(this)); + this._authPrompt.connect('reset', this._onReset.bind(this)); ++ this._authPrompt.connect('loading', this._onLoading.bind(this)); ++ this._authPrompt.connect('mechanisms-changed', this._onMechanismsChanged.bind(this)); + this._promptBox.add_child(this._authPrompt); + } + +@@ -817,6 +888,12 @@ export const UnlockDialog = GObject.registerClass({ + reactive: progress > 0, + can_focus: progress > 0, + }); ++ this._authIndicatorButton.set({ ++ opacity: 255 * progress, ++ scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress, ++ scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress, ++ }); ++ this._updateUserSwitchVisibility(); + + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + +@@ -834,7 +911,7 @@ export const UnlockDialog = GObject.registerClass({ + translation_y: -FADE_OUT_TRANSLATION * progress * scaleFactor, + }); + +- this._otherUserButton.set({ ++ this._bottomButtonGroup.set({ + opacity: 255 * progress, + scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress, + scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress, +@@ -858,6 +935,52 @@ export const UnlockDialog = GObject.registerClass({ + this._authPrompt.begin({userName}); + } + ++ _onLoading(_authPrompt, isLoading) { ++ this._authMenuButton.updateReactive(!isLoading); ++ } ++ ++ _onMechanismsChanged(_authPrompt, mechanisms, selectedMechanism) { ++ this._authMenuButton.clearItems({ ++ sectionName: PRIMARY_UNLOCK_METHOD_SECTION_NAME, ++ }); ++ ++ this._authIndicatorButton.clearItems(); ++ ++ if (mechanisms.length === 0) ++ return; ++ ++ for (const m of mechanisms) { ++ if (GdmUtil.isSelectable(m)) { ++ this._authMenuButton.addItem({ ++ sectionName: PRIMARY_UNLOCK_METHOD_SECTION_NAME, ++ ...m, ++ }); ++ } else { ++ this._authIndicatorButton.addItem({ ++ iconName: GdmUtil.getIconName(m), ++ description: this._getUnlockDescription(m), ++ ...m, ++ }); ++ } ++ } ++ ++ if (Object.keys(selectedMechanism).length > 0) ++ this._authMenuButton.setActiveItem(selectedMechanism); ++ ++ this._authIndicatorButton.updateDescriptionLabel(); ++ } ++ ++ _getUnlockDescription(mechanism) { ++ // This is only used for non selectable mechanisms. ++ // Currently only fingerprint is non selectable ++ switch (mechanism.role) { ++ case GdmConst.FINGERPRINT_ROLE_NAME: ++ return _('Unlock with fingerprint'); ++ default: ++ throw new Error(`Failed getting unlock description: ${mechanism.role}`); ++ } ++ } ++ + _escape() { + if (this._authPrompt && this.allowCancel) + this._authPrompt.cancel(); +@@ -921,7 +1044,8 @@ export const UnlockDialog = GObject.registerClass({ + this._otherUserButton.visible = this._userManager.can_switch() && + this._userManager.has_multiple_users && + this._screenSaverSettings.get_boolean('user-switch-enabled') && +- !this._lockdownSettings.get_boolean('disable-user-switching'); ++ !this._lockdownSettings.get_boolean('disable-user-switching') && ++ this._promptBox.visible; + } + + cancel() { +-- +2.53.0 + + +From 8256dce648e05285a4bf40b98d547e585f6e3d06 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 3 Dec 2024 07:39:32 -0500 +Subject: [PATCH 29/42] unlockDialog: Update hint text based on mockup + +This considers future mechanisms which might be the default ones, i.e. +smartcard and passkey. And have special hint texts. +--- + js/ui/unlockDialog.js | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index af5a238d87..80c3dd813a 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -423,9 +423,17 @@ class UnlockDialogClock extends St.BoxLayout { + } + + _updateHint() { +- this._hint.text = this._seat.touch_mode +- ? _('Swipe up to unlock') +- : _('Click or press a key to unlock'); ++ const authMechanism = this._selectedAuthMechanism; ++ let text; ++ ++ if (authMechanism?.role === GdmConst.SMARTCARD_ROLE_NAME) ++ text = _('Insert smartcard'); ++ else if (this._seat.touch_mode) ++ text = _('Swipe up'); ++ else ++ text = _('Click or press a key'); ++ ++ this._hint.text = text; + } + + _onDestroy() { +-- +2.53.0 + + +From 52f38a7b2d63a96f3af2e1ec87f3832821094fdd Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 14 Oct 2025 19:19:15 +0200 +Subject: [PATCH 30/42] gdm/util: Increase time of messages based on new + environment variable + +called 'GDM_MESSAGE_TIME_MULTIPLIER'. This is can used for testing +purposes. When no set, the multiplier is 1 which does nothing. +--- + js/gdm/util.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 660c6e4ddc..12dec5dd46 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -43,6 +43,7 @@ export const DISABLE_USER_LIST_KEY = 'disable-user-list'; + // or 2 seconds, whichever is longer + const USER_READ_TIME = 48; + const USER_READ_TIME_MIN = 2000; ++const MESSAGE_TIME_MULTIPLIER = GLib.getenv('GDM_MESSAGE_TIME_MULTIPLIER') ?? 1; + + const FINGERPRINT_SERVICE_PROXY_TIMEOUT = 5000; + const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15; +@@ -278,7 +279,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + return 0; + + // We probably could be smarter here +- return Math.max(message.length * USER_READ_TIME, USER_READ_TIME_MIN); ++ return Math.max(message.length * USER_READ_TIME * MESSAGE_TIME_MULTIPLIER, ++ USER_READ_TIME_MIN); + } + + finishMessageQueue() { +-- +2.53.0 + + +From b49076f274bc33e32e52747e6858d28fb029a1fc Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 15 Sep 2025 17:13:17 +0200 +Subject: [PATCH 31/42] gdm/util: Allow null _hold and don't recreate dummy + holds + +_hold property is used to inform the caller of begin method (authPrompt) +when it's started the verification (calling acquire) and when it +finished starting it, either successfully or with an error (calling +release). + +There's no need to create dummy holds that won't be used anywhere +externally. + +In the next commits the use of _hold will be simplified considerably, +but for now, just accept getting null and dont create dummy ones. +--- + js/gdm/authPrompt.js | 6 +----- + js/gdm/util.js | 15 ++++++--------- + 2 files changed, 7 insertions(+), 14 deletions(-) + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 11d90a4de3..9a9c0c3802 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -858,11 +858,7 @@ export const AuthPrompt = GObject.registerClass({ + + this.updateSensitivity({sensitive: false}); + +- let hold = params.hold; +- if (!hold) +- hold = new Batch.Hold(); +- +- this._userVerifier.begin(params.userName, hold); ++ this._userVerifier.begin(params.userName, params.hold); + this.verificationStatus = AuthPromptStatus.VERIFYING; + } + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 12dec5dd46..247048ddab 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -462,8 +462,6 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + if (this._userVerifier && + !this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) { +- if (!this._hold?.isAcquired()) +- this._hold = new Batch.Hold(); + await this._maybeStartFingerprintVerification(); + } + } +@@ -527,7 +525,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + _reportInitError(where, error, serviceName) { + logError(error, where); +- this._hold.release(); ++ this._hold?.release(); + + this._queueMessage(serviceName, _('Authentication error'), MessageType.ERROR); + this._failCounter++; +@@ -565,7 +563,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this.reauthenticating = true; + this._connectSignals(); + this._beginVerification(); +- this._hold.release(); ++ this._hold?.release(); + } + + async _getUserVerifier() { +@@ -589,7 +587,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + this._connectSignals(); + this._beginVerification(); +- this._hold.release(); ++ this._hold?.release(); + } + + _connectSignals() { +@@ -707,7 +705,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + async _startService(serviceName) { +- this._hold.acquire(); ++ this._hold?.acquire(); + try { + this._activeServices.add(serviceName); + if (this._userName) { +@@ -724,7 +722,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + if (!this.serviceIsForeground(serviceName)) { + logError(e, + `Failed to start ${serviceName} for ${this._userName}`); +- this._hold.release(); ++ this._hold?.release(); + return; + } + this._reportInitError( +@@ -734,7 +732,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + e, serviceName); + return; + } +- this._hold.release(); ++ this._hold?.release(); + } + + _beginVerification() { +@@ -867,7 +865,6 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + _retry(serviceName) { +- this._hold = new Batch.Hold(); + this._connectSignals(); + this._startService(serviceName); + } +-- +2.53.0 + + +From 50aef73235bd66243f1472a24711840beea5eb27 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 20 Aug 2025 10:54:33 +0200 +Subject: [PATCH 32/42] gdm/util: Add fingerprintManager + +Move fingerprint bits to new fingerprintManager class. + +This helps keeping util simpler and tries to encapsulate fingerprint +functionality inside fingerprintManager. + +This will be improved in the next commits when moving fingerprint +authentication to a specific class instead of util. +--- + js/gdm/util.js | 154 +++++++++------------------------- + js/js-resources.gresource.xml | 1 + + js/misc/fingerprintManager.js | 140 +++++++++++++++++++++++++++++++ + 3 files changed, 179 insertions(+), 116 deletions(-) + create mode 100644 js/misc/fingerprintManager.js + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 247048ddab..ae464665e2 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -5,6 +5,8 @@ import GLib from 'gi://GLib'; + import * as Signals from '../misc/signals.js'; + + import * as Batch from './batch.js'; ++import * as Const from './const.js'; ++import * as FingerprintManager from '../misc/fingerprintManager.js'; + import * as OVirt from './oVirt.js'; + import * as Vmware from './vmware.js'; + import * as Main from '../ui/main.js'; +@@ -13,11 +15,6 @@ import {loadInterfaceXML} from '../misc/fileUtils.js'; + import * as Params from '../misc/params.js'; + import * as SmartcardManager from '../misc/smartcardManager.js'; + +-const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml( +- loadInterfaceXML('net.reactivated.Fprint.Manager')); +-const FprintDeviceInfo = Gio.DBusInterfaceInfo.new_for_xml( +- loadInterfaceXML('net.reactivated.Fprint.Device')); +- + Gio._promisify(Gdm.Client.prototype, 'open_reauthentication_channel'); + Gio._promisify(Gdm.Client.prototype, 'get_user_verifier'); + Gio._promisify(Gdm.UserVerifierProxy.prototype, +@@ -60,12 +57,6 @@ export const MessageType = { + ERROR: 3, + }; + +-const FingerprintReaderType = { +- NONE: 0, +- PRESS: 1, +- SWIPE: 2, +-}; +- + /** + * @param {Clutter.Actor} actor + */ +@@ -141,7 +132,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + this._defaultService = null; + this._preemptingService = null; +- this._fingerprintReaderType = FingerprintReaderType.NONE; ++ this._fingerprintReaderType = FingerprintManager.FingerprintReaderType.NONE; ++ this._fingerprintReaderFound = false; + + this._messageQueue = []; + this._messageQueueTimeoutId = 0; +@@ -205,8 +197,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._userName = userName; + this.reauthenticating = false; + +- this._checkForFingerprintReader().catch(e => +- this._handleFingerprintError(e)); ++ this._fingerprintManager?.checkReaderType(this._cancellable); + + // If possible, reauthenticate an already running session, + // so any session specific credentials get updated appropriately +@@ -255,6 +246,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._smartcardManager?.disconnectObject(this); + this._smartcardManager = null; + ++ this._fingerprintManager?.disconnectObject(this); + this._fingerprintManager = null; + + for (let service in this._credentialManagers) +@@ -366,112 +358,40 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + async _initFingerprintManager() { +- if (this._fprintManager) ++ if (this._fingerprintManager) + return; + +- const fprintManager = new Gio.DBusProxy({ +- g_connection: Gio.DBus.system, +- g_name: 'net.reactivated.Fprint', +- g_object_path: '/net/reactivated/Fprint/Manager', +- g_interface_name: FprintManagerInfo.name, +- g_interface_info: FprintManagerInfo, +- g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | +- Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION | +- Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, +- }); +- +- try { +- if (!this._getDetectedDefaultService()) { +- // Other authentication methods would have already been detected by +- // now as possibilities if they were available. +- // If we're here it means that FINGERPRINT_AUTHENTICATION_KEY is +- // true and so fingerprint authentication is our last potential +- // option, so go ahead a synchronously look for a fingerprint device +- // during startup or default service update. +- fprintManager.init(null); +- // Do not wait too much for fprintd to reply, as in case it hangs +- // we should fail early without having the shell to misbehave +- fprintManager.set_default_timeout(FINGERPRINT_SERVICE_PROXY_TIMEOUT); +- +- const [devicePath] = fprintManager.GetDefaultDeviceSync(); +- this._fprintManager = fprintManager; +- +- const fprintDeviceProxy = this._getFingerprintDeviceProxy(devicePath); +- fprintDeviceProxy.init(null); +- this._setFingerprintReaderType(fprintDeviceProxy['scan-type']); +- } else { +- // Ensure fingerprint service starts, but do not wait for it +- const cancellable = this._cancellable; +- await fprintManager.init_async(GLib.PRIORITY_DEFAULT, cancellable); +- await this._updateFingerprintReaderType(fprintManager, cancellable); +- this._fprintManager = fprintManager; +- } +- } catch (e) { +- this._handleFingerprintError(e); +- } +- } +- +- _getFingerprintDeviceProxy(devicePath) { +- return new Gio.DBusProxy({ +- g_connection: Gio.DBus.system, +- g_name: 'net.reactivated.Fprint', +- g_object_path: devicePath, +- g_interface_name: FprintDeviceInfo.name, +- g_interface_info: FprintDeviceInfo, +- g_flags: Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, +- }); +- } +- +- _handleFingerprintError(e) { +- this._fingerprintReaderType = FingerprintReaderType.NONE; +- +- if (e instanceof GLib.Error) { +- if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) +- return; +- if (e.matches(Gio.DBusError, Gio.DBusError.SERVICE_UNKNOWN)) +- return; +- if (Gio.DBusError.is_remote_error(e) && +- Gio.DBusError.get_remote_error(e) === +- 'net.reactivated.Fprint.Error.NoSuchDevice') +- return; +- } +- +- logError(e, 'Failed to interact with fprintd service'); +- } ++ this._fingerprintManager = ++ new FingerprintManager.FingerprintManager(this._cancellable); ++ this._fingerprintManager.connectObject( ++ 'reader-type-changed', () => this._onFingerprintReaderTypeChanged(), ++ this); + +- async _checkForFingerprintReader() { +- if (!this._fprintManager) { +- this._updateDefaultService(); +- return; ++ if (!this._getDetectedDefaultService()) { ++ // Other authentication methods would have already been detected by ++ // now as possibilities if they were available. ++ // If we're here it means that FINGERPRINT_AUTHENTICATION_KEY is ++ // true and so fingerprint authentication is our last potential ++ // option, so go ahead a synchronously look for a fingerprint device ++ // during startup or default service update. ++ // Do not wait too much for fprintd to reply, as in case it hangs ++ // we should fail early without having the shell to misbehave ++ this._fingerprintManager.setDefaultTimeout(FINGERPRINT_SERVICE_PROXY_TIMEOUT); ++ await this._fingerprintManager.checkReaderType(this._cancellable); ++ } else { ++ // Ensure fingerprint service starts, but do not wait for it ++ this._fingerprintManager.checkReaderType(this._cancellable); + } +- +- if (this._fingerprintReaderType !== FingerprintReaderType.NONE) +- return; +- +- await this._updateFingerprintReaderType(this._fprintManager, this._cancellable); + } + +- async _updateFingerprintReaderType(fprintManager, cancellable) { +- // Wrappers don't support null cancellable, so let's ignore it in case +- const args = cancellable ? [cancellable] : []; +- const [devicePath] = await fprintManager.GetDefaultDeviceAsync(...args); +- const fprintDeviceProxy = this._getFingerprintDeviceProxy(devicePath); +- await fprintDeviceProxy.init_async(GLib.PRIORITY_DEFAULT, cancellable); +- this._setFingerprintReaderType(fprintDeviceProxy['scan-type']); ++ _onFingerprintReaderTypeChanged() { ++ this._fingerprintReaderType = this._fingerprintManager.readerType; ++ this._fingerprintReaderFound = this._fingerprintManager.readerFound; + this._updateDefaultService(); + + if (this._userVerifier && +- !this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) { +- await this._maybeStartFingerprintVerification(); +- } +- } +- +- _setFingerprintReaderType(fprintDeviceType) { +- this._fingerprintReaderType = +- FingerprintReaderType[fprintDeviceType.toUpperCase()]; +- +- if (this._fingerprintReaderType === undefined) +- throw new Error(`Unexpected fingerprint device type '${fprintDeviceType}'`); ++ !this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) ++ this._maybeStartFingerprintVerification(); + } + + _onCredentialManagerAuthenticated(credentialManager, _token) { +@@ -641,7 +561,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + serviceIsFingerprint(serviceName) { +- return this._fingerprintReaderType !== FingerprintReaderType.NONE && ++ return this._fingerprintReaderFound && + serviceName === Const.FINGERPRINT_SERVICE_NAME; + } + +@@ -654,9 +574,11 @@ export class ShellUserVerifier extends Signals.EventEmitter { + let needsReset = false; + + if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { +- this._initFingerprintManager().catch(logError); ++ this._initFingerprintManager(); + } else if (this._fingerprintManager) { ++ this._fingerprintManager.disconnectObject(this); + this._fingerprintManager = null; ++ this._fingerprintReaderFound = false; + this._fingerprintReaderType = FingerprintReaderType.NONE; + + if (this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) +@@ -684,7 +606,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + return Const.PASSWORD_SERVICE_NAME; + else if (this._smartcardManager) + return Const.SMARTCARD_SERVICE_NAME; +- else if (this._fingerprintReaderType !== FingerprintReaderType.NONE) ++ else if (this._fingerprintReaderFound) + return Const.FINGERPRINT_SERVICE_NAME; + return null; + } +@@ -742,7 +664,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + async _maybeStartFingerprintVerification() { + if (this._userName && +- this._fingerprintReaderType !== FingerprintReaderType.NONE && ++ this._fingerprintReaderFound && + !this.serviceIsForeground(Const.FINGERPRINT_SERVICE_NAME)) + await this._startService(Const.FINGERPRINT_SERVICE_NAME); + } +@@ -765,7 +687,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + // We don't show fingerprint messages directly since it's + // not the main auth service. Instead we use the messages + // as a cue to display our own message. +- if (this._fingerprintReaderType === FingerprintReaderType.SWIPE) { ++ if (this._fingerprintReaderType === FingerprintManager.FingerprintReaderType.SWIPE) { + // Translators: this message is shown below the password entry field + // to indicate the user can swipe their finger on the fingerprint reader + this._queueMessage(serviceName, _('(or swipe finger across reader)'), +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index 254d2af958..2712bfcc84 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -26,6 +26,7 @@ + misc/dependencies.js + misc/errorUtils.js + misc/extensionUtils.js ++ misc/fingerprintManager.js + misc/fileUtils.js + misc/gnomeSession.js + misc/history.js +diff --git a/js/misc/fingerprintManager.js b/js/misc/fingerprintManager.js +new file mode 100644 +index 0000000000..5bcb7bf6f0 +--- /dev/null ++++ b/js/misc/fingerprintManager.js +@@ -0,0 +1,140 @@ ++import Gio from 'gi://Gio'; ++import GLib from 'gi://GLib'; ++import GObject from 'gi://GObject'; ++ ++import {loadInterfaceXML} from './fileUtils.js'; ++ ++const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml( ++ loadInterfaceXML('net.reactivated.Fprint.Manager')); ++const FprintDeviceInfo = Gio.DBusInterfaceInfo.new_for_xml( ++ loadInterfaceXML('net.reactivated.Fprint.Device')); ++ ++export const FingerprintReaderType = { ++ NONE: 0, ++ PRESS: 1, ++ SWIPE: 2, ++}; ++ ++let _fingerprintManager = null; ++ ++/** ++ * @returns {FingerprintManager} ++ */ ++export function getFingerprintManager() { ++ if (_fingerprintManager == null) ++ _fingerprintManager = new FingerprintManager(); ++ ++ return _fingerprintManager; ++} ++ ++class FingerprintManager extends GObject.Object { ++ static [GObject.GTypeName] = 'FingerprintManager'; ++ ++ static [GObject.properties] = { ++ 'reader-type': GObject.ParamSpec.uint( ++ 'reader-type', null, null, ++ GObject.ParamFlags.READWRITE, ++ FingerprintReaderType.NONE, FingerprintReaderType.SWIPE, ++ FingerprintReaderType.NONE), ++ }; ++ ++ static [GObject.signals] = { ++ 'reader-type-changed': {}, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor() { ++ super(); ++ ++ this._fingerprintManagerProxy = new Gio.DBusProxy({ ++ g_connection: Gio.DBus.system, ++ g_name: 'net.reactivated.Fprint', ++ g_object_path: '/net/reactivated/Fprint/Manager', ++ g_interface_name: FprintManagerInfo.name, ++ g_interface_info: FprintManagerInfo, ++ g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | ++ Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION | ++ Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, ++ }); ++ ++ this._initFingerprintManagerProxy(); ++ } ++ ++ get readerFound() { ++ return this.readerType !== FingerprintReaderType.NONE; ++ } ++ ++ setDefaultTimeout(timeout) { ++ this._fingerprintManagerProxy.set_default_timeout(timeout); ++ } ++ ++ async checkReaderType(cancellable) { ++ try { ++ // Wrappers don't support null cancellable, so let's ignore it in case ++ const args = cancellable ? [cancellable] : []; ++ const [devicePath] = ++ await this._fingerprintManagerProxy.GetDefaultDeviceAsync(...args); ++ ++ const fprintDeviceProxy = this._getFprintDeviceProxy(devicePath); ++ await fprintDeviceProxy.init_async(GLib.PRIORITY_DEFAULT, cancellable ?? null); ++ ++ const fingerprintReaderKey = fprintDeviceProxy['scan-type'].toUpperCase(); ++ const fingerprintReaderType = FingerprintReaderType[fingerprintReaderKey]; ++ this._setFingerprintReaderType(fingerprintReaderType); ++ } catch (e) { ++ if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ return; ++ this._handleFingerprintError(e); ++ } ++ } ++ ++ async _initFingerprintManagerProxy() { ++ try { ++ await this._fingerprintManagerProxy.init_async( ++ GLib.PRIORITY_DEFAULT, null); ++ await this.checkReaderType(); ++ } catch (e) { ++ this._handleFingerprintError(e); ++ } ++ } ++ ++ _getFprintDeviceProxy(devicePath) { ++ return new Gio.DBusProxy({ ++ g_connection: Gio.DBus.system, ++ g_name: 'net.reactivated.Fprint', ++ g_object_path: devicePath, ++ g_interface_name: FprintDeviceInfo.name, ++ g_interface_info: FprintDeviceInfo, ++ g_flags: Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS, ++ }); ++ } ++ ++ _setFingerprintReaderType(fingerprintReaderType) { ++ if (fingerprintReaderType === undefined) ++ throw new Error(`Unexpected fingerprint device type '${fingerprintReaderType}'`); ++ ++ if (this.readerType === fingerprintReaderType) ++ return; ++ ++ this.readerType = fingerprintReaderType; ++ this.emit('reader-type-changed'); ++ } ++ ++ _handleFingerprintError(e) { ++ this._setFingerprintReaderType(FingerprintReaderType.NONE); ++ ++ if (e instanceof GLib.Error) { ++ if (e.matches(Gio.DBusError, Gio.DBusError.SERVICE_UNKNOWN)) ++ return; ++ if (Gio.DBusError.is_remote_error(e) && ++ Gio.DBusError.get_remote_error(e) === ++ 'net.reactivated.Fprint.Error.NoSuchDevice') ++ return; ++ } ++ ++ logError(e, 'Failed to interact with fprintd service'); ++ } ++} +-- +2.53.0 + + +From 584b5621794d7aa445f46a366ca43847ac81779d Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 23 Sep 2025 16:45:59 +0200 +Subject: [PATCH 33/42] gdm/misc: Add PasskeyDeviceManager + +This utility will be used in the next commits, when passkey authentication +is implemented, to detect when a passkey has been inserted or removed. + +To detect if a sysfs device is a passkey (fido2), it's been used the +implementation of systemd in fido_id_desc.c. +--- + js/js-resources.gresource.xml | 1 + + js/misc/dependencies.js | 1 + + js/misc/passkeyDeviceManager.js | 71 +++++++++++++++++++++++++++++++++ + 3 files changed, 73 insertions(+) + create mode 100644 js/misc/passkeyDeviceManager.js + +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index 2712bfcc84..26b55b75bb 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -40,6 +40,7 @@ + misc/objectManager.js + misc/params.js + misc/parentalControlsManager.js ++ misc/passkeyDeviceManager.js + misc/permissionStore.js + misc/signals.js + misc/signalTracker.js +diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js +index f8f98d4871..c3c51aa2de 100644 +--- a/js/misc/dependencies.js ++++ b/js/misc/dependencies.js +@@ -18,6 +18,7 @@ import 'gi://GdkPixbuf?version=2.0'; + import 'gi://GnomeBG?version=4.0'; + import 'gi://GnomeDesktop?version=4.0'; + import 'gi://Graphene?version=1.0'; ++import 'gi://GUdev?version=1.0'; + import 'gi://GWeather?version=4.0'; + import 'gi://IBus?version=1.0'; + import 'gi://Pango?version=1.0'; +diff --git a/js/misc/passkeyDeviceManager.js b/js/misc/passkeyDeviceManager.js +new file mode 100644 +index 0000000000..9650ac1d2d +--- /dev/null ++++ b/js/misc/passkeyDeviceManager.js +@@ -0,0 +1,71 @@ ++import GObject from 'gi://GObject'; ++import GUdev from 'gi://GUdev'; ++ ++let _passkeyDeviceManager = null; ++ ++/** ++ * @returns {PasskeyDeviceManager} ++ */ ++export function getPasskeyDeviceManager() { ++ if (_passkeyDeviceManager == null) ++ _passkeyDeviceManager = new PasskeyDeviceManager(); ++ ++ return _passkeyDeviceManager; ++} ++ ++class PasskeyDeviceManager extends GObject.Object { ++ static [GObject.GTypeName] = 'PasskeyDeviceManager'; ++ ++ static [GObject.signals] = { ++ 'passkey-inserted': {param_types: [GObject.TYPE_JSOBJECT]}, ++ 'passkey-removed': {param_types: [GObject.TYPE_JSOBJECT]}, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor() { ++ super(); ++ ++ this._insertedPasskeys = new Map(); ++ this._udevClient = new GUdev.Client({subsystems: ['hidraw']}); ++ ++ this._onLoaded(); ++ } ++ ++ get hasInsertedPasskeys() { ++ return this._insertedPasskeys.size > 0; ++ } ++ ++ _onLoaded() { ++ this._udevClient.query_by_subsystem('hidraw') ++ .forEach(d => this._addPasskey(d)); ++ ++ this._udevClient.connect('uevent', (_, action, device) => { ++ if (action === 'add') ++ this._addPasskey(device); ++ else if (action === 'remove') ++ this._removePasskey(device); ++ }); ++ } ++ ++ _addPasskey(device) { ++ const sysfsPath = device.get_sysfs_path(); ++ const isFido = device.get_property_as_int('ID_FIDO_TOKEN') === 1; ++ if (!isFido || this._insertedPasskeys.has(sysfsPath)) ++ return; ++ ++ this._insertedPasskeys.set(sysfsPath, device); ++ this.emit('passkey-inserted', device); ++ } ++ ++ _removePasskey(device) { ++ const sysfsPath = device.get_sysfs_path(); ++ if (!this._insertedPasskeys.has(sysfsPath)) ++ return; ++ ++ this._insertedPasskeys.delete(sysfsPath); ++ this.emit('passkey-removed', device); ++ } ++} +-- +2.53.0 + + +From 93c424c183dce14d4aa305fba71ccb2361a3b1dc Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 18 Aug 2025 12:08:21 +0200 +Subject: [PATCH 34/42] gdm: Add AuthServices +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +All authentication mechanisms that were handled in util.js UserVerifier +are ported to a new class called AuthServices. In util.js it's left +getting the userVerifier proxies. + +The default workflow of authServices is: +1. beginVerification method is called with userName (optional) and + new userVerifierProxies. +2. 'mechanisms-changed' signal is emitted with the current enabled + mechanisms and a default selected one. +3. Calling selectMechanism method makes the selected authentication + mechanism start (will do nothing if mechanism was already selected). +4. When a mechanism is started, depending on the mechanism, it will + emit signals like 'ask-question', 'show-choice-list'..., and receive + feedback with methods like answerQuery or selectChoice. +5. After the back and forth of signals and methods, the authentication + can succeed ('verification-complete') or fail ('verification-failed'). +6. If the verification fails, authServices is in charge of requesting a reset, + either soft (reusing the username and the selected mechanism) or hard + (full reset). + +The main properties of authServices are: +* _enabledRoles: Contains the roles that are currently enabled in GDM settings. + Valid roles are defined in const.js (PASSWORD_ROLE_NAME, SMARTCARD_ROLE_NAME...). + This determines which mechanisms are included in _enabledMechanisms. +* _enabledMechanisms: Contains the mechanisms that are currently enabled. These + are the ones sent on 'mechanisms-changed'. +* _selectedMechanism: It's the currently selected mechanism. Similar of + what foregroundService was in UserVerifier. It must be one mechanism of the + ones in _enabledMechanisms. +* _userVerifier, and its siblings: They're the proxies used to have PAM + conversations with different PAM services trough the GDM API. + This is the interface that requests a password, a selection from a choice + list, etc. and gets the responses. + +The authentication of password, smartcard and fingerprint now is +implemented in a child class of AuthServices called AuthServicesLegacy. +1. It implements a hardcoded array of mechanisms based on roles (password, + smartcard and fingerprint). +2. Each mechanism is driven by a different PAM service, and _roleToService + is used to decide which service to use when starting a selected mechanism. +3. When a mechanism is selected, it's emited 'reset' signal with softReset: true + which will start a conversation reusing the username and the selected + mechanism. +4. Fingerprint mechanism is non selectable, so it will be started + automatically on beginVerification, if it's enabled. +5. Authentication services from credentialManagers are also supported, they + won't be shown in the mechanisms list, but they will be automatically started + if they have preauthenticated credentials. + +Based on the previous work done by: + - Marco Trevisan (Treviño) + - Ray Strode +--- + js/gdm/authPrompt.js | 44 +-- + js/gdm/authServices.js | 476 ++++++++++++++++++++++ + js/gdm/authServicesLegacy.js | 324 +++++++++++++++ + js/gdm/util.js | 718 +++++++--------------------------- + js/js-resources.gresource.xml | 2 + + js/misc/fingerprintManager.js | 4 - + po/POTFILES.in | 1 + + 7 files changed, 961 insertions(+), 608 deletions(-) + create mode 100644 js/gdm/authServices.js + create mode 100644 js/gdm/authServicesLegacy.js + +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index 9a9c0c3802..d8c7c6409d 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -10,7 +10,6 @@ import St from 'gi://St'; + import * as Animation from '../ui/animation.js'; + import * as AuthList from './authList.js'; + import * as Batch from './batch.js'; +-import * as Const from './const.js'; + import * as GdmUtil from './util.js'; + import * as Params from '../misc/params.js'; + import * as ShellEntry from '../ui/shellEntry.js'; +@@ -92,10 +91,7 @@ export const AuthPrompt = GObject.registerClass({ + 'verification-failed', this._onVerificationFailed.bind(this), + 'verification-complete', this._onVerificationComplete.bind(this), + 'reset', this._onReset.bind(this), +- 'smartcard-status-changed', this._onSmartcardStatusChanged.bind(this), +- 'credential-manager-authenticated', this._onCredentialManagerAuthenticated.bind(this), + this); +- this.smartcardDetected = this._userVerifier.smartcardDetected; + + this.connect('destroy', this._onDestroy.bind(this)); + +@@ -445,31 +441,6 @@ export const AuthPrompt = GObject.registerClass({ + this.emit('prompted'); + } + +- _onCredentialManagerAuthenticated() { +- if (this.verificationStatus !== AuthPromptStatus.VERIFICATION_SUCCEEDED) +- this.reset(); +- } +- +- _onSmartcardStatusChanged() { +- this.smartcardDetected = this._userVerifier.smartcardDetected; +- +- // Most of the time we want to reset if the user inserts or removes +- // a smartcard. Smartcard insertion "preempts" what the user was +- // doing, and smartcard removal aborts the preemption. +- // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying +- // with a smartcard +- // 2) Don't reset if we've already succeeded at verification and +- // the user is getting logged in. +- if (this._userVerifier.serviceIsDefault(Const.SMARTCARD_SERVICE_NAME) && +- (this.verificationStatus === AuthPromptStatus.VERIFYING || +- this.verificationStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) && +- this.smartcardDetected) +- return; +- +- if (this.verificationStatus !== AuthPromptStatus.VERIFICATION_SUCCEEDED) +- this.reset(); +- } +- + _onShowMessage(_userVerifier, serviceName, message, type) { + let wiggleParameters = {duration: 0}; + +@@ -782,6 +753,13 @@ export const AuthPrompt = GObject.registerClass({ + if (invalidStatus.includes(this.verificationStatus)) + return false; + ++ const oldPromptStep = this._promptStep; ++ this._promptStep = 0; ++ if (!this._userVerifier.selectMechanism(mechanism)) ++ this._promptStep = oldPromptStep; ++ ++ this._updateCancelButton(); ++ + return true; + } + +@@ -803,8 +781,10 @@ export const AuthPrompt = GObject.registerClass({ + this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch(3000, + this._onUserStoppedTypePreemptiveAnswer.bind(this)); + +- if (this._userVerifier) +- this._userVerifier.cancel(); ++ if (softReset) ++ this._userVerifier?.cancel(); ++ else ++ this._userVerifier?.reset(); + + reuseEntryText = reuseEntryText || this._preemptiveInput; + +@@ -828,7 +808,7 @@ export const AuthPrompt = GObject.registerClass({ + if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED) + return; + resetType = ResetType.PROVIDE_USERNAME; +- } else if (this._userVerifier.foregroundServiceDeterminesUsername()) { ++ } else if (!this._userVerifier.needsUsername()) { + // We don't need to know the username if the user preempted the login screen + // with a smartcard or with preauthenticated oVirt credentials + resetType = ResetType.DONT_PROVIDE_USERNAME; +diff --git a/js/gdm/authServices.js b/js/gdm/authServices.js +new file mode 100644 +index 0000000000..f702b15a04 +--- /dev/null ++++ b/js/gdm/authServices.js +@@ -0,0 +1,476 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++import * as FingerprintManager from '../misc/fingerprintManager.js'; ++import * as Params from '../misc/params.js'; ++import * as SmartcardManager from '../misc/smartcardManager.js'; ++import {logErrorUnlessCancelled} from '../misc/errorUtils.js'; ++import * as Util from './util.js'; ++import Gdm from 'gi://Gdm'; ++import GLib from 'gi://GLib'; ++import Gio from 'gi://Gio'; ++import GObject from 'gi://GObject'; ++ ++Gio._promisify(Gdm.Client.prototype, 'open_reauthentication_channel'); ++Gio._promisify(Gdm.Client.prototype, 'get_user_verifier'); ++Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification_for_user'); ++Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification'); ++ ++export class AuthServices extends GObject.Object { ++ static [GObject.GTypeName] = 'AuthServices'; ++ ++ static [GObject.signals] = { ++ 'queue-message': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_UINT], ++ }, ++ 'queue-priority-message': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_UINT], ++ }, ++ 'wait-pending-messages': { ++ param_types: [GObject.TYPE_JSOBJECT], ++ }, ++ 'filter-messages': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_UINT], ++ }, ++ 'verification-failed': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_BOOLEAN], ++ }, ++ 'verification-complete': {}, ++ 'ask-question': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_BOOLEAN], ++ }, ++ 'reset': { ++ param_types: [GObject.TYPE_JSOBJECT], ++ }, ++ 'show-choice-list': { ++ param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_JSOBJECT], ++ }, ++ 'mechanisms-changed': {}, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ static SupportedRoles = []; ++ static RoleToService = {}; ++ ++ static supportsAny(roles) { ++ return roles.some(r => this.SupportedRoles.includes(r)); ++ } ++ ++ constructor(params) { ++ super(); ++ params = Params.parse(params, { ++ client: null, ++ enabledRoles: [], ++ allowedFailures: 3, ++ reauthOnly: false, ++ }); ++ ++ this._client = params.client; ++ this._enabledRoles = params.enabledRoles; ++ this._allowedFailures = params.allowedFailures; ++ this._reauthOnly = params.reauthOnly; ++ ++ this._failCounter = 0; ++ this._activeServices = new Set(); ++ this._unavailableServices = new Set(); ++ ++ this._cancellable = null; ++ ++ this._connectSmartcardManager(); ++ this._connectFingerprintManager(); ++ } ++ ++ get selectedMechanism() { ++ return this._selectedMechanism; ++ } ++ ++ get enabledMechanisms() { ++ return this._enabledMechanisms; ++ } ++ ++ get _roleToService() { ++ return this.constructor.RoleToService; ++ } ++ ++ get supportedRoles() { ++ return this.constructor.SupportedRoles; ++ } ++ ++ selectChoice(serviceName, key) { ++ this._handleSelectChoice(serviceName, key); ++ } ++ ++ async answerQuery(serviceName, answer) { ++ try { ++ await this._waitPendingMessages(); ++ await this._handleAnswerQuery(serviceName, answer); ++ } catch (e) { ++ logErrorUnlessCancelled(e); ++ } ++ } ++ ++ async beginVerification(userName, userVerifierProxies) { ++ this._cancellable?.cancel(); ++ this._cancellable = new Gio.Cancellable(); ++ this._userName = userName; ++ ++ try { ++ this._updateUserVerifier(userVerifierProxies); ++ await this._startServices(this._cancellable); ++ } catch (e) { ++ if (e.error?.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ return; ++ ++ this._failCounter++; ++ throw e; ++ } ++ ++ this._handleBeginVerification(); ++ } ++ ++ selectMechanism(mechanism) { ++ if (this._selectedMechanism?.role === mechanism.role && ++ this._selectedMechanism?.serviceName === mechanism.serviceName) ++ return false; ++ ++ this._selectedMechanism = this._enabledMechanisms?.find(m => ++ m.role === mechanism.role && ++ m.serviceName === mechanism.serviceName ++ ); ++ ++ this._handleSelectMechanism(); ++ ++ return !!this._selectedMechanism; ++ } ++ ++ needsUsername() { ++ return this._handleNeedsUsername(); ++ } ++ ++ reset() { ++ this._failCounter = 0; ++ ++ this._handleReset(); ++ } ++ ++ cancel() { ++ this._handleCancel(); ++ } ++ ++ clear() { ++ this._cancellable?.cancel(); ++ this._cancellable = null; ++ ++ this._unavailableServices.clear(); ++ this._activeServices.clear(); ++ ++ this._verificationComplete = false; ++ ++ this._clearUserVerifier(); ++ ++ this._handleClear(); ++ } ++ ++ _clearUserVerifier() { ++ this._disconnectUserVerifierSignals(); ++ this._userVerifier = null; ++ this._userVerifierChoiceList = null; ++ this._userVerifierCustomJSON = null; ++ } ++ ++ _disconnectUserVerifierSignals() { ++ this._userVerifier?.get_connection().disconnectObject(this); ++ this._userVerifier?.disconnectObject(this); ++ this._userVerifierChoiceList?.disconnectObject(this); ++ this._userVerifierCustomJSON?.disconnectObject(this); ++ } ++ ++ _updateEnabledMechanisms() { ++ this._enabledMechanisms = []; ++ ++ this._handleUpdateEnabledMechanisms(); ++ ++ this.emit('mechanisms-changed'); ++ } ++ ++ _connectSmartcardManager() { ++ this._smartcardManager = SmartcardManager.getSmartcardManager(); ++ this._smartcardManager.connectObject( ++ 'smartcard-inserted', () => this._handleSmartcardChanged(), ++ 'smartcard-removed', () => this._handleSmartcardChanged(), ++ this); ++ } ++ ++ _connectFingerprintManager() { ++ // Fingerprint can only work on lockscreen ++ if (!this._reauthOnly) ++ return; ++ ++ this._fingerprintManager = FingerprintManager.getFingerprintManager(); ++ this._fingerprintManager.connectObject( ++ 'reader-type-changed', () => this._handleFingerprintChanged(), ++ this); ++ } ++ ++ _waitPendingMessages() { ++ const cancellable = this._cancellable; ++ return new Promise((resolve, reject) => { ++ let done = false; ++ const safeResolve = () => { ++ if (!done) { ++ done = true; ++ if (cancellable?.is_cancelled()) { ++ reject(new GLib.Error( ++ Gio.IOErrorEnum, ++ Gio.IOErrorEnum.CANCELLED, ++ 'Operation was cancelled')); ++ } else { ++ resolve(); ++ } ++ } ++ }; ++ const safeReject = err => { ++ if (!done) { ++ done = true; ++ reject(err); ++ } ++ }; ++ const waiter = { ++ resolve: safeResolve, ++ reject: safeReject, ++ }; ++ this.emit('wait-pending-messages', waiter); ++ ++ GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, () => { ++ safeReject(new Error('Timed out waiting for pending messages')); ++ return GLib.SOURCE_REMOVE; ++ }); ++ }); ++ } ++ ++ _updateUserVerifier(proxies) { ++ this._disconnectUserVerifierSignals(); ++ this._userVerifier = proxies.userVerifier; ++ this._userVerifierChoiceList = proxies.userVerifierChoiceList; ++ this._userVerifierCustomJSON = proxies.userVerifierCustomJSON; ++ this._connectUserVerifierSignals(); ++ } ++ ++ _connectUserVerifierSignals() { ++ this._userVerifier.get_connection().connectObject( ++ 'closed', () => this._clearUserVerifier(), ++ this); ++ ++ this._userVerifier.connectObject( ++ 'info', (_, ...args) => this._onInfo(...args), ++ 'problem', (_, ...args) => this._onProblem(...args), ++ 'info-query', (_, ...args) => this._onInfoQuery(...args), ++ 'secret-info-query', (_, ...args) => this._onSecretInfoQuery(...args), ++ 'conversation-started', (_, ...args) => this._onConversationStarted(...args), ++ 'conversation-stopped', (_, ...args) => this._onConversationStopped(...args), ++ 'service-unavailable', (_, ...args) => this._onServiceUnavailable(...args), ++ 'reset', () => this.emit('reset', {}), ++ 'verification-complete', (_, ...args) => this._onVerificationComplete(...args), ++ this); ++ ++ this._userVerifierChoiceList?.connectObject( ++ 'choice-query', (_, ...args) => this._onChoiceListQuery(...args), ++ this); ++ ++ this._userVerifierCustomJSON?.connectObject( ++ 'request', (_, ...args) => this._onCustomJSONRequest(...args), ++ this); ++ } ++ ++ _onInfo(serviceName, info) { ++ this._handleOnInfo(serviceName, info); ++ } ++ ++ _onProblem(serviceName, problem) { ++ this._handleOnProblem(serviceName, problem); ++ } ++ ++ _onInfoQuery(serviceName, question) { ++ this._handleOnInfoQuery(serviceName, question); ++ } ++ ++ _onSecretInfoQuery(serviceName, secretQuestion) { ++ this._handleOnSecretInfoQuery(serviceName, secretQuestion); ++ } ++ ++ _onConversationStarted(serviceName) { ++ this._activeServices.add(serviceName); ++ ++ this._handleOnConversationStarted(serviceName); ++ } ++ ++ _onConversationStopped(serviceName) { ++ this._activeServices.delete(serviceName); ++ ++ this.emit('filter-messages', serviceName, Util.MessageType.ERROR); ++ ++ this._handleOnConversationStopped(serviceName); ++ } ++ ++ _onServiceUnavailable(serviceName, errorMessage) { ++ this._unavailableServices.add(serviceName); ++ ++ if (this._selectedMechanism?.serviceName === serviceName && errorMessage) { ++ this.emit('queue-message', ++ serviceName, ++ errorMessage, ++ Util.MessageType.ERROR); ++ } ++ ++ this._handleOnServiceUnavailable(serviceName, errorMessage); ++ } ++ ++ _onVerificationComplete(serviceName) { ++ this._handleOnVerificationComplete(serviceName); ++ this.emit('verification-complete'); ++ } ++ ++ _onChoiceListQuery(serviceName, promptMessage, list) { ++ this._handleOnChoiceListQuery(serviceName, promptMessage, list); ++ } ++ ++ _onCustomJSONRequest(serviceName, protocol, version, json) { ++ this._handleOnCustomJSONRequest(serviceName, protocol, version, json); ++ } ++ ++ _canRetry() { ++ return this._userName && ++ (this._reauthOnly || this._failCounter < this._allowedFailures); ++ } ++ ++ async _verificationFailed(serviceName, shouldRetry) { ++ this._handleVerificationFailed(serviceName); ++ ++ const doneTrying = !shouldRetry || !this._canRetry(); ++ ++ this.emit('verification-failed', serviceName, !doneTrying); ++ ++ try { ++ await this._waitPendingMessages(); ++ this.emit('reset', {softReset: !doneTrying}); ++ } catch (e) { ++ logErrorUnlessCancelled(e); ++ } ++ } ++ ++ async _startServices(cancellable) { ++ for (const serviceName of this._getEnabledServices()) { ++ if (this._canStartService(serviceName)) { ++ // eslint-disable-next-line no-await-in-loop ++ await this._startService(serviceName, cancellable); ++ } ++ } ++ } ++ ++ _getEnabledServices() { ++ const services = this._enabledRoles ++ .map(r => this._roleToService[r]) ++ .filter(s => s); // filter undefined ++ ++ services.push(...this._getCredentialManagerServices()); ++ ++ // Remove duplicates ++ return [...new Set(services)]; ++ } ++ ++ _getCredentialManagerServices() { ++ return this._handleGetCredentialManagerServices(); ++ } ++ ++ _canStartService(serviceName) { ++ return !this._activeServices.has(serviceName) && ++ !this._unavailableServices.has(serviceName) && ++ this._handleCanStartService(serviceName); ++ } ++ ++ async _startService(serviceName, cancellable) { ++ try { ++ this._activeServices.add(serviceName); ++ if (this._userName) { ++ await this._userVerifier.call_begin_verification_for_user( ++ serviceName, this._userName, cancellable); ++ } else { ++ await this._userVerifier.call_begin_verification( ++ serviceName, cancellable); ++ } ++ } catch (e) { ++ this._activeServices.delete(serviceName); ++ if (e instanceof GLib.Error && ++ Gio.DBusError.is_remote_error(e) && ++ Gio.DBusError.get_remote_error(e) === ++ 'org.gnome.DisplayManager.SessionWorker.Error.ServiceUnavailable') ++ this._unavailableServices.add(serviceName); ++ ++ throw new Util.InitError(e, ++ this._userName ++ ? `Failed to start ${serviceName} verification for user` ++ : `Failed to start ${serviceName} verification`, ++ serviceName); ++ } ++ } ++ ++ _handleSelectChoice() {} ++ ++ async _handleAnswerQuery() {} ++ ++ _handleBeginVerification() {} ++ ++ _handleSelectMechanism() {} ++ ++ _handleNeedsUsername() { ++ return true; ++ } ++ ++ _handleReset() {} ++ ++ _handleCancel() {} ++ ++ _handleClear() {} ++ ++ _handleUpdateEnabledMechanisms() { ++ throw new GObject.NotImplementedError( ++ `_handleUpdateEnabledMechanisms in ${this.constructor.name}`); ++ } ++ ++ _handleSmartcardChanged() {} ++ ++ _handleFingerprintChanged() {} ++ ++ _handleOnInfo() {} ++ ++ _handleOnProblem() {} ++ ++ _handleOnInfoQuery() {} ++ ++ _handleOnSecretInfoQuery() {} ++ ++ _handleOnConversationStarted() {} ++ ++ _handleOnConversationStopped() {} ++ ++ _handleOnServiceUnavailable() {} ++ ++ _handleOnVerificationComplete() {} ++ ++ _handleOnChoiceListQuery() {} ++ ++ _handleOnCustomJSONRequest() {} ++ ++ _handleVerificationFailed() {} ++ ++ _handleGetCredentialManagerServices() { ++ return []; ++ } ++ ++ _handleCanStartService() { ++ throw new GObject.NotImplementedError( ++ `_handleCanStartService in ${this.constructor.name}`); ++ } ++} +diff --git a/js/gdm/authServicesLegacy.js b/js/gdm/authServicesLegacy.js +new file mode 100644 +index 0000000000..e9c1676ab9 +--- /dev/null ++++ b/js/gdm/authServicesLegacy.js +@@ -0,0 +1,324 @@ ++import GLib from 'gi://GLib'; ++import GObject from 'gi://GObject'; ++ ++import * as Const from './const.js'; ++import {FingerprintReaderType} from '../misc/fingerprintManager.js'; ++import * as OVirt from './oVirt.js'; ++import * as Util from './util.js'; ++import * as Vmware from './vmware.js'; ++import {AuthServices} from './authServices.js'; ++ ++const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15; ++ ++const Mechanisms = [ ++ { ++ serviceName: Const.PASSWORD_SERVICE_NAME, ++ role: Const.PASSWORD_ROLE_NAME, ++ name: 'Password', ++ }, ++ { ++ serviceName: Const.SMARTCARD_SERVICE_NAME, ++ role: Const.SMARTCARD_ROLE_NAME, ++ name: 'Smartcard', ++ }, ++ { ++ serviceName: Const.FINGERPRINT_SERVICE_NAME, ++ role: Const.FINGERPRINT_ROLE_NAME, ++ name: 'Fingerprint', ++ }, ++]; ++ ++export class AuthServicesLegacy extends AuthServices { ++ static [GObject.GTypeName] = 'AuthServicesLegacy'; ++ ++ static SupportedRoles = [ ++ Const.PASSWORD_ROLE_NAME, ++ Const.SMARTCARD_ROLE_NAME, ++ Const.FINGERPRINT_ROLE_NAME, ++ ]; ++ ++ static RoleToService = { ++ [Const.PASSWORD_ROLE_NAME]: Const.PASSWORD_SERVICE_NAME, ++ [Const.SMARTCARD_ROLE_NAME]: Const.SMARTCARD_SERVICE_NAME, ++ [Const.FINGERPRINT_ROLE_NAME]: Const.FINGERPRINT_SERVICE_NAME, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(params) { ++ super(params); ++ ++ this._updateEnabledMechanisms(); ++ ++ this._credentialManagers = {}; ++ this._addCredentialManager(OVirt.SERVICE_NAME, OVirt.getOVirtCredentialsManager()); ++ this._addCredentialManager(Vmware.SERVICE_NAME, Vmware.getVmwareCredentialsManager()); ++ } ++ ++ _handleSelectChoice(serviceName, key) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ this._userVerifierChoiceList.call_select_choice( ++ serviceName, key, this._cancellable, null); ++ } ++ ++ async _handleAnswerQuery(serviceName, answer) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ if (this._selectedMechanism.role === Const.SMARTCARD_ROLE_NAME) ++ this._smartcardInProgress = true; ++ ++ await this._userVerifier.call_answer_query(serviceName, ++ answer, ++ this._cancellable, ++ null); ++ } ++ ++ _handleBeginVerification() { ++ this._fingerprintManager?.checkReaderType(this._cancellable); ++ ++ this.emit('mechanisms-changed'); ++ } ++ ++ _handleSelectMechanism() { ++ if (this._selectedMechanism) ++ this.emit('reset', {softReset: true}); ++ } ++ ++ _handleNeedsUsername() { ++ // Username won't be needed when there's only one mechanism and is ++ // Smartcard, or if the selected mechanism is a credential manager ++ return !(this._enabledMechanisms.length === 1 && ++ this._enabledMechanisms[0].role === Const.SMARTCARD_ROLE_NAME || ++ Object.keys(this._credentialManagers).includes(this._selectedMechanism?.serviceName)); ++ } ++ ++ _handleReset() { ++ this._selectedMechanism = null; ++ } ++ ++ _handleClear() { ++ this._smartcardInProgress = false; ++ } ++ ++ _handleUpdateEnabledMechanisms() { ++ if (!this._fingerprintManager?.readerFound) { ++ this._enabledMechanisms.push(...Mechanisms.filter(m => ++ this._enabledRoles.includes(m.role) && ++ m.role !== Const.FINGERPRINT_ROLE_NAME ++ )); ++ } else { ++ this._enabledMechanisms.push(...Mechanisms.filter(m => ++ this._enabledRoles.includes(m.role) ++ )); ++ } ++ } ++ ++ _handleSmartcardChanged() { ++ if (this._selectedMechanism?.role !== Const.SMARTCARD_ROLE_NAME || ++ this._smartcardInProgress && this._smartcardManager.hasInsertedTokens()) ++ return; ++ ++ this.emit('reset', {softReset: true}); ++ } ++ ++ _handleFingerprintChanged() { ++ if (!this._enabledRoles.includes(Const.FINGERPRINT_ROLE_NAME)) ++ return; ++ ++ this._updateEnabledMechanisms(); ++ this.emit('reset', {softReset: true, reuseEntryText: true}); ++ } ++ ++ _handleOnInfo(serviceName, info) { ++ if (serviceName === this._selectedMechanism?.serviceName) { ++ this.emit('queue-message', serviceName, info, Util.MessageType.INFO); ++ } else if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName)) { ++ // We don't show fingerprint messages directly since it's ++ // not the main auth service. Instead we use the messages ++ // as a cue to display our own message. ++ this.emit('queue-message', ++ serviceName, ++ this._fingerprintManager?.readerType === FingerprintReaderType.SWIPE ++ // Translators: this message is shown below the password entry field ++ // to indicate the user can swipe their finger on the fingerprint reader ++ ? _('(or swipe finger across reader)') ++ // Translators: this message is shown below the password entry field ++ // to indicate the user can place their finger on the fingerprint reader instead ++ : _('(or place finger on reader)'), ++ Util.MessageType.HINT); ++ } ++ } ++ ++ _handleOnProblem(serviceName, problem) { ++ if (serviceName === this._selectedMechanism?.serviceName || ++ (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName))) { ++ this.emit('queue-priority-message', ++ serviceName, ++ problem, ++ Util.MessageType.ERROR); ++ } ++ ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName)) { ++ // pam_fprintd allows the user to retry multiple (maybe even infinite! ++ // times before failing the authentication conversation. ++ // We don't want this behavior to bypass the max-tries setting the user has set, ++ // so we count the problem messages to know how many times the user has failed. ++ // Once we hit the max number of failures we allow, it's time to failure the ++ // conversation from our side. We can't do that right away, however, because ++ // we may drop pending messages coming from pam_fprintd. In order to make sure ++ // the user sees everything, we queue the failure up to get handled in the ++ // near future, after we've finished up the current round of messages. ++ this._failCounter++; ++ ++ if (this._canRetry()) ++ return; ++ ++ if (this._fingerprintFailedId) ++ GLib.source_remove(this._fingerprintFailedId); ++ ++ this._fingerprintFailedId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ++ FINGERPRINT_ERROR_TIMEOUT_WAIT, () => { ++ this._fingerprintFailedId = 0; ++ if (!this._cancellable.is_cancelled()) ++ this._verificationFailed(serviceName, false); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ } ++ ++ _handleOnInfoQuery(serviceName, question) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ this.emit('ask-question', serviceName, question, false); ++ } ++ ++ _handleOnSecretInfoQuery(serviceName, secretQuestion) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ let token = null; ++ if (this._credentialManagers[serviceName]) ++ token = this._credentialManagers[serviceName].token; ++ ++ if (token) { ++ this.answerQuery(serviceName, token); ++ return; ++ } ++ ++ this.emit('ask-question', serviceName, secretQuestion, true); ++ } ++ ++ _handleOnConversationStopped(serviceName) { ++ if (serviceName !== this._selectedMechanism?.serviceName && ++ serviceName !== Const.FINGERPRINT_SERVICE_NAME) ++ return; ++ ++ // If the login failed with the preauthenticated oVirt credentials ++ // then discard the credentials and revert to default authentication ++ // mechanism. ++ if (this._credentialManagers[serviceName]) { ++ this._credentialManagers[serviceName].token = null; ++ this._selectedMechanism = null; ++ this._verificationFailed(serviceName, false); ++ return; ++ } ++ ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._unavailableServices.has(serviceName)) { ++ this._enabledMechanisms = this._enabledMechanisms ++ .filter(m => m.serviceName !== serviceName); ++ this.emit('mechanisms-changed'); ++ } ++ ++ if (this._unavailableServices.has(serviceName)) ++ return; ++ ++ // if the password service fails, then cancel everything. ++ // But if, e.g., fingerprint fails, still give ++ // password authentication a chance to succeed ++ if (serviceName === this._selectedMechanism?.serviceName) ++ this._failCounter++; ++ ++ this._verificationFailed(serviceName, true); ++ } ++ ++ _handleOnServiceUnavailable(serviceName, errorMessage) { ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName) && ++ errorMessage) { ++ this.emit('queue-message', ++ serviceName, ++ errorMessage, ++ Util.MessageType.ERROR); ++ } ++ } ++ ++ _handleVerificationFailed(serviceName) { ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName) && ++ this._fingerprintFailedId) ++ GLib.source_remove(this._fingerprintFailedId); ++ } ++ ++ _handleOnVerificationComplete(serviceName) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ if (this._credentialManagers[serviceName]) { ++ this._credentialManagers[serviceName].token = null; ++ this._selectedMechanism = null; ++ } ++ } ++ ++ _handleOnChoiceListQuery(serviceName, promptMessage, list) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ const choiceList = {}; ++ for (const [key, value] of Object.entries(list.deepUnpack())) ++ choiceList[key] = {title: value}; ++ ++ this.emit('show-choice-list', serviceName, promptMessage, choiceList); ++ } ++ ++ _handleGetCredentialManagerServices() { ++ return Object.keys(this._credentialManagers); ++ } ++ ++ _handleCanStartService(serviceName) { ++ return serviceName === this._selectedMechanism?.serviceName || ++ (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._enabledMechanisms.some(m => m.serviceName === serviceName) && ++ this._userName); ++ } ++ ++ _addCredentialManager(serviceName, credentialManager) { ++ if (this._credentialManagers[serviceName]) ++ return; ++ ++ this._credentialManagers[serviceName] = credentialManager; ++ if (credentialManager.token) ++ this._onCredentialManagerAuthenticated(credentialManager); ++ ++ credentialManager.connectObject( ++ 'user-authenticated', () => this._onCredentialManagerAuthenticated(credentialManager), ++ this); ++ } ++ ++ _onCredentialManagerAuthenticated(credentialManager) { ++ this._selectedMechanism = { ++ serviceName: credentialManager.service, ++ role: Const.PASSWORD_ROLE_NAME, ++ }; ++ this.emit('reset', {softReset: true}); ++ } ++} +diff --git a/js/gdm/util.js b/js/gdm/util.js +index ae464665e2..8c11c63c49 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -1,25 +1,13 @@ + import Clutter from 'gi://Clutter'; +-import Gdm from 'gi://Gdm'; + import Gio from 'gi://Gio'; + import GLib from 'gi://GLib'; + import * as Signals from '../misc/signals.js'; + + import * as Batch from './batch.js'; + import * as Const from './const.js'; +-import * as FingerprintManager from '../misc/fingerprintManager.js'; +-import * as OVirt from './oVirt.js'; +-import * as Vmware from './vmware.js'; + import * as Main from '../ui/main.js'; +-import {logErrorUnlessCancelled} from '../misc/errorUtils.js'; +-import {loadInterfaceXML} from '../misc/fileUtils.js'; + import * as Params from '../misc/params.js'; +-import * as SmartcardManager from '../misc/smartcardManager.js'; +- +-Gio._promisify(Gdm.Client.prototype, 'open_reauthentication_channel'); +-Gio._promisify(Gdm.Client.prototype, 'get_user_verifier'); +-Gio._promisify(Gdm.UserVerifierProxy.prototype, +- 'call_begin_verification_for_user'); +-Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification'); ++import {AuthServicesLegacy} from './authServicesLegacy.js'; + + const CLONE_FADE_ANIMATION_TIME = 250; + +@@ -42,9 +30,6 @@ const USER_READ_TIME = 48; + const USER_READ_TIME_MIN = 2000; + const MESSAGE_TIME_MULTIPLIER = GLib.getenv('GDM_MESSAGE_TIME_MULTIPLIER') ?? 1; + +-const FINGERPRINT_SERVICE_PROXY_TIMEOUT = 5000; +-const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15; +- + /** + * Keep messages in order by priority + * +@@ -57,6 +42,15 @@ export const MessageType = { + ERROR: 3, + }; + ++export class InitError extends Error { ++ constructor(error, message, serviceName) { ++ super(message); ++ this.error = error; ++ this.where = message; ++ this.serviceName = serviceName; ++ } ++} ++ + /** + * @param {Clutter.Actor} actor + */ +@@ -130,53 +124,12 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._client = client; + this._cancellable = null; + +- this._defaultService = null; +- this._preemptingService = null; +- this._fingerprintReaderType = FingerprintManager.FingerprintReaderType.NONE; +- this._fingerprintReaderFound = false; +- + this._messageQueue = []; + this._messageQueueTimeoutId = 0; + +- this._failCounter = 0; +- this._activeServices = new Set(); +- this._unavailableServices = new Set(); +- +- this._credentialManagers = {}; +- +- this.reauthenticating = false; +- this.smartcardDetected = false; +- + this._settings = new Gio.Settings({schema_id: LOGIN_SCREEN_SCHEMA}); + this._settings.connect('changed', () => this._onSettingsChanged()); +- this._updateEnabledServices(); +- this._updateDefaultService(); +- +- this.addCredentialManager(OVirt.SERVICE_NAME, OVirt.getOVirtCredentialsManager()); +- this.addCredentialManager(Vmware.SERVICE_NAME, Vmware.getVmwareCredentialsManager()); +- } +- +- addCredentialManager(serviceName, credentialManager) { +- if (this._credentialManagers[serviceName]) +- return; +- +- this._credentialManagers[serviceName] = credentialManager; +- if (credentialManager.token) { +- this._onCredentialManagerAuthenticated(credentialManager, +- credentialManager.token); +- } +- +- credentialManager.connectObject('user-authenticated', +- this._onCredentialManagerAuthenticated.bind(this), this); +- } +- +- removeCredentialManager(serviceName) { +- let credentialManager = this._credentialManagers[serviceName]; +- if (!credentialManager) +- return; +- +- credentialManager.disconnectObject(this); +- delete this._credentialManagers[serviceName]; ++ this._updateAuthServices(); + } + + get hasPendingMessages() { +@@ -191,79 +144,72 @@ export class ShellUserVerifier extends Signals.EventEmitter { + return this._messageQueue ? this._messageQueue[0] : null; + } + +- begin(userName, hold) { ++ async begin(userName, hold) { ++ this._cancellable?.cancel(); + this._cancellable = new Gio.Cancellable(); +- this._hold = hold; +- this._userName = userName; +- this.reauthenticating = false; + +- this._fingerprintManager?.checkReaderType(this._cancellable); ++ try { ++ const proxies = await this._getUserVerifierProxies(userName, this._cancellable); ++ await this._authServicesLegacy?.beginVerification(userName, proxies); ++ this._userVerifier = proxies.userVerifier; ++ } catch (e) { ++ if (e instanceof InitError) ++ this._reportInitError(e); ++ } + +- // If possible, reauthenticate an already running session, +- // so any session specific credentials get updated appropriately +- if (userName) +- this._openReauthenticationChannel(userName); +- else +- this._getUserVerifier(); ++ hold?.release(); + } + +- cancel() { +- if (this._cancellable) +- this._cancellable.cancel(); ++ selectMechanism(mechanism) { ++ return this._authServicesLegacy?.selectMechanism(mechanism); ++ } + +- if (this._userVerifier) { +- this._userVerifier.call_cancel_sync(null); +- this.clear(); +- } ++ needsUsername() { ++ return this._authServicesLegacy?.needsUsername(); + } + +- _clearUserVerifier() { +- if (this._userVerifier) { +- this._disconnectSignals(); +- this._userVerifier.get_connection().disconnectObject(this); +- this._userVerifier = null; +- this._userVerifierChoiceList = null; +- } ++ reset() { ++ this._authServicesLegacy?.reset(); ++ ++ this._userVerifier?.call_cancel_sync(null); ++ ++ this.clear(); ++ } ++ ++ cancel() { ++ this._authServicesLegacy?.cancel(); ++ ++ this._userVerifier?.call_cancel_sync(null); ++ ++ this.clear(); + } + + clear() { +- if (this._cancellable) { +- this._cancellable.cancel(); +- this._cancellable = null; +- } ++ this._authServicesLegacy?.clear(); + +- this._clearUserVerifier(); + this._clearMessageQueue(); +- this._activeServices.clear(); ++ ++ this._cancellable?.cancel(); ++ this._cancellable = null; ++ ++ this._userVerifier = null; + } + + destroy() { ++ this._authServicesLegacy?.disconnectObject(this); ++ + this.cancel(); + + this._settings.run_dispose(); + this._settings = null; +- +- this._smartcardManager?.disconnectObject(this); +- this._smartcardManager = null; +- +- this._fingerprintManager?.disconnectObject(this); +- this._fingerprintManager = null; +- +- for (let service in this._credentialManagers) +- this.removeCredentialManager(service); + } + + selectChoice(serviceName, key) { +- this._userVerifierChoiceList.call_select_choice(serviceName, key, this._cancellable, null); ++ this._authServicesLegacy?.selectChoice(serviceName, key); + } + +- async answerQuery(serviceName, answer) { +- try { +- await this._handlePendingMessages(); +- this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null); +- } catch (e) { +- logErrorUnlessCancelled(e); +- } ++ answerQuery(serviceName, answer) { ++ this._authServicesLegacy?.answerQuery(serviceName, answer); + } + + _getIntervalForMessage(message) { +@@ -275,7 +221,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + USER_READ_TIME_MIN); + } + +- finishMessageQueue() { ++ _finishMessageQueue() { + if (!this.hasPendingMessages) + return; + +@@ -318,7 +264,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._messageQueue.shift(); + this._queueMessageTimeout(); + } else { +- this.finishMessageQueue(); ++ this._finishMessageQueue(); + } + + return GLib.SOURCE_REMOVE; +@@ -348,7 +294,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + _clearMessageQueue() { +- this.finishMessageQueue(); ++ this._finishMessageQueue(); + + if (this._messageQueueTimeoutId !== 0) { + GLib.source_remove(this._messageQueueTimeoutId); +@@ -357,471 +303,142 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this.emit('show-message', null, null, MessageType.NONE); + } + +- async _initFingerprintManager() { +- if (this._fingerprintManager) +- return; +- +- this._fingerprintManager = +- new FingerprintManager.FingerprintManager(this._cancellable); +- this._fingerprintManager.connectObject( +- 'reader-type-changed', () => this._onFingerprintReaderTypeChanged(), +- this); +- +- if (!this._getDetectedDefaultService()) { +- // Other authentication methods would have already been detected by +- // now as possibilities if they were available. +- // If we're here it means that FINGERPRINT_AUTHENTICATION_KEY is +- // true and so fingerprint authentication is our last potential +- // option, so go ahead a synchronously look for a fingerprint device +- // during startup or default service update. +- // Do not wait too much for fprintd to reply, as in case it hangs +- // we should fail early without having the shell to misbehave +- this._fingerprintManager.setDefaultTimeout(FINGERPRINT_SERVICE_PROXY_TIMEOUT); +- await this._fingerprintManager.checkReaderType(this._cancellable); +- } else { +- // Ensure fingerprint service starts, but do not wait for it +- this._fingerprintManager.checkReaderType(this._cancellable); +- } +- } +- +- _onFingerprintReaderTypeChanged() { +- this._fingerprintReaderType = this._fingerprintManager.readerType; +- this._fingerprintReaderFound = this._fingerprintManager.readerFound; +- this._updateDefaultService(); +- +- if (this._userVerifier && +- !this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) +- this._maybeStartFingerprintVerification(); +- } +- +- _onCredentialManagerAuthenticated(credentialManager, _token) { +- this._preemptingService = credentialManager.service; +- this.emit('credential-manager-authenticated'); +- } +- +- _initSmartcardManager() { +- if (this._smartcardManager) +- return; +- +- this._smartcardManager = SmartcardManager.getSmartcardManager(); +- +- // We check for smartcards right away, since an inserted smartcard +- // at startup should result in immediately initiating authentication. +- // This is different than fingerprint readers, where we only check them +- // after a user has been picked. +- this.smartcardDetected = false; +- this._checkForSmartcard(); +- +- this._updateDefaultService(); +- +- this._smartcardManager.connectObject( +- 'smartcard-inserted', () => this._checkForSmartcard(), +- 'smartcard-removed', () => this._checkForSmartcard(), this); +- } +- +- _checkForSmartcard() { +- let smartcardDetected; ++ _reportInitError(initError) { ++ const {error, where, serviceName} = initError; + +- if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) +- smartcardDetected = false; +- else if (this._reauthOnly) +- smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); +- else +- smartcardDetected = this._smartcardManager.hasInsertedTokens(); +- +- if (smartcardDetected !== this.smartcardDetected) { +- this.smartcardDetected = smartcardDetected; +- +- if (this.smartcardDetected) +- this._preemptingService = Const.SMARTCARD_SERVICE_NAME; +- else if (this._preemptingService === Const.SMARTCARD_SERVICE_NAME) +- this._preemptingService = null; +- +- this._updateDefaultService(); +- +- this.emit('smartcard-status-changed'); +- } +- } +- +- _reportInitError(where, error, serviceName) { + logError(error, where); +- this._hold?.release(); + + this._queueMessage(serviceName, _('Authentication error'), MessageType.ERROR); +- this._failCounter++; + this._verificationFailed(serviceName, false); + } + +- async _openReauthenticationChannel(userName) { +- try { +- this._clearUserVerifier(); +- this._userVerifier = await this._client.open_reauthentication_channel( +- userName, this._cancellable); +- this._userVerifier.get_connection().connectObject('closed', +- () => this.clear(), this); +- } catch (e) { +- if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) +- return; +- if (e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && +- !this._reauthOnly) { +- // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there +- // is no session to reauthenticate. Fall back to performing +- // verification from this login session +- this._getUserVerifier(); +- return; ++ async _getUserVerifierProxies(userName, cancellable) { ++ const proxies = {}; ++ ++ if (userName) { ++ try { ++ proxies.userVerifier = await this._client.open_reauthentication_channel( ++ userName, cancellable); ++ } catch (e) { ++ if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ throw e; ++ if (e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) && ++ !this._reauthOnly) { ++ // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there ++ // is no session to reauthenticate. Fall back to performing ++ // verification from this login session ++ return this._getUserVerifierProxies(null, cancellable); ++ } ++ throw new InitError(e, 'Failed to open reauthentication channel'); ++ } ++ } else { ++ try { ++ proxies.userVerifier = await this._client.get_user_verifier( ++ cancellable); ++ } catch (e) { ++ if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ throw e; ++ throw new InitError(e, 'Failed to obtain user verifier'); + } +- +- this._reportInitError('Failed to open reauthentication channel', e); +- return; + } + +- if (this._client.get_user_verifier_choice_list) +- this._userVerifierChoiceList = this._client.get_user_verifier_choice_list(); +- else +- this._userVerifierChoiceList = null; +- +- this.reauthenticating = true; +- this._connectSignals(); +- this._beginVerification(); +- this._hold?.release(); +- } +- +- async _getUserVerifier() { + try { +- this._clearUserVerifier(); +- this._userVerifier = +- await this._client.get_user_verifier(this._cancellable); +- this._userVerifier.get_connection().connectObject('closed', +- () => this.clear(), this); ++ if (this._client.get_user_verifier_choice_list) ++ proxies.userVerifierChoiceList = await this._client.get_user_verifier_choice_list(); ++ if (this._client.get_user_verifier_custom_json) ++ proxies.userVerifierCustomJSON = await this._client.get_user_verifier_custom_json(); + } catch (e) { +- if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) +- return; +- this._reportInitError('Failed to obtain user verifier', e); +- return; ++ throw new InitError(e, 'Failed to obtain user verifier extensions'); + } + +- if (this._client.get_user_verifier_choice_list) +- this._userVerifierChoiceList = this._client.get_user_verifier_choice_list(); +- else +- this._userVerifierChoiceList = null; +- +- this._connectSignals(); +- this._beginVerification(); +- this._hold?.release(); +- } +- +- _connectSignals() { +- this._disconnectSignals(); +- +- this._userVerifier.connectObject( +- 'info', this._onInfo.bind(this), +- 'problem', this._onProblem.bind(this), +- 'info-query', this._onInfoQuery.bind(this), +- 'secret-info-query', this._onSecretInfoQuery.bind(this), +- 'conversation-started', this._onConversationStarted.bind(this), +- 'conversation-stopped', this._onConversationStopped.bind(this), +- 'service-unavailable', this._onServiceUnavailable.bind(this), +- 'reset', this._onReset.bind(this), +- 'verification-complete', this._onVerificationComplete.bind(this), +- this); +- +- if (this._userVerifierChoiceList) { +- this._userVerifierChoiceList.connectObject('choice-query', +- this._onChoiceListQuery.bind(this), this); +- } +- } +- +- _disconnectSignals() { +- this._userVerifier?.disconnectObject(this); +- this._userVerifierChoiceList?.disconnectObject(this); +- } +- +- _getForegroundService() { +- if (this._preemptingService) +- return this._preemptingService; +- +- return this._defaultService; +- } +- +- serviceIsForeground(serviceName) { +- return serviceName === this._getForegroundService(); +- } +- +- foregroundServiceDeterminesUsername() { +- for (let serviceName in this._credentialManagers) { +- if (this.serviceIsForeground(serviceName)) +- return true; +- } +- +- return this.serviceIsForeground(Const.SMARTCARD_SERVICE_NAME); +- } +- +- serviceIsDefault(serviceName) { +- return serviceName === this._defaultService; ++ return proxies; + } + + serviceIsFingerprint(serviceName) { +- return this._fingerprintReaderFound && +- serviceName === Const.FINGERPRINT_SERVICE_NAME; ++ return serviceName === Const.FINGERPRINT_SERVICE_NAME; + } + + _onSettingsChanged() { +- this._updateEnabledServices(); +- this._updateDefaultService(); +- } +- +- _updateEnabledServices() { +- let needsReset = false; +- +- if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) { +- this._initFingerprintManager(); +- } else if (this._fingerprintManager) { +- this._fingerprintManager.disconnectObject(this); +- this._fingerprintManager = null; +- this._fingerprintReaderFound = false; +- this._fingerprintReaderType = FingerprintReaderType.NONE; +- +- if (this._activeServices.has(Const.FINGERPRINT_SERVICE_NAME)) +- needsReset = true; +- } +- +- if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) { +- this._initSmartcardManager(); +- } else if (this._smartcardManager) { +- this._smartcardManager.disconnectObject(this); +- this._smartcardManager = null; +- +- if (this._activeServices.has(Const.SMARTCARD_SERVICE_NAME)) +- needsReset = true; +- } +- +- if (needsReset) +- this._cancelAndReset(); +- } +- +- _getDetectedDefaultService() { +- if (this._smartcardManager?.loggedInWithToken()) +- return Const.SMARTCARD_SERVICE_NAME; +- else if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) +- return Const.PASSWORD_SERVICE_NAME; +- else if (this._smartcardManager) +- return Const.SMARTCARD_SERVICE_NAME; +- else if (this._fingerprintReaderFound) +- return Const.FINGERPRINT_SERVICE_NAME; +- return null; ++ this._updateAuthServices(); + } + +- _updateDefaultService() { +- const oldDefaultService = this._defaultService; +- this._defaultService = this._getDetectedDefaultService(); +- +- if (!this._defaultService) { +- log('no authentication service is enabled, using password authentication'); +- this._defaultService = Const.PASSWORD_SERVICE_NAME; +- } ++ _updateAuthServices() { ++ const enabledRoles = []; + +- if (oldDefaultService && +- oldDefaultService !== this._defaultService && +- this._activeServices.has(oldDefaultService)) +- this._cancelAndReset(); +- } ++ if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) ++ enabledRoles.push(Const.PASSWORD_ROLE_NAME); ++ if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) ++ enabledRoles.push(Const.SMARTCARD_ROLE_NAME); ++ if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) ++ enabledRoles.push(Const.FINGERPRINT_ROLE_NAME); + +- async _startService(serviceName) { +- this._hold?.acquire(); +- try { +- this._activeServices.add(serviceName); +- if (this._userName) { +- await this._userVerifier.call_begin_verification_for_user( +- serviceName, this._userName, this._cancellable); +- } else { +- await this._userVerifier.call_begin_verification( +- serviceName, this._cancellable); +- } +- } catch (e) { +- this._activeServices.delete(serviceName); +- if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) +- return; +- if (!this.serviceIsForeground(serviceName)) { +- logError(e, +- `Failed to start ${serviceName} for ${this._userName}`); +- this._hold?.release(); +- return; +- } +- this._reportInitError( +- this._userName +- ? `Failed to start ${serviceName} verification for user` +- : `Failed to start ${serviceName} verification`, +- e, serviceName); ++ if (JSON.stringify(enabledRoles) === JSON.stringify(this._enabledRoles)) + return; +- } +- this._hold?.release(); +- } + +- _beginVerification() { +- this._startService(this._getForegroundService()); +- this._maybeStartFingerprintVerification().catch(logError); +- } ++ this._enabledRoles = enabledRoles; + +- async _maybeStartFingerprintVerification() { +- if (this._userName && +- this._fingerprintReaderFound && +- !this.serviceIsForeground(Const.FINGERPRINT_SERVICE_NAME)) +- await this._startService(Const.FINGERPRINT_SERVICE_NAME); ++ this._createAuthServices(); + } + +- _onChoiceListQuery(client, serviceName, promptMessage, list) { +- if (!this.serviceIsForeground(serviceName)) +- return; +- +- const choiceList = {}; +- for (const [key, value] of Object.entries(list.deepUnpack())) +- choiceList[key] = {title: value}; +- +- this.emit('show-choice-list', serviceName, promptMessage, choiceList); +- } +- +- _onInfo(client, serviceName, info) { +- if (this.serviceIsForeground(serviceName)) { +- this._queueMessage(serviceName, info, MessageType.INFO); +- } else if (this.serviceIsFingerprint(serviceName)) { +- // We don't show fingerprint messages directly since it's +- // not the main auth service. Instead we use the messages +- // as a cue to display our own message. +- if (this._fingerprintReaderType === FingerprintManager.FingerprintReaderType.SWIPE) { +- // Translators: this message is shown below the password entry field +- // to indicate the user can swipe their finger on the fingerprint reader +- this._queueMessage(serviceName, _('(or swipe finger across reader)'), +- MessageType.HINT); +- } else { +- // Translators: this message is shown below the password entry field +- // to indicate the user can place their finger on the fingerprint reader instead +- this._queueMessage(serviceName, _('(or place finger on reader)'), +- MessageType.HINT); +- } +- } +- } +- +- _onProblem(client, serviceName, problem) { +- const isFingerprint = this.serviceIsFingerprint(serviceName); +- +- if (!this.serviceIsForeground(serviceName) && !isFingerprint) +- return; +- +- this._queuePriorityMessage(serviceName, problem, MessageType.ERROR); +- +- if (isFingerprint) { +- // pam_fprintd allows the user to retry multiple (maybe even infinite! +- // times before failing the authentication conversation. +- // We don't want this behavior to bypass the max-tries setting the user has set, +- // so we count the problem messages to know how many times the user has failed. +- // Once we hit the max number of failures we allow, it's time to failure the +- // conversation from our side. We can't do that right away, however, because +- // we may drop pending messages coming from pam_fprintd. In order to make sure +- // the user sees everything, we queue the failure up to get handled in the +- // near future, after we've finished up the current round of messages. +- this._failCounter++; +- +- if (!this._canRetry()) { +- if (this._fingerprintFailedId) +- GLib.source_remove(this._fingerprintFailedId); +- +- const cancellable = this._cancellable; +- this._fingerprintFailedId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, +- FINGERPRINT_ERROR_TIMEOUT_WAIT, () => { +- this._fingerprintFailedId = 0; +- if (!cancellable.is_cancelled()) +- this._verificationFailed(serviceName, false); +- return GLib.SOURCE_REMOVE; +- }); +- } +- } +- } ++ _createAuthServices() { ++ this._clearAuthServices(); + +- _onInfoQuery(client, serviceName, question) { +- if (!this.serviceIsForeground(serviceName)) +- return; ++ const params = { ++ client: this._client, ++ enabledRoles: this._enabledRoles, ++ allowedFailures: this.allowedFailures, ++ reauthOnly: this._reauthOnly, ++ }; ++ if (AuthServicesLegacy.supportsAny(this._enabledRoles)) ++ this._authServicesLegacy = new AuthServicesLegacy(params); + +- this.emit('ask-question', serviceName, question, false); ++ this._connectAuthServices(); + } + +- _onSecretInfoQuery(client, serviceName, secretQuestion) { +- if (!this.serviceIsForeground(serviceName)) +- return; +- +- let token = null; +- if (this._credentialManagers[serviceName]) +- token = this._credentialManagers[serviceName].token; +- +- if (token) { +- this.answerQuery(serviceName, token); +- return; +- } +- +- this.emit('ask-question', serviceName, secretQuestion, true); ++ _clearAuthServices() { ++ this._authServicesLegacy?.disconnectObject(this); ++ this._authServicesLegacy?.clear(); ++ this._authServicesLegacy = null; + } + +- _onReset() { +- // Clear previous attempts to authenticate +- this._failCounter = 0; +- this._activeServices.clear(); +- this._unavailableServices.clear(); +- this._updateDefaultService(); +- +- this.emit('reset'); ++ _connectAuthServices() { ++ [this._authServicesLegacy].forEach(authServices => { ++ authServices?.connectObject( ++ 'ask-question', (_, ...args) => this.emit('ask-question', ...args), ++ 'queue-message', (_, ...args) => this._queueMessage(...args), ++ 'queue-priority-message', (_, ...args) => this._queuePriorityMessage(...args), ++ 'wait-pending-messages', (_, ...args) => this._waitPendingMessages(...args), ++ 'filter-messages', (_, ...args) => this._filterServiceMessages(...args), ++ 'verification-failed', (_, ...args) => this._verificationFailed(...args), ++ 'verification-complete', (_, ...args) => this.emit('verification-complete', ...args), ++ 'reset', (_, ...args) => this.emit('reset', ...args), ++ 'show-choice-list', (_, ...args) => this.emit('show-choice-list', ...args), ++ 'mechanisms-changed', (_, ...args) => this._onMechanismsChanged(...args), ++ this); ++ }); + } + +- _onVerificationComplete(_client, serviceName) { +- const isCredentialManager = !!this._credentialManagers[serviceName]; +- const isForeground = this.serviceIsForeground(serviceName); +- if (isCredentialManager && isForeground) { +- this._credentialManagers[serviceName].token = null; +- this._preemptingService = null; +- } +- +- this.emit('verification-complete'); ++ _verificationFailed(serviceName, canRetry) { ++ this._filterServiceMessages(serviceName, MessageType.ERROR); ++ this.emit('verification-failed', serviceName, canRetry); + } + +- _cancelAndReset() { +- this.cancel(); +- this._onReset(); +- } ++ _onMechanismsChanged() { ++ const mechanisms = this._authServicesLegacy?.enabledMechanisms ?? []; + +- _retry(serviceName) { +- this._connectSignals(); +- this._startService(serviceName); +- } ++ const selectedMechanism = this._authServicesLegacy?.selectedMechanism ?? ++ mechanisms.find(m => isSelectable(m)) ?? ++ {}; + +- _canRetry() { +- return this._userName && +- (this._reauthOnly || this._failCounter < this.allowedFailures); ++ this.emit('mechanisms-changed', mechanisms, selectedMechanism); + } + +- async _verificationFailed(serviceName, shouldRetry) { +- if (serviceName === Const.FINGERPRINT_SERVICE_NAME) { +- if (this._fingerprintFailedId) +- GLib.source_remove(this._fingerprintFailedId); +- } +- +- // For Not Listed / enterprise logins, immediately reset +- // the dialog +- // Otherwise, when in login mode we allow ALLOWED_FAILURES attempts. +- // After that, we go back to the welcome screen. +- this._filterServiceMessages(serviceName, MessageType.ERROR); +- +- const doneTrying = !shouldRetry || !this._canRetry(); +- +- this.emit('verification-failed', serviceName, !doneTrying); ++ async _waitPendingMessages(waiter) { + try { +- if (doneTrying) { +- this._disconnectSignals(); +- await this._handlePendingMessages(); +- this._cancelAndReset(); +- } else { +- await this._handlePendingMessages(); +- this._retry(serviceName); +- } ++ await this._handlePendingMessages(); ++ waiter.resolve(); + } catch (e) { +- logErrorUnlessCancelled(e); ++ waiter.reject(e); + } + } + +@@ -840,47 +457,4 @@ export class ShellUserVerifier extends Signals.EventEmitter { + }); + }); + } +- +- _onServiceUnavailable(_client, serviceName, errorMessage) { +- this._unavailableServices.add(serviceName); +- +- if (!errorMessage) +- return; +- +- if (this.serviceIsForeground(serviceName) || this.serviceIsFingerprint(serviceName)) +- this._queueMessage(serviceName, errorMessage, MessageType.ERROR); +- } +- +- _onConversationStarted(client, serviceName) { +- this._activeServices.add(serviceName); +- } +- +- _onConversationStopped(client, serviceName) { +- this._activeServices.delete(serviceName); +- +- // If the login failed with the preauthenticated oVirt credentials +- // then discard the credentials and revert to default authentication +- // mechanism. +- const isCredentialManager = !!this._credentialManagers[serviceName]; +- const isForeground = this.serviceIsForeground(serviceName); +- if (isCredentialManager && isForeground) { +- this._credentialManagers[serviceName].token = null; +- this._preemptingService = null; +- this._verificationFailed(serviceName, false); +- return; +- } +- +- this._filterServiceMessages(serviceName, MessageType.ERROR); +- +- if (this._unavailableServices.has(serviceName)) +- return; +- +- // if the password service fails, then cancel everything. +- // But if, e.g., fingerprint fails, still give +- // password authentication a chance to succeed +- if (isForeground) +- this._failCounter++; +- +- this._verificationFailed(serviceName, true); +- } + } +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index 26b55b75bb..b6801acdb7 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -4,6 +4,8 @@ + gdm/authList.js + gdm/authMenuButton.js + gdm/authPrompt.js ++ gdm/authServices.js ++ gdm/authServicesLegacy.js + gdm/batch.js + gdm/const.js + gdm/credentialManager.js +diff --git a/js/misc/fingerprintManager.js b/js/misc/fingerprintManager.js +index 5bcb7bf6f0..609017a571 100644 +--- a/js/misc/fingerprintManager.js ++++ b/js/misc/fingerprintManager.js +@@ -67,10 +67,6 @@ class FingerprintManager extends GObject.Object { + return this.readerType !== FingerprintReaderType.NONE; + } + +- setDefaultTimeout(timeout) { +- this._fingerprintManagerProxy.set_default_timeout(timeout); +- } +- + async checkReaderType(cancellable) { + try { + // Wrappers don't support null cancellable, so let's ignore it in case +diff --git a/po/POTFILES.in b/po/POTFILES.in +index ee0829c96e..eb58487b4c 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -11,6 +11,7 @@ data/X-GNOME-Shell-Utilities.directory.desktop.in + js/dbusServices/extensions/extensionPrefsDialog.js + js/dbusServices/extensions/ui/extension-error-page.ui + js/gdm/authPrompt.js ++js/gdm/authServicesLegacy.js + js/gdm/loginDialog.js + js/gdm/util.js + js/misc/breakManager.js +-- +2.53.0 + + +From c2118d91c727bea67722313839d72db170993659 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 16 Feb 2026 12:21:54 +0100 +Subject: [PATCH 35/42] gdm: Add fingerprint ready state to delay showing icon + +Fingerprint mechanism now has a ready state that controls when it +appears in the authentication UI. When fingerprint authentication +starts, the mechanism is marked as not ready and filtered from the +enabledMechanisms list. Only after the fingerprint service has been +running for a brief window (0.5s) is it marked as ready and emitted +via mechanisms-changed. + +This prevents the fingerprint icon from briefly appearing in the UI +for users who don't have enrolled fingerprints. In those cases, the +fingerprint service starts but quickly stops with service-unavailable, +so the timeout never completes and the icon never shows. +--- + js/gdm/authServices.js | 2 +- + js/gdm/authServicesLegacy.js | 55 ++++++++++++++++++++++++++++++++---- + 2 files changed, 51 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/authServices.js b/js/gdm/authServices.js +index f702b15a04..7f0cda791a 100644 +--- a/js/gdm/authServices.js ++++ b/js/gdm/authServices.js +@@ -87,7 +87,7 @@ export class AuthServices extends GObject.Object { + } + + get enabledMechanisms() { +- return this._enabledMechanisms; ++ return this._enabledMechanisms?.filter(m => m.ready !== false); + } + + get _roleToService() { +diff --git a/js/gdm/authServicesLegacy.js b/js/gdm/authServicesLegacy.js +index e9c1676ab9..68a3c6990a 100644 +--- a/js/gdm/authServicesLegacy.js ++++ b/js/gdm/authServicesLegacy.js +@@ -9,6 +9,7 @@ import * as Vmware from './vmware.js'; + import {AuthServices} from './authServices.js'; + + const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15; ++const FINGERPRINT_READY_TIMEOUT_MS = 500; + + const Mechanisms = [ + { +@@ -55,6 +56,8 @@ export class AuthServicesLegacy extends AuthServices { + this._credentialManagers = {}; + this._addCredentialManager(OVirt.SERVICE_NAME, OVirt.getOVirtCredentialsManager()); + this._addCredentialManager(Vmware.SERVICE_NAME, Vmware.getVmwareCredentialsManager()); ++ ++ this._fingerprintReadyTimeoutId = 0; + } + + _handleSelectChoice(serviceName, key) { +@@ -103,6 +106,39 @@ export class AuthServicesLegacy extends AuthServices { + + _handleClear() { + this._smartcardInProgress = false; ++ this._clearFingerprintReadyTimeout(); ++ } ++ ++ _clearFingerprintReadyTimeout() { ++ if (this._fingerprintReadyTimeoutId) { ++ GLib.source_remove(this._fingerprintReadyTimeoutId); ++ this._fingerprintReadyTimeoutId = 0; ++ } ++ } ++ ++ _handleOnConversationStarted(serviceName) { ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME && ++ this._fingerprintReadyTimeoutId === 0) { ++ this._fingerprintReadyTimeoutId = GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, ++ FINGERPRINT_READY_TIMEOUT_MS, ++ () => { ++ this._fingerprintReadyTimeoutId = 0; ++ this._setFingerprintReady(); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ } ++ ++ _setFingerprintReady() { ++ const mechanism = this._enabledMechanisms.find(m => ++ m.role === Const.FINGERPRINT_ROLE_NAME); ++ if (!mechanism || mechanism.ready) ++ return; ++ ++ mechanism.ready = true; ++ ++ this.emit('mechanisms-changed'); + } + + _handleUpdateEnabledMechanisms() { +@@ -115,6 +151,13 @@ export class AuthServicesLegacy extends AuthServices { + this._enabledMechanisms.push(...Mechanisms.filter(m => + this._enabledRoles.includes(m.role) + )); ++ ++ // Mark fingerprint as not ready until service confirms ++ // it's working for this user ++ const fingerprintMechanism = this._enabledMechanisms.find(m => ++ m.role === Const.FINGERPRINT_ROLE_NAME); ++ if (fingerprintMechanism) ++ fingerprintMechanism.ready = false; + } + } + +@@ -232,11 +275,13 @@ export class AuthServicesLegacy extends AuthServices { + return; + } + +- if (serviceName === Const.FINGERPRINT_SERVICE_NAME && +- this._unavailableServices.has(serviceName)) { +- this._enabledMechanisms = this._enabledMechanisms +- .filter(m => m.serviceName !== serviceName); +- this.emit('mechanisms-changed'); ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME) { ++ this._clearFingerprintReadyTimeout(); ++ if (this._unavailableServices.has(serviceName)) { ++ this._enabledMechanisms = this._enabledMechanisms ++ .filter(m => m.serviceName !== serviceName); ++ this.emit('mechanisms-changed'); ++ } + } + + if (this._unavailableServices.has(serviceName)) +-- +2.53.0 + + +From 0ec2d1f973cc28ab3f3b4bb5476b348627e987ba Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 6 Feb 2024 14:09:34 -0500 +Subject: [PATCH 36/42] gdm: Add authServicesSSSDSwitchable + +This new authService child is used by SSSD to control multiple authentication +mechanisms from a single PAM conversation using 'gdm-switchable-auth' +service. It relies on the new JSON extension with a new 'auth-selection' +protocol provided by pam_sss. + +This initial commit just supports password mechanism. Future commits will add +support for other mechanisms. + +The basic workflow: +1. Begin verification starts the only service 'gdm-switchable-auth'. +2. _userVerifierCustomJson emits 'request' signal with a JSON message. + That message contains the list of available mechanisms and a priority list + that indicates which mechanism should be used first. +3. Those mechanisms are formated and stored in _enabledMechanisms. The + default mechanism is selected and the 'mechanisms-changed' signal is emitted. +4. When the mechanism is selected, its role is used with a switch to + handle the different authentication flows. Depending on the role, + different signals will be emitted, like 'ask-question'... +5. When handling user feedback, the selected mechanism role is used with + a switch to determine how to format the response and is sent using + _userVerifierCustomJson. +6. When verification succeeds, 'verification-complete' signal is emitted, + if it fails, 'verification-failed' is emitted. + +Now authServicesLegacy and authServicesSwitchable are mutually exclusive, but in +the next commit they won't be. + +When a mechanism is selected, both authServices try to store it. +Only the one that has it in _enabledMechanisms keeps it; the other gets null. +The stored selected mechanism indicates whether authServicesLegacy or +authServicesSwitchable will handle interactions. +--- + js/gdm/authServicesSSSDSwitchable.js | 167 +++++++++++++++++++++++++++ + js/gdm/loginDialog.js | 5 +- + js/gdm/util.js | 46 ++++++-- + js/js-resources.gresource.xml | 1 + + js/ui/unlockDialog.js | 1 + + po/POTFILES.in | 1 + + 6 files changed, 213 insertions(+), 8 deletions(-) + create mode 100644 js/gdm/authServicesSSSDSwitchable.js + +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +new file mode 100644 +index 0000000000..b6e2e06697 +--- /dev/null ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -0,0 +1,167 @@ ++import GObject from 'gi://GObject'; ++ ++import * as Const from './const.js'; ++import * as Util from './util.js'; ++import {AuthServices} from './authServices.js'; ++ ++export class AuthServicesSSSDSwitchable extends AuthServices { ++ static [GObject.GTypeName] = 'AuthServicesSSSDSwitchable'; ++ ++ static SupportedRoles = [ ++ Const.PASSWORD_ROLE_NAME, ++ ]; ++ ++ static RoleToService = { ++ [Const.PASSWORD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(params) { ++ super(params); ++ } ++ ++ _handleAnswerQuery(serviceName, answer) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ let response; ++ switch (this._selectedMechanism.role) { ++ case Const.PASSWORD_ROLE_NAME: ++ response = this._formatResponse(answer); ++ this._sendResponse(response); ++ break; ++ } ++ } ++ ++ _handleSelectMechanism() { ++ switch (this._selectedMechanism?.role) { ++ case Const.PASSWORD_ROLE_NAME: ++ this._startPasswordLogin(); ++ break; ++ } ++ } ++ ++ _handleReset() { ++ this._savedMechanism = null; ++ } ++ ++ _handleCancel() { ++ if (this._selectedMechanism) ++ this._savedMechanism = this._selectedMechanism; ++ } ++ ++ _handleClear() { ++ this._mechanisms = null; ++ this._priorityList = null; ++ this._enabledMechanisms = null; ++ this._selectedMechanism = null; ++ } ++ ++ _handleOnCustomJSONRequest(_serviceName, _protocol, _version, json) { ++ let requestObject; ++ ++ try { ++ requestObject = JSON.parse(json); ++ } catch (e) { ++ logError(e); ++ return; ++ } ++ ++ const {authSelection} = requestObject; ++ if (authSelection) { ++ this._mechanisms = authSelection.mechanisms; ++ this._priorityList = authSelection.priority; ++ ++ if (this._mechanisms) ++ this._updateEnabledMechanisms(); ++ } ++ } ++ ++ _handleUpdateEnabledMechanisms() { ++ this._enabledMechanisms.push(...Object.keys(this._mechanisms) ++ .map(id => ({ ++ serviceName: Const.SWITCHABLE_AUTH_SERVICE_NAME, ++ id, ++ ...this._mechanisms[id], ++ })) ++ // filter out mechanisms with roles that are not enabled ++ .filter(m => this._enabledRoles.includes(m.role))); ++ ++ const selectedMechanism = ++ this._enabledMechanisms ++ .find(m => this._savedMechanism?.role === m.role) ?? ++ this._priorityList ++ .map(id => this._enabledMechanisms.find(m => m.id === id))[0] ?? ++ this._enabledMechanisms[0]; ++ this.selectMechanism(selectedMechanism); ++ ++ this._savedMechanism = null; ++ } ++ ++ _handleOnInfo(serviceName, info) { ++ if (serviceName === this._selectedMechanism?.serviceName) ++ this.emit('queue-message', serviceName, info, Util.MessageType.INFO); ++ } ++ ++ _handleOnProblem(serviceName, problem) { ++ if (serviceName === this._selectedMechanism?.serviceName) { ++ this.emit('queue-priority-message', ++ serviceName, ++ problem, ++ Util.MessageType.ERROR); ++ } ++ } ++ ++ _handleOnConversationStopped(serviceName) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ if (this._unavailableServices.has(serviceName)) ++ return; ++ ++ this._failCounter++; ++ this._verificationFailed(serviceName, true); ++ } ++ ++ _handleCanStartService(serviceName) { ++ return serviceName === Const.SWITCHABLE_AUTH_SERVICE_NAME && ++ !this._enabledMechanisms; ++ } ++ ++ _formatResponse(answer) { ++ const {role, id} = this._selectedMechanism; ++ ++ let response; ++ switch (role) { ++ case Const.PASSWORD_ROLE_NAME: { ++ response = {password: answer}; ++ break; ++ } ++ default: ++ throw new GObject.NotImplementedError(`formatResponse: ${role}`); ++ } ++ ++ return { ++ authSelection: { ++ status: 'Ok', ++ [id]: response, ++ }, ++ }; ++ } ++ ++ _sendResponse(response) { ++ const {serviceName} = this._selectedMechanism; ++ ++ this._userVerifierCustomJSON.call_reply( ++ serviceName, JSON.stringify(response), this._cancellable, null); ++ } ++ ++ _startPasswordLogin() { ++ const {serviceName, prompt} = this._selectedMechanism; ++ ++ this.emit('ask-question', serviceName, prompt, true); ++ } ++} +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index ddb7e603b2..4c08f0957e 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -452,7 +452,10 @@ export const LoginDialog = GObject.registerClass({ + this._gdmClient = new Gdm.Client(); + + try { +- this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]); ++ this._gdmClient.set_enabled_extensions([ ++ Gdm.UserVerifierChoiceList.interface_info().name, ++ Gdm.UserVerifierCustomJSON.interface_info().name, ++ ]); + } catch { + } + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 8c11c63c49..ab4eccec42 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -8,6 +8,7 @@ import * as Const from './const.js'; + import * as Main from '../ui/main.js'; + import * as Params from '../misc/params.js'; + import {AuthServicesLegacy} from './authServicesLegacy.js'; ++import {AuthServicesSSSDSwitchable} from './authServicesSSSDSwitchable.js'; + + const CLONE_FADE_ANIMATION_TIME = 250; + +@@ -15,6 +16,7 @@ export const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + export const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; + export const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + export const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; ++export const SWITCHABLE_AUTHENTICATION_KEY = 'enable-switchable-authentication'; + export const BANNER_MESSAGE_KEY = 'banner-message-enable'; + export const BANNER_MESSAGE_SOURCE_KEY = 'banner-message-source'; + export const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; +@@ -150,6 +152,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + + try { + const proxies = await this._getUserVerifierProxies(userName, this._cancellable); ++ await this._authServicesSSSDSwitchable?.beginVerification(userName, proxies); + await this._authServicesLegacy?.beginVerification(userName, proxies); + this._userVerifier = proxies.userVerifier; + } catch (e) { +@@ -161,14 +164,19 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + selectMechanism(mechanism) { +- return this._authServicesLegacy?.selectMechanism(mechanism); ++ let selected = false; ++ selected |= this._authServicesSSSDSwitchable?.selectMechanism(mechanism); ++ selected |= this._authServicesLegacy?.selectMechanism(mechanism); ++ return selected; + } + + needsUsername() { +- return this._authServicesLegacy?.needsUsername(); ++ return this._authServicesSSSDSwitchable?.needsUsername() || ++ this._authServicesLegacy?.needsUsername(); + } + + reset() { ++ this._authServicesSSSDSwitchable?.reset(); + this._authServicesLegacy?.reset(); + + this._userVerifier?.call_cancel_sync(null); +@@ -177,6 +185,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + cancel() { ++ this._authServicesSSSDSwitchable?.cancel(); + this._authServicesLegacy?.cancel(); + + this._userVerifier?.call_cancel_sync(null); +@@ -185,6 +194,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + clear() { ++ this._authServicesSSSDSwitchable?.clear(); + this._authServicesLegacy?.clear(); + + this._clearMessageQueue(); +@@ -196,6 +206,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + destroy() { ++ this._authServicesSSSDSwitchable?.disconnectObject(this); + this._authServicesLegacy?.disconnectObject(this); + + this.cancel(); +@@ -205,10 +216,12 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + selectChoice(serviceName, key) { ++ this._authServicesSSSDSwitchable?.selectChoice(serviceName, key); + this._authServicesLegacy?.selectChoice(serviceName, key); + } + + answerQuery(serviceName, answer) { ++ this._authServicesSSSDSwitchable?.answerQuery(serviceName, answer); + this._authServicesLegacy?.answerQuery(serviceName, answer); + } + +@@ -372,10 +385,15 @@ export class ShellUserVerifier extends Signals.EventEmitter { + if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) + enabledRoles.push(Const.FINGERPRINT_ROLE_NAME); + +- if (JSON.stringify(enabledRoles) === JSON.stringify(this._enabledRoles)) ++ const switchableAuthentication = ++ this._settings.get_boolean(SWITCHABLE_AUTHENTICATION_KEY); ++ ++ if (JSON.stringify(enabledRoles) === JSON.stringify(this._enabledRoles) && ++ switchableAuthentication === this._switchableAuthenticationEnabled) + return; + + this._enabledRoles = enabledRoles; ++ this._switchableAuthenticationEnabled = switchableAuthentication; + + this._createAuthServices(); + } +@@ -389,20 +407,30 @@ export class ShellUserVerifier extends Signals.EventEmitter { + allowedFailures: this.allowedFailures, + reauthOnly: this._reauthOnly, + }; +- if (AuthServicesLegacy.supportsAny(this._enabledRoles)) ++ if (this._switchableAuthenticationEnabled && ++ AuthServicesSSSDSwitchable.supportsAny(this._enabledRoles)) ++ this._authServicesSSSDSwitchable = new AuthServicesSSSDSwitchable(params); ++ else if (AuthServicesLegacy.supportsAny(this._enabledRoles)) + this._authServicesLegacy = new AuthServicesLegacy(params); + + this._connectAuthServices(); + } + + _clearAuthServices() { ++ this._authServicesSSSDSwitchable?.disconnectObject(this); ++ this._authServicesSSSDSwitchable?.clear(); ++ this._authServicesSSSDSwitchable = null; ++ + this._authServicesLegacy?.disconnectObject(this); + this._authServicesLegacy?.clear(); + this._authServicesLegacy = null; + } + + _connectAuthServices() { +- [this._authServicesLegacy].forEach(authServices => { ++ [ ++ this._authServicesSSSDSwitchable, ++ this._authServicesLegacy, ++ ].forEach(authServices => { + authServices?.connectObject( + 'ask-question', (_, ...args) => this.emit('ask-question', ...args), + 'queue-message', (_, ...args) => this._queueMessage(...args), +@@ -424,9 +452,13 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + _onMechanismsChanged() { +- const mechanisms = this._authServicesLegacy?.enabledMechanisms ?? []; ++ const mechanismsSwitchable = this._authServicesSSSDSwitchable?.enabledMechanisms ?? []; ++ const mechanismsLegacy = this._authServicesLegacy?.enabledMechanisms ?? []; ++ const mechanisms = [...mechanismsSwitchable, ...mechanismsLegacy]; + +- const selectedMechanism = this._authServicesLegacy?.selectedMechanism ?? ++ const selectedMechanism = ++ this._authServicesSSSDSwitchable?.selectedMechanism ?? ++ this._authServicesLegacy?.selectedMechanism ?? + mechanisms.find(m => isSelectable(m)) ?? + {}; + +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index b6801acdb7..a7bcbd8898 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -6,6 +6,7 @@ + gdm/authPrompt.js + gdm/authServices.js + gdm/authServicesLegacy.js ++ gdm/authServicesSSSDSwitchable.js + gdm/batch.js + gdm/const.js + gdm/credentialManager.js +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 80c3dd813a..a16f68fee3 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -570,6 +570,7 @@ export const UnlockDialog = GObject.registerClass({ + try { + this._gdmClient.set_enabled_extensions([ + Gdm.UserVerifierChoiceList.interface_info().name, ++ Gdm.UserVerifierCustomJSON.interface_info().name, + ]); + } catch { + } +diff --git a/po/POTFILES.in b/po/POTFILES.in +index eb58487b4c..d1e4d64872 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -12,6 +12,7 @@ js/dbusServices/extensions/extensionPrefsDialog.js + js/dbusServices/extensions/ui/extension-error-page.ui + js/gdm/authPrompt.js + js/gdm/authServicesLegacy.js ++js/gdm/authServicesSSSDSwitchable.js + js/gdm/loginDialog.js + js/gdm/util.js + js/misc/breakManager.js +-- +2.53.0 + + +From baa6d1f833d409208661e69e5474158e94e8f089 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 12 Nov 2025 17:25:33 +0100 +Subject: [PATCH 37/42] authServicesSSSDSwitchable: Allow resetting expired + password + +JSON protocol can't inform when a password is expired, so it's needed to +check the info message. + +When the password is expired, a resetting process will be started. It will +have multiple requests to insert the current password and insert the new +password. This has to be done using the old flow because the PAM JSON protocol +doesn't support this process yet. +--- + js/gdm/authServicesSSSDSwitchable.js | 27 ++++++++++++++++++++++++++- + 1 file changed, 26 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +index b6e2e06697..5f89478ff9 100644 +--- a/js/gdm/authServicesSSSDSwitchable.js ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -23,10 +23,19 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + super(params); + } + +- _handleAnswerQuery(serviceName, answer) { ++ async _handleAnswerQuery(serviceName, answer) { + if (serviceName !== this._selectedMechanism?.serviceName) + return; + ++ if (this._selectedMechanism.role === Const.PASSWORD_ROLE_NAME && ++ this._resettingPassword) { ++ await this._userVerifier.call_answer_query(serviceName, ++ answer, ++ this._cancellable, ++ null); ++ return; ++ } ++ + let response; + switch (this._selectedMechanism.role) { + case Const.PASSWORD_ROLE_NAME: +@@ -58,6 +67,8 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this._priorityList = null; + this._enabledMechanisms = null; + this._selectedMechanism = null; ++ ++ this._resettingPassword = false; + } + + _handleOnCustomJSONRequest(_serviceName, _protocol, _version, json) { +@@ -102,6 +113,13 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + + _handleOnInfo(serviceName, info) { ++ // sssd can't inform about expired password from JSON so it's needed ++ // to check the info message and handle the reset using the old flow ++ if (serviceName === this._selectedMechanism?.serviceName && ++ this._selectedMechanism.role === Const.PASSWORD_ROLE_NAME && ++ info.includes('Password expired. Change your password now')) ++ this._resettingPassword = true; ++ + if (serviceName === this._selectedMechanism?.serviceName) + this.emit('queue-message', serviceName, info, Util.MessageType.INFO); + } +@@ -115,6 +133,13 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + } + ++ _handleOnSecretInfoQuery(serviceName, secretQuestion) { ++ if (serviceName === this._selectedMechanism?.serviceName && ++ this._selectedMechanism.role === Const.PASSWORD_ROLE_NAME && ++ this._resettingPassword) ++ this.emit('ask-question', serviceName, secretQuestion, true); ++ } ++ + _handleOnConversationStopped(serviceName) { + if (serviceName !== this._selectedMechanism?.serviceName) + return; +-- +2.53.0 + + +From 901bb24c5228ab67f417abfe32c1ffde066be986 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 21 Aug 2025 22:25:02 +0200 +Subject: [PATCH 38/42] gdm: Allow starting authServicesLegacy as fallback + +There can be cases where authServicesSSSDSwitchable doesn't support some +authentication methods. +In such cases, we should start authServicesLegacy as a fallback for +those unsupported methods. + +authServicesSSSDSwitchable leads when the fallback will happen emitting +'mechanisms-changed'. Then unsupportedRoles, from authServicesSSSDSwitchable will +be used to update the enabledRoles in authServicesLegacy. + +While authServicesSSSDSwitchable hasn't started its service and didn't receive +any mechanisms from JSON protocol, all roles are considered unsupported. +If it receives any message from GDM userVerifier before receiving +'auth-selection' JSON message, that will mean that pam_unix is being used and +it should fallback to authServicesLegacy (all roles are unsupported). +--- + js/gdm/authServices.js | 22 ++++++++++ + js/gdm/authServicesLegacy.js | 5 +++ + js/gdm/authServicesSSSDSwitchable.js | 62 +++++++++++++++++++++++++++- + js/gdm/util.js | 28 +++++++++++-- + 4 files changed, 111 insertions(+), 6 deletions(-) + +diff --git a/js/gdm/authServices.js b/js/gdm/authServices.js +index 7f0cda791a..e2dbba8aff 100644 +--- a/js/gdm/authServices.js ++++ b/js/gdm/authServices.js +@@ -98,6 +98,10 @@ export class AuthServices extends GObject.Object { + return this.constructor.SupportedRoles; + } + ++ get unsupportedRoles() { ++ return this._handleGetUnsupportedRoles(); ++ } ++ + selectChoice(serviceName, key) { + this._handleSelectChoice(serviceName, key); + } +@@ -173,6 +177,18 @@ export class AuthServices extends GObject.Object { + this._handleClear(); + } + ++ updateEnabledRoles(roles) { ++ if (this._enabledRoles.length === roles.length && ++ this._enabledRoles.every(r => roles.includes(r))) ++ return false; ++ ++ this._enabledRoles = roles; ++ ++ this._handleUpdateEnabledRoles(); ++ ++ return true; ++ } ++ + _clearUserVerifier() { + this._disconnectUserVerifierSignals(); + this._userVerifier = null; +@@ -416,6 +432,10 @@ export class AuthServices extends GObject.Object { + } + } + ++ _handleGetUnsupportedRoles() { ++ return []; ++ } ++ + _handleSelectChoice() {} + + async _handleAnswerQuery() {} +@@ -434,6 +454,8 @@ export class AuthServices extends GObject.Object { + + _handleClear() {} + ++ _handleUpdateEnabledRoles() {} ++ + _handleUpdateEnabledMechanisms() { + throw new GObject.NotImplementedError( + `_handleUpdateEnabledMechanisms in ${this.constructor.name}`); +diff --git a/js/gdm/authServicesLegacy.js b/js/gdm/authServicesLegacy.js +index 68a3c6990a..0813622a01 100644 +--- a/js/gdm/authServicesLegacy.js ++++ b/js/gdm/authServicesLegacy.js +@@ -141,6 +141,11 @@ export class AuthServicesLegacy extends AuthServices { + this.emit('mechanisms-changed'); + } + ++ _handleUpdateEnabledRoles() { ++ this._selectedMechanism = null; ++ this._updateEnabledMechanisms(); ++ } ++ + _handleUpdateEnabledMechanisms() { + if (!this._fingerprintManager?.readerFound) { + this._enabledMechanisms.push(...Mechanisms.filter(m => +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +index 5f89478ff9..9649dd1382 100644 +--- a/js/gdm/authServicesSSSDSwitchable.js ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -4,6 +4,12 @@ import * as Const from './const.js'; + import * as Util from './util.js'; + import {AuthServices} from './authServices.js'; + ++const MechanismsStatus = { ++ WAITING: 0, ++ NOT_FOUND: 1, ++ FOUND: 2, ++}; ++ + export class AuthServicesSSSDSwitchable extends AuthServices { + static [GObject.GTypeName] = 'AuthServicesSSSDSwitchable'; + +@@ -21,6 +27,8 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + + constructor(params) { + super(params); ++ ++ this._mechanismsStatus = MechanismsStatus.WAITING; + } + + async _handleAnswerQuery(serviceName, answer) { +@@ -53,13 +61,30 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + } + ++ _handleGetUnsupportedRoles() { ++ // Until we know the mechanisms or wait for them, ++ // consider supportedRoles as supported ++ switch (this._mechanismsStatus) { ++ case MechanismsStatus.WAITING: ++ case MechanismsStatus.FOUND: ++ return this._enabledRoles.filter(r => !this.supportedRoles.includes(r)); ++ case MechanismsStatus.NOT_FOUND: ++ return this._enabledRoles; ++ default: ++ throw new GObject.NotImplementedError(`invalid MechanismStatus: ${this._mechanismsStatus}`); ++ } ++ } ++ + _handleReset() { + this._savedMechanism = null; ++ this._mechanismsStatus = MechanismsStatus.WAITING; + } + + _handleCancel() { +- if (this._selectedMechanism) ++ if (this._selectedMechanism) { + this._savedMechanism = this._selectedMechanism; ++ this._mechanismsStatus = MechanismsStatus.WAITING; ++ } + } + + _handleClear() { +@@ -89,6 +114,10 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + if (this._mechanisms) + this._updateEnabledMechanisms(); + } ++ ++ this._mechanismsStatus = authSelection ++ ? MechanismsStatus.FOUND ++ : MechanismsStatus.NOT_FOUND; + } + + _handleUpdateEnabledMechanisms() { +@@ -113,6 +142,9 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + + _handleOnInfo(serviceName, info) { ++ if (!this._eventExpected()) ++ return; ++ + // sssd can't inform about expired password from JSON so it's needed + // to check the info message and handle the reset using the old flow + if (serviceName === this._selectedMechanism?.serviceName && +@@ -125,6 +157,9 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + + _handleOnProblem(serviceName, problem) { ++ if (!this._eventExpected()) ++ return; ++ + if (serviceName === this._selectedMechanism?.serviceName) { + this.emit('queue-priority-message', + serviceName, +@@ -133,7 +168,16 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + } + ++ _handleOnInfoQuery() { ++ if (!this._eventExpected()) ++ // eslint-disable-next-line no-useless-return ++ return; ++ } ++ + _handleOnSecretInfoQuery(serviceName, secretQuestion) { ++ if (!this._eventExpected()) ++ return; ++ + if (serviceName === this._selectedMechanism?.serviceName && + this._selectedMechanism.role === Const.PASSWORD_ROLE_NAME && + this._resettingPassword) +@@ -153,7 +197,7 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + + _handleCanStartService(serviceName) { + return serviceName === Const.SWITCHABLE_AUTH_SERVICE_NAME && +- !this._enabledMechanisms; ++ this._mechanismsStatus === MechanismsStatus.WAITING; + } + + _formatResponse(answer) { +@@ -184,6 +228,20 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + serviceName, JSON.stringify(response), this._cancellable, null); + } + ++ _eventExpected() { ++ // If legacy PAM messages are received before receiving JSON PAM ++ // messages informing about mechanisms, then pam_unix is being ++ // used and the user is not supported by pam_sss using JSON. ++ // Fallback to legacy authentication services. ++ if (this._mechanismsStatus === MechanismsStatus.WAITING) { ++ this._mechanismsStatus = MechanismsStatus.NOT_FOUND; ++ this.emit('mechanisms-changed'); ++ return false; ++ } ++ ++ return true; ++ } ++ + _startPasswordLogin() { + const {serviceName, prompt} = this._selectedMechanism; + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index ab4eccec42..36a55822cc 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -197,6 +197,11 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this._authServicesSSSDSwitchable?.clear(); + this._authServicesLegacy?.clear(); + ++ if (this._authServicesSSSDSwitchable) { ++ this._authServicesLegacy?.updateEnabledRoles( ++ this._authServicesSSSDSwitchable.unsupportedRoles); ++ } ++ + this._clearMessageQueue(); + + this._cancellable?.cancel(); +@@ -408,10 +413,14 @@ export class ShellUserVerifier extends Signals.EventEmitter { + reauthOnly: this._reauthOnly, + }; + if (this._switchableAuthenticationEnabled && +- AuthServicesSSSDSwitchable.supportsAny(this._enabledRoles)) ++ AuthServicesSSSDSwitchable.supportsAny(this._enabledRoles)) { + this._authServicesSSSDSwitchable = new AuthServicesSSSDSwitchable(params); +- else if (AuthServicesLegacy.supportsAny(this._enabledRoles)) ++ ++ params.enabledRoles = this._authServicesSSSDSwitchable.unsupportedRoles; + this._authServicesLegacy = new AuthServicesLegacy(params); ++ } else if (AuthServicesLegacy.supportsAny(this._enabledRoles)) { ++ this._authServicesLegacy = new AuthServicesLegacy(params); ++ } + + this._connectAuthServices(); + } +@@ -452,9 +461,12 @@ export class ShellUserVerifier extends Signals.EventEmitter { + } + + _onMechanismsChanged() { +- const mechanismsSwitchable = this._authServicesSSSDSwitchable?.enabledMechanisms ?? []; ++ if (this._enableFallbackMechanisms()) ++ return; ++ ++ const mechanismsSSSDSwitchable = this._authServicesSSSDSwitchable?.enabledMechanisms ?? []; + const mechanismsLegacy = this._authServicesLegacy?.enabledMechanisms ?? []; +- const mechanisms = [...mechanismsSwitchable, ...mechanismsLegacy]; ++ const mechanisms = [...mechanismsSSSDSwitchable, ...mechanismsLegacy]; + + const selectedMechanism = + this._authServicesSSSDSwitchable?.selectedMechanism ?? +@@ -465,6 +477,14 @@ export class ShellUserVerifier extends Signals.EventEmitter { + this.emit('mechanisms-changed', mechanisms, selectedMechanism); + } + ++ _enableFallbackMechanisms() { ++ if (!this._authServicesSSSDSwitchable || !this._authServicesLegacy) ++ return false; ++ ++ return this._authServicesLegacy.updateEnabledRoles( ++ this._authServicesSSSDSwitchable.unsupportedRoles); ++ } ++ + async _waitPendingMessages(waiter) { + try { + await this._handlePendingMessages(); +-- +2.53.0 + + +From e3e3917d895f852c286bde7030365f25c6c024a2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Wed, 11 Feb 2026 03:36:54 +0100 +Subject: [PATCH 39/42] ui/qrCode: Add a QR Code widget + +The widget can only have a squared size that is picked using the maximum +between the provided width ad height properties, using the minimum of +the space available. + +The qr code is always generated using the minimum sized image with a +transparent background (so that the widget background color is used) +while the foreground is colored following the theme. + +The texture is then draw using the nearest filter so that it will adjust +to the actor size without the need to regenerate it on size changes +theme +--- + data/theme/gnome-shell-sass/_widgets.scss | 1 + + .../gnome-shell-sass/widgets/_qr-code.scss | 14 +++ + data/theme/meson.build | 1 + + js/js-resources.gresource.xml | 1 + + js/misc/dependencies.js | 1 + + js/ui/qrCode.js | 119 ++++++++++++++++++ + 6 files changed, 137 insertions(+) + create mode 100644 data/theme/gnome-shell-sass/widgets/_qr-code.scss + create mode 100644 js/ui/qrCode.js + +diff --git a/data/theme/gnome-shell-sass/_widgets.scss b/data/theme/gnome-shell-sass/_widgets.scss +index 9eac62d958..1e3fefa9d7 100644 +--- a/data/theme/gnome-shell-sass/_widgets.scss ++++ b/data/theme/gnome-shell-sass/_widgets.scss +@@ -45,5 +45,6 @@ + @import 'widgets/misc'; + @import 'widgets/keyboard'; + @import 'widgets/looking-glass'; ++@import 'widgets/qr-code'; + // Lock / login screen + @import 'widgets/login-lock'; +diff --git a/data/theme/gnome-shell-sass/widgets/_qr-code.scss b/data/theme/gnome-shell-sass/widgets/_qr-code.scss +new file mode 100644 +index 0000000000..da04425bd6 +--- /dev/null ++++ b/data/theme/gnome-shell-sass/widgets/_qr-code.scss +@@ -0,0 +1,14 @@ ++// QR Code ++.qr-code { ++ border-radius: $base_border_radius * .5; ++ border-width: 1em; ++ @if ($variant == 'light') { ++ $qrcode_bg_color: mix($fg_color, $bg_color, 8%); ++ background-color: $qrcode_bg_color; ++ border-color: $qrcode_bg_color; ++ } @else { ++ background-color: $system_fg_color; ++ border-color: $system_fg_color; ++ color: $system_bg_color; ++ } ++} +diff --git a/data/theme/meson.build b/data/theme/meson.build +index 8d01ae826d..d6f2d8e802 100644 +--- a/data/theme/meson.build ++++ b/data/theme/meson.build +@@ -28,6 +28,7 @@ theme_sources = files([ + 'gnome-shell-sass/widgets/_overview.scss', + 'gnome-shell-sass/widgets/_panel.scss', + 'gnome-shell-sass/widgets/_popovers.scss', ++ 'gnome-shell-sass/widgets/_qr-code.scss', + 'gnome-shell-sass/widgets/_quick-settings.scss', + 'gnome-shell-sass/widgets/_screenshot.scss', + 'gnome-shell-sass/widgets/_scrollbars.scss', +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index a7bcbd8898..59a428df9a 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -108,6 +108,7 @@ + ui/pointerWatcher.js + ui/popupMenu.js + ui/quickSettings.js ++ ui/qrCode.js + ui/remoteSearch.js + ui/ripples.js + ui/runDialog.js +diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js +index c3c51aa2de..fbb83d1164 100644 +--- a/js/misc/dependencies.js ++++ b/js/misc/dependencies.js +@@ -16,6 +16,7 @@ import 'gi://GioUnix?version=2.0'; + import 'gi://GDesktopEnums?version=3.0'; + import 'gi://GdkPixbuf?version=2.0'; + import 'gi://GnomeBG?version=4.0'; ++import 'gi://GnomeQR?version=4.0'; + import 'gi://GnomeDesktop?version=4.0'; + import 'gi://Graphene?version=1.0'; + import 'gi://GUdev?version=1.0'; +diff --git a/js/ui/qrCode.js b/js/ui/qrCode.js +new file mode 100644 +index 0000000000..5f2bb85557 +--- /dev/null ++++ b/js/ui/qrCode.js +@@ -0,0 +1,119 @@ ++import Clutter from 'gi://Clutter'; ++import Cogl from 'gi://Cogl'; ++import GObject from 'gi://GObject'; ++import Gio from 'gi://Gio'; ++import GnomeQR from 'gi://GnomeQR'; ++import St from 'gi://St'; ++import {logErrorUnlessCancelled} from '../misc/errorUtils.js'; ++ ++Gio._promisify(GnomeQR, 'generate_qr_code_async'); ++ ++const QR_CODE_DEFAULT_SIZE = 150; ++const QR_CODE_TRANSPARENT_COLOR = new GnomeQR.Color({alpha: 0}); ++ ++export class QrCode extends St.Bin { ++ static [GObject.GTypeName] = 'QrCode'; ++ static [GObject.properties] = { ++ 'url': GObject.ParamSpec.string( ++ 'url', null, null, ++ GObject.ParamFlags.READWRITE, ++ null), ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(params) { ++ const qrSize = Math.max(params.width ?? 0, params.height ?? 0) || ++ QR_CODE_DEFAULT_SIZE; ++ ++ super({ ++ styleClass: 'qr-code', ++ width: qrSize, ++ height: qrSize, ++ ...params, ++ }); ++ ++ this.set_child(new Clutter.Actor({ ++ xExpand: true, ++ yExpand: true, ++ xAlign: Clutter.ActorAlign.FILL, ++ yAlign: Clutter.ActorAlign.FILL, ++ minificationFilter: Clutter.ScalingFilter.NEAREST, ++ magnificationFilter: Clutter.ScalingFilter.NEAREST, ++ })); ++ ++ this._fgColor = null; ++ ++ this.connect('notify::url', () => ++ this._update().catch(logErrorUnlessCancelled)); ++ }; ++ ++ vfunc_allocate(box) { ++ const width = box.get_width(); ++ const height = box.get_height(); ++ ++ box.set_size(Math.min(width, height), Math.min(width, height)); ++ super.vfunc_allocate(box); ++ } ++ ++ vfunc_style_changed() { ++ super.vfunc_style_changed(); ++ ++ if (!this.get_parent()) ++ return; ++ ++ const node = this.get_theme_node(); ++ const fgColor = node.get_foreground_color(); ++ ++ if (!this._fgColor?.equal(fgColor)) { ++ this._fgColor = fgColor; ++ this._update().catch(logErrorUnlessCancelled); ++ } ++ } ++ ++ async _update() { ++ const fgColor = QrCode.#getGnomeQRColor(this._fgColor); ++ ++ this._cancellable?.cancel(); ++ const cancellable = new Gio.Cancellable(); ++ this._cancellable = cancellable; ++ ++ const [pixelData, qrSize] = await GnomeQR.generate_qr_code_async( ++ this.url, ++ 0 /* size, are fine with the minimum value */, ++ QR_CODE_TRANSPARENT_COLOR, ++ fgColor, ++ GnomeQR.PixelFormat.RGBA_8888, ++ GnomeQR.EccLevel.LOW, ++ cancellable); ++ ++ const coglContext = global.stage.context.get_backend().get_cogl_context(); ++ const bpp = Cogl.pixel_format_get_bytes_per_pixel(Cogl.PixelFormat.RGBA_8888, 0); ++ const rowStride = qrSize * bpp; ++ ++ const content = St.ImageContent.new_with_preferred_size(qrSize, qrSize); ++ content.set_bytes( ++ coglContext, ++ pixelData, ++ Cogl.PixelFormat.RGBA_8888, ++ qrSize, ++ qrSize, ++ rowStride); ++ ++ this.child.set_content(content); ++ } ++ ++ static #getGnomeQRColor(color) { ++ if (!color) ++ return null; ++ ++ return new GnomeQR.Color({ ++ red: color.red, ++ green: color.green, ++ blue: color.blue, ++ alpha: color.alpha, ++ }); ++ } ++}; +-- +2.53.0 + + +From d056723159604b6c251e273972599250bedc3897 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 6 Feb 2024 14:18:24 -0500 +Subject: [PATCH 40/42] gdm: Add support for Web Login in + authServicesSSSDSwitchable +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +SSSD supports Web Login via a url on an external device. It +advertises this support over the 'auth-selection' JSON protocol. + +This commit adds Web Login support by showing the new WebLoginDialog that +shows an url and a QR code. + +Use the bindings of the new GnomeQR library to generate the QR code. + +Co-authored-by: Marco Trevisan (Treviño) +--- + .../gnome-shell-sass/widgets/_login-lock.scss | 100 ++++++ + js/gdm/authPrompt.js | 104 +++++- + js/gdm/authServices.js | 6 + + js/gdm/authServicesSSSDSwitchable.js | 82 +++++ + js/gdm/const.js | 1 + + js/gdm/loginDialog.js | 5 +- + js/gdm/util.js | 5 + + js/gdm/webLogin.js | 310 ++++++++++++++++++ + js/js-resources.gresource.xml | 1 + + js/ui/unlockDialog.js | 3 +- + js/ui/userWidget.js | 8 + + po/POTFILES.in | 1 + + 12 files changed, 614 insertions(+), 12 deletions(-) + create mode 100644 js/gdm/webLogin.js + +diff --git a/data/theme/gnome-shell-sass/widgets/_login-lock.scss b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +index c18a6bb8a8..ec76f01252 100644 +--- a/data/theme/gnome-shell-sass/widgets/_login-lock.scss ++++ b/data/theme/gnome-shell-sass/widgets/_login-lock.scss +@@ -15,6 +15,10 @@ $_gdm_dialog_width: 25em; + .login-dialog-prompt-layout { + width: $_gdm_dialog_width; + spacing: $base_padding * 1.5; ++ ++ &.web-login-active { ++ width: $_gdm_dialog_width * 1.5; ++ } + } + + .login-dialog-prompt-entry-area { +@@ -324,8 +328,88 @@ $_gdm_dialog_width: 25em; + } + } + ++.web-login-spinner { ++ background-color: rgba(0, 0, 0, 0.5); ++ border: 5px rgba(0, 0, 0, 0.0); ++ border-radius: 50px; ++} ++ ++.web-login-title-label { ++ @include fontsize($base_font_size); ++ color: darken($_gdm_fg,30%); ++ text-align: center; ++} ++ ++.web-login-url-label { ++ @include fontsize($base_font_size); ++ @extend %monospace; ++ color: $_gdm_fg; ++ text-align: center; ++ ++ &.web-login-url-label-long { ++ @include fontsize($base_font_size - 2); ++ } ++} ++ ++.web-login-code-title-label { ++ @include fontsize($base_font_size); ++ color: $_gdm_fg; ++ text-align: center; ++} ++ ++.web-login-code-label { ++ @include fontsize($base_font_size); ++ color: $_gdm_fg; ++ font-weight: bold; ++ text-align: center; ++} ++ ++.web-login-prompt { ++ padding-top: $base_padding; ++ padding-bottom: $base_padding; ++ padding-left: $base_padding * 4.5; ++ padding-right: $base_padding * 4.5; ++ spacing: 1.75em; ++} ++ ++.web-login-button-label { ++ @include fontsize($base_font_size + 2); ++ color: $_gdm_fg; ++ min-width: 12em; ++ text-align: center; ++ font-weight: bold; ++} ++ ++@mixin web-login-buttons($context){ ++ .web-login-intro-button, ++ .web-login-prompt-button { ++ @if $context == 'login' { @include login_dialog_item_button(); } ++ @if $context == 'unlock' { @extend %lockscreen_button; } ++ border-radius: $base_border_radius * 4; ++ } ++ ++ .web-login-intro-button { ++ padding: 0; ++ } ++ ++ .web-login-prompt-button { ++ padding: $base_padding * 2.5 $base_padding * 4; ++ margin: $base_margin * 6 $base_margin * 2; ++ width: 8em; ++ } ++} ++ ++.login-dialog { ++ @include web-login-buttons(login); ++} ++ ++.unlock-dialog { ++ @include web-login-buttons(unlock); ++} ++ + // Screen Shield + // a.k.a. the lockscreen, uses transparent styles ++ + .unlock-dialog { + background-color: transparent; + +@@ -483,3 +567,19 @@ $_gdm_dialog_width: 25em; + } + } + } ++ ++// QR Code ++.qr-code { ++ border-radius: $base_border_radius * .5; ++ border-width: 1em; ++ @if ($variant == 'light') { ++ $qrcode_bg_color: mix($fg_color, $bg_color, 8%); ++ background-color: $qrcode_bg_color; ++ border-color: $qrcode_bg_color; ++ color: $fg_color; ++ } @else { ++ background-color: $_gdm_fg; ++ border-color: $_gdm_fg; ++ color: $_gdm_bg; ++ } ++} +diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js +index d8c7c6409d..cdad827764 100644 +--- a/js/gdm/authPrompt.js ++++ b/js/gdm/authPrompt.js +@@ -14,6 +14,7 @@ import * as GdmUtil from './util.js'; + import * as Params from '../misc/params.js'; + import * as ShellEntry from '../ui/shellEntry.js'; + import * as UserWidget from '../ui/userWidget.js'; ++import * as WebLogin from './webLogin.js'; + import {wiggle} from '../misc/animationUtils.js'; + + const DEFAULT_BUTTON_WELL_ICON_SIZE = 16; +@@ -88,6 +89,7 @@ export const AuthPrompt = GObject.registerClass({ + 'show-message', this._onShowMessage.bind(this), + 'show-choice-list', this._onShowChoiceList.bind(this), + 'mechanisms-changed', (_, ...args) => this.emit('mechanisms-changed', ...args), ++ 'web-login', this._onWebLogin.bind(this), + 'verification-failed', this._onVerificationFailed.bind(this), + 'verification-complete', this._onVerificationComplete.bind(this), + 'reset', this._onReset.bind(this), +@@ -305,6 +307,28 @@ export const AuthPrompt = GObject.registerClass({ + this._defaultButtonWell.add_child(this._spinner); + + this.setActorInDefaultButtonWell(this._nextButton); ++ ++ this._webLoginIntro = new WebLogin.WebLoginIntro(); ++ this._webLoginIntro.set({ ++ y_expand: true, ++ }); ++ this._webLoginIntro.connect('clicked', () => { ++ this._webLoginIntro.hide(); ++ this._openWebLoginDialog(); ++ }); ++ this._mainBox.add_child(this._webLoginIntro); ++ ++ this._webLoginDialog = new WebLogin.WebLoginDialog(); ++ this._webLoginDialog.connect('cancel', () => { ++ if (this._webLoginDialog.isLoading) { ++ this.reset({softReset: true}); ++ } else { ++ this._closeWebLoginDialog(); ++ this._fadeInElement(this._webLoginIntro); ++ } ++ }); ++ this._webLoginDialog.connect('loading', () => this.emit('loading', this._webLoginDialog.isLoading)); ++ this.add_child(this._webLoginDialog); + } + + _updateCancelButton() { +@@ -441,6 +465,51 @@ export const AuthPrompt = GObject.registerClass({ + this.emit('prompted'); + } + ++ _onWebLogin(_, serviceName, introMessage, message, url, code, buttons) { ++ if (this._queryingService) ++ this.clear(); ++ ++ this._queryingService = serviceName; ++ this._promptStep++; ++ this._updateCancelButton(); ++ ++ this._webLoginParams = {message, url, code, buttons}; ++ ++ this._entryArea.hide(); ++ ++ if (this._preemptiveAnswer) ++ this._preemptiveAnswer = null; ++ ++ if (!this._webLoginDialog.visible && introMessage) { ++ this._webLoginIntro.setMessage(introMessage); ++ this._fadeInElement(this._webLoginIntro); ++ } else { ++ this._openWebLoginDialog(); ++ } ++ ++ this.emit('prompted'); ++ } ++ ++ _closeWebLoginDialog() { ++ this._webLoginDialog.hide(); ++ ++ this._userWell.get_child()?.showAvatar(); ++ this._mainBox.show(); ++ ++ this.remove_style_class_name('web-login-active'); ++ } ++ ++ _openWebLoginDialog() { ++ this._userWell.get_child()?.hideAvatar(); ++ this._mainBox.hide(); ++ ++ this._webLoginDialog.update(this._webLoginParams); ++ if (!this._webLoginDialog.visible) ++ this._fadeInElement(this._webLoginDialog); ++ ++ this.add_style_class_name('web-login-active'); ++ } ++ + _onShowMessage(_userVerifier, serviceName, message, type) { + let wiggleParameters = {duration: 0}; + +@@ -461,7 +530,9 @@ export const AuthPrompt = GObject.registerClass({ + // show the entry area to allow getting a preemptive answer + if (message && + !this._entryArea.visible && +- !this._authList.visible) ++ !this._authList.visible && ++ !this._webLoginIntro.visible && ++ !this._webLoginDialog.visible) + this._fadeInElement(this._entryArea); + + this.emit('prompted'); +@@ -487,12 +558,14 @@ export const AuthPrompt = GObject.registerClass({ + this.stopSpinning(true); + this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED; + +- this._mainBox.reactive = false; +- this._mainBox.can_focus = false; +- this._mainBox.ease({ +- opacity: 0, +- duration: MESSAGE_FADE_OUT_ANIMATION_TIME, +- mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ [this._mainBox, this._webLoginDialog].forEach(widget => { ++ widget.reactive = false; ++ widget.can_focus = false; ++ widget.ease({ ++ opacity: 0, ++ duration: MESSAGE_FADE_OUT_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ }); + }); + + this.emit('verification-complete'); +@@ -564,6 +637,9 @@ export const AuthPrompt = GObject.registerClass({ + stopSpinning(animate) { + this.emit('loading', false); + this.setActorInDefaultButtonWell(this._nextButton, animate); ++ ++ if (this._webLoginDialog.isLoading) ++ this._webLoginDialog.stopLoading(); + } + + clear(params) { +@@ -581,10 +657,14 @@ export const AuthPrompt = GObject.registerClass({ + this._authListTitle.child.text = ''; + this._authList.clear(); + this._authList.hide(); ++ this._webLoginIntro.hide(); ++ this._closeWebLoginDialog(); + +- this._mainBox.opacity = 255; +- this._mainBox.reactive = true; +- this._mainBox.can_focus = true; ++ [this._mainBox, this._webLoginDialog].forEach(widget => { ++ widget.opacity = 255; ++ widget.reactive = true; ++ widget.can_focus = true; ++ }); + } + + setQuestion(question) { +@@ -596,6 +676,8 @@ export const AuthPrompt = GObject.registerClass({ + this._entry.hint_text = question; + + this._authList.hide(); ++ this._webLoginIntro.hide(); ++ this._closeWebLoginDialog(); + + this._fadeInElement(this._entryArea); + } +@@ -689,6 +771,8 @@ export const AuthPrompt = GObject.registerClass({ + updateSensitivity({sensitive}) { + const authWidget = [ + this._authList, ++ this._webLoginIntro, ++ this._webLoginDialog, + ].find(widget => widget.visible) ?? this._entry; + + if (authWidget.reactive === sensitive) +diff --git a/js/gdm/authServices.js b/js/gdm/authServices.js +index e2dbba8aff..1801fbdc3a 100644 +--- a/js/gdm/authServices.js ++++ b/js/gdm/authServices.js +@@ -45,6 +45,12 @@ export class AuthServices extends GObject.Object { + param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_JSOBJECT], + }, + 'mechanisms-changed': {}, ++ 'web-login': { ++ param_types: [ ++ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, ++ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_JSOBJECT, ++ ], ++ }, + }; + + static { +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +index 9649dd1382..98171b32c2 100644 +--- a/js/gdm/authServicesSSSDSwitchable.js ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -1,3 +1,4 @@ ++import GLib from 'gi://GLib'; + import GObject from 'gi://GObject'; + + import * as Const from './const.js'; +@@ -15,10 +16,12 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + + static SupportedRoles = [ + Const.PASSWORD_ROLE_NAME, ++ Const.WEB_LOGIN_ROLE_NAME, + ]; + + static RoleToService = { + [Const.PASSWORD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, ++ [Const.WEB_LOGIN_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + }; + + static { +@@ -58,6 +61,9 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + case Const.PASSWORD_ROLE_NAME: + this._startPasswordLogin(); + break; ++ case Const.WEB_LOGIN_ROLE_NAME: ++ this._startWebLogin(); ++ break; + } + } + +@@ -94,6 +100,8 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this._selectedMechanism = null; + + this._resettingPassword = false; ++ ++ this._clearWebLoginTimeout(); + } + + _handleOnCustomJSONRequest(_serviceName, _protocol, _version, json) { +@@ -130,6 +138,8 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + // filter out mechanisms with roles that are not enabled + .filter(m => this._enabledRoles.includes(m.role))); + ++ this._trackWebLoginTimeout(); ++ + const selectedMechanism = + this._enabledMechanisms + .find(m => this._savedMechanism?.role === m.role) ?? +@@ -141,6 +151,31 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this._savedMechanism = null; + } + ++ _trackWebLoginTimeout() { ++ this._clearWebLoginTimeout(); ++ ++ const webLoginMechanism = this._enabledMechanisms ++ .find(m => m.role === Const.WEB_LOGIN_ROLE_NAME); ++ if (!webLoginMechanism) ++ return; ++ ++ const {timeout} = webLoginMechanism; ++ if (!timeout) ++ return; ++ ++ this._webLoginTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, ++ timeout, () => { ++ if (this._selectedMechanism?.role !== Const.WEB_LOGIN_ROLE_NAME) ++ webLoginMechanism.needsRefresh = true; ++ else ++ this.emit('reset', {softReset: true}); ++ ++ this._webLoginTimeoutId = 0; ++ ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ + _handleOnInfo(serviceName, info) { + if (!this._eventExpected()) + return; +@@ -209,6 +244,10 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + response = {password: answer}; + break; + } ++ case Const.WEB_LOGIN_ROLE_NAME: { ++ response = {}; ++ break; ++ } + default: + throw new GObject.NotImplementedError(`formatResponse: ${role}`); + } +@@ -247,4 +286,47 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + + this.emit('ask-question', serviceName, prompt, true); + } ++ ++ _startWebLogin() { ++ const { ++ serviceName, ++ initPrompt, linkPrompt, ++ uri, code, needsRefresh, ++ } = this._selectedMechanism; ++ ++ if (!linkPrompt || !uri) ++ return; ++ ++ if (needsRefresh) { ++ this.emit('reset', {softReset: true}); ++ return; ++ } ++ ++ const buttons = [{ ++ default: true, ++ needsLoading: true, ++ label: _('Done'), ++ action: () => this._webLoginDone(), ++ }]; ++ ++ this.emit('web-login', serviceName, initPrompt, linkPrompt, uri, code, buttons); ++ } ++ ++ _webLoginDone() { ++ if (this._selectedMechanism?.role !== Const.WEB_LOGIN_ROLE_NAME) ++ return; ++ ++ const response = this._formatResponse(); ++ this._sendResponse(response); ++ ++ this._clearWebLoginTimeout(); ++ } ++ ++ _clearWebLoginTimeout() { ++ if (!this._webLoginTimeoutId) ++ return; ++ ++ GLib.source_remove(this._webLoginTimeoutId); ++ this._webLoginTimeoutId = 0; ++ } + } +diff --git a/js/gdm/const.js b/js/gdm/const.js +index 2f37446c8a..e8ca48625a 100644 +--- a/js/gdm/const.js ++++ b/js/gdm/const.js +@@ -3,6 +3,7 @@ + export const PASSWORD_ROLE_NAME = 'password'; + export const SMARTCARD_ROLE_NAME = 'smartcard'; + export const FINGERPRINT_ROLE_NAME = 'fingerprint'; ++export const WEB_LOGIN_ROLE_NAME = 'eidp'; + + export const PASSWORD_SERVICE_NAME = 'gdm-password'; + export const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; +diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js +index 4c08f0957e..11b578e38f 100644 +--- a/js/gdm/loginDialog.js ++++ b/js/gdm/loginDialog.js +@@ -746,7 +746,10 @@ export const LoginDialog = GObject.registerClass({ + let authPromptAllocation = null; + let authPromptWidth = 0; + if (this._authPrompt.visible) { +- authPromptAllocation = this._getFixedTopActorAllocation(dialogBox, this._authPrompt); ++ if (this._authPrompt.has_style_class_name('web-login-active')) ++ authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt); ++ else ++ authPromptAllocation = this._getFixedTopActorAllocation(dialogBox, this._authPrompt); + authPromptWidth = authPromptAllocation.x2 - authPromptAllocation.x1; + } + +diff --git a/js/gdm/util.js b/js/gdm/util.js +index 36a55822cc..fe2216f8c7 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -17,6 +17,7 @@ export const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; + export const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + export const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; + export const SWITCHABLE_AUTHENTICATION_KEY = 'enable-switchable-authentication'; ++export const WEB_AUTHENTICATION_KEY = 'enable-web-authentication'; + export const BANNER_MESSAGE_KEY = 'banner-message-enable'; + export const BANNER_MESSAGE_SOURCE_KEY = 'banner-message-source'; + export const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; +@@ -94,6 +95,7 @@ export function isSelectable(mechanism) { + switch (mechanism.role) { + case Const.PASSWORD_ROLE_NAME: + case Const.SMARTCARD_ROLE_NAME: ++ case Const.WEB_LOGIN_ROLE_NAME: + return true; + case Const.FINGERPRINT_ROLE_NAME: + return false; +@@ -389,6 +391,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + enabledRoles.push(Const.SMARTCARD_ROLE_NAME); + if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) + enabledRoles.push(Const.FINGERPRINT_ROLE_NAME); ++ if (this._settings.get_boolean(WEB_AUTHENTICATION_KEY)) ++ enabledRoles.push(Const.WEB_LOGIN_ROLE_NAME); + + const switchableAuthentication = + this._settings.get_boolean(SWITCHABLE_AUTHENTICATION_KEY); +@@ -451,6 +455,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { + 'reset', (_, ...args) => this.emit('reset', ...args), + 'show-choice-list', (_, ...args) => this.emit('show-choice-list', ...args), + 'mechanisms-changed', (_, ...args) => this._onMechanismsChanged(...args), ++ 'web-login', (_, ...args) => this.emit('web-login', ...args), + this); + }); + } +diff --git a/js/gdm/webLogin.js b/js/gdm/webLogin.js +new file mode 100644 +index 0000000000..69717260fc +--- /dev/null ++++ b/js/gdm/webLogin.js +@@ -0,0 +1,310 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++// ++// A widget showing a URL for web login ++/* exported WebLoginPrompt */ ++ ++import Clutter from 'gi://Clutter'; ++import GObject from 'gi://GObject'; ++import Pango from 'gi://Pango'; ++import St from 'gi://St'; ++import {Spinner} from '../ui/animation.js'; ++import * as Params from '../misc/params.js'; ++import {QrCode} from '../ui/qrCode.js'; ++ ++const QR_CODE_SIZE = 150; ++const WEB_LOGIN_SPINNER_SIZE = 35; ++ ++export const WebLoginPrompt = GObject.registerClass( ++class WebLoginPrompt extends St.BoxLayout { ++ _init(params) { ++ const {qrSize: qrCodeSize, message, url, code} = Params.parse(params, { ++ qrSize: QR_CODE_SIZE, ++ message: null, ++ url: null, ++ code: null, ++ }); ++ ++ super._init({ ++ styleClass: 'web-login-prompt', ++ vertical: true, ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ ++ this._urlTitleLabel = new St.Label({ ++ style_class: 'web-login-title-label', ++ }); ++ this._urlTitleLabel.clutterText.set({ ++ lineWrap: true, ++ ellipsize: Pango.EllipsizeMode.NONE, ++ }); ++ this.add_child(this._urlTitleLabel); ++ ++ this._qrCode = new QrCode({ ++ width: qrCodeSize, ++ height: qrCodeSize, ++ xAlign: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(this._qrCode); ++ ++ this._urlLabel = new St.Label({ ++ style_class: 'web-login-url-label', ++ x_expand: true, ++ }); ++ this.add_child(this._urlLabel); ++ ++ this._codeBox = new St.BoxLayout({ ++ x_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ ++ this._codeTitleLabel = new St.Label({ ++ text: _('Login code: '), ++ style_class: 'web-login-code-title-label', ++ }); ++ this._codeBox.add_child(this._codeTitleLabel); ++ ++ this._codeLabel = new St.Label({ ++ style_class: 'web-login-code-label', ++ }); ++ this._codeBox.add_child(this._codeLabel); ++ ++ this.update({message, url, code}); ++ } ++ ++ update(params) { ++ const {message, url, code} = Params.parse(params, { ++ message: null, ++ url: null, ++ code: null, ++ }); ++ ++ if (message !== null) ++ this._urlTitleLabel.text = message; ++ ++ if (url !== null) { ++ this._qrCode.set({url}); ++ ++ const formattedUrl = this._formatURLForDisplay(url); ++ this._urlLabel.text = formattedUrl; ++ ++ if (formattedUrl.length > 45) ++ this._urlLabel.add_style_class_name('web-login-url-label-long'); ++ else ++ this._urlLabel.remove_style_class_name('web-login-url-label-long'); ++ } ++ ++ if (code !== null) { ++ this._codeLabel.text = code; ++ if (!this._codeBox.get_parent()) ++ this.add_child(this._codeBox); ++ } else { ++ // eslint-disable-next-line no-lonely-if ++ if (this._codeBox.get_parent()) ++ this.remove_child(this._codeBox); ++ } ++ } ++ ++ _formatURLForDisplay(url) { ++ const http = 'http://'; ++ const https = 'https://'; ++ ++ if (!url) ++ return http; ++ ++ if (url.endsWith('/')) ++ url = url.substring(0, url.length - 1); ++ ++ if (url.startsWith(http)) ++ return url.substring(http.length); ++ ++ if (url.startsWith(https)) ++ return url.substring(https.length); ++ ++ return url; ++ } ++}); ++ ++export const WebLoginDialog = GObject.registerClass({ ++ Signals: { ++ 'cancel': {}, ++ 'loading': {}, ++ }, ++}, class WebLoginDialog extends St.Widget { ++ _init(params) { ++ const {message, url, code, buttons} = Params.parse(params, { ++ message: null, ++ url: null, ++ code: null, ++ buttons: [], ++ }); ++ ++ super._init({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true, ++ visible: false, ++ }); ++ this.bind_property_full('reactive', ++ this, 'opacity', ++ GObject.BindingFlags.SYNC_CREATE, ++ (_bind, source) => [true, source ? 255 : 128], ++ null); ++ ++ this._contentBox = new St.BoxLayout({ ++ orientation: Clutter.Orientation.VERTICAL, ++ x_expand: true, ++ y_expand: true, ++ }); ++ this.add_child(this._contentBox); ++ ++ this._webLoginPrompt = new WebLoginPrompt({code, message, url}); ++ this._contentBox.add_child(this._webLoginPrompt); ++ ++ this._buttonBox = new St.BoxLayout({ ++ orientation: Clutter.Orientation.HORIZONTAL, ++ x_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ this._contentBox.add_child(this._buttonBox); ++ ++ this._updateButtons(buttons); ++ ++ this._spinnerFrame = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ style_class: 'web-login-spinner', ++ }); ++ this.add_child(this._spinnerFrame); ++ ++ this._spinner = new Spinner(WEB_LOGIN_SPINNER_SIZE, { ++ hideOnStop: true, ++ }); ++ this._spinnerFrame.add_child(this._spinner); ++ } ++ ++ _updateButtons(buttons) { ++ this._buttonBox.get_children().forEach(b => b.disconnectObject(this)); ++ this._buttonBox.remove_all_children(); ++ ++ this._cancelButton = this._addButton({ ++ label: _('Cancel'), ++ action: () => this.emit('cancel'), ++ }); ++ ++ buttons?.forEach(b => this._addButton(b)); ++ } ++ ++ _addButton(b) { ++ const button = new St.Button({ ++ style_class: 'web-login-prompt-button', ++ can_focus: true, ++ accessible_name: b.label, ++ child: new St.Label({ ++ text: b.label, ++ style_class: 'web-login-button-label', ++ }), ++ }); ++ ++ button.connectObject('clicked', () => { ++ if (b.needsLoading) ++ this._startLoading(); ++ b.action(); ++ }, this); ++ ++ button.default = b.default; ++ ++ this._buttonBox.add_child(button); ++ ++ this.bind_property('reactive', ++ button, 'reactive', ++ GObject.BindingFlags.SYNC_CREATE); ++ this.bind_property('reactive', ++ button, 'can_focus', ++ GObject.BindingFlags.SYNC_CREATE); ++ ++ return button; ++ } ++ ++ vfunc_key_focus_in() { ++ this._buttonBox.get_children().find(b => b.default)?.grab_key_focus(); ++ } ++ ++ update(params) { ++ const {message, url, code, buttons} = Params.parse(params, { ++ message: null, ++ url: null, ++ code: null, ++ buttons: [], ++ }); ++ ++ this.stopLoading(); ++ ++ this._webLoginPrompt.update({message, url, code}); ++ ++ this._updateButtons(buttons); ++ } ++ ++ _startLoading() { ++ this._webLoginPrompt.opacity = 128; ++ ++ this._buttonBox.get_children() ++ .filter(b => b !== this._cancelButton) ++ .forEach(b => { ++ b.opacity = 128; ++ b.can_focus = false; ++ b.reactive = false; ++ }); ++ this._cancelButton.grab_key_focus(); ++ ++ this._spinnerFrame.show(); ++ this._spinner.play(); ++ ++ this.isLoading = true; ++ this.emit('loading'); ++ } ++ ++ stopLoading() { ++ this._webLoginPrompt.opacity = 255; ++ ++ this._buttonBox.get_children() ++ .filter(b => b !== this._cancelButton) ++ .forEach(b => { ++ b.opacity = 255; ++ b.can_focus = this.reactive; ++ b.reactive = this.reactive; ++ }); ++ ++ this._spinnerFrame.hide(); ++ this._spinner.stop(); ++ ++ this.isLoading = false; ++ this.emit('loading'); ++ } ++}); ++ ++export var WebLoginIntro = GObject.registerClass( ++class WebLoginIntro extends St.Button { ++ _init(params) { ++ const {message} = Params.parse(params, { ++ message: null, ++ }); ++ ++ const label = new St.Label({ ++ text: message, ++ style_class: 'web-login-button-label', ++ }); ++ ++ super._init({ ++ style_class: 'web-login-intro-button', ++ accessible_name: message, ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ can_focus: true, ++ child: label, ++ }); ++ } ++ ++ setMessage(message) { ++ this.child.text = message; ++ this.accessible_name = message; ++ } ++}); +diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml +index 59a428df9a..85d8cc5c90 100644 +--- a/js/js-resources.gresource.xml ++++ b/js/js-resources.gresource.xml +@@ -15,6 +15,7 @@ + gdm/realmd.js + gdm/util.js + gdm/vmware.js ++ gdm/webLogin.js + + extensions/extension.js + extensions/sharedInternals.js +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index a16f68fee3..8a824f700a 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -494,7 +494,8 @@ class UnlockDialogLayout extends Clutter.LayoutManager { + // Authentication Box + const dialog = container.get_parent(); + let stackY; +- if (dialog._activePage === dialog._clock) { ++ if (dialog._activePage === dialog._clock || ++ dialog._authPrompt?.has_style_class_name('web-login-active')) { + stackY = Math.min( + Math.floor(centerY - stackHeight / 2.0), + height - stackHeight - maxNotificationsHeight); +diff --git a/js/ui/userWidget.js b/js/ui/userWidget.js +index 0331a1d328..e0ffd32278 100644 +--- a/js/ui/userWidget.js ++++ b/js/ui/userWidget.js +@@ -215,4 +215,12 @@ class UserWidget extends St.BoxLayout { + _updateUser() { + this._avatar.update(); + } ++ ++ hideAvatar() { ++ this._avatar?.hide(); ++ } ++ ++ showAvatar() { ++ this._avatar?.show(); ++ } + }); +diff --git a/po/POTFILES.in b/po/POTFILES.in +index d1e4d64872..9a7f8db8ea 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -15,6 +15,7 @@ js/gdm/authServicesLegacy.js + js/gdm/authServicesSSSDSwitchable.js + js/gdm/loginDialog.js + js/gdm/util.js ++js/gdm/webLogin.js + js/misc/breakManager.js + js/misc/brightnessManager.js + js/misc/systemActions.js +-- +2.53.0 + + +From 675ebdd383025c8b3a7ee76dbe81fb0f5e6353e4 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 14 Jan 2025 07:31:59 -0500 +Subject: [PATCH 41/42] gdm: Add support for Smartcard in + authServicesSSSDSwitchable + +This allows selecting smartcard as a login method. + +While SSSD can inform for each user what mechanisms are available, it +can't know if a user has smartcard mechanism available until a valid smartcard +is inserted. To allow smartcard mechanism, if it's enabled in GDM, add an empty +smartcard mechanism for every user. + +When a smartcard is inserted, authServicesSSSDSwitchable will be restarted to +check if a cert is available for the current user. This won't change the +authentication state (if it was doing password auth, that won't change). + +To avoid multiple events, SmartcardManager won't connect to tokens with +'/login_token', those are aliases for already connected tokens. +--- + js/gdm/authServicesSSSDSwitchable.js | 70 ++++++++++++++++++++++++++++ + js/misc/smartcardManager.js | 6 +++ + 2 files changed, 76 insertions(+) + +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +index 98171b32c2..b0c3583786 100644 +--- a/js/gdm/authServicesSSSDSwitchable.js ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -16,11 +16,13 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + + static SupportedRoles = [ + Const.PASSWORD_ROLE_NAME, ++ Const.SMARTCARD_ROLE_NAME, + Const.WEB_LOGIN_ROLE_NAME, + ]; + + static RoleToService = { + [Const.PASSWORD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, ++ [Const.SMARTCARD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + [Const.WEB_LOGIN_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + }; + +@@ -34,6 +36,18 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this._mechanismsStatus = MechanismsStatus.WAITING; + } + ++ _handleSelectChoice(serviceName, key) { ++ if (serviceName !== this._selectedMechanism?.serviceName) ++ return; ++ ++ if (this._selectedMechanism.role === Const.SMARTCARD_ROLE_NAME) { ++ const certificates = this._selectedMechanism.certificates; ++ const cert = certificates.find(c => c.keyId === key); ++ this._selectedSmartcard = cert; ++ this.emit('ask-question', serviceName, cert.pinPrompt, true); ++ } ++ } ++ + async _handleAnswerQuery(serviceName, answer) { + if (serviceName !== this._selectedMechanism?.serviceName) + return; +@@ -50,6 +64,7 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + let response; + switch (this._selectedMechanism.role) { + case Const.PASSWORD_ROLE_NAME: ++ case Const.SMARTCARD_ROLE_NAME: + response = this._formatResponse(answer); + this._sendResponse(response); + break; +@@ -61,6 +76,9 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + case Const.PASSWORD_ROLE_NAME: + this._startPasswordLogin(); + break; ++ case Const.SMARTCARD_ROLE_NAME: ++ this._startSmartcardLogin(); ++ break; + case Const.WEB_LOGIN_ROLE_NAME: + this._startWebLogin(); + break; +@@ -98,6 +116,7 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this._priorityList = null; + this._enabledMechanisms = null; + this._selectedMechanism = null; ++ this._selectedSmartcard = null; + + this._resettingPassword = false; + +@@ -176,6 +195,14 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + }); + } + ++ _handleSmartcardChanged() { ++ if (!this._selectedMechanism || ++ !this._enabledMechanisms.some(({role}) => role === Const.SMARTCARD_ROLE_NAME)) ++ return; ++ ++ this.emit('reset', {softReset: true, reuseEntryText: true}); ++ } ++ + _handleOnInfo(serviceName, info) { + if (!this._eventExpected()) + return; +@@ -244,6 +271,11 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + response = {password: answer}; + break; + } ++ case Const.SMARTCARD_ROLE_NAME: { ++ const {tokenName, moduleName, keyId, label} = this._selectedSmartcard; ++ response = {pin: answer, tokenName, moduleName, keyId, label}; ++ break; ++ } + case Const.WEB_LOGIN_ROLE_NAME: { + response = {}; + break; +@@ -287,6 +319,44 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this.emit('ask-question', serviceName, prompt, true); + } + ++ _startSmartcardLogin() { ++ const {serviceName, certificates} = this._selectedMechanism; ++ ++ if (certificates.length === 1) { ++ this._selectedSmartcard = certificates[0]; ++ this.emit('ask-question', serviceName, certificates[0].pinPrompt, true); ++ return; ++ } ++ ++ const choiceList = {}; ++ for (const cert of certificates) ++ choiceList[cert.keyId] = this._parseCertInstruction(cert.certInstruction); ++ ++ const prompt = certificates.length === 0 ++ ? _('Insert Smartcard') ++ : _('Select Identity'); ++ ++ this.emit('show-choice-list', serviceName, prompt, choiceList); ++ } ++ ++ _parseCertInstruction(certInstruction) { ++ // Currently sssd can't split cert data in a more granular way ++ // so it's parsed manually here ++ const [description, subject] = certInstruction.split('\n'); ++ ++ const fields = subject?.split(',').map(f => f.trim()) ?? []; ++ const commonName = fields.find(f => f.startsWith('CN='))?.substring(3); ++ const organization = fields.find(f => f.startsWith('O='))?.substring(2); ++ ++ return { ++ title: commonName, ++ subtitle: description, ++ iconName: organization ? 'vcard-symbolic' : null, ++ iconTitle: organization ? _('Organization') : null, ++ iconSubtitle: organization, ++ }; ++ } ++ + _startWebLogin() { + const { + serviceName, +diff --git a/js/misc/smartcardManager.js b/js/misc/smartcardManager.js +index 51471e51d4..3eabd2f3be 100644 +--- a/js/misc/smartcardManager.js ++++ b/js/misc/smartcardManager.js +@@ -70,6 +70,12 @@ class SmartcardManager extends Signals.EventEmitter { + } + + _addToken(token) { ++ // This is an alias, mirroring the real token. ++ // Skip it to avoid duplicate events ++ const objectPath = token.get_object_path(); ++ if (objectPath.endsWith('/login_token')) ++ return; ++ + this._updateToken(token); + + token.connectObject('g-properties-changed', (proxy, properties) => { +-- +2.53.0 + + +From 36c26f28f6e5b1883267a12b3e76c3fd0c888cb3 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 15 Sep 2025 16:36:42 +0200 +Subject: [PATCH 42/42] gdm: Add support for Passkey in + authServicesSSSDSwitchable + +This allows selecting passkey authentication mechanism. + +Use passkeyManager to detect when there's a passkey inserted or removed +to restart the service so the JSON protocol gives valid information +about the passkey. + +It's used 'show-choice-list' with an emtpy list, to display the touch +instruction. +--- + js/gdm/authServices.js | 12 ++++++++ + js/gdm/authServicesSSSDSwitchable.js | 46 +++++++++++++++++++++++++++- + js/gdm/const.js | 1 + + js/gdm/util.js | 4 +++ + js/ui/unlockDialog.js | 2 ++ + 5 files changed, 64 insertions(+), 1 deletion(-) + +diff --git a/js/gdm/authServices.js b/js/gdm/authServices.js +index 1801fbdc3a..e1039485a3 100644 +--- a/js/gdm/authServices.js ++++ b/js/gdm/authServices.js +@@ -2,6 +2,7 @@ + + import * as FingerprintManager from '../misc/fingerprintManager.js'; + import * as Params from '../misc/params.js'; ++import * as PasskeyDeviceManager from '../misc/passkeyDeviceManager.js'; + import * as SmartcardManager from '../misc/smartcardManager.js'; + import {logErrorUnlessCancelled} from '../misc/errorUtils.js'; + import * as Util from './util.js'; +@@ -85,6 +86,7 @@ export class AuthServices extends GObject.Object { + this._cancellable = null; + + this._connectSmartcardManager(); ++ this._connectPasskeyDeviceManager(); + this._connectFingerprintManager(); + } + +@@ -225,6 +227,14 @@ export class AuthServices extends GObject.Object { + this); + } + ++ _connectPasskeyDeviceManager() { ++ this._passkeyDeviceManager = PasskeyDeviceManager.getPasskeyDeviceManager(); ++ this._passkeyDeviceManager.connectObject( ++ 'passkey-inserted', () => this._handlePasskeyChanged(), ++ 'passkey-removed', () => this._handlePasskeyChanged(), ++ this); ++ } ++ + _connectFingerprintManager() { + // Fingerprint can only work on lockscreen + if (!this._reauthOnly) +@@ -469,6 +479,8 @@ export class AuthServices extends GObject.Object { + + _handleSmartcardChanged() {} + ++ _handlePasskeyChanged() {} ++ + _handleFingerprintChanged() {} + + _handleOnInfo() {} +diff --git a/js/gdm/authServicesSSSDSwitchable.js b/js/gdm/authServicesSSSDSwitchable.js +index b0c3583786..9c8464ac9d 100644 +--- a/js/gdm/authServicesSSSDSwitchable.js ++++ b/js/gdm/authServicesSSSDSwitchable.js +@@ -17,12 +17,14 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + static SupportedRoles = [ + Const.PASSWORD_ROLE_NAME, + Const.SMARTCARD_ROLE_NAME, ++ Const.PASSKEY_ROLE_NAME, + Const.WEB_LOGIN_ROLE_NAME, + ]; + + static RoleToService = { + [Const.PASSWORD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + [Const.SMARTCARD_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, ++ [Const.PASSKEY_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + [Const.WEB_LOGIN_ROLE_NAME]: Const.SWITCHABLE_AUTH_SERVICE_NAME, + }; + +@@ -68,6 +70,13 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + response = this._formatResponse(answer); + this._sendResponse(response); + break; ++ case Const.PASSKEY_ROLE_NAME: ++ response = this._formatResponse(answer); ++ this._sendResponse(response); ++ ++ this.emit('show-choice-list', serviceName, ++ this._selectedMechanism.touchInstruction, {}); ++ break; + } + } + +@@ -79,6 +88,9 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + case Const.SMARTCARD_ROLE_NAME: + this._startSmartcardLogin(); + break; ++ case Const.PASSKEY_ROLE_NAME: ++ this._startPasskeyLogin(); ++ break; + case Const.WEB_LOGIN_ROLE_NAME: + this._startWebLogin(); + break; +@@ -203,6 +215,14 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + this.emit('reset', {softReset: true, reuseEntryText: true}); + } + ++ _handlePasskeyChanged() { ++ if (!this._selectedMechanism || ++ !this._enabledMechanisms.some(({role}) => role === Const.PASSKEY_ROLE_NAME)) ++ return; ++ ++ this.emit('reset', {softReset: true, reuseEntryText: true}); ++ } ++ + _handleOnInfo(serviceName, info) { + if (!this._eventExpected()) + return; +@@ -263,7 +283,7 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + } + + _formatResponse(answer) { +- const {role, id} = this._selectedMechanism; ++ const {role, id, kerberos, cryptoChallenge} = this._selectedMechanism; + + let response; + switch (role) { +@@ -276,6 +296,10 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + response = {pin: answer, tokenName, moduleName, keyId, label}; + break; + } ++ case Const.PASSKEY_ROLE_NAME: { ++ response = {pin: answer, kerberos, cryptoChallenge}; ++ break; ++ } + case Const.WEB_LOGIN_ROLE_NAME: { + response = {}; + break; +@@ -357,6 +381,26 @@ export class AuthServicesSSSDSwitchable extends AuthServices { + }; + } + ++ _startPasskeyLogin() { ++ const { ++ serviceName, ++ keyConnected, initInstruction, ++ pinPrompt, pinAttempts, ++ } = this._selectedMechanism; ++ ++ if (!keyConnected) { ++ this.emit('show-choice-list', serviceName, initInstruction, {}); ++ return; ++ } ++ ++ this.emit('ask-question', serviceName, pinPrompt, true); ++ ++ if (pinAttempts <= 3 && pinAttempts > 0) { ++ const message = _('You have %d attempts left. If the passkey gets locked, you may not able to access your account.').format(pinAttempts); ++ this.emit('queue-message', serviceName, message, Util.MessageType.INFO); ++ } ++ } ++ + _startWebLogin() { + const { + serviceName, +diff --git a/js/gdm/const.js b/js/gdm/const.js +index e8ca48625a..7c48599e64 100644 +--- a/js/gdm/const.js ++++ b/js/gdm/const.js +@@ -3,6 +3,7 @@ + export const PASSWORD_ROLE_NAME = 'password'; + export const SMARTCARD_ROLE_NAME = 'smartcard'; + export const FINGERPRINT_ROLE_NAME = 'fingerprint'; ++export const PASSKEY_ROLE_NAME = 'passkey'; + export const WEB_LOGIN_ROLE_NAME = 'eidp'; + + export const PASSWORD_SERVICE_NAME = 'gdm-password'; +diff --git a/js/gdm/util.js b/js/gdm/util.js +index fe2216f8c7..5baa676a6a 100644 +--- a/js/gdm/util.js ++++ b/js/gdm/util.js +@@ -16,6 +16,7 @@ export const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; + export const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; + export const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; + export const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; ++export const PASSKEY_AUTHENTICATION_KEY = 'enable-passkey-authentication'; + export const SWITCHABLE_AUTHENTICATION_KEY = 'enable-switchable-authentication'; + export const WEB_AUTHENTICATION_KEY = 'enable-web-authentication'; + export const BANNER_MESSAGE_KEY = 'banner-message-enable'; +@@ -95,6 +96,7 @@ export function isSelectable(mechanism) { + switch (mechanism.role) { + case Const.PASSWORD_ROLE_NAME: + case Const.SMARTCARD_ROLE_NAME: ++ case Const.PASSKEY_ROLE_NAME: + case Const.WEB_LOGIN_ROLE_NAME: + return true; + case Const.FINGERPRINT_ROLE_NAME: +@@ -389,6 +391,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { + enabledRoles.push(Const.PASSWORD_ROLE_NAME); + if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) + enabledRoles.push(Const.SMARTCARD_ROLE_NAME); ++ if (this._settings.get_boolean(PASSKEY_AUTHENTICATION_KEY)) ++ enabledRoles.push(Const.PASSKEY_ROLE_NAME); + if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) + enabledRoles.push(Const.FINGERPRINT_ROLE_NAME); + if (this._settings.get_boolean(WEB_AUTHENTICATION_KEY)) +diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js +index 8a824f700a..16ac696029 100644 +--- a/js/ui/unlockDialog.js ++++ b/js/ui/unlockDialog.js +@@ -428,6 +428,8 @@ class UnlockDialogClock extends St.BoxLayout { + + if (authMechanism?.role === GdmConst.SMARTCARD_ROLE_NAME) + text = _('Insert smartcard'); ++ else if (authMechanism?.role === GdmConst.PASSKEY_ROLE_NAME) ++ text = _('Insert security key'); + else if (this._seat.touch_mode) + text = _('Swipe up'); + else +-- +2.53.0 + diff --git a/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch b/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch index cdee8c9..283cb56 100644 --- a/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch +++ b/0001-app-Fall-back-to-window-title-instead-of-WM_CLASS.patch @@ -1,4 +1,4 @@ -From 55c1dfed04084e9dbaf034263172bc745f905718 Mon Sep 17 00:00:00 2001 +From 9529c76492722852802aa7aa8d351e98610487ff 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 @@ -11,10 +11,10 @@ a .desktop file ... 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-app.c b/src/shell-app.c -index 04d0ab508b..4a55daf809 100644 +index 916783a597..9a90daa1dc 100644 --- a/src/shell-app.c +++ b/src/shell-app.c -@@ -247,7 +247,7 @@ shell_app_get_name (ShellApp *app) +@@ -262,7 +262,7 @@ shell_app_get_name (ShellApp *app) const char *name = NULL; if (window) @@ -24,5 +24,5 @@ index 04d0ab508b..4a55daf809 100644 name = C_("program", "Unknown"); return name; -- -2.45.2 +2.51.1 diff --git a/0001-build-Lower-gsettings-desktop-schemas-requirement.patch b/0001-build-Lower-gsettings-desktop-schemas-requirement.patch new file mode 100644 index 0000000..54dc1c7 --- /dev/null +++ b/0001-build-Lower-gsettings-desktop-schemas-requirement.patch @@ -0,0 +1,27 @@ +From 196fce5906753d6dad766dac4494038ff89599c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 10 Nov 2025 12:43:12 +0100 +Subject: [PATCH] build: Lower gsettings-desktop-schemas requirement + +The module won't be rebased to 49.x, instead the required schema +additions have been backported. +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index f1df40f702..decb2ba136 100644 +--- a/meson.build ++++ b/meson.build +@@ -27,7 +27,7 @@ gjs_req = '>= 1.73.1' + gtk_req = '>= 4.0' + mutter_req = '>= 49.0' + polkit_req = '>= 0.100' +-schemas_req = '>= 49.alpha' ++schemas_req = '>= 47.0' + systemd_req = '>= 246' + gnome_desktop_req = '>= 40' + pipewire_req = '>= 0.3.49' +-- +2.51.1 + diff --git a/0001-data-Update-generated-stylesheets.patch b/0001-data-Update-generated-stylesheets.patch index 69c870f..15cc6e2 100644 --- a/0001-data-Update-generated-stylesheets.patch +++ b/0001-data-Update-generated-stylesheets.patch @@ -1,19 +1,479 @@ -From 170ccbd3d237bf980f46ae87e3da8238df9f8a3c Mon Sep 17 00:00:00 2001 +From be6698d9c8f7fbd164ac1d49b1b56b6a1cb0a059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 16 Apr 2024 20:49:40 +0200 Subject: [PATCH] data: Update generated stylesheets --- - data/theme/gnome-shell-dark.css | 3 +++ - data/theme/gnome-shell-high-contrast.css | 3 +++ - data/theme/gnome-shell-light.css | 3 +++ - 3 files changed, 9 insertions(+) + data/theme/gnome-shell-dark.css | 468 +++++++++++++++++----- + data/theme/gnome-shell-high-contrast.css | 484 ++++++++++++++++++----- + data/theme/gnome-shell-light.css | 467 +++++++++++++++++----- + 3 files changed, 1095 insertions(+), 324 deletions(-) diff --git a/data/theme/gnome-shell-dark.css b/data/theme/gnome-shell-dark.css -index 339adf7e7b..5dd7d29a3a 100644 +index 82e68c5..a96fde1 100644 --- a/data/theme/gnome-shell-dark.css +++ b/data/theme/gnome-shell-dark.css -@@ -1720,6 +1720,9 @@ StScrollBar { +@@ -42,7 +42,9 @@ stage { + text-align: center; + transition-duration: 100ms; } + +-.login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button, .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-title, ++.login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton, .unlock-dialog .button, + .unlock-dialog .icon-button, + .unlock-dialog .message-notification-group .message-collapse-button, +@@ -51,131 +53,132 @@ stage { + .message .message-header .unlock-dialog .message-expand-button, + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, +-.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, +-#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, ++.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, ++#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + border-radius: 8px; + padding: 3px 24px; + font-weight: bold; + transition: border-width 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + color: #ffffff; + background-color: st-mix(#ffffff, #36363a, 9%); } +- .icon-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, ++ .icon-button:focus, .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, + .message .message-header .message-close-button:focus, .message-notification-group .message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button:focus { + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(#ffffff, #36363a, 9%), 5%); } +- .icon-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, ++ .icon-button:focus:hover, .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, + .message .message-header .message-close-button:focus:hover, .message-notification-group .message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#ffffff, #36363a, 9%), 4%), 5%); } +- .icon-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, ++ .icon-button:hover, .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, + .message .message-header .message-close-button:hover, .message-notification-group .message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button:hover { + color: #ffffff; + background-color: st-lighten(st-mix(#ffffff, #36363a, 9%), 4%); } +- .icon-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, ++ .icon-button:insensitive, .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, + .message .message-header .message-close-button:insensitive, .message-notification-group .message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button:insensitive { + color: st-transparentize(#ffffff, 0.5); + background-color: st-darken(st-mix(#ffffff, #36363a, 9%), 3%); } +- .icon-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, +- .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, ++ .icon-button:selected, .login-dialog-button.next-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-auth-menu-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, ++ .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, + .message .message-header .message-close-button:active, .message-notification-group .message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button:active { + color: #ffffff; + background-color: st-lighten(st-mix(#ffffff, #36363a, 9%), 9%); } +- .icon-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, +- .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, ++ .icon-button:selected:hover, .login-dialog-button.next-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-auth-menu-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, ++ .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, + .message .message-header .message-close-button:active:hover, .message-notification-group .message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #36363a, 9%), 9%), 4%); } +- .icon-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, +- .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, ++ .icon-button:selected:focus, .login-dialog-button.next-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-auth-menu-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, ++ .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, + .message .message-header .message-close-button:active:focus, .message-notification-group .message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#ffffff, #36363a, 9%), 9%), 5%); } +- .icon-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, ++ .icon-button:checked, .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, + .message .message-header .message-close-button:checked, .message-notification-group .message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button:checked { + color: #ffffff; + background-color: st-lighten(st-mix(#ffffff, #36363a, 9%), 8%); } +- .icon-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, ++ .icon-button:checked:hover, .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, + .message .message-header .message-close-button:checked:hover, .message-notification-group .message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #36363a, 9%), 8%), 4%); } +- .icon-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, ++ .icon-button:checked:active, .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, + .message .message-header .message-close-button:checked:active, .message-notification-group .message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button:checked:active { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #36363a, 9%), 8%), 9%); } + +-.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, ++.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.next-button, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-auth-menu-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, + .message .message-header .flat.message-close-button, .message-notification-group .flat.message-collapse-button, .calendar .calendar-month-header .pager-button, .button.flat { + color: #ffffff; + background-color: #36363a; } +- .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, ++ .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.next-button:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, + .message .message-header .flat.message-close-button:focus, .message-notification-group .flat.message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button.flat:focus { + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), rgba(54, 54, 58, 0.25), 5%); } +- .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, ++ .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.next-button:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, + .message .message-header .flat.message-close-button:focus:hover, .message-notification-group .flat.message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button.flat:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(#36363a, 7%), 5%); } +- .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, ++ .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.next-button:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, + .message .message-header .flat.message-close-button:hover, .message-notification-group .flat.message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button.flat:hover { + color: #ffffff; + background-color: st-lighten(#36363a, 7%); } +- .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, ++ .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.next-button:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-auth-menu-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, + .message .message-header .flat.message-close-button:insensitive, .message-notification-group .flat.message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button.flat:insensitive { + color: st-transparentize(#ffffff, 0.5); + background-color: #36363a; } +- .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, +- .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, ++ .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.next-button:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-auth-menu-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, ++ .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.next-button:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-auth-menu-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, + .message .message-header .flat.message-close-button:active, .message-notification-group .flat.message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button.flat:active { + color: #ffffff; + background-color: st-lighten(#36363a, 9%); } +- .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, +- .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, ++ .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.next-button:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, ++ .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.next-button:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, + .message .message-header .flat.message-close-button:active:hover, .message-notification-group .flat.message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button.flat:active:hover { + background-color: st-lighten(st-lighten(#36363a, 9%), 7%); } +- .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, +- .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, ++ .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.next-button:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, ++ .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.next-button:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, + .message .message-header .flat.message-close-button:active:focus, .message-notification-group .flat.message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button.flat:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(#36363a, 9%), 5%); } +- .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, ++ .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.next-button:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-auth-menu-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, + .message .message-header .flat.message-close-button:checked, .message-notification-group .flat.message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button.flat:checked { + color: #ffffff; + background-color: st-lighten(#36363a, 8%); } +- .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, ++ .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.next-button:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, + .message .message-header .flat.message-close-button:checked:hover, .message-notification-group .flat.message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button.flat:checked:hover { + background-color: st-lighten(st-lighten(#36363a, 8%), 7%); } +- .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, ++ .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.next-button:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, + .message .message-header .flat.message-close-button:checked:active, .message-notification-group .flat.message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button.flat:checked:active { + background-color: st-lighten(st-lighten(#36363a, 8%), 9%); } + +-.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, ++.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.next-button, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-auth-menu-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, + .message .message-header .default.message-close-button, .message-notification-group .default.message-collapse-button, .calendar .calendar-month-header .default.pager-button, .button.default { + color: -st-accent-fg-color; + background-color: -st-accent-color; } +- .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, ++ .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.next-button:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-auth-menu-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, + .message .message-header .default.message-close-button:focus, .message-notification-group .default.message-collapse-button:focus, .calendar .calendar-month-header .default.pager-button:focus, .button.default:focus { + color: -st-accent-fg-color; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; + box-shadow: inset 0 0 0 2px st-transparentize(st-lighten(-st-accent-color, 30%), 0.2) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), -st-accent-color, 5%); } +- .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, ++ .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.next-button:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, + .message .message-header .default.message-close-button:focus:hover, .message-notification-group .default.message-collapse-button:focus:hover, .calendar .calendar-month-header .default.pager-button:focus:hover, .button.default:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(-st-accent-color, 4%), 5%); } +- .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, ++ .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.next-button:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-auth-menu-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, + .message .message-header .default.message-close-button:hover, .message-notification-group .default.message-collapse-button:hover, .calendar .calendar-month-header .default.pager-button:hover, .button.default:hover { + color: -st-accent-fg-color; + background-color: st-lighten(-st-accent-color, 4%); } +- .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, ++ .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.next-button:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-auth-menu-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, + .message .message-header .default.message-close-button:insensitive, .message-notification-group .default.message-collapse-button:insensitive, .calendar .calendar-month-header .default.pager-button:insensitive, .button.default:insensitive { + color: st-transparentize(-st-accent-fg-color, 0.5); + background-color: st-darken(-st-accent-color, 3%); } +- .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, ++ .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.next-button:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-auth-menu-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, + .message .message-header .default.message-close-button:active, .message-notification-group .default.message-collapse-button:active, .calendar .calendar-month-header .default.pager-button:active, .button.default:active { + color: -st-accent-fg-color; + background-color: st-lighten(-st-accent-color, 9%); } +- .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, ++ .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.next-button:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-auth-menu-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, + .message .message-header .default.message-close-button:active:hover, .message-notification-group .default.message-collapse-button:active:hover, .calendar .calendar-month-header .default.pager-button:active:hover, .button.default:active:hover { + background-color: st-lighten(st-lighten(-st-accent-color, 9%), 4%); } +- .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, ++ .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.next-button:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-auth-menu-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, + .message .message-header .default.message-close-button:active:focus, .message-notification-group .default.message-collapse-button:active:focus, .calendar .calendar-month-header .default.pager-button:active:focus, .button.default:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(-st-accent-color, 9%), 5%); } + +@@ -358,7 +361,7 @@ StEntry { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #36363a, 9%), 8%), 9%); } + .modal-dialog .modal-dialog-button-box .modal-dialog-button:insensitive { + color: st-transparentize(#ffffff, 0.5); +- background-color: st-darken(st-mix(#ffffff, #36363a, 9%), 3%); } ++ background-color: rgba(255, 255, 255, 0.1); } + + .dash-label, .window-caption, .screenshot-ui-tooltip { + background-color: rgba(0, 0, 0, 0.9); +@@ -382,12 +385,13 @@ StEntry { + font-weight: 700; + font-size: 1.364em; } + +-.login-dialog-auth-list-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, ++.login-dialog-auth-list-title-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, + .polkit-dialog-user-layout .polkit-dialog-user-root-label, .message-dialog-content .message-dialog-title.lightweight { + font-weight: 700; + font-size: 1.182em; } + +-.login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { ++.login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels, .login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle, .login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { + font-weight: 700; + font-size: 1em; } + +@@ -405,7 +409,7 @@ StEntry { + font-weight: 400; + font-size: 0.818em; } + +-#LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { ++.web-login-url-label, #LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { + font-family: monospace; } + + .unlock-dialog-clock .unlock-dialog-clock-time, #panel, .weather-button .weather-box .weather-grid .weather-forecast-temp, .world-clocks-button .world-clocks-grid .world-clocks-timezone, .world-clocks-button .world-clocks-grid .world-clocks-time, .events-button .events-box .events-list .event-box .event-time, .calendar .calendar-day-heading, .calendar .calendar-day { +@@ -523,34 +527,34 @@ StEntry { + .login-dialog .login-dialog-prompt-entry StLabel.hint-text, .app-folder-dialog .folder-name-container .folder-name-entry StLabel.hint-text, .search-entry StLabel.hint-text { + color: rgba(250, 250, 251, 0.7); } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + color: #fafafb; + background-color: st-mix(#fafafb, #2e2e33, 9%); } +- .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { ++ .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { + color: st-transparentize(#fafafb, 0.5); + background-color: st-darken(st-mix(#fafafb, #2e2e33, 9%), 3%); } +- .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { ++ .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(#fafafb, #2e2e33, 9%), 5%); } +- .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { ++ .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #2e2e33, 9%), 4%), 5%); } +- .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { ++ .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #2e2e33, 9%), 4%); } +- .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active { ++ .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #2e2e33, 9%), 9%); } +- .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { ++ .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #2e2e33, 9%), 9%), 4%); } +- .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { ++ .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #2e2e33, 9%), 9%), 5%); } +- .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { ++ .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #2e2e33, 9%), 8%); } +- .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { ++ .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #2e2e33, 9%), 8%), 4%); } +- .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { ++ .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #2e2e33, 9%), 8%), 9%); } + + /* Lockscreen Elements */ +@@ -566,10 +570,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, + .unlock-dialog .screenshot-ui-show-pointer-button, ++.unlock-dialog .login-dialog-button.next-button, + .unlock-dialog .login-dialog-button.a11y-button, + .unlock-dialog .login-dialog-button.cancel-button, + .unlock-dialog .login-dialog-button.switch-user-button, +-.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .login-dialog-auth-list-item { ++.unlock-dialog .login-dialog-button.login-dialog-auth-menu-button, ++.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.1); } + .unlock-dialog-notifications-container .message StButton:focus, +@@ -584,10 +591,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus, + .message .message-header .unlock-dialog .message-close-button:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:focus, ++ .unlock-dialog .login-dialog-button.next-button:focus, + .unlock-dialog .login-dialog-button.a11y-button:focus, + .unlock-dialog .login-dialog-button.cancel-button:focus, + .unlock-dialog .login-dialog-button.switch-user-button:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .login-dialog-auth-list-item:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .web-login-intro-button:focus, ++ .unlock-dialog .web-login-prompt-button:focus, .unlock-dialog .login-dialog-auth-list-title:focus, .unlock-dialog .login-dialog-auth-list-item:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), rgba(250, 250, 251, 0.1), 5%); } +@@ -603,10 +613,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus:hover, + .message .message-header .unlock-dialog .message-close-button:focus:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:focus:hover, ++ .unlock-dialog .login-dialog-button.next-button:focus:hover, + .unlock-dialog .login-dialog-button.a11y-button:focus:hover, + .unlock-dialog .login-dialog-button.cancel-button:focus:hover, + .unlock-dialog .login-dialog-button.switch-user-button:focus:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .web-login-intro-button:focus:hover, ++ .unlock-dialog .web-login-prompt-button:focus:hover, .unlock-dialog .login-dialog-auth-list-title:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), rgba(250, 250, 251, 0.13), 5%); } + .unlock-dialog-notifications-container .message StButton:hover, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:hover, .unlock-dialog .button:hover, +@@ -620,10 +633,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:hover, + .message .message-header .unlock-dialog .message-close-button:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:hover, ++ .unlock-dialog .login-dialog-button.next-button:hover, + .unlock-dialog .login-dialog-button.a11y-button:hover, + .unlock-dialog .login-dialog-button.cancel-button:hover, + .unlock-dialog .login-dialog-button.switch-user-button:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .login-dialog-auth-list-item:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .web-login-intro-button:hover, ++ .unlock-dialog .web-login-prompt-button:hover, .unlock-dialog .login-dialog-auth-list-title:hover, .unlock-dialog .login-dialog-auth-list-item:hover { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.13); } + .unlock-dialog-notifications-container .message StButton:active, +@@ -638,10 +654,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active, + .message .message-header .unlock-dialog .message-close-button:active, + .unlock-dialog .screenshot-ui-show-pointer-button:active, ++ .unlock-dialog .login-dialog-button.next-button:active, + .unlock-dialog .login-dialog-button.a11y-button:active, + .unlock-dialog .login-dialog-button.cancel-button:active, + .unlock-dialog .login-dialog-button.switch-user-button:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .login-dialog-auth-list-item:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .web-login-intro-button:active, ++ .unlock-dialog .web-login-prompt-button:active, .unlock-dialog .login-dialog-auth-list-title:active, .unlock-dialog .login-dialog-auth-list-item:active { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.16); } + .unlock-dialog-notifications-container .message StButton:active:hover, +@@ -656,10 +675,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .unlock-dialog .message-close-button:active:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:active:hover, ++ .unlock-dialog .login-dialog-button.next-button:active:hover, + .unlock-dialog .login-dialog-button.a11y-button:active:hover, + .unlock-dialog .login-dialog-button.cancel-button:active:hover, + .unlock-dialog .login-dialog-button.switch-user-button:active:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .web-login-intro-button:active:hover, ++ .unlock-dialog .web-login-prompt-button:active:hover, .unlock-dialog .login-dialog-auth-list-title:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { + background-color: rgba(250, 250, 251, 0.19); } + .unlock-dialog-notifications-container .message StButton:active:focus, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:active:focus, .unlock-dialog .button:active:focus, +@@ -673,10 +695,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .unlock-dialog .message-close-button:active:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:active:focus, ++ .unlock-dialog .login-dialog-button.next-button:active:focus, + .unlock-dialog .login-dialog-button.a11y-button:active:focus, + .unlock-dialog .login-dialog-button.cancel-button:active:focus, + .unlock-dialog .login-dialog-button.switch-user-button:active:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .web-login-intro-button:active:focus, ++ .unlock-dialog .web-login-prompt-button:active:focus, .unlock-dialog .login-dialog-auth-list-title:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), rgba(250, 250, 251, 0.16), 5%); } + .unlock-dialog-notifications-container .message StButton:checked, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked, .unlock-dialog .button:checked, +@@ -690,10 +715,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked, + .message .message-header .unlock-dialog .message-close-button:checked, + .unlock-dialog .screenshot-ui-show-pointer-button:checked, ++ .unlock-dialog .login-dialog-button.next-button:checked, + .unlock-dialog .login-dialog-button.a11y-button:checked, + .unlock-dialog .login-dialog-button.cancel-button:checked, + .unlock-dialog .login-dialog-button.switch-user-button:checked, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .login-dialog-auth-list-item:checked { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .web-login-intro-button:checked, ++ .unlock-dialog .web-login-prompt-button:checked, .unlock-dialog .login-dialog-auth-list-title:checked, .unlock-dialog .login-dialog-auth-list-item:checked { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #36363a, 9%), 8%); } + .unlock-dialog-notifications-container .message StButton:checked:hover, +@@ -708,10 +736,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:hover, + .message .message-header .unlock-dialog .message-close-button:checked:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:hover, ++ .unlock-dialog .login-dialog-button.next-button:checked:hover, + .unlock-dialog .login-dialog-button.a11y-button:checked:hover, + .unlock-dialog .login-dialog-button.cancel-button:checked:hover, + .unlock-dialog .login-dialog-button.switch-user-button:checked:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .web-login-intro-button:checked:hover, ++ .unlock-dialog .web-login-prompt-button:checked:hover, .unlock-dialog .login-dialog-auth-list-title:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #36363a, 9%), 8%), 4%); } + .unlock-dialog-notifications-container .message StButton:checked:active, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked:active, .unlock-dialog .button:checked:active, +@@ -725,10 +756,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:active, + .message .message-header .unlock-dialog .message-close-button:checked:active, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:active, ++ .unlock-dialog .login-dialog-button.next-button:checked:active, + .unlock-dialog .login-dialog-button.a11y-button:checked:active, + .unlock-dialog .login-dialog-button.cancel-button:checked:active, + .unlock-dialog .login-dialog-button.switch-user-button:checked:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .web-login-intro-button:checked:active, ++ .unlock-dialog .web-login-prompt-button:checked:active, .unlock-dialog .login-dialog-auth-list-title:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #36363a, 9%), 8%), 9%); } + .unlock-dialog-notifications-container .message StButton:insensitive, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:insensitive, .unlock-dialog .button:insensitive, +@@ -742,12 +776,15 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:insensitive, + .message .message-header .unlock-dialog .message-close-button:insensitive, + .unlock-dialog .screenshot-ui-show-pointer-button:insensitive, ++ .unlock-dialog .login-dialog-button.next-button:insensitive, + .unlock-dialog .login-dialog-button.a11y-button:insensitive, + .unlock-dialog .login-dialog-button.cancel-button:insensitive, + .unlock-dialog .login-dialog-button.switch-user-button:insensitive, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:insensitive, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .web-login-intro-button:insensitive, ++ .unlock-dialog .web-login-prompt-button:insensitive, .unlock-dialog .login-dialog-auth-list-title:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { + color: st-transparentize(#fafafb, 0.5); +- background-color: st-darken(st-mix(#fafafb, #36363a, 9%), 3%); } ++ background-color: rgba(250, 250, 251, 0.1); } + + .unlock-dialog .login-dialog-prompt-entry { + background-color: rgba(250, 250, 251, 0.1); +@@ -767,6 +804,7 @@ StEntry { + .unlock-dialog .login-dialog-prompt-entry StLabel.hint-text { + color: rgba(250, 250, 251, 0.7); } + ++/* Login Dialog Elements */ + /* WIDGETS */ + .shell-link { + color: st-lighten(-st-accent-color, 20%); } +@@ -790,18 +828,18 @@ StEntry StIcon.peek-password { + padding: 0 4px; } + + StEntry StLabel.hint-text { +- margin-left: 2px; } ++ margin-left: 8px; } + + /* Buttons */ + .button { + min-height: 1.5em; } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button { + border-radius: 999px; + padding: 0.818em; + min-height: 1.091em; } +- .icon-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, ++ .icon-button StIcon, .login-dialog-button.next-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-auth-menu-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, + .message .message-header .message-close-button StIcon, .message-notification-group .message-collapse-button StIcon, .calendar .calendar-month-header .pager-button StIcon { + icon-size: 1.091em; + -st-icon-style: symbolic; } +@@ -1836,6 +1874,9 @@ StScrollBar { min-width: 0.5455em; min-height: 0.5455em; background-color: #ffffff; } @@ -23,11 +483,925 @@ index 339adf7e7b..5dd7d29a3a 100644 #panel .panel-button.screen-recording-indicator StBoxLayout, #panel .panel-button.screen-sharing-indicator StBoxLayout { spacing: 0.409em; } #panel .panel-button.screen-recording-indicator StIcon, #panel .panel-button.screen-sharing-indicator StIcon { +@@ -2037,7 +2078,7 @@ StScrollBar { + .quick-settings { + padding: 18px; + border-radius: 36px; } +- .quick-settings .icon-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, ++ .quick-settings .icon-button, .quick-settings .login-dialog-button.next-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-auth-menu-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, + .quick-settings .message .message-header .message-close-button, + .message .message-header .quick-settings .message-close-button, .quick-settings .screenshot-ui-show-pointer-button, .quick-settings .button { + padding: 10.5px; } +@@ -2145,7 +2186,7 @@ StScrollBar { + .quick-slider > StBoxLayout { + spacing: 6px; } + +-.quick-slider .icon-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, ++.quick-slider .icon-button, .quick-slider .login-dialog-button.next-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-auth-menu-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, + .quick-slider .message .message-header .message-close-button, + .message .message-header .quick-slider .message-close-button, .quick-slider .screenshot-ui-show-pointer-button { + padding: 6px; } +@@ -2219,16 +2260,16 @@ StScrollBar { + icon-size: 32px !important; + -st-icon-style: regular !important; } + +-.background-app-item .icon-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, ++.background-app-item .icon-button, .background-app-item .login-dialog-button.next-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-auth-menu-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, + .background-app-item .message .message-header .message-close-button, + .message .message-header .background-app-item .message-close-button, .background-app-item .screenshot-ui-show-pointer-button { + padding: 6px; + background-color: rgba(255, 255, 255, 0.13); } +- .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, ++ .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.next-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, + .background-app-item .message .message-header .message-close-button:hover, + .message .message-header .background-app-item .message-close-button:hover, .background-app-item .screenshot-ui-show-pointer-button:hover { + background-color: rgba(255, 255, 255, 0.22); } +- .background-app-item .icon-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, ++ .background-app-item .icon-button:active, .background-app-item .login-dialog-button.next-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, + .background-app-item .message .message-header .message-close-button:active, + .message .message-header .background-app-item .message-close-button:active, .background-app-item .screenshot-ui-show-pointer-button:active { + background-color: rgba(255, 255, 255, 0.31); } +@@ -2560,26 +2601,26 @@ StScrollBar { + padding-bottom: 0; } + .app-folder-dialog .folder-name-container .folder-name-entry { + width: 12em; } +- .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, ++ .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.next-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, + .app-folder-dialog .message .message-header .message-close-button, + .message .message-header .app-folder-dialog .message-close-button, .app-folder-dialog .screenshot-ui-show-pointer-button { + color: #fafafb; + background-color: st-mix(#fafafb, #38383b, 9%); } +- .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, ++ .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.next-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, + .app-folder-dialog .message .message-header .message-close-button:hover, + .message .message-header .app-folder-dialog .message-close-button:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:hover { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #38383b, 9%), 4%); } +- .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, ++ .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.next-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, + .app-folder-dialog .message .message-header .message-close-button:active, + .message .message-header .app-folder-dialog .message-close-button:active, .app-folder-dialog .screenshot-ui-show-pointer-button:active { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #38383b, 9%), 9%); } +- .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, ++ .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.next-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, + .app-folder-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .app-folder-dialog .message-close-button:active:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #38383b, 9%), 9%), 4%); } +- .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, ++ .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.next-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, + .app-folder-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .app-folder-dialog .message-close-button:active:focus, .app-folder-dialog .screenshot-ui-show-pointer-button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #38383b, 9%), 9%), 5%); } +@@ -3032,6 +3073,13 @@ StScrollBar { + background-color: st-lighten(-st-accent-color, 5%); + color: st-lighten(-st-accent-fg-color, 5%); } + ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #fafafb; ++ border-color: #fafafb; ++ color: #2e2e33; } ++ + .login-dialog, + .unlock-dialog { + color: #fafafb; } +@@ -3044,19 +3092,62 @@ StScrollBar { + .unlock-dialog .login-dialog-prompt-layout { + width: 25em; + spacing: 9px; } ++ .login-dialog .login-dialog-prompt-layout.web-login-active, ++ .unlock-dialog .login-dialog-prompt-layout.web-login-active { ++ width: 37.5em; } ++ .login-dialog .login-dialog-prompt-entry-area, ++ .unlock-dialog .login-dialog-prompt-entry-area { ++ margin: 0.5em 20px; } ++ .login-dialog .login-dialog-prompt-entry, ++ .unlock-dialog .login-dialog-prompt-entry { ++ border-radius: 12px; ++ padding-right: 3em; } ++ .login-dialog .login-dialog-default-button-well, ++ .unlock-dialog .login-dialog-default-button-well { ++ margin-right: 1em; } + + .login-dialog-bottom-button-group { + padding: 32px; + spacing: 16px; } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + padding: 1.091em; } + ++.login-dialog-button.next-button { ++ background-color: transparent !important; ++ padding: 0; } ++ + .login-dialog-button.cancel-button { +- padding: 9px; } ++ padding: 12px; } ++ ++.login-dialog-auth-menu-button-popup { ++ padding: 18px; ++ margin-right: 12px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header { ++ font-size: 0.909em; ++ text-align: center; ++ font-weight: bold; ++ padding-top: 18px; ++ padding-bottom: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header:first-child { ++ padding-top: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator { ++ spacing: 3px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-name { ++ font-size: 1.159em; ++ font-weight: bold; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-description { ++ font-size: 0.977em; } ++ ++.login-dialog-auth-menu-button-indicator { ++ background-color: transparent !important; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons { ++ spacing: 18px; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons .login-dialog-auth-menu-button-indicator-icon { ++ icon-size: 2em; } + + .login-dialog-button-box { +- spacing: 12px; } ++ height: 4em; } + + .conflicting-session-dialog-content { + spacing: 20px; } +@@ -3118,49 +3209,104 @@ StScrollBar { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(#222226, 9%), 5%); } + + .login-dialog-auth-list-view { +- -st-vfade-offset: 3em; } ++ -st-vfade-offset: 3em; ++ max-height: 13em; } + + .login-dialog-auth-list { +- spacing: 6px; +- margin-left: 2em; } +- +-.login-dialog-auth-list-title { +- margin-left: 2em; +- padding-bottom: 6px; } ++ spacing: 9px; } + ++.login-dialog .login-dialog-auth-list-title, + .login-dialog .login-dialog-auth-list-item { + color: #fafafb; +- background-color: st-mix(#fafafb, #222226, 9%); +- border-radius: 9.6px; +- padding: 7.2px; } +- .login-dialog .login-dialog-auth-list-item:selected, .login-dialog .login-dialog-auth-list-item:focus { ++ background-color: #47474c; ++ border-radius: 12px; } ++ .login-dialog .login-dialog-auth-list-title:selected, .login-dialog .login-dialog-auth-list-title:focus, ++ .login-dialog .login-dialog-auth-list-item:selected, ++ .login-dialog .login-dialog-auth-list-item:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(#fafafb, #222226, 9%), 5%); } +- .login-dialog .login-dialog-auth-list-item:selected:hover, .login-dialog .login-dialog-auth-list-item:focus:hover { +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #222226, 9%), 4%), 5%); } ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), #47474c, 5%); } ++ .login-dialog .login-dialog-auth-list-title:selected:hover, .login-dialog .login-dialog-auth-list-title:focus:hover, ++ .login-dialog .login-dialog-auth-list-item:selected:hover, ++ .login-dialog .login-dialog-auth-list-item:focus:hover { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(#47474c, 4%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:hover, + .login-dialog .login-dialog-auth-list-item:hover { + color: #fafafb; +- background-color: st-lighten(st-mix(#fafafb, #222226, 9%), 4%); } ++ background-color: st-lighten(#47474c, 4%); } ++ .login-dialog .login-dialog-auth-list-title:active, + .login-dialog .login-dialog-auth-list-item:active { + color: #fafafb; +- background-color: st-lighten(st-mix(#fafafb, #222226, 9%), 9%); } ++ background-color: st-lighten(#47474c, 9%); } ++ .login-dialog .login-dialog-auth-list-title:active:hover, + .login-dialog .login-dialog-auth-list-item:active:hover { +- background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 4%); } ++ background-color: st-lighten(st-lighten(#47474c, 9%), 4%); } ++ .login-dialog .login-dialog-auth-list-title:active:focus, + .login-dialog .login-dialog-auth-list-item:active:focus { +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 5%); } ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(#47474c, 9%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:insensitive, ++ .login-dialog .login-dialog-auth-list-item:insensitive { ++ color: st-transparentize(#fafafb, 0.5); ++ background-color: st-darken(#47474c, 3%); } ++ ++.login-dialog .login-dialog-auth-list-title { ++ background-color: rgba(250, 250, 251, 0.02) !important; ++ color: #fafafb !important; ++ margin: 0.5em 20px; } ++ ++.login-dialog .login-dialog-auth-list-item { ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } + + .unlock-dialog .login-dialog-auth-list-item { +- border-radius: 9.6px; +- padding: 7.2px; } ++ border-radius: 12px; ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } ++ ++.unlock-dialog .login-dialog-auth-list-title { ++ background-color: transparent !important; ++ color: #fafafb !important; ++ padding: 0; ++ margin: 0; } + +-.login-dialog-auth-list-label:ltr { +- padding-left: 15px; +- text-align: left; } ++.login-dialog-auth-list-title-label { ++ padding: 6px; ++ text-align: center; } + +-.login-dialog-auth-list-label:rtl { +- padding-right: 15px; +- text-align: right; } ++.login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle { ++ text-align: center; ++ padding: 1.8px 0; } ++ ++.login-dialog-auth-list-item-title { ++ color: #fafafb; } ++ ++.login-dialog-auth-list-item-subtitle { ++ color: #c1c1ce; ++ font-weight: 500; } ++ ++.login-dialog-item-icon { ++ width: 1.3em; ++ height: 1.3em; ++ color: #fafafb; ++ padding: 4.2px 6px; ++ border-radius: 8px; } ++ .login-dialog-item-icon:hover { ++ background-color: #2e2e33; } ++ .login-dialog-item-icon-popup.popup-menu { ++ min-width: 0; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels { ++ spacing: 3px; ++ text-align: center; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :first-child { ++ color: #b3b3b3; ++ font-weight: 500; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :last-child { ++ color: #ffffff; } + + .login-dialog-user-list-view { + width: 25em; +@@ -3189,6 +3335,9 @@ StScrollBar { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 4%); } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 5%); } ++ .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:insensitive { ++ color: st-transparentize(#fafafb, 0.5); ++ background-color: st-darken(st-mix(#fafafb, #222226, 9%), 3%); } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .user-icon { + border: 2px solid transparent; } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .login-dialog-timed-login-indicator { +@@ -3200,6 +3349,102 @@ StScrollBar { + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:logged-in .user-icon StIcon { + background-color: st-transparentize(-st-accent-color, 0.7); } + ++.web-login-spinner { ++ background-color: rgba(0, 0, 0, 0.5); ++ border: 5px rgba(0, 0, 0, 0); ++ border-radius: 50px; } ++ ++.web-login-title-label { ++ font-size: 1em; ++ color: #a5a5b7; ++ text-align: center; } ++ ++.web-login-url-label { ++ font-size: 1em; ++ color: #fafafb; ++ text-align: center; } ++ .web-login-url-label.web-login-url-label-long { ++ font-size: 0.818em; } ++ ++.web-login-code-title-label { ++ font-size: 1em; ++ color: #fafafb; ++ text-align: center; } ++ ++.web-login-code-label { ++ font-size: 1em; ++ color: #fafafb; ++ font-weight: bold; ++ text-align: center; } ++ ++.web-login-prompt { ++ padding-top: 6px; ++ padding-bottom: 6px; ++ padding-left: 27px; ++ padding-right: 27px; ++ spacing: 1.75em; } ++ ++.web-login-button-label { ++ font-size: 1.182em; ++ color: #fafafb; ++ min-width: 12em; ++ text-align: center; ++ font-weight: bold; } ++ ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button { ++ color: #fafafb; ++ background-color: st-mix(#fafafb, #222226, 9%); ++ border-radius: 32px; } ++ .login-dialog .web-login-intro-button:selected, .login-dialog .web-login-intro-button:focus, ++ .login-dialog .web-login-prompt-button:selected, ++ .login-dialog .web-login-prompt-button:focus { ++ color: #fafafb; ++ box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.2) !important; ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(#fafafb, #222226, 9%), 5%); } ++ .login-dialog .web-login-intro-button:selected:hover, .login-dialog .web-login-intro-button:focus:hover, ++ .login-dialog .web-login-prompt-button:selected:hover, ++ .login-dialog .web-login-prompt-button:focus:hover { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #222226, 9%), 4%), 5%); } ++ .login-dialog .web-login-intro-button:hover, ++ .login-dialog .web-login-prompt-button:hover { ++ color: #fafafb; ++ background-color: st-lighten(st-mix(#fafafb, #222226, 9%), 4%); } ++ .login-dialog .web-login-intro-button:active, ++ .login-dialog .web-login-prompt-button:active { ++ color: #fafafb; ++ background-color: st-lighten(st-mix(#fafafb, #222226, 9%), 9%); } ++ .login-dialog .web-login-intro-button:active:hover, ++ .login-dialog .web-login-prompt-button:active:hover { ++ background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 4%); } ++ .login-dialog .web-login-intro-button:active:focus, ++ .login-dialog .web-login-prompt-button:active:focus { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-lighten(st-mix(#fafafb, #222226, 9%), 9%), 5%); } ++ .login-dialog .web-login-intro-button:insensitive, ++ .login-dialog .web-login-prompt-button:insensitive { ++ color: st-transparentize(#fafafb, 0.5); ++ background-color: st-darken(st-mix(#fafafb, #222226, 9%), 3%); } ++ ++.login-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.login-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ ++.unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button { ++ border-radius: 32px; } ++ ++.unlock-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.unlock-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ + .unlock-dialog { + background-color: transparent; } + +@@ -3308,3 +3553,10 @@ StScrollBar { + .login-dialog .user-widget.vertical .user-icon StIcon, + .unlock-dialog .user-widget.vertical .user-icon StIcon { + padding: 30px; } ++ ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #fafafb; ++ border-color: #fafafb; ++ color: #222226; } diff --git a/data/theme/gnome-shell-high-contrast.css b/data/theme/gnome-shell-high-contrast.css -index 4bb622aa26..8f94fa4101 100644 +index b69823f..0ed28a3 100644 --- a/data/theme/gnome-shell-high-contrast.css +++ b/data/theme/gnome-shell-high-contrast.css -@@ -1897,6 +1897,9 @@ StScrollBar { +@@ -42,7 +42,9 @@ stage { + text-align: center; + transition-duration: 100ms; } + +-.login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button, .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-title, ++.login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton, .unlock-dialog .button, + .unlock-dialog .icon-button, + .unlock-dialog .message-notification-group .message-collapse-button, +@@ -51,34 +53,35 @@ stage { + .message .message-header .unlock-dialog .message-expand-button, + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, +-.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, +-#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, ++.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, ++#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + border-radius: 8px; + padding: 3px 24px; + font-weight: bold; + transition: border-width 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-mix(#ffffff, #1a1a1a, 9%), #ffffff, 87%); } +- .icon-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, ++ .icon-button:focus, .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, + .message .message-header .message-close-button:focus, .message-notification-group .message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-mix(#ffffff, #1a1a1a, 9%), #ffffff, 87%), 5%); } +- .icon-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, ++ .icon-button:focus:hover, .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, + .message .message-header .message-close-button:focus:hover, .message-notification-group .message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 4%), #ffffff, 87%), 5%); } +- .icon-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, ++ .icon-button:hover, .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, + .message .message-header .message-close-button:hover, .message-notification-group .message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 4%), #ffffff, 87%); } +- .icon-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, ++ .icon-button:insensitive, .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, + .message .message-header .message-close-button:insensitive, .message-notification-group .message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: st-transparentize(#ffffff, 0.5); +@@ -86,33 +89,33 @@ stage { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +- .icon-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, +- .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, ++ .icon-button:selected, .login-dialog-button.next-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-auth-menu-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, ++ .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, + .message .message-header .message-close-button:active, .message-notification-group .message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 9%), #ffffff, 87%); } +- .icon-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, +- .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, ++ .icon-button:selected:hover, .login-dialog-button.next-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-auth-menu-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, ++ .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, + .message .message-header .message-close-button:active:hover, .message-notification-group .message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 9%), 4%); } +- .icon-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, +- .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, ++ .icon-button:selected:focus, .login-dialog-button.next-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-auth-menu-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, ++ .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, + .message .message-header .message-close-button:active:focus, .message-notification-group .message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 9%), #ffffff, 87%), 5%); } +- .icon-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, ++ .icon-button:checked, .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, + .message .message-header .message-close-button:checked, .message-notification-group .message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button:checked { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), #ffffff, 87%); } +- .icon-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, ++ .icon-button:checked:hover, .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, + .message .message-header .message-close-button:checked:hover, .message-notification-group .message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), 4%); } +- .icon-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, ++ .icon-button:checked:active, .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, + .message .message-header .message-close-button:checked:active, .message-notification-group .message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button:checked:active { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), 9%); } + +-.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, ++.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.next-button, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-auth-menu-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, + .message .message-header .flat.message-close-button, .message-notification-group .flat.message-collapse-button, .calendar .calendar-month-header .pager-button, .button.flat { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; +@@ -120,21 +123,21 @@ stage { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +- .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, ++ .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.next-button:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, + .message .message-header .flat.message-close-button:focus, .message-notification-group .flat.message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button.flat:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), rgba(26, 26, 26, 0.25), 5%); } +- .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, ++ .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.next-button:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, + .message .message-header .flat.message-close-button:focus:hover, .message-notification-group .flat.message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button.flat:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(#1a1a1a, 7%), #ffffff, 87%), 5%); } +- .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, ++ .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.next-button:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, + .message .message-header .flat.message-close-button:hover, .message-notification-group .flat.message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button.flat:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(#1a1a1a, 7%), #ffffff, 87%); } +- .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, ++ .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.next-button:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-auth-menu-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, + .message .message-header .flat.message-close-button:insensitive, .message-notification-group .flat.message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button.flat:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: st-transparentize(#ffffff, 0.5); +@@ -142,33 +145,33 @@ stage { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +- .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, +- .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, ++ .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.next-button:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-auth-menu-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, ++ .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.next-button:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-auth-menu-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, + .message .message-header .flat.message-close-button:active, .message-notification-group .flat.message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button.flat:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(#1a1a1a, 9%), #ffffff, 87%); } +- .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, +- .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, ++ .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.next-button:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, ++ .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.next-button:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, + .message .message-header .flat.message-close-button:active:hover, .message-notification-group .flat.message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button.flat:active:hover { + background-color: st-lighten(st-lighten(#1a1a1a, 9%), 7%); } +- .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, +- .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, ++ .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.next-button:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, ++ .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.next-button:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, + .message .message-header .flat.message-close-button:active:focus, .message-notification-group .flat.message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button.flat:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(#1a1a1a, 9%), #ffffff, 87%), 5%); } +- .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, ++ .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.next-button:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-auth-menu-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, + .message .message-header .flat.message-close-button:checked, .message-notification-group .flat.message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button.flat:checked { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(#1a1a1a, 8%), #ffffff, 87%); } +- .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, ++ .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.next-button:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, + .message .message-header .flat.message-close-button:checked:hover, .message-notification-group .flat.message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button.flat:checked:hover { + background-color: st-lighten(st-lighten(#1a1a1a, 8%), 7%); } +- .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, ++ .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.next-button:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, + .message .message-header .flat.message-close-button:checked:active, .message-notification-group .flat.message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button.flat:checked:active { + background-color: st-lighten(st-lighten(#1a1a1a, 8%), 9%); } + +-.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, ++.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.next-button, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-auth-menu-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, + .message .message-header .default.message-close-button, .message-notification-group .default.message-collapse-button, .calendar .calendar-month-header .default.pager-button, .button.default { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); +@@ -176,7 +179,7 @@ stage { + border: none; + color: -st-accent-fg-color; + background-color: st-mix(-st-accent-color, #ffffff, 87%); } +- .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, ++ .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.next-button:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-auth-menu-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, + .message .message-header .default.message-close-button:focus, .message-notification-group .default.message-collapse-button:focus, .calendar .calendar-month-header .default.pager-button:focus, .button.default:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); +@@ -186,10 +189,10 @@ stage { + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; + box-shadow: inset 0 0 0 2px st-transparentize(st-lighten(-st-accent-color, 30%), 0.1) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(-st-accent-color, #ffffff, 87%), 5%); } +- .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, ++ .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.next-button:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, + .message .message-header .default.message-close-button:focus:hover, .message-notification-group .default.message-collapse-button:focus:hover, .calendar .calendar-month-header .default.pager-button:focus:hover, .button.default:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(-st-accent-color, 4%), #ffffff, 87%), 5%); } +- .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, ++ .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.next-button:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-auth-menu-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, + .message .message-header .default.message-close-button:hover, .message-notification-group .default.message-collapse-button:hover, .calendar .calendar-month-header .default.pager-button:hover, .button.default:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); +@@ -197,7 +200,7 @@ stage { + border: none; + color: -st-accent-fg-color; + background-color: st-mix(st-lighten(-st-accent-color, 4%), #ffffff, 87%); } +- .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, ++ .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.next-button:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-auth-menu-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, + .message .message-header .default.message-close-button:insensitive, .message-notification-group .default.message-collapse-button:insensitive, .calendar .calendar-month-header .default.pager-button:insensitive, .button.default:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); +@@ -208,7 +211,7 @@ stage { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +- .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, ++ .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.next-button:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-auth-menu-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, + .message .message-header .default.message-close-button:active, .message-notification-group .default.message-collapse-button:active, .calendar .calendar-month-header .default.pager-button:active, .button.default:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); +@@ -216,10 +219,10 @@ stage { + border: none; + color: -st-accent-fg-color; + background-color: st-mix(st-lighten(-st-accent-color, 9%), #ffffff, 87%); } +- .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, ++ .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.next-button:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-auth-menu-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, + .message .message-header .default.message-close-button:active:hover, .message-notification-group .default.message-collapse-button:active:hover, .calendar .calendar-month-header .default.pager-button:active:hover, .button.default:active:hover { + background-color: st-lighten(st-lighten(-st-accent-color, 9%), 4%); } +- .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, ++ .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.next-button:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-auth-menu-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, + .message .message-header .default.message-close-button:active:focus, .message-notification-group .default.message-collapse-button:active:focus, .calendar .calendar-month-header .default.pager-button:active:focus, .button.default:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(-st-accent-color, 9%), #ffffff, 87%), 5%); } + +@@ -439,7 +442,7 @@ StEntry { + .modal-dialog .modal-dialog-button-box .modal-dialog-button:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: st-transparentize(#ffffff, 0.5); +- background-color: st-darken(st-mix(#ffffff, #1a1a1a, 9%), 3%); ++ background-color: rgba(255, 255, 255, 0.1); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +@@ -468,12 +471,13 @@ StEntry { + font-weight: 700; + font-size: 1.364em; } + +-.login-dialog-auth-list-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, ++.login-dialog-auth-list-title-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, + .polkit-dialog-user-layout .polkit-dialog-user-root-label, .message-dialog-content .message-dialog-title.lightweight { + font-weight: 700; + font-size: 1.182em; } + +-.login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { ++.login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels, .login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle, .login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { + font-weight: 700; + font-size: 1em; } + +@@ -491,7 +495,7 @@ StEntry { + font-weight: 400; + font-size: 0.818em; } + +-#LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { ++.web-login-url-label, #LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { + font-family: monospace; } + + .unlock-dialog-clock .unlock-dialog-clock-time, #panel, .weather-button .weather-box .weather-grid .weather-forecast-temp, .world-clocks-button .world-clocks-grid .world-clocks-timezone, .world-clocks-button .world-clocks-grid .world-clocks-time, .events-button .events-box .events-list .event-box .event-time, .calendar .calendar-day-heading, .calendar .calendar-day { +@@ -641,43 +645,43 @@ StEntry { + .login-dialog .login-dialog-prompt-entry StLabel.hint-text, .app-folder-dialog .folder-name-container .folder-name-entry StLabel.hint-text, .search-entry StLabel.hint-text { + color: rgba(255, 255, 255, 0.7); } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-mix(#ffffff, #0d0d0d, 9%), #ffffff, 87%); } +- .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { ++ .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: st-transparentize(#ffffff, 0.5); + background-color: st-darken(st-mix(#ffffff, #0d0d0d, 9%), 3%); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +- .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { ++ .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-mix(#ffffff, #0d0d0d, 9%), #ffffff, 87%), 5%); } +- .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { ++ .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 4%), #ffffff, 87%), 5%); } +- .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { ++ .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 4%), #ffffff, 87%); } +- .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active { ++ .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 9%), #ffffff, 87%); } +- .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { ++ .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 9%), 4%); } +- .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { ++ .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 9%), #ffffff, 87%), 5%); } +- .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { ++ .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 8%), #ffffff, 87%); } +- .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { ++ .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 8%), 4%); } +- .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { ++ .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #0d0d0d, 9%), 8%), 9%); } + + /* Lockscreen Elements */ +@@ -693,10 +697,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, + .unlock-dialog .screenshot-ui-show-pointer-button, ++.unlock-dialog .login-dialog-button.next-button, + .unlock-dialog .login-dialog-button.a11y-button, + .unlock-dialog .login-dialog-button.cancel-button, + .unlock-dialog .login-dialog-button.switch-user-button, +-.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .login-dialog-auth-list-item { ++.unlock-dialog .login-dialog-button.login-dialog-auth-menu-button, ++.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(rgba(255, 255, 255, 0.1), #ffffff, 87%); } +@@ -712,10 +719,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus, + .message .message-header .unlock-dialog .message-close-button:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:focus, ++ .unlock-dialog .login-dialog-button.next-button:focus, + .unlock-dialog .login-dialog-button.a11y-button:focus, + .unlock-dialog .login-dialog-button.cancel-button:focus, + .unlock-dialog .login-dialog-button.switch-user-button:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .login-dialog-auth-list-item:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .web-login-intro-button:focus, ++ .unlock-dialog .web-login-prompt-button:focus, .unlock-dialog .login-dialog-auth-list-title:focus, .unlock-dialog .login-dialog-auth-list-item:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; +@@ -732,10 +742,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus:hover, + .message .message-header .unlock-dialog .message-close-button:focus:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:focus:hover, ++ .unlock-dialog .login-dialog-button.next-button:focus:hover, + .unlock-dialog .login-dialog-button.a11y-button:focus:hover, + .unlock-dialog .login-dialog-button.cancel-button:focus:hover, + .unlock-dialog .login-dialog-button.switch-user-button:focus:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .web-login-intro-button:focus:hover, ++ .unlock-dialog .web-login-prompt-button:focus:hover, .unlock-dialog .login-dialog-auth-list-title:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(rgba(255, 255, 255, 0.13), #ffffff, 87%), 5%); } + .unlock-dialog-notifications-container .message StButton:hover, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:hover, .unlock-dialog .button:hover, +@@ -749,10 +762,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:hover, + .message .message-header .unlock-dialog .message-close-button:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:hover, ++ .unlock-dialog .login-dialog-button.next-button:hover, + .unlock-dialog .login-dialog-button.a11y-button:hover, + .unlock-dialog .login-dialog-button.cancel-button:hover, + .unlock-dialog .login-dialog-button.switch-user-button:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .login-dialog-auth-list-item:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .web-login-intro-button:hover, ++ .unlock-dialog .web-login-prompt-button:hover, .unlock-dialog .login-dialog-auth-list-title:hover, .unlock-dialog .login-dialog-auth-list-item:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(rgba(255, 255, 255, 0.13), #ffffff, 87%); } +@@ -768,10 +784,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active, + .message .message-header .unlock-dialog .message-close-button:active, + .unlock-dialog .screenshot-ui-show-pointer-button:active, ++ .unlock-dialog .login-dialog-button.next-button:active, + .unlock-dialog .login-dialog-button.a11y-button:active, + .unlock-dialog .login-dialog-button.cancel-button:active, + .unlock-dialog .login-dialog-button.switch-user-button:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .login-dialog-auth-list-item:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .web-login-intro-button:active, ++ .unlock-dialog .web-login-prompt-button:active, .unlock-dialog .login-dialog-auth-list-title:active, .unlock-dialog .login-dialog-auth-list-item:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(rgba(255, 255, 255, 0.16), #ffffff, 87%); } +@@ -787,10 +806,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .unlock-dialog .message-close-button:active:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:active:hover, ++ .unlock-dialog .login-dialog-button.next-button:active:hover, + .unlock-dialog .login-dialog-button.a11y-button:active:hover, + .unlock-dialog .login-dialog-button.cancel-button:active:hover, + .unlock-dialog .login-dialog-button.switch-user-button:active:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .web-login-intro-button:active:hover, ++ .unlock-dialog .web-login-prompt-button:active:hover, .unlock-dialog .login-dialog-auth-list-title:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { + background-color: rgba(255, 255, 255, 0.19); } + .unlock-dialog-notifications-container .message StButton:active:focus, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:active:focus, .unlock-dialog .button:active:focus, +@@ -804,10 +826,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .unlock-dialog .message-close-button:active:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:active:focus, ++ .unlock-dialog .login-dialog-button.next-button:active:focus, + .unlock-dialog .login-dialog-button.a11y-button:active:focus, + .unlock-dialog .login-dialog-button.cancel-button:active:focus, + .unlock-dialog .login-dialog-button.switch-user-button:active:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .web-login-intro-button:active:focus, ++ .unlock-dialog .web-login-prompt-button:active:focus, .unlock-dialog .login-dialog-auth-list-title:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(rgba(255, 255, 255, 0.16), #ffffff, 87%), 5%); } + .unlock-dialog-notifications-container .message StButton:checked, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked, .unlock-dialog .button:checked, +@@ -821,10 +846,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked, + .message .message-header .unlock-dialog .message-close-button:checked, + .unlock-dialog .screenshot-ui-show-pointer-button:checked, ++ .unlock-dialog .login-dialog-button.next-button:checked, + .unlock-dialog .login-dialog-button.a11y-button:checked, + .unlock-dialog .login-dialog-button.cancel-button:checked, + .unlock-dialog .login-dialog-button.switch-user-button:checked, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .login-dialog-auth-list-item:checked { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .web-login-intro-button:checked, ++ .unlock-dialog .web-login-prompt-button:checked, .unlock-dialog .login-dialog-auth-list-title:checked, .unlock-dialog .login-dialog-auth-list-item:checked { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), #ffffff, 87%); } +@@ -840,10 +868,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:hover, + .message .message-header .unlock-dialog .message-close-button:checked:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:hover, ++ .unlock-dialog .login-dialog-button.next-button:checked:hover, + .unlock-dialog .login-dialog-button.a11y-button:checked:hover, + .unlock-dialog .login-dialog-button.cancel-button:checked:hover, + .unlock-dialog .login-dialog-button.switch-user-button:checked:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .web-login-intro-button:checked:hover, ++ .unlock-dialog .web-login-prompt-button:checked:hover, .unlock-dialog .login-dialog-auth-list-title:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), 4%); } + .unlock-dialog-notifications-container .message StButton:checked:active, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked:active, .unlock-dialog .button:checked:active, +@@ -857,10 +888,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:active, + .message .message-header .unlock-dialog .message-close-button:checked:active, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:active, ++ .unlock-dialog .login-dialog-button.next-button:checked:active, + .unlock-dialog .login-dialog-button.a11y-button:checked:active, + .unlock-dialog .login-dialog-button.cancel-button:checked:active, + .unlock-dialog .login-dialog-button.switch-user-button:checked:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .web-login-intro-button:checked:active, ++ .unlock-dialog .web-login-prompt-button:checked:active, .unlock-dialog .login-dialog-auth-list-title:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #1a1a1a, 9%), 8%), 9%); } + .unlock-dialog-notifications-container .message StButton:insensitive, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:insensitive, .unlock-dialog .button:insensitive, +@@ -874,13 +908,16 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:insensitive, + .message .message-header .unlock-dialog .message-close-button:insensitive, + .unlock-dialog .screenshot-ui-show-pointer-button:insensitive, ++ .unlock-dialog .login-dialog-button.next-button:insensitive, + .unlock-dialog .login-dialog-button.a11y-button:insensitive, + .unlock-dialog .login-dialog-button.cancel-button:insensitive, + .unlock-dialog .login-dialog-button.switch-user-button:insensitive, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:insensitive, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .web-login-intro-button:insensitive, ++ .unlock-dialog .web-login-prompt-button:insensitive, .unlock-dialog .login-dialog-auth-list-title:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: st-transparentize(#ffffff, 0.5); +- background-color: st-darken(st-mix(#ffffff, #1a1a1a, 9%), 3%); ++ background-color: rgba(255, 255, 255, 0.1); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + box-shadow: none; + border: none; } +@@ -907,6 +944,7 @@ StEntry { + .unlock-dialog .login-dialog-prompt-entry StLabel.hint-text { + color: rgba(255, 255, 255, 0.7); } + ++/* Login Dialog Elements */ + /* WIDGETS */ + .shell-link { + color: st-lighten(-st-accent-color, 20%); } +@@ -931,18 +969,18 @@ StEntry StIcon.peek-password { + padding: 0 4px; } + + StEntry StLabel.hint-text { +- margin-left: 2px; } ++ margin-left: 8px; } + + /* Buttons */ + .button { + min-height: 1.5em; } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button { + border-radius: 999px; + padding: 0.818em; + min-height: 1.091em; } +- .icon-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, ++ .icon-button StIcon, .login-dialog-button.next-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-auth-menu-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, + .message .message-header .message-close-button StIcon, .message-notification-group .message-collapse-button StIcon, .calendar .calendar-month-header .pager-button StIcon { + icon-size: 1.091em; + -st-icon-style: symbolic; } +@@ -2032,6 +2070,9 @@ StScrollBar { min-width: 0.5455em; min-height: 0.5455em; background-color: #ffffff; } @@ -37,20 +1411,1312 @@ index 4bb622aa26..8f94fa4101 100644 #panel .panel-button.screen-recording-indicator StBoxLayout, #panel .panel-button.screen-sharing-indicator StBoxLayout { spacing: 0.409em; } #panel .panel-button.screen-recording-indicator StIcon, #panel .panel-button.screen-sharing-indicator StIcon { +@@ -2233,7 +2274,7 @@ StScrollBar { + .quick-settings { + padding: 18px; + border-radius: 36px; } +- .quick-settings .icon-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, ++ .quick-settings .icon-button, .quick-settings .login-dialog-button.next-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-auth-menu-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, + .quick-settings .message .message-header .message-close-button, + .message .message-header .quick-settings .message-close-button, .quick-settings .screenshot-ui-show-pointer-button, .quick-settings .button { + padding: 10.5px; } +@@ -2361,7 +2402,7 @@ StScrollBar { + .quick-slider > StBoxLayout { + spacing: 6px; } + +-.quick-slider .icon-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, ++.quick-slider .icon-button, .quick-slider .login-dialog-button.next-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-auth-menu-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, + .quick-slider .message .message-header .message-close-button, + .message .message-header .quick-slider .message-close-button, .quick-slider .screenshot-ui-show-pointer-button { + padding: 6px; } +@@ -2443,16 +2484,16 @@ StScrollBar { + icon-size: 32px !important; + -st-icon-style: regular !important; } + +-.background-app-item .icon-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, ++.background-app-item .icon-button, .background-app-item .login-dialog-button.next-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-auth-menu-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, + .background-app-item .message .message-header .message-close-button, + .message .message-header .background-app-item .message-close-button, .background-app-item .screenshot-ui-show-pointer-button { + padding: 6px; + background-color: rgba(255, 255, 255, 0.13); } +- .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, ++ .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.next-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, + .background-app-item .message .message-header .message-close-button:hover, + .message .message-header .background-app-item .message-close-button:hover, .background-app-item .screenshot-ui-show-pointer-button:hover { + background-color: rgba(255, 255, 255, 0.22); } +- .background-app-item .icon-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, ++ .background-app-item .icon-button:active, .background-app-item .login-dialog-button.next-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, + .background-app-item .message .message-header .message-close-button:active, + .message .message-header .background-app-item .message-close-button:active, .background-app-item .screenshot-ui-show-pointer-button:active { + background-color: rgba(255, 255, 255, 0.31); } +@@ -2839,29 +2880,29 @@ StScrollBar { + padding-bottom: 0; } + .app-folder-dialog .folder-name-container .folder-name-entry { + width: 12em; } +- .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, ++ .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.next-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, + .app-folder-dialog .message .message-header .message-close-button, + .message .message-header .app-folder-dialog .message-close-button, .app-folder-dialog .screenshot-ui-show-pointer-button { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-mix(#ffffff, #252525, 9%), #ffffff, 87%); } +- .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, ++ .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.next-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, + .app-folder-dialog .message .message-header .message-close-button:hover, + .message .message-header .app-folder-dialog .message-close-button:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #252525, 9%), 4%), #ffffff, 87%); } +- .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, ++ .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.next-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, + .app-folder-dialog .message .message-header .message-close-button:active, + .message .message-header .app-folder-dialog .message-close-button:active, .app-folder-dialog .screenshot-ui-show-pointer-button:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + background-color: st-mix(st-lighten(st-mix(#ffffff, #252525, 9%), 9%), #ffffff, 87%); } +- .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, ++ .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.next-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, + .app-folder-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .app-folder-dialog .message-close-button:active:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #252525, 9%), 9%), 4%); } +- .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, ++ .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.next-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, + .app-folder-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .app-folder-dialog .message-close-button:active:focus, .app-folder-dialog .screenshot-ui-show-pointer-button:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #252525, 9%), 9%), #ffffff, 87%), 5%); } +@@ -3362,6 +3403,13 @@ StScrollBar { + background-color: st-lighten(-st-accent-color, 5%); + color: st-lighten(-st-accent-fg-color, 5%); } + ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #ffffff; ++ border-color: #ffffff; ++ color: #0d0d0d; } ++ + .login-dialog, + .unlock-dialog { + color: #ffffff; } +@@ -3374,19 +3422,62 @@ StScrollBar { + .unlock-dialog .login-dialog-prompt-layout { + width: 25em; + spacing: 9px; } ++ .login-dialog .login-dialog-prompt-layout.web-login-active, ++ .unlock-dialog .login-dialog-prompt-layout.web-login-active { ++ width: 37.5em; } ++ .login-dialog .login-dialog-prompt-entry-area, ++ .unlock-dialog .login-dialog-prompt-entry-area { ++ margin: 0.5em 20px; } ++ .login-dialog .login-dialog-prompt-entry, ++ .unlock-dialog .login-dialog-prompt-entry { ++ border-radius: 12px; ++ padding-right: 3em; } ++ .login-dialog .login-dialog-default-button-well, ++ .unlock-dialog .login-dialog-default-button-well { ++ margin-right: 1em; } + + .login-dialog-bottom-button-group { + padding: 32px; + spacing: 16px; } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + padding: 1.091em; } + ++.login-dialog-button.next-button { ++ background-color: transparent !important; ++ padding: 0; } ++ + .login-dialog-button.cancel-button { +- padding: 9px; } ++ padding: 12px; } ++ ++.login-dialog-auth-menu-button-popup { ++ padding: 18px; ++ margin-right: 12px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header { ++ font-size: 0.909em; ++ text-align: center; ++ font-weight: bold; ++ padding-top: 18px; ++ padding-bottom: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header:first-child { ++ padding-top: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator { ++ spacing: 3px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-name { ++ font-size: 1.159em; ++ font-weight: bold; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-description { ++ font-size: 0.977em; } ++ ++.login-dialog-auth-menu-button-indicator { ++ background-color: transparent !important; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons { ++ spacing: 18px; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons .login-dialog-auth-menu-button-indicator-icon { ++ icon-size: 2em; } + + .login-dialog-button-box { +- spacing: 12px; } ++ height: 4em; } + + .conflicting-session-dialog-content { + spacing: 20px; } +@@ -3455,53 +3546,112 @@ StScrollBar { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(#000000, 9%), #ffffff, 87%), 5%); } + + .login-dialog-auth-list-view { +- -st-vfade-offset: 3em; } ++ -st-vfade-offset: 3em; ++ max-height: 13em; } + + .login-dialog-auth-list { +- spacing: 6px; +- margin-left: 2em; } +- +-.login-dialog-auth-list-title { +- margin-left: 2em; +- padding-bottom: 6px; } ++ spacing: 9px; } + ++.login-dialog .login-dialog-auth-list-title, + .login-dialog .login-dialog-auth-list-item { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; +- background-color: st-mix(st-mix(#ffffff, #000000, 9%), #ffffff, 87%); +- border-radius: 9.6px; +- padding: 7.2px; } +- .login-dialog .login-dialog-auth-list-item:selected, .login-dialog .login-dialog-auth-list-item:focus { ++ background-color: st-mix(#1a1a1a, #ffffff, 87%); ++ border-radius: 12px; } ++ .login-dialog .login-dialog-auth-list-title:selected, .login-dialog .login-dialog-auth-list-title:focus, ++ .login-dialog .login-dialog-auth-list-item:selected, ++ .login-dialog .login-dialog-auth-list-item:focus { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; + box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-mix(#ffffff, #000000, 9%), #ffffff, 87%), 5%); } +- .login-dialog .login-dialog-auth-list-item:selected:hover, .login-dialog .login-dialog-auth-list-item:focus:hover { +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 4%), #ffffff, 87%), 5%); } ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(#1a1a1a, #ffffff, 87%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:selected:hover, .login-dialog .login-dialog-auth-list-title:focus:hover, ++ .login-dialog .login-dialog-auth-list-item:selected:hover, ++ .login-dialog .login-dialog-auth-list-item:focus:hover { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(#1a1a1a, 4%), #ffffff, 87%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:hover, + .login-dialog .login-dialog-auth-list-item:hover { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; +- background-color: st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 4%), #ffffff, 87%); } ++ background-color: st-mix(st-lighten(#1a1a1a, 4%), #ffffff, 87%); } ++ .login-dialog .login-dialog-auth-list-title:active, + .login-dialog .login-dialog-auth-list-item:active { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); + color: #ffffff; +- background-color: st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), #ffffff, 87%); } ++ background-color: st-mix(st-lighten(#1a1a1a, 9%), #ffffff, 87%); } ++ .login-dialog .login-dialog-auth-list-title:active:hover, + .login-dialog .login-dialog-auth-list-item:active:hover { +- background-color: st-lighten(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), 4%); } ++ background-color: st-lighten(st-lighten(#1a1a1a, 9%), 4%); } ++ .login-dialog .login-dialog-auth-list-title:active:focus, + .login-dialog .login-dialog-auth-list-item:active:focus { +- background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), #ffffff, 87%), 5%); } ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(#1a1a1a, 9%), #ffffff, 87%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:insensitive, ++ .login-dialog .login-dialog-auth-list-item:insensitive { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: st-transparentize(#ffffff, 0.5); ++ background-color: st-darken(#1a1a1a, 3%); ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ box-shadow: none; ++ border: none; } ++ ++.login-dialog .login-dialog-auth-list-title { ++ background-color: rgba(255, 255, 255, 0.02) !important; ++ color: #ffffff !important; ++ margin: 0.5em 20px; } ++ ++.login-dialog .login-dialog-auth-list-item { ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } + + .unlock-dialog .login-dialog-auth-list-item { +- border-radius: 9.6px; +- padding: 7.2px; } ++ border-radius: 12px; ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } ++ ++.unlock-dialog .login-dialog-auth-list-title { ++ background-color: transparent !important; ++ color: #ffffff !important; ++ padding: 0; ++ margin: 0; } ++ ++.login-dialog-auth-list-title-label { ++ padding: 6px; ++ text-align: center; } ++ ++.login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle { ++ text-align: center; ++ padding: 1.8px 0; } + +-.login-dialog-auth-list-label:ltr { +- padding-left: 15px; +- text-align: left; } ++.login-dialog-auth-list-item-title { ++ color: #ffffff; } + +-.login-dialog-auth-list-label:rtl { +- padding-right: 15px; +- text-align: right; } ++.login-dialog-auth-list-item-subtitle { ++ color: #cccccc; ++ font-weight: 500; } ++ ++.login-dialog-item-icon { ++ width: 1.3em; ++ height: 1.3em; ++ color: #ffffff; ++ padding: 4.2px 6px; ++ border-radius: 8px; } ++ .login-dialog-item-icon:hover { ++ background-color: #0d0d0d; } ++ .login-dialog-item-icon-popup.popup-menu { ++ min-width: 0; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels { ++ spacing: 3px; ++ text-align: center; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :first-child { ++ color: #b3b3b3; ++ font-weight: 500; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :last-child { ++ color: #ffffff; } + + .login-dialog-user-list-view { + width: 25em; +@@ -3534,6 +3684,13 @@ StScrollBar { + background-color: st-lighten(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), 4%); } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:active:focus { + background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), #ffffff, 87%), 5%); } ++ .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:insensitive { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: st-transparentize(#ffffff, 0.5); ++ background-color: st-darken(st-mix(#ffffff, #000000, 9%), 3%); ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ box-shadow: none; ++ border: none; } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .user-icon { + border: 2px solid transparent; } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .login-dialog-timed-login-indicator { +@@ -3545,6 +3702,110 @@ StScrollBar { + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:logged-in .user-icon StIcon { + background-color: st-transparentize(-st-accent-color, 0.7); } + ++.web-login-spinner { ++ background-color: rgba(0, 0, 0, 0.5); ++ border: 5px rgba(0, 0, 0, 0); ++ border-radius: 50px; } ++ ++.web-login-title-label { ++ font-size: 1em; ++ color: #b3b3b3; ++ text-align: center; } ++ ++.web-login-url-label { ++ font-size: 1em; ++ color: #ffffff; ++ text-align: center; } ++ .web-login-url-label.web-login-url-label-long { ++ font-size: 0.818em; } ++ ++.web-login-code-title-label { ++ font-size: 1em; ++ color: #ffffff; ++ text-align: center; } ++ ++.web-login-code-label { ++ font-size: 1em; ++ color: #ffffff; ++ font-weight: bold; ++ text-align: center; } ++ ++.web-login-prompt { ++ padding-top: 6px; ++ padding-bottom: 6px; ++ padding-left: 27px; ++ padding-right: 27px; ++ spacing: 1.75em; } ++ ++.web-login-button-label { ++ font-size: 1.182em; ++ color: #ffffff; ++ min-width: 12em; ++ text-align: center; ++ font-weight: bold; } ++ ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: #ffffff; ++ background-color: st-mix(st-mix(#ffffff, #000000, 9%), #ffffff, 87%); ++ border-radius: 32px; } ++ .login-dialog .web-login-intro-button:selected, .login-dialog .web-login-intro-button:focus, ++ .login-dialog .web-login-prompt-button:selected, ++ .login-dialog .web-login-prompt-button:focus { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: #ffffff; ++ box-shadow: inset 0 0 0 2px st-transparentize(st-mix(-st-accent-color, #ffffff, 60%), 0.1) !important; ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-mix(#ffffff, #000000, 9%), #ffffff, 87%), 5%); } ++ .login-dialog .web-login-intro-button:selected:hover, .login-dialog .web-login-intro-button:focus:hover, ++ .login-dialog .web-login-prompt-button:selected:hover, ++ .login-dialog .web-login-prompt-button:focus:hover { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 4%), #ffffff, 87%), 5%); } ++ .login-dialog .web-login-intro-button:hover, ++ .login-dialog .web-login-prompt-button:hover { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: #ffffff; ++ background-color: st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 4%), #ffffff, 87%); } ++ .login-dialog .web-login-intro-button:active, ++ .login-dialog .web-login-prompt-button:active { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: #ffffff; ++ background-color: st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), #ffffff, 87%); } ++ .login-dialog .web-login-intro-button:active:hover, ++ .login-dialog .web-login-prompt-button:active:hover { ++ background-color: st-lighten(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), 4%); } ++ .login-dialog .web-login-intro-button:active:focus, ++ .login-dialog .web-login-prompt-button:active:focus { ++ background-color: st-mix(st-mix(-st-accent-color, #ffffff, 60%), st-mix(st-lighten(st-mix(#ffffff, #000000, 9%), 9%), #ffffff, 87%), 5%); } ++ .login-dialog .web-login-intro-button:insensitive, ++ .login-dialog .web-login-prompt-button:insensitive { ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ color: st-transparentize(#ffffff, 0.5); ++ background-color: st-darken(st-mix(#ffffff, #000000, 9%), 3%); ++ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); ++ box-shadow: none; ++ border: none; } ++ ++.login-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.login-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ ++.unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button { ++ border-radius: 32px; } ++ ++.unlock-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.unlock-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ + .unlock-dialog { + background-color: transparent; } + +@@ -3655,3 +3916,10 @@ StScrollBar { + .login-dialog .user-widget.vertical .user-icon StIcon, + .unlock-dialog .user-widget.vertical .user-icon StIcon { + padding: 30px; } ++ ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #ffffff; ++ border-color: #ffffff; ++ color: #000000; } diff --git a/data/theme/gnome-shell-light.css b/data/theme/gnome-shell-light.css -index 82a5b0bc18..128aa93dea 100644 +index 2b67846..792690e 100644 --- a/data/theme/gnome-shell-light.css +++ b/data/theme/gnome-shell-light.css -@@ -1720,6 +1720,9 @@ StScrollBar { +@@ -42,7 +42,9 @@ stage { + text-align: center; + transition-duration: 100ms; } + +-.login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button, .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item, .login-dialog .login-dialog-auth-list-title, ++.login-dialog .login-dialog-auth-list-item, .login-dialog-not-listed-button, .unlock-dialog-notifications-container .message StButton, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton, .unlock-dialog .button, + .unlock-dialog .icon-button, + .unlock-dialog .message-notification-group .message-collapse-button, +@@ -51,131 +53,132 @@ stage { + .message .message-header .unlock-dialog .message-expand-button, + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, +-.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, +-#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, ++.unlock-dialog .screenshot-ui-show-pointer-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item, #LookingGlassDialog .notebook-tab, .screenshot-ui-show-pointer-button, .screenshot-ui-type-button, #LookingGlassDebugFlags .lg-debug-flag-button, #LookingGlassPropertyInspector .lg-obj-inspector-close-button, ++#LookingGlassPropertyInspector .lg-obj-inspector-button, #LookingGlassDialog > #Toolbar .lg-toolbar-button, .icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + border-radius: 8px; + padding: 3px 24px; + font-weight: bold; + transition: border-width 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94), box-shadow 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button, .button { + color: #222226; + background-color: st-mix(#222226, #fafafb, 12%); } +- .icon-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, ++ .icon-button:focus, .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus, .screenshot-ui-show-pointer-button:focus, .message .message-header .message-expand-button:focus, + .message .message-header .message-close-button:focus, .message-notification-group .message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button:focus { + color: #222226; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; + background-color: st-mix(-st-accent-color, st-mix(#222226, #fafafb, 12%), 5%); } +- .icon-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, ++ .icon-button:focus:hover, .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover, .screenshot-ui-show-pointer-button:focus:hover, .message .message-header .message-expand-button:focus:hover, + .message .message-header .message-close-button:focus:hover, .message-notification-group .message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button:focus:hover { + background-color: st-mix(-st-accent-color, st-darken(st-mix(#222226, #fafafb, 12%), 4%), 5%); } +- .icon-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, ++ .icon-button:hover, .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover, .screenshot-ui-show-pointer-button:hover, .message .message-header .message-expand-button:hover, + .message .message-header .message-close-button:hover, .message-notification-group .message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button:hover { + color: #222226; + background-color: st-darken(st-mix(#222226, #fafafb, 12%), 4%); } +- .icon-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, ++ .icon-button:insensitive, .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive, .screenshot-ui-show-pointer-button:insensitive, .message .message-header .message-expand-button:insensitive, + .message .message-header .message-close-button:insensitive, .message-notification-group .message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button:insensitive { + color: st-transparentize(#222226, 0.6); + background-color: st-lighten(st-mix(#222226, #fafafb, 12%), 3%); } +- .icon-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, +- .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, ++ .icon-button:selected, .login-dialog-button.next-button:selected, .login-dialog-button.a11y-button:selected, .login-dialog-button.cancel-button:selected, .login-dialog-button.switch-user-button:selected, .login-dialog-button.login-dialog-auth-menu-button:selected, .login-dialog-button.login-dialog-session-list-button:selected, .screenshot-ui-show-pointer-button:selected, .message .message-header .message-expand-button:selected, ++ .message .message-header .message-close-button:selected, .message-notification-group .message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button:selected, .icon-button:active, .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active, .screenshot-ui-show-pointer-button:active, .message .message-header .message-expand-button:active, + .message .message-header .message-close-button:active, .message-notification-group .message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button:active { + color: #222226; + background-color: st-darken(st-mix(#222226, #fafafb, 12%), 9%); } +- .icon-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, +- .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, ++ .icon-button:selected:hover, .login-dialog-button.next-button:selected:hover, .login-dialog-button.a11y-button:selected:hover, .login-dialog-button.cancel-button:selected:hover, .login-dialog-button.switch-user-button:selected:hover, .login-dialog-button.login-dialog-auth-menu-button:selected:hover, .login-dialog-button.login-dialog-session-list-button:selected:hover, .screenshot-ui-show-pointer-button:selected:hover, .message .message-header .message-expand-button:selected:hover, ++ .message .message-header .message-close-button:selected:hover, .message-notification-group .message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button:selected:hover, .icon-button:active:hover, .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover, .screenshot-ui-show-pointer-button:active:hover, .message .message-header .message-expand-button:active:hover, + .message .message-header .message-close-button:active:hover, .message-notification-group .message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button:active:hover { + background-color: st-darken(st-darken(st-mix(#222226, #fafafb, 12%), 9%), 4%); } +- .icon-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, +- .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, ++ .icon-button:selected:focus, .login-dialog-button.next-button:selected:focus, .login-dialog-button.a11y-button:selected:focus, .login-dialog-button.cancel-button:selected:focus, .login-dialog-button.switch-user-button:selected:focus, .login-dialog-button.login-dialog-auth-menu-button:selected:focus, .login-dialog-button.login-dialog-session-list-button:selected:focus, .screenshot-ui-show-pointer-button:selected:focus, .message .message-header .message-expand-button:selected:focus, ++ .message .message-header .message-close-button:selected:focus, .message-notification-group .message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button:selected:focus, .icon-button:active:focus, .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus, .screenshot-ui-show-pointer-button:active:focus, .message .message-header .message-expand-button:active:focus, + .message .message-header .message-close-button:active:focus, .message-notification-group .message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button:active:focus { + background-color: st-mix(-st-accent-color, st-darken(st-mix(#222226, #fafafb, 12%), 9%), 5%); } +- .icon-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, ++ .icon-button:checked, .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked, .screenshot-ui-show-pointer-button:checked, .message .message-header .message-expand-button:checked, + .message .message-header .message-close-button:checked, .message-notification-group .message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button:checked { + color: #222226; + background-color: st-darken(st-mix(#222226, #fafafb, 12%), 8%); } +- .icon-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, ++ .icon-button:checked:hover, .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover, .screenshot-ui-show-pointer-button:checked:hover, .message .message-header .message-expand-button:checked:hover, + .message .message-header .message-close-button:checked:hover, .message-notification-group .message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button:checked:hover { + background-color: st-darken(st-darken(st-mix(#222226, #fafafb, 12%), 8%), 4%); } +- .icon-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, ++ .icon-button:checked:active, .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active, .screenshot-ui-show-pointer-button:checked:active, .message .message-header .message-expand-button:checked:active, + .message .message-header .message-close-button:checked:active, .message-notification-group .message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button:checked:active { + background-color: st-darken(st-darken(st-mix(#222226, #fafafb, 12%), 8%), 9%); } + +-.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, ++.calendar .calendar-day-heading, .calendar .calendar-day, .calendar .calendar-month-header .calendar-month-label, .icon-button.flat, .flat.login-dialog-button.next-button, .flat.login-dialog-button.a11y-button, .flat.login-dialog-button.cancel-button, .flat.login-dialog-button.switch-user-button, .flat.login-dialog-button.login-dialog-auth-menu-button, .flat.login-dialog-button.login-dialog-session-list-button, .flat.screenshot-ui-show-pointer-button, .message .message-header .flat.message-expand-button, + .message .message-header .flat.message-close-button, .message-notification-group .flat.message-collapse-button, .calendar .calendar-month-header .pager-button, .button.flat { + color: #222226; + background-color: #fafafb; } +- .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, ++ .calendar .calendar-day-heading:focus, .calendar .calendar-day:focus, .calendar .calendar-month-header .calendar-month-label:focus, .icon-button.flat:focus, .flat.login-dialog-button.next-button:focus, .flat.login-dialog-button.a11y-button:focus, .flat.login-dialog-button.cancel-button:focus, .flat.login-dialog-button.switch-user-button:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:focus, .flat.login-dialog-button.login-dialog-session-list-button:focus, .flat.screenshot-ui-show-pointer-button:focus, .message .message-header .flat.message-expand-button:focus, + .message .message-header .flat.message-close-button:focus, .message-notification-group .flat.message-collapse-button:focus, .calendar .calendar-month-header .pager-button:focus, .button.flat:focus { + color: #222226; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; + background-color: st-mix(-st-accent-color, rgba(250, 250, 251, 0.25), 5%); } +- .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, ++ .calendar .calendar-day-heading:focus:hover, .calendar .calendar-day:focus:hover, .calendar .calendar-month-header .calendar-month-label:focus:hover, .icon-button.flat:focus:hover, .flat.login-dialog-button.next-button:focus:hover, .flat.login-dialog-button.a11y-button:focus:hover, .flat.login-dialog-button.cancel-button:focus:hover, .flat.login-dialog-button.switch-user-button:focus:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .flat.login-dialog-button.login-dialog-session-list-button:focus:hover, .flat.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .flat.message-expand-button:focus:hover, + .message .message-header .flat.message-close-button:focus:hover, .message-notification-group .flat.message-collapse-button:focus:hover, .calendar .calendar-month-header .pager-button:focus:hover, .button.flat:focus:hover { + background-color: st-mix(-st-accent-color, st-darken(#fafafb, 7%), 5%); } +- .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, ++ .calendar .calendar-day-heading:hover, .calendar .calendar-day:hover, .calendar .calendar-month-header .calendar-month-label:hover, .icon-button.flat:hover, .flat.login-dialog-button.next-button:hover, .flat.login-dialog-button.a11y-button:hover, .flat.login-dialog-button.cancel-button:hover, .flat.login-dialog-button.switch-user-button:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:hover, .flat.login-dialog-button.login-dialog-session-list-button:hover, .flat.screenshot-ui-show-pointer-button:hover, .message .message-header .flat.message-expand-button:hover, + .message .message-header .flat.message-close-button:hover, .message-notification-group .flat.message-collapse-button:hover, .calendar .calendar-month-header .pager-button:hover, .button.flat:hover { + color: #222226; + background-color: st-darken(#fafafb, 7%); } +- .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, ++ .calendar .calendar-day-heading:insensitive, .calendar .calendar-day:insensitive, .calendar .calendar-month-header .calendar-month-label:insensitive, .icon-button.flat:insensitive, .flat.login-dialog-button.next-button:insensitive, .flat.login-dialog-button.a11y-button:insensitive, .flat.login-dialog-button.cancel-button:insensitive, .flat.login-dialog-button.switch-user-button:insensitive, .flat.login-dialog-button.login-dialog-auth-menu-button:insensitive, .flat.login-dialog-button.login-dialog-session-list-button:insensitive, .flat.screenshot-ui-show-pointer-button:insensitive, .message .message-header .flat.message-expand-button:insensitive, + .message .message-header .flat.message-close-button:insensitive, .message-notification-group .flat.message-collapse-button:insensitive, .calendar .calendar-month-header .pager-button:insensitive, .button.flat:insensitive { + color: st-transparentize(#222226, 0.6); + background-color: #fafafb; } +- .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, +- .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, ++ .calendar .calendar-day-heading:selected, .calendar .calendar-day:selected, .calendar .calendar-month-header .calendar-month-label:selected, .icon-button.flat:selected, .flat.login-dialog-button.next-button:selected, .flat.login-dialog-button.a11y-button:selected, .flat.login-dialog-button.cancel-button:selected, .flat.login-dialog-button.switch-user-button:selected, .flat.login-dialog-button.login-dialog-auth-menu-button:selected, .flat.login-dialog-button.login-dialog-session-list-button:selected, .flat.screenshot-ui-show-pointer-button:selected, .message .message-header .flat.message-expand-button:selected, ++ .message .message-header .flat.message-close-button:selected, .message-notification-group .flat.message-collapse-button:selected, .calendar .calendar-month-header .pager-button:selected, .button.flat:selected, .calendar .calendar-day-heading:active, .calendar .calendar-day:active, .calendar .calendar-month-header .calendar-month-label:active, .icon-button.flat:active, .flat.login-dialog-button.next-button:active, .flat.login-dialog-button.a11y-button:active, .flat.login-dialog-button.cancel-button:active, .flat.login-dialog-button.switch-user-button:active, .flat.login-dialog-button.login-dialog-auth-menu-button:active, .flat.login-dialog-button.login-dialog-session-list-button:active, .flat.screenshot-ui-show-pointer-button:active, .message .message-header .flat.message-expand-button:active, + .message .message-header .flat.message-close-button:active, .message-notification-group .flat.message-collapse-button:active, .calendar .calendar-month-header .pager-button:active, .button.flat:active { + color: #222226; + background-color: st-darken(#fafafb, 9%); } +- .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, +- .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, ++ .calendar .calendar-day-heading:selected:hover, .calendar .calendar-day:selected:hover, .calendar .calendar-month-header .calendar-month-label:selected:hover, .icon-button.flat:selected:hover, .flat.login-dialog-button.next-button:selected:hover, .flat.login-dialog-button.a11y-button:selected:hover, .flat.login-dialog-button.cancel-button:selected:hover, .flat.login-dialog-button.switch-user-button:selected:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:hover, .flat.login-dialog-button.login-dialog-session-list-button:selected:hover, .flat.screenshot-ui-show-pointer-button:selected:hover, .message .message-header .flat.message-expand-button:selected:hover, ++ .message .message-header .flat.message-close-button:selected:hover, .message-notification-group .flat.message-collapse-button:selected:hover, .calendar .calendar-month-header .pager-button:selected:hover, .button.flat:selected:hover, .calendar .calendar-day-heading:active:hover, .calendar .calendar-day:active:hover, .calendar .calendar-month-header .calendar-month-label:active:hover, .icon-button.flat:active:hover, .flat.login-dialog-button.next-button:active:hover, .flat.login-dialog-button.a11y-button:active:hover, .flat.login-dialog-button.cancel-button:active:hover, .flat.login-dialog-button.switch-user-button:active:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:active:hover, .flat.login-dialog-button.login-dialog-session-list-button:active:hover, .flat.screenshot-ui-show-pointer-button:active:hover, .message .message-header .flat.message-expand-button:active:hover, + .message .message-header .flat.message-close-button:active:hover, .message-notification-group .flat.message-collapse-button:active:hover, .calendar .calendar-month-header .pager-button:active:hover, .button.flat:active:hover { + background-color: st-darken(st-darken(#fafafb, 9%), 7%); } +- .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, +- .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, ++ .calendar .calendar-day-heading:selected:focus, .calendar .calendar-day:selected:focus, .calendar .calendar-month-header .calendar-month-label:selected:focus, .icon-button.flat:selected:focus, .flat.login-dialog-button.next-button:selected:focus, .flat.login-dialog-button.a11y-button:selected:focus, .flat.login-dialog-button.cancel-button:selected:focus, .flat.login-dialog-button.switch-user-button:selected:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:selected:focus, .flat.login-dialog-button.login-dialog-session-list-button:selected:focus, .flat.screenshot-ui-show-pointer-button:selected:focus, .message .message-header .flat.message-expand-button:selected:focus, ++ .message .message-header .flat.message-close-button:selected:focus, .message-notification-group .flat.message-collapse-button:selected:focus, .calendar .calendar-month-header .pager-button:selected:focus, .button.flat:selected:focus, .calendar .calendar-day-heading:active:focus, .calendar .calendar-day:active:focus, .calendar .calendar-month-header .calendar-month-label:active:focus, .icon-button.flat:active:focus, .flat.login-dialog-button.next-button:active:focus, .flat.login-dialog-button.a11y-button:active:focus, .flat.login-dialog-button.cancel-button:active:focus, .flat.login-dialog-button.switch-user-button:active:focus, .flat.login-dialog-button.login-dialog-auth-menu-button:active:focus, .flat.login-dialog-button.login-dialog-session-list-button:active:focus, .flat.screenshot-ui-show-pointer-button:active:focus, .message .message-header .flat.message-expand-button:active:focus, + .message .message-header .flat.message-close-button:active:focus, .message-notification-group .flat.message-collapse-button:active:focus, .calendar .calendar-month-header .pager-button:active:focus, .button.flat:active:focus { + background-color: st-mix(-st-accent-color, st-darken(#fafafb, 9%), 5%); } +- .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, ++ .calendar .calendar-day-heading:checked, .calendar .calendar-day:checked, .calendar .calendar-month-header .calendar-month-label:checked, .icon-button.flat:checked, .flat.login-dialog-button.next-button:checked, .flat.login-dialog-button.a11y-button:checked, .flat.login-dialog-button.cancel-button:checked, .flat.login-dialog-button.switch-user-button:checked, .flat.login-dialog-button.login-dialog-auth-menu-button:checked, .flat.login-dialog-button.login-dialog-session-list-button:checked, .flat.screenshot-ui-show-pointer-button:checked, .message .message-header .flat.message-expand-button:checked, + .message .message-header .flat.message-close-button:checked, .message-notification-group .flat.message-collapse-button:checked, .calendar .calendar-month-header .pager-button:checked, .button.flat:checked { + color: #222226; + background-color: st-darken(#fafafb, 8%); } +- .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, ++ .calendar .calendar-day-heading:checked:hover, .calendar .calendar-day:checked:hover, .calendar .calendar-month-header .calendar-month-label:checked:hover, .icon-button.flat:checked:hover, .flat.login-dialog-button.next-button:checked:hover, .flat.login-dialog-button.a11y-button:checked:hover, .flat.login-dialog-button.cancel-button:checked:hover, .flat.login-dialog-button.switch-user-button:checked:hover, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:hover, .flat.login-dialog-button.login-dialog-session-list-button:checked:hover, .flat.screenshot-ui-show-pointer-button:checked:hover, .message .message-header .flat.message-expand-button:checked:hover, + .message .message-header .flat.message-close-button:checked:hover, .message-notification-group .flat.message-collapse-button:checked:hover, .calendar .calendar-month-header .pager-button:checked:hover, .button.flat:checked:hover { + background-color: st-darken(st-darken(#fafafb, 8%), 7%); } +- .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, ++ .calendar .calendar-day-heading:checked:active, .calendar .calendar-day:checked:active, .calendar .calendar-month-header .calendar-month-label:checked:active, .icon-button.flat:checked:active, .flat.login-dialog-button.next-button:checked:active, .flat.login-dialog-button.a11y-button:checked:active, .flat.login-dialog-button.cancel-button:checked:active, .flat.login-dialog-button.switch-user-button:checked:active, .flat.login-dialog-button.login-dialog-auth-menu-button:checked:active, .flat.login-dialog-button.login-dialog-session-list-button:checked:active, .flat.screenshot-ui-show-pointer-button:checked:active, .message .message-header .flat.message-expand-button:checked:active, + .message .message-header .flat.message-close-button:checked:active, .message-notification-group .flat.message-collapse-button:checked:active, .calendar .calendar-month-header .pager-button:checked:active, .button.flat:checked:active { + background-color: st-darken(st-darken(#fafafb, 8%), 9%); } + +-.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, ++.keyboard-brightness-level .button:checked, .quick-toggle:checked, .calendar .calendar-day.calendar-today, .icon-button.default, .default.login-dialog-button.next-button, .default.login-dialog-button.a11y-button, .default.login-dialog-button.cancel-button, .default.login-dialog-button.switch-user-button, .default.login-dialog-button.login-dialog-auth-menu-button, .default.login-dialog-button.login-dialog-session-list-button, .default.screenshot-ui-show-pointer-button, .message .message-header .default.message-expand-button, + .message .message-header .default.message-close-button, .message-notification-group .default.message-collapse-button, .calendar .calendar-month-header .default.pager-button, .button.default { + color: -st-accent-fg-color; + background-color: -st-accent-color; } +- .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, ++ .keyboard-brightness-level .button:focus:checked, .quick-toggle:focus:checked, .calendar .calendar-day.calendar-today:focus, .icon-button.default:focus, .default.login-dialog-button.next-button:focus, .default.login-dialog-button.a11y-button:focus, .default.login-dialog-button.cancel-button:focus, .default.login-dialog-button.switch-user-button:focus, .default.login-dialog-button.login-dialog-auth-menu-button:focus, .default.login-dialog-button.login-dialog-session-list-button:focus, .default.screenshot-ui-show-pointer-button:focus, .message .message-header .default.message-expand-button:focus, + .message .message-header .default.message-close-button:focus, .message-notification-group .default.message-collapse-button:focus, .calendar .calendar-month-header .default.pager-button:focus, .button.default:focus { + color: -st-accent-fg-color; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; + box-shadow: inset 0 0 0 2px st-transparentize(st-darken(-st-accent-color, 20%), 0.2) !important; + background-color: st-mix(-st-accent-color, -st-accent-color, 5%); } +- .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, ++ .keyboard-brightness-level .button:focus:hover:checked, .quick-toggle:focus:hover:checked, .calendar .calendar-day.calendar-today:focus:hover, .icon-button.default:focus:hover, .default.login-dialog-button.next-button:focus:hover, .default.login-dialog-button.a11y-button:focus:hover, .default.login-dialog-button.cancel-button:focus:hover, .default.login-dialog-button.switch-user-button:focus:hover, .default.login-dialog-button.login-dialog-auth-menu-button:focus:hover, .default.login-dialog-button.login-dialog-session-list-button:focus:hover, .default.screenshot-ui-show-pointer-button:focus:hover, .message .message-header .default.message-expand-button:focus:hover, + .message .message-header .default.message-close-button:focus:hover, .message-notification-group .default.message-collapse-button:focus:hover, .calendar .calendar-month-header .default.pager-button:focus:hover, .button.default:focus:hover { + background-color: st-mix(-st-accent-color, st-darken(-st-accent-color, 4%), 5%); } +- .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, ++ .keyboard-brightness-level .button:hover:checked, .quick-toggle:hover:checked, .calendar .calendar-day.calendar-today:hover, .icon-button.default:hover, .default.login-dialog-button.next-button:hover, .default.login-dialog-button.a11y-button:hover, .default.login-dialog-button.cancel-button:hover, .default.login-dialog-button.switch-user-button:hover, .default.login-dialog-button.login-dialog-auth-menu-button:hover, .default.login-dialog-button.login-dialog-session-list-button:hover, .default.screenshot-ui-show-pointer-button:hover, .message .message-header .default.message-expand-button:hover, + .message .message-header .default.message-close-button:hover, .message-notification-group .default.message-collapse-button:hover, .calendar .calendar-month-header .default.pager-button:hover, .button.default:hover { + color: -st-accent-fg-color; + background-color: st-darken(-st-accent-color, 4%); } +- .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, ++ .keyboard-brightness-level .button:insensitive:checked, .quick-toggle:insensitive:checked, .calendar .calendar-day.calendar-today:insensitive, .icon-button.default:insensitive, .default.login-dialog-button.next-button:insensitive, .default.login-dialog-button.a11y-button:insensitive, .default.login-dialog-button.cancel-button:insensitive, .default.login-dialog-button.switch-user-button:insensitive, .default.login-dialog-button.login-dialog-auth-menu-button:insensitive, .default.login-dialog-button.login-dialog-session-list-button:insensitive, .default.screenshot-ui-show-pointer-button:insensitive, .message .message-header .default.message-expand-button:insensitive, + .message .message-header .default.message-close-button:insensitive, .message-notification-group .default.message-collapse-button:insensitive, .calendar .calendar-month-header .default.pager-button:insensitive, .button.default:insensitive { + color: st-transparentize(-st-accent-fg-color, 0.6); + background-color: st-lighten(-st-accent-color, 3%); } +- .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, ++ .keyboard-brightness-level .button:active:checked, .quick-toggle:active:checked, .calendar .calendar-day.calendar-today:active, .icon-button.default:active, .default.login-dialog-button.next-button:active, .default.login-dialog-button.a11y-button:active, .default.login-dialog-button.cancel-button:active, .default.login-dialog-button.switch-user-button:active, .default.login-dialog-button.login-dialog-auth-menu-button:active, .default.login-dialog-button.login-dialog-session-list-button:active, .default.screenshot-ui-show-pointer-button:active, .message .message-header .default.message-expand-button:active, + .message .message-header .default.message-close-button:active, .message-notification-group .default.message-collapse-button:active, .calendar .calendar-month-header .default.pager-button:active, .button.default:active { + color: -st-accent-fg-color; + background-color: st-darken(-st-accent-color, 9%); } +- .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, ++ .keyboard-brightness-level .button:active:hover:checked, .quick-toggle:active:hover:checked, .calendar .calendar-day.calendar-today:active:hover, .icon-button.default:active:hover, .default.login-dialog-button.next-button:active:hover, .default.login-dialog-button.a11y-button:active:hover, .default.login-dialog-button.cancel-button:active:hover, .default.login-dialog-button.switch-user-button:active:hover, .default.login-dialog-button.login-dialog-auth-menu-button:active:hover, .default.login-dialog-button.login-dialog-session-list-button:active:hover, .default.screenshot-ui-show-pointer-button:active:hover, .message .message-header .default.message-expand-button:active:hover, + .message .message-header .default.message-close-button:active:hover, .message-notification-group .default.message-collapse-button:active:hover, .calendar .calendar-month-header .default.pager-button:active:hover, .button.default:active:hover { + background-color: st-darken(st-darken(-st-accent-color, 9%), 4%); } +- .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, ++ .keyboard-brightness-level .button:active:focus:checked, .quick-toggle:active:focus:checked, .calendar .calendar-day.calendar-today:active:focus, .icon-button.default:active:focus, .default.login-dialog-button.next-button:active:focus, .default.login-dialog-button.a11y-button:active:focus, .default.login-dialog-button.cancel-button:active:focus, .default.login-dialog-button.switch-user-button:active:focus, .default.login-dialog-button.login-dialog-auth-menu-button:active:focus, .default.login-dialog-button.login-dialog-session-list-button:active:focus, .default.screenshot-ui-show-pointer-button:active:focus, .message .message-header .default.message-expand-button:active:focus, + .message .message-header .default.message-close-button:active:focus, .message-notification-group .default.message-collapse-button:active:focus, .calendar .calendar-month-header .default.pager-button:active:focus, .button.default:active:focus { + background-color: st-mix(-st-accent-color, st-darken(-st-accent-color, 9%), 5%); } + +@@ -358,7 +361,7 @@ StEntry { + background-color: st-darken(st-darken(st-mix(#222226, #fafafb, 12%), 8%), 9%); } + .modal-dialog .modal-dialog-button-box .modal-dialog-button:insensitive { + color: st-transparentize(#222226, 0.6); +- background-color: st-lighten(st-mix(#222226, #fafafb, 12%), 3%); } ++ background-color: rgba(34, 34, 38, 0.1); } + + .dash-label, .window-caption, .screenshot-ui-tooltip { + background-color: rgba(0, 0, 0, 0.9); +@@ -382,12 +385,13 @@ StEntry { + font-weight: 700; + font-size: 1.364em; } + +-.login-dialog-auth-list-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, ++.login-dialog-auth-list-title-label, #LookingGlassDebugFlags .lg-debug-flags-header, #LookingGlassExtensions .lg-extensions-none, .word-suggestions, .bt-menu-placeholder.popup-menu-item, .restart-message, .polkit-dialog-user-layout .polkit-dialog-user-label, + .polkit-dialog-user-layout .polkit-dialog-user-root-label, .message-dialog-content .message-dialog-title.lightweight { + font-weight: 700; + font-size: 1.182em; } + +-.login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { ++.login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels, .login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle, .login-dialog-not-listed-label, #LookingGlassExtensions .lg-extension .lg-extension-name, #LookingGlassWindows .lg-window .lg-window-name, #LookingGlassPropertyInspector .lg-obj-inspector-title, .background-app-item .title, .quick-toggle .quick-toggle-title, .osd-window, .dialog-list .dialog-list-title, .message-list-controls, .weather-button .weather-box .weather-header-box .weather-header, .world-clocks-button .world-clocks-header, .events-button .events-box .events-list .event-box .event-summary, .events-button .events-box .events-title, .calendar .calendar-month-header .calendar-month-label { + font-weight: 700; + font-size: 1em; } + +@@ -405,7 +409,7 @@ StEntry { + font-weight: 400; + font-size: 0.818em; } + +-#LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { ++.web-login-url-label, #LookingGlassDebugFlags .lg-debug-flag-button StLabel, #LookingGlassEvaluator .evaluator-results, .lg-dialog .actor-link { + font-family: monospace; } + + .unlock-dialog-clock .unlock-dialog-clock-time, #panel, .weather-button .weather-box .weather-grid .weather-forecast-temp, .world-clocks-button .world-clocks-grid .world-clocks-timezone, .world-clocks-button .world-clocks-grid .world-clocks-time, .events-button .events-box .events-list .event-box .event-time, .calendar .calendar-day-heading, .calendar .calendar-day { +@@ -523,34 +527,34 @@ StEntry { + .login-dialog .login-dialog-prompt-entry StLabel.hint-text, .app-folder-dialog .folder-name-container .folder-name-entry StLabel.hint-text, .search-entry StLabel.hint-text { + color: rgba(250, 250, 251, 0.7); } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + color: #fafafb; + background-color: st-mix(#fafafb, #2e2e33, 12%); } +- .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { ++ .login-dialog-button.next-button:insensitive, .login-dialog-button.a11y-button:insensitive, .login-dialog-button.cancel-button:insensitive, .login-dialog-button.switch-user-button:insensitive, .login-dialog-button.login-dialog-auth-menu-button:insensitive, .login-dialog-button.login-dialog-session-list-button:insensitive { + color: st-transparentize(#fafafb, 0.6); + background-color: st-lighten(st-mix(#fafafb, #2e2e33, 12%), 3%); } +- .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { ++ .login-dialog-button.next-button:focus, .login-dialog-button.a11y-button:focus, .login-dialog-button.cancel-button:focus, .login-dialog-button.switch-user-button:focus, .login-dialog-button.login-dialog-auth-menu-button:focus, .login-dialog-button.login-dialog-session-list-button:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; + background-color: st-mix(-st-accent-color, st-mix(#fafafb, #2e2e33, 12%), 5%); } +- .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { ++ .login-dialog-button.next-button:focus:hover, .login-dialog-button.a11y-button:focus:hover, .login-dialog-button.cancel-button:focus:hover, .login-dialog-button.switch-user-button:focus:hover, .login-dialog-button.login-dialog-auth-menu-button:focus:hover, .login-dialog-button.login-dialog-session-list-button:focus:hover { + background-color: st-mix(-st-accent-color, st-darken(st-mix(#fafafb, #2e2e33, 12%), 4%), 5%); } +- .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { ++ .login-dialog-button.next-button:hover, .login-dialog-button.a11y-button:hover, .login-dialog-button.cancel-button:hover, .login-dialog-button.switch-user-button:hover, .login-dialog-button.login-dialog-auth-menu-button:hover, .login-dialog-button.login-dialog-session-list-button:hover { + color: #fafafb; + background-color: st-darken(st-mix(#fafafb, #2e2e33, 12%), 4%); } +- .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-session-list-button:active { ++ .login-dialog-button.next-button:active, .login-dialog-button.a11y-button:active, .login-dialog-button.cancel-button:active, .login-dialog-button.switch-user-button:active, .login-dialog-button.login-dialog-auth-menu-button:active, .login-dialog-button.login-dialog-session-list-button:active { + color: #fafafb; + background-color: st-darken(st-mix(#fafafb, #2e2e33, 12%), 9%); } +- .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { ++ .login-dialog-button.next-button:active:hover, .login-dialog-button.a11y-button:active:hover, .login-dialog-button.cancel-button:active:hover, .login-dialog-button.switch-user-button:active:hover, .login-dialog-button.login-dialog-auth-menu-button:active:hover, .login-dialog-button.login-dialog-session-list-button:active:hover { + background-color: st-darken(st-darken(st-mix(#fafafb, #2e2e33, 12%), 9%), 4%); } +- .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { ++ .login-dialog-button.next-button:active:focus, .login-dialog-button.a11y-button:active:focus, .login-dialog-button.cancel-button:active:focus, .login-dialog-button.switch-user-button:active:focus, .login-dialog-button.login-dialog-auth-menu-button:active:focus, .login-dialog-button.login-dialog-session-list-button:active:focus { + background-color: st-mix(-st-accent-color, st-darken(st-mix(#fafafb, #2e2e33, 12%), 9%), 5%); } +- .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { ++ .login-dialog-button.next-button:checked, .login-dialog-button.a11y-button:checked, .login-dialog-button.cancel-button:checked, .login-dialog-button.switch-user-button:checked, .login-dialog-button.login-dialog-auth-menu-button:checked, .login-dialog-button.login-dialog-session-list-button:checked { + color: #fafafb; + background-color: st-darken(st-mix(#fafafb, #2e2e33, 12%), 8%); } +- .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { ++ .login-dialog-button.next-button:checked:hover, .login-dialog-button.a11y-button:checked:hover, .login-dialog-button.cancel-button:checked:hover, .login-dialog-button.switch-user-button:checked:hover, .login-dialog-button.login-dialog-auth-menu-button:checked:hover, .login-dialog-button.login-dialog-session-list-button:checked:hover { + background-color: st-darken(st-darken(st-mix(#fafafb, #2e2e33, 12%), 8%), 4%); } +- .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { ++ .login-dialog-button.next-button:checked:active, .login-dialog-button.a11y-button:checked:active, .login-dialog-button.cancel-button:checked:active, .login-dialog-button.switch-user-button:checked:active, .login-dialog-button.login-dialog-auth-menu-button:checked:active, .login-dialog-button.login-dialog-session-list-button:checked:active { + background-color: st-darken(st-darken(st-mix(#fafafb, #2e2e33, 12%), 8%), 9%); } + + /* Lockscreen Elements */ +@@ -566,10 +570,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button, + .message .message-header .unlock-dialog .message-close-button, + .unlock-dialog .screenshot-ui-show-pointer-button, ++.unlock-dialog .login-dialog-button.next-button, + .unlock-dialog .login-dialog-button.a11y-button, + .unlock-dialog .login-dialog-button.cancel-button, + .unlock-dialog .login-dialog-button.switch-user-button, +-.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .login-dialog-auth-list-item { ++.unlock-dialog .login-dialog-button.login-dialog-auth-menu-button, ++.unlock-dialog .login-dialog-button.login-dialog-session-list-button, .unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button, .unlock-dialog .login-dialog-auth-list-title, .unlock-dialog .login-dialog-auth-list-item { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.1); } + .unlock-dialog-notifications-container .message StButton:focus, +@@ -584,10 +591,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus, + .message .message-header .unlock-dialog .message-close-button:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:focus, ++ .unlock-dialog .login-dialog-button.next-button:focus, + .unlock-dialog .login-dialog-button.a11y-button:focus, + .unlock-dialog .login-dialog-button.cancel-button:focus, + .unlock-dialog .login-dialog-button.switch-user-button:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .login-dialog-auth-list-item:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus, .unlock-dialog .web-login-intro-button:focus, ++ .unlock-dialog .web-login-prompt-button:focus, .unlock-dialog .login-dialog-auth-list-title:focus, .unlock-dialog .login-dialog-auth-list-item:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; + background-color: st-mix(-st-accent-color, rgba(250, 250, 251, 0.1), 5%); } +@@ -603,10 +613,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:focus:hover, + .message .message-header .unlock-dialog .message-close-button:focus:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:focus:hover, ++ .unlock-dialog .login-dialog-button.next-button:focus:hover, + .unlock-dialog .login-dialog-button.a11y-button:focus:hover, + .unlock-dialog .login-dialog-button.cancel-button:focus:hover, + .unlock-dialog .login-dialog-button.switch-user-button:focus:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:focus:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:focus:hover, .unlock-dialog .web-login-intro-button:focus:hover, ++ .unlock-dialog .web-login-prompt-button:focus:hover, .unlock-dialog .login-dialog-auth-list-title:focus:hover, .unlock-dialog .login-dialog-auth-list-item:focus:hover { + background-color: st-mix(-st-accent-color, rgba(250, 250, 251, 0.13), 5%); } + .unlock-dialog-notifications-container .message StButton:hover, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:hover, .unlock-dialog .button:hover, +@@ -620,10 +633,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:hover, + .message .message-header .unlock-dialog .message-close-button:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:hover, ++ .unlock-dialog .login-dialog-button.next-button:hover, + .unlock-dialog .login-dialog-button.a11y-button:hover, + .unlock-dialog .login-dialog-button.cancel-button:hover, + .unlock-dialog .login-dialog-button.switch-user-button:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .login-dialog-auth-list-item:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:hover, .unlock-dialog .web-login-intro-button:hover, ++ .unlock-dialog .web-login-prompt-button:hover, .unlock-dialog .login-dialog-auth-list-title:hover, .unlock-dialog .login-dialog-auth-list-item:hover { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.13); } + .unlock-dialog-notifications-container .message StButton:active, +@@ -638,10 +654,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active, + .message .message-header .unlock-dialog .message-close-button:active, + .unlock-dialog .screenshot-ui-show-pointer-button:active, ++ .unlock-dialog .login-dialog-button.next-button:active, + .unlock-dialog .login-dialog-button.a11y-button:active, + .unlock-dialog .login-dialog-button.cancel-button:active, + .unlock-dialog .login-dialog-button.switch-user-button:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .login-dialog-auth-list-item:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active, .unlock-dialog .web-login-intro-button:active, ++ .unlock-dialog .web-login-prompt-button:active, .unlock-dialog .login-dialog-auth-list-title:active, .unlock-dialog .login-dialog-auth-list-item:active { + color: #fafafb; + background-color: rgba(250, 250, 251, 0.16); } + .unlock-dialog-notifications-container .message StButton:active:hover, +@@ -656,10 +675,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .unlock-dialog .message-close-button:active:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:active:hover, ++ .unlock-dialog .login-dialog-button.next-button:active:hover, + .unlock-dialog .login-dialog-button.a11y-button:active:hover, + .unlock-dialog .login-dialog-button.cancel-button:active:hover, + .unlock-dialog .login-dialog-button.switch-user-button:active:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .unlock-dialog .web-login-intro-button:active:hover, ++ .unlock-dialog .web-login-prompt-button:active:hover, .unlock-dialog .login-dialog-auth-list-title:active:hover, .unlock-dialog .login-dialog-auth-list-item:active:hover { + background-color: rgba(250, 250, 251, 0.19); } + .unlock-dialog-notifications-container .message StButton:active:focus, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:active:focus, .unlock-dialog .button:active:focus, +@@ -673,10 +695,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .unlock-dialog .message-close-button:active:focus, + .unlock-dialog .screenshot-ui-show-pointer-button:active:focus, ++ .unlock-dialog .login-dialog-button.next-button:active:focus, + .unlock-dialog .login-dialog-button.a11y-button:active:focus, + .unlock-dialog .login-dialog-button.cancel-button:active:focus, + .unlock-dialog .login-dialog-button.switch-user-button:active:focus, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .unlock-dialog .web-login-intro-button:active:focus, ++ .unlock-dialog .web-login-prompt-button:active:focus, .unlock-dialog .login-dialog-auth-list-title:active:focus, .unlock-dialog .login-dialog-auth-list-item:active:focus { + background-color: st-mix(-st-accent-color, rgba(250, 250, 251, 0.16), 5%); } + .unlock-dialog-notifications-container .message StButton:checked, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked, .unlock-dialog .button:checked, +@@ -690,10 +715,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked, + .message .message-header .unlock-dialog .message-close-button:checked, + .unlock-dialog .screenshot-ui-show-pointer-button:checked, ++ .unlock-dialog .login-dialog-button.next-button:checked, + .unlock-dialog .login-dialog-button.a11y-button:checked, + .unlock-dialog .login-dialog-button.cancel-button:checked, + .unlock-dialog .login-dialog-button.switch-user-button:checked, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .login-dialog-auth-list-item:checked { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked, .unlock-dialog .web-login-intro-button:checked, ++ .unlock-dialog .web-login-prompt-button:checked, .unlock-dialog .login-dialog-auth-list-title:checked, .unlock-dialog .login-dialog-auth-list-item:checked { + color: #fafafb; + background-color: st-darken(st-mix(#fafafb, #fafafb, 12%), 8%); } + .unlock-dialog-notifications-container .message StButton:checked:hover, +@@ -708,10 +736,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:hover, + .message .message-header .unlock-dialog .message-close-button:checked:hover, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:hover, ++ .unlock-dialog .login-dialog-button.next-button:checked:hover, + .unlock-dialog .login-dialog-button.a11y-button:checked:hover, + .unlock-dialog .login-dialog-button.cancel-button:checked:hover, + .unlock-dialog .login-dialog-button.switch-user-button:checked:hover, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:hover, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:hover, .unlock-dialog .web-login-intro-button:checked:hover, ++ .unlock-dialog .web-login-prompt-button:checked:hover, .unlock-dialog .login-dialog-auth-list-title:checked:hover, .unlock-dialog .login-dialog-auth-list-item:checked:hover { + background-color: st-darken(st-darken(st-mix(#fafafb, #fafafb, 12%), 8%), 4%); } + .unlock-dialog-notifications-container .message StButton:checked:active, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:checked:active, .unlock-dialog .button:checked:active, +@@ -725,10 +756,13 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:checked:active, + .message .message-header .unlock-dialog .message-close-button:checked:active, + .unlock-dialog .screenshot-ui-show-pointer-button:checked:active, ++ .unlock-dialog .login-dialog-button.next-button:checked:active, + .unlock-dialog .login-dialog-button.a11y-button:checked:active, + .unlock-dialog .login-dialog-button.cancel-button:checked:active, + .unlock-dialog .login-dialog-button.switch-user-button:checked:active, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:checked:active, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:checked:active, .unlock-dialog .web-login-intro-button:checked:active, ++ .unlock-dialog .web-login-prompt-button:checked:active, .unlock-dialog .login-dialog-auth-list-title:checked:active, .unlock-dialog .login-dialog-auth-list-item:checked:active { + background-color: st-darken(st-darken(st-mix(#fafafb, #fafafb, 12%), 8%), 9%); } + .unlock-dialog-notifications-container .message StButton:insensitive, + .unlock-dialog-notifications-container .unlock-dialog-notification-source StButton:insensitive, .unlock-dialog .button:insensitive, +@@ -742,12 +776,15 @@ StEntry { + .unlock-dialog .message .message-header .message-close-button:insensitive, + .message .message-header .unlock-dialog .message-close-button:insensitive, + .unlock-dialog .screenshot-ui-show-pointer-button:insensitive, ++ .unlock-dialog .login-dialog-button.next-button:insensitive, + .unlock-dialog .login-dialog-button.a11y-button:insensitive, + .unlock-dialog .login-dialog-button.cancel-button:insensitive, + .unlock-dialog .login-dialog-button.switch-user-button:insensitive, +- .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { ++ .unlock-dialog .login-dialog-button.login-dialog-auth-menu-button:insensitive, ++ .unlock-dialog .login-dialog-button.login-dialog-session-list-button:insensitive, .unlock-dialog .web-login-intro-button:insensitive, ++ .unlock-dialog .web-login-prompt-button:insensitive, .unlock-dialog .login-dialog-auth-list-title:insensitive, .unlock-dialog .login-dialog-auth-list-item:insensitive { + color: st-transparentize(#fafafb, 0.6); +- background-color: st-lighten(st-mix(#fafafb, #fafafb, 12%), 3%); } ++ background-color: rgba(250, 250, 251, 0.1); } + + .unlock-dialog .login-dialog-prompt-entry { + background-color: rgba(250, 250, 251, 0.1); +@@ -767,6 +804,7 @@ StEntry { + .unlock-dialog .login-dialog-prompt-entry StLabel.hint-text { + color: rgba(250, 250, 251, 0.7); } + ++/* Login Dialog Elements */ + /* WIDGETS */ + .shell-link { + color: st-darken(-st-accent-color, 10%); } +@@ -790,18 +828,18 @@ StEntry StIcon.peek-password { + padding: 0 4px; } + + StEntry StLabel.hint-text { +- margin-left: 2px; } ++ margin-left: 8px; } + + /* Buttons */ + .button { + min-height: 1.5em; } + +-.icon-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, ++.icon-button, .login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button, .screenshot-ui-show-pointer-button, .message .message-header .message-expand-button, + .message .message-header .message-close-button, .message-notification-group .message-collapse-button, .calendar .calendar-month-header .pager-button { + border-radius: 999px; + padding: 0.818em; + min-height: 1.091em; } +- .icon-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, ++ .icon-button StIcon, .login-dialog-button.next-button StIcon, .login-dialog-button.a11y-button StIcon, .login-dialog-button.cancel-button StIcon, .login-dialog-button.switch-user-button StIcon, .login-dialog-button.login-dialog-auth-menu-button StIcon, .login-dialog-button.login-dialog-session-list-button StIcon, .screenshot-ui-show-pointer-button StIcon, .message .message-header .message-expand-button StIcon, + .message .message-header .message-close-button StIcon, .message-notification-group .message-collapse-button StIcon, .calendar .calendar-month-header .pager-button StIcon { + icon-size: 1.091em; + -st-icon-style: symbolic; } +@@ -1837,6 +1875,9 @@ StScrollBar { min-width: 0.5455em; min-height: 0.5455em; - background-color: #282828; } + background-color: #222226; } + #panel .panel-button#panelActivities .activities-logo { + icon-size: 1.36375em; + padding: 0 6px; } #panel .panel-button.screen-recording-indicator StBoxLayout, #panel .panel-button.screen-sharing-indicator StBoxLayout { spacing: 0.409em; } #panel .panel-button.screen-recording-indicator StIcon, #panel .panel-button.screen-sharing-indicator StIcon { +@@ -2038,7 +2079,7 @@ StScrollBar { + .quick-settings { + padding: 18px; + border-radius: 36px; } +- .quick-settings .icon-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, ++ .quick-settings .icon-button, .quick-settings .login-dialog-button.next-button, .quick-settings .login-dialog-button.a11y-button, .quick-settings .login-dialog-button.cancel-button, .quick-settings .login-dialog-button.switch-user-button, .quick-settings .login-dialog-button.login-dialog-auth-menu-button, .quick-settings .login-dialog-button.login-dialog-session-list-button, .quick-settings .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-settings .pager-button, .quick-settings .message-notification-group .message-collapse-button, .message-notification-group .quick-settings .message-collapse-button, .quick-settings .message .message-header .message-expand-button, .message .message-header .quick-settings .message-expand-button, + .quick-settings .message .message-header .message-close-button, + .message .message-header .quick-settings .message-close-button, .quick-settings .screenshot-ui-show-pointer-button, .quick-settings .button { + padding: 10.5px; } +@@ -2146,7 +2187,7 @@ StScrollBar { + .quick-slider > StBoxLayout { + spacing: 6px; } + +-.quick-slider .icon-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, ++.quick-slider .icon-button, .quick-slider .login-dialog-button.next-button, .quick-slider .login-dialog-button.a11y-button, .quick-slider .login-dialog-button.cancel-button, .quick-slider .login-dialog-button.switch-user-button, .quick-slider .login-dialog-button.login-dialog-auth-menu-button, .quick-slider .login-dialog-button.login-dialog-session-list-button, .quick-slider .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .quick-slider .pager-button, .quick-slider .message-notification-group .message-collapse-button, .message-notification-group .quick-slider .message-collapse-button, .quick-slider .message .message-header .message-expand-button, .message .message-header .quick-slider .message-expand-button, + .quick-slider .message .message-header .message-close-button, + .message .message-header .quick-slider .message-close-button, .quick-slider .screenshot-ui-show-pointer-button { + padding: 6px; } +@@ -2220,16 +2261,16 @@ StScrollBar { + icon-size: 32px !important; + -st-icon-style: regular !important; } + +-.background-app-item .icon-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, ++.background-app-item .icon-button, .background-app-item .login-dialog-button.next-button, .background-app-item .login-dialog-button.a11y-button, .background-app-item .login-dialog-button.cancel-button, .background-app-item .login-dialog-button.switch-user-button, .background-app-item .login-dialog-button.login-dialog-auth-menu-button, .background-app-item .login-dialog-button.login-dialog-session-list-button, .background-app-item .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .background-app-item .pager-button, .background-app-item .message-notification-group .message-collapse-button, .message-notification-group .background-app-item .message-collapse-button, .background-app-item .message .message-header .message-expand-button, .message .message-header .background-app-item .message-expand-button, + .background-app-item .message .message-header .message-close-button, + .message .message-header .background-app-item .message-close-button, .background-app-item .screenshot-ui-show-pointer-button { + padding: 6px; + background-color: rgba(34, 34, 38, 0.13); } +- .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, ++ .background-app-item .icon-button:hover, .background-app-item .login-dialog-button.next-button:hover, .background-app-item .login-dialog-button.a11y-button:hover, .background-app-item .login-dialog-button.cancel-button:hover, .background-app-item .login-dialog-button.switch-user-button:hover, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:hover, .background-app-item .login-dialog-button.login-dialog-session-list-button:hover, .background-app-item .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .background-app-item .pager-button:hover, .background-app-item .message-notification-group .message-collapse-button:hover, .message-notification-group .background-app-item .message-collapse-button:hover, .background-app-item .message .message-header .message-expand-button:hover, .message .message-header .background-app-item .message-expand-button:hover, + .background-app-item .message .message-header .message-close-button:hover, + .message .message-header .background-app-item .message-close-button:hover, .background-app-item .screenshot-ui-show-pointer-button:hover { + background-color: rgba(34, 34, 38, 0.22); } +- .background-app-item .icon-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, ++ .background-app-item .icon-button:active, .background-app-item .login-dialog-button.next-button:active, .background-app-item .login-dialog-button.a11y-button:active, .background-app-item .login-dialog-button.cancel-button:active, .background-app-item .login-dialog-button.switch-user-button:active, .background-app-item .login-dialog-button.login-dialog-auth-menu-button:active, .background-app-item .login-dialog-button.login-dialog-session-list-button:active, .background-app-item .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .background-app-item .pager-button:active, .background-app-item .message-notification-group .message-collapse-button:active, .message-notification-group .background-app-item .message-collapse-button:active, .background-app-item .message .message-header .message-expand-button:active, .message .message-header .background-app-item .message-expand-button:active, + .background-app-item .message .message-header .message-close-button:active, + .message .message-header .background-app-item .message-close-button:active, .background-app-item .screenshot-ui-show-pointer-button:active { + background-color: rgba(34, 34, 38, 0.31); } +@@ -2561,26 +2602,26 @@ StScrollBar { + padding-bottom: 0; } + .app-folder-dialog .folder-name-container .folder-name-entry { + width: 12em; } +- .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, ++ .app-folder-dialog .icon-button, .app-folder-dialog .login-dialog-button.next-button, .app-folder-dialog .login-dialog-button.a11y-button, .app-folder-dialog .login-dialog-button.cancel-button, .app-folder-dialog .login-dialog-button.switch-user-button, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button, .app-folder-dialog .calendar .calendar-month-header .pager-button, .calendar .calendar-month-header .app-folder-dialog .pager-button, .app-folder-dialog .message-notification-group .message-collapse-button, .message-notification-group .app-folder-dialog .message-collapse-button, .app-folder-dialog .message .message-header .message-expand-button, .message .message-header .app-folder-dialog .message-expand-button, + .app-folder-dialog .message .message-header .message-close-button, + .message .message-header .app-folder-dialog .message-close-button, .app-folder-dialog .screenshot-ui-show-pointer-button { + color: #fafafb; + background-color: st-mix(#fafafb, #38383b, 12%); } +- .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, ++ .app-folder-dialog .icon-button:hover, .app-folder-dialog .login-dialog-button.next-button:hover, .app-folder-dialog .login-dialog-button.a11y-button:hover, .app-folder-dialog .login-dialog-button.cancel-button:hover, .app-folder-dialog .login-dialog-button.switch-user-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:hover, .app-folder-dialog .message-notification-group .message-collapse-button:hover, .message-notification-group .app-folder-dialog .message-collapse-button:hover, .app-folder-dialog .message .message-header .message-expand-button:hover, .message .message-header .app-folder-dialog .message-expand-button:hover, + .app-folder-dialog .message .message-header .message-close-button:hover, + .message .message-header .app-folder-dialog .message-close-button:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:hover { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #38383b, 12%), 4%); } +- .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, ++ .app-folder-dialog .icon-button:active, .app-folder-dialog .login-dialog-button.next-button:active, .app-folder-dialog .login-dialog-button.a11y-button:active, .app-folder-dialog .login-dialog-button.cancel-button:active, .app-folder-dialog .login-dialog-button.switch-user-button:active, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active, .app-folder-dialog .calendar .calendar-month-header .pager-button:active, .calendar .calendar-month-header .app-folder-dialog .pager-button:active, .app-folder-dialog .message-notification-group .message-collapse-button:active, .message-notification-group .app-folder-dialog .message-collapse-button:active, .app-folder-dialog .message .message-header .message-expand-button:active, .message .message-header .app-folder-dialog .message-expand-button:active, + .app-folder-dialog .message .message-header .message-close-button:active, + .message .message-header .app-folder-dialog .message-close-button:active, .app-folder-dialog .screenshot-ui-show-pointer-button:active { + color: #fafafb; + background-color: st-lighten(st-mix(#fafafb, #38383b, 12%), 9%); } +- .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, ++ .app-folder-dialog .icon-button:active:hover, .app-folder-dialog .login-dialog-button.next-button:active:hover, .app-folder-dialog .login-dialog-button.a11y-button:active:hover, .app-folder-dialog .login-dialog-button.cancel-button:active:hover, .app-folder-dialog .login-dialog-button.switch-user-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:hover, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:hover, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:hover, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:hover, .app-folder-dialog .message-notification-group .message-collapse-button:active:hover, .message-notification-group .app-folder-dialog .message-collapse-button:active:hover, .app-folder-dialog .message .message-header .message-expand-button:active:hover, .message .message-header .app-folder-dialog .message-expand-button:active:hover, + .app-folder-dialog .message .message-header .message-close-button:active:hover, + .message .message-header .app-folder-dialog .message-close-button:active:hover, .app-folder-dialog .screenshot-ui-show-pointer-button:active:hover { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #38383b, 12%), 9%), 4%); } +- .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, ++ .app-folder-dialog .icon-button:active:focus, .app-folder-dialog .login-dialog-button.next-button:active:focus, .app-folder-dialog .login-dialog-button.a11y-button:active:focus, .app-folder-dialog .login-dialog-button.cancel-button:active:focus, .app-folder-dialog .login-dialog-button.switch-user-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-auth-menu-button:active:focus, .app-folder-dialog .login-dialog-button.login-dialog-session-list-button:active:focus, .app-folder-dialog .calendar .calendar-month-header .pager-button:active:focus, .calendar .calendar-month-header .app-folder-dialog .pager-button:active:focus, .app-folder-dialog .message-notification-group .message-collapse-button:active:focus, .message-notification-group .app-folder-dialog .message-collapse-button:active:focus, .app-folder-dialog .message .message-header .message-expand-button:active:focus, .message .message-header .app-folder-dialog .message-expand-button:active:focus, + .app-folder-dialog .message .message-header .message-close-button:active:focus, + .message .message-header .app-folder-dialog .message-close-button:active:focus, .app-folder-dialog .screenshot-ui-show-pointer-button:active:focus { + background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #38383b, 12%), 9%), 5%); } +@@ -3033,6 +3074,12 @@ StScrollBar { + background-color: st-lighten(-st-accent-color, 5%); + color: st-lighten(-st-accent-fg-color, 5%); } + ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #e9e9ea; ++ border-color: #e9e9ea; } ++ + .login-dialog, + .unlock-dialog { + color: #fafafb; } +@@ -3045,19 +3092,62 @@ StScrollBar { + .unlock-dialog .login-dialog-prompt-layout { + width: 25em; + spacing: 9px; } ++ .login-dialog .login-dialog-prompt-layout.web-login-active, ++ .unlock-dialog .login-dialog-prompt-layout.web-login-active { ++ width: 37.5em; } ++ .login-dialog .login-dialog-prompt-entry-area, ++ .unlock-dialog .login-dialog-prompt-entry-area { ++ margin: 0.5em 20px; } ++ .login-dialog .login-dialog-prompt-entry, ++ .unlock-dialog .login-dialog-prompt-entry { ++ border-radius: 12px; ++ padding-right: 3em; } ++ .login-dialog .login-dialog-default-button-well, ++ .unlock-dialog .login-dialog-default-button-well { ++ margin-right: 1em; } + + .login-dialog-bottom-button-group { + padding: 32px; + spacing: 16px; } + +-.login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-session-list-button { ++.login-dialog-button.next-button, .login-dialog-button.a11y-button, .login-dialog-button.cancel-button, .login-dialog-button.switch-user-button, .login-dialog-button.login-dialog-auth-menu-button, .login-dialog-button.login-dialog-session-list-button { + padding: 1.091em; } + ++.login-dialog-button.next-button { ++ background-color: transparent !important; ++ padding: 0; } ++ + .login-dialog-button.cancel-button { +- padding: 9px; } ++ padding: 12px; } ++ ++.login-dialog-auth-menu-button-popup { ++ padding: 18px; ++ margin-right: 12px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header { ++ font-size: 0.909em; ++ text-align: center; ++ font-weight: bold; ++ padding-top: 18px; ++ padding-bottom: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-header:first-child { ++ padding-top: 6px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator { ++ spacing: 3px; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-name { ++ font-size: 1.159em; ++ font-weight: bold; } ++ .login-dialog-auth-menu-button-popup .login-dialog-auth-menu-item-indicator .login-dialog-auth-menu-item-indicator-description { ++ font-size: 0.977em; } ++ ++.login-dialog-auth-menu-button-indicator { ++ background-color: transparent !important; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons { ++ spacing: 18px; } ++ .login-dialog-auth-menu-button-indicator .login-dialog-auth-menu-button-indicator-icons .login-dialog-auth-menu-button-indicator-icon { ++ icon-size: 2em; } + + .login-dialog-button-box { +- spacing: 12px; } ++ height: 4em; } + + .conflicting-session-dialog-content { + spacing: 20px; } +@@ -3119,49 +3209,104 @@ StScrollBar { + background-color: st-mix(-st-accent-color, st-lighten(#222226, 9%), 5%); } + + .login-dialog-auth-list-view { +- -st-vfade-offset: 3em; } ++ -st-vfade-offset: 3em; ++ max-height: 13em; } + + .login-dialog-auth-list { +- spacing: 6px; +- margin-left: 2em; } +- +-.login-dialog-auth-list-title { +- margin-left: 2em; +- padding-bottom: 6px; } ++ spacing: 9px; } + ++.login-dialog .login-dialog-auth-list-title, + .login-dialog .login-dialog-auth-list-item { + color: #fafafb; +- background-color: st-mix(#fafafb, #222226, 12%); +- border-radius: 9.6px; +- padding: 7.2px; } +- .login-dialog .login-dialog-auth-list-item:selected, .login-dialog .login-dialog-auth-list-item:focus { ++ background-color: #ffffff; ++ border-radius: 12px; } ++ .login-dialog .login-dialog-auth-list-title:selected, .login-dialog .login-dialog-auth-list-title:focus, ++ .login-dialog .login-dialog-auth-list-item:selected, ++ .login-dialog .login-dialog-auth-list-item:focus { + color: #fafafb; + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; +- background-color: st-mix(-st-accent-color, st-mix(#fafafb, #222226, 12%), 5%); } +- .login-dialog .login-dialog-auth-list-item:selected:hover, .login-dialog .login-dialog-auth-list-item:focus:hover { +- background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #222226, 12%), 4%), 5%); } ++ background-color: st-mix(-st-accent-color, #ffffff, 5%); } ++ .login-dialog .login-dialog-auth-list-title:selected:hover, .login-dialog .login-dialog-auth-list-title:focus:hover, ++ .login-dialog .login-dialog-auth-list-item:selected:hover, ++ .login-dialog .login-dialog-auth-list-item:focus:hover { ++ background-color: st-mix(-st-accent-color, st-lighten(#ffffff, 4%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:hover, + .login-dialog .login-dialog-auth-list-item:hover { + color: #fafafb; +- background-color: st-lighten(st-mix(#fafafb, #222226, 12%), 4%); } ++ background-color: st-lighten(#ffffff, 4%); } ++ .login-dialog .login-dialog-auth-list-title:active, + .login-dialog .login-dialog-auth-list-item:active { + color: #fafafb; +- background-color: st-lighten(st-mix(#fafafb, #222226, 12%), 9%); } ++ background-color: st-lighten(#ffffff, 9%); } ++ .login-dialog .login-dialog-auth-list-title:active:hover, + .login-dialog .login-dialog-auth-list-item:active:hover { +- background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 4%); } ++ background-color: st-lighten(st-lighten(#ffffff, 9%), 4%); } ++ .login-dialog .login-dialog-auth-list-title:active:focus, + .login-dialog .login-dialog-auth-list-item:active:focus { +- background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 5%); } ++ background-color: st-mix(-st-accent-color, st-lighten(#ffffff, 9%), 5%); } ++ .login-dialog .login-dialog-auth-list-title:insensitive, ++ .login-dialog .login-dialog-auth-list-item:insensitive { ++ color: st-transparentize(#fafafb, 0.6); ++ background-color: st-darken(#ffffff, 3%); } ++ ++.login-dialog .login-dialog-auth-list-title { ++ background-color: rgba(250, 250, 251, 0.02) !important; ++ color: #fafafb !important; ++ margin: 0.5em 20px; } ++ ++.login-dialog .login-dialog-auth-list-item { ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } + + .unlock-dialog .login-dialog-auth-list-item { +- border-radius: 9.6px; +- padding: 7.2px; } ++ border-radius: 12px; ++ min-height: 3em; ++ padding: 9px; ++ margin: 0 20px; ++ margin-bottom: 4px; } ++ ++.unlock-dialog .login-dialog-auth-list-title { ++ background-color: transparent !important; ++ color: #fafafb !important; ++ padding: 0; ++ margin: 0; } + +-.login-dialog-auth-list-label:ltr { +- padding-left: 15px; +- text-align: left; } ++.login-dialog-auth-list-title-label { ++ padding: 6px; ++ text-align: center; } ++ ++.login-dialog-auth-list-item-title, ++.login-dialog-auth-list-item-subtitle { ++ text-align: center; ++ padding: 1.8px 0; } ++ ++.login-dialog-auth-list-item-title { ++ color: #fafafb; } + +-.login-dialog-auth-list-label:rtl { +- padding-right: 15px; +- text-align: right; } ++.login-dialog-auth-list-item-subtitle { ++ color: #c1c1ce; ++ font-weight: 500; } ++ ++.login-dialog-item-icon { ++ width: 1.3em; ++ height: 1.3em; ++ color: #fafafb; ++ padding: 4.2px 6px; ++ border-radius: 8px; } ++ .login-dialog-item-icon:hover { ++ background-color: #2e2e33; } ++ .login-dialog-item-icon-popup.popup-menu { ++ min-width: 0; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels { ++ spacing: 3px; ++ text-align: center; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :first-child { ++ color: black; ++ font-weight: 500; } ++ .login-dialog-item-icon-popup-box .login-dialog-item-icon-popup-labels > :last-child { ++ color: #222226; } + + .login-dialog-user-list-view { + width: 25em; +@@ -3190,6 +3335,9 @@ StScrollBar { + background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 4%); } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:active:focus { + background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 5%); } ++ .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:insensitive { ++ color: st-transparentize(#fafafb, 0.6); ++ background-color: st-darken(st-mix(#fafafb, #222226, 12%), 3%); } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .user-icon { + border: 2px solid transparent; } + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item .login-dialog-timed-login-indicator { +@@ -3201,6 +3349,102 @@ StScrollBar { + .login-dialog-user-list-view .login-dialog-user-list .login-dialog-user-list-item:logged-in .user-icon StIcon { + background-color: st-transparentize(-st-accent-color, 0.7); } + ++.web-login-spinner { ++ background-color: rgba(0, 0, 0, 0.5); ++ border: 5px rgba(0, 0, 0, 0); ++ border-radius: 50px; } ++ ++.web-login-title-label { ++ font-size: 1em; ++ color: #a5a5b7; ++ text-align: center; } ++ ++.web-login-url-label { ++ font-size: 1em; ++ color: #fafafb; ++ text-align: center; } ++ .web-login-url-label.web-login-url-label-long { ++ font-size: 0.818em; } ++ ++.web-login-code-title-label { ++ font-size: 1em; ++ color: #fafafb; ++ text-align: center; } ++ ++.web-login-code-label { ++ font-size: 1em; ++ color: #fafafb; ++ font-weight: bold; ++ text-align: center; } ++ ++.web-login-prompt { ++ padding-top: 6px; ++ padding-bottom: 6px; ++ padding-left: 27px; ++ padding-right: 27px; ++ spacing: 1.75em; } ++ ++.web-login-button-label { ++ font-size: 1.182em; ++ color: #fafafb; ++ min-width: 12em; ++ text-align: center; ++ font-weight: bold; } ++ ++.login-dialog .web-login-intro-button, ++.login-dialog .web-login-prompt-button { ++ color: #fafafb; ++ background-color: st-mix(#fafafb, #222226, 12%); ++ border-radius: 32px; } ++ .login-dialog .web-login-intro-button:selected, .login-dialog .web-login-intro-button:focus, ++ .login-dialog .web-login-prompt-button:selected, ++ .login-dialog .web-login-prompt-button:focus { ++ color: #fafafb; ++ box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.2) !important; ++ background-color: st-mix(-st-accent-color, st-mix(#fafafb, #222226, 12%), 5%); } ++ .login-dialog .web-login-intro-button:selected:hover, .login-dialog .web-login-intro-button:focus:hover, ++ .login-dialog .web-login-prompt-button:selected:hover, ++ .login-dialog .web-login-prompt-button:focus:hover { ++ background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #222226, 12%), 4%), 5%); } ++ .login-dialog .web-login-intro-button:hover, ++ .login-dialog .web-login-prompt-button:hover { ++ color: #fafafb; ++ background-color: st-lighten(st-mix(#fafafb, #222226, 12%), 4%); } ++ .login-dialog .web-login-intro-button:active, ++ .login-dialog .web-login-prompt-button:active { ++ color: #fafafb; ++ background-color: st-lighten(st-mix(#fafafb, #222226, 12%), 9%); } ++ .login-dialog .web-login-intro-button:active:hover, ++ .login-dialog .web-login-prompt-button:active:hover { ++ background-color: st-lighten(st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 4%); } ++ .login-dialog .web-login-intro-button:active:focus, ++ .login-dialog .web-login-prompt-button:active:focus { ++ background-color: st-mix(-st-accent-color, st-lighten(st-mix(#fafafb, #222226, 12%), 9%), 5%); } ++ .login-dialog .web-login-intro-button:insensitive, ++ .login-dialog .web-login-prompt-button:insensitive { ++ color: st-transparentize(#fafafb, 0.6); ++ background-color: st-darken(st-mix(#fafafb, #222226, 12%), 3%); } ++ ++.login-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.login-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ ++.unlock-dialog .web-login-intro-button, ++.unlock-dialog .web-login-prompt-button { ++ border-radius: 32px; } ++ ++.unlock-dialog .web-login-intro-button { ++ padding: 0; } ++ ++.unlock-dialog .web-login-prompt-button { ++ padding: 15px 24px; ++ margin: 24px 8px; ++ width: 8em; } ++ + .unlock-dialog { + background-color: transparent; } + +@@ -3309,3 +3553,10 @@ StScrollBar { + .login-dialog .user-widget.vertical .user-icon StIcon, + .unlock-dialog .user-widget.vertical .user-icon StIcon { + padding: 30px; } ++ ++.qr-code { ++ border-radius: 4px; ++ border-width: 1em; ++ background-color: #e9e9ea; ++ border-color: #e9e9ea; ++ color: #222226; } -- -2.45.2 +2.53.0 diff --git a/0001-extensionDownloader-Refuse-to-override-system-extens.patch b/0001-extensionDownloader-Refuse-to-override-system-extens.patch index ae5a764..2cd3853 100644 --- a/0001-extensionDownloader-Refuse-to-override-system-extens.patch +++ b/0001-extensionDownloader-Refuse-to-override-system-extens.patch @@ -1,4 +1,4 @@ -From 7e82f0a73a1d1576e94b17e21d07fd3978ba7c33 Mon Sep 17 00:00:00 2001 +From aab759b25529480698b745393067d60b48b04e3d 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] extensionDownloader: Refuse to override system extensions @@ -13,10 +13,10 @@ already installed system-wide. 1 file changed, 9 insertions(+) diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js -index 0366c5dfa8..5b27543aa6 100644 +index fe687cba43..cc6cd0a0b5 100644 --- a/js/ui/extensionDownloader.js +++ b/js/ui/extensionDownloader.js -@@ -39,6 +39,15 @@ export async function installExtension(uuid, invocation) { +@@ -37,6 +37,15 @@ export async function installExtension(uuid, invocation) { return; } @@ -33,5 +33,5 @@ index 0366c5dfa8..5b27543aa6 100644 uuid, shell_version: Config.PACKAGE_VERSION, -- -2.45.2 +2.51.1 diff --git a/0001-gdm-Work-around-failing-fingerprint-auth.patch b/0001-gdm-Work-around-failing-fingerprint-auth.patch index 2baa7b6..30d2260 100644 --- a/0001-gdm-Work-around-failing-fingerprint-auth.patch +++ b/0001-gdm-Work-around-failing-fingerprint-auth.patch @@ -1,4 +1,4 @@ -From ff73f820b1398e5d60b8984b0003db46ad70b8ec Mon Sep 17 00:00:00 2001 +From 5293ee585f876169e31f3b6af737a42d30302f71 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 28 Apr 2021 16:50:03 +0200 Subject: [PATCH] gdm: Work around failing fingerprint auth @@ -9,44 +9,70 @@ the PAM configuration has not been updated and no prints are enrolled. So, consider a verification failure within one second to be a service failure instead. --- - js/gdm/util.js | 18 ++++++++++++++++++ - 1 file changed, 18 insertions(+) + js/gdm/authServicesLegacy.js | 44 +++++++++++++++++++++++++----------- + 1 file changed, 31 insertions(+), 13 deletions(-) -diff --git a/js/gdm/util.js b/js/gdm/util.js -index 97df6d687e..371953cb25 100644 ---- a/js/gdm/util.js -+++ b/js/gdm/util.js -@@ -109,6 +109,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { - this._defaultService = null; - this._preemptingService = null; - this._fingerprintReaderType = FingerprintReaderType.NONE; -+ this._fprintStartTime = -1; +diff --git a/js/gdm/authServicesLegacy.js b/js/gdm/authServicesLegacy.js +index 0813622a01..c85a9572f5 100644 +--- a/js/gdm/authServicesLegacy.js ++++ b/js/gdm/authServicesLegacy.js +@@ -58,6 +58,7 @@ export class AuthServicesLegacy extends AuthServices { + this._addCredentialManager(Vmware.SERVICE_NAME, Vmware.getVmwareCredentialsManager()); - this._messageQueue = []; - this._messageQueueTimeoutId = 0; -@@ -669,6 +670,10 @@ export class ShellUserVerifier extends Signals.EventEmitter { - this._hold.acquire(); - try { - this._activeServices.add(serviceName); + this._fingerprintReadyTimeoutId = 0; ++ this._fprintStartTime = -1; + } + + _handleSelectChoice(serviceName, key) { +@@ -117,16 +118,20 @@ export class AuthServicesLegacy extends AuthServices { + } + + _handleOnConversationStarted(serviceName) { +- if (serviceName === Const.FINGERPRINT_SERVICE_NAME && +- this._fingerprintReadyTimeoutId === 0) { +- this._fingerprintReadyTimeoutId = GLib.timeout_add( +- GLib.PRIORITY_DEFAULT, +- FINGERPRINT_READY_TIMEOUT_MS, +- () => { +- this._fingerprintReadyTimeoutId = 0; +- this._setFingerprintReady(); +- return GLib.SOURCE_REMOVE; +- }); ++ if (serviceName === Const.FINGERPRINT_SERVICE_NAME) { ++ this._fprintStartTime = GLib.get_monotonic_time(); + -+ if (serviceName == FINGERPRINT_SERVICE_NAME) -+ this._fprintStartTime = GLib.get_monotonic_time(); + - if (this._userName) { - await this._userVerifier.call_begin_verification_for_user( - serviceName, this._userName, this._cancellable); -@@ -763,6 +768,7 @@ export class ShellUserVerifier extends Signals.EventEmitter { - const cancellable = this._cancellable; - this._fingerprintFailedId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, - FINGERPRINT_ERROR_TIMEOUT_WAIT, () => { -+ log("Generating _verificationFailed!"); - this._fingerprintFailedId = 0; - if (!cancellable.is_cancelled()) - this._verificationFailed(serviceName, false); -@@ -829,6 +835,18 @@ export class ShellUserVerifier extends Signals.EventEmitter { - if (serviceName === FINGERPRINT_SERVICE_NAME) { - if (this._fingerprintFailedId) - GLib.source_remove(this._fingerprintFailedId); ++ if (this._fingerprintReadyTimeoutId === 0) { ++ this._fingerprintReadyTimeoutId = GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, ++ FINGERPRINT_READY_TIMEOUT_MS, ++ () => { ++ this._fingerprintReadyTimeoutId = 0; ++ this._setFingerprintReady(); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } + } + } + +@@ -234,6 +239,7 @@ export class AuthServicesLegacy extends AuthServices { + + this._fingerprintFailedId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, + FINGERPRINT_ERROR_TIMEOUT_WAIT, () => { ++ log("Generating _verificationFailed!"); + this._fingerprintFailedId = 0; + if (!this._cancellable.is_cancelled()) + this._verificationFailed(serviceName, false); +@@ -314,9 +320,21 @@ export class AuthServicesLegacy extends AuthServices { + + _handleVerificationFailed(serviceName) { + if (serviceName === Const.FINGERPRINT_SERVICE_NAME && +- this._enabledMechanisms.some(m => m.serviceName === serviceName) && +- this._fingerprintFailedId) +- GLib.source_remove(this._fingerprintFailedId); ++ this._enabledMechanisms.some(m => m.serviceName === serviceName)) { ++ if (this._fingerprintFailedId) ++ GLib.source_remove(this._fingerprintFailedId); + + // On Fedora we have the problem that fingerprint auth fails + // immediately if the PAM configuration has not been updated and no @@ -56,12 +82,12 @@ index 97df6d687e..371953cb25 100644 + if (this._fprintStartTime > GLib.get_monotonic_time() - GLib.USEC_PER_SEC) { + log("Fingerprint service failed almost immediately, considering it unavailable."); + log("Please fix your configuration by running: authselect select --force sssd with-fingerprint with-silent-lastlog"); -+ this._onServiceUnavailable(this._client, serviceName, null); -+ return; ++ this._onServiceUnavailable(serviceName, null); + } - } ++ } + } - // For Not Listed / enterprise logins, immediately reset + _handleOnVerificationComplete(serviceName) { -- -2.43.2 +2.53.0 diff --git a/0001-main-Dump-stack-on-segfaults-by-default.patch b/0001-main-Dump-stack-on-segfaults-by-default.patch index a18c992..d979608 100644 --- a/0001-main-Dump-stack-on-segfaults-by-default.patch +++ b/0001-main-Dump-stack-on-segfaults-by-default.patch @@ -1,4 +1,4 @@ -From 2060b040f91c3751861a1c1b985c5fe8cc891d58 Mon Sep 17 00:00:00 2001 +From e50bb4482d8171b3cad748706b930684e0edccae 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 @@ -8,10 +8,10 @@ Subject: [PATCH] main: Dump stack on segfaults by default 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c -index 4be47258eb..b324b6b96a 100644 +index af4fa8597e..03772ec7fb 100644 --- a/src/main.c +++ b/src/main.c -@@ -47,6 +47,7 @@ static char *script_path = NULL; +@@ -46,6 +46,7 @@ static char *script_path = NULL; #define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 #define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 @@ -19,7 +19,7 @@ index 4be47258eb..b324b6b96a 100644 enum { SHELL_DEBUG_BACKTRACE_WARNINGS = 1, SHELL_DEBUG_BACKTRACE_SEGFAULTS = 2, -@@ -381,8 +382,11 @@ shell_init_debug (const char *debug_env) +@@ -364,8 +365,11 @@ shell_init_debug (const char *debug_env) { "backtrace-segfaults", SHELL_DEBUG_BACKTRACE_SEGFAULTS }, }; @@ -34,5 +34,5 @@ index 4be47258eb..b324b6b96a 100644 static GLogWriterOutput -- -2.45.2 +2.51.1 diff --git a/0001-main-Register-session-with-GDM-on-startup.patch b/0001-main-Register-session-with-GDM-on-startup.patch new file mode 100644 index 0000000..9d01192 --- /dev/null +++ b/0001-main-Register-session-with-GDM-on-startup.patch @@ -0,0 +1,46 @@ +From 32a013304f8c507cb72a5419c7f4b7c553253520 Mon Sep 17 00:00:00 2001 +From: Adrian Vovk +Date: Thu, 1 May 2025 18:34:52 -0400 +Subject: [PATCH] main: Register session with GDM on startup + +When no monitor is connected, gnome-shell starts but never emits +`startup-prepared` nor `startup-complete`. As a result, GDM never +detects that the shell has launched and plymouthd remains running, +preventing the system from reaching graphical.target. + +Ensure that registerSessionWithGDM() is called even when no monitor +is connected, so GDM can terminate plymouthd and allow the system to +reach graphical.target properly. + +Part of https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/285 + +Part-of: +--- + js/ui/main.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/js/ui/main.js b/js/ui/main.js +index ae7a805..1679b3f 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -336,6 +336,8 @@ async function _initializeUI() { + extensionManager = new ExtensionSystem.ExtensionManager(); + extensionManager.init(); + ++ LoginManager.registerSessionWithGDM(); ++ + if (sessionMode.isGreeter && screenShield) { + layoutManager.connect('startup-prepared', () => { + screenShield.showDialog(); +@@ -382,8 +384,6 @@ async function _initializeUI() { + sessionMode.currentMode !== 'initial-setup') + _handleLockScreenWarning(); + +- LoginManager.registerSessionWithGDM(); +- + if (perfModule) { + let perfOutput = GLib.getenv('SHELL_PERF_OUTPUT'); + Scripting.runPerfScript(perfModule, perfOutput); +-- +2.51.0 + diff --git a/0001-panel-Use-branding-in-activities-button.patch b/0001-panel-Use-branding-in-activities-button.patch index fb3ce1c..ebeedbe 100644 --- a/0001-panel-Use-branding-in-activities-button.patch +++ b/0001-panel-Use-branding-in-activities-button.patch @@ -1,4 +1,4 @@ -From c0b2f60108e7b8cffa10306ef54c620fe71a6735 Mon Sep 17 00:00:00 2001 +From 755bd786c49c0b37c06aac781d2a7ec34357b49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 22 May 2024 18:42:06 +0200 Subject: [PATCH] panel: Use branding in activities button @@ -10,10 +10,10 @@ Replace the upstream workspace dots with a red hat. 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/theme/gnome-shell-sass/widgets/_panel.scss b/data/theme/gnome-shell-sass/widgets/_panel.scss -index 2a29eee9da..02f8232de6 100644 +index 1b84dc201f..1514d3ead2 100644 --- a/data/theme/gnome-shell-sass/widgets/_panel.scss +++ b/data/theme/gnome-shell-sass/widgets/_panel.scss -@@ -58,6 +58,11 @@ $panel_transition_duration: 250ms; // same as the overview transition duration +@@ -56,6 +56,11 @@ $panel_transition_duration: 250ms; // same as the overview transition duration min-height: $scalable_icon_size * 0.5; background-color: $panel_fg_color; } @@ -26,10 +26,10 @@ index 2a29eee9da..02f8232de6 100644 // screen activity indicators diff --git a/js/ui/panel.js b/js/ui/panel.js -index 62c8ebb31a..1d11cc1b5f 100644 +index bdb6f7b197..5be0c477c6 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js -@@ -425,7 +425,15 @@ class ActivitiesButton extends PanelMenu.Button { +@@ -208,7 +208,15 @@ class ActivitiesButton extends PanelMenu.Button { accessible_name: _('Activities'), }); @@ -47,5 +47,5 @@ index 62c8ebb31a..1d11cc1b5f 100644 Main.overview.connectObject('showing', () => this.add_style_pseudo_class('checked'), -- -2.45.2 +2.51.1 diff --git a/0001-screenShield-unblank-when-inserting-smartcard.patch b/0001-screenShield-unblank-when-inserting-smartcard.patch index 6b1d735..3177504 100644 --- a/0001-screenShield-unblank-when-inserting-smartcard.patch +++ b/0001-screenShield-unblank-when-inserting-smartcard.patch @@ -1,4 +1,4 @@ -From 17e77f1729b8542c46d5020d55edce4daf7ab24a Mon Sep 17 00:00:00 2001 +From 688687465d7f45da81847554221fb8c72d079510 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 @@ -13,10 +13,10 @@ action to get the screen to unblank. 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js -index d2236fb900..b62440dff6 100644 +index 2087c393ed..428421b370 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js -@@ -94,8 +94,10 @@ export class ScreenShield extends Signals.EventEmitter { +@@ -92,8 +92,10 @@ export class ScreenShield extends Signals.EventEmitter { this._smartcardManager = SmartcardManager.getSmartcardManager(); this._smartcardManager.connect('smartcard-inserted', (manager, token) => { @@ -29,5 +29,5 @@ index d2236fb900..b62440dff6 100644 this._credentialManagers = {}; -- -2.45.2 +2.51.1 diff --git a/0001-st-texture-cache-purge-on-resume.patch b/0001-st-texture-cache-purge-on-resume.patch index 6ac3d68..3f88c94 100644 --- a/0001-st-texture-cache-purge-on-resume.patch +++ b/0001-st-texture-cache-purge-on-resume.patch @@ -1,4 +1,4 @@ -From 0bd4ecbbcc47a8574edae46b56695572a846a1e2 Mon Sep 17 00:00:00 2001 +From b434e61f95b1bb63b8f5e027a718091482be232a Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 15 Jan 2019 12:54:32 -0500 Subject: [PATCH] st-texture-cache: purge on resume @@ -12,10 +12,10 @@ so the texture cache needs to evict all textures in that situation. 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/js/ui/main.js b/js/ui/main.js -index 73980edb88..5c17258177 100644 +index 40898e15c2..bb6d8f6723 100644 --- a/js/ui/main.js +++ b/js/ui/main.js -@@ -263,7 +263,11 @@ async function _initializeUI() { +@@ -298,7 +298,11 @@ async function _initializeUI() { return true; }); @@ -29,7 +29,7 @@ index 73980edb88..5c17258177 100644 global.context.connect('notify::unsafe-mode', () => { if (!global.context.unsafe_mode) diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c -index e03ec44619..ac19e98cf5 100644 +index 2c7601da1a..0df930ad43 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -131,6 +131,16 @@ st_texture_cache_class_init (StTextureCacheClass *klass) @@ -50,17 +50,17 @@ index e03ec44619..ac19e98cf5 100644 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 7ca08ab34a..f600938047 100644 +index 4158298042..5a77165813 100644 --- a/src/st/st-texture-cache.h +++ b/src/st/st-texture-cache.h -@@ -42,6 +42,7 @@ typedef enum { +@@ -43,6 +43,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, + ClutterActor *st_texture_cache_load_gicon (StTextureCache *cache, + StThemeNode *theme_node, -- -2.47.1 +2.51.1 diff --git a/0001-systemActions-Optionally-allow-restart-shutdown-on-l.patch b/0001-systemActions-Optionally-allow-restart-shutdown-on-l.patch deleted file mode 100644 index be85a3e..0000000 --- a/0001-systemActions-Optionally-allow-restart-shutdown-on-l.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 193b664c7daa2ea7707e8b7fba03afe07e2cbd2a Mon Sep 17 00:00:00 2001 -From: Joan Torres Lopez -Date: Tue, 15 Jul 2025 11:27:01 +0200 -Subject: [PATCH] systemActions: Optionally allow restart/shutdown on lock - screen - -Support the new 'restart-enabled' setting from 'org.gnome.desktop.screensaver' -to optionally allow restart/shutdown from the lock screen. - -Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1584 -Part-of: ---- - js/misc/systemActions.js | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js -index a027a8134..c24d254b4 100644 ---- a/js/misc/systemActions.js -+++ b/js/misc/systemActions.js -@@ -12,10 +12,12 @@ import * as Screenshot from '../ui/screenshot.js'; - - const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; - const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; -+const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; - const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; - const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; - const DISABLE_LOG_OUT_KEY = 'disable-log-out'; - const DISABLE_RESTART_KEY = 'disable-restart-buttons'; -+const RESTART_ENABLED_KEY = 'restart-enabled'; - const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out'; - - const POWER_OFF_ACTION_ID = 'power-off'; -@@ -153,6 +155,7 @@ const SystemActions = GObject.registerClass({ - this._loginScreenSettings = new Gio.Settings({schema_id: LOGIN_SCREEN_SCHEMA}); - this._lockdownSettings = new Gio.Settings({schema_id: LOCKDOWN_SCHEMA}); - this._orientationSettings = new Gio.Settings({schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen'}); -+ this._screenSaverSettings = new Gio.Settings({schema_id: SCREENSAVER_SCHEMA}); - - this._session = new GnomeSession.SessionManager(); - this._loginManager = LoginManager.getLoginManager(); -@@ -182,6 +185,9 @@ const SystemActions = GObject.registerClass({ - this._lockdownSettings.connect(`changed::${DISABLE_LOG_OUT_KEY}`, - () => this._updateHaveShutdown()); - -+ this._screenSaverSettings.connect(`changed::${RESTART_ENABLED_KEY}`, -+ () => this._updateHaveShutdown()); -+ - this.forceUpdate(); - - this._orientationSettings.connect('changed::orientation-lock', () => { -@@ -347,7 +353,8 @@ const SystemActions = GObject.registerClass({ - } - - _updatePowerOff() { -- let disabled = Main.sessionMode.isLocked || -+ let disabled = (Main.sessionMode.isLocked && -+ !this._screenSaverSettings.get_boolean(RESTART_ENABLED_KEY)) || - (Main.sessionMode.isGreeter && - this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); - this._actions.get(POWER_OFF_ACTION_ID).available = this._canHavePowerOff && !disabled; --- -2.49.0 - - diff --git a/0001-theme-Welcome-Illustration.patch b/0001-theme-Welcome-Illustration.patch index e3b48fe..9cb1668 100644 --- a/0001-theme-Welcome-Illustration.patch +++ b/0001-theme-Welcome-Illustration.patch @@ -1,4 +1,4 @@ -From 2c43d26950c709020ba78fdc2742adc4fd2841e5 Mon Sep 17 00:00:00 2001 +From a25806709e5cf5d73c2f261f89e8b7be1759d7c7 Mon Sep 17 00:00:00 2001 From: Jakub Steiner Date: Tue, 20 Aug 2024 09:30:36 +0200 Subject: [PATCH] theme: Welcome Illustration @@ -9,7 +9,7 @@ Subject: [PATCH] theme: Welcome Illustration 1 file changed, 106 insertions(+), 245 deletions(-) diff --git a/data/theme/gnome-shell-start.svg b/data/theme/gnome-shell-start.svg -index af139cf0d..609880493 100644 +index af139cf0da..6098804938 100644 --- a/data/theme/gnome-shell-start.svg +++ b/data/theme/gnome-shell-start.svg @@ -1,67 +1,37 @@ @@ -459,5 +459,5 @@ index af139cf0d..609880493 100644 + inkscape:original="M 313.48633 103.79883 L 305.27734 108.99023 L 317.28516 113.2793 L 278.90234 137.55273 L 287.01172 144.30859 L 334.9043 114.01953 L 329.75391 109.73047 L 313.48633 103.79883 z M 339.08398 126.42578 C 336.58186 126.44685 334.02099 126.72082 331.4043 127.24805 C 326.21038 128.33537 321.15063 130.43744 316.22461 133.55273 C 311.3442 136.63918 307.7837 139.98905 305.54297 143.60352 C 303.38732 147.22202 302.62161 150.82875 303.24609 154.42383 C 303.95565 158.02294 306.10659 161.31916 309.69727 164.31055 C 313.32741 167.33481 317.52958 169.33062 322.30469 170.30078 C 327.16487 171.27497 332.19279 171.22013 337.38672 170.13281 C 342.66571 169.04952 347.74459 166.9634 352.625 163.87695 C 357.55102 160.76166 361.0924 157.39584 363.24805 153.77734 C 365.44315 150.19171 366.18612 146.59911 365.47656 143 C 364.85208 139.40492 362.72389 136.09458 359.09375 133.07031 C 355.50307 130.07892 351.27814 128.09723 346.41797 127.12305 C 344.03041 126.63796 341.58611 126.40471 339.08398 126.42578 z M 339.03711 134.75977 C 340.59668 134.65532 342.09264 134.71475 343.52344 134.9375 C 346.38503 135.38301 348.86185 136.47651 350.95312 138.21875 C 353.08387 139.99386 354.25653 141.94201 354.47266 144.06055 C 354.6888 146.17909 353.95735 148.37553 352.27734 150.65234 C 350.68241 152.93318 348.17475 155.15496 344.75391 157.31836 C 341.37867 159.45291 338.0017 160.95311 334.625 161.81641 C 331.28776 162.71258 328.18972 162.9377 325.32812 162.49219 C 322.46654 162.04668 319.96863 160.93528 317.83789 159.16016 C 315.74662 157.41792 314.59309 155.48768 314.37695 153.36914 C 314.16081 151.2506 314.87313 149.0382 316.51367 146.72852 C 318.19368 144.4517 320.72242 142.24587 324.09766 140.11133 C 327.5185 137.94792 330.87439 136.43177 334.16602 135.56445 C 335.85437 135.1328 337.47754 134.86421 339.03711 134.75977 z " + d="m 314.52344,100.95703 a 3.0252571,3.0252571 0 0 0 -2.6543,0.28516 l -8.20898,5.1914 a 3.0252571,3.0252571 0 0 0 0.59961,5.40625 l 6.1621,2.20118 -33.13671,20.95507 a 3.0252571,3.0252571 0 0 0 -0.31836,4.88086 l 8.10937,6.75586 a 3.0252571,3.0252571 0 0 0 3.55274,0.23242 l 47.89257,-30.28906 a 3.0252571,3.0252571 0 0 0 0.31836,-4.88086 l -5.15039,-4.28906 a 3.0252571,3.0252571 0 0 0 -0.89843,-0.51758 z m 24.53515,22.44336 c -2.70552,0.0228 -5.45955,0.32018 -8.25195,0.88281 a 3.0252571,3.0252571 0 0 0 -0.0215,0.004 c -5.57359,1.1668 -10.9804,3.42211 -16.17774,6.70898 -5.15435,3.2597 -9.07893,6.89093 -11.63476,11.01368 a 3.0252571,3.0252571 0 0 0 -0.0293,0.0449 c -2.44678,4.10719 -3.43195,8.54475 -2.67774,12.88672 a 3.0252571,3.0252571 0 0 0 0.0117,0.0684 c 0.86616,4.39343 3.51054,8.31439 7.48438,11.625 4.00932,3.34015 8.71238,5.56847 13.9414,6.63085 a 3.0252571,3.0252571 0 0 0 0.008,0.002 c 5.27203,1.05674 10.73013,0.98909 16.2832,-0.17188 0.001,-2.8e-4 0.003,-0.002 0.004,-0.002 5.65232,-1.16046 11.08416,-3.3969 16.24414,-6.66016 5.19597,-3.28601 9.11209,-6.93484 11.59179,-11.08789 2.49319,-4.07791 3.46133,-8.54055 2.61524,-12.89062 -0.77549,-4.41697 -3.41096,-8.36912 -7.41992,-11.70899 -3.98043,-3.31609 -8.70902,-5.52412 -14.00977,-6.58789 -6.3e-4,-1.3e-4 -0.001,1.3e-4 -0.002,0 -2.59631,-0.52736 -5.25296,-0.7806 -7.95899,-0.75781 z m 0.17969,14.37695 c 1.35455,-0.0907 2.62538,-0.0376 3.82031,0.14844 2.38032,0.37058 4.29192,1.22835 5.95899,2.61719 1.71385,1.4278 2.3223,2.61846 2.44531,3.82422 0.12477,1.22298 -0.24192,2.62183 -1.61914,4.48828 a 3.0252571,3.0252571 0 0 0 -0.0449,0.0625 c -1.27029,1.81658 -3.46759,3.82347 -6.66211,5.84375 -3.14603,1.98959 -6.23089,3.34817 -9.26172,4.12305 a 3.0252571,3.0252571 0 0 0 -0.0352,0.01 c -3.00691,0.80747 -5.66623,0.98001 -8.04687,0.60938 -2.37037,-0.36904 -4.30499,-1.23959 -6.01953,-2.66797 -1.66738,-1.3891 -2.26205,-2.55145 -2.38672,-3.77344 -0.12591,-1.23411 0.24428,-2.66937 1.58203,-4.56055 1.37458,-1.85462 3.60235,-3.84584 6.74609,-5.83398 3.20097,-2.02436 6.27281,-3.3982 9.21094,-4.17383 1.52545,-0.3894 2.96138,-0.62632 4.3125,-0.7168 z" /> -- -2.46.2 +2.51.1 diff --git a/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch b/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch index f7ece9e..e1b4d58 100644 --- a/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch +++ b/0001-windowMenu-Bring-back-workspaces-submenu-for-static-.patch @@ -1,4 +1,4 @@ -From 15df1086028087e5a81830323137a8730d2c5888 Mon Sep 17 00:00:00 2001 +From 29f77afc6d76cadf3f0cac7b0e741049af21e86d 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 @@ -13,10 +13,10 @@ are used. 1 file changed, 17 insertions(+) diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js -index c69c183d7c..2db3962012 100644 +index c140dee4dd..6e28244723 100644 --- a/js/ui/windowMenu.js +++ b/js/ui/windowMenu.js -@@ -153,6 +153,23 @@ export class WindowMenu extends PopupMenu.PopupMenu { +@@ -138,6 +138,23 @@ export class WindowMenu extends PopupMenu.PopupMenu { window.change_workspace(workspace.get_neighbor(dir)); }); } @@ -41,5 +41,5 @@ index c69c183d7c..2db3962012 100644 } -- -2.45.2 +2.51.1 diff --git a/0002-Reapply-main-Notify-gnome-session-when-we-re-ready.patch b/0002-Reapply-main-Notify-gnome-session-when-we-re-ready.patch new file mode 100644 index 0000000..3d90439 --- /dev/null +++ b/0002-Reapply-main-Notify-gnome-session-when-we-re-ready.patch @@ -0,0 +1,46 @@ +From 100b2139c689e8e9ef8095cdd7da8eb016a3f53f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 13 Nov 2025 12:57:58 +0100 +Subject: [PATCH 2/2] Reapply "main: Notify gnome-session when we're ready" + +This reverts commit d697bdceaefeae49ad0e03df7352884899c3e14a. +--- + js/ui/main.js | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/js/ui/main.js b/js/ui/main.js +index bb6d8f6723..7e1b84d694 100644 +--- a/js/ui/main.js ++++ b/js/ui/main.js +@@ -330,6 +330,7 @@ async function _initializeUI() { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + Shell.util_sd_notify(); + global.context.notify_ready(); ++ _notifyGnomeSessionReady(); + return GLib.SOURCE_REMOVE; + }); + +@@ -394,6 +395,20 @@ async function _initializeUI() { + }); + } + ++async function _notifyGnomeSessionReady() { ++ try { ++ let params = GLib.Variant.new('(ss)', ['org.gnome.Shell.desktop', '']); ++ await Gio.DBus.session.call( ++ 'org.gnome.SessionManager', ++ '/org/gnome/SessionManager', ++ 'org.gnome.SessionManager', ++ 'RegisterClient', params, null, ++ Gio.DBusCallFlags.NONE, -1, null); ++ } catch (e) { ++ log(`Error notifying gnome-session that we're ready: ${e.message}`); ++ } ++} ++ + function _handleShowWelcomeScreen() { + const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION); + if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) { +-- +2.51.1 + diff --git a/disable-unlock-entry-until-question.patch b/disable-unlock-entry-until-question.patch index 28f1a9e..cc3a169 100644 --- a/disable-unlock-entry-until-question.patch +++ b/disable-unlock-entry-until-question.patch @@ -1,4 +1,4 @@ -From 8a4efef39a0f48371c632d449688f29d06b9eed7 Mon Sep 17 00:00:00 2001 +From 2af9a2832992b7b89465b47cf9adb979b8d6b087 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 @@ -12,10 +12,10 @@ front, before a password is asked. 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js -index 8a46559d90..9309f60886 100644 +index 800e2f1851..7ee8186dcc 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js -@@ -211,7 +211,7 @@ export const AuthPrompt = GObject.registerClass({ +@@ -217,7 +217,7 @@ export const AuthPrompt = GObject.registerClass({ [this._textEntry, this._passwordEntry].forEach(entry => { entry.clutter_text.connect('text-changed', () => { @@ -25,10 +25,10 @@ index 8a46559d90..9309f60886 100644 }); -- -2.45.2 +2.51.1 -From 007bbb694d4f30175caecbc9ada91515634bfd0b Mon Sep 17 00:00:00 2001 +From 691685e9582e23cce382841c1d66c44ea56ceb33 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 @@ -38,10 +38,10 @@ Subject: [PATCH 2/3] authPrompt: don't spin unless answering question 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js -index 9309f60886..3e72668967 100644 +index 7ee8186dcc..97b276dbf9 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js -@@ -275,13 +275,14 @@ export const AuthPrompt = GObject.registerClass({ +@@ -281,13 +281,14 @@ export const AuthPrompt = GObject.registerClass({ this.verificationStatus = AuthPromptStatus.VERIFICATION_IN_PROGRESS; this.updateSensitivity(false); @@ -61,10 +61,10 @@ index 9309f60886..3e72668967 100644 this.emit('next'); } -- -2.45.2 +2.51.1 -From 92f66a5ff91e47d46a48d7ee51c0808f7ce1eaf9 Mon Sep 17 00:00:00 2001 +From 60c374af34ec60a3aa0403888d70a835774209af 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 @@ -82,10 +82,10 @@ all. 1 file changed, 36 insertions(+) diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js -index 3e72668967..254b4dbb88 100644 +index 97b276dbf9..2bf722b140 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js -@@ -70,6 +70,8 @@ export const AuthPrompt = GObject.registerClass({ +@@ -69,6 +69,8 @@ export const AuthPrompt = GObject.registerClass({ this._defaultButtonWellActor = null; this._cancelledRetries = 0; @@ -94,7 +94,7 @@ index 3e72668967..254b4dbb88 100644 let reauthenticationOnly; if (this._mode === AuthPromptMode.UNLOCK_ONLY) reauthenticationOnly = true; -@@ -127,8 +129,14 @@ export const AuthPrompt = GObject.registerClass({ +@@ -130,8 +132,14 @@ export const AuthPrompt = GObject.registerClass({ } _onDestroy() { @@ -108,8 +108,8 @@ index 3e72668967..254b4dbb88 100644 + this._userVerifier.destroy(); this._userVerifier = null; - } -@@ -282,6 +290,11 @@ export const AuthPrompt = GObject.registerClass({ + this._entry = null; +@@ -288,6 +296,11 @@ export const AuthPrompt = GObject.registerClass({ this._userVerifier.answerQuery(this._queryingService, this._entry.text); } else { this._preemptiveAnswer = this._entry.text; @@ -121,7 +121,7 @@ index 3e72668967..254b4dbb88 100644 } this.emit('next'); -@@ -493,6 +506,11 @@ export const AuthPrompt = GObject.registerClass({ +@@ -510,6 +523,11 @@ export const AuthPrompt = GObject.registerClass({ } setQuestion(question) { @@ -133,7 +133,7 @@ index 3e72668967..254b4dbb88 100644 this._entry.hint_text = question; this._authList.hide(); -@@ -614,6 +632,19 @@ export const AuthPrompt = GObject.registerClass({ +@@ -639,6 +657,19 @@ export const AuthPrompt = GObject.registerClass({ this._updateEntry(false); } @@ -153,18 +153,18 @@ index 3e72668967..254b4dbb88 100644 reset() { let oldStatus = this.verificationStatus; this.verificationStatus = AuthPromptStatus.NOT_VERIFYING; -@@ -621,6 +652,11 @@ export const AuthPrompt = GObject.registerClass({ +@@ -646,6 +677,11 @@ export const AuthPrompt = GObject.registerClass({ this.cancelButton.can_focus = this._hasCancelButton; this._preemptiveAnswer = null; + if (this._preemptiveAnswerWatchId) + this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId); -+ this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch(500, ++ this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch(3000, + this._onUserStoppedTypePreemptiveAnswer.bind(this)); + if (this._userVerifier) this._userVerifier.cancel(); -- -2.45.2 +2.51.1 diff --git a/enforce-smartcard-at-unlock.patch b/enforce-smartcard-at-unlock.patch index 289bf3c..78533bb 100644 --- a/enforce-smartcard-at-unlock.patch +++ b/enforce-smartcard-at-unlock.patch @@ -1,4 +1,4 @@ -From eaa7217db15a28e88ce0b5a84827c306148561ca Mon Sep 17 00:00:00 2001 +From 54a9bed1caa83ee00ca95f3431a3ee2a15a1ee82 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 @@ -16,10 +16,10 @@ This commit adds the necessary api to detect that case. 1 file changed, 7 insertions(+) diff --git a/js/misc/smartcardManager.js b/js/misc/smartcardManager.js -index 32573cd384..6c48c80a19 100644 +index b886110d91..51471e51d4 100644 --- a/js/misc/smartcardManager.js +++ b/js/misc/smartcardManager.js -@@ -118,4 +118,11 @@ class SmartcardManager extends Signals.EventEmitter { +@@ -116,4 +116,11 @@ class SmartcardManager extends Signals.EventEmitter { return true; } @@ -32,10 +32,10 @@ index 32573cd384..6c48c80a19 100644 + } } -- -2.45.2 +2.51.1 -From d7ef26d7b9d352232c9e0a24a04bcfcf6eee7d9a Mon Sep 17 00:00:00 2001 +From bdbed658d7a2fa48b9e7195691f0db3c8f4868e3 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 @@ -48,7 +48,7 @@ gets used for unlock, too. 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/gdm/util.js b/js/gdm/util.js -index c9c040beb7..c0d8852880 100644 +index aced46e78c..28190660e8 100644 --- a/js/gdm/util.js +++ b/js/gdm/util.js @@ -465,6 +465,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { @@ -72,10 +72,10 @@ index c9c040beb7..c0d8852880 100644 else if (this._smartcardManager) return SMARTCARD_SERVICE_NAME; -- -2.45.2 +2.51.1 -From 9bff98c2757e4591035e408aa9cee703cec74bdf Mon Sep 17 00:00:00 2001 +From 2cae726f8e9887c5f49254f9af39bada00e81ca7 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 @@ -88,7 +88,7 @@ after we get a smartcard insertion event. 1 file changed, 2 insertions(+) diff --git a/js/gdm/util.js b/js/gdm/util.js -index c0d8852880..430af4d8ef 100644 +index 28190660e8..c87690f859 100644 --- a/js/gdm/util.js +++ b/js/gdm/util.js @@ -490,6 +490,8 @@ export class ShellUserVerifier extends Signals.EventEmitter { @@ -101,5 +101,5 @@ index c0d8852880..430af4d8ef 100644 } } -- -2.45.2 +2.51.1 diff --git a/fix-some-js-warnings.patch b/fix-some-js-warnings.patch index 9c313a2..eda858a 100644 --- a/fix-some-js-warnings.patch +++ b/fix-some-js-warnings.patch @@ -1,4 +1,4 @@ -From 04bf0729ed6ae75736e297a10603bcb0a564cb28 Mon Sep 17 00:00:00 2001 +From cbb32cb80221d04121d4942bf41a23dd44ada9a9 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 1/2] popupMenu: Guard against non-menu-item children @@ -9,10 +9,10 @@ This avoid a harmless but annoying warning. 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js -index b6ad545087..cbbcfea1ff 100644 +index 0426ce5452..2c882c1067 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js -@@ -951,7 +951,8 @@ export class PopupMenuBase extends Signals.EventEmitter { +@@ -967,7 +967,8 @@ export class PopupMenuBase extends Signals.EventEmitter { } _getMenuItems() { @@ -23,10 +23,10 @@ index b6ad545087..cbbcfea1ff 100644 }); } -- -2.45.2 +2.51.1 -From cffe984d9b85494fcf34500e78875b66c1cec534 Mon Sep 17 00:00:00 2001 +From 406c67ea0877384d85e5dcb5be644acf491cbd4d 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 2/2] st/shadow: Check pipeline when painting @@ -38,10 +38,10 @@ called before paint() or that the pipeline was created successfully. 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/st/st-shadow.c b/src/st/st-shadow.c -index a406b61532..574445aeac 100644 +index 0e7908ac1d..280562b5b7 100644 --- a/src/st/st-shadow.c +++ b/src/st/st-shadow.c -@@ -294,9 +294,10 @@ st_shadow_helper_paint (StShadowHelper *helper, +@@ -288,9 +288,10 @@ st_shadow_helper_paint (StShadowHelper *helper, ClutterActorBox *actor_box, uint8_t paint_opacity) { @@ -58,5 +58,5 @@ index a406b61532..574445aeac 100644 + paint_opacity); } -- -2.45.2 +2.51.1 diff --git a/gdm-support-banner-message-file.patch b/gdm-support-banner-message-file.patch deleted file mode 100644 index 5157f26..0000000 --- a/gdm-support-banner-message-file.patch +++ /dev/null @@ -1,232 +0,0 @@ -From f328eee88c4e700a29625e87fd6fc997a138c66b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 22 Nov 2024 19:23:59 +0100 -Subject: [PATCH 1/3] loginDialog: Split out getBannerText() helper - -The new methods will make it easier to add alternative sources -for the banner text. - -Part-of: ---- - js/gdm/loginDialog.js | 13 ++++++++++--- - 1 file changed, 10 insertions(+), 3 deletions(-) - -diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js -index 27baae83ad..c3c397c0d1 100644 ---- a/js/gdm/loginDialog.js -+++ b/js/gdm/loginDialog.js -@@ -873,11 +873,18 @@ export const LoginDialog = GObject.registerClass({ - this._authPrompt.cancelButton.visible = cancelVisible; - } - -+ _getBannerText() { -+ const enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); -+ if (!enabled) -+ return null; -+ -+ return this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); -+ } -+ - _updateBanner() { -- let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); -- let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); -+ const text = this._getBannerText(); - -- if (enabled && text) { -+ if (text) { - this._bannerLabel.set_text(text); - this._bannerLabel.show(); - } else { --- -2.48.1 - - -From 5f92c12c727bd447d7e539b1e6c8f3ac6ddf715a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 22 Nov 2024 19:36:42 +0100 -Subject: [PATCH 2/3] loginDialog: Update banner asynchronously - -We will soon allow reading the banner text from a file. Prepare -for that by making the method asynchronous. - -Part-of: ---- - js/gdm/loginDialog.js | 15 +++++++++------ - 1 file changed, 9 insertions(+), 6 deletions(-) - -diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js -index c3c397c0d1..125cf299fa 100644 ---- a/js/gdm/loginDialog.js -+++ b/js/gdm/loginDialog.js -@@ -505,9 +505,9 @@ export const LoginDialog = GObject.registerClass({ - this._settings = new Gio.Settings({schema_id: GdmUtil.LOGIN_SCREEN_SCHEMA}); - - this._settings.connect(`changed::${GdmUtil.BANNER_MESSAGE_KEY}`, -- this._updateBanner.bind(this)); -+ () => this._updateBanner().catch(logError)); - this._settings.connect(`changed::${GdmUtil.BANNER_MESSAGE_TEXT_KEY}`, -- this._updateBanner.bind(this)); -+ () => this._updateBanner().catch(logError)); - this._settings.connect(`changed::${GdmUtil.DISABLE_USER_LIST_KEY}`, - this._updateDisableUserList.bind(this)); - this._settings.connect(`changed::${GdmUtil.LOGO_KEY}`, -@@ -576,7 +576,7 @@ export const LoginDialog = GObject.registerClass({ - this._bannerLabel.clutter_text.line_wrap = true; - this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; - bannerBox.add_child(this._bannerLabel); -- this._updateBanner(); -+ this._updateBanner().catch(logError); - - this._sessionMenuButton = new SessionMenuButton(); - this._sessionMenuButton.connect('session-activated', -@@ -873,16 +873,19 @@ export const LoginDialog = GObject.registerClass({ - this._authPrompt.cancelButton.visible = cancelVisible; - } - -- _getBannerText() { -+ async _getBannerText() { - const enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); - if (!enabled) - return null; - -+ // placeholder -+ await false; -+ - return this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); - } - -- _updateBanner() { -- const text = this._getBannerText(); -+ async _updateBanner() { -+ const text = await this._getBannerText(); - - if (text) { - this._bannerLabel.set_text(text); --- -2.48.1 - - -From a73a4f8455b1374b38724a532137a9b614a7fb19 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 22 Nov 2024 19:17:44 +0100 -Subject: [PATCH 3/3] loginDialog: Support loading banner message from file - -Support the new `banner-message-path` and `banner-message-source` -settings, which allows loading the banner message from a path -instead of GSettings. This is mainly useful for `/etc/motd` and -similar mechanisms, to show the same message for both graphical -and non-graphical logins. - -Part-of: ---- - js/gdm/loginDialog.js | 53 +++++++++++++++++++++++++++++++++++++++++-- - js/gdm/util.js | 2 ++ - 2 files changed, 53 insertions(+), 2 deletions(-) - -diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js -index 125cf299fa..fa91615026 100644 ---- a/js/gdm/loginDialog.js -+++ b/js/gdm/loginDialog.js -@@ -46,6 +46,8 @@ const _SCROLL_ANIMATION_TIME = 500; - const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0; - const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60; - -+Gio._promisify(Gio.File.prototype, 'load_contents_async'); -+ - export const UserListItem = GObject.registerClass({ - Signals: {'activate': {}}, - }, class UserListItem extends St.Button { -@@ -508,6 +510,16 @@ export const LoginDialog = GObject.registerClass({ - () => this._updateBanner().catch(logError)); - this._settings.connect(`changed::${GdmUtil.BANNER_MESSAGE_TEXT_KEY}`, - () => this._updateBanner().catch(logError)); -+ this._settings.connect(`changed::${GdmUtil.BANNER_MESSAGE_SOURCE_KEY}`, -+ () => { -+ if (this._updateBannerMessageFile()) -+ this._updateBanner().catch(logError); -+ }); -+ this._settings.connect(`changed::${GdmUtil.BANNER_MESSAGE_PATH_KEY}`, -+ () => { -+ if (this._updateBannerMessageFile()) -+ this._updateBanner().catch(logError); -+ }); - this._settings.connect(`changed::${GdmUtil.DISABLE_USER_LIST_KEY}`, - this._updateDisableUserList.bind(this)); - this._settings.connect(`changed::${GdmUtil.LOGO_KEY}`, -@@ -576,6 +588,8 @@ export const LoginDialog = GObject.registerClass({ - this._bannerLabel.clutter_text.line_wrap = true; - this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; - bannerBox.add_child(this._bannerLabel); -+ -+ this._updateBannerMessageFile(); - this._updateBanner().catch(logError); - - this._sessionMenuButton = new SessionMenuButton(); -@@ -873,13 +887,48 @@ export const LoginDialog = GObject.registerClass({ - this._authPrompt.cancelButton.visible = cancelVisible; - } - -+ _updateBannerMessageFile() { -+ const path = this._settings.get_string(GdmUtil.BANNER_MESSAGE_SOURCE_KEY) === 'file' -+ ? this._settings.get_string(GdmUtil.BANNER_MESSAGE_PATH_KEY) -+ : null; -+ const file = path -+ ? Gio.File.new_for_path(path) -+ : null; -+ -+ if (!file && !this._bannerMessageFile) -+ return false; -+ -+ if (file && this._bannerMessageFile && this._bannerMessageFile.equal(file)) -+ return false; -+ -+ this._bannerMessageMonitor?.disconnectObject(this); -+ this._bannerMessageMonitor = null; -+ -+ this._bannerMessageFile = file; -+ -+ if (file) { -+ this._bannerMessageMonitor = file.monitor_file(Gio.FileMonitorFlags.NONE, null); -+ this._bannerMessageMonitor.connectObject( -+ 'changed', () => this._updateBanner().catch(logError), this); -+ } -+ -+ return true; -+ } -+ - async _getBannerText() { - const enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY); - if (!enabled) - return null; - -- // placeholder -- await false; -+ if (this._bannerMessageFile) { -+ try { -+ const [contents] = await this._bannerMessageFile.load_contents_async(null); -+ return new TextDecoder().decode(contents); -+ } catch (e) { -+ console.error(`Failed to read banner from ${this._bannerMessageFile.get_path()}: ${e.message}`); -+ return null; -+ } -+ } - - return this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY); - } -diff --git a/js/gdm/util.js b/js/gdm/util.js -index 1e0154f36f..f6b797c321 100644 ---- a/js/gdm/util.js -+++ b/js/gdm/util.js -@@ -33,7 +33,9 @@ export const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; - export const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; - export const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; - export const BANNER_MESSAGE_KEY = 'banner-message-enable'; -+export const BANNER_MESSAGE_SOURCE_KEY = 'banner-message-source'; - export const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; -+export const BANNER_MESSAGE_PATH_KEY = 'banner-message-path'; - export const ALLOWED_FAILURES_KEY = 'allowed-failures'; - - export const LOGO_KEY = 'logo'; --- -2.48.1 - diff --git a/gnome-shell-enabled-extensions-background-logos.patch b/gnome-shell-enabled-extensions-background-logos.patch index 759f383..d11bdb8 100644 --- a/gnome-shell-enabled-extensions-background-logos.patch +++ b/gnome-shell-enabled-extensions-background-logos.patch @@ -1,4 +1,4 @@ -From 8fcaf1708fee86b1c16c9749dc1794be5a2a86fd Mon Sep 17 00:00:00 2001 +From f8340bf01c7520af83a3098e7c574c67facbde1e Mon Sep 17 00:00:00 2001 From: rpm-build Date: Mon, 28 Feb 2022 10:27:09 -0500 Subject: [PATCH] data: Enable logo extension out of the box @@ -12,7 +12,7 @@ This commit makes sure it gets enabled out of the box. 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 3dd9d2d86b..fe1e266f8c 100644 +index 9a55ee71db..1f9862a779 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -12,7 +12,7 @@ @@ -25,5 +25,5 @@ index 3dd9d2d86b..fe1e266f8c 100644 GNOME Shell extensions have a UUID property; this key lists extensions -- -2.45.2 +2.51.1 diff --git a/gnome-shell-favourite-apps-firefox.patch b/gnome-shell-favourite-apps-firefox.patch index efbf8bb..bd49906 100644 --- a/gnome-shell-favourite-apps-firefox.patch +++ b/gnome-shell-favourite-apps-firefox.patch @@ -1,35 +1,22 @@ -From 8a6298a1e7f7d46abf86ddec8ca2c8edbe4a589f Mon Sep 17 00:00:00 2001 +From 723135b8a757c02e7f598a0a95f99287dda805cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 17 Sep 2014 07:11:12 +0200 Subject: [PATCH] Replace Web with Firefox in default favorites --- data/default-apps/dash.txt | 2 +- - js/ui/appFavorites.js | 1 + - 2 files changed, 2 insertions(+), 1 deletion(-) + 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/default-apps/dash.txt b/data/default-apps/dash.txt -index d0c2c21eb8..a926311701 100644 +index 3687574be8..cdd8ef04e3 100644 --- a/data/default-apps/dash.txt +++ b/data/default-apps/dash.txt @@ -1,4 +1,4 @@ -org.gnome.Epiphany.desktop +org.mozilla.Firefox.desktop org.gnome.Calendar.desktop - org.gnome.Music.desktop - gnome.Nautilus.desktop -diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js -index e3a76dce96..b6525f3cc9 100644 ---- a/js/ui/appFavorites.js -+++ b/js/ui/appFavorites.js -@@ -48,6 +48,7 @@ const RENAMED_DESKTOP_IDS = { - 'gnotski.desktop': 'org.gnome.Klotski.desktop', - 'gtali.desktop': 'org.gnome.Tali.desktop', - 'iagno.desktop': 'org.gnome.Reversi.desktop', -+ 'firefox.desktop': 'org.mozilla.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', + org.gnome.Nautilus.desktop + org.gnome.Software.desktop -- -2.48.1 +2.51.1 diff --git a/gnome-shell-favourite-apps-terminal.patch b/gnome-shell-favourite-apps-terminal.patch index eefbf01..2082bfa 100644 --- a/gnome-shell-favourite-apps-terminal.patch +++ b/gnome-shell-favourite-apps-terminal.patch @@ -1,4 +1,4 @@ -From c20b996f0cf89d4620d01fa777787b80b15c8a37 Mon Sep 17 00:00:00 2001 +From 578b2f5c52ab55ae1839f9d055c56762f13e01cf 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 @@ -8,16 +8,16 @@ Subject: [PATCH] appFavorites: Add terminal 1 file changed, 1 insertion(+) diff --git a/data/default-apps/dash.txt b/data/default-apps/dash.txt -index a926311701..5178256f6a 100644 +index cdd8ef04e3..4ab3f2913f 100644 --- a/data/default-apps/dash.txt +++ b/data/default-apps/dash.txt -@@ -3,5 +3,6 @@ org.gnome.Calendar.desktop - org.gnome.Music.desktop - gnome.Nautilus.desktop +@@ -2,5 +2,6 @@ org.mozilla.Firefox.desktop + org.gnome.Calendar.desktop + org.gnome.Nautilus.desktop org.gnome.Software.desktop +org.gnome.Ptyxis.desktop org.gnome.TextEditor.desktop org.gnome.Calculator.desktop -- -2.48.1 +2.51.1 diff --git a/gnome-shell.spec b/gnome-shell.spec index 4baa034..303f40c 100644 --- a/gnome-shell.spec +++ b/gnome-shell.spec @@ -2,7 +2,7 @@ ## (rpmautospec version 0.6.5) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 3; + release_number = 2; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -18,7 +18,7 @@ %endif Name: gnome-shell -Version: 47.4 +Version: 49.4 Release: %autorelease Summary: Window management and application launching for GNOME @@ -28,21 +28,32 @@ Source0: https://download.gnome.org/sources/gnome-shell/%{major_version}/ # Replace Epiphany with Firefox in the default favourite apps list, etc # and enable background extension by default -Patch: tweak-app-defaults.patch Patch: gnome-shell-favourite-apps-firefox.patch Patch: gnome-shell-favourite-apps-terminal.patch Patch: gnome-shell-enabled-extensions-background-logos.patch -# Some users might have a broken PAM config, so we really need this -# downstream patch to stop trying on configuration errors. -Patch: 0001-gdm-Work-around-failing-fingerprint-auth.patch +# girpository-2.0 port would require updates to both gjs and glib2, +# so revert it +Patch: revert-gir-2.0-port.patch + +# Required schemas have been backported +Patch: 0001-build-Lower-gsettings-desktop-schemas-requirement.patch + +# Revert gnome-session related changes +Patch: 0001-Revert-data-Drop-org.gnome.Shell.desktop.patch +Patch: 0002-Reapply-main-Notify-gnome-session-when-we-re-ready.patch # GDM/Lock stuff Patch: 0001-screenShield-unblank-when-inserting-smartcard.patch Patch: enforce-smartcard-at-unlock.patch Patch: disable-unlock-entry-until-question.patch -Patch: gdm-support-banner-message-file.patch -Patch: 0001-systemActions-Optionally-allow-restart-shutdown-on-l.patch +Patch: 0001-main-Register-session-with-GDM-on-startup.patch +# Passwordless work +# https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3212 +Patch: 0001-Support-for-web-login-and-unified-auth-mechanism.patch +# Some users might have a broken PAM config, so we really need this +# downstream patch to stop trying on configuration errors. +Patch: 0001-gdm-Work-around-failing-fingerprint-auth.patch # Extensions Patch: 0001-extensionDownloader-Refuse-to-override-system-extens.patch @@ -56,6 +67,9 @@ Patch: 0001-st-texture-cache-purge-on-resume.patch Patch: fix-some-js-warnings.patch Patch: 0001-data-Update-generated-stylesheets.patch Patch: 0001-theme-Welcome-Illustration.patch +Patch: screenshot-tool.patch +Patch: 0001-Revert-status-keyboard-Limit-the-input-method-indica.patch +Patch: 0001-Revert-Require-gjs-1.81.2-for-build-because-Intl.Seg.patch %define eds_version 3.45.1 %define gnome_desktop_version 44.0-7 @@ -64,16 +78,16 @@ Patch: 0001-theme-Welcome-Illustration.patch %define gjs_version 1.73.1 %define gtk4_version 4.0.0 %define adwaita_version 1.5.0 -%define mutter_version 47.0 +%define mutter_version 49.0 %define polkit_version 0.100 %define gsettings_desktop_schemas_version 47~alpha %define ibus_version 1.5.2 %define gnome_bluetooth_version 1:42.3 %define gstreamer_version 1.4.5 -%define pipewire_version 0.3.0 +%define pipewire_version 0.3.49 %define gnome_settings_daemon_version 3.37.1 -BuildRequires: bash-completion +BuildRequires: pkgconfig(bash-completion) BuildRequires: gcc BuildRequires: meson BuildRequires: git @@ -97,6 +111,7 @@ BuildRequires: pkgconfig(libpipewire-0.3) >= %{pipewire_version} BuildRequires: pkgconfig(gtk4) >= %{gtk4_version} BuildRequires: gettext >= 0.19.6 BuildRequires: python3 +BuildRequires: python3-argcomplete # for rst2man BuildRequires: python3-docutils @@ -110,11 +125,9 @@ BuildRequires: pkgconfig(libpulse) BuildRequires: gnome-bluetooth-libs-devel >= %{gnome_bluetooth_version} %endif # Bootstrap requirements -BuildRequires: gtk-doc %ifnarch s390 s390x Recommends: 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 Recommends: gnome-session-xsession @@ -142,7 +155,9 @@ Requires: xdg-user-dirs-gtk # needed for schemas Requires: at-spi2-atk%{?_isa} # needed for on-screen keyboard -Requires: ibus%{?_isa} >= %{ibus_version} +Recommends: ibus%{?_isa} >= %{ibus_version} +# needed for gobject-introspection typelib +Requires: ibus-libs%{?_isa} >= %{ibus_version} # needed for "show keyboard layout" Requires: tecla # needed for the user menu @@ -152,6 +167,7 @@ Requires: gdm-libs%{?_isa} Requires: gnome-control-center # needed by some utilities Requires: python3%{_isa} +Requires: python3-argcomplete # needed for the dual-GPU launch menu Requires: switcheroo-control # needed for clocks/weather integration @@ -178,6 +194,7 @@ Requires: webkitgtk6.0%{?_isa} ExcludeArch: %{ix86} %endif +Provides: gnome-shell(api) = %{major_version} Provides: desktop-notification-daemon = %{version}-%{release} Provides: PolicyKit-authentication-agent = %{version}-%{release} Provides: bundled(gvc) @@ -241,6 +258,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.Porta %doc NEWS README.md %{_bindir}/gnome-shell %{_bindir}/gnome-extensions +%{_bindir}/gnome-screenshot-tool %{_bindir}/gnome-shell-extension-tool %{_bindir}/gnome-shell-test-tool %{_datadir}/glib-2.0/schemas/*.xml @@ -248,6 +266,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.Porta %{_datadir}/applications/org.gnome.Shell.Extensions.desktop %{_datadir}/applications/org.gnome.Shell.desktop %{_datadir}/bash-completion/completions/gnome-extensions +%{_datadir}/bash-completion/completions/gnome-screenshot-tool %{_datadir}/gnome-control-center/keybindings/50-gnome-shell-launchers.xml %{_datadir}/gnome-control-center/keybindings/50-gnome-shell-screenshots.xml %{_datadir}/gnome-control-center/keybindings/50-gnome-shell-system.xml @@ -258,13 +277,17 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.Porta %{_datadir}/dbus-1/services/org.gnome.Shell.HotplugSniffer.service %{_datadir}/dbus-1/services/org.gnome.Shell.Notifications.service %{_datadir}/dbus-1/services/org.gnome.Shell.Screencast.service +%{_datadir}/dbus-1/interfaces/org.gnome.Shell.Brightness.xml %{_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.Shell.ScreenTime.xml %{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider.xml %{_datadir}/dbus-1/interfaces/org.gnome.ShellSearchProvider2.xml +%{_datadir}/desktop-directories/X-GNOME-Shell-System.directory +%{_datadir}/desktop-directories/X-GNOME-Shell-Utilities.directory %{_datadir}/icons/hicolor/scalable/apps/org.gnome.Shell.Extensions.svg %{_datadir}/icons/hicolor/symbolic/apps/org.gnome.Shell.Extensions-symbolic.svg %{_userunitdir}/org.gnome.Shell-disable-extensions.service @@ -276,6 +299,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.Porta %{_libexecdir}/gnome-shell-perf-helper %{_libexecdir}/gnome-shell-hotplug-sniffer %{_mandir}/man1/gnome-extensions.1* +%{_mandir}/man1/gnome-screenshot-tool.1* %{_mandir}/man1/gnome-shell.1* %if %{portal_helper} @@ -288,9 +312,76 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Shell.Porta %changelog ## START: Generated by rpmautospec -* Wed Sep 24 2025 Darren Archibald - 47.4-3 +* Tue Feb 17 2026 Joan Torres Lopez - 49.4-2 +- Update passwordless GDM implementation to match upstream + +* Wed Feb 11 2026 Florian Müllner - 49.4-1 +- Update to 49.4 + +* Thu Jan 29 2026 Joan Torres Lopez - 49.1-9 +- Extend display time of messages interval + +* Wed Jan 21 2026 Joan Torres Lopez - 49.1-8 +- Allow preemptive answer for 3 secs before locking text entry + +* Wed Jan 21 2026 Joan Torres Lopez - 49.1-7 +- Update passwordless GDM patch series + +* Thu Jan 15 2026 Joan Torres Lopez - 49.1-6 +- Update styles for passwordless GDM + +* Thu Jan 15 2026 Joan Torres Lopez - 49.1-5 +- Add passwordless gdm patch series + +* Fri Nov 14 2025 Joan Torres Lopez - 49.1-4 +- Register session with GDM on startup + +* Thu Nov 13 2025 Florian Müllner - 49.1-3 +- Provide gnome-shell(api) that extension packages can use to check + compatibility + +* Thu Nov 13 2025 Florian Müllner - 49.1-2 +- Revert gnome-session related changes + +* Tue Nov 11 2025 Florian Müllner - 49.1-1 +- Update to 49.1 + +* Wed Oct 15 2025 Florian Müllner - 47.10-1 +- Update to 47.10 + +* Wed Oct 15 2025 Florian Müllner - 47.9-2 +- Remove unused gnome-desktop-3 require + +* Wed Aug 27 2025 Florian Müllner - 47.9-1 +- Update to 47.9 + +* Wed Aug 13 2025 Florian Müllner - 47.8-6 +- Add missing require + +* Wed Aug 13 2025 Florian Müllner - 47.8-5 +- Revert basing input method indicator length on graphemes + +* Thu Jul 31 2025 Joan Torres Lopez - 47.8-4 - Allow restart/shutdown on lock screen +* Tue Jul 22 2025 Florian Müllner - 47.8-3 +- Bump release to force rebuild + +* Tue Jul 22 2025 Florian Müllner - 47.8-2 +- Add screenshot CLI tool + +* Tue Jul 15 2025 Florian Müllner - 47.8-1 +- Update to 47.8 + +* Mon Jun 02 2025 Florian Müllner - 47.7-1 +- Update to 47.7 + +* Wed May 21 2025 Florian Müllner - 47.6-1 +- Update to 47.6 + +* Mon May 05 2025 Florian Müllner - 47.5-1 +- Update to 47.5 + * Fri Feb 14 2025 Florian Müllner - 47.4-2 - data: Tweak app defaults diff --git a/revert-gir-2.0-port.patch b/revert-gir-2.0-port.patch new file mode 100644 index 0000000..fc38431 --- /dev/null +++ b/revert-gir-2.0-port.patch @@ -0,0 +1,199 @@ +From 445bf69e9aafa8ad76e0b4bc70cd56359f717776 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 6 Nov 2025 15:20:15 +0100 +Subject: [PATCH 1/3] Revert "build: Depend on GLib 2.86.0" + +This reverts commit 02d9487c1992972a344cac5866f0aedd357827ee. +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 0cc6cceb94..ff5fc88734 100644 +--- a/meson.build ++++ b/meson.build +@@ -21,7 +21,7 @@ libmutter_test_pc = 'libmutter-test-' + mutter_api_version + ecal_req = '>= 3.33.1' + eds_req = '>= 3.33.1' + gcr_req = '>= 3.90.0' +-gio_req = '>= 2.86.0' ++gio_req = '>= 2.85.1' + gjs_req = '>= 1.85.90' + gtk_req = '>= 4.0' + mutter_req = '>= 49.0' +-- +2.51.1 + + +From f4a4ce8a0608d8fb8c39cf7b81c828a22e305e63 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 6 Nov 2025 15:21:29 +0100 +Subject: [PATCH 2/3] Revert "build: Depend on gjs 1.85.90" + +This reverts commit 08563fae7cd392527b0712c583750add21ac97d7. +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index ff5fc88734..5bc23778ef 100644 +--- a/meson.build ++++ b/meson.build +@@ -22,7 +22,7 @@ ecal_req = '>= 3.33.1' + eds_req = '>= 3.33.1' + gcr_req = '>= 3.90.0' + gio_req = '>= 2.85.1' +-gjs_req = '>= 1.85.90' ++gjs_req = '>= 1.85.1' + gtk_req = '>= 4.0' + mutter_req = '>= 49.0' + polkit_req = '>= 0.100' +-- +2.51.1 + + +From 6f94c04657a9013dd9ca2e5366e6118d8cb96b20 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 6 Nov 2025 15:27:19 +0100 +Subject: [PATCH 3/3] Revert "Port to gjs-1.85/girepository-2.0" + +This reverts commit c8e28918aa96c53333ea7019eb24642b7878b548. +--- + meson.build | 7 ++++--- + src/main.c | 23 ++++++++++------------- + src/run-js-test.c | 9 +++------ + 3 files changed, 17 insertions(+), 22 deletions(-) + +diff --git a/meson.build b/meson.build +index 5bc23778ef..ddd7d2c6e4 100644 +--- a/meson.build ++++ b/meson.build +@@ -21,8 +21,9 @@ libmutter_test_pc = 'libmutter-test-' + mutter_api_version + ecal_req = '>= 3.33.1' + eds_req = '>= 3.33.1' + gcr_req = '>= 3.90.0' +-gio_req = '>= 2.85.1' +-gjs_req = '>= 1.85.1' ++gio_req = '>= 2.79.2' ++gi_req = '>= 1.49.1' ++gjs_req = '>= 1.81.2' + gtk_req = '>= 4.0' + mutter_req = '>= 49.0' + polkit_req = '>= 0.100' +@@ -72,7 +73,7 @@ ecal_dep = dependency('libecal-2.0', version: ecal_req) + eds_dep = dependency('libedataserver-1.2', version: eds_req) + gcr_dep = dependency('gcr-4', version: gcr_req) + gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') +-gi_dep = dependency('girepository-2.0', version: gio_req) ++gi_dep = dependency('gobject-introspection-1.0', version: gi_req) + gio_dep = dependency('gio-2.0', version: gio_req) + gio_unix_dep = dependency('gio-unix-2.0', version: gio_req) + gjs_dep = dependency('gjs-1.0', version: gjs_req) +diff --git a/src/main.c b/src/main.c +index 99eca3e3c9..af4fa8597e 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -13,7 +13,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -133,7 +133,7 @@ shell_dbus_init (gboolean replace) + + #ifdef HAVE_EXE_INTROSPECTION + static void +-maybe_add_rpath_introspection_paths (GIRepository *repo) ++maybe_add_rpath_introspection_paths (void) + { + ElfW (Dyn) *dyn; + ElfW (Dyn) *rpath = NULL; +@@ -210,8 +210,8 @@ maybe_add_rpath_introspection_paths (GIRepository *repo) + g_debug ("Prepending RPATH directory '%s' " + "to introsepciton library search path", + rpath_dir->str); +- gi_repository_prepend_search_path (repo, rpath_dir->str); +- gi_repository_prepend_library_path (repo, rpath_dir->str); ++ g_irepository_prepend_search_path (rpath_dir->str); ++ g_irepository_prepend_library_path (rpath_dir->str); + } + } + #endif /* HAVE_EXE_INTROSPECTION */ +@@ -219,23 +219,20 @@ maybe_add_rpath_introspection_paths (GIRepository *repo) + static void + shell_introspection_init (void) + { +- g_autoptr (GIRepository) repo = NULL; + +- repo = gi_repository_dup_default (); +- +- gi_repository_prepend_search_path (repo, MUTTER_TYPELIB_DIR); +- gi_repository_prepend_search_path (repo, SHELL_TYPELIB_DIR); ++ g_irepository_prepend_search_path (MUTTER_TYPELIB_DIR); ++ g_irepository_prepend_search_path (SHELL_TYPELIB_DIR); + + /* We need to explicitly add the directories where the private libraries are + * installed to the GIR's library path, so that they can be found at runtime + * when linking using DT_RUNPATH (instead of DT_RPATH), which is the default + * for some linkers (e.g. gold) and in some distros (e.g. Debian). + */ +- gi_repository_prepend_library_path (repo, MUTTER_TYPELIB_DIR); +- gi_repository_prepend_library_path (repo, GNOME_SHELL_PKGLIBDIR); ++ g_irepository_prepend_library_path (MUTTER_TYPELIB_DIR); ++ g_irepository_prepend_library_path (GNOME_SHELL_PKGLIBDIR); + + #ifdef HAVE_EXE_INTROSPECTION +- maybe_add_rpath_introspection_paths (repo); ++ maybe_add_rpath_introspection_paths (); + #endif + } + +@@ -614,7 +611,7 @@ main (int argc, char **argv) + context = meta_create_context (WM_NAME); + meta_context_add_option_entries (context, gnome_shell_options, + GETTEXT_PACKAGE); +- meta_context_add_option_group (context, gi_repository_get_option_group ()); ++ meta_context_add_option_group (context, g_irepository_get_option_group ()); + + session_mode = (char *) g_getenv ("GNOME_SHELL_SESSION_MODE"); + +diff --git a/src/run-js-test.c b/src/run-js-test.c +index 7a539d3a23..4f795252c2 100644 +--- a/src/run-js-test.c ++++ b/src/run-js-test.c +@@ -30,7 +30,7 @@ + #include + #include + +-#include ++#include + #include + + #include "shell-global.h" +@@ -64,7 +64,6 @@ int + main (int argc, char **argv) + { + GOptionContext *context; +- g_autoptr (GIRepository) repo = NULL; + g_autoptr (GError) error = NULL; + ShellGlobal *global; + GjsContext *js_context; +@@ -86,10 +85,8 @@ main (int argc, char **argv) + global = shell_global_get (); + js_context = _shell_global_get_gjs_context (global); + +- repo = gi_repository_dup_default (); +- +- gi_repository_prepend_search_path (repo, MUTTER_TYPELIB_DIR); +- gi_repository_prepend_search_path (repo, SHELL_TYPELIB_DIR); ++ g_irepository_prepend_search_path (MUTTER_TYPELIB_DIR); ++ g_irepository_prepend_search_path (SHELL_TYPELIB_DIR); + + if (argc < 2) + { +-- +2.51.1 + diff --git a/screenshot-tool.patch b/screenshot-tool.patch new file mode 100644 index 0000000..096b3a7 --- /dev/null +++ b/screenshot-tool.patch @@ -0,0 +1,453 @@ +From 83a585f075bacf766e4cf40429fc41c217cf2e29 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 6 Nov 2025 15:17:41 +0100 +Subject: [PATCH 1/2] Revert "screenshot: Remove gnome-screenshot from allowed + senders" + +The following commit will add a CLI screenshot tool that reuses +the D-Bus name from gnome-screenshot, so re-allow the caller. + +This reverts commit c9944dbf9bd47a972eb4f3908fefe5878d315d6e. +--- + js/ui/screenshot.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js +index 209207710b..328080235c 100644 +--- a/js/ui/screenshot.js ++++ b/js/ui/screenshot.js +@@ -2450,6 +2450,7 @@ export class ScreenshotService { + this._senderChecker = new DBusSenderChecker([ + 'org.gnome.SettingsDaemon.MediaKeys', + 'org.freedesktop.impl.portal.desktop.gnome', ++ 'org.gnome.Screenshot', + ]); + + this._lockdownSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.lockdown'}); +-- +2.51.1 + + +From 525260b46f147fd5272e9c5047a8263f4db1634f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 18 Jul 2025 21:34:36 +0200 +Subject: [PATCH 2/2] Add screenshot CLI tool + +The tool provides the same API as gnome-screenshot, but leverages +GNOME's built-in screenshot UI instead of implementing its own +interactive dialog. +--- + man/gnome-screenshot-tool.rst | 49 +++++++ + man/meson.build | 23 ++- + meson.build | 2 + + src/gnome-screenshot-tool | 266 ++++++++++++++++++++++++++++++++++ + src/meson.build | 26 ++++ + 5 files changed, 358 insertions(+), 8 deletions(-) + create mode 100644 man/gnome-screenshot-tool.rst + create mode 100755 src/gnome-screenshot-tool + +diff --git a/man/gnome-screenshot-tool.rst b/man/gnome-screenshot-tool.rst +new file mode 100644 +index 0000000000..a471738394 +--- /dev/null ++++ b/man/gnome-screenshot-tool.rst +@@ -0,0 +1,49 @@ ++===================== ++gnome-screenshot-tool ++===================== ++ ++----------------------------------------- ++Screenshot CLI tool for the GNOME desktop ++----------------------------------------- ++ ++:Manual section: 1 ++:Manual group: User Commands ++ ++SYNOPSIS ++-------- ++**gnome-screenshot-tool** [*OPTION*...] ++ ++DESCRIPTION ++----------- ++**gnome-screenshot-tool** is a GNOME utility for taking screenshots of ++the entire screen, a window or a user-defined area of the screen. ++ ++OPTIONS ++------- ++``-w``, ``--window`` ++ ++ Grab the currently active window instead of the entire screen ++ ++``-a``, ``--area`` ++ ++ Grab an area of the screen instead of the entire screen ++ ++``-i``, ``--interactive`` ++ ++ Bring up GNOME's native screenshot dialog ++ ++``-p``, ``--include-pointer`` ++ ++ Include the pointer with the screenshot ++ ++``-d``, ``--delay``\ =\ *SECONDS* ++ ++ Take the screenshot after the specified delay [in seconds] ++ ++``-f``, ``--file``\ =\ *FILENAME* ++ ++ Save the screenshot directly to this file ++ ++``-h``, ``--help`` ++ ++ Show a summary of the available options +diff --git a/man/meson.build b/man/meson.build +index c61bf51513..cfb9c8e07f 100644 +--- a/man/meson.build ++++ b/man/meson.build +@@ -1,8 +1,15 @@ +-custom_target('man page', +- input: 'gnome-shell.rst', +- output: 'gnome-shell.1', +- command: [rst2man, '--syntax-highlight=none', '@INPUT@'], +- capture: true, +- install_dir: mandir + '/man1', +- install: true +-) ++man_pages = [ ++ 'gnome-shell.rst', ++ 'gnome-screenshot-tool.rst', ++] ++ ++foreach page : man_pages ++ custom_target(f'@page@ man page', ++ input: page, ++ output: '@BASENAME@.1', ++ command: [rst2man, '--syntax-highlight=none', '@INPUT@'], ++ capture: true, ++ install_dir: mandir + '/man1', ++ install: true ++ ) ++endforeach +diff --git a/meson.build b/meson.build +index ddd7d2c6e4..327e5f0b7e 100644 +--- a/meson.build ++++ b/meson.build +@@ -141,6 +141,8 @@ if get_option('man') + subdir('man') + endif + ++bash_completion = dependency('bash-completion', required: false) ++ + mutter_typelibdir = mutter_dep.get_variable('typelibdir') + python = find_program('python3') + gjs = find_program('gjs') +diff --git a/src/gnome-screenshot-tool b/src/gnome-screenshot-tool +new file mode 100755 +index 0000000000..e29650abfe +--- /dev/null ++++ b/src/gnome-screenshot-tool +@@ -0,0 +1,266 @@ ++#!/usr/bin/env python3 ++ ++import argparse ++import os ++import gi ++ ++gi.require_version('Gdk', '4.0') ++gi.require_version('Gtk', '4.0') ++gi.require_version('GdkPixbuf', '2.0') ++from gi.repository import GLib, Gio, GdkPixbuf, Gdk, Gtk # type: ignore ++ ++try: ++ import argcomplete ++except ModuleNotFoundError: ++ pass ++ ++NAME = "org.gnome.Shell.Screenshot" ++OBJECT_PATH = "/org/gnome/Shell/Screenshot" ++INTERFACE = "org.gnome.Shell.Screenshot" ++ ++class Screenshot: ++ def __init__(self): ++ self._proxy = Gio.DBusProxy.new_for_bus_sync( ++ bus_type = Gio.BusType.SESSION, ++ flags = Gio.DBusProxyFlags.NONE, ++ info = None, ++ name = NAME, ++ object_path = OBJECT_PATH, ++ interface_name = INTERFACE, ++ cancellable = None, ++ ) ++ ++ def screenshot(self, include_cursor) -> GdkPixbuf.Pixbuf: ++ variant = self._proxy.call_sync( ++ method_name = "Screenshot", ++ parameters = GLib.Variant( ++ "(bbs)", ++ ( ++ include_cursor, ++ True, ++ self._get_tmp_filename(), ++ ), ++ ), ++ flags = Gio.DBusCallFlags.NO_AUTO_START, ++ timeout_msec = -1, ++ cancellable = None, ++ ) ++ return self._get_screenshot_from_result(variant) ++ ++ def screenshot_window(self, include_cursor) -> GdkPixbuf.Pixbuf: ++ variant = self._proxy.call_sync( ++ method_name = "ScreenshotWindow", ++ parameters = GLib.Variant( ++ "(bbbs)", ++ ( ++ True, ++ include_cursor, ++ True, ++ self._get_tmp_filename(), ++ ), ++ ), ++ flags = Gio.DBusCallFlags.NO_AUTO_START, ++ timeout_msec = -1, ++ cancellable = None, ++ ) ++ return self._get_screenshot_from_result(variant) ++ ++ def screenshot_area(self, x, y, width, height) -> GdkPixbuf.Pixbuf: ++ variant = self._proxy.call_sync( ++ method_name = "ScreenshotArea", ++ parameters = GLib.Variant( ++ "(iiiibs)", ++ ( ++ x, ++ y, ++ width, ++ height, ++ True, ++ self._get_tmp_filename(), ++ ), ++ ), ++ flags = Gio.DBusCallFlags.NO_AUTO_START, ++ timeout_msec = -1, ++ cancellable = None, ++ ) ++ return self._get_screenshot_from_result(variant) ++ ++ def interactive_screenshot(self) -> GdkPixbuf.Pixbuf: ++ variant = self._proxy.call_sync( ++ method_name = "InteractiveScreenshot", ++ parameters = None, ++ flags = Gio.DBusCallFlags.NO_AUTO_START, ++ timeout_msec = GLib.MAXINT, ++ cancellable = None, ++ ) ++ [success, uri] = variant ++ file = Gio.File.new_for_uri(uri) ++ return self._get_screenshot_from_result((success, file.get_path())) ++ ++ def select_area(self) -> tuple[int, int, int, int]: ++ variant = self._proxy.call_sync( ++ method_name = "SelectArea", ++ parameters = None, ++ flags = Gio.DBusCallFlags.NO_AUTO_START, ++ timeout_msec = -1, ++ cancellable = None, ++ ) ++ [x, y, width, height] = variant ++ return (x, y, width, height) ++ ++ def _get_screenshot_from_result(self, variant): ++ [success, filename] = variant ++ assert success ++ screenshot = GdkPixbuf.Pixbuf.new_from_file(filename) ++ GLib.unlink(filename) ++ return screenshot ++ ++ def _get_tmp_filename(self): ++ path = GLib.build_filenamev([ ++ GLib.get_user_cache_dir(), ++ "gnome-screenshot-tool", ++ ]) ++ GLib.mkdir_with_parents(path, 0o700) ++ return GLib.build_filenamev([path, f'scr-{GLib.random_int()}']) ++ ++class ScreenshotApp(Gtk.Application): ++ def __init__(self, config): ++ super().__init__(application_id="org.gnome.Screenshot") ++ ++ self._config = config ++ ++ def do_startup(self): ++ Gtk.Application.do_startup(self) ++ self._proxy = Screenshot() ++ ++ def do_activate(self): ++ self.hold() ++ if self._config.area: ++ self._selected_area = self._proxy.select_area() ++ self._start_screenshot_timeout() ++ ++ def _start_screenshot_timeout(self): ++ GLib.timeout_add_seconds(self._config.delay, self._take_screenshot) ++ ++ def _take_screenshot(self): ++ include_pointer = self._config.include_pointer ++ ++ try: ++ if self._config.interactive: ++ screenshot = self._proxy.interactive_screenshot() ++ elif self._config.area: ++ [x, y, width, height] = self._selected_area ++ screenshot = self._proxy.screenshot_area(x, y, width, height) ++ elif self._config.window: ++ screenshot = self._proxy.screenshot_window(include_pointer) ++ else: ++ screenshot = self._proxy.screenshot(include_pointer) ++ ++ # if self._config.clipboard: ++ # self._save_screenshot_to_clipboard(screenshot) ++ ++ if self._config.file: ++ target_file = Gio.File.new_for_commandline_arg(self._config.file) ++ override = True ++ else: ++ target_file = self._get_default_target_file() ++ override = False ++ ++ self._save_screenshot_to_file(screenshot, target_file, override) ++ except Exception as e: ++ print(f'Failed to take screenshot: {e}') ++ finally: ++ self.release() ++ ++ return GLib.SOURCE_REMOVE ++ ++ def _save_screenshot_to_clipboard(self, screenshot): ++ dpy = Gdk.Display.get_default() ++ clipboard = dpy.get_clipboard() ++ clipboard.set(screenshot) ++ ++ def _save_screenshot_to_file(self, screenshot, file, override): ++ if override: ++ ostream = file.replace(None, False, Gio.FileCreateFlags.NONE, None) ++ else: ++ ostream = file.create(Gio.FileCreateFlags.NONE, None) ++ ++ [_, ext] = os.path.splitext(file.get_basename()) ++ if not ext: ++ ext = 'png' ++ else: ++ ext = ext[1:] ++ ++ def find_writable_format(formats, ext): ++ for f in formats: ++ for e in f.get_extensions(): ++ if e == ext and f.is_writable(): ++ return f.get_name() ++ return None ++ ++ formats = GdkPixbuf.Pixbuf.get_formats() ++ format = find_writable_format(formats, ext) ++ ++ if format == 'png': ++ keys = ["tEXt::Software"] ++ values = ["gnome-screenshot-tool"] ++ else: ++ keys = None ++ values = None ++ ++ screenshot.save_to_streamv(ostream, format, keys, values, None) ++ ++ def _get_default_target_file(self): ++ path = GLib.build_filenamev([ ++ GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) or GLib.get_home_dir(), ++ "Screenshots", ++ ]) ++ dir = Gio.File.new_for_path(path) ++ try: ++ dir.make_directory_with_parents(None) ++ except GLib.Error as e: ++ if not e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.EXISTS): ++ raise e ++ ++ timestamp = GLib.DateTime.new_now_local().format('%Y-%m-%d %H-%M-%S') ++ name = "Screenshot From %s" % (timestamp) ++ ++ def suffixes() -> str: ++ yield '' ++ ++ i = 1 ++ while True: ++ yield f'-{i}' ++ i = i + 1 ++ ++ for suffix in suffixes(): ++ file = dir.get_child(f'{name}{suffix}.png') ++ if not file.query_exists(None): ++ return file ++ ++def main(): ++ parser = argparse.ArgumentParser() ++ ++ group = parser.add_mutually_exclusive_group() ++ group.add_argument("-w", "--window", action="store_true", help="Grab a window instead of the entire screen") ++ group.add_argument("-a", "--area", action="store_true", help="Grab an area of the screen instead of the entire screen") ++ group.add_argument("-i", "--interactive", action="store_true", help="Interactively set options") ++ ++ # parser.add_argument("-c", "--clipboard", action="store_true", help="Send the grab directly to the clipboard") ++ parser.add_argument("-p", "--include-pointer", action="store_true", help="Include the pointer with the screenshot") ++ parser.add_argument("-d", "--delay", type=int, default=0, help="Take a screenshot after the specified delay (in seconds)") ++ parser.add_argument("-f", "--file", type=str, help="Save screenshot directly to this file") ++ ++ if argcomplete: ++ argcomplete.autocomplete(parser) ++ args = parser.parse_args() ++ ++ if args.interactive: ++ if args.include_pointer: ++ print("Option --include-pointer is ignored in interactive mode.") ++ ++ app = ScreenshotApp(args) ++ app.run(None) ++ ++if __name__ == "__main__": ++ main() +diff --git a/src/meson.build b/src/meson.build +index a4ce889eac..d83d84a5e9 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -315,3 +315,29 @@ run_test = executable('run-js-test', 'run-js-test.c', + include_directories: [conf_inc], + build_rpath: mutter_typelibdir, + ) ++ ++install_data( ++ 'gnome-screenshot-tool', ++ install_dir: bindir ++) ++ ++if bash_completion.found() ++ bash_completion_dir = bash_completion.get_variable( ++ pkgconfig: 'completionsdir', ++ pkgconfig_define: ['datadir', datadir], ++ ) ++ ++ register_python_argcomplete = find_program('register-python-argcomplete') ++ ++ custom_target( ++ 'screenshot-tool-bash-completion', ++ output: 'gnome-screenshot-tool', ++ command: [ ++ register_python_argcomplete, ++ 'gnome-screenshot-tool', ++ ], ++ capture: true, ++ install_dir: bash_completion_dir, ++ install: true, ++ ) ++endif +-- +2.51.1 + diff --git a/sources b/sources index 8f242ab..37e97f1 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (gnome-shell-47.4.tar.xz) = 65a0dab1bdd6957e52c624fde21ff3fe1d189aa0d6e2a13035ab53ab51e82ff8a88d28a5308c77d4650d5e1b0f643be702ee767379c96a10308752924c79dd72 +f101a8f261f6e3ac9f2b08efad71fd6be85f3c7d9070c747e5b9420fa9faa9e86281f76774385807bd9b1ce100fc82b37fa646147e7a4003c7ce3f8cf66b5670 gnome-shell-49.4.tar.xz diff --git a/tweak-app-defaults.patch b/tweak-app-defaults.patch deleted file mode 100644 index 431f705..0000000 --- a/tweak-app-defaults.patch +++ /dev/null @@ -1,384 +0,0 @@ -From cfeade9501e158b426f571d05ab15ee6dbe49e75 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 7 Feb 2025 15:01:48 +0100 -Subject: [PATCH 1/3] data: Generate dash/app-grid defaults from text files - -Defining default apps as serialized GVariants isn't very human-friendly, -which likely contributes to the fact that the lists are in parts horribly -outdated (Books! Cheese! Screenshot! gedit!). - -Instead, generate the lists at build time from simple text files, which -should be much easier to update. - -Part-of: ---- - data/default-apps/app-grid.txt | 21 ++++++++++++++ - data/default-apps/dash.txt | 6 ++++ - data/default-apps/utilities-folder.txt | 17 +++++++++++ - data/meson.build | 8 ++++++ - data/org.gnome.shell.gschema.xml.in | 28 +++--------------- - js/misc/config.js.in | 2 ++ - js/misc/meson.build | 4 +++ - js/ui/appDisplay.js | 19 ++---------- - meson.build | 1 + - meson/generate-app-list.py | 40 ++++++++++++++++++++++++++ - 10 files changed, 106 insertions(+), 40 deletions(-) - create mode 100644 data/default-apps/app-grid.txt - create mode 100644 data/default-apps/dash.txt - create mode 100644 data/default-apps/utilities-folder.txt - create mode 100755 meson/generate-app-list.py - -diff --git a/data/default-apps/app-grid.txt b/data/default-apps/app-grid.txt -new file mode 100644 -index 0000000000..d78ed165eb ---- /dev/null -+++ b/data/default-apps/app-grid.txt -@@ -0,0 +1,21 @@ -+org.gnome.Geary.desktop -+org.gnome.Contacts.desktop -+org.gnome.Weather.desktop -+org.gnome.clocks.desktop -+org.gnome.Maps.desktop -+org.gnome.Books.desktop -+org.gnome.Photos.desktop -+org.gnome.Totem.desktop -+org.gnome.Calculator.desktop -+org.gnome.gedit.desktop -+simple-scan.desktop -+org.gnome.Settings.desktop -+org.gnome.SystemMonitor.desktop -+org.gnome.Boxes.desktop -+org.gnome.Terminal.desktop -+Utilities # folder -+org.gnome.Characters.desktop -+yelp.desktop -+org.gnome.Screenshot.desktop -+org.gnome.Cheese.desktop -+org.gnome.font-viewer.desktop -diff --git a/data/default-apps/dash.txt b/data/default-apps/dash.txt -new file mode 100644 -index 0000000000..2a8e8bcd06 ---- /dev/null -+++ b/data/default-apps/dash.txt -@@ -0,0 +1,6 @@ -+org.gnome.Epiphany.desktop -+org.gnome.Calendar.desktop -+org.gnome.Music.desktop -+gnome.Nautilus.desktop -+org.gnome.Software.desktop -+org.gnome.TextEditor.desktop -diff --git a/data/default-apps/utilities-folder.txt b/data/default-apps/utilities-folder.txt -new file mode 100644 -index 0000000000..6169977751 ---- /dev/null -+++ b/data/default-apps/utilities-folder.txt -@@ -0,0 +1,17 @@ -+# Sorted by name as shown in menus, not filename -+nm-connection-editor.desktop # Advanced Network Configuration -+org.gnome.DejaDup.desktop # Backups -+org.gnome.Characters.desktop # Characters -+org.gnome.Connections.desktop # Connections -+org.gnome.DiskUtility.desktop # Disks -+org.gnome.baobab.desktop # Disk Usage Analyzer -+org.gnome.Evince.desktop # Document Viewer -+org.gnome.FileRoller.desktop # File Roller -+org.gnome.font-viewer.desktop # Fonts -+org.gnome.Loupe.desktop # Image Viewer -+org.gnome.Logs.desktop # Logs -+org.freedesktop.MalcontentControl.desktop # Parental Controls -+org.gnome.seahorse.Application.desktop # Passwords and Keys -+org.freedesktop.GnomeAbrt.desktop # Problem Reporting -+org.gnome.tweaks.desktop # Tweaks -+org.gnome.Usage.desktop # Usage -diff --git a/data/meson.build b/data/meson.build -index ed13b6baea..c7565ee810 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -90,6 +90,14 @@ install_data(keybinding_files, install_dir: keysdir) - - schemaconf = configuration_data() - schemaconf.set('GETTEXT_PACKAGE', meson.project_name()) -+schemaconf.set('DASH_APPS', run_command( -+ generate_app_list, 'default-apps/dash.txt', -+ check: true, -+).stdout()) -+schemaconf.set('APP_GRID_APPS', run_command( -+ generate_app_list, '--pages', 'default-apps/app-grid.txt', -+ check: true, -+).stdout()) - schema = configure_file( - input: 'org.gnome.shell.gschema.xml.in', - output: 'org.gnome.shell.gschema.xml', -diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in -index 3cd37692a7..b1c2534f9c 100644 ---- a/data/org.gnome.shell.gschema.xml.in -+++ b/data/org.gnome.shell.gschema.xml.in -@@ -61,7 +61,9 @@ - - - -- [ 'org.gnome.Epiphany.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop', 'org.gnome.TextEditor.desktop'] -+ -+ @DASH_APPS@ -+ - List of desktop file IDs for favorite applications - - The applications corresponding to these identifiers -@@ -118,29 +120,7 @@ - - - }>, -- 'org.gnome.Contacts.desktop': <{'position': <1>}>, -- 'org.gnome.Weather.desktop': <{'position': <2>}>, -- 'org.gnome.clocks.desktop': <{'position': <3>}>, -- 'org.gnome.Maps.desktop': <{'position': <4>}>, -- 'org.gnome.Books.desktop': <{'position': <5>}>, -- 'org.gnome.Photos.desktop': <{'position': <6>}>, -- 'org.gnome.Totem.desktop': <{'position': <7>}>, -- 'org.gnome.Calculator.desktop': <{'position': <8>}>, -- 'org.gnome.gedit.desktop': <{'position': <9>}>, -- 'simple-scan.desktop': <{'position': <10>}>, -- 'org.gnome.Settings.desktop': <{'position': <11>}>, -- 'org.gnome.SystemMonitor.desktop': <{'position': <12>}>, -- 'org.gnome.Boxes.desktop': <{'position': <13>}>, -- 'org.gnome.Terminal.desktop': <{'position': <14>}>, -- 'Utilities': <{'position': <15>}>, -- 'org.gnome.Characters.desktop': <{'position': <16>}>, -- 'yelp.desktop': <{'position': <17>}>, -- 'org.gnome.Screenshot.desktop': <{'position': <18>}>, -- 'org.gnome.Cheese.desktop': <{'position': <19>}>, -- 'org.gnome.font-viewer.desktop': <{'position': <20>}> -- }] -+ @APP_GRID_APPS@ - ]]> - Layout of the app picker - -diff --git a/js/misc/config.js.in b/js/misc/config.js.in -index a5069e438a..6485d614f5 100644 ---- a/js/misc/config.js.in -+++ b/js/misc/config.js.in -@@ -20,3 +20,5 @@ export const LIBMUTTER_API_VERSION = '@LIBMUTTER_API_VERSION@'; - - export const HAVE_BLUETOOTH = pkg.checkSymbol('GnomeBluetooth', '3.0', - 'Client.default_adapter_state'); -+ -+export const UTILITIES_FOLDER_APPS = @UTILS_FOLDER_APPS@; -diff --git a/js/misc/meson.build b/js/misc/meson.build -index 5fc8ca433f..4b137ef3e6 100644 ---- a/js/misc/meson.build -+++ b/js/misc/meson.build -@@ -5,6 +5,10 @@ jsconf.set('GETTEXT_PACKAGE', meson.project_name()) - jsconf.set('LIBMUTTER_API_VERSION', mutter_api_version) - jsconf.set10('HAVE_NETWORKMANAGER', have_networkmanager) - jsconf.set10('HAVE_PORTAL_HELPER', have_portal_helper) -+jsconf.set('UTILS_FOLDER_APPS', run_command( -+ generate_app_list, '../../data/default-apps/utilities-folder.txt', -+ check: true, -+).stdout()) - jsconf.set('datadir', datadir) - jsconf.set('libexecdir', libexecdir) - -diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js -index bdce3bdeed..992004b8f9 100644 ---- a/js/ui/appDisplay.js -+++ b/js/ui/appDisplay.js -@@ -24,6 +24,8 @@ import * as SystemActions from '../misc/systemActions.js'; - - import * as Main from './main.js'; - -+import {UTILITIES_FOLDER_APPS} from '../misc/config.js'; -+ - const MENU_POPUP_TIMEOUT = 600; - const POPDOWN_DIALOG_TIMEOUT = 500; - -@@ -60,22 +62,7 @@ const DEFAULT_FOLDERS = { - 'Utilities': { - name: 'X-GNOME-Utilities.directory', - categories: ['X-GNOME-Utilities'], -- apps: [ -- 'org.freedesktop.GnomeAbrt.desktop', -- 'nm-connection-editor.desktop', -- 'org.gnome.baobab.desktop', -- 'org.gnome.Connections.desktop', -- 'org.gnome.DejaDup.desktop', -- 'org.gnome.DiskUtility.desktop', -- 'org.gnome.Evince.desktop', -- 'org.gnome.FileRoller.desktop', -- 'org.gnome.font-viewer.desktop', -- 'org.gnome.Loupe.desktop', -- 'org.freedesktop.MalcontentControl.desktop', -- 'org.gnome.seahorse.Application.desktop', -- 'org.gnome.tweaks.desktop', -- 'org.gnome.Usage.desktop', -- ], -+ apps: UTILITIES_FOLDER_APPS, - }, - 'YaST': { - name: 'suse-yast.directory', -diff --git a/meson.build b/meson.build -index c9955ca651..6f10a366bf 100644 ---- a/meson.build -+++ b/meson.build -@@ -137,6 +137,7 @@ endif - mutter_typelibdir = mutter_dep.get_variable('typelibdir') - python = find_program('python3') - gjs = find_program('gjs') -+generate_app_list = find_program('meson/generate-app-list.py') - - cc = meson.get_compiler('c') - -diff --git a/meson/generate-app-list.py b/meson/generate-app-list.py -new file mode 100755 -index 0000000000..067c36967c ---- /dev/null -+++ b/meson/generate-app-list.py -@@ -0,0 +1,40 @@ -+#!/usr/bin/env python3 -+ -+import argparse -+ -+def read_app_ids(path) -> str: -+ ids = [] -+ with open(path, "r") as file: -+ for line in file: -+ # strip comments -+ line, _, _ = line.partition('#'); -+ line = line.strip() -+ if len(line) > 0: -+ ids.append(line) -+ return ids -+ -+def print_as_array(ids): -+ mapped_ids = list(map(lambda i: f" '{i}'", ids)) -+ print('[') -+ print(',\n'.join(mapped_ids)) -+ print(']') -+ -+def print_as_pages(ids): -+ mapped_ids = [] -+ for i, id in enumerate(ids): -+ mapped_ids.append(f" '{id}': <{{'position': <{i}>}}>") -+ -+ print('[{') -+ print(',\n'.join(mapped_ids)) -+ print('}]') -+ -+parser = argparse.ArgumentParser() -+parser.add_argument('--pages', action='store_true') -+parser.add_argument('file') -+args = parser.parse_args() -+ -+ids = read_app_ids(args.file) -+if args.pages: -+ print_as_pages(ids) -+else: -+ print_as_array(ids) --- -2.48.1 - - -From aeb7adf061d74d75edc85f65520cb404e8a4361d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 14 Feb 2025 15:44:30 +0100 -Subject: [PATCH 2/3] data: Tweak dash/app-grid defaults - -With the current RHEL 10 app set, the dash and app grid look -rather empty. - -Address this by - - adding Calculator to dash - - moving Characters and Baobab out of the Utilities folder - -With those changes, there are at least 5 apps in the dash and 2 rows -in the app grid. ---- - data/default-apps/dash.txt | 1 + - data/default-apps/utilities-folder.txt | 2 -- - 2 files changed, 1 insertion(+), 2 deletions(-) - -diff --git a/data/default-apps/dash.txt b/data/default-apps/dash.txt -index 2a8e8bcd06..d0c2c21eb8 100644 ---- a/data/default-apps/dash.txt -+++ b/data/default-apps/dash.txt -@@ -4,3 +4,4 @@ org.gnome.Music.desktop - gnome.Nautilus.desktop - org.gnome.Software.desktop - org.gnome.TextEditor.desktop -+org.gnome.Calculator.desktop -diff --git a/data/default-apps/utilities-folder.txt b/data/default-apps/utilities-folder.txt -index 6169977751..187e3cfc54 100644 ---- a/data/default-apps/utilities-folder.txt -+++ b/data/default-apps/utilities-folder.txt -@@ -1,10 +1,8 @@ - # Sorted by name as shown in menus, not filename - nm-connection-editor.desktop # Advanced Network Configuration - org.gnome.DejaDup.desktop # Backups --org.gnome.Characters.desktop # Characters - org.gnome.Connections.desktop # Connections - org.gnome.DiskUtility.desktop # Disks --org.gnome.baobab.desktop # Disk Usage Analyzer - org.gnome.Evince.desktop # Document Viewer - org.gnome.FileRoller.desktop # File Roller - org.gnome.font-viewer.desktop # Fonts --- -2.48.1 - - -From 7a21fca2f59d68896f54600b64e5d9a216203e9e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 7 Feb 2025 15:54:15 +0100 -Subject: [PATCH 3/3] appDisplay: Filter apps in default folder - -We currently create the default folder with the corresponding -app list, regardless of whether the apps are actually part of -the default install or not. - -This matters when a user explicitly install such an app later, -as it will be hidden away in the folder rather than appended -to the app grid as expected. - -To avoid that, only add currently-installed apps to the folder -when creating it. - -Part-of: ---- - js/ui/appDisplay.js | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) - -diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js -index 992004b8f9..6bd2d899cd 100644 ---- a/js/ui/appDisplay.js -+++ b/js/ui/appDisplay.js -@@ -1419,12 +1419,16 @@ class AppDisplay extends BaseAppView { - if (this._folderSettings.get_strv('folder-children').length > 0) - return; - -+ const appSys = Shell.AppSystem.get_default(); - const folders = Object.keys(DEFAULT_FOLDERS); - this._folderSettings.set_strv('folder-children', folders); - - const {path} = this._folderSettings; - for (const folder of folders) { - const {name, categories, apps} = DEFAULT_FOLDERS[folder]; -+ const filteredApps = apps -+ ? apps.filter(id => appSys.lookup_app(id) != null) -+ : []; - const child = new Gio.Settings({ - schema_id: 'org.gnome.desktop.app-folders.folder', - path: `${path}folders/${folder}/`, -@@ -1432,8 +1436,8 @@ class AppDisplay extends BaseAppView { - child.set_string('name', name); - child.set_boolean('translate', true); - child.set_strv('categories', categories); -- if (apps) -- child.set_strv('apps', apps); -+ if (filteredApps.length > 0) -+ child.set_strv('apps', filteredApps); - } - } - --- -2.48.1 -