gnome-shell/js/ui/status/bluetooth.js
Florian Müllner 463757569a status/bluetooth: Use promise to initialize proxy
There's no good reason for waiting for the proxy to be initialized
to connect signals. In fact, connecting the signal beforehand
ensures that the handler is in place when the proxy fetches the
properties, so we don't have to call the handler explicitly.

That in turn allows us to rely on the signal parameters to only process
changed properties.

To achieve that, construct the proxy manually, and then initialize
it asynchronously in a Promise.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2391>
2022-08-01 12:33:35 +00:00

180 lines
6.3 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */
const { Gio, GLib, GnomeBluetooth, GObject } = imports.gi;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const { loadInterfaceXML } = imports.misc.fileUtils;
const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill';
const RfkillManagerInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Rfkill');
const rfkillManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(RfkillManagerInterface);
const HAD_BLUETOOTH_DEVICES_SETUP = 'had-bluetooth-devices-setup';
var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
_init() {
super._init();
this._indicator = this._addIndicator();
this._indicator.icon_name = 'bluetooth-active-symbolic';
this._hadSetupDevices = global.settings.get_boolean(HAD_BLUETOOTH_DEVICES_SETUP);
this._client = new GnomeBluetooth.Client();
this._client.connect('notify::default-adapter', () => {
const newAdapter = this._client.default_adapter ?? null;
if (newAdapter && this._adapter)
this._setHadSetupDevices(this._getDeviceInfos().length > 0);
this._adapter = newAdapter;
this._deviceNotifyConnected.clear();
this._sync();
});
this._client.connect('notify::default-adapter-powered', this._sync.bind(this));
this._proxy = new Gio.DBusProxy({
g_connection: Gio.DBus.session,
g_name: BUS_NAME,
g_object_path: OBJECT_PATH,
g_interface_name: rfkillManagerInfo.name,
g_interface_info: rfkillManagerInfo,
});
this._proxy.connect('g-properties-changed', (p, properties) => {
if ('BluetoothHardwareAirplaneMode' in properties.unpack())
this._sync();
});
this._proxy.init_async(GLib.PRIORITY_DEFAULT, null)
.catch(e => console.error(e.message));
this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true);
this._toggleItem = new PopupMenu.PopupMenuItem('');
this._toggleItem.connect('activate', () => {
if (!this._client.default_adapter_powered) {
this._proxy.BluetoothAirplaneMode = false;
this._client.default_adapter_powered = true;
} else {
this._proxy.BluetoothAirplaneMode = true;
}
});
this._item.menu.addMenuItem(this._toggleItem);
this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
this.menu.addMenuItem(this._item);
this._syncId = 0;
this._adapter = null;
this._deviceNotifyConnected = new Set();
const deviceStore = this._client.get_devices();
for (let i = 0; i < deviceStore.get_n_items(); i++)
this._connectDeviceNotify(deviceStore.get_item(i));
this._client.connect('device-removed', (c, path) => {
this._deviceNotifyConnected.delete(path);
this._queueSync.bind(this);
});
this._client.connect('device-added', (c, device) => {
this._connectDeviceNotify(device);
this._sync();
});
Main.sessionMode.connect('updated', this._sync.bind(this));
this._sync();
}
_setHadSetupDevices(value) {
if (this._hadSetupDevices === value)
return;
this._hadSetupDevices = value;
global.settings.set_boolean(
HAD_BLUETOOTH_DEVICES_SETUP, this._hadSetupDevices);
}
_connectDeviceNotify(device) {
const path = device.get_object_path();
if (this._deviceNotifyConnected.has(path))
return;
device.connect('notify::alias', this._queueSync.bind(this));
device.connect('notify::paired', this._queueSync.bind(this));
device.connect('notify::trusted', this._queueSync.bind(this));
device.connect('notify::connected', this._queueSync.bind(this));
this._deviceNotifyConnected.add(path);
}
_getDeviceInfos() {
const deviceStore = this._client.get_devices();
let deviceInfos = [];
for (let i = 0; i < deviceStore.get_n_items(); i++) {
const device = deviceStore.get_item(i);
if (device.paired || device.trusted) {
deviceInfos.push({
connected: device.connected,
name: device.alias,
});
}
}
return deviceInfos;
}
_queueSync() {
if (this._syncId)
return;
this._syncId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
this._syncId = 0;
this._sync();
return GLib.SOURCE_REMOVE;
});
}
_sync() {
const devices = this._getDeviceInfos();
const connectedDevices = devices.filter(dev => dev.connected);
const nConnectedDevices = connectedDevices.length;
let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
this.menu.setSensitive(sensitive);
this._indicator.visible = nConnectedDevices > 0;
const adapterPowered = this._client.default_adapter_powered;
// Remember if there were setup devices and show the menu
// if we've seen setup devices and we're not hard blocked
if (this._hadSetupDevices)
this._item.visible = !this._proxy.BluetoothHardwareAirplaneMode;
else
this._item.visible = adapterPowered;
this._item.icon.icon_name = adapterPowered
? 'bluetooth-active-symbolic' : 'bluetooth-disabled-symbolic';
if (nConnectedDevices > 1)
/* Translators: this is the number of connected bluetooth devices */
this._item.label.text = ngettext('%d Connected', '%d Connected', nConnectedDevices).format(nConnectedDevices);
else if (nConnectedDevices === 1)
this._item.label.text = connectedDevices[0].name;
else if (adapterPowered)
this._item.label.text = _('Bluetooth On');
else
this._item.label.text = _('Bluetooth Off');
this._toggleItem.label.text = adapterPowered ? _('Turn Off') : _('Turn On');
}
});