Commit 45ad2cbdcba9

Vincent Demeester <vincent@sbr.pm>
2026-02-13 22:58:18
feat(laptop-keyboard-inhibit): add Bluetooth keyboard monitoring
Replace udev-based name matching (doesn't work for Bluetooth) with bluetooth-monitor daemon that polls bluetoothctl for connection status. - Polls every 5 seconds for BT keyboard connection state - Automatically finds MAC address by keyboard name - Disables internal keyboard when BT keyboard connects - Re-enables when BT keyboard disconnects - Fallback for USB keyboards via udev still works Configured for Eyelash Corne on okinawa.
1 parent f175240
Changed files (1)
modules
laptop-keyboard-inhibit
modules/laptop-keyboard-inhibit/default.nix
@@ -1,15 +1,16 @@
 # Automatically inhibit (disable) the laptop's internal keyboard when an
 # external keyboard is connected, and re-enable it on disconnect.
 #
-# Works with both USB and Bluetooth keyboards via udev rules that write
-# to the kernel's input device `inhibited` sysfs attribute (Linux 5.11+).
+# Uses bluetooth-monitor for Bluetooth keyboards (D-Bus events) and udev
+# rules for USB keyboards. Writes to the kernel's input device `inhibited`
+# sysfs attribute (Linux 5.11+).
 #
 # Usage:
 #   services.laptop-keyboard-inhibit = {
 #     enable = true;
-#     # Match by device name (Bluetooth keyboards, or any input device)
+#     # Match by device name (uses bluetooth-monitor for BT detection)
 #     keyboards = [ "Eyelash Corne" ];
-#     # Or match USB keyboards by vendor:product ID
+#     # Or match USB keyboards by vendor:product ID (uses udev)
 #     usbKeyboards = [ { vendor = "3297"; product = "1969"; } ];
 #   };
 {
@@ -104,11 +105,72 @@ in
   };
 
   config = mkIf cfg.enable {
-    services.udev.extraRules = concatStringsSep "\n" (
-      lib.filter (s: s != "") [
-        nameRules
-        usbRules
-      ]
-    );
+    # USB keyboard detection via udev
+    services.udev.extraRules = mkIf (usbRules != "") usbRules;
+
+    # Bluetooth keyboard detection via bluetooth-monitor
+    systemd.services.bluetooth-keyboard-monitor = mkIf (cfg.keyboards != [ ]) {
+      description = "Monitor Bluetooth keyboards and toggle internal keyboard";
+      after = [ "bluetooth.service" ];
+      requires = [ "bluetooth.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        RestartSec = 5;
+      };
+
+      script =
+        let
+          # Get Bluetooth MAC addresses for configured keyboard names
+          # User needs to pair keyboards first with bluetoothctl
+          getDeviceScript = pkgs.writeShellScript "get-bt-devices" ''
+            # Wait for bluetooth to be ready
+            sleep 2
+            
+            # Find MAC addresses for configured keyboard names
+            ${concatMapStringsSep "\n" (name: ''
+              MAC=$(bluetoothctl devices | grep -i "${name}" | awk '{print $2}' | head -1)
+              if [ -n "$MAC" ]; then
+                echo "$MAC"
+              fi
+            '') cfg.keyboards}
+          '';
+        in
+        ''
+          set -euo pipefail
+          
+          # Get MAC addresses of configured keyboards
+          MACS=$(${getDeviceScript})
+          
+          if [ -z "$MACS" ]; then
+            echo "No Bluetooth keyboards found. Make sure they are paired."
+            echo "Use: bluetoothctl pair <MAC>"
+            sleep 10
+            exit 1
+          fi
+          
+          # Monitor each keyboard
+          while IFS= read -r MAC; do
+            (
+              echo "Monitoring Bluetooth keyboard: $MAC"
+              while true; do
+                if bluetoothctl info "$MAC" 2>/dev/null | grep -q "Connected: yes"; then
+                  # Keyboard connected - disable internal keyboard
+                  ${toggleScript} inhibit
+                else
+                  # Keyboard disconnected - enable internal keyboard
+                  ${toggleScript} uninhibit
+                fi
+                sleep 5
+              done
+            ) &
+          done <<< "$MACS"
+          
+          # Wait for all background monitors
+          wait
+        '';
+    };
   };
 }