You are not logged in.

#1 2016-05-09 14:28:28

imdn
Member
Registered: 2016-04-12
Posts: 40

[Solved] Invoking external script from udev rules

Hi,

I intend to remap the middle mouse button whenever an external mouse is plugged into my laptop using udev. I have adapted instructions from the links below to accomplish this

http://ubuntuforums.org/showthread.php?t=1865740, and
http://granjow.net/udev-rules.html

The general idea is that udev triggers the wrapper script, which in turn, invokes another script which uses xinput to remap the buttons. The wrapper script is necessary since udev is paused while the script executes and xinput does not list the mouse right as soon as it is plugged in. Atleast that is how it is supposed to work.

This is my udev rules file 41-mouse.rules

# Logitech B110 Optical Mouse
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c05b", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/imdn/.Xauthority", RUN+="/opt/myscripts/mouse_starter.sh", MODE="0666", OWNER="imdn", SYMLINK+="logitech-b110"

/opt/myscripts/mouse_starter.sh - an executable wrapper script to invoke the actual script

#!/bin/bash
export DISPLAY=$DISPLAY
export LOG=/tmp/mouse_middle.log
export XAUTHORITY=$XAUTHORITY

date > $LOG
echo "DISPLAY-'$DISPLAY'" >> $LOG
echo "Now invoking script from starter script ..." >> $LOG
/opt/myscripts/logitech_b110.sh &

/opt/myscripts/logitech_b110.sh - the actual executable script which remaps the button

#!/bin/bash
sleep 5
xinput >> $LOG

# Get xinput device id
logitech_mouse_id=$(xinput | grep "Logitech USB Optical Mouse" | awk {'print substr($7,4,2)'})
if [ $logitech_mouse_id != '' ]; then
    echo "Logitech B110 Optical mouse detected (xinput id - '$logitech_mouse_id') !  Remapping middle button" >> $LOG
    # Map button 6 (scroll wheel tilt-left button) and  7 (scroll wheel tilt-right button) to button 2 (middle button)
    xinput set-button-map $logitech_mouse_id 1 2 3 4 5 2 2 8 9 10 11 12 13 14 15 16
else
    echo "Xinput did not find Logitech mouse" >> $LOG
fi

The problem is that despite calling the second script from inside the wrapper script, the mouse is still not listed by xinput. The mouse is listed by xinput only after both scripts have finished. Increasing the sleep duration does not help.

Running the script standalone works fine. I am also using the latest stable version of udev (systemd) and xinput.

Any ideas what the problem occurs and how to fix this ? Has anything changed in udev regarding the way external programs are run (considering both the sources I cited are quite old) ? Is there any other way I can accomplish remapping of the buttons when it is plugged in?

Last edited by imdn (2016-05-09 17:44:45)

Offline

#2 2016-05-09 15:06:10

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 20,196

Re: [Solved] Invoking external script from udev rules

What items are being written to your log file?  Have you evidence that mouse_starter.sh is writing to /mnt/mouse_middle.log? 
Who owns that file?  What are the write permissions for non-owners?
You might consider using the logger command to write messages to the journal rather than rolling your own logging mechanism.  It might yield better information.


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#3 2016-05-09 15:26:05

imdn
Member
Registered: 2016-04-12
Posts: 40

Re: [Solved] Invoking external script from udev rules

ewaller wrote:

What items are being written to your log file?  Have you evidence that mouse_starter.sh is writing to /mnt/mouse_middle.log? 
Who owns that file?  What are the write permissions for non-owners?
You might consider using the logger command to write messages to the journal rather than rolling your own logging mechanism.  It might yield better information.

The expected statements are being written to the logfile just fine. Infact that is how I diagnosed that the mouse was not showing up in the xinput list. Thanks for the 'logger' suggestion, but in this case logging and file permissions are not where the issue lies. Below are the contents of the logfile should anyone be interested

Mon May  9 17:18:44 CEST 2016
DISPLAY-':0'
Now invoking script from starter script ...

⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ AlpsPS/2 ALPS DualPoint TouchPad          id=15   [slave  pointer  (2)]
⎜   ↳ AlpsPS/2 ALPS DualPoint Stick             id=16   [slave  pointer  (2)]
⎜   ↳ Microsoft Natural® Ergonomic Keyboard 4000        id=11   [slave  pointer  (2)]
⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Video Bus                                 id=7    [slave  keyboard (3)]
    ↳ Power Button                              id=8    [slave  keyboard (3)]
    ↳ Sleep Button                              id=9    [slave  keyboard (3)]
    ↳ Laptop_Integrated_Webcam_FHD              id=13   [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              id=14   [slave  keyboard (3)]
    ↳ Dell WMI hotkeys                          id=17   [slave  keyboard (3)]
    ↳ Microsoft Natural® Ergonomic Keyboard 4000        id=10   [slave  keyboard (3)]

Xinput did not find Logitech mouse

It would seems xinput is not aware of the mouse despite the second script workaround as suggested in the original sources. Running xinput after the scripts have finished running shows the following missing line.

↳ Logitech USB Optical Mouse                id=12   [slave  pointer  (2)]

Last edited by imdn (2016-05-09 15:27:05)

Offline

#4 2016-05-09 15:55:00

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 20,196

Re: [Solved] Invoking external script from udev rules

Ah....


Take a look at my /etc/acpi/handler script.  It turns off the touchpad when the lid is closed, and enables it when the lid is opened. I think the magic may be in how you are defining Xauthority.  I don;t think the script is finding your variables because it is not you:

ewaller@turing/home/ewaller % cat /etc/acpi/handler.sh
#!/bin/bash
# Default acpi script that takes an entry for all actions

case "$1" in
    button/power)
        case "$2" in
            PBTN|PWRF)
                logger 'PowerButton pressed'
                ;;
            *)
                logger "ACPI action undefined: $2"
                ;;
        esac
        ;;
    button/sleep)
        case "$2" in
            SLPB|SBTN)
                logger 'SleepButton pressed'
                ;;
            *)
                logger "ACPI action undefined: $2"
                ;;
        esac
        ;;
    ac_adapter)
        case "$2" in
            AC|ACAD|ADP0)
                case "$4" in
                    00000000)
                        logger 'AC unpluged'
                        ;;
                    00000001)
                        logger 'AC pluged'
                        ;;
                esac
                ;;
            *)
                logger "ACPI action undefined: $2"
                ;;
        esac
        ;;
    battery)
        case "$2" in
            BAT0)
                case "$4" in
                    00000000)
                        logger 'Battery online'
                        ;;
                    00000001)
                        logger 'Battery offline'
                        ;;
                esac
                ;;
            CPU0)
                ;;
            *)  logger "ACPI action undefined: $2" ;;
        esac
        ;;
    button/lid)
        case "$3" in
            close)
                export XAUTHORITY=`ls -1 /home/*/.Xauthority | head -n 1`
                export DISPLAY=":`ls -1 /tmp/.X11-unix/ | sed -e s/^X//g | head -n 1`"
                #xinput set-prop "SynPS/2 Synaptics TouchPad" 307 1
                synclient TouchpadOff=1
                logger 'LID closed, touchpad off'
                ;;
            open)
                logger 'LID opened, touchpad on'
                export XAUTHORITY=`ls -1 /home/*/.Xauthority | head -n 1`
                export DISPLAY=":`ls -1 /tmp/.X11-unix/ | sed -e s/^X//g | head -n 1`"
                #xinput set-prop "SynPS/2 Synaptics TouchPad" 307 0
                synclient TouchpadOff=0
                ;;
            *)
                logger "ACPI action undefined: $3"
                ;;
        esac
        ;;
    *)
        logger "ACPI group/action undefined: $1 / $2"
esac
;;

# vim:set ts=4 sw=4 ft=sh et:
ewaller@turing/home/ewaller %

This script looks through everyone's stuff and uses the first one it finds.  Hackish?  Yeah.


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#5 2016-05-09 16:20:19

imdn
Member
Registered: 2016-04-12
Posts: 40

Re: [Solved] Invoking external script from udev rules

ewaller wrote:

Ah....


Take a look at my /etc/acpi/handler script.  It turns off the touchpad when the lid is closed, and enables it when the lid is opened. I think the magic may be in how you are defining Xauthority.  I don;t think the script is finding your variables because it is not you:

....

This script looks through everyone's stuff and uses the first one it finds.  Hackish?  Yeah.

Thanks, tried it , but I am the only user and verified that Xauthority points to the same file (/home/imdn/.Xauthority) - so it didn't work in my case. Besides there are other examples of defining the XAUTHORITY environment variable in udev rules (e.g. https://bbs.archlinux.org/viewtopic.php?id=146725 and https://bugs.launchpad.net/ubuntu/+sour … comments/3)

Offline

#6 2016-05-09 17:43:17

imdn
Member
Registered: 2016-04-12
Posts: 40

Re: [Solved] Invoking external script from udev rules

Solution
Ok, I managed to put together a solution. According to https://bbs.archlinux.org/viewtopic.php?id=180079,  Xorg supposedly resets settings after udev runs. This would explain why the mouse does not show up in the list. So the solution is as follows:

  • Write udev rule to trigger a shell script (lets call this primary)

  • Primary shell script writes to a temporary lock file

  • Set an inotifywatch on the lock file. If it changes trigger the secondary shell script which remaps the mouse buttons (optionally do this in .xinitrc)

Relevant code below (adapted from the above thread)

/etc/udev/rules.d/41-mouse.rules

# Logitech B110 Optical Mouse
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c05b", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/imdn/.Xauthority", RUN+="/opt/myscripts/mouse_udev.sh", MODE="0666", OWNER="imdn", SYMLINK+="logitech-b110"

/opt/myscripts/mouse_udev.sh (make sure it's executable)

#!/bin/bash

# Path to lock file
lock="/tmp/mouse-middle.lock"

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

# Just putting in a timestamp
echo "Modified - $(date)" > $lock &

/opt/myscripts/logitech_b110.sh (make sure it's executable)

#!/bin/bash
sleep 2
LOG="/tmp/mouse-middle.log"
xinput > $LOG # In case of errors check to see if mouse is in the list of devices

# Get mouse id
logitech_mouse_id=$(xinput | grep "Logitech USB Optical Mouse" | awk {'print substr($7,4,2)'})

if [ $logitech_mouse_id != '' ]; then
    echo "Logitech B110 Optical mouse detected (xinput id - '$logitech_mouse_id') ! " >> $LOG
    # Map button 6 (scroll wheel tilt-left button) and  7 (scroll wheel tilt-right button) to button 2 (middle button)
    xinput set-button-map $logitech_mouse_id 1 2 3 4 5 2 2 8 9 10 11 12 13 14 15 16
    notify-send -t 3000 "Remapped Middle-button functionality"
else
    echo "Xinput did not find Logitech mouse" >> $LOG
    notify-send -t 3000 "Could not find logitech mouse"
fi

~/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

Call file-notify manually by invoking

~/bin/file-inotify /tmp/mouse-middle.lock /opt/myscripts/logitech_b110.sh & 

or add it to ~/.xinitrc

/opt/myscripts/logitech_b110.sh &
~/bin/file-inotify /tmp/mouse-middle.lock /opt/myscripts/logitech_b110.sh &  # Triggered by udev rule

Last edited by imdn (2016-05-09 17:48:12)

Offline

#7 2016-09-27 07:56:48

kokesssss
Member
Registered: 2012-11-06
Posts: 6

Re: [Solved] Invoking external script from udev rules

Thanks, finally found a working solution for configuring my extra USB mouse button at system start and plug in.

Two little details
* better use ~/.xprofile than ~/.xinitrc, when you use a display manager: https://wiki.archlinux.org/index.php/xprofile
* you will need to install community/inotify-tools to have needed inotifywait

The trick with file lock and inotify rocks. I tried some timeouts and background processes but X refreshed it's xinput list always after my udev rule. Thanks again.

Last edited by kokesssss (2016-09-27 08:04:27)

Offline

Board footer

Powered by FluxBB