diff options
Diffstat (limited to '')
-rw-r--r-- | js/ui/accessDialog.js | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/js/ui/accessDialog.js b/js/ui/accessDialog.js new file mode 100644 index 0000000..8788e47 --- /dev/null +++ b/js/ui/accessDialog.js @@ -0,0 +1,160 @@ +/* exported AccessDialogDBus */ +const { Clutter, Gio, GLib, GObject, Pango, Shell, St } = imports.gi; + +const CheckBox = imports.ui.checkBox; +const Dialog = imports.ui.dialog; +const ModalDialog = imports.ui.modalDialog; + +const { loadInterfaceXML } = imports.misc.fileUtils; + +const RequestIface = loadInterfaceXML('org.freedesktop.impl.portal.Request'); +const AccessIface = loadInterfaceXML('org.freedesktop.impl.portal.Access'); + +var DialogResponse = { + OK: 0, + CANCEL: 1, + CLOSED: 2, +}; + +var AccessDialog = GObject.registerClass( +class AccessDialog extends ModalDialog.ModalDialog { + _init(invocation, handle, title, description, body, options) { + super._init({ styleClass: 'access-dialog' }); + + this._invocation = invocation; + this._handle = handle; + + this._requestExported = false; + this._request = Gio.DBusExportedObject.wrapJSObject(RequestIface, this); + + for (let option in options) + options[option] = options[option].deepUnpack(); + + this._buildLayout(title, description, body, options); + } + + _buildLayout(title, description, body, options) { + // No support for non-modal system dialogs, so ignore the option + // let modal = options['modal'] || true; + let denyLabel = options['deny_label'] || _("Deny Access"); + let grantLabel = options['grant_label'] || _("Grant Access"); + let choices = options['choices'] || []; + + let content = new Dialog.MessageDialogContent({ title, description }); + this.contentLayout.add_actor(content); + + this._choices = new Map(); + + for (let i = 0; i < choices.length; i++) { + let [id, name, opts, selected] = choices[i]; + if (opts.length > 0) + continue; // radio buttons, not implemented + + let check = new CheckBox.CheckBox(); + check.getLabelActor().text = name; + check.checked = selected == "true"; + content.add_child(check); + + this._choices.set(id, check); + } + + let bodyLabel = new St.Label({ + text: body, + x_align: Clutter.ActorAlign.CENTER, + }); + bodyLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; + bodyLabel.clutter_text.line_wrap = true; + content.add_child(bodyLabel); + + this.addButton({ + label: denyLabel, + action: () => this._sendResponse(DialogResponse.CANCEL), + key: Clutter.KEY_Escape, + }); + this.addButton({ + label: grantLabel, + action: () => this._sendResponse(DialogResponse.OK), + }); + } + + open() { + if (!super.open()) + return false; + + let connection = this._invocation.get_connection(); + this._requestExported = this._request.export(connection, this._handle); + return true; + } + + CloseAsync(invocation, _params) { + if (this._invocation.get_sender() != invocation.get_sender()) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + ''); + return; + } + + this._sendResponse(DialogResponse.CLOSED); + } + + _sendResponse(response) { + if (this._requestExported) + this._request.unexport(); + this._requestExported = false; + + let results = {}; + if (response == DialogResponse.OK) { + for (let [id, check] of this._choices) { + let checked = check.checked ? 'true' : 'false'; + results[id] = new GLib.Variant('s', checked); + } + } + + // Delay actual response until the end of the close animation (if any) + this.connect('closed', () => { + this._invocation.return_value(new GLib.Variant('(ua{sv})', + [response, results])); + }); + this.close(); + } +}); + +var AccessDialogDBus = class { + constructor() { + this._accessDialog = null; + + this._windowTracker = Shell.WindowTracker.get_default(); + + this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(AccessIface, this); + this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/portal/desktop'); + + Gio.DBus.session.own_name('org.gnome.Shell.Portal', Gio.BusNameOwnerFlags.REPLACE, null, null); + } + + AccessDialogAsync(params, invocation) { + if (this._accessDialog) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.LIMITS_EXCEEDED, + 'Already showing a system access dialog'); + return; + } + + let [handle, appId, parentWindow_, title, description, body, options] = params; + // We probably want to use parentWindow and global.display.focus_window + // for this check in the future + if (appId && `${appId}.desktop` !== this._windowTracker.focus_app.id) { + invocation.return_error_literal(Gio.DBusError, + Gio.DBusError.ACCESS_DENIED, + 'Only the focused app is allowed to show a system access dialog'); + return; + } + + let dialog = new AccessDialog( + invocation, handle, title, description, body, options); + dialog.open(); + + dialog.connect('closed', () => (this._accessDialog = null)); + + this._accessDialog = dialog; + } +}; |