// Copyright 2018 Bartosz Jaroszewski // SPDX-License-Identifier: GPL-2.0-or-later // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . const Main = imports.ui.main; const GLib = imports.gi.GLib; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const UiExtension = Me.imports.ui; const Bluetooth = Me.imports.bluetooth; const Utils = Me.imports.utils; const Settings = Me.imports.settings.Settings; class BluetoothQuickConnect { constructor(bluetooth, settings) { this._logger = new Utils.Logger(settings); this._logger.info('Initializing extension'); this._menu = bluetooth._item.menu; this._proxy = bluetooth._proxy; this._controller = new Bluetooth.BluetoothController(); this._settings = settings this._items = {}; } enable() { this._logger.info('Enabling extension'); this._controller.enable(); this._refresh(); this._connectControllerSignals(); this._connectIdleMonitor(); this._connectMenuSignals(); } _connectMenuSignals() { this._connectSignal(this._menu, 'open-state-changed', (menu, isOpen) => { this._logger.info(`Menu toggled: ${isOpen}`); if (isOpen) this._disconnectIdleMonitor() else this._connectIdleMonitor(); if (isOpen && this._settings.isAutoPowerOnEnabled() && this._proxy.BluetoothAirplaneMode) { this._logger.info('Disabling airplane mode'); this._proxy.BluetoothAirplaneMode = false; } }); } disable() { this._logger.info('Disabling extension'); this._destroy(); } test() { try { this._logger.info('Testing bluetoothctl'); GLib.spawn_command_line_sync("bluetoothctl --version"); this._logger.info('Test succeeded'); } catch (error) { Main.notifyError(_('Bluetooth quick connect'), _(`Error trying to execute "bluetoothctl"`)); this._logger.info('Test failed'); } } _connectControllerSignals() { this._logger.info('Connecting bluetooth controller signals'); this._connectSignal(this._controller, 'device-inserted', (ctrl, device) => { this._logger.info(`Device inserted event: ${device.name}`); this._addMenuItem(device); }); this._connectSignal(this._controller, 'device-changed', (ctrl, device) => { this._logger.info(`Device changed event: ${device.name}`); if (device.isDefault) this._refresh(); else this._syncMenuItem(device); }); this._connectSignal(this._controller, 'device-deleted', () => { this._logger.info(`Device deleted event`); this._refresh(); }); this._connectSignal(Main.sessionMode, 'updated', () => { this._refresh() }); } _syncMenuItem(device) { this._logger.info(`Synchronizing device menu item: ${device.name}`); let item = this._items[device.mac] || this._addMenuItem(device); item.sync(device); } _addMenuItem(device) { this._logger.info(`Adding device menu item: ${device.name}`); let menuItem = new UiExtension.PopupBluetoothDeviceMenuItem( device, { showRefreshButton: this._settings.isShowRefreshButtonEnabled(), closeMenuOnAction: !this._settings.isKeepMenuOnToggleEnabled() } ); this._items[device.mac] = menuItem; this._menu.addMenuItem(menuItem, 1); return menuItem; } _connectIdleMonitor() { if (this._idleMonitorId) return; this._logger.info('Connecting idle monitor'); this._idleMonitorId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._settings.autoPowerOffCheckingInterval() * 1000, () => { if (this._settings.isAutoPowerOffEnabled() && this._controller.getConnectedDevices().length === 0) this._proxy.BluetoothAirplaneMode = true; return true; }); } _disconnectIdleMonitor() { if (!this._idleMonitorId) return; this._logger.info('Disconnecting idle monitor'); GLib.Source.remove(this._idleMonitorId); this._idleMonitorId = null; } _connectSignal(subject, signal_name, method) { let signal_id = subject.connect(signal_name, method); this._signals.push({ subject: subject, signal_id: signal_id }); } _refresh() { this._removeDevicesFromMenu(); this._addDevicesToMenu(); this._logger.info('Refreshing devices list'); } _addDevicesToMenu() { this._controller.getDevices().forEach((device) => { this._addMenuItem(device); }); } _removeDevicesFromMenu() { Object.values(this._items).forEach((item) => { item.destroy(); }); this._items = {}; } _destroy() { this._disconnectSignals(); this._removeDevicesFromMenu(); this._disconnectIdleMonitor(); if (this._controller) this._controller.destroy(); } } Utils.addSignalsHelperMethods(BluetoothQuickConnect.prototype); let bluetoothQuickConnect = null; function init() { let bluetooth = Main.panel.statusArea.aggregateMenu._bluetooth; let settings = new Settings(); bluetoothQuickConnect = new BluetoothQuickConnect(bluetooth, settings); } function enable() { bluetoothQuickConnect.test(); bluetoothQuickConnect.enable(); } function disable() { bluetoothQuickConnect.disable(); }