You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1211 lines
41 KiB
1211 lines
41 KiB
From f821b65401284cc31f68f0eb1b2e71ae3a90a122 Mon Sep 17 00:00:00 2001
|
|
From: Ray Strode <rstrode@redhat.com>
|
|
Date: Tue, 18 Jul 2017 12:58:14 -0400
|
|
Subject: [PATCH 1/2] gdm: Add AuthList control
|
|
|
|
Ultimately, we want to add support for GDM's new ChoiceList
|
|
PAM extension. That extension allows PAM modules to present
|
|
a list of choices to the user. Before we can support that
|
|
extension, however, we need to have a list control in the
|
|
login-screen/unlock screen. This commit adds that control.
|
|
|
|
For the most part, it's a copy-and-paste of the gdm userlist,
|
|
but with less features. It lacks API specific to the users,
|
|
lacks the built in timed login indicator, etc. It does feature
|
|
a label heading.
|
|
---
|
|
.../widgets/_login-dialog.scss | 26 +++
|
|
js/gdm/authList.js | 176 ++++++++++++++++++
|
|
js/js-resources.gresource.xml | 1 +
|
|
3 files changed, 203 insertions(+)
|
|
create mode 100644 js/gdm/authList.js
|
|
|
|
diff --git a/data/theme/gnome-shell-sass/widgets/_login-dialog.scss b/data/theme/gnome-shell-sass/widgets/_login-dialog.scss
|
|
index 84539342d..f68d5de99 100644
|
|
--- a/data/theme/gnome-shell-sass/widgets/_login-dialog.scss
|
|
+++ b/data/theme/gnome-shell-sass/widgets/_login-dialog.scss
|
|
@@ -86,60 +86,86 @@
|
|
.caps-lock-warning-label,
|
|
.login-dialog-message-warning {
|
|
color: $osd_fg_color;
|
|
}
|
|
}
|
|
|
|
.login-dialog-logo-bin { padding: 24px 0px; }
|
|
.login-dialog-banner { color: darken($osd_fg_color,10%); }
|
|
.login-dialog-button-box { width: 23em; spacing: 5px; }
|
|
.login-dialog-message { text-align: center; }
|
|
.login-dialog-message-hint, .login-dialog-message {
|
|
color: darken($osd_fg_color, 20%);
|
|
min-height: 2.75em;
|
|
}
|
|
.login-dialog-user-selection-box { padding: 100px 0px; }
|
|
.login-dialog-not-listed-label {
|
|
padding-left: 2px;
|
|
.login-dialog-not-listed-button:focus &,
|
|
.login-dialog-not-listed-button:hover & {
|
|
color: $osd_fg_color;
|
|
}
|
|
}
|
|
|
|
.login-dialog-not-listed-label {
|
|
@include fontsize($base_font_size - 1);
|
|
font-weight: bold;
|
|
color: darken($osd_fg_color,30%);
|
|
padding-top: 1em;
|
|
}
|
|
|
|
+.login-dialog-auth-list-view { -st-vfade-offset: 1em; }
|
|
+.login-dialog-auth-list {
|
|
+ spacing: 6px;
|
|
+ margin-left: 2em;
|
|
+}
|
|
+
|
|
+.login-dialog-auth-list-title {
|
|
+ margin-left: 2em;
|
|
+}
|
|
+
|
|
+.login-dialog-auth-list-item {
|
|
+ border-radius: $base_border_radius + 4px;
|
|
+ padding: 6px;
|
|
+ color: darken($osd_fg_color,30%);
|
|
+ &:focus, &:selected { background-color: $selected_bg_color; color: $selected_fg_color; }
|
|
+}
|
|
+
|
|
+.login-dialog-auth-list-label {
|
|
+ @include fontsize($base_font_size + 2);
|
|
+ font-weight: bold;
|
|
+ padding-left: 15px;
|
|
+
|
|
+ &:ltr { padding-left: 14px; text-align: left; }
|
|
+ &:rtl { padding-right: 14px; text-align: right; }
|
|
+}
|
|
+
|
|
.login-dialog-user-list-view { -st-vfade-offset: 1em; }
|
|
.login-dialog-user-list {
|
|
spacing: 12px;
|
|
width: 23em;
|
|
&:expanded .login-dialog-user-list-item:selected { background-color: $selected_bg_color; color: $selected_fg_color; }
|
|
&:expanded .login-dialog-user-list-item:logged-in { border-right: 2px solid $selected_bg_color; }
|
|
}
|
|
|
|
.login-dialog-user-list-item {
|
|
border-radius: $base_border_radius + 4px;
|
|
padding: 6px;
|
|
color: darken($osd_fg_color,30%);
|
|
&:ltr .user-widget { padding-right: 1em; }
|
|
&:rtl .user-widget { padding-left: 1em; }
|
|
.login-dialog-timed-login-indicator {
|
|
height: 2px;
|
|
margin-top: 6px;
|
|
background-color: $osd_fg_color;
|
|
}
|
|
&:focus .login-dialog-timed-login-indicator { background-color: $selected_fg_color; }
|
|
}
|
|
|
|
.user-widget-label {
|
|
color: $osd_fg_color;
|
|
}
|
|
|
|
.user-widget.horizontal .user-widget-label {
|
|
@include fontsize($base_font_size + 2);
|
|
font-weight: bold;
|
|
padding-left: 15px;
|
|
diff --git a/js/gdm/authList.js b/js/gdm/authList.js
|
|
new file mode 100644
|
|
index 000000000..fb223a972
|
|
--- /dev/null
|
|
+++ b/js/gdm/authList.js
|
|
@@ -0,0 +1,176 @@
|
|
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
+/*
|
|
+ * Copyright 2017 Red Hat, Inc
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2, or (at your option)
|
|
+ * any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+/* exported AuthList */
|
|
+
|
|
+const { Clutter, GObject, Meta, St } = imports.gi;
|
|
+
|
|
+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: false,
|
|
+ });
|
|
+
|
|
+ super._init({
|
|
+ style_class: 'login-dialog-auth-list-item',
|
|
+ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
|
+ can_focus: true,
|
|
+ child: label,
|
|
+ reactive: true,
|
|
+ });
|
|
+
|
|
+ this.connect('key-focus-in',
|
|
+ () => this._setSelected(true));
|
|
+ this.connect('key-focus-out',
|
|
+ () => this._setSelected(false));
|
|
+ this.connect('notify::hover',
|
|
+ () => this._setSelected(this.hover));
|
|
+
|
|
+ this.connect('clicked', this._onClicked.bind(this));
|
|
+ }
|
|
+
|
|
+ _onClicked() {
|
|
+ this.emit('activate');
|
|
+ }
|
|
+
|
|
+ _setSelected(selected) {
|
|
+ if (selected) {
|
|
+ this.add_style_pseudo_class('selected');
|
|
+ this.grab_key_focus();
|
|
+ } else {
|
|
+ this.remove_style_pseudo_class('selected');
|
|
+ }
|
|
+ }
|
|
+});
|
|
+
|
|
+var AuthList = GObject.registerClass({
|
|
+ Signals: {
|
|
+ 'activate': { param_types: [GObject.TYPE_STRING] },
|
|
+ 'item-added': { param_types: [AuthListItem.$gtype] },
|
|
+ },
|
|
+}, class AuthList extends St.BoxLayout {
|
|
+ _init() {
|
|
+ super._init({
|
|
+ vertical: true,
|
|
+ style_class: 'login-dialog-auth-list-layout',
|
|
+ x_align: Clutter.ActorAlign.START,
|
|
+ y_align: Clutter.ActorAlign.CENTER,
|
|
+ });
|
|
+
|
|
+ this.label = new St.Label({ style_class: 'login-dialog-auth-list-title' });
|
|
+ this.add_child(this.label);
|
|
+
|
|
+ this._scrollView = new St.ScrollView({
|
|
+ style_class: 'login-dialog-auth-list-view',
|
|
+ });
|
|
+ this._scrollView.set_policy(
|
|
+ St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
|
+ this.add_child(this._scrollView);
|
|
+
|
|
+ this._box = new St.BoxLayout({
|
|
+ vertical: true,
|
|
+ style_class: 'login-dialog-auth-list',
|
|
+ pseudo_class: 'expanded',
|
|
+ });
|
|
+
|
|
+ this._scrollView.add_actor(this._box);
|
|
+ this._items = new Map();
|
|
+
|
|
+ this.connect('key-focus-in', this._moveFocusToItems.bind(this));
|
|
+ }
|
|
+
|
|
+ _moveFocusToItems() {
|
|
+ let hasItems = this.numItems > 0;
|
|
+
|
|
+ if (!hasItems)
|
|
+ return;
|
|
+
|
|
+ if (global.stage.get_key_focus() !== this)
|
|
+ return;
|
|
+
|
|
+ let focusSet = this.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
|
+ if (!focusSet) {
|
|
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
|
+ this._moveFocusToItems();
|
|
+ return false;
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+
|
|
+ _onItemActivated(activatedItem) {
|
|
+ this.emit('activate', activatedItem.key);
|
|
+ }
|
|
+
|
|
+ scrollToItem(item) {
|
|
+ let box = item.get_allocation_box();
|
|
+
|
|
+ let adjustment = this._scrollView.get_vscroll_bar().get_adjustment();
|
|
+
|
|
+ let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
|
|
+ adjustment.ease(value, {
|
|
+ duration: SCROLL_ANIMATION_TIME,
|
|
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
+ });
|
|
+ }
|
|
+
|
|
+ addItem(key, text) {
|
|
+ this.removeItem(key);
|
|
+
|
|
+ let item = new AuthListItem(key, text);
|
|
+ this._box.add(item);
|
|
+
|
|
+ this._items.set(key, item);
|
|
+
|
|
+ item.connect('activate', this._onItemActivated.bind(this));
|
|
+
|
|
+ // Try to keep the focused item front-and-center
|
|
+ item.connect('key-focus-in', () => this.scrollToItem(item));
|
|
+
|
|
+ this._moveFocusToItems();
|
|
+
|
|
+ this.emit('item-added', item);
|
|
+ }
|
|
+
|
|
+ removeItem(key) {
|
|
+ if (!this._items.has(key))
|
|
+ return;
|
|
+
|
|
+ let item = this._items.get(key);
|
|
+
|
|
+ item.destroy();
|
|
+
|
|
+ this._items.delete(key);
|
|
+ }
|
|
+
|
|
+ get numItems() {
|
|
+ return this._items.size;
|
|
+ }
|
|
+
|
|
+ clear() {
|
|
+ this.label.text = '';
|
|
+ this._box.destroy_all_children();
|
|
+ this._items.clear();
|
|
+ }
|
|
+});
|
|
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
|
|
index e65e0e9cf..b2c603a55 100644
|
|
--- a/js/js-resources.gresource.xml
|
|
+++ b/js/js-resources.gresource.xml
|
|
@@ -1,33 +1,34 @@
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<gresources>
|
|
<gresource prefix="/org/gnome/shell">
|
|
+ <file>gdm/authList.js</file>
|
|
<file>gdm/authPrompt.js</file>
|
|
<file>gdm/batch.js</file>
|
|
<file>gdm/loginDialog.js</file>
|
|
<file>gdm/oVirt.js</file>
|
|
<file>gdm/credentialManager.js</file>
|
|
<file>gdm/vmware.js</file>
|
|
<file>gdm/realmd.js</file>
|
|
<file>gdm/util.js</file>
|
|
|
|
<file>misc/config.js</file>
|
|
<file>misc/extensionUtils.js</file>
|
|
<file>misc/fileUtils.js</file>
|
|
<file>misc/gnomeSession.js</file>
|
|
<file>misc/history.js</file>
|
|
<file>misc/ibusManager.js</file>
|
|
<file>misc/inputMethod.js</file>
|
|
<file>misc/introspect.js</file>
|
|
<file>misc/jsParse.js</file>
|
|
<file>misc/keyboardManager.js</file>
|
|
<file>misc/loginManager.js</file>
|
|
<file>misc/modemManager.js</file>
|
|
<file>misc/objectManager.js</file>
|
|
<file>misc/params.js</file>
|
|
<file>misc/parentalControlsManager.js</file>
|
|
<file>misc/permissionStore.js</file>
|
|
<file>misc/smartcardManager.js</file>
|
|
<file>misc/systemActions.js</file>
|
|
<file>misc/util.js</file>
|
|
<file>misc/weather.js</file>
|
|
|
|
--
|
|
2.34.1
|
|
|
|
From 5a2fda2fe2526f81c4dbbee6512182f19fc76a74 Mon Sep 17 00:00:00 2001
|
|
From: Ray Strode <rstrode@redhat.com>
|
|
Date: Mon, 17 Jul 2017 16:48:03 -0400
|
|
Subject: [PATCH 2/2] gdmUtil: Enable support for GDM's ChoiceList PAM
|
|
extension
|
|
|
|
This commit hooks up support for GDM's ChoiceList PAM extension.
|
|
---
|
|
js/gdm/authPrompt.js | 71 +++++++++++++++++++++++++++++++++++++++++--
|
|
js/gdm/loginDialog.js | 5 +++
|
|
js/gdm/util.js | 28 +++++++++++++++++
|
|
js/ui/unlockDialog.js | 7 +++++
|
|
4 files changed, 109 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
|
|
index 84c608b2f..4da91e096 100644
|
|
--- a/js/gdm/authPrompt.js
|
|
+++ b/js/gdm/authPrompt.js
|
|
@@ -1,36 +1,37 @@
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported AuthPrompt */
|
|
|
|
const { Clutter, GLib, GObject, Meta, Pango, Shell, St } = imports.gi;
|
|
|
|
const Animation = imports.ui.animation;
|
|
+const AuthList = imports.gdm.authList;
|
|
const Batch = imports.gdm.batch;
|
|
const GdmUtil = imports.gdm.util;
|
|
const OVirt = imports.gdm.oVirt;
|
|
const Vmware = imports.gdm.vmware;
|
|
const Params = imports.misc.params;
|
|
const ShellEntry = imports.ui.shellEntry;
|
|
const UserWidget = imports.ui.userWidget;
|
|
const Util = imports.misc.util;
|
|
|
|
var DEFAULT_BUTTON_WELL_ICON_SIZE = 16;
|
|
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000;
|
|
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300;
|
|
|
|
var MESSAGE_FADE_OUT_ANIMATION_TIME = 500;
|
|
|
|
var AuthPromptMode = {
|
|
UNLOCK_ONLY: 0,
|
|
UNLOCK_OR_LOG_IN: 1,
|
|
};
|
|
|
|
var AuthPromptStatus = {
|
|
NOT_VERIFYING: 0,
|
|
VERIFYING: 1,
|
|
VERIFICATION_FAILED: 2,
|
|
VERIFICATION_SUCCEEDED: 3,
|
|
VERIFICATION_CANCELLED: 4,
|
|
VERIFICATION_IN_PROGRESS: 5,
|
|
};
|
|
|
|
var BeginRequestType = {
|
|
@@ -48,144 +49,164 @@ var AuthPrompt = GObject.registerClass({
|
|
'reset': { param_types: [GObject.TYPE_UINT] },
|
|
},
|
|
}, class AuthPrompt extends St.BoxLayout {
|
|
_init(gdmClient, mode) {
|
|
super._init({
|
|
style_class: 'login-dialog-prompt-layout',
|
|
vertical: true,
|
|
x_expand: true,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
});
|
|
|
|
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
|
|
|
|
this._gdmClient = gdmClient;
|
|
this._mode = mode;
|
|
this._defaultButtonWellActor = null;
|
|
this._cancelledRetries = 0;
|
|
|
|
this._idleMonitor = Meta.IdleMonitor.get_core();
|
|
|
|
let reauthenticationOnly;
|
|
if (this._mode == AuthPromptMode.UNLOCK_ONLY)
|
|
reauthenticationOnly = true;
|
|
else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
|
|
reauthenticationOnly = false;
|
|
|
|
this._userVerifier = new GdmUtil.ShellUserVerifier(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.smartcardDetected = this._userVerifier.smartcardDetected;
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
this._userWell = new St.Bin({
|
|
x_expand: true,
|
|
y_expand: true,
|
|
});
|
|
this.add_child(this._userWell);
|
|
|
|
this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN;
|
|
|
|
- this._initEntryRow();
|
|
+ this._initInputRow();
|
|
|
|
let capsLockPlaceholder = new St.Label();
|
|
this.add_child(capsLockPlaceholder);
|
|
|
|
this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({
|
|
x_expand: true,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
});
|
|
this.add_child(this._capsLockWarningLabel);
|
|
|
|
this._capsLockWarningLabel.bind_property('visible',
|
|
capsLockPlaceholder, 'visible',
|
|
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
|
|
|
|
this._message = new St.Label({
|
|
opacity: 0,
|
|
styleClass: 'login-dialog-message',
|
|
y_expand: true,
|
|
x_expand: true,
|
|
y_align: Clutter.ActorAlign.START,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
});
|
|
this._message.clutter_text.line_wrap = true;
|
|
this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
this.add_child(this._message);
|
|
}
|
|
|
|
_onDestroy() {
|
|
if (this._preemptiveAnswerWatchId) {
|
|
this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
|
|
this._preemptiveAnswerWatchId = 0;
|
|
}
|
|
|
|
this._userVerifier.destroy();
|
|
this._userVerifier = null;
|
|
}
|
|
|
|
vfunc_key_press_event(keyPressEvent) {
|
|
if (keyPressEvent.keyval == Clutter.KEY_Escape)
|
|
this.cancel();
|
|
return super.vfunc_key_press_event(keyPressEvent);
|
|
}
|
|
|
|
- _initEntryRow() {
|
|
+ _initInputRow() {
|
|
this._mainBox = new St.BoxLayout({
|
|
style_class: 'login-dialog-button-box',
|
|
vertical: false,
|
|
});
|
|
this.add_child(this._mainBox);
|
|
|
|
this.cancelButton = new St.Button({
|
|
style_class: 'modal-dialog-button button cancel-button',
|
|
accessible_name: _('Cancel'),
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
|
reactive: this._hasCancelButton,
|
|
can_focus: this._hasCancelButton,
|
|
x_align: Clutter.ActorAlign.START,
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
child: new St.Icon({ icon_name: 'go-previous-symbolic' }),
|
|
});
|
|
if (this._hasCancelButton)
|
|
this.cancelButton.connect('clicked', () => this.cancel());
|
|
else
|
|
this.cancelButton.opacity = 0;
|
|
this._mainBox.add_child(this.cancelButton);
|
|
|
|
+ this._authList = new AuthList.AuthList();
|
|
+ this._authList.set({
|
|
+ visible: false,
|
|
+ });
|
|
+ this._authList.connect('activate', (list, key) => {
|
|
+ this._authList.reactive = false;
|
|
+ this._authList.ease({
|
|
+ opacity: 0,
|
|
+ duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
|
|
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
+ onComplete: () => {
|
|
+ this._authList.clear();
|
|
+ this._authList.hide();
|
|
+ this._userVerifier.selectChoice(this._queryingService, key);
|
|
+ },
|
|
+ });
|
|
+ });
|
|
+ this._mainBox.add_child(this._authList);
|
|
+
|
|
let entryParams = {
|
|
style_class: 'login-dialog-prompt-entry',
|
|
can_focus: true,
|
|
x_expand: true,
|
|
};
|
|
|
|
this._entry = null;
|
|
|
|
this._textEntry = new St.Entry(entryParams);
|
|
ShellEntry.addContextMenu(this._textEntry, { actionMode: Shell.ActionMode.NONE });
|
|
|
|
this._passwordEntry = new St.PasswordEntry(entryParams);
|
|
ShellEntry.addContextMenu(this._passwordEntry, { actionMode: Shell.ActionMode.NONE });
|
|
|
|
this._entry = this._passwordEntry;
|
|
this._mainBox.add_child(this._entry);
|
|
this._entry.grab_key_focus();
|
|
|
|
this._timedLoginIndicator = new St.Bin({
|
|
style_class: 'login-dialog-timed-login-indicator',
|
|
scale_x: 0,
|
|
});
|
|
|
|
this.add_child(this._timedLoginIndicator);
|
|
|
|
[this._textEntry, this._passwordEntry].forEach(entry => {
|
|
entry.clutter_text.connect('text-changed', () => {
|
|
if (!this._userVerifier.hasPendingMessages && this._queryingService && !this._preemptiveAnswer)
|
|
this._fadeOutMessage();
|
|
});
|
|
@@ -276,60 +297,74 @@ var AuthPrompt = GObject.registerClass({
|
|
this._entry = this._textEntry;
|
|
}
|
|
this._capsLockWarningLabel.visible = secret;
|
|
}
|
|
|
|
_onAskQuestion(verifier, serviceName, question, secret) {
|
|
if (this._queryingService)
|
|
this.clear();
|
|
|
|
this._queryingService = serviceName;
|
|
if (this._preemptiveAnswer) {
|
|
this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
|
|
this._preemptiveAnswer = null;
|
|
return;
|
|
}
|
|
|
|
this._updateEntry(secret);
|
|
|
|
// Hack: The question string comes directly from PAM, if it's "Password:"
|
|
// we replace it with our own to allow localization, if it's something
|
|
// else we remove the last colon and any trailing or leading spaces.
|
|
if (question === 'Password:' || question === 'Password: ')
|
|
this.setQuestion(_('Password'));
|
|
else
|
|
this.setQuestion(question.replace(/: *$/, '').trim());
|
|
|
|
this.updateSensitivity(true);
|
|
this.emit('prompted');
|
|
}
|
|
|
|
+ _onShowChoiceList(userVerifier, serviceName, promptMessage, choiceList) {
|
|
+ if (this._queryingService)
|
|
+ this.clear();
|
|
+
|
|
+ this._queryingService = serviceName;
|
|
+
|
|
+ if (this._preemptiveAnswer)
|
|
+ this._preemptiveAnswer = null;
|
|
+
|
|
+ this.setChoiceList(promptMessage, choiceList);
|
|
+ this.updateSensitivity(true);
|
|
+ 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(GdmUtil.SMARTCARD_SERVICE_NAME) &&
|
|
this.verificationStatus == AuthPromptStatus.VERIFYING &&
|
|
this.smartcardDetected)
|
|
return;
|
|
|
|
if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
|
|
this.reset();
|
|
}
|
|
|
|
_onShowMessage(_userVerifier, serviceName, message, type) {
|
|
this.setMessage(serviceName, message, type);
|
|
this.emit('prompted');
|
|
}
|
|
|
|
_onVerificationFailed(userVerifier, serviceName, canRetry) {
|
|
@@ -411,109 +446,141 @@ var AuthPrompt = GObject.registerClass({
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
|
|
this._defaultButtonWellActor = actor;
|
|
}
|
|
|
|
startSpinning() {
|
|
this.setActorInDefaultButtonWell(this._spinner, true);
|
|
}
|
|
|
|
stopSpinning() {
|
|
this.setActorInDefaultButtonWell(null, false);
|
|
}
|
|
|
|
clear() {
|
|
this._entry.text = '';
|
|
this.stopSpinning();
|
|
+ this._authList.clear();
|
|
+ this._authList.hide();
|
|
}
|
|
|
|
setQuestion(question) {
|
|
if (this._preemptiveAnswerWatchId) {
|
|
this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
|
|
this._preemptiveAnswerWatchId = 0;
|
|
}
|
|
|
|
this._entry.hint_text = question;
|
|
|
|
+ this._authList.hide();
|
|
this._entry.show();
|
|
this._entry.grab_key_focus();
|
|
}
|
|
|
|
+ _fadeInChoiceList() {
|
|
+ this._authList.set({
|
|
+ opacity: 0,
|
|
+ visible: true,
|
|
+ reactive: false,
|
|
+ });
|
|
+ this._authList.ease({
|
|
+ opacity: 255,
|
|
+ duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
|
|
+ transition: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
+ onComplete: () => (this._authList.reactive = true),
|
|
+ });
|
|
+ }
|
|
+
|
|
+ setChoiceList(promptMessage, choiceList) {
|
|
+ this._authList.clear();
|
|
+ this._authList.label.text = promptMessage;
|
|
+ for (let key in choiceList) {
|
|
+ let text = choiceList[key];
|
|
+ this._authList.addItem(key, text);
|
|
+ }
|
|
+
|
|
+ this._entry.hide();
|
|
+ if (this._message.text === '')
|
|
+ this._message.hide();
|
|
+ this._fadeInChoiceList();
|
|
+ }
|
|
+
|
|
getAnswer() {
|
|
let text;
|
|
|
|
if (this._preemptiveAnswer) {
|
|
text = this._preemptiveAnswer;
|
|
this._preemptiveAnswer = null;
|
|
} else {
|
|
text = this._entry.get_text();
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
_fadeOutMessage() {
|
|
if (this._message.opacity == 0)
|
|
return;
|
|
this._message.remove_all_transitions();
|
|
this._message.ease({
|
|
opacity: 0,
|
|
duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
});
|
|
}
|
|
|
|
setMessage(serviceName, message, type) {
|
|
if (type == GdmUtil.MessageType.ERROR)
|
|
this._message.add_style_class_name('login-dialog-message-warning');
|
|
else
|
|
this._message.remove_style_class_name('login-dialog-message-warning');
|
|
|
|
if (type == GdmUtil.MessageType.HINT)
|
|
this._message.add_style_class_name('login-dialog-message-hint');
|
|
else
|
|
this._message.remove_style_class_name('login-dialog-message-hint');
|
|
|
|
+ this._message.show();
|
|
if (message) {
|
|
this._message.remove_all_transitions();
|
|
this._message.text = message;
|
|
this._message.opacity = 255;
|
|
} else {
|
|
this._message.opacity = 0;
|
|
}
|
|
|
|
if (type === GdmUtil.MessageType.ERROR &&
|
|
this._userVerifier.serviceIsFingerprint(serviceName)) {
|
|
// TODO: Use Await for wiggle to be over before unfreezing the user verifier queue
|
|
const wiggleParameters = {
|
|
duration: 65,
|
|
wiggleCount: 3,
|
|
};
|
|
this._userVerifier.increaseCurrentMessageTimeout(
|
|
wiggleParameters.duration * (wiggleParameters.wiggleCount + 2));
|
|
Util.wiggle(this._message, wiggleParameters);
|
|
}
|
|
}
|
|
|
|
updateSensitivity(sensitive) {
|
|
if (this._entry.reactive === sensitive)
|
|
return;
|
|
|
|
this._entry.reactive = sensitive;
|
|
|
|
if (sensitive) {
|
|
this._entry.grab_key_focus();
|
|
} else {
|
|
diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js
|
|
index d2a82b43d..41dd99646 100644
|
|
--- a/js/gdm/loginDialog.js
|
|
+++ b/js/gdm/loginDialog.js
|
|
@@ -391,60 +391,65 @@ var SessionMenuButton = GObject.registerClass({
|
|
let item = new PopupMenu.PopupMenuItem(sessionName);
|
|
this._menu.addMenuItem(item);
|
|
this._items[id] = item;
|
|
|
|
item.connect('activate', () => {
|
|
this.setActiveSession(id);
|
|
this.emit('session-activated', this._activeSessionId);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
var LoginDialog = GObject.registerClass({
|
|
Signals: {
|
|
'failed': {},
|
|
'wake-up-screen': {},
|
|
},
|
|
}, class LoginDialog extends St.Widget {
|
|
_init(parentActor) {
|
|
super._init({ style_class: 'login-dialog', visible: false });
|
|
|
|
this.get_accessible().set_role(Atk.Role.WINDOW);
|
|
|
|
this.add_constraint(new Layout.MonitorConstraint({ primary: true }));
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
parentActor.add_child(this);
|
|
|
|
this._userManager = AccountsService.UserManager.get_default();
|
|
this._gdmClient = new Gdm.Client();
|
|
|
|
+ try {
|
|
+ this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]);
|
|
+ } catch (e) {
|
|
+ }
|
|
+
|
|
this._settings = new Gio.Settings({ schema_id: GdmUtil.LOGIN_SCREEN_SCHEMA });
|
|
|
|
this._settings.connect('changed::%s'.format(GdmUtil.BANNER_MESSAGE_KEY),
|
|
this._updateBanner.bind(this));
|
|
this._settings.connect('changed::%s'.format(GdmUtil.BANNER_MESSAGE_TEXT_KEY),
|
|
this._updateBanner.bind(this));
|
|
this._settings.connect('changed::%s'.format(GdmUtil.DISABLE_USER_LIST_KEY),
|
|
this._updateDisableUserList.bind(this));
|
|
this._settings.connect('changed::%s'.format(GdmUtil.LOGO_KEY),
|
|
this._updateLogo.bind(this));
|
|
|
|
this._textureCache = St.TextureCache.get_default();
|
|
this._updateLogoTextureId = this._textureCache.connect('texture-file-changed',
|
|
this._updateLogoTexture.bind(this));
|
|
|
|
this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box',
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
vertical: true,
|
|
visible: false });
|
|
this.add_child(this._userSelectionBox);
|
|
|
|
this._userList = new UserList();
|
|
this._userSelectionBox.add_child(this._userList);
|
|
|
|
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.hide();
|
|
this.add_child(this._authPrompt);
|
|
diff --git a/js/gdm/util.js b/js/gdm/util.js
|
|
index e62114cb1..3f327400f 100644
|
|
--- a/js/gdm/util.js
|
|
+++ b/js/gdm/util.js
|
|
@@ -211,90 +211,98 @@ var ShellUserVerifier = class {
|
|
this._cancellable = new Gio.Cancellable();
|
|
this._hold = hold;
|
|
this._userName = userName;
|
|
this.reauthenticating = false;
|
|
|
|
this._checkForFingerprintReader();
|
|
|
|
// If possible, reauthenticate an already running session,
|
|
// so any session specific credentials get updated appropriately
|
|
if (userName)
|
|
this._openReauthenticationChannel(userName);
|
|
else
|
|
this._getUserVerifier();
|
|
}
|
|
|
|
cancel() {
|
|
if (this._cancellable)
|
|
this._cancellable.cancel();
|
|
|
|
if (this._userVerifier) {
|
|
this._userVerifier.call_cancel_sync(null);
|
|
this.clear();
|
|
}
|
|
}
|
|
|
|
_clearUserVerifier() {
|
|
if (this._userVerifier) {
|
|
this._disconnectSignals();
|
|
this._userVerifier.run_dispose();
|
|
this._userVerifier = null;
|
|
+ if (this._userVerifierChoiceList) {
|
|
+ this._userVerifierChoiceList.run_dispose();
|
|
+ this._userVerifierChoiceList = null;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
clear() {
|
|
if (this._cancellable) {
|
|
this._cancellable.cancel();
|
|
this._cancellable = null;
|
|
}
|
|
|
|
this._clearUserVerifier();
|
|
this._clearMessageQueue();
|
|
}
|
|
|
|
destroy() {
|
|
this.cancel();
|
|
|
|
this._settings.run_dispose();
|
|
this._settings = null;
|
|
|
|
this._smartcardManager.disconnect(this._smartcardInsertedId);
|
|
this._smartcardManager.disconnect(this._smartcardRemovedId);
|
|
this._smartcardManager = null;
|
|
|
|
for (let service in this._credentialManagers) {
|
|
let credentialManager = this._credentialManagers[service];
|
|
credentialManager.disconnect(credentialManager._authenticatedSignalId);
|
|
credentialManager = null;
|
|
}
|
|
}
|
|
|
|
+ selectChoice(serviceName, key) {
|
|
+ this._userVerifierChoiceList.call_select_choice(serviceName, key, this._cancellable, null);
|
|
+ }
|
|
+
|
|
answerQuery(serviceName, answer) {
|
|
if (!this.hasPendingMessages) {
|
|
this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
|
|
} else {
|
|
const cancellable = this._cancellable;
|
|
let signalId = this.connect('no-more-messages', () => {
|
|
this.disconnect(signalId);
|
|
if (!cancellable.is_cancelled())
|
|
this._userVerifier.call_answer_query(serviceName, answer, cancellable, null);
|
|
});
|
|
}
|
|
}
|
|
|
|
_getIntervalForMessage(message) {
|
|
if (!message)
|
|
return 0;
|
|
|
|
// We probably could be smarter here
|
|
return message.length * USER_READ_TIME;
|
|
}
|
|
|
|
finishMessageQueue() {
|
|
if (!this.hasPendingMessages)
|
|
return;
|
|
|
|
this._messageQueue = [];
|
|
|
|
this.emit('no-more-messages');
|
|
}
|
|
|
|
@@ -429,103 +437,116 @@ var ShellUserVerifier = class {
|
|
_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);
|
|
} 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;
|
|
}
|
|
|
|
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);
|
|
} catch (e) {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
return;
|
|
this._reportInitError('Failed to obtain user verifier', e);
|
|
return;
|
|
}
|
|
|
|
+ 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._signalIds = [];
|
|
|
|
let id = this._userVerifier.connect('info', this._onInfo.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('problem', this._onProblem.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('info-query', this._onInfoQuery.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('secret-info-query', this._onSecretInfoQuery.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('conversation-stopped', this._onConversationStopped.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('service-unavailable', this._onServiceUnavailable.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('reset', this._onReset.bind(this));
|
|
this._signalIds.push(id);
|
|
id = this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this));
|
|
this._signalIds.push(id);
|
|
+
|
|
+ if (this._userVerifierChoiceList)
|
|
+ this._userVerifierChoiceList.connect('choice-query', this._onChoiceListQuery.bind(this));
|
|
}
|
|
|
|
_disconnectSignals() {
|
|
if (!this._signalIds || !this._userVerifier)
|
|
return;
|
|
|
|
this._signalIds.forEach(s => this._userVerifier.disconnect(s));
|
|
this._signalIds = [];
|
|
}
|
|
|
|
_getForegroundService() {
|
|
if (this._preemptingService)
|
|
return this._preemptingService;
|
|
|
|
return this._defaultService;
|
|
}
|
|
|
|
serviceIsForeground(serviceName) {
|
|
return serviceName == this._getForegroundService();
|
|
}
|
|
|
|
serviceIsDefault(serviceName) {
|
|
return serviceName == this._defaultService;
|
|
}
|
|
|
|
serviceIsFingerprint(serviceName) {
|
|
return this._fingerprintReaderType !== FingerprintReaderType.NONE &&
|
|
serviceName === FINGERPRINT_SERVICE_NAME;
|
|
}
|
|
|
|
@@ -554,60 +575,67 @@ var ShellUserVerifier = class {
|
|
} else {
|
|
await this._userVerifier.call_begin_verification(
|
|
serviceName, this._cancellable);
|
|
}
|
|
} catch (e) {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
|
return;
|
|
if (!this.serviceIsForeground(serviceName)) {
|
|
logError(e, 'Failed to start %s for %s'.format(serviceName, this._userName));
|
|
this._hold.release();
|
|
return;
|
|
}
|
|
this._reportInitError(this._userName
|
|
? 'Failed to start %s verification for user'.format(serviceName)
|
|
: 'Failed to start %s verification'.format(serviceName), e,
|
|
serviceName);
|
|
return;
|
|
}
|
|
this._hold.release();
|
|
}
|
|
|
|
_beginVerification() {
|
|
this._startService(this._getForegroundService());
|
|
|
|
if (this._userName &&
|
|
this._fingerprintReaderType !== FingerprintReaderType.NONE &&
|
|
!this.serviceIsForeground(FINGERPRINT_SERVICE_NAME))
|
|
this._startService(FINGERPRINT_SERVICE_NAME);
|
|
}
|
|
|
|
+ _onChoiceListQuery(client, serviceName, promptMessage, list) {
|
|
+ if (!this.serviceIsForeground(serviceName))
|
|
+ return;
|
|
+
|
|
+ this.emit('show-choice-list', serviceName, promptMessage, list.deep_unpack());
|
|
+ }
|
|
+
|
|
_onInfo(client, serviceName, info) {
|
|
if (this.serviceIsForeground(serviceName)) {
|
|
this._queueMessage(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 === 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) {
|
|
diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js
|
|
index 5b55cb08a..f4655b25b 100644
|
|
--- a/js/ui/unlockDialog.js
|
|
+++ b/js/ui/unlockDialog.js
|
|
@@ -466,60 +466,67 @@ class UnlockDialogLayout extends Clutter.LayoutManager {
|
|
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;
|
|
});
|
|
|
|
--
|
|
2.34.1
|
|
|