2025-05-09 09:48:24 +02:00

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
};