Skip to content

Commit

Permalink
Convert the keyring unlock dialog to a clutter dialog (#12452)
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephMcc authored Oct 20, 2024
1 parent 79abe5e commit a6f0b81
Show file tree
Hide file tree
Showing 13 changed files with 1,451 additions and 0 deletions.
8 changes: 8 additions & 0 deletions data/theme/cinnamon-sass/widgets/_base.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
.flashspot { background-color: white; }

// Caps-lock warning
.caps-lock-warning-label {
text-align: center;
padding-bottom: 8px;
@extend %caption;
color: $warning_color;
}

// links
.cinnamon-link {
color: $link_color;
Expand Down
15 changes: 15 additions & 0 deletions data/theme/cinnamon-sass/widgets/_switch-check.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ $check_width: 20px;
&:focus:checked StBin { background-image: url("checkbox.svg"); }
}

// New CheckBoxes

.check-box-2 {
StBoxLayout { spacing: 0.8em; }

StBin {
width: $check_width;
height: $check_height;
background-image: url("checkbox-off.svg");
}
&:focus StBin { background-image: url("checkbox-off.svg"); }
&:checked StBin { background-image: url("checkbox.svg"); }
&:focus:checked StBin { background-image: url("checkbox.svg"); }
}

// radio

.radiobutton {
Expand Down
2 changes: 2 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Build-Depends:
libcinnamon-menu-3-dev (>= 4.8),
libcjs-dev (>= 4.8),
libdbus-1-dev,
libgcr-3-dev,
libgirepository1.0-dev (>= 1.29.15),
libgl1-mesa-dev,
libglib2.0-dev (>= 2.52),
Expand Down Expand Up @@ -57,6 +58,7 @@ Depends:
gir1.2-cvc-1.0,
gir1.2-ecal-2.0,
gir1.2-edataserver-1.2,
gir1.2-gcr-3,
gir1.2-gdkpixbuf-2.0,
gir1.2-gkbd-3.0,
gir1.2-gsound-1.0,
Expand Down
38 changes: 38 additions & 0 deletions js/ui/checkBox.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Pango = imports.gi.Pango;
const Cinnamon = imports.gi.Cinnamon;
const St = imports.gi.St;
Expand Down Expand Up @@ -151,3 +152,40 @@ var CheckBox = class extends CheckBoxBase {
return this._container.label;
}
}

var CheckBox2 = GObject.registerClass(
class CheckBox2 extends St.Button {
_init(label) {
let container = new St.BoxLayout();
super._init({
style_class: 'check-box-2',
important: true,
child: container,
button_mask: St.ButtonMask.ONE,
toggle_mode: true,
can_focus: true,
x_fill: true,
y_fill: true,
});

this._box = new St.Bin();
this._box.set_y_align(Clutter.ActorAlign.START);
container.add_actor(this._box);

this._label = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
this._label.clutter_text.set_line_wrap(true);
this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
container.add_actor(this._label);

if (label)
this.setLabel(label);
}

setLabel(label) {
this._label.set_text(label);
}

getLabelActor() {
return this._label;
}
});
56 changes: 56 additions & 0 deletions js/ui/cinnamonEntry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const Clutter = imports.gi.Clutter;
const Cinnamon = imports.gi.Cinnamon;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Pango = imports.gi.Pango;
const St = imports.gi.St;

const Main = imports.ui.main;
Expand Down Expand Up @@ -164,3 +166,57 @@ function addContextMenu(entry, params) {
entry.clutter_text.connect('button-press-event', _onClicked);
entry.connect('popup-menu', _onPopup);
}

var CapsLockWarning = GObject.registerClass(
class CapsLockWarning extends St.Label {
_init(params) {
let defaultParams = { style_class: 'prompt-dialog-error-label' };
super._init(Object.assign(defaultParams, params));

this.text = _('Caps lock is on');

this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this.clutter_text.line_wrap = true;

let seat = Clutter.get_default_backend().get_default_seat();
this._keymap = seat.get_keymap();

this.connect('notify::mapped', () => {
if (this.is_mapped()) {
this._stateChangedId = this._keymap.connect('state-changed',
() => this._sync(true));
} else {
this._keymap.disconnect(this._stateChangedId);
this.stateChangedId = 0;
}

this._sync(false);
});

this.connect('destroy', () => {
if (this._stateChangedId)
this._keymap.disconnect(this._stateChangedId);
});
}

_sync(animate) {
let capsLockOn = this._keymap.get_caps_lock_state();

this.remove_all_transitions();

const { naturalHeightSet } = this;
this.natural_height_set = false;
let [, height] = this.get_preferred_height(-1);
this.natural_height_set = naturalHeightSet;

this.ease({
height: capsLockOn ? height : 0,
opacity: capsLockOn ? 255 : 0,
duration: animate ? 200 : 0,
onComplete: () => {
if (capsLockOn)
this.height = -1;
},
});
}
});
199 changes: 199 additions & 0 deletions js/ui/keyringPrompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const Cinnamon = imports.gi.Cinnamon;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Pango = imports.gi.Pango;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gcr = imports.gi.Gcr;

const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;
const CinnamonEntry = imports.ui.cinnamonEntry;
const CheckBox = imports.ui.checkBox;
const Util = imports.misc.util;

var KeyringDialog = GObject.registerClass(
class KeyringDialog extends ModalDialog.ModalDialog {
_init() {
super._init({ styleClass: 'prompt-dialog' });

this.prompt = new Cinnamon.KeyringPrompt();
this.prompt.connect('show-password', this._onShowPassword.bind(this));
this.prompt.connect('show-confirm', this._onShowConfirm.bind(this));
this.prompt.connect('prompt-close', this._onHidePrompt.bind(this));

let content = new Dialog.MessageDialogContent();

this.prompt.bind_property('message',
content, 'title', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('description',
content, 'description', GObject.BindingFlags.SYNC_CREATE);

let passwordBox = new St.BoxLayout({
style_class: 'prompt-dialog-password-layout',
vertical: true,
});

this._passwordEntry = new St.Entry({
style_class: 'prompt-dialog-password-entry',
can_focus: true,
x_align: Clutter.ActorAlign.CENTER,
});
this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
CinnamonEntry.addContextMenu(this._passwordEntry, { isPassword: true });
this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));
this.prompt.bind_property('password-visible',
this._passwordEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
passwordBox.add_child(this._passwordEntry);

this._confirmEntry = new St.Entry({
style_class: 'prompt-dialog-password-entry',
can_focus: true,
x_align: Clutter.ActorAlign.CENTER,
});
this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
CinnamonEntry.addContextMenu(this._confirmEntry, { isPassword: true });
this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
this.prompt.bind_property('confirm-visible',
this._confirmEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
passwordBox.add_child(this._confirmEntry);

this.prompt.set_password_actor(this._passwordEntry.clutter_text);
this.prompt.set_confirm_actor(this._confirmEntry.clutter_text);

let warningBox = new St.BoxLayout({ vertical: true });

let capsLockWarning = new CinnamonEntry.CapsLockWarning();
let syncCapsLockWarningVisibility = () => {
capsLockWarning.visible =
this.prompt.password_visible || this.prompt.confirm_visible;
};
this.prompt.connect('notify::password-visible', syncCapsLockWarningVisibility);
this.prompt.connect('notify::confirm-visible', syncCapsLockWarningVisibility);
warningBox.add_child(capsLockWarning);

let warning = new St.Label({ style_class: 'prompt-dialog-error-label' });
warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
warning.clutter_text.line_wrap = true;
this.prompt.bind_property('warning',
warning, 'text', GObject.BindingFlags.SYNC_CREATE);
this.prompt.connect('notify::warning-visible', () => {
warning.opacity = this.prompt.warning_visible ? 255 : 0;
});
this.prompt.connect('notify::warning', () => {
if (this._passwordEntry && this.prompt.warning !== '')
Util.wiggle(this._passwordEntry);
});
warningBox.add_child(warning);

passwordBox.add_child(warningBox);
content.add_child(passwordBox);

this._choice = new CheckBox.CheckBox2();
this.prompt.bind_property('choice-label', this._choice.getLabelActor(),
'text', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('choice-chosen', this._choice,
'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
this.prompt.bind_property('choice-visible', this._choice,
'visible', GObject.BindingFlags.SYNC_CREATE);
content.add_child(this._choice);

this.contentLayout.add_child(content);

this._cancelButton = this.addButton({
label: '',
action: this._onCancelButton.bind(this),
key: Clutter.KEY_Escape,
});
this._continueButton = this.addButton({
label: '',
action: this._onContinueButton.bind(this),
default: true,
});

this.prompt.bind_property('cancel-label', this._cancelButton,
'label', GObject.BindingFlags.SYNC_CREATE);
this.prompt.bind_property('continue-label', this._continueButton,
'label', GObject.BindingFlags.SYNC_CREATE);
}

_updateSensitivity(sensitive) {
if (this._passwordEntry)
this._passwordEntry.reactive = sensitive;

if (this._confirmEntry)
this._confirmEntry.reactive = sensitive;

this._continueButton.can_focus = sensitive;
this._continueButton.reactive = sensitive;
}

_ensureOpen() {
// NOTE: ModalDialog.open() is safe to call if the dialog is
// already open - it just returns true without side-effects
if (this.open())
return true;

// The above fail if e.g. unable to get input grab
//
// In an ideal world this wouldn't happen (because
// Cinnamon is in complete control of the session) but that's
// just not how things work right now.

log('keyringPrompt: Failed to show modal dialog.' +
' Dismissing prompt request');
this.prompt.cancel();
return false;
}

_onShowPassword() {
this._ensureOpen();
this._updateSensitivity(true);
this._passwordEntry.text = '';
this._passwordEntry.grab_key_focus();
}

_onShowConfirm() {
this._ensureOpen();
this._updateSensitivity(true);
this._confirmEntry.text = '';
this._continueButton.grab_key_focus();
}

_onHidePrompt() {
this.close();
}

_onPasswordActivate() {
if (this.prompt.confirm_visible)
this._confirmEntry.grab_key_focus();
else
this._onContinueButton();
}

_onConfirmActivate() {
this._onContinueButton();
}

_onContinueButton() {
this._updateSensitivity(false);
this.prompt.complete();
}

_onCancelButton() {
this.prompt.cancel();
}
});

function init() {
prompter = new Gcr.SystemPrompter();
prompter.connect('new-prompt', () => {
let dialog = new KeyringDialog();
return dialog.prompt;
});

let connection = Gio.DBus.session;
prompter.register(connection);
Gio.bus_own_name_on_connection (connection, 'org.gnome.keyring.SystemPrompter',
Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
}
3 changes: 3 additions & 0 deletions js/ui/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const Expo = imports.ui.expo;
const Panel = imports.ui.panel;
const PlacesManager = imports.ui.placesManager;
const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
const KeyringPrompt = imports.ui.keyringPrompt;
const RunDialog = imports.ui.runDialog;
const Layout = imports.ui.layout;
const LookingGlass = imports.ui.lookingGlass;
Expand Down Expand Up @@ -434,6 +435,8 @@ function start() {
PolkitAuthenticationAgent.init();
}

KeyringPrompt.init();

_startDate = new Date();

global.display.connect('restart', () => {
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ cmenu = dependency('libcinnamon-menu-3.0', version: '>= 4.8.0')
cogl = dependency('muffin-cogl-0')
cogl_path = dependency('muffin-cogl-path-0')
dbus = dependency('dbus-1')
gcr = dependency('gcr-base-3', version:'>=3.7.5')
gdkx11 = dependency('gdk-x11-3.0')
gi = dependency('gobject-introspection-1.0', version: '>= 0.9.2')
polkit = dependency('polkit-agent-1', version: '>= 0.100')
Expand Down
Loading

0 comments on commit a6f0b81

Please sign in to comment.