201 lines
7.0 KiB
JavaScript
201 lines
7.0 KiB
JavaScript
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
import BluetoothController from "./bluetooth.js";
|
|
import Settings from "./settings.js";
|
|
import { PopupBluetoothDeviceMenuItem } from "./ui.js";
|
|
import { Logger } from "./utils.js";
|
|
import GLib from "gi://GLib";
|
|
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
|
|
import * as Main from "resource:///org/gnome/shell/ui/main.js";
|
|
import * as PopupMenu from "resource:///org/gnome/shell/ui/popupMenu.js";
|
|
class BluetoothQuickConnect extends Extension {
|
|
_settings;
|
|
_logger;
|
|
_controller;
|
|
_menu;
|
|
_proxy;
|
|
_items = {};
|
|
_signals = [];
|
|
_idleMonitorId = null;
|
|
enable() {
|
|
this._settings = new Settings(this);
|
|
this._logger = new Logger(this._settings);
|
|
this._controller = new BluetoothController();
|
|
this._logger.log("Enabling extension");
|
|
this._queueModify();
|
|
this._menu = new PopupMenu.PopupMenuSection();
|
|
this._items = {};
|
|
this._controller.enable();
|
|
this._refresh();
|
|
this._connectControllerSignals();
|
|
this._connectIdleMonitor();
|
|
this._connectMenuSignals();
|
|
}
|
|
disable() {
|
|
this._logger.log("Disabling extension");
|
|
this._removeDevicesFromMenu();
|
|
this._disconnectIdleMonitor();
|
|
this._settings = null;
|
|
this._logger = null;
|
|
this._controller?.destroy();
|
|
this._controller = null;
|
|
this._menu = null;
|
|
this._disconnectSignals();
|
|
}
|
|
_queueModify() {
|
|
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
|
|
if (!Main.panel.statusArea.quickSettings._bluetooth) {
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
const btIndicator = Main.panel.statusArea.quickSettings._bluetooth;
|
|
const bluetoothToggle = btIndicator.quickSettingsItems[0];
|
|
bluetoothToggle._updateDeviceVisibility = () => {
|
|
bluetoothToggle._deviceSection.actor.visible = false;
|
|
bluetoothToggle._placeholderItem.actor.visible = false;
|
|
};
|
|
bluetoothToggle._updateDeviceVisibility();
|
|
this._proxy = bluetoothToggle._client._proxy;
|
|
bluetoothToggle.menu.addMenuItem(this._menu, 0);
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
}
|
|
_connectMenuSignals() {
|
|
this._connectSignal(this._menu, "open-state-changed", (_menu, isOpen) => {
|
|
this._logger.log(`Menu toggled: ${isOpen}`);
|
|
if (isOpen) {
|
|
this._disconnectIdleMonitor();
|
|
} else {
|
|
this._connectIdleMonitor();
|
|
}
|
|
if (isOpen && this._settings.isAutoPowerOnEnabled() && this._proxy.BluetoothAirplaneMode) {
|
|
this._logger.log("Disabling airplane mode");
|
|
this._proxy.BluetoothAirplaneMode = false;
|
|
}
|
|
});
|
|
}
|
|
_connectControllerSignals() {
|
|
this._logger.log("Connecting bluetooth controller signals");
|
|
this._connectSignal(this._controller, "default-adapter-changed", (_ctrl) => {
|
|
this._logger.log("Default adapter changed event");
|
|
this._refresh();
|
|
});
|
|
this._connectSignal(this._controller, "device-inserted", (_ctrl, device) => {
|
|
this._logger.log(`Device inserted event: ${device.alias || device.name}`);
|
|
if (device.paired) {
|
|
this._addMenuItem(device);
|
|
} else {
|
|
this._logger.log(`Device ${device.alias || device.name} not paired, ignoring`);
|
|
}
|
|
});
|
|
this._connectSignal(this._controller, "device-changed", (_ctrl, device) => {
|
|
this._logger.log(`Device changed event: ${device.alias || device.name}`);
|
|
if (device.paired) this._syncMenuItem(device);
|
|
else
|
|
this._logger.log(
|
|
`Skipping change event for unpaired device ${device.alias || device.name}`
|
|
);
|
|
});
|
|
this._connectSignal(this._controller, "device-deleted", (_ctrl, skipDevicePath) => {
|
|
this._logger.log("Device deleted event");
|
|
this._refresh(skipDevicePath);
|
|
});
|
|
}
|
|
_syncMenuItem(device) {
|
|
this._logger.log(`Synchronizing device menu item: ${device.alias || device.name}`);
|
|
const item = this._items[device.address || ""] || this._addMenuItem(device);
|
|
item.sync(device);
|
|
}
|
|
_addMenuItem(device) {
|
|
this._logger.log(`Adding device menu item: ${device.alias || device.name} ${device.address}`);
|
|
const menuItem = new PopupBluetoothDeviceMenuItem(
|
|
this._controller._client,
|
|
device,
|
|
this._logger,
|
|
{
|
|
showRefreshButton: this._settings.isShowRefreshButtonEnabled(),
|
|
closeMenuOnAction: !this._settings.isKeepMenuOnToggleEnabled(),
|
|
showBatteryValue: this._settings.isShowBatteryValueEnabled(),
|
|
showBatteryIcon: this._settings.isShowBatteryIconEnabled()
|
|
}
|
|
);
|
|
this._items[device.address || ""] = menuItem;
|
|
this._menu.addMenuItem(menuItem);
|
|
return menuItem;
|
|
}
|
|
_connectIdleMonitor() {
|
|
if (this._idleMonitorId) return;
|
|
this._logger.log("Connecting idle monitor");
|
|
this._idleMonitorId = GLib.timeout_add(
|
|
GLib.PRIORITY_DEFAULT,
|
|
this._settings.autoPowerOffCheckingInterval() * 1e3,
|
|
() => {
|
|
if (this._settings.isAutoPowerOffEnabled() && this._controller.getConnectedDevices().length === 0) {
|
|
this._proxy.BluetoothAirplaneMode = true;
|
|
}
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
_disconnectIdleMonitor() {
|
|
if (!this._idleMonitorId) return;
|
|
this._logger.log("Disconnecting idle monitor");
|
|
GLib.Source.remove(this._idleMonitorId);
|
|
this._idleMonitorId = null;
|
|
}
|
|
_connectSignal(subject, signal_name, method) {
|
|
if (!this._signals) this._signals = [];
|
|
const signal_id = subject.connect(signal_name, method);
|
|
this._signals.push({
|
|
subject,
|
|
signal_id
|
|
});
|
|
}
|
|
_disconnectSignals() {
|
|
if (!this._signals) return;
|
|
for (const signal of this._signals) {
|
|
signal.subject.disconnect(signal.signal_id);
|
|
}
|
|
this._signals = [];
|
|
}
|
|
_refresh(skipDevice = null) {
|
|
this._removeDevicesFromMenu();
|
|
this._addDevicesToMenu(skipDevice);
|
|
this._logger.log("Refreshing devices list");
|
|
}
|
|
_addDevicesToMenu(skipDevice = null) {
|
|
for (const device of this._controller.getDevices().sort((a, b) => {
|
|
if (!a.name) return 0;
|
|
return a.name.localeCompare(b.name || "");
|
|
})) {
|
|
if (device.paired && device.get_object_path() !== skipDevice) {
|
|
this._addMenuItem(device);
|
|
} else {
|
|
this._logger.log(`skipping adding device ${device.alias || device.name}`);
|
|
}
|
|
}
|
|
}
|
|
_removeDevicesFromMenu() {
|
|
for (const item of Object.values(this._items)) {
|
|
item.destroy();
|
|
}
|
|
this._items = {};
|
|
}
|
|
}
|
|
export {
|
|
BluetoothQuickConnect as default
|
|
};
|