15

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

Rumesh
  • 1,439

2 Answers2

8

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
Jacob Vlijm
  • 83,767
  • 7
    You are giving a bicycle to a man with a broken fast car instead of fixing the car... – solsTiCe May 30 '15 at 20:29
  • 1
    @solsTiCe 1. This is no bicycle, but a perfectly functional option. Keep in mind that all notify-, event- or whatever driven actions exist of running some kind of a loop somehow. 2. I suggest you fix the Ferrari then. – Jacob Vlijm May 30 '15 at 20:38
  • @Rumesh Perfect! Glad it works. – Jacob Vlijm May 31 '15 at 05:45
  • @JacobVlijm the command to run the script at starup does not work, I used python3 /usr/local/bin/connect_screen.py instead. I suggest you edit the answer so that other people do not have trouble getting the script to run on startup – Rumesh Jun 06 '15 at 08:43
  • 1
    @Rumesh running scripts in $PATH with file extension, and with preceding language is a not-so-elegant solution. The question is if you want to run it for one user or for all users. In the last case, another setup than the suggested is needed, but preferably not the one you are suggesting :). The sleep 15 might not be sufficient, but a more elegant solution could be to make the break "smart", let the script try/except to start up until the startup procedure is "ready" for the script to run. WOuld be a minor addition. Let me know. – Jacob Vlijm Jun 06 '15 at 16:54
  • 1
    @Rumesh scripts in $PATH should not have language extension and be executable, so run without python3 see https://lintian.debian.org/tags/script-with-language-extension.html – Jacob Vlijm Jun 06 '15 at 16:56
  • I only have one user on my of so I only need the script for one user. What command do you want me to try? I'll do it and tell you the result – Rumesh Jun 06 '15 at 17:09
  • @JacobVlijm it be better to make another script and run the Python script from that? It would avoid the file extension and the sleep command may also be used properly – Rumesh Jun 06 '15 at 17:11
  • @Rumesh is the script only for personal use (locally)? No need for an extra script, I can make this one "wait" for the desktop to be fully loaded. As a background information: for an executable to work, the language extension plays no role at all, you can leave it away if you want (but then also the command to run it would change :) ) Only for importing the language extension is important. see http://askubuntu.com/a/590416/72216 – Jacob Vlijm Jun 06 '15 at 17:19
  • @JacobVlijm script is only needed locally. Ok I'll try to just use the script name and let you know how it works out – Rumesh Jun 07 '15 at 01:48
  • @Rumesh Nono, that was just as information, it won't change a thing :) I'll make another version. – Jacob Vlijm Jun 07 '15 at 04:31
  • @JacobVlijm well I just tried with conect_screen and it works. but I had to mark it as executable with chmod +x first. – Rumesh Jun 07 '15 at 04:37
  • @Rumesh ??? That is odd, since it won't change anything to the timing. Let me know if it only succeeded incidentally, than Iĺl make an edited version :) – Jacob Vlijm Jun 07 '15 at 04:40
  • @JacobVlijm I dont really understand the need to use sleep anyway, dont the commands just run when the user has logged in? – Rumesh Jun 07 '15 at 04:43
  • @Rumesh In some cases, commands break if the GUI is not completely finished loading if the script starts. This is often the case with touchpad related commands, many things concerning external screens, (sometimes) loading indicators etc. Then building in a break befaore the script actually starts helps. Look here: http://askubuntu.com/questions/609490/running-a-script-at-startup-that-uses-xrandr-to-set-display-options/609499#609499 It only worked after a time out of 100 seconds :) (see futher below in the comments). I am pretty sure that is the case in your situation as well. – Jacob Vlijm Jun 07 '15 at 04:49
  • @Rumesh See my edited answer. This should end all startup issues :) – Jacob Vlijm Jun 07 '15 at 06:40
  • @JacobVlijm The old script worked fine when I put connect_screen in the startup applications, Ill edit the script to the new one you put up but should I put python3 /usr/local/bin/connect_screen.py or just connect_screen. I am asking because you said that using the language name and extension is not a very elegant solution – Rumesh Jun 07 '15 at 07:26
  • @Rumesh If the script is called connect_screen.py, the command should be with language extension, else not. It is not that any of them causes a disaster, but using no extension with scripts in $PATH (and no preceding python3) is the "good practice" way :), but not using python3 would need the script to be executable. – Jacob Vlijm Jun 07 '15 at 07:32
  • 1
    @JacobVlijm I had already set it to be executable before so I guess I can just use connect_screen – Rumesh Jun 07 '15 at 07:34
  • @Rumesh perfect, but as said, then the script should be named likewise :) – Jacob Vlijm Jun 07 '15 at 07:36
  • @JacobVlijm Yes I already renamed the script :) Thanks for all the help – Rumesh Jun 07 '15 at 07:37
  • Unfortunately, I did notice a slight increase in CPU load from xorg while using this script... Makes me hesitant about whether to use it or not. – Franko Nov 26 '22 at 16:19
  • Last night I noticed Netflix video streams were slightly skipping on regular intervals while using this script. I killed the script -- video is back to normal. This is a deal breaker for me. – Franko Nov 27 '22 at 14:27
4

This can be achieved from following bash script as well.

#!/usr/bin/env bash

xrandr=$(xrandr)

con_monitors=$(echo $xrandr | grep -c " connected ")

    if [[ $con_monitors -gt 1 ]]; then
        # All the layouts are saved in "screenlayout" folder.
        # eg cmd. xrandr --output HDMI-1 --mode 2560x1440 --pos 0x0 --rotate normal --output DP-1 --off --output eDP-1 --primary --mode 1920x1080 --pos 283x1440 --rotate normal --output DP-2 --off
        for layout in ~/.screenlayout/*.sh; do
            ./layout
        done
    fi
  • Bhaskar I know this answer was from ages ago but in case you're still around: 1. will this work automatically or does it need to be run manually / have a script run it every n seconds? 2. I have various screen layouts as bash scripts on my desktop, and didn't know about ./screenlayout - should I be using this preferentially? 3. could I modify this to change change xrandr AND audio settings when a scrren is turned on, AND change them back when that screen is turned off, do you think? Thanks! – dez93_2000 Jun 05 '23 at 17:16