Skip to content

Commit e85ac3f

Browse files
committed
Convert the keyring unlock dialog to a clutter dialog
1 parent 520b4db commit e85ac3f

13 files changed

+1451
-0
lines changed

data/theme/cinnamon-sass/widgets/_base.scss

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
.flashspot { background-color: white; }
22

3+
// Caps-lock warning
4+
.caps-lock-warning-label {
5+
text-align: center;
6+
padding-bottom: 8px;
7+
@extend %caption;
8+
color: $warning_color;
9+
}
10+
311
// links
412
.cinnamon-link {
513
color: $link_color;

data/theme/cinnamon-sass/widgets/_switch-check.scss

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ $check_width: 20px;
3131
&:focus:checked StBin { background-image: url("checkbox.svg"); }
3232
}
3333

34+
// New CheckBoxes
35+
36+
.check-box-2 {
37+
StBoxLayout { spacing: 0.8em; }
38+
39+
StBin {
40+
width: $check_width;
41+
height: $check_height;
42+
background-image: url("checkbox-off.svg");
43+
}
44+
&:focus StBin { background-image: url("checkbox-off.svg"); }
45+
&:checked StBin { background-image: url("checkbox.svg"); }
46+
&:focus:checked StBin { background-image: url("checkbox.svg"); }
47+
}
48+
3449
// radio
3550

3651
.radiobutton {

debian/control

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Build-Depends:
1515
libcinnamon-menu-3-dev (>= 4.8),
1616
libcjs-dev (>= 4.8),
1717
libdbus-1-dev,
18+
libgcr-4-dev,
1819
libgirepository1.0-dev (>= 1.29.15),
1920
libgl1-mesa-dev,
2021
libglib2.0-dev (>= 2.52),
@@ -57,6 +58,7 @@ Depends:
5758
gir1.2-cvc-1.0,
5859
gir1.2-ecal-2.0,
5960
gir1.2-edataserver-1.2,
61+
gir1.2-gcr-4,
6062
gir1.2-gdkpixbuf-2.0,
6163
gir1.2-gkbd-3.0,
6264
gir1.2-gsound-1.0,

js/ui/checkBox.js

+38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const Clutter = imports.gi.Clutter;
2+
const GObject = imports.gi.GObject;
23
const Pango = imports.gi.Pango;
34
const Cinnamon = imports.gi.Cinnamon;
45
const St = imports.gi.St;
@@ -151,3 +152,40 @@ var CheckBox = class extends CheckBoxBase {
151152
return this._container.label;
152153
}
153154
}
155+
156+
var CheckBox2 = GObject.registerClass(
157+
class CheckBox2 extends St.Button {
158+
_init(label) {
159+
let container = new St.BoxLayout();
160+
super._init({
161+
style_class: 'check-box-2',
162+
important: true,
163+
child: container,
164+
button_mask: St.ButtonMask.ONE,
165+
toggle_mode: true,
166+
can_focus: true,
167+
x_fill: true,
168+
y_fill: true,
169+
});
170+
171+
this._box = new St.Bin();
172+
this._box.set_y_align(Clutter.ActorAlign.START);
173+
container.add_actor(this._box);
174+
175+
this._label = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
176+
this._label.clutter_text.set_line_wrap(true);
177+
this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
178+
container.add_actor(this._label);
179+
180+
if (label)
181+
this.setLabel(label);
182+
}
183+
184+
setLabel(label) {
185+
this._label.set_text(label);
186+
}
187+
188+
getLabelActor() {
189+
return this._label;
190+
}
191+
});

js/ui/cinnamonEntry.js

+56
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const Clutter = imports.gi.Clutter;
22
const Cinnamon = imports.gi.Cinnamon;
3+
const GObject = imports.gi.GObject;
34
const Gtk = imports.gi.Gtk;
45
const Lang = imports.lang;
6+
const Pango = imports.gi.Pango;
57
const St = imports.gi.St;
68

79
const Main = imports.ui.main;
@@ -164,3 +166,57 @@ function addContextMenu(entry, params) {
164166
entry.clutter_text.connect('button-press-event', _onClicked);
165167
entry.connect('popup-menu', _onPopup);
166168
}
169+
170+
var CapsLockWarning = GObject.registerClass(
171+
class CapsLockWarning extends St.Label {
172+
_init(params) {
173+
let defaultParams = { style_class: 'prompt-dialog-error-label' };
174+
super._init(Object.assign(defaultParams, params));
175+
176+
this.text = _('Caps lock is on');
177+
178+
this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
179+
this.clutter_text.line_wrap = true;
180+
181+
let seat = Clutter.get_default_backend().get_default_seat();
182+
this._keymap = seat.get_keymap();
183+
184+
this.connect('notify::mapped', () => {
185+
if (this.is_mapped()) {
186+
this._stateChangedId = this._keymap.connect('state-changed',
187+
() => this._sync(true));
188+
} else {
189+
this._keymap.disconnect(this._stateChangedId);
190+
this.stateChangedId = 0;
191+
}
192+
193+
this._sync(false);
194+
});
195+
196+
this.connect('destroy', () => {
197+
if (this._stateChangedId)
198+
this._keymap.disconnect(this._stateChangedId);
199+
});
200+
}
201+
202+
_sync(animate) {
203+
let capsLockOn = this._keymap.get_caps_lock_state();
204+
205+
this.remove_all_transitions();
206+
207+
const { naturalHeightSet } = this;
208+
this.natural_height_set = false;
209+
let [, height] = this.get_preferred_height(-1);
210+
this.natural_height_set = naturalHeightSet;
211+
212+
this.ease({
213+
height: capsLockOn ? height : 0,
214+
opacity: capsLockOn ? 255 : 0,
215+
duration: animate ? 200 : 0,
216+
onComplete: () => {
217+
if (capsLockOn)
218+
this.height = -1;
219+
},
220+
});
221+
}
222+
});

js/ui/keyringPrompt.js

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
const Cinnamon = imports.gi.Cinnamon;
2+
const Clutter = imports.gi.Clutter;
3+
const St = imports.gi.St;
4+
const Pango = imports.gi.Pango;
5+
const Gio = imports.gi.Gio;
6+
const GObject = imports.gi.GObject;
7+
const Gcr = imports.gi.Gcr;
8+
9+
const Dialog = imports.ui.dialog;
10+
const ModalDialog = imports.ui.modalDialog;
11+
const CinnamonEntry = imports.ui.cinnamonEntry;
12+
const CheckBox = imports.ui.checkBox;
13+
const Util = imports.misc.util;
14+
15+
var KeyringDialog = GObject.registerClass(
16+
class KeyringDialog extends ModalDialog.ModalDialog {
17+
_init() {
18+
super._init({ styleClass: 'prompt-dialog' });
19+
20+
this.prompt = new Cinnamon.KeyringPrompt();
21+
this.prompt.connect('show-password', this._onShowPassword.bind(this));
22+
this.prompt.connect('show-confirm', this._onShowConfirm.bind(this));
23+
this.prompt.connect('prompt-close', this._onHidePrompt.bind(this));
24+
25+
let content = new Dialog.MessageDialogContent();
26+
27+
this.prompt.bind_property('message',
28+
content, 'title', GObject.BindingFlags.SYNC_CREATE);
29+
this.prompt.bind_property('description',
30+
content, 'description', GObject.BindingFlags.SYNC_CREATE);
31+
32+
let passwordBox = new St.BoxLayout({
33+
style_class: 'prompt-dialog-password-layout',
34+
vertical: true,
35+
});
36+
37+
this._passwordEntry = new St.Entry({
38+
style_class: 'prompt-dialog-password-entry',
39+
can_focus: true,
40+
x_align: Clutter.ActorAlign.CENTER,
41+
});
42+
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
43+
CinnamonEntry.addContextMenu(this._passwordEntry, { isPassword: true });
44+
this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));
45+
this.prompt.bind_property('password-visible',
46+
this._passwordEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
47+
passwordBox.add_child(this._passwordEntry);
48+
49+
this._confirmEntry = new St.Entry({
50+
style_class: 'prompt-dialog-password-entry',
51+
can_focus: true,
52+
x_align: Clutter.ActorAlign.CENTER,
53+
});
54+
this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
55+
CinnamonEntry.addContextMenu(this._confirmEntry, { isPassword: true });
56+
this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
57+
this.prompt.bind_property('confirm-visible',
58+
this._confirmEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
59+
passwordBox.add_child(this._confirmEntry);
60+
61+
this.prompt.set_password_actor(this._passwordEntry.clutter_text);
62+
this.prompt.set_confirm_actor(this._confirmEntry.clutter_text);
63+
64+
let warningBox = new St.BoxLayout({ vertical: true });
65+
66+
let capsLockWarning = new CinnamonEntry.CapsLockWarning();
67+
let syncCapsLockWarningVisibility = () => {
68+
capsLockWarning.visible =
69+
this.prompt.password_visible || this.prompt.confirm_visible;
70+
};
71+
this.prompt.connect('notify::password-visible', syncCapsLockWarningVisibility);
72+
this.prompt.connect('notify::confirm-visible', syncCapsLockWarningVisibility);
73+
warningBox.add_child(capsLockWarning);
74+
75+
let warning = new St.Label({ style_class: 'prompt-dialog-error-label' });
76+
warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
77+
warning.clutter_text.line_wrap = true;
78+
this.prompt.bind_property('warning',
79+
warning, 'text', GObject.BindingFlags.SYNC_CREATE);
80+
this.prompt.connect('notify::warning-visible', () => {
81+
warning.opacity = this.prompt.warning_visible ? 255 : 0;
82+
});
83+
this.prompt.connect('notify::warning', () => {
84+
if (this._passwordEntry && this.prompt.warning !== '')
85+
Util.wiggle(this._passwordEntry);
86+
});
87+
warningBox.add_child(warning);
88+
89+
passwordBox.add_child(warningBox);
90+
content.add_child(passwordBox);
91+
92+
this._choice = new CheckBox.CheckBox2();
93+
this.prompt.bind_property('choice-label', this._choice.getLabelActor(),
94+
'text', GObject.BindingFlags.SYNC_CREATE);
95+
this.prompt.bind_property('choice-chosen', this._choice,
96+
'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
97+
this.prompt.bind_property('choice-visible', this._choice,
98+
'visible', GObject.BindingFlags.SYNC_CREATE);
99+
content.add_child(this._choice);
100+
101+
this.contentLayout.add_child(content);
102+
103+
this._cancelButton = this.addButton({
104+
label: '',
105+
action: this._onCancelButton.bind(this),
106+
key: Clutter.KEY_Escape,
107+
});
108+
this._continueButton = this.addButton({
109+
label: '',
110+
action: this._onContinueButton.bind(this),
111+
default: true,
112+
});
113+
114+
this.prompt.bind_property('cancel-label', this._cancelButton,
115+
'label', GObject.BindingFlags.SYNC_CREATE);
116+
this.prompt.bind_property('continue-label', this._continueButton,
117+
'label', GObject.BindingFlags.SYNC_CREATE);
118+
}
119+
120+
_updateSensitivity(sensitive) {
121+
if (this._passwordEntry)
122+
this._passwordEntry.reactive = sensitive;
123+
124+
if (this._confirmEntry)
125+
this._confirmEntry.reactive = sensitive;
126+
127+
this._continueButton.can_focus = sensitive;
128+
this._continueButton.reactive = sensitive;
129+
}
130+
131+
_ensureOpen() {
132+
// NOTE: ModalDialog.open() is safe to call if the dialog is
133+
// already open - it just returns true without side-effects
134+
if (this.open())
135+
return true;
136+
137+
// The above fail if e.g. unable to get input grab
138+
//
139+
// In an ideal world this wouldn't happen (because
140+
// Cinnamon is in complete control of the session) but that's
141+
// just not how things work right now.
142+
143+
log('keyringPrompt: Failed to show modal dialog.' +
144+
' Dismissing prompt request');
145+
this.prompt.cancel();
146+
return false;
147+
}
148+
149+
_onShowPassword() {
150+
this._ensureOpen();
151+
this._updateSensitivity(true);
152+
this._passwordEntry.text = '';
153+
this._passwordEntry.grab_key_focus();
154+
}
155+
156+
_onShowConfirm() {
157+
this._ensureOpen();
158+
this._updateSensitivity(true);
159+
this._confirmEntry.text = '';
160+
this._continueButton.grab_key_focus();
161+
}
162+
163+
_onHidePrompt() {
164+
this.close();
165+
}
166+
167+
_onPasswordActivate() {
168+
if (this.prompt.confirm_visible)
169+
this._confirmEntry.grab_key_focus();
170+
else
171+
this._onContinueButton();
172+
}
173+
174+
_onConfirmActivate() {
175+
this._onContinueButton();
176+
}
177+
178+
_onContinueButton() {
179+
this._updateSensitivity(false);
180+
this.prompt.complete();
181+
}
182+
183+
_onCancelButton() {
184+
this.prompt.cancel();
185+
}
186+
});
187+
188+
function init() {
189+
prompter = new Gcr.SystemPrompter();
190+
prompter.connect('new-prompt', () => {
191+
let dialog = new KeyringDialog();
192+
return dialog.prompt;
193+
});
194+
195+
let connection = Gio.DBus.session;
196+
prompter.register(connection);
197+
Gio.bus_own_name_on_connection (connection, 'org.gnome.keyring.SystemPrompter',
198+
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
199+
}

js/ui/main.js

+3
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ const Expo = imports.ui.expo;
103103
const Panel = imports.ui.panel;
104104
const PlacesManager = imports.ui.placesManager;
105105
const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
106+
const KeyringPrompt = imports.ui.keyringPrompt;
106107
const RunDialog = imports.ui.runDialog;
107108
const Layout = imports.ui.layout;
108109
const LookingGlass = imports.ui.lookingGlass;
@@ -434,6 +435,8 @@ function start() {
434435
PolkitAuthenticationAgent.init();
435436
}
436437

438+
KeyringPrompt.init();
439+
437440
_startDate = new Date();
438441

439442
global.display.connect('restart', () => {

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ cmenu = dependency('libcinnamon-menu-3.0', version: '>= 4.8.0')
2828
cogl = dependency('muffin-cogl-0')
2929
cogl_path = dependency('muffin-cogl-path-0')
3030
dbus = dependency('dbus-1')
31+
gcr = dependency('gcr-4', version:'>=3.90.0')
3132
gdkx11 = dependency('gdk-x11-3.0')
3233
gi = dependency('gobject-introspection-1.0', version: '>= 0.9.2')
3334
polkit = dependency('polkit-agent-1', version: '>= 0.100')

0 commit comments

Comments
 (0)