You are not logged in.

#1 2014-04-15 07:22:53

Markus00000
Member
Registered: 2011-03-27
Posts: 318

[SOLVED] Script run by udev for external USB keyboard stopped working

Goal: When the USB keyboard is being connected, a script should run to change several keyboard settings.

This udev rule successfully runs the script when the keyboard is being connected:

ACTION=="add", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", RUN+="/home/username/bin/keyboard-udev", OWNER="username"

The wrapper script (as suggested in this superuser thread):

/home/username/bin/keyboard-udev
--------------------------------
#!/bin/bash
# Background process to allow udev to continue
# Sleep to wait for keyboard being activated
(sleep 1 && /home/username/bin/keyboard) &

Sleeping longer has no effect.

The script to change the keyboard settings:

/home/username/bin/keyboard
---------------------------
#!/bin/bash
DISPLAY=":0"
HOME=/home/username
XAUTHORITY=$HOME/.Xauthority
export DISPLAY XAUTHORITY HOME
xset r rate 175 75
numlockx
setxkbmap -layout "de" -variant nodeadkeys
xmodmap $HOME/.Xmodmap
xbindkeys
notify-send "This notification is being shown!"

As you can see, I've checked with a notify-send command that the script is indeed being executed.

If I run this script manually after the keyboard has been connected, it successfully changes everything it is supposed to change.

The successful notify-send command should prove that DISPLAY, HOME and XAUTHORITY are correctly set.

Question: Why does the script work when run manually, but fail when run via udev?

Note that all of this did work some time ago and only broke, maybe, 1 to 6 months ago (not sure when exactly). I assume it broke after some update.

Last edited by Markus00000 (2014-05-16 10:18:18)

Offline

#2 2014-04-15 14:26:46

berbae
Member
From: France
Registered: 2007-02-12
Posts: 1,302

Re: [SOLVED] Script run by udev for external USB keyboard stopped working

Markus00000 wrote:

If I run this script manually after the keyboard has been connected, it successfully changes everything it is supposed to change

If I were you I would stick to this manual approach, rather than using udev to act in the upper graphical layer where it is not supposed to go or without warranty of success.

Offline

#3 2014-04-15 16:14:50

Markus00000
Member
Registered: 2011-03-27
Posts: 318

Re: [SOLVED] Script run by udev for external USB keyboard stopped working

I connect and disconnect the USB keyboard often, therefore, the manual approach is quite cumbersome. Maybe there is an alternative to udev which is supposed to act in the upper graphical layer?

Offline

#4 2014-05-16 08:39:51

Pichuco
Member
Registered: 2014-05-16
Posts: 1

Re: [SOLVED] Script run by udev for external USB keyboard stopped working

Hello,

I run into the same problem:

The background is that the script is run 4 times(!). Here is my
testscript:

#!/bin/bash

# No mixture or confusion due to locale output
export LANG=C

DEBUG=1

if [ $DEBUG -ne 0 ]; then
    # redirect all script output to a temporary logfile
    exec >/tmp/`basename $0`-$$.log 2>&1
    echo "Command line of this script is |$@|"
    set -x
    echo =======================================
fi


usr=""
while [ -z "$usr" ]; do
    usr=`who | awk '/\(:[0-9]\)/ { print $1 }' | sort -u`

    sleep 1
done

for i in $usr; do
    pid=`ps -u $i | grep startkde | awk '{ print $1 }'`
    disp=`cat /proc/$pid/environ | tr '\000' '\n' | sed -ne 's/DISPLAY=//p'`

    export DISPLAY=$disp
    # set
    # sleep 1
    # test -f /home/$i/.xmodmap && su $i -c "DISPLAY=$disp xset -q"
    date
    test -f /home/$i/.xmodmap && su $i -c "DISPLAY=$disp xmodmap -pk | grep 91"
    test -f /home/$i/.xmodmap && su $i -c "DISPLAY=$disp /usr/bin/xmodmap /home/$i/.xmodmap"
    sleep 1
    test -f /home/$i/.xmodmap && su $i -c "DISPLAY=$disp xmodmap -pk | grep 91"
    echo Return code is $?
done

Maybe you find some hints. For me the solution will be to add a udev
remove rule removing a temporary file. The settings will only be done
if this file does not exist and is created once the settings are done.

Hth -- Peter

Offline

#5 2014-05-16 10:17:41

Markus00000
Member
Registered: 2011-03-27
Posts: 318

Re: [SOLVED] Script run by udev for external USB keyboard stopped working

This no longer works as of 2014-07-28. Updated answer in the post below this one.

Thank you so much for registering and replying!

All I had to do was to modify my script as follows:

/home/username/bin/keyboard-udev
--------------------------------
#!/bin/bash
# Background process to allow udev to continue

# Path to lock file
lock="/tmp/keyboard.lock"

# Lock the file (other atomic alternatives would be "ln" or "mkdir")
exec 9>"$lock"
if ! flock -n 9; then
	# Script is already running
	exit 1
fi

/home/username/bin/keyboard &

# The lock file will be unlocked when the script ends

Additionally, I removed the one second sleep. The script failed once since but I don't know whether it was caused by the missing delay.

It did work at least 20 times, thank you!

Last edited by Markus00000 (2014-07-28 19:47:56)

Offline

#6 2014-07-28 19:47:21

Markus00000
Member
Registered: 2011-03-27
Posts: 318

Re: [SOLVED] Script run by udev for external USB keyboard stopped working

Updated solution

Why? Because the above solution stopped working. It might be the case that Xorg resets some settings after udev ran. See https://bugzilla.redhat.com/show_bug.cgi?id=601853 for more details. As the bug has been reported in 2010, I'm not sure why the above solution worked at all. Anyways, in my case "xset r rate [number] [number]" was overridden among other settings.

Here's the udev rule I'm using which you'll have to adjust to match your keyboard:

/etc/udev/rules.d/99-keyboard.rules
-----------------------------------
# WARNING: Command might be executed multiple times
ACTION=="add", ATTRS{phys}=="usb-0000:00:14.0-?/input0", RUN+="/usr/local/bin/keyboard-udev"

I failed to build a set of udev rules that consistently ran the command only once. With this rule it will be executed 1-3 times. To list possible attributes run:

udevadm info -a -n /dev/input/by-id/<your_keyboard>

Now to the script that udev runs. Only one instance of this script should ever run at any time because of the locked file check. (Though with this new solution the script has never been invoked concurrently on my machine.) Previously, the script that set the keyboard settings could be run directly at this point. Now, the workaround is to write to a temporary file instead. In other words, the temporary file gets written whenever the keyboard is connected and the udev rule consequently applied:

/usr/local/bin/keyboard-udev
----------------------------
#!/bin/bash
# Background process to allow udev to continue

# Path to lock file
lock="/tmp/keyboard.lock"

# Lock the file (other atomic alternatives would be "ln" or "mkdir")
exec 9>"$lock"
if ! flock -n 9; then
	notify-send -t 5000 "Keyboard script is already running."
	exit 1
fi

#/usr/local/bin/keyboard &  # If Xorg would not reset settings after udev finished
echo '' > /tmp/keyboard.lock &

# The lock file will be unlocked when the script ends

The script that applies the keyboard settings does no longer require any environment variables. If you're interested in how many times it's executed on your machine, uncomment the notify-send command. Obviously, you'll want to change these settings and commands to your liking:

/usr/local/bin/keyboard
-----------------------
#!/bin/bash
xset r rate 175 75
numlockx
setxkbmap -layout "de" -variant nodeadkeys
xmodmap ${HOME}/.Xmodmap
(killall xbindkeys || true) &> /dev/null  # Continue even if exit status is 1
xbindkeys  # Only one instance will run
#notify-send -t 5000 "Keyboard settings applied"

Finally, the keyboard settings are automatically applied when Xorg is started. I assume udev can run much earlier in the boot process and therefore might be executed  long before Xorg is running. The settings are also applied whenever the keyboard is plugged in while Xorg is running. That's whenever the keyboard-udev script writes to the temporary file, thereby indicating that the keyboard was connected:

~/.xinitrc
----------
[...]
/usr/local/bin/keyboard &
file-inotify /tmp/keyboard.lock /usr/local/bin/keyboard &  # Triggered by udev rule
[...]

As file-notify is reusable I've put it in an extra file:

~/bin/file-inotify
------------------
#!/bin/bash
# Usage: file-inotify <file> <command>
# Command is run when file is written.

path=$(realpath "$1")
job="$2"
#basename=$(basename "$1")
dirname=$(dirname "$1")

inotifywait -m -e close_write --format '%w%f' "$dirname" \
	| while read file
do
	if [[ $(realpath "$file") == "$path" ]]; then
		sh -c "$job"
	fi
done

That's all. Except for the cosmetic issue that the script runs multiple times, it's working great so far.

Last edited by Markus00000 (2014-07-29 05:28:18)

Offline

Board footer

Powered by FluxBB