summaryrefslogtreecommitdiffstats
path: root/js/ui/unlockDialog.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/ui/unlockDialog.js')
-rw-r--r--js/ui/unlockDialog.js899
1 files changed, 899 insertions, 0 deletions
diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js
new file mode 100644
index 0000000..24c8160
--- /dev/null
+++ b/js/ui/unlockDialog.js
@@ -0,0 +1,899 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported UnlockDialog */
+
+const {
+ AccountsService, Atk, Clutter, Gdm, Gio,
+ GnomeDesktop, GLib, GObject, Meta, Shell, St,
+} = imports.gi;
+
+const Background = imports.ui.background;
+const Layout = imports.ui.layout;
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+const SwipeTracker = imports.ui.swipeTracker;
+
+const AuthPrompt = imports.gdm.authPrompt;
+
+// The timeout before going back automatically to the lock screen (in seconds)
+const IDLE_TIMEOUT = 2 * 60;
+
+// The timeout before showing the unlock hint (in seconds)
+const HINT_TIMEOUT = 4;
+
+const CROSSFADE_TIME = 300;
+const FADE_OUT_TRANSLATION = 200;
+const FADE_OUT_SCALE = 0.3;
+
+const BLUR_BRIGHTNESS = 0.55;
+const BLUR_SIGMA = 60;
+
+const SUMMARY_ICON_SIZE = 32;
+
+var NotificationsBox = GObject.registerClass({
+ Signals: { 'wake-up-screen': {} },
+}, class NotificationsBox extends St.BoxLayout {
+ _init() {
+ super._init({
+ vertical: true,
+ name: 'unlockDialogNotifications',
+ style_class: 'unlock-dialog-notifications-container',
+ });
+
+ this._scrollView = new St.ScrollView({ hscrollbar_policy: St.PolicyType.NEVER });
+ this._notificationBox = new St.BoxLayout({
+ vertical: true,
+ style_class: 'unlock-dialog-notifications-container',
+ });
+ this._scrollView.add_actor(this._notificationBox);
+
+ this.add_child(this._scrollView);
+
+ this._settings = new Gio.Settings({
+ schema_id: 'org.gnome.desktop.notifications',
+ });
+
+ this._sources = new Map();
+ Main.messageTray.getSources().forEach(source => {
+ this._sourceAdded(Main.messageTray, source, true);
+ });
+ this._updateVisibility();
+
+ Main.messageTray.connectObject('source-added',
+ this._sourceAdded.bind(this), this);
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ _onDestroy() {
+ let items = this._sources.entries();
+ for (let [source, obj] of items)
+ this._removeSource(source, obj);
+ }
+
+ _updateVisibility() {
+ this._notificationBox.visible =
+ this._notificationBox.get_children().some(a => a.visible);
+
+ this.visible = this._notificationBox.visible;
+ }
+
+ _makeNotificationSource(source, box) {
+ let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
+ box.add_child(sourceActor);
+
+ let textBox = new St.BoxLayout({
+ x_expand: true,
+ y_expand: true,
+ y_align: Clutter.ActorAlign.CENTER,
+ });
+ box.add_child(textBox);
+
+ let title = new St.Label({
+ text: source.title,
+ style_class: 'unlock-dialog-notification-label',
+ x_expand: true,
+ x_align: Clutter.ActorAlign.START,
+ });
+ textBox.add(title);
+
+ let count = source.unseenCount;
+ let countLabel = new St.Label({
+ text: `${count}`,
+ visible: count > 1,
+ style_class: 'unlock-dialog-notification-count-text',
+ });
+ textBox.add(countLabel);
+
+ box.visible = count !== 0;
+ return [title, countLabel];
+ }
+
+ _makeNotificationDetailedSource(source, box) {
+ let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
+ let sourceBin = new St.Bin({ child: sourceActor });
+ box.add(sourceBin);
+
+ let textBox = new St.BoxLayout({ vertical: true });
+ box.add_child(textBox);
+
+ let title = new St.Label({
+ text: source.title.replace(/\n/g, ' '),
+ style_class: 'unlock-dialog-notification-label',
+ });
+ textBox.add(title);
+
+ let visible = false;
+ for (let i = 0; i < source.notifications.length; i++) {
+ let n = source.notifications[i];
+
+ if (n.acknowledged)
+ continue;
+
+ let body = '';
+ if (n.bannerBodyText) {
+ const bodyText = n.bannerBodyText.replace(/\n/g, ' ');
+ body = n.bannerBodyMarkup
+ ? bodyText
+ : GLib.markup_escape_text(bodyText, -1);
+ }
+
+ let label = new St.Label({ style_class: 'unlock-dialog-notification-count-text' });
+ label.clutter_text.set_markup(`<b>${n.title}</b> ${body}`);
+ textBox.add(label);
+
+ visible = true;
+ }
+
+ box.visible = visible;
+ return [title, null];
+ }
+
+ _shouldShowDetails(source) {
+ return source.policy.detailsInLockScreen ||
+ source.narrowestPrivacyScope === MessageTray.PrivacyScope.SYSTEM;
+ }
+
+ _updateSourceBoxStyle(source, obj, box) {
+ let hasCriticalNotification =
+ source.notifications.some(n => n.urgency === MessageTray.Urgency.CRITICAL);
+
+ if (hasCriticalNotification !== obj.hasCriticalNotification) {
+ obj.hasCriticalNotification = hasCriticalNotification;
+
+ if (hasCriticalNotification)
+ box.add_style_class_name('critical');
+ else
+ box.remove_style_class_name('critical');
+ }
+ }
+
+ _showSource(source, obj, box) {
+ if (obj.detailed)
+ [obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
+ else
+ [obj.titleLabel, obj.countLabel] = this._makeNotificationSource(source, box);
+
+ box.visible = obj.visible && (source.unseenCount > 0);
+
+ this._updateSourceBoxStyle(source, obj, box);
+ }
+
+ _wakeUpScreenForSource(source) {
+ if (!this._settings.get_boolean('show-banners'))
+ return;
+ const obj = this._sources.get(source);
+ if (obj?.sourceBox.visible)
+ this.emit('wake-up-screen');
+ }
+
+ _sourceAdded(tray, source, initial) {
+ let obj = {
+ visible: source.policy.showInLockScreen,
+ detailed: this._shouldShowDetails(source),
+ sourceBox: null,
+ titleLabel: null,
+ countLabel: null,
+ hasCriticalNotification: false,
+ };
+
+ obj.sourceBox = new St.BoxLayout({
+ style_class: 'unlock-dialog-notification-source',
+ x_expand: true,
+ });
+ this._showSource(source, obj, obj.sourceBox);
+ this._notificationBox.add_child(obj.sourceBox);
+
+ source.connectObject(
+ 'notify::count', () => this._countChanged(source, obj),
+ 'notify::title', () => this._titleChanged(source, obj),
+ 'destroy', () => {
+ this._removeSource(source, obj);
+ this._updateVisibility();
+ }, this);
+ obj.policyChangedId = source.policy.connect('notify', (policy, pspec) => {
+ if (pspec.name === 'show-in-lock-screen')
+ this._visibleChanged(source, obj);
+ else
+ this._detailedChanged(source, obj);
+ });
+
+ this._sources.set(source, obj);
+
+ if (!initial) {
+ // block scrollbars while animating, if they're not needed now
+ let boxHeight = this._notificationBox.height;
+ if (this._scrollView.height >= boxHeight)
+ this._scrollView.vscrollbar_policy = St.PolicyType.NEVER;
+
+ let widget = obj.sourceBox;
+ let [, natHeight] = widget.get_preferred_height(-1);
+ widget.height = 0;
+ widget.ease({
+ height: natHeight,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ duration: 250,
+ onComplete: () => {
+ this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
+ widget.set_height(-1);
+ },
+ });
+
+ this._updateVisibility();
+ this._wakeUpScreenForSource(source);
+ }
+ }
+
+ _titleChanged(source, obj) {
+ obj.titleLabel.text = source.title;
+ }
+
+ _countChanged(source, obj) {
+ // A change in the number of notifications may change whether we show
+ // details.
+ let newDetailed = this._shouldShowDetails(source);
+ let oldDetailed = obj.detailed;
+
+ obj.detailed = newDetailed;
+
+ if (obj.detailed || oldDetailed !== newDetailed) {
+ // A new notification was pushed, or a previous notification was destroyed.
+ // Give up, and build the list again.
+
+ obj.sourceBox.destroy_all_children();
+ obj.titleLabel = obj.countLabel = null;
+ this._showSource(source, obj, obj.sourceBox);
+ } else {
+ let count = source.unseenCount;
+ obj.countLabel.text = `${count}`;
+ obj.countLabel.visible = count > 1;
+ }
+
+ obj.sourceBox.visible = obj.visible && (source.unseenCount > 0);
+
+ this._updateVisibility();
+ this._wakeUpScreenForSource(source);
+ }
+
+ _visibleChanged(source, obj) {
+ if (obj.visible === source.policy.showInLockScreen)
+ return;
+
+ obj.visible = source.policy.showInLockScreen;
+ obj.sourceBox.visible = obj.visible && source.unseenCount > 0;
+
+ this._updateVisibility();
+ this._wakeUpScreenForSource(source);
+ }
+
+ _detailedChanged(source, obj) {
+ let newDetailed = this._shouldShowDetails(source);
+ if (obj.detailed === newDetailed)
+ return;
+
+ obj.detailed = newDetailed;
+
+ obj.sourceBox.destroy_all_children();
+ obj.titleLabel = obj.countLabel = null;
+ this._showSource(source, obj, obj.sourceBox);
+ }
+
+ _removeSource(source, obj) {
+ obj.sourceBox.destroy();
+ obj.sourceBox = obj.titleLabel = obj.countLabel = null;
+
+ source.policy.disconnect(obj.policyChangedId);
+
+ this._sources.delete(source);
+ }
+});
+
+var Clock = GObject.registerClass(
+class UnlockDialogClock extends St.BoxLayout {
+ _init() {
+ super._init({ style_class: 'unlock-dialog-clock', vertical: true });
+
+ this._time = new St.Label({
+ style_class: 'unlock-dialog-clock-time',
+ x_align: Clutter.ActorAlign.CENTER,
+ });
+ this._date = new St.Label({
+ style_class: 'unlock-dialog-clock-date',
+ x_align: Clutter.ActorAlign.CENTER,
+ });
+ this._hint = new St.Label({
+ style_class: 'unlock-dialog-clock-hint',
+ x_align: Clutter.ActorAlign.CENTER,
+ opacity: 0,
+ });
+
+ this.add_child(this._time);
+ this.add_child(this._date);
+ this.add_child(this._hint);
+
+ this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
+ this._wallClock.connect('notify::clock', this._updateClock.bind(this));
+
+ this._seat = Clutter.get_default_backend().get_default_seat();
+ this._seat.connectObject('notify::touch-mode',
+ this._updateHint.bind(this), this);
+
+ this._monitorManager = Meta.MonitorManager.get();
+ this._monitorManager.connectObject('power-save-mode-changed',
+ () => (this._hint.opacity = 0), this);
+
+ this._idleMonitor = global.backend.get_core_idle_monitor();
+ this._idleWatchId = this._idleMonitor.add_idle_watch(HINT_TIMEOUT * 1000, () => {
+ this._hint.ease({
+ opacity: 255,
+ duration: CROSSFADE_TIME,
+ });
+ });
+
+ this._updateClock();
+ this._updateHint();
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ _updateClock() {
+ this._time.text = this._wallClock.clock;
+
+ let date = new Date();
+ /* Translators: This is a time format for a date in
+ long format */
+ let dateFormat = Shell.util_translate_time_string(N_('%A %B %-d'));
+ this._date.text = date.toLocaleFormat(dateFormat);
+ }
+
+ _updateHint() {
+ this._hint.text = this._seat.touch_mode
+ ? _('Swipe up to unlock')
+ : _('Click or press a key to unlock');
+ }
+
+ _onDestroy() {
+ this._wallClock.run_dispose();
+
+ this._idleMonitor.remove_watch(this._idleWatchId);
+ }
+});
+
+var UnlockDialogLayout = GObject.registerClass(
+class UnlockDialogLayout extends Clutter.LayoutManager {
+ _init(stack, notifications, switchUserButton) {
+ super._init();
+
+ this._stack = stack;
+ this._notifications = notifications;
+ this._switchUserButton = switchUserButton;
+ }
+
+ vfunc_get_preferred_width(container, forHeight) {
+ return this._stack.get_preferred_width(forHeight);
+ }
+
+ vfunc_get_preferred_height(container, forWidth) {
+ return this._stack.get_preferred_height(forWidth);
+ }
+
+ vfunc_allocate(container, box) {
+ let [width, height] = box.get_size();
+
+ let tenthOfHeight = height / 10.0;
+ let thirdOfHeight = height / 3.0;
+
+ let [, , stackWidth, stackHeight] =
+ this._stack.get_preferred_size();
+
+ let [, , notificationsWidth, notificationsHeight] =
+ this._notifications.get_preferred_size();
+
+ let columnWidth = Math.max(stackWidth, notificationsWidth);
+
+ let columnX1 = Math.floor((width - columnWidth) / 2.0);
+ let actorBox = new Clutter.ActorBox();
+
+ // Notifications
+ let maxNotificationsHeight = Math.min(
+ notificationsHeight,
+ height - tenthOfHeight - stackHeight);
+
+ actorBox.x1 = columnX1;
+ actorBox.y1 = height - maxNotificationsHeight;
+ actorBox.x2 = columnX1 + columnWidth;
+ actorBox.y2 = actorBox.y1 + maxNotificationsHeight;
+
+ this._notifications.allocate(actorBox);
+
+ // Authentication Box
+ let stackY = Math.min(
+ thirdOfHeight,
+ height - stackHeight - maxNotificationsHeight);
+
+ actorBox.x1 = columnX1;
+ actorBox.y1 = stackY;
+ actorBox.x2 = columnX1 + columnWidth;
+ actorBox.y2 = stackY + stackHeight;
+
+ this._stack.allocate(actorBox);
+
+ // Switch User button
+ if (this._switchUserButton.visible) {
+ let [, , natWidth, natHeight] =
+ this._switchUserButton.get_preferred_size();
+
+ const textDirection = this._switchUserButton.get_text_direction();
+ if (textDirection === Clutter.TextDirection.RTL)
+ actorBox.x1 = box.x1 + natWidth;
+ else
+ actorBox.x1 = box.x2 - (natWidth * 2);
+
+ actorBox.y1 = box.y2 - (natHeight * 2);
+ actorBox.x2 = actorBox.x1 + natWidth;
+ actorBox.y2 = actorBox.y1 + natHeight;
+
+ this._switchUserButton.allocate(actorBox);
+ }
+ }
+});
+
+var UnlockDialog = GObject.registerClass({
+ Signals: {
+ 'failed': {},
+ 'wake-up-screen': {},
+ },
+}, class UnlockDialog extends St.Widget {
+ _init(parentActor) {
+ super._init({
+ accessible_role: Atk.Role.WINDOW,
+ style_class: 'unlock-dialog',
+ visible: false,
+ reactive: true,
+ });
+
+ parentActor.add_child(this);
+
+ this._gdmClient = new Gdm.Client();
+
+ try {
+ this._gdmClient.set_enabled_extensions([
+ Gdm.UserVerifierChoiceList.interface_info().name,
+ ]);
+ } catch (e) {
+ }
+
+ this._adjustment = new St.Adjustment({
+ actor: this,
+ lower: 0,
+ upper: 2,
+ page_size: 1,
+ page_increment: 1,
+ });
+ this._adjustment.connect('notify::value', () => {
+ this._setTransitionProgress(this._adjustment.value);
+ });
+
+ this._swipeTracker = new SwipeTracker.SwipeTracker(this,
+ Clutter.Orientation.VERTICAL,
+ Shell.ActionMode.UNLOCK_SCREEN);
+ this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
+ this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
+ this._swipeTracker.connect('end', this._swipeEnd.bind(this));
+
+ this.connect('scroll-event', (o, event) => {
+ if (this._swipeTracker.canHandleScrollEvent(event))
+ return Clutter.EVENT_PROPAGATE;
+
+ let direction = event.get_scroll_direction();
+ if (direction === Clutter.ScrollDirection.UP)
+ this._showClock();
+ else if (direction === Clutter.ScrollDirection.DOWN)
+ this._showPrompt();
+ return Clutter.EVENT_STOP;
+ });
+
+ this._activePage = null;
+
+ let tapAction = new Clutter.TapAction();
+ tapAction.connect('tap', this._showPrompt.bind(this));
+ this.add_action(tapAction);
+
+ // Background
+ this._backgroundGroup = new Clutter.Actor();
+ this.add_child(this._backgroundGroup);
+
+ this._bgManagers = [];
+
+ const themeContext = St.ThemeContext.get_for_stage(global.stage);
+ themeContext.connectObject('notify::scale-factor',
+ () => this._updateBackgroundEffects(), this);
+
+ this._updateBackgrounds();
+ Main.layoutManager.connectObject('monitors-changed',
+ this._updateBackgrounds.bind(this), this);
+
+ this._userManager = AccountsService.UserManager.get_default();
+ this._userName = GLib.get_user_name();
+ this._user = this._userManager.get_user(this._userName);
+
+ // Authentication & Clock stack
+ this._stack = new Shell.Stack();
+
+ this._promptBox = new St.BoxLayout({ vertical: true });
+ this._promptBox.set_pivot_point(0.5, 0.5);
+ this._promptBox.hide();
+ this._stack.add_child(this._promptBox);
+
+ this._clock = new Clock();
+ this._clock.set_pivot_point(0.5, 0.5);
+ this._stack.add_child(this._clock);
+ this._showClock();
+
+ this.allowCancel = false;
+
+ Main.ctrlAltTabManager.addGroup(this, _('Unlock Window'), 'dialog-password-symbolic');
+
+ // Notifications
+ this._notificationsBox = new NotificationsBox();
+ this._notificationsBox.connect('wake-up-screen', () => this.emit('wake-up-screen'));
+
+ // Switch User button
+ this._otherUserButton = new St.Button({
+ style_class: 'modal-dialog-button 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',
+ });
+ this._otherUserButton.set_pivot_point(0.5, 0.5);
+ this._otherUserButton.connect('clicked', this._otherUserClicked.bind(this));
+
+ this._screenSaverSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.screensaver' });
+
+ this._screenSaverSettings.connectObject('changed::user-switch-enabled',
+ this._updateUserSwitchVisibility.bind(this), this);
+
+ this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });
+ this._lockdownSettings.connect('changed::disable-user-switching',
+ this._updateUserSwitchVisibility.bind(this));
+
+ this._user.connectObject('notify::is-loaded',
+ this._updateUserSwitchVisibility.bind(this), this);
+
+ this._updateUserSwitchVisibility();
+
+ // Main Box
+ let mainBox = new St.Widget();
+ mainBox.add_constraint(new Layout.MonitorConstraint({ primary: true }));
+ mainBox.add_child(this._stack);
+ mainBox.add_child(this._notificationsBox);
+ mainBox.add_child(this._otherUserButton);
+ mainBox.layout_manager = new UnlockDialogLayout(
+ this._stack,
+ this._notificationsBox,
+ this._otherUserButton);
+ this.add_child(mainBox);
+
+ this._idleMonitor = global.backend.get_core_idle_monitor();
+ this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, this._escape.bind(this));
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ vfunc_key_press_event(keyEvent) {
+ if (this._activePage === this._promptBox ||
+ (this._promptBox && this._promptBox.visible))
+ return Clutter.EVENT_PROPAGATE;
+
+ const { keyval } = keyEvent;
+ if (keyval === Clutter.KEY_Shift_L ||
+ keyval === Clutter.KEY_Shift_R ||
+ keyval === Clutter.KEY_Shift_Lock ||
+ keyval === Clutter.KEY_Caps_Lock)
+ return Clutter.EVENT_PROPAGATE;
+
+ let unichar = keyEvent.unicode_value;
+
+ this._showPrompt();
+
+ if (GLib.unichar_isgraph(unichar))
+ this._authPrompt.addCharacter(unichar);
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ vfunc_captured_event(event) {
+ if (Main.keyboard.maybeHandleEvent(event))
+ return Clutter.EVENT_STOP;
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ _createBackground(monitorIndex) {
+ let monitor = Main.layoutManager.monitors[monitorIndex];
+ let widget = new St.Widget({
+ style_class: 'screen-shield-background',
+ x: monitor.x,
+ y: monitor.y,
+ width: monitor.width,
+ height: monitor.height,
+ effect: new Shell.BlurEffect({ name: 'blur' }),
+ });
+
+ let bgManager = new Background.BackgroundManager({
+ container: widget,
+ monitorIndex,
+ controlPosition: false,
+ });
+
+ this._bgManagers.push(bgManager);
+
+ this._backgroundGroup.add_child(widget);
+ }
+
+ _updateBackgroundEffects() {
+ const themeContext = St.ThemeContext.get_for_stage(global.stage);
+
+ for (const widget of this._backgroundGroup) {
+ const effect = widget.get_effect('blur');
+
+ if (effect) {
+ effect.set({
+ brightness: BLUR_BRIGHTNESS,
+ sigma: BLUR_SIGMA * themeContext.scale_factor,
+ });
+ }
+ }
+ }
+
+ _updateBackgrounds() {
+ for (let i = 0; i < this._bgManagers.length; i++)
+ this._bgManagers[i].destroy();
+
+ this._bgManagers = [];
+ this._backgroundGroup.destroy_all_children();
+
+ for (let i = 0; i < Main.layoutManager.monitors.length; i++)
+ this._createBackground(i);
+ this._updateBackgroundEffects();
+ }
+
+ _ensureAuthPrompt() {
+ if (!this._authPrompt) {
+ this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient,
+ AuthPrompt.AuthPromptMode.UNLOCK_ONLY);
+ this._authPrompt.connect('failed', this._fail.bind(this));
+ this._authPrompt.connect('cancelled', this._fail.bind(this));
+ this._authPrompt.connect('reset', this._onReset.bind(this));
+ this._promptBox.add_child(this._authPrompt);
+ }
+
+ this._authPrompt.reset();
+ this._authPrompt.updateSensitivity(true);
+ }
+
+ _maybeDestroyAuthPrompt() {
+ let focus = global.stage.key_focus;
+ if (focus === null ||
+ (this._authPrompt && this._authPrompt.contains(focus)) ||
+ (this._otherUserButton && focus === this._otherUserButton))
+ this.grab_key_focus();
+
+ if (this._authPrompt) {
+ this._authPrompt.destroy();
+ this._authPrompt = null;
+ }
+ }
+
+ _showClock() {
+ if (this._activePage === this._clock)
+ return;
+
+ this._activePage = this._clock;
+
+ this._adjustment.ease(0, {
+ duration: CROSSFADE_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => this._maybeDestroyAuthPrompt(),
+ });
+ }
+
+ _showPrompt() {
+ this._ensureAuthPrompt();
+
+ if (this._activePage === this._promptBox)
+ return;
+
+ this._activePage = this._promptBox;
+
+ this._adjustment.ease(1, {
+ duration: CROSSFADE_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ }
+
+ _setTransitionProgress(progress) {
+ this._promptBox.visible = progress > 0;
+ this._clock.visible = progress < 1;
+
+ this._otherUserButton.set({
+ reactive: progress > 0,
+ can_focus: progress > 0,
+ });
+
+ const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
+
+ this._promptBox.set({
+ opacity: 255 * progress,
+ scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
+ scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
+ translation_y: FADE_OUT_TRANSLATION * (1 - progress) * scaleFactor,
+ });
+
+ this._clock.set({
+ opacity: 255 * (1 - progress),
+ scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * (1 - progress),
+ scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * (1 - progress),
+ translation_y: -FADE_OUT_TRANSLATION * progress * scaleFactor,
+ });
+
+ this._otherUserButton.set({
+ opacity: 255 * progress,
+ scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
+ scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
+ });
+ }
+
+ _fail() {
+ this._showClock();
+ this.emit('failed');
+ }
+
+ _onReset(authPrompt, beginRequest) {
+ let userName;
+ if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) {
+ this._authPrompt.setUser(this._user);
+ userName = this._userName;
+ } else {
+ userName = null;
+ }
+
+ this._authPrompt.begin({ userName });
+ }
+
+ _escape() {
+ if (this._authPrompt && this.allowCancel)
+ this._authPrompt.cancel();
+ }
+
+ _swipeBegin(tracker, monitor) {
+ if (monitor !== Main.layoutManager.primaryIndex)
+ return;
+
+ this._adjustment.remove_transition('value');
+
+ this._ensureAuthPrompt();
+
+ let progress = this._adjustment.value;
+ tracker.confirmSwipe(this._stack.height,
+ [0, 1],
+ progress,
+ Math.round(progress));
+ }
+
+ _swipeUpdate(tracker, progress) {
+ this._adjustment.value = progress;
+ }
+
+ _swipeEnd(tracker, duration, endProgress) {
+ this._activePage = endProgress
+ ? this._promptBox
+ : this._clock;
+
+ this._adjustment.ease(endProgress, {
+ mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
+ duration,
+ onComplete: () => {
+ if (this._activePage === this._clock)
+ this._maybeDestroyAuthPrompt();
+ },
+ });
+ }
+
+ _otherUserClicked() {
+ Gdm.goto_login_session_sync(null);
+
+ this._authPrompt.cancel();
+ }
+
+ _onDestroy() {
+ this.popModal();
+
+ if (this._idleWatchId) {
+ this._idleMonitor.remove_watch(this._idleWatchId);
+ this._idleWatchId = 0;
+ }
+
+ if (this._gdmClient) {
+ this._gdmClient = null;
+ delete this._gdmClient;
+ }
+ }
+
+ _updateUserSwitchVisibility() {
+ this._otherUserButton.visible = this._userManager.can_switch() &&
+ this._screenSaverSettings.get_boolean('user-switch-enabled') &&
+ !this._lockdownSettings.get_boolean('disable-user-switching');
+ }
+
+ cancel() {
+ if (this._authPrompt)
+ this._authPrompt.cancel();
+ }
+
+ finish(onComplete) {
+ if (!this._authPrompt) {
+ onComplete();
+ return;
+ }
+
+ this._authPrompt.finish(onComplete);
+ }
+
+ open(timestamp) {
+ this.show();
+
+ if (this._isModal)
+ return true;
+
+ let modalParams = {
+ timestamp,
+ actionMode: Shell.ActionMode.UNLOCK_SCREEN,
+ };
+ let grab = Main.pushModal(Main.uiGroup, modalParams);
+ if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
+ Main.popModal(grab);
+ return false;
+ }
+
+ this._grab = grab;
+ this._isModal = true;
+
+ return true;
+ }
+
+ activate() {
+ this._showPrompt();
+ }
+
+ popModal(timestamp) {
+ if (this._isModal) {
+ Main.popModal(this._grab, timestamp);
+ this._grab = null;
+ this._isModal = false;
+ }
+ }
+});