Linux – Script in udev rule doesn’t run

linuxudevusb

I'm running Ubuntu 9.10 (Karmic Koala) on a laptop and would like NumLock to automatically toggle depending on whether my USB keyboard is plugged in (numlock on) or unplugged (numlock off).

To accomplish this, I first installed the "numlockx" package. numlockx on and numlockx off works fine.

To hook into the device system, I thought I'd use udev. I have read "Writing udev rules", but I'm having trouble getting the udev rule to work.

First, here's an example of the dmesg output:

[20906.985102] usb 3-2: new low speed USB device using uhci_hcd and address 6
[20907.166403] usb 3-2: configuration #1 chosen from 1 choice
[20907.192904] input: Microsoft Natural® Ergonomic Keyboard 4000 as /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.0/input/input20
[20907.193100] microsoft 0003:045E:00DB.000B: input,hidraw1: USB HID v1.11 Keyboard [Microsoft Natural® Ergonomic Keyboard 4000] on usb-0000:00:1a.0-2/input0
[20907.217810] input: Microsoft Natural® Ergonomic Keyboard 4000 as /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21
[20907.217979] microsoft 0003:045E:00DB.000C: input,hidraw2: USB HID v1.11 Device [Microsoft Natural® Ergonomic Keyboard 4000] on usb-0000:00:1a.0-2/input1

I used udevadm info to gather the device info:

> udevadm info -a -p /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21

  looking at device '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21':
    KERNEL=="input21"
    SUBSYSTEM=="input"
    DRIVER==""
    ATTR{phys}=="usb-0000:00:1a.0-2/input1"
    ATTR{uniq}==""
    ATTR{modalias}=="input:b0003v045Ep00DBe0111-e0,1,2,3,4,14,k71,72,73,74,75,77,79,7A,7B,7C,7D,7E,7F,80,81,82,83,84,85,86,87,88,89,8A,8B,8C,8E,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B0,B1,B2,B3,B4,B5,B6,B8,B9,BA,BB,BC,BD,BE,BF,C0,C1,C2,CE,CF,D0,D1,D2,D5,D9,DB,DF,E2,E7,E8,E9,EA,EB,F0,100,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r6,a20,m4,lsfw"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1':
    KERNELS=="3-2:1.1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usbhid"
    ATTRS{bInterfaceNumber}=="01"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{bInterfaceClass}=="03"
    ATTRS{bInterfaceSubClass}=="00"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{modalias}=="usb:v045Ep00DBd0173dc00dsc00dp00ic03isc00ip00"
    ATTRS{supports_autosuspend}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb3/3-2':
    KERNELS=="3-2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="a0"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="532"
    ATTRS{idVendor}=="045e"
    ATTRS{idProduct}=="00db"
    ATTRS{bcdDevice}=="0173"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{speed}=="1.5"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="6"
    ATTRS{version}==" 2.00"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Microsoft"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb3':
    KERNELS=="usb3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{urbnum}=="127"
    ATTRS{idVendor}=="1d6b"
    ATTRS{idProduct}=="0001"
    ATTRS{bcdDevice}=="0206"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="1"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Linux 2.6.31-16-generic uhci_hcd"
    ATTRS{product}=="UHCI Host Controller"
    ATTRS{serial}=="0000:00:1a.0"
    ATTRS{authorized_default}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0':
    KERNELS=="0000:00:1a.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="uhci_hcd"
    ATTRS{vendor}=="0x8086"
    ATTRS{device}=="0x2937"
    ATTRS{subsystem_vendor}=="0x1558"
    ATTRS{subsystem_device}=="0x0860"
    ATTRS{class}=="0x0c0300"
    ATTRS{irq}=="16"
    ATTRS{local_cpus}=="ff"
    ATTRS{local_cpulist}=="0-7"
    ATTRS{modalias}=="pci:v00008086d00002937sv00001558sd00000860bc0Csc03i00"
    ATTRS{broken_parity_status}=="0"
    ATTRS{msi_bus}==""

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

So I created a file called /etc/udev/rules.d/usb-keyboard.rules:

# Turn on NumLock when keyboard is plugged in.
ACTION=="add", ATTRS{manufacturer}=="Microsoft", SUBSYSTEM=="input", RUN+="/usr/bin/numlockx on"

# Turn off NumLock when keyboard is unplugged.
ACTION=="remove", ATTRS{manufacturer}=="Microsoft", SUBSYSTEM=="input", RUN+="/usr/bin/numlockx off"

I have used udevadm test to verify that the rules are correct:

> udevadm test --action=add /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21
run_command: calling: test
udevadm_test: version 147

[...]
parse_file: reading '/etc/udev/rules.d/usb-keyboard.rules' as rules file
udev_rules_new: rules use 180864 bytes tokens (15072 * 12 bytes), 31614 bytes buffer
udev_rules_new: temporary index used 49760 bytes (2488 * 20 bytes)
udev_device_new_from_syspath: device 0x28d7d80 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21'
udev_rules_apply_to_event: RUN '/sbin/modprobe -b $env{MODALIAS}' /lib/udev/rules.d/80-drivers.rules:5
udev_rules_apply_to_event: RUN 'socket:@/org/freedesktop/hal/udev_event' /lib/udev/rules.d/90-hal.rules:2
udev_device_new_from_syspath: device 0x28d8560 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1'
udev_device_new_from_syspath: device 0x28d8708 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2'
udev_rules_apply_to_event: RUN '/usr/bin/numlockx on' /etc/udev/rules.d/usb-keyboard.rules:7
udevadm_test: UDEV_LOG=6
udevadm_test: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21
udevadm_test: PRODUCT=3/45e/db/111
udevadm_test: NAME="Microsoft Natural® Ergonomic Keyboard 4000"
udevadm_test: PHYS="usb-0000:00:1a.0-2/input1"
udevadm_test: UNIQ=""
udevadm_test: EV==10001f
udevadm_test: KEY==837fff 2c3027 bf004444 0 0 1 10f84 8a27c007 ff7f7bfa d9415fff febeffdf ffefffff ffffffff fffffffe
udevadm_test: REL==40
udevadm_test: ABS==1 0
udevadm_test: MSC==10
udevadm_test: MODALIAS=input:b0003v045Ep00DBe0111-e0,1,2,3,4,14,k71,72,73,74,75,77,79,7A,7B,7C,7D,7E,7F,80,81,82,83,84,85,86,87,88,89,8A,8B,8C,8E,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B0,B1,B2,B3,B4,B5,B6,B8,B9,BA,BB,BC,BD,BE,BF,C0,C1,C2,CE,CF,D0,D1,D2,D5,D9,DB,DF,E2,E7,E8,E9,EA,EB,F0,100,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r6,a20,m4,lsfw
udevadm_test: ACTION=add
udevadm_test: SUBSYSTEM=input
udevadm_test: run: '/sbin/modprobe -b input:b0003v045Ep00DBe0111-e0,1,2,3,4,14,k71,72,73,74,75,77,79,7A,7B,7C,7D,7E,7F,80,81,82,83,84,85,86,87,88,89,8A,8B,8C,8E,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B0,B1,B2,B3,B4,B5,B6,B8,B9,BA,BB,BC,BD,BE,BF,C0,C1,C2,CE,CF,D0,D1,D2,D5,D9,DB,DF,E2,E7,E8,E9,EA,EB,F0,100,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r6,a20,m4,lsfw'
udevadm_test: run: 'socket:@/org/freedesktop/hal/udev_event'
udevadm_test: run: '/usr/bin/numlockx on'

And here's the "remove" test:

> udevadm test --action=remove /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21

run_command: calling: test
udevadm_test: version 147

[...]
parse_file: reading '/etc/udev/rules.d/usb-keyboard.rules' as rules file
udev_rules_new: rules use 180864 bytes tokens (15072 * 12 bytes), 31614 bytes buffer
udev_rules_new: temporary index used 49760 bytes (2488 * 20 bytes)
udev_device_new_from_syspath: device 0xd2fd80 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21'
udev_rules_apply_to_event: RUN 'socket:@/org/freedesktop/hal/udev_event' /lib/udev/rules.d/90-hal.rules:2
udev_device_new_from_syspath: device 0xd2fff8 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1'
udev_device_new_from_syspath: device 0xd30690 has devpath '/devices/pci0000:00/0000:00:1a.0/usb3/3-2'
udev_rules_apply_to_event: RUN '/usr/bin/numlockx off' /etc/udev/rules.d/usb-keyboard.rules:10
udevadm_test: UDEV_LOG=6
udevadm_test: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2:1.1/input/input21
udevadm_test: PRODUCT=3/45e/db/111
udevadm_test: NAME="Microsoft Natural® Ergonomic Keyboard 4000"
udevadm_test: PHYS="usb-0000:00:1a.0-2/input1"
udevadm_test: UNIQ=""
udevadm_test: EV==10001f
udevadm_test: KEY==837fff 2c3027 bf004444 0 0 1 10f84 8a27c007 ff7f7bfa d9415fff febeffdf ffefffff ffffffff fffffffe
udevadm_test: REL==40
udevadm_test: ABS==1 0
udevadm_test: MSC==10
udevadm_test: MODALIAS=input:b0003v045Ep00DBe0111-e0,1,2,3,4,14,k71,72,73,74,75,77,79,7A,7B,7C,7D,7E,7F,80,81,82,83,84,85,86,87,88,89,8A,8B,8C,8E,90,96,98,9B,9C,9E,9F,A1,A3,A4,A5,A6,A7,A8,A9,AB,AC,AD,AE,B0,B1,B2,B3,B4,B5,B6,B8,B9,BA,BB,BC,BD,BE,BF,C0,C1,C2,CE,CF,D0,D1,D2,D5,D9,DB,DF,E2,E7,E8,E9,EA,EB,F0,100,162,166,16A,16E,178,179,17A,17B,17C,17D,17F,180,181,182,185,18C,18D,192,193,195,1A0,1A1,1A2,1A3,1A4,1A5,1A6,1A7,1A8,1A9,1AA,1AB,1AC,1AD,1AE,1B0,1B1,1B7,r6,a20,m4,lsfw
udevadm_test: ACTION=remove
udevadm_test: SUBSYSTEM=input
udevadm_test: run: 'socket:@/org/freedesktop/hal/udev_event'
udevadm_test: run: '/usr/bin/numlockx off'

The problem is that when the keyboard is plugged in or unplugged, the NumLock status doesn't change. I have tried restarting the udev service, with no luck. Are my udev rules wrong? Am I going about this the wrong way?

Best Answer

The problem is that you probably have tested numlockx in (as it's name suggests) some X environment. X clients (GUI programs that connect to X server, e.g. Firefox or GEdit) need to know the server to connect to, and also must pass some kind of authorization. Try logging in from plain console, no matter by root or plain user, and starting some GUI application: it will show some DISPLAY related errors because it doesn't know.

To fix this you need to set environment variable DISPLAY; if you have only one X server it almost always has the address :0.

Try this at plain console: type numlockx on, and it will show "Error opening display!". Type DISPLAY=:0 numlockx on, and it will work (worked for me at least).

So you may set this environment variable in udev or just launch sh -c 'DISPLAY=:0 numlockx <state>'.