From 8dc8423999ddab6430c842aef4b9ee9780a91fdd Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Tue, 21 Apr 2026 12:04:31 -0400 Subject: [PATCH] dialogs: Convert some more system dialogs to PopupDialog. - Keyring prompts. - Network prompts (wifi password, etc...) - polkit/pkexec prompts. Test file included for keyring dialogs. ref: 81eba1c670c1e4ff1b2747d704247f7aa246ec81 --- js/testing/testKeyringPrompt.js | 100 +++++++++++++++++++++++++++++ js/ui/keyringPrompt.js | 21 ++---- js/ui/networkAgent.js | 24 ++++--- js/ui/polkitAuthenticationAgent.js | 37 +++-------- 4 files changed, 131 insertions(+), 51 deletions(-) create mode 100644 js/testing/testKeyringPrompt.js diff --git a/js/testing/testKeyringPrompt.js b/js/testing/testKeyringPrompt.js new file mode 100644 index 0000000000..32e9280bd0 --- /dev/null +++ b/js/testing/testKeyringPrompt.js @@ -0,0 +1,100 @@ +// Test helpers for keyring prompt dialog. +// Call from Looking Glass: +// +// imports.ui.testKeyringPrompt.passwordPrompt() +// imports.ui.testKeyringPrompt.passwordPrompt("Save this password in your keyring") +// imports.ui.testKeyringPrompt.passwordPromptNoChoice() +// imports.ui.testKeyringPrompt.confirmPrompt() +// imports.ui.testKeyringPrompt.newPasswordPrompt() + +const Gcr = imports.gi.Gcr; +const KeyringPrompt = imports.ui.keyringPrompt; + +function passwordPrompt(choiceLabel) { + let dialog = new KeyringPrompt.KeyringDialog(); + let prompt = dialog.prompt; + + prompt.set_message("Unlock Keyring"); + prompt.set_description("Enter password for keyring 'login' to unlock"); + prompt.set_choice_label(choiceLabel || "Always unlock this keyring whenever logged in"); + prompt.set_continue_label("Unlock"); + prompt.set_cancel_label("Cancel"); + + prompt.password_async(null, (source, result) => { + try { + let password = prompt.password_finish(result); + log(`[testKeyringPrompt] password: ${password}, choice_chosen: ${prompt.choice_chosen}`); + } catch(e) { + log(`[testKeyringPrompt] cancelled: ${e.message}`); + } + }); + + return dialog; +} + +function passwordPromptNoChoice() { + let dialog = new KeyringPrompt.KeyringDialog(); + let prompt = dialog.prompt; + + prompt.set_message("Unlock Keyring"); + prompt.set_description("Enter password for keyring 'login' to unlock"); + prompt.set_choice_label(""); + prompt.set_continue_label("Unlock"); + prompt.set_cancel_label("Cancel"); + + prompt.password_async(null, (source, result) => { + try { + let password = prompt.password_finish(result); + log(`[testKeyringPrompt] password: ${password}`); + } catch(e) { + log(`[testKeyringPrompt] cancelled: ${e.message}`); + } + }); + + return dialog; +} + +function confirmPrompt(choiceLabel) { + let dialog = new KeyringPrompt.KeyringDialog(); + let prompt = dialog.prompt; + + prompt.set_message("Trust This Key"); + prompt.set_description("Do you want to mark this key as trusted?"); + prompt.set_choice_label(choiceLabel || "Always trust this key"); + prompt.set_continue_label("Trust"); + prompt.set_cancel_label("Cancel"); + + prompt.confirm_async(null, (source, result) => { + try { + let reply = prompt.confirm_finish(result); + log(`[testKeyringPrompt] confirm reply: ${reply}, choice_chosen: ${prompt.choice_chosen}`); + } catch(e) { + log(`[testKeyringPrompt] cancelled: ${e.message}`); + } + }); + + return dialog; +} + +function newPasswordPrompt(choiceLabel) { + let dialog = new KeyringPrompt.KeyringDialog(); + let prompt = dialog.prompt; + + prompt.set_message("Change Keyring Password"); + prompt.set_description("Enter a new password for the 'login' keyring"); + prompt.set_password_new(true); + prompt.set_choice_label(choiceLabel || "Always unlock this keyring whenever logged in"); + prompt.set_continue_label("Continue"); + prompt.set_cancel_label("Cancel"); + + prompt.password_async(null, (source, result) => { + try { + let password = prompt.password_finish(result); + log(`[testKeyringPrompt] new password: ${password}, choice_chosen: ${prompt.choice_chosen}`); + } catch(e) { + log(`[testKeyringPrompt] cancelled: ${e.message}`); + } + }); + + return dialog; +} diff --git a/js/ui/keyringPrompt.js b/js/ui/keyringPrompt.js index 6a4cd65863..bbeee939ac 100644 --- a/js/ui/keyringPrompt.js +++ b/js/ui/keyringPrompt.js @@ -7,13 +7,13 @@ const GObject = imports.gi.GObject; const Gcr = imports.gi.Gcr; const Dialog = imports.ui.dialog; -const ModalDialog = imports.ui.modalDialog; +const PopupDialog = imports.ui.popupDialog; const CinnamonEntry = imports.ui.cinnamonEntry; const CheckBox = imports.ui.checkBox; const Util = imports.misc.util; var KeyringDialog = GObject.registerClass( -class KeyringDialog extends ModalDialog.ModalDialog { +class KeyringDialog extends PopupDialog.PopupDialog { _init() { super._init({ styleClass: 'prompt-dialog' }); @@ -127,32 +127,25 @@ class KeyringDialog extends ModalDialog.ModalDialog { } _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'); + global.logError('keyringPrompt: Failed to show dialog. Dismissing prompt request'); this.prompt.cancel(); return false; } _onShowPassword() { - this._ensureOpen(); + if (!this._ensureOpen()) + return; this._updateSensitivity(true); this._passwordEntry.text = ''; this._passwordEntry.grab_key_focus(); } _onShowConfirm() { - this._ensureOpen(); + if (!this._ensureOpen()) + return; this._updateSensitivity(true); this._confirmEntry.text = ''; this._continueButton.grab_key_focus(); diff --git a/js/ui/networkAgent.js b/js/ui/networkAgent.js index d54d19d470..a83f36bf68 100644 --- a/js/ui/networkAgent.js +++ b/js/ui/networkAgent.js @@ -14,13 +14,13 @@ const Signals = imports.signals; const Dialog = imports.ui.dialog; const Main = imports.ui.main; const MessageTray = imports.ui.messageTray; -const ModalDialog = imports.ui.modalDialog; +const PopupDialog = imports.ui.popupDialog; const CinnamonEntry = imports.ui.cinnamonEntry; const VPN_UI_GROUP = 'VPN Plugin UI'; var NetworkSecretDialog = GObject.registerClass( -class NetworkSecretDialog extends ModalDialog.ModalDialog { +class NetworkSecretDialog extends PopupDialog.PopupDialog { _init(agent, requestId, connection, settingName, hints, flags, contentOverride) { super._init({ styleClass: 'prompt-dialog' }); @@ -140,14 +140,14 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog { if (valid) { this._agent.respond(this._requestId, Cinnamon.NetworkAgentResponse.CONFIRMED); - this.close(global.get_current_time()); + this.close(); } // do nothing if not valid } cancel() { this._agent.respond(this._requestId, Cinnamon.NetworkAgentResponse.USER_CANCELED); - this.close(global.get_current_time()); + this.close(); } _validateWpaPsk(secret) { @@ -419,8 +419,7 @@ var VPNRequestHandler = class { this._agent.respond(this._requestId, Cinnamon.NetworkAgentResponse.USER_CANCELED); if (this._newStylePlugin && this._cinnamonDialog) { - this._cinnamonDialog.close(global.get_current_time()); - this._cinnamonDialog.destroy(); + this._cinnamonDialog.close(); } else { try { this._stdin.write('QUIT\n\n', null); @@ -579,7 +578,11 @@ var VPNRequestHandler = class { if (contentOverride && contentOverride.secrets.length) { // Only show the dialog if we actually have something to ask this._cinnamonDialog = new NetworkSecretDialog(this._agent, this._requestId, this._connection, 'vpn', [], this._flags, contentOverride); - this._cinnamonDialog.open(global.get_current_time()); + if (!this._cinnamonDialog.open()) { + this._agent.respond(this._requestId, Cinnamon.NetworkAgentResponse.INTERNAL_ERROR); + this.destroy(); + return; + } } else { this._agent.respond(this._requestId, Cinnamon.NetworkAgentResponse.CONFIRMED); this.destroy(); @@ -727,14 +730,15 @@ var NetworkAgent = class { delete this._dialogs[requestId]; }); this._dialogs[requestId] = dialog; - dialog.open(global.get_current_time()); + if (!dialog.open()) + this._native.respond(requestId, Cinnamon.NetworkAgentResponse.INTERNAL_ERROR); } _cancelRequest(agent, requestId) { if (this._dialogs[requestId]) { - this._dialogs[requestId].close(global.get_current_time()); - this._dialogs[requestId].destroy(); + let dialog = this._dialogs[requestId]; delete this._dialogs[requestId]; + dialog.close(); } else if (this._vpnRequests[requestId]) { this._vpnRequests[requestId].cancel(false); delete this._vpnRequests[requestId]; diff --git a/js/ui/polkitAuthenticationAgent.js b/js/ui/polkitAuthenticationAgent.js index d3875ed372..bae3440007 100644 --- a/js/ui/polkitAuthenticationAgent.js +++ b/js/ui/polkitAuthenticationAgent.js @@ -37,7 +37,7 @@ const Meta = imports.gi.Meta; const Dialog = imports.ui.dialog; const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; +const PopupDialog = imports.ui.popupDialog; const CinnamonEntry = imports.ui.cinnamonEntry; const PopupMenu = imports.ui.popupMenu; const UserWidget = imports.ui.userWidget; @@ -46,8 +46,6 @@ const Util = imports.misc.util; const DIALOG_ICON_SIZE = 64; const DELAYED_RESET_TIMEOUT = 200; -const MAX_MODAL_RETRIES = 2; - var RootUser = class { constructor(name) { this.userName = name; @@ -115,7 +113,7 @@ var AdminUser = class { var AuthenticationDialog = GObject.registerClass({ Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } } -}, class AuthenticationDialog extends ModalDialog.ModalDialog { +}, class AuthenticationDialog extends PopupDialog.PopupDialog { _init(actionId, description, cookie, userNames) { super._init({ styleClass: 'prompt-dialog' }); @@ -128,8 +126,6 @@ var AuthenticationDialog = GObject.registerClass({ this._visibleAvatar = null; this._adminUsers = []; - this._modalRetryCount = 0; - this._sessionCompletedId = 0; this._sessionRequestId = 0; this._sessionShowErrorId = 0; @@ -356,29 +352,16 @@ var AuthenticationDialog = GObject.registerClass({ } _ensureOpen(focusEntry=false) { - // NOTE: ModalDialog.open() is safe to call if the dialog is - // already open - it just returns true without side-effects - if (!this.open(global.get_current_time())) { - // This can fail if e.g. unable to get input grab (double-click admin:// folder in nemo). - if (this._modalRetryCount < MAX_MODAL_RETRIES) { - this._modalRetryCount++; - GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { - this._ensureOpen(focusEntry); - return GLib.SOURCE_REMOVE; - }); - - return; - } - - log('polkitAuthenticationAgent: Failed to show modal dialog.' + + if (!this.open()) { + global.logError('polkitAuthenticationAgent: Failed to show dialog.' + ' Dismissing authentication request for action-id ' + this.actionId + ' cookie ' + this._cookie); this._emitDone(true); - } else { - if (focusEntry) { - this._passwordEntry.grab_key_focus(); - } + return; } + + if (focusEntry) + this._passwordEntry.grab_key_focus(); } _emitDone(dismissed) { @@ -515,7 +498,7 @@ var AuthenticationDialog = GObject.registerClass({ } let resetDialog = () => { - if (this.state != ModalDialog.State.OPENED) + if (this.state != PopupDialog.State.OPENED) return; this._passwordEntry.hide(); @@ -614,7 +597,7 @@ var AuthenticationAgent = class { } _completeRequest(dismissed) { - this._currentDialog.close(global.get_current_time()); + this._currentDialog.close(); this._currentDialog = null; this._native.complete(dismissed);