Ubuntu – Run script when monitor is connected

eventsmultiple-monitorsscriptsudevxrandr

I am trying to run a script located in usr/local/bin/ when I connect an external monitor to my laptop. I have tried to add a new udev rule but that did not work. I created a new file in /etc/udev/rules.d called vga-monitor-connect.rules. The contents of the file were

SUBSYSTEM=="drm", ACTION=="change", RUN+="/usr/local/bin/panel-fix"

I took the line from this answer

After searching online I also tried the following rule

KERNEL=="card0", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/rumesh/.Xauthority", RUN+="/usr/local/bin/panel-fix"

However this didn't work either.

I have run the script manually and I can confirm that it works so it is not a problem with my script.

I also want to make it clear that I do not know much about udev so the rule I have used may be wrong. If anyone knows the proper rule for my problem please leave an answer.

My graphics card is an Intel GM965 integrated chipset

Best Answer

An alternative way to run a command if a screen is connected or disconnected

An alternative solution would be to run a tiny background script. Running the script below in the background, I could not measure any increase in processor load whatsoever.

It is an easy an convenient way to run a script, or any other command, whenever a second screen is connected or disconnected.

The example script

  • Simply checks every five seconds how many times the string " connected " occurs in the output of the command xrandr (mind the space after "connected" to prevent false matches with "disconnected"). Each occurrence represents a connected screen.
  • If the number of occurrences changes, either a screen was connected or disconnected. The change is "noticed" by the script and can be connected to a command, you can set in the head section of the script.

The script

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

#--- set both commands (connect / disconnect) below
connect_command = "gedit"
disconnect_command = ""
#---

def get(cmd): return subprocess.check_output(cmd).decode("utf-8")
# - to count the occurrenc of " connected "
def count_screens(xr): return xr.count(" connected ")
# - to run the connect / disconnect command(s)
def run_command(cmd): subprocess.Popen(["/bin/bash", "-c", cmd])

# first count
xr1 = None

while True:
    time.sleep(5)
    # second count
    xr2 = count_screens(get(["xrandr"]))
    # check if there is a change in the screen state
    if xr2 != xr1:
        print("change")
        if xr2 == 2:

            # command to run if connected (two screens)
            run_command(connect_command)
        elif xr2 == 1:
            # command to run if disconnected (one screen)
            # uncomment run_command(disconnect_command) to enable, then also comment out pass
            pass
            # run_command(disconnect_command)
    # set the second count as initial state for the next loop
    xr1 = xr2

How to use

  1. Copy the script into an empty file, save it as connect_screen.py
  2. In the head section, set the command to run on connect ( I set "gedit" as an example, mind the quotes). Also it is possible to set a command on disconnect, likewise. Else leave disconnect_command = "" as it is.

    If you do use a disconnect- command, also uncomment the line:

    run_command(disconnect_command)
    

    and comment out the line:

    pass
    

    As indicated in the script

  3. Test-run the script from a terminal, connect your screen and see if all works fine.
  4. If all works fine, add it to your startup applications: Dash > Startup Applications > Add the command:

    /bin/bash -c "sleep 15&&python3 /path/to/connect_screen.py"
    

    The sleep 15 is to make the desktop start up completely before the script starts to run. Just to make sure.


EDIT

How to run the script on start up in a "smart" way.

The break of sleep 15 should work in general, but since the start up time differs per system, It might take some experimenting to find the right break time. With a small addition, the script becomes "smart", and waits for the xrandr command to be successful before it starts the actual script. If you use the version below, you only need to add the command:

python3 /path/to/connect_screen.py

to your Startup Applications. Further usage is exactly the same as the version above.

The script

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

#--- set both commands (connect / disconnect) below
connect_command = "gedit"
disconnect_command = ""
#---

while True:
    time.sleep(5)
    try:
        subprocess.Popen(["xrandr"])
    except:
        pass
    else:
        break


# function to get the output of xrandr
def get(cmd): return subprocess.check_output(cmd).decode("utf-8")
# - to count the occurrenc of " connected "
def count_screens(xr): return xr.count(" connected ")
# - to run the connect / disconnect command(s)
def run_command(cmd): subprocess.Popen(["/bin/bash", "-c", cmd])

# first count
xr1 = None

while True:
    time.sleep(5)
    # second count
    xr2 = count_screens(get(["xrandr"]))
    # check if there is a change in the screen state
    if xr2 != xr1:
        if xr2 == 2:
            # command to run if connected (two screens)
            run_command(connect_command)
        elif xr2 == 1:
            # command to run if disconnected (one screen)
            # uncomment run_command(disconnect_command) to enable, then also comment out pass
            pass
            # run_command(disconnect_command)
    # set the second count as initial state for the next loop
    xr1 = xr2