BLE Unlock
A GNOME Shell 49 extension that locks and unlocks your session based on the Bluetooth RSSI of a nearby device. When your phone (or any BT device) moves out of range the screen locks; when it comes back in range the screen unlocks after a short delay.
Requirements
- GNOME Shell 49
- BlueZ (the Linux Bluetooth stack)
- Node.js and npm (for building)
Build & Install
cd generated/
npm install # install TypeScript + type stubs
make # compile TypeScript → dist/
make install # pack extension + gnome-extensions install --force
Reload GNOME Shell after installing, then enable the extension:
gnome-extensions enable ble-unlock@blelock.github.io
Running Tests
make test # run all vitest tests
npm run test:unit # unit tests only (thresholds pure logic)
npm run test:integration # integration tests (state-machine)
Nested-Shell Testing (GNOME 49)
Start a nested GNOME Shell session to test the extension live without risking your main session:
dbus-run-session gnome-shell --devkit --wayland
Inside the nested session, enable the extension:
gnome-extensions enable ble-unlock@blelock.github.io
Run the GJS/Jasmine integration test inside the nested session:
gjs tests/gnome-shell/extension.test.js
GSettings Keys
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Whether BLE monitoring is active |
device-address |
string | "" |
MAC address of the BT device to monitor |
rssi-lock-threshold |
int | -75 |
Screen locks when RSSI falls below this value (dBm) |
rssi-unlock-threshold |
int | -65 |
Screen unlocks when RSSI rises above this value (dBm) |
poll-interval |
int | 3 |
How often to read the BLE RSSI (seconds) |
Read or write keys with gsettings:
gsettings set org.gnome.shell.extensions.bleunlock device-address 'AA:BB:CC:DD:EE:FF'
gsettings set org.gnome.shell.extensions.bleunlock enabled true
Architecture
generated/
├── thresholds.ts # Pure logic: classifySignal / shouldLock / shouldUnlock
├── bleMonitor.ts # GObject that polls BlueZ RSSI via Gio.DBusProxy
├── quickSettingsMenu.ts # Quick Settings toggle + device list submenu
├── extension.ts # Extension enable/disable; wires monitor → screen lock
├── prefs.ts # GTK4/Adwaita preferences window (separate process)
└── schemas/ # GSettings schema (5 keys)
Two-process boundary:
- Shell process (
extension.ts,bleMonitor.ts,quickSettingsMenu.ts): GI libs only — Clutter, St, Meta, Gio, GLib, GObject. No Gtk/Adw/Gdk. - Prefs process (
prefs.ts): GTK4 + Adwaita + Gio/GLib only. No Clutter/Meta/St/Shell.
Data flow:
BleMonitorpollsorg.bluez.Device1.RSSI(cached D-Bus property) every N seconds.- Each poll feeds into
thresholds.tspure functions to detect state transitions. - On transition,
BleMonitor.onSignalBelowThreshold()oronSignalAboveThreshold()fires. extension.tswires those hooks toorg.gnome.ScreenSaver.SetActive.- Unlock is delayed 3 seconds to avoid spurious unlocks on brief signal loss.
Debugging
Watch live extension logs:
journalctl -f -o cat /usr/bin/gnome-shell | grep BleUnlock
Inspect GSettings values:
gsettings list-recursively org.gnome.shell.extensions.bleunlock
Check BlueZ device RSSI manually:
dbus-send --system --print-reply \
--dest=org.bluez /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF \
org.freedesktop.DBus.Properties.Get \
string:org.bluez.Device1 string:RSSI