169 lines
5.6 KiB
JavaScript
169 lines
5.6 KiB
JavaScript
import Clutter from 'gi://Clutter';
|
|
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
import GObject from 'gi://GObject';
|
|
import Pango from 'gi://Pango';
|
|
import Shell from 'gi://Shell';
|
|
import St from 'gi://St';
|
|
|
|
import * as CheckBox from './checkBox.js';
|
|
import * as Dialog from './dialog.js';
|
|
import * as ModalDialog from './modalDialog.js';
|
|
|
|
import {loadInterfaceXML} from '../misc/fileUtils.js';
|
|
|
|
const RequestIface = loadInterfaceXML('org.freedesktop.impl.portal.Request');
|
|
const AccessIface = loadInterfaceXML('org.freedesktop.impl.portal.Access');
|
|
|
|
/** @enum {number} */
|
|
const DialogResponse = {
|
|
OK: 0,
|
|
CANCEL: 1,
|
|
CLOSED: 2,
|
|
};
|
|
|
|
const 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');
|
|
let grantLabel = options['grant_label'] || _('Allow');
|
|
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);
|
|
}
|
|
|
|
if (body) {
|
|
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();
|
|
}
|
|
});
|
|
|
|
export class AccessDialogDBus {
|
|
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;
|
|
}
|
|
}
|