3

I am running Ubuntu-gnome 16.04 LTS 4.4.0-62-generic.

I would like to do following: Run either ...

  • (a) an application (GUI) [/path/to/my_app.desktop]
  • or (b) an interactive script in a terminal [e.g.: $ gnome-terminal -x /path/to/script.sh]

... whenever a mount of any (USB) device is recognized.

The execution on the user's X-server seems mandatory since varying user input is required in response to the running application/script; it can not simply run in the background.

For the last 2 days I have done extensive research and experimented with both, udev-rules and systemd.services. My re-occurring problem tough is, that either of the latter approaches requires X-authentification. Altough there sure are (convoluted) ways to make the authentification happen, I do not like the idea to breach the innate system security by exporting the $XAUTHORITY variables from root to the user's session...

I suppose there should be an alternative way to make the following happening:

  1. detect when/if a new (USB) device was mounted
  2. start an application/script(in terminal) with USER privileges on user desktop (i.e. user's Xsession?)
  3. (optional: pass the devicename on as a variable)

(creation of mount-specific .config files?; org.gnome.desktop.media-handling?; autostart-script observing /home/$USERNAME/media .mounts?; editing /etc/fstab; ...?)

Any hints would be highly appreciated.

  • Hi brunuser, I was pretty sure I answered a question like this before, but it turned out to be a look-alike :). Please mention if all is clear (or not). – Jacob Vlijm Feb 16 '17 at 6:15
3

(Old answer, new answer further below)


Run a script or command to run whenever a usb drive gets connected

If, for whatever reasons, you do not want to use udev rules or anything more complicated, then use the script below.

Simply running the script, with your command to run as argument, will do the job.

The script:

#!/usr/bin/env python3
import subprocess
import time
import sys

cmd = " ".join(sys.argv[1:])

def get_mountedlist():
    return [(item.split()[0].replace("├─", "").replace("└─", ""),
             item[item.find("/"):]) for item in subprocess.check_output(
            ["/bin/bash", "-c", "lsblk"]).decode("utf-8").split("\n") if "/" in item]

def identify(disk):
    command = "find /dev/disk -ls | grep /"+disk
    return "usb" in subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

mounted1 = get_mountedlist()
while True:
    time.sleep(4)
    mounted2 = get_mountedlist()
    if [d for d in mounted2 if all([not d in mounted1, d != "/", identify(d[0]) == True])]:
        subprocess.Popen(["/bin/bash", "-c", cmd])
    mounted1 = mounted2

To use

  1. Copy the script into an empty file, save it as run_usbactions.py
  2. Test- run the script by (e.g.) the command:

    python3 /path/to/run_usbactions.py <command_to_run> <optional_args>
    

    In my test, I used e.g.:

    python3 /path/to/run_usbactions.py gedit file
    

    to open file with gedit once a usb drive is connected.

  3. If all works fine, add it to Startup Applications: Dash > Startup Applications > Add. Add the command:

    python3 /path/to/run_usbactions.py <command_to_run> <optional_args>
    

Explanation

  • Once per four seconds, in the function get_mountedlist(), the script reads the output of lsblk.

  • In case additional partitions or devices are mounted, the output of (e.g.) the command:

    find /dev/disk -ls | grep sdc1
    

    will include the string usb, and identify the mounted drive as a usb drive.
    Looking at it now, I very well could replace it by a more "pythonic" way of doing it, instead of a system call, but since I copied it from an older script, I didn't (yet).

  • subsequently, if the new drive is a usb, the command (+ possible args), as set in " ".join(sys.argv[1:]) is run.




EDIT

(December 29, 2017)

Using pyudev

Since I "met" pyudev, I thought I should share the major simplification and clearer operation it brings. A simple script to perform any action on inserting a usb device then becomes:

#!/usr/bin/env python3
import pyudev
import subprocess
import sys

cmd = " ".join(sys.argv[1:])

monitor = pyudev.Monitor.from_netlink(pyudev.Context())
monitor.filter_by('block')

for device in iter(monitor.poll, None):
    if all([
        device['ACTION'] == "add", 'ID_FS_TYPE' in device,
        device['ID_USB_DRIVER'] == "usb-storage",
        ]):
        print("added", device.get('ID_FS_LABEL'))
        subprocess.Popen(["/bin/bash", "-c", cmd])

Usage

Make sure pyudev is installed:

sudo apt install python3-pyudev

Further instructions are exactly similar.

  • thanks for Your fast reply. As stated in my OQ, udev rules simply do not start any application on the user interface.... Since I am a programming newbie - but willing to learn - let me try to ask a question regarding your script: def identify(disk): command = "find /dev/disk -ls | grep /"+disk return "usb" in subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8") defines the command identify(disk) by returning any mount events containing the string "usb" in find /dev/disk -ls output.: Why is in command=...+disk the addition of the latter required? – brunuser Feb 16 '17 at 13:14
  • @brunuser I added an explanation. Please mention if all is clear (or not). – Jacob Vlijm Feb 16 '17 at 13:50
  • Vljim , thanks again. Yeah, I was wondering especially about the command = "find /dev/disk -ls | grep /"*+disk* expression, i.e. the +disk operator to the grep / pattern, but I got it now; in my case the line would be command = "find /dev/disk -ls | grep /sdb. If I might be allowed to ask one further (stupid) question: if [d for d in mounted2 if all([not d in mounted1, d != "/", identify(d[0]) == True])]: what does the identify(d[0]) == True condition mean/test? (I think I got the rest, but the latter is enigmatic to me.) – brunuser Feb 16 '17 at 18:13
  • @brunuser please ask! The function identify(device) returns True if the device is a usb device, hence identify(d[0]) == True . One more thing: (only) if the answer solved your question, would you be so kind to accept the answer (tick the big "V" below the up/down arrows on the left). It is the appropriate way to indicate the answer worked for you. – Jacob Vlijm Feb 16 '17 at 18:37
  • @ Vlijm. Right now I am trying to reverse-engineer Your python script as a bash version (practical experience helps me to better understand). Altough not having excessively tested Your script, I highly appreciate Your response and will therefore mark it as answered for now. :) – brunuser Feb 16 '17 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.