Skip to content

About

Run a command when a Bluetooth device connects or disconnects — with a cancellable countdown before it happens.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

bt-trigger

Run a command when a Bluetooth device connects or disconnects — with a way to say no.

I wanted one thing: pick up the Xbox controller, and the desk turns into a console. Monitors off, TV on, Steam in Big Picture. The obvious approach — a udev rule on the input device — turned out to be the wrong shape, because the interesting part is not detecting the controller. It is everything around it: the controller also connects when I press the Guide button at my desk to check the battery, and I would rather not have both monitors go black mid-sentence.

So bt-trigger listens to BlueZ on the system bus and, before it runs anything, can put a notification with a Cancel button and a countdown on your screen.

Python 3 and PyGObject. No daemon of its own to keep alive, no polling, no bluetoothctl scraping.

What it does

  • Fires on connect, disconnect, or both, per rule.
  • Matches devices by MAC address or by name pattern (*Controller*).
  • Runs any shell command, with the device in the environment.
  • Optional countdown with a Cancel button before the command runs.
  • when / unless conditions — shell commands that gate a rule, so it only fires when the TV is actually plugged in, or not when the mode is already on.
  • grace for disconnects — fire only if the device is still gone after N seconds. Bluetooth drops for a moment all the time, and a controller that fell asleep is not a controller that left.
  • A global off switch (bt-trigger disable) that survives restarts.
  • Edge-triggered: only the transition fires, never the state. A device already connected when the daemon starts is remembered, not acted on.

Install

Needs python-gobject (Arch), python3-gi (Debian/Ubuntu), python3-gobject (Fedora) — plus a running bluetooth.service.

git clone https://github.com/maTzko13/bt-trigger.git
cd bt-trigger

install -Dm755 bt-trigger ~/.local/bin/bt-trigger
install -Dm644 bt-trigger.conf.example ~/.config/bt-trigger/bt-trigger.conf

bt-trigger list                 # your paired devices and their addresses
$EDITOR ~/.config/bt-trigger/bt-trigger.conf
bt-trigger dry-run couch-mode   # show the countdown, print the command, run nothing

Then install it as a user service — see examples/systemd.

Configuration

INI. One [rule:NAME] per thing you want to happen.

[rule:couch-mode]
device  = AA:BB:CC:11:22:33
event   = connect
confirm = 15
command = ~/.local/bin/tv-game-mode on
when    = ~/.local/bin/tv-game-mode available
unless  = ~/.local/bin/tv-game-mode status --quiet
Key Meaning
device MAC address, name pattern (*Controller*, case insensitive) or *. Comma separated for several.
event connect, disconnect or both.
command Run through /bin/sh. Gets $BT_RULE, $BT_EVENT, $BT_DEVICE (MAC), $BT_DEVICE_NAME.
confirm Seconds of cancellable countdown. Omit or 0 to run immediately.
confirm_title, confirm_body Notification text. Placeholders: {name} {mac} {rule} {event} {seconds}.
confirm_cancel_label, confirm_now_label, confirm_icon Notification looks.
confirm_fallback No notification daemon: skip (default) or run.
when Shell command that must succeed for the rule to fire.
unless Shell command that, if it succeeds, skips the rule.
grace Disconnect rules: fire only if still gone after N seconds. A reconnect cancels it.
cooldown Ignore repeats of this rule for N seconds. Default 5.
enabled false parks a rule without deleting it.

bt-trigger.conf.example carries more rules than you want — headphones, lock-when-away, any-gamepad — all switched off. Delete what you do not need.

Usage

bt-trigger [-c FILE] [-v] COMMAND [RULE]
Command Effect
daemon Watch and act. The default, and what the unit runs.
list Every device BlueZ knows: address, name, connected, paired, icon.
monitor Print connect/disconnect events as they happen, act on nothing. Use this to find out what your device is actually called.
status Config path, rules, armed or not.
test RULE Run one rule now, countdown included.
dry-run RULE Same, but print the command instead of running it.
enable / disable / toggle Global off switch, persists across restarts.
armed Exit 0 when armed. For a status bar or menu checkmark.

systemctl --user reload bt-trigger (or kill -HUP) re-reads the config without losing what the daemon knows about your devices.

Design notes

Four decisions that are not obvious from the outside, and that you may want to disagree with:

The countdown does not ride on the notification's timeout. It has its own timer. When a notification daemon decides to close a notification is its own business — GNOME, mako, swaync and quickshell all behave differently, and some ignore expire_timeout entirely. The command fires after exactly the seconds you configured.

Dismissing the notification cancels; letting it expire does not. From NotificationClosed, only reason 2 (dismissed by the user) counts as a cancellation. Reason 1 (expired) is what happens when you ignore it, which is consent. Reason 3 is us closing it ourselves.

If the notification cannot be posted, the command does not run. That is confirm_fallback = skip, the default. A countdown nobody can see is not a countdown, and asking for confirmation and then proceeding when nobody could have answered is worse than not asking. Set confirm_fallback = run on headless machines where you meant it.

Only edges, and the starting state is read, not acted on. At startup the daemon asks BlueZ GetManagedObjects and records what is already connected. Without that, every login with the controller still switched on would fire every connect rule.

Debugging

bt-trigger monitor              # does BlueZ see the device at all?
bt-trigger -v daemon            # verbose, in the foreground
journalctl --user -u bt-trigger -f

Nothing happens when the device connects:

  • bt-trigger monitor shows nothing → BlueZ is not reporting it. Some dongles connect as plain HID without a BlueZ device object.
  • It shows the event but no rule fires → check device against the name in bt-trigger list, and remember that when/unless can veto.
  • The rule fires but the command does nothing → it runs with the daemon's environment. Test it with systemd-run --user --pty sh -c 'your command'.

The example that started this

examples/couch-mode/ holds the actual script running on my machine: displays, audio and Steam, plus the four things that cost me time (why hyprctl cannot disable an output, why the way back must not need a keyboard, why gamemoderun must not wrap Steam, and why the HDMI audio port has to be found by name).

Licence

MIT. See LICENSE.

About

Run a command when a Bluetooth device connects or disconnects — with a cancellable countdown before it happens.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages