Files
2026-04-30 00:57:24 +02:00

172 lines
5.9 KiB
TypeScript

import Gio from 'gi://Gio';
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 { BleMonitor } from './bleMonitor.js';
import { BleUnlockIndicator } from './quickSettingsMenu.js';
const SCREENSAVER_IFACE = `
<node>
<interface name="org.gnome.ScreenSaver">
<method name="SetActive">
<arg type="b" direction="in"/>
</method>
</interface>
</node>`;
export default class BleUnlockExtension extends Extension {
private _monitor: BleMonitor | null = null;
private _indicator: InstanceType<typeof BleUnlockIndicator> | null = null;
private _settings: Gio.Settings | null = null;
private _screenSaverProxy: Gio.DBusProxy | null = null;
private _unlockTimerId: number | null = null;
private _settingsChangedIds: number[] = [];
enable(): void {
this._settings = this.getSettings();
this._monitor = new BleMonitor();
// Wire lock/unlock hooks
this._monitor.onSignalBelowThreshold = () => {
console.debug('[BleUnlock] Locking screen');
this._setScreenSaverActive(true);
};
this._monitor.onSignalAboveThreshold = () => {
console.debug('[BleUnlock] Scheduling unlock');
if (this._unlockTimerId !== null) {
GLib.Source.remove(this._unlockTimerId);
this._unlockTimerId = null;
}
const UNLOCK_DELAY_SEC = 3;
this._unlockTimerId = GLib.timeout_add_seconds(
GLib.PRIORITY_DEFAULT,
UNLOCK_DELAY_SEC,
() => {
console.debug('[BleUnlock] Unlocking screen');
this._setScreenSaverActive(false);
this._unlockTimerId = null;
return GLib.SOURCE_REMOVE;
},
);
};
// Create ScreenSaver proxy
try {
this._screenSaverProxy = Gio.DBusProxy.new_sync(
Gio.DBus.session,
Gio.DBusProxyFlags.NONE,
Gio.DBusNodeInfo.new_for_xml(SCREENSAVER_IFACE).lookup_interface('org.gnome.ScreenSaver'),
'org.gnome.ScreenSaver',
'/org/gnome/ScreenSaver',
'org.gnome.ScreenSaver',
null,
);
} catch (e) {
console.error(`[BleUnlock] Failed to create ScreenSaver proxy: ${e}`);
}
// Create indicator and add to quick settings
this._indicator = new BleUnlockIndicator(
this._settings,
this._monitor,
() => this.openPreferences(),
this.uuid,
);
Main.panel.statusArea.quickSettings.addExternalIndicator(this._indicator as any);
// Start monitoring if enabled
if (this._settings.get_boolean('enabled')) {
this._startMonitoring();
}
// Watch settings changes
this._settingsChangedIds.push(
this._settings.connect('changed::enabled', () => {
if (this._settings!.get_boolean('enabled')) {
this._startMonitoring();
} else {
this._monitor?.stopMonitoring();
}
}),
);
this._settingsChangedIds.push(
this._settings.connect('changed::device-address', () => {
if (this._settings!.get_boolean('enabled')) {
this._monitor?.stopMonitoring();
this._startMonitoring();
}
}),
);
this._settingsChangedIds.push(
this._settings.connect('changed::poll-interval', () => {
if (this._settings!.get_boolean('enabled')) {
this._monitor?.stopMonitoring();
this._startMonitoring();
}
}),
);
}
/**
* disable() is NOT called on screen lock because this extension uses
* session-modes: ["user", "unlock-dialog"]. It is called when the extension
* is disabled, GNOME Shell exits, or the user logs out.
*
* Stops all timers, disconnects all signals, nulls all references, destroys all widgets.
*/
disable(): void {
if (this._unlockTimerId !== null) {
GLib.Source.remove(this._unlockTimerId);
this._unlockTimerId = null;
}
this._monitor?.stopMonitoring();
this._monitor = null;
this._indicator?.destroy();
this._indicator = null;
if (this._settings) {
for (const id of this._settingsChangedIds) {
this._settings.disconnect(id);
}
}
this._settingsChangedIds = [];
this._settings = null;
this._screenSaverProxy = null;
}
private _startMonitoring(): void {
if (!this._monitor || !this._settings) return;
const address = this._settings.get_string('device-address');
if (!address) return;
const config = {
rssiLockThreshold: this._settings.get_int('rssi-lock-threshold'),
rssiUnlockThreshold: this._settings.get_int('rssi-unlock-threshold'),
};
const pollInterval = this._settings.get_int('poll-interval');
this._monitor.startMonitoring(address, config, pollInterval);
}
private _setScreenSaverActive(active: boolean): void {
if (!this._screenSaverProxy) return;
try {
this._screenSaverProxy.call(
'SetActive',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new GLib.Variant('(b)', [active]) as any,
Gio.DBusCallFlags.NONE,
-1,
null,
null,
);
} catch (e) {
console.error(`[BleUnlock] SetActive(${active}) failed: ${e}`);
}
}
}