[BRLTTY] btkey: your Linux console as a Bluetooth keyboard for a phone

Nicolas Pitre nico at fluxnic.net
Thu Sep 3 23:54:07 UTC 2026


On Thu, 3 Sep 2026, Kyle wrote:

> I already use bluetooth keyboards with my desktop that can pair with up to 2
> other devices,

Sure. What I ultimately wanted is a Bluetooth-capable tenkeyless 
mechanical keyboard. I didn't find any.

> I'm also not particularly fond of running something like this as root, 
> and I would think this should be able to be avoided by its running 
> user being added to the bluetooth group or similar.

On root, no group covers it, because what btkey needs is not "access to 
Bluetooth". It needed four things, and it now needs three:

- Grabbing the keyboards is EVIOCGRAB on /dev/input/event*, which is 
  root:input mode 0660. That one really is a group. It is also larger 
  than it looks: an evdev node sits below the VT layer, below X and 
  Wayland, and below any notion of focus, so anything a member of input 
  runs can read every key on the machine, including a root password 
  typed at a sudo prompt on another console. Narrower than root in what 
  it permits, wider in when it applies, since it is permanent and covers 
  every process you run.

- The HID profile's channels are L2CAP PSM 17 and 19. Below 0x1000, so 
  binding them wants CAP_NET_BIND_SERVICE, which no group grants. But 
  btkey need not be the one to bind them: bluetoothd will, for any 
  client that registers a profile with a PSM, and it does that for an 
  unprivileged caller. I registered both PSMs as my ordinary uid against 
  a bluetoothd started without its input plugin, and it bound both.

- Running that bluetoothd, and stopping the system one to make room for 
  it, is root. This is the expensive choice and it is deliberate: the 
  alternative removes the Bluetooth HID host role from the machine 
  permanently, so your own Bluetooth keyboard and mouse stop working. 
  There is a supported way to do it that way, btkey-system-bluetoothd, 
  which turns it into a one-time setup.

- Writing the class of device, which only --audio needs, goes through a 
  raw HCI socket. An ordinary user may read from the controller but not 
  command it: EPERM without CAP_NET_RAW.

The console was the fourth and is gone as of 0.1.7. btkey now holds its 
own /dev/ttyN rather than root's /dev/tty0, and switching consoles from 
it runs on the same permission chvt has always had.

On attaching capabilities: I looked, and it is the wrong tool twice 
over. File capabilities do not apply to scripts, and btkey's launcher is 
Python, so setcap on it is inert exactly as setuid on a script is; 
setcap on /usr/bin/python3 would hand the capability to every Python 
program on the machine. And item by item they do not fit anyway. The 
only capability that opens a root:input node for a non-member is 
CAP_DAC_OVERRIDE, which bypasses every file permission check there is. 
/var/lib/bluetooth is drwx------ root root, so the pairing keys need 
root or that same capability. And stopping a system unit is polkit 
rather than a capability: the rule here is auth_admin_keep, a password 
prompt, which no capability changes.

What is reachable is your underlying point, through configuration rather 
than setcap: usermod -aG input, the one-time bluetoothd setup, the PSMs 
taken from bluetoothd instead of bound directly, and no --audio. After 
that btkey runs as you, with no sudo, ever. It needs code that does not 
exist yet for the PSM part.

Whether that is an improvement is a real question. It trades one 
deliberate sudo btkey, gone when the program exits, for two permanent 
changes to the machine: the HID host role removed for good, and an 
account whose every process can read every keystroke. I would keep sudo 
btkey as the default and document the other path. All four items are 
written up in docs/DESIGN.md under "Why it runs as root" if you want the 
detail.


Nicolas


More information about the BRLTTY mailing list