2017-12-19 12:41:14 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
|
|
|
|
// the following is a modified version of bolt/contrib/js/client.js
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Gio from 'gi://Gio';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Polkit from 'gi://Polkit';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import * as Signals from '../../misc/signals.js';
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as Main from '../main.js';
|
|
|
|
import * as MessageTray from '../messageTray.js';
|
|
|
|
import {SystemIndicator} from '../quickSettings.js';
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import {loadInterfaceXML} from '../../misc/fileUtils.js';
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2018-09-06 00:55:20 +00:00
|
|
|
/* Keep in sync with data/org.freedesktop.bolt.xml */
|
2018-08-23 00:55:02 +00:00
|
|
|
|
2018-09-06 00:55:20 +00:00
|
|
|
const BoltClientInterface = loadInterfaceXML('org.freedesktop.bolt1.Manager');
|
|
|
|
const BoltDeviceInterface = loadInterfaceXML('org.freedesktop.bolt1.Device');
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
const BoltDeviceProxy = Gio.DBusProxy.makeProxyWrapper(BoltDeviceInterface);
|
|
|
|
|
2023-07-30 12:56:59 +00:00
|
|
|
/** @enum {string} */
|
2023-07-10 09:53:00 +00:00
|
|
|
const Status = {
|
2018-03-05 20:03:20 +00:00
|
|
|
DISCONNECTED: 'disconnected',
|
2018-04-19 12:22:04 +00:00
|
|
|
CONNECTING: 'connecting',
|
2018-03-05 20:03:20 +00:00
|
|
|
CONNECTED: 'connected',
|
|
|
|
AUTHORIZING: 'authorizing',
|
|
|
|
AUTH_ERROR: 'auth-error',
|
2019-08-20 21:43:54 +00:00
|
|
|
AUTHORIZED: 'authorized',
|
2017-12-19 12:41:14 +00:00
|
|
|
};
|
|
|
|
|
2023-07-30 12:56:59 +00:00
|
|
|
/** @enum {string} */
|
2023-07-10 09:53:00 +00:00
|
|
|
const Policy = {
|
2018-03-05 20:03:20 +00:00
|
|
|
DEFAULT: 'default',
|
|
|
|
MANUAL: 'manual',
|
2019-08-20 21:43:54 +00:00
|
|
|
AUTO: 'auto',
|
2017-12-19 12:41:14 +00:00
|
|
|
};
|
|
|
|
|
2023-07-30 12:56:59 +00:00
|
|
|
/** @enum {string} */
|
2023-07-10 09:53:00 +00:00
|
|
|
const AuthCtrl = {
|
2018-03-05 20:03:20 +00:00
|
|
|
NONE: 'none',
|
|
|
|
};
|
|
|
|
|
2023-07-30 12:56:59 +00:00
|
|
|
/** @enum {string} */
|
2023-07-10 09:53:00 +00:00
|
|
|
const AuthMode = {
|
2018-03-05 20:05:32 +00:00
|
|
|
DISABLED: 'disabled',
|
2019-08-20 21:43:54 +00:00
|
|
|
ENABLED: 'enabled',
|
2017-12-19 12:41:14 +00:00
|
|
|
};
|
|
|
|
|
2018-02-26 18:21:43 +00:00
|
|
|
const BOLT_DBUS_CLIENT_IFACE = 'org.freedesktop.bolt1.Manager';
|
2017-12-19 12:41:14 +00:00
|
|
|
const BOLT_DBUS_NAME = 'org.freedesktop.bolt';
|
|
|
|
const BOLT_DBUS_PATH = '/org/freedesktop/bolt';
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
class Client extends Signals.EventEmitter {
|
2017-10-31 01:19:44 +00:00
|
|
|
constructor() {
|
2022-07-04 22:30:44 +00:00
|
|
|
super();
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._proxy = null;
|
|
|
|
this.probing = false;
|
2019-12-19 19:50:37 +00:00
|
|
|
this._getProxy();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
async _getProxy() {
|
|
|
|
let nodeInfo = Gio.DBusNodeInfo.new_for_xml(BoltClientInterface);
|
2018-02-26 18:21:43 +00:00
|
|
|
try {
|
2019-12-19 19:50:37 +00:00
|
|
|
this._proxy = await Gio.DBusProxy.new(
|
|
|
|
Gio.DBus.system,
|
|
|
|
Gio.DBusProxyFlags.DO_NOT_AUTO_START,
|
|
|
|
nodeInfo.lookup_interface(BOLT_DBUS_CLIENT_IFACE),
|
|
|
|
BOLT_DBUS_NAME,
|
|
|
|
BOLT_DBUS_PATH,
|
|
|
|
BOLT_DBUS_CLIENT_IFACE,
|
|
|
|
null);
|
2019-01-29 01:26:39 +00:00
|
|
|
} catch (e) {
|
2022-02-07 14:14:06 +00:00
|
|
|
log(`error creating bolt proxy: ${e.message}`);
|
2018-02-26 18:21:43 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-08-15 22:36:59 +00:00
|
|
|
this._proxy.connectObject('g-properties-changed',
|
|
|
|
this._onPropertiesChanged.bind(this), this);
|
2019-01-29 20:18:46 +00:00
|
|
|
this._deviceAddedId = this._proxy.connectSignal('DeviceAdded', this._onDeviceAdded.bind(this));
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this.probing = this._proxy.Probing;
|
|
|
|
if (this.probing)
|
|
|
|
this.emit('probing-changed', this.probing);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onPropertiesChanged(proxy, properties) {
|
2022-08-10 09:44:42 +00:00
|
|
|
const probingChanged = !!properties.lookup_value('Probing', null);
|
|
|
|
if (probingChanged) {
|
|
|
|
this.probing = this._proxy.Probing;
|
|
|
|
this.emit('probing-changed', this.probing);
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDeviceAdded(proxy, emitter, params) {
|
2019-01-29 20:18:46 +00:00
|
|
|
let [path] = params;
|
|
|
|
let device = new BoltDeviceProxy(Gio.DBus.system,
|
|
|
|
BOLT_DBUS_NAME,
|
|
|
|
path);
|
|
|
|
this.emit('device-added', device);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* public methods */
|
2017-10-31 00:03:21 +00:00
|
|
|
close() {
|
2017-12-19 12:41:14 +00:00
|
|
|
if (!this._proxy)
|
|
|
|
return;
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._proxy.disconnectSignal(this._deviceAddedId);
|
2021-08-15 22:36:59 +00:00
|
|
|
this._proxy.disconnectObject(this);
|
2019-01-29 20:18:46 +00:00
|
|
|
this._proxy = null;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2022-06-23 12:53:29 +00:00
|
|
|
async enrollDevice(id, policy) {
|
|
|
|
try {
|
|
|
|
const [path] = await this._proxy.EnrollDeviceAsync(id, policy, AuthCtrl.NONE);
|
|
|
|
const device = new BoltDeviceProxy(Gio.DBus.system, BOLT_DBUS_NAME, path);
|
|
|
|
return device;
|
|
|
|
} catch (error) {
|
|
|
|
Gio.DBusError.strip_remote_error(error);
|
|
|
|
throw error;
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2018-03-05 20:05:32 +00:00
|
|
|
|
2019-08-19 17:55:49 +00:00
|
|
|
get authMode() {
|
2018-03-05 20:05:32 +00:00
|
|
|
return this._proxy.AuthMode;
|
2017-12-19 12:41:14 +00:00
|
|
|
}
|
2023-07-10 09:53:00 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* helper class to automatically authorize new devices */
|
2023-07-10 09:53:00 +00:00
|
|
|
class AuthRobot extends Signals.EventEmitter {
|
2017-10-31 01:19:44 +00:00
|
|
|
constructor(client) {
|
2022-07-04 22:30:44 +00:00
|
|
|
super();
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._client = client;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._devicesToEnroll = [];
|
|
|
|
this._enrolling = false;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._client.connect('device-added', this._onDeviceAdded.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
close() {
|
2019-01-29 20:18:46 +00:00
|
|
|
this.disconnectAll();
|
|
|
|
this._client = null;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* the "device-added" signal will be emitted by boltd for every
|
|
|
|
* device that is not currently stored in the database. We are
|
|
|
|
* only interested in those devices, because all known devices
|
|
|
|
* will be handled by the user himself */
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDeviceAdded(cli, dev) {
|
2019-01-29 20:18:46 +00:00
|
|
|
if (dev.Status !== Status.CONNECTED)
|
|
|
|
return;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2018-03-05 20:05:32 +00:00
|
|
|
/* check if authorization is enabled in the daemon. if not
|
|
|
|
* we won't even bother authorizing, because we will only
|
2019-09-12 15:26:08 +00:00
|
|
|
* get an error back. The exact contents of AuthMode might
|
2018-03-05 20:05:32 +00:00
|
|
|
* change in the future, but must contain AuthMode.ENABLED
|
|
|
|
* if it is enabled. */
|
|
|
|
if (!cli.authMode.split('|').includes(AuthMode.ENABLED))
|
|
|
|
return;
|
2019-09-12 15:26:08 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
/* check if we should enroll the device */
|
|
|
|
let res = [false];
|
|
|
|
this.emit('enroll-device', dev, res);
|
|
|
|
if (res[0] !== true)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* ok, we should authorize the device, add it to the back
|
|
|
|
* of the list */
|
|
|
|
this._devicesToEnroll.push(dev);
|
|
|
|
this._enrollDevices();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* The enrollment queue:
|
|
|
|
* - new devices will be added to the end of the array.
|
|
|
|
* - an idle callback will be scheduled that will keep
|
|
|
|
* calling itself as long as there a devices to be
|
|
|
|
* enrolled.
|
|
|
|
*/
|
2017-10-31 00:03:21 +00:00
|
|
|
_enrollDevices() {
|
2019-01-29 20:18:46 +00:00
|
|
|
if (this._enrolling)
|
|
|
|
return;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._enrolling = true;
|
|
|
|
GLib.idle_add(GLib.PRIORITY_DEFAULT,
|
|
|
|
this._enrollDevicesIdle.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2022-06-23 12:53:29 +00:00
|
|
|
async _enrollDevicesIdle() {
|
2019-01-29 20:18:46 +00:00
|
|
|
let devices = this._devicesToEnroll;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
let dev = devices.shift();
|
|
|
|
if (dev === undefined)
|
|
|
|
return GLib.SOURCE_REMOVE;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2022-06-23 12:53:29 +00:00
|
|
|
try {
|
|
|
|
await this._client.enrollDevice(dev.Uid, Policy.DEFAULT);
|
|
|
|
|
|
|
|
/* TODO: scan the list of devices to be authorized for children
|
|
|
|
* of this device and remove them (and their children and
|
|
|
|
* their children and ....) from the device queue
|
|
|
|
*/
|
|
|
|
this._enrolling = this._devicesToEnroll.length > 0;
|
|
|
|
|
|
|
|
if (this._enrolling) {
|
|
|
|
GLib.idle_add(GLib.PRIORITY_DEFAULT,
|
|
|
|
this._enrollDevicesIdle.bind(this));
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
this.emit('enroll-failed', null, error);
|
|
|
|
}
|
2019-01-29 20:18:46 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2017-12-19 12:41:14 +00:00
|
|
|
}
|
2023-07-10 09:53:00 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* eof client.js */
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const Indicator = GObject.registerClass(
|
2022-07-27 02:01:53 +00:00
|
|
|
class Indicator extends SystemIndicator {
|
2019-07-16 09:24:13 +00:00
|
|
|
_init() {
|
|
|
|
super._init();
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._indicator = this._addIndicator();
|
2017-12-19 12:41:14 +00:00
|
|
|
this._indicator.icon_name = 'thunderbolt-symbolic';
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._client = new Client();
|
|
|
|
this._client.connect('probing-changed', this._onProbing.bind(this));
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._robot = new AuthRobot(this._client);
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._robot.connect('enroll-device', this._onEnrollDevice.bind(this));
|
|
|
|
this._robot.connect('enroll-failed', this._onEnrollFailed.bind(this));
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
Main.sessionMode.connect('updated', this._sync.bind(this));
|
2017-12-19 12:41:14 +00:00
|
|
|
this._sync();
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._source = null;
|
2019-01-17 14:22:14 +00:00
|
|
|
this._perm = null;
|
2019-12-19 19:50:37 +00:00
|
|
|
this._createPermission();
|
|
|
|
}
|
2019-01-17 14:22:14 +00:00
|
|
|
|
2019-12-19 19:50:37 +00:00
|
|
|
async _createPermission() {
|
|
|
|
try {
|
|
|
|
this._perm = await Polkit.Permission.new('org.freedesktop.bolt.enroll', null, null);
|
|
|
|
} catch (e) {
|
2022-02-07 14:14:06 +00:00
|
|
|
log(`Failed to get PolKit permission: ${e}`);
|
2019-12-19 19:50:37 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2017-12-19 12:41:14 +00:00
|
|
|
this._robot.close();
|
2019-01-29 20:18:46 +00:00
|
|
|
this._client.close();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_ensureSource() {
|
2017-12-19 12:41:14 +00:00
|
|
|
if (!this._source) {
|
|
|
|
this._source = new MessageTray.Source(_("Thunderbolt"),
|
|
|
|
'thunderbolt-symbolic');
|
2019-08-19 20:20:35 +00:00
|
|
|
this._source.connect('destroy', () => (this._source = null));
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
Main.messageTray.add(this._source);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._source;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_notify(title, body) {
|
2017-12-19 12:41:14 +00:00
|
|
|
if (this._notification)
|
|
|
|
this._notification.destroy();
|
|
|
|
|
|
|
|
let source = this._ensureSource();
|
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
this._notification = new MessageTray.Notification(source, title, body);
|
|
|
|
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
2017-10-31 00:38:18 +00:00
|
|
|
this._notification.connect('destroy', () => {
|
2017-12-19 12:41:14 +00:00
|
|
|
this._notification = null;
|
|
|
|
});
|
|
|
|
this._notification.connect('activated', () => {
|
|
|
|
let app = Shell.AppSystem.get_default().lookup_app('gnome-thunderbolt-panel.desktop');
|
|
|
|
if (app)
|
|
|
|
app.activate();
|
|
|
|
});
|
2019-05-13 21:32:31 +00:00
|
|
|
this._source.showNotification(this._notification);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* Session callbacks */
|
2017-10-31 00:03:21 +00:00
|
|
|
_sync() {
|
2017-12-19 12:41:14 +00:00
|
|
|
let active = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
|
2019-01-29 20:18:46 +00:00
|
|
|
this._indicator.visible = active && this._client.probing;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* Bolt.Client callbacks */
|
2017-10-31 00:03:21 +00:00
|
|
|
_onProbing(cli, probing) {
|
2019-01-29 20:18:46 +00:00
|
|
|
if (probing)
|
|
|
|
this._indicator.icon_name = 'thunderbolt-acquiring-symbolic';
|
|
|
|
else
|
|
|
|
this._indicator.icon_name = 'thunderbolt-symbolic';
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
this._sync();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
|
|
|
/* AuthRobot callbacks */
|
2017-10-31 00:03:21 +00:00
|
|
|
_onEnrollDevice(obj, device, policy) {
|
2019-01-17 14:22:14 +00:00
|
|
|
/* only authorize new devices when in an unlocked user session */
|
2019-01-29 20:18:46 +00:00
|
|
|
let unlocked = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
|
2019-01-17 14:22:14 +00:00
|
|
|
/* and if we have the permission to do so, otherwise we trigger a PolKit dialog */
|
|
|
|
let allowed = this._perm && this._perm.allowed;
|
|
|
|
|
|
|
|
let auth = unlocked && allowed;
|
2019-01-29 20:18:46 +00:00
|
|
|
policy[0] = auth;
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2022-02-07 14:14:06 +00:00
|
|
|
log(`thunderbolt: [${device.Name}] auto enrollment: ${auth ? 'yes' : 'no'} (allowed: ${allowed ? 'yes' : 'no'})`);
|
2019-01-17 14:22:14 +00:00
|
|
|
|
2019-01-29 20:18:46 +00:00
|
|
|
if (auth)
|
|
|
|
return; /* we are done */
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2019-01-17 14:22:14 +00:00
|
|
|
if (!unlocked) {
|
2019-01-29 20:18:46 +00:00
|
|
|
const title = _("Unknown Thunderbolt device");
|
|
|
|
const body = _("New device has been detected while you were away. Please disconnect and reconnect the device to start using it.");
|
|
|
|
this._notify(title, body);
|
2019-01-17 14:22:14 +00:00
|
|
|
} else {
|
|
|
|
const title = _("Unauthorized Thunderbolt device");
|
2019-01-29 20:18:46 +00:00
|
|
|
const body = _("New device has been detected and needs to be authorized by an administrator.");
|
|
|
|
this._notify(title, body);
|
2019-01-17 14:22:14 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-12-19 12:41:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onEnrollFailed(obj, device, error) {
|
2019-01-29 20:18:46 +00:00
|
|
|
const title = _("Thunderbolt authorization error");
|
|
|
|
const body = _("Could not authorize the Thunderbolt device: %s").format(error.message);
|
|
|
|
this._notify(title, body);
|
2017-12-19 12:41:14 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|