status/bluetooth: Split out BtClient object

The new class abstracts away the nitty-gritty of bluetooth- and
airplane-mode handling, and exposes just what the UI needs.

This will become more important with quick settings, where there's
a stronger separation between top bar icon and quick toggle.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2391>
This commit is contained in:
Florian Müllner 2022-07-28 15:00:45 +02:00 committed by Marge Bot
parent 463757569a
commit fcd08fae94

View File

@ -17,28 +17,42 @@ const rfkillManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(RfkillManagerInterfa
const HAD_BLUETOOTH_DEVICES_SETUP = 'had-bluetooth-devices-setup'; const HAD_BLUETOOTH_DEVICES_SETUP = 'had-bluetooth-devices-setup';
var Indicator = GObject.registerClass( const BtClient = GObject.registerClass({
class Indicator extends PanelMenu.SystemIndicator { Properties: {
'available': GObject.ParamSpec.boolean('available', '', '',
GObject.ParamFlags.READABLE,
false),
'active': GObject.ParamSpec.boolean('active', '', '',
GObject.ParamFlags.READABLE,
false),
},
Signals: {
'devices-changed': {},
},
}, class BtClient extends GObject.Object {
_init() { _init() {
super._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._hadSetupDevices = global.settings.get_boolean(HAD_BLUETOOTH_DEVICES_SETUP);
this._client = new GnomeBluetooth.Client(); this._client = new GnomeBluetooth.Client();
this._client.connect('notify::default-adapter-powered', () => {
this.notify('active');
this.notify('available');
});
this._client.connect('notify::default-adapter', () => { this._client.connect('notify::default-adapter', () => {
const newAdapter = this._client.default_adapter ?? null; const newAdapter = this._client.default_adapter ?? null;
if (newAdapter && this._adapter) if (newAdapter && this._adapter)
this._setHadSetupDevices(this._getDeviceInfos().length > 0); this._setHadSetupDevices([...this._getDevices()].length > 0);
this._adapter = newAdapter; this._adapter = newAdapter;
this._deviceNotifyConnected.clear(); this._deviceNotifyConnected.clear();
this._sync(); this.emit('devices-changed');
this.notify('active');
this.notify('available');
}); });
this._client.connect('notify::default-adapter-powered', this._sync.bind(this));
this._proxy = new Gio.DBusProxy({ this._proxy = new Gio.DBusProxy({
g_connection: Gio.DBus.session, g_connection: Gio.DBus.session,
@ -49,28 +63,11 @@ class Indicator extends PanelMenu.SystemIndicator {
}); });
this._proxy.connect('g-properties-changed', (p, properties) => { this._proxy.connect('g-properties-changed', (p, properties) => {
if ('BluetoothHardwareAirplaneMode' in properties.unpack()) if ('BluetoothHardwareAirplaneMode' in properties.unpack())
this._sync(); this.notify('available');
}); });
this._proxy.init_async(GLib.PRIORITY_DEFAULT, null) this._proxy.init_async(GLib.PRIORITY_DEFAULT, null)
.catch(e => console.error(e.message)); .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._adapter = null;
this._deviceNotifyConnected = new Set(); this._deviceNotifyConnected = new Set();
@ -81,14 +78,51 @@ class Indicator extends PanelMenu.SystemIndicator {
this._client.connect('device-removed', (c, path) => { this._client.connect('device-removed', (c, path) => {
this._deviceNotifyConnected.delete(path); this._deviceNotifyConnected.delete(path);
this._queueSync.bind(this); this.emit('devices-changed');
}); });
this._client.connect('device-added', (c, device) => { this._client.connect('device-added', (c, device) => {
this._connectDeviceNotify(device); this._connectDeviceNotify(device);
this._sync(); this.emit('devices-changed');
});
}
get available() {
// If there were set up devices, assume there is an adapter
// that can be powered on as long as we're not hard blocked
return this._hadSetupDevices
? !this._proxy.BluetoothHardwareAirplaneMode
: this.active;
}
get active() {
return this._client.default_adapter_powered;
}
toggleActive() {
this._proxy.BluetoothAirplaneMode = this.active;
if (!this._client.default_adapter_powered)
this._client.default_adapter_powered = true;
}
*getDevices() {
const deviceStore = this._client.get_devices();
for (let i = 0; i < deviceStore.get_n_items(); i++) {
const device = deviceStore.get_item(i);
if (device.paired || device.trusted)
yield device;
}
}
_queueDevicesChanged() {
if (this._devicesChangedId)
return;
this._devicesChangedId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
delete this._devicesChangedId;
this.emit('devices-changed');
return GLib.SOURCE_REMOVE;
}); });
Main.sessionMode.connect('updated', this._sync.bind(this));
this._sync();
} }
_setHadSetupDevices(value) { _setHadSetupDevices(value) {
@ -106,44 +140,46 @@ class Indicator extends PanelMenu.SystemIndicator {
if (this._deviceNotifyConnected.has(path)) if (this._deviceNotifyConnected.has(path))
return; return;
device.connect('notify::alias', this._queueSync.bind(this)); device.connect('notify::alias', () => this._queueDevicesChanged());
device.connect('notify::paired', this._queueSync.bind(this)); device.connect('notify::paired', () => this._queueDevicesChanged());
device.connect('notify::trusted', this._queueSync.bind(this)); device.connect('notify::trusted', () => this._queueDevicesChanged());
device.connect('notify::connected', this._queueSync.bind(this)); device.connect('notify::connected', () => this._queueDevicesChanged());
this._deviceNotifyConnected.add(path); this._deviceNotifyConnected.add(path);
} }
});
_getDeviceInfos() { var Indicator = GObject.registerClass(
const deviceStore = this._client.get_devices(); class Indicator extends PanelMenu.SystemIndicator {
let deviceInfos = []; _init() {
super._init();
for (let i = 0; i < deviceStore.get_n_items(); i++) { this._indicator = this._addIndicator();
const device = deviceStore.get_item(i); this._indicator.icon_name = 'bluetooth-active-symbolic';
if (device.paired || device.trusted) { this._client = new BtClient();
deviceInfos.push({ this._client.connectObject(
connected: device.connected, 'notify::active', () => this._sync(),
name: device.alias, 'devices-changed', () => this._sync(), this);
});
}
}
return deviceInfos; this._item = new PopupMenu.PopupSubMenuMenuItem(_('Bluetooth'), true);
} this._client.bind_property('available',
this._item, 'visible',
GObject.BindingFlags.SYNC_CREATE);
_queueSync() { this._toggleItem = new PopupMenu.PopupMenuItem('');
if (this._syncId) this._toggleItem.connect('activate', () => this._client.toggleActive());
return; this._item.menu.addMenuItem(this._toggleItem);
this._syncId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
this._syncId = 0; this._item.menu.addSettingsAction(_('Bluetooth Settings'), 'gnome-bluetooth-panel.desktop');
this._sync(); this.menu.addMenuItem(this._item);
return GLib.SOURCE_REMOVE;
}); Main.sessionMode.connect('updated', this._sync.bind(this));
this._sync();
} }
_sync() { _sync() {
const devices = this._getDeviceInfos(); const devices = [...this._client.getDevices()];
const connectedDevices = devices.filter(dev => dev.connected); const connectedDevices = devices.filter(dev => dev.connected);
const nConnectedDevices = connectedDevices.length; const nConnectedDevices = connectedDevices.length;
@ -152,28 +188,19 @@ class Indicator extends PanelMenu.SystemIndicator {
this.menu.setSensitive(sensitive); this.menu.setSensitive(sensitive);
this._indicator.visible = nConnectedDevices > 0; this._indicator.visible = nConnectedDevices > 0;
const adapterPowered = this._client.default_adapter_powered; this._item.icon.icon_name = this._client.active
// 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'; ? 'bluetooth-active-symbolic' : 'bluetooth-disabled-symbolic';
if (nConnectedDevices > 1) if (nConnectedDevices > 1)
/* Translators: this is the number of connected bluetooth devices */ /* Translators: this is the number of connected bluetooth devices */
this._item.label.text = ngettext('%d Connected', '%d Connected', nConnectedDevices).format(nConnectedDevices); this._item.label.text = ngettext('%d Connected', '%d Connected', nConnectedDevices).format(nConnectedDevices);
else if (nConnectedDevices === 1) else if (nConnectedDevices === 1)
this._item.label.text = connectedDevices[0].name; this._item.label.text = connectedDevices[0].alias;
else if (adapterPowered) else if (this._client.active)
this._item.label.text = _('Bluetooth On'); this._item.label.text = _('Bluetooth On');
else else
this._item.label.text = _('Bluetooth Off'); this._item.label.text = _('Bluetooth Off');
this._toggleItem.label.text = adapterPowered ? _('Turn Off') : _('Turn On'); this._toggleItem.label.text = this._client.active ? _('Turn Off') : _('Turn On');
} }
}); });