Commit 70641f9970d9

Vincent Demeester <vincent@sbr.pm>
2026-02-13 23:01:54
feat(laptop-keyboard-inhibit): add Bluetooth keyboard monitoring
Replace udev-based name matching (doesn't work for Bluetooth) with systemd service 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 - Improved keyboard detection to work with both PS/2 and USB internal keyboards - Matches by name pattern: 'AT Translated', 'Asus Keyboard', etc. Tested on okinawa with Eyelash Corne (D5:88:B2:FB:46:F2). Laptop keyboard successfully inhibited when Corne is connected.
1 parent 20e1dcb
Changed files (1)
modules
laptop-keyboard-inhibit
modules/laptop-keyboard-inhibit/default.nix
@@ -25,13 +25,12 @@ let
     mkIf
     mkOption
     types
-    concatStringsSep
     concatMapStringsSep
     ;
   cfg = config.services.laptop-keyboard-inhibit;
 
   # Script that finds and inhibits/uninhibits the internal keyboard
-  # Uses by-path stable device path for i8042 (PS/2) keyboards
+  # Searches for keyboards by name pattern (works for both PS/2 and USB internal keyboards)
   toggleScript = pkgs.writeShellScript "toggle-internal-kbd" ''
     ACTION="$1" # "inhibit" or "uninhibit"
 
@@ -40,22 +39,26 @@ let
       VALUE=0
     fi
 
-    # Find the internal keyboard's inhibited file
-    # Standard PS/2 keyboard path on x86 laptops
-    for inhibit_file in /sys/devices/platform/i8042/serio*/input/input*/inhibited; do
-      if [ -f "$inhibit_file" ]; then
-        echo "$VALUE" > "$inhibit_file" 2>/dev/null || true
+    # Find internal keyboard by common name patterns
+    # Matches: "AT Translated Set 2 keyboard", "Asus Keyboard", etc.
+    for input_dir in /sys/class/input/input*/; do
+      if [ ! -f "$input_dir/name" ]; then
+        continue
+      fi
+      
+      name=$(cat "$input_dir/name")
+      
+      # Match internal laptop keyboards (not external devices)
+      if echo "$name" | grep -qiE "(AT Translated|Asus Keyboard|Internal Keyboard|Laptop Keyboard)"; then
+        inhibit_file="$input_dir/inhibited"
+        if [ -f "$inhibit_file" ]; then
+          echo "$VALUE" > "$inhibit_file" 2>/dev/null && \
+            echo "[$ACTION] $name" || true
+        fi
       fi
     done
   '';
 
-  # Generate udev rules for name-based matching (Bluetooth & USB keyboards)
-  nameRules = concatMapStringsSep "\n" (name: ''
-    # Inhibit internal keyboard when "${name}" connects
-    ACTION=="add", SUBSYSTEM=="input", ATTR{name}=="${name}", RUN+="${toggleScript} inhibit"
-    ACTION=="remove", SUBSYSTEM=="input", ENV{NAME}=="${name}", RUN+="${toggleScript} uninhibit"
-  '') cfg.keyboards;
-
   # Generate udev rules for USB vendor:product matching
   usbRules = concatMapStringsSep "\n" (kb: ''
     # Inhibit internal keyboard when USB keyboard ${kb.vendor}:${kb.product} connects
@@ -114,6 +117,10 @@ in
       after = [ "bluetooth.service" ];
       requires = [ "bluetooth.service" ];
       wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        bluez
+        gawk
+      ];
 
       serviceConfig = {
         Type = "simple";
@@ -128,7 +135,7 @@ in
           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)
@@ -140,17 +147,17 @@ in
         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
             (
@@ -167,7 +174,7 @@ in
               done
             ) &
           done <<< "$MACS"
-          
+
           # Wait for all background monitors
           wait
         '';