How can I detect when a monitor is plugged in or unplugged?
NOTE: This was tested on a laptop with a i915 driven graphics card.
Background
NOTE: When a new screen is plugged in, no event is sent to the host, this stayed true even after my last edit. So the only way is to use polling. Trying to make them as effiicient as possible...
EDIT #3
Finally there's one better solution (through ACPI):
There's still no event, but ACPI seems more efficient than xrandr
to inquire. (Nota: This requires ACPI kernel modules loaded, but doesn't require root privileges).
My final solution (using bash):
isVgaConnected() {
local crtState
read -a < /proc/acpi/video/VID/CRT0/state crtState
test $(( ( ${crtState[1]} >>4 ) ${1:+*-1+1} )) -ne 0
}
Now a test:
$ if isVgaConnected; then echo yes; else echo no; fi
yes
It's plugged in, so now I unplug it:
$ if isVgaConnected; then echo yes; else echo no; fi
no
NOTE: ${1:+*-1+1}
permit a boolean argument: If something is present, answer would be inverted: ( crtState >> 4 ) * -1 + 1
.
and the final script:
#!/bin/bash
export crtProcEntry=/proc/acpi/video/VID/CRT0/state
isVgaConnected() {
local crtState
read -a < $crtProcEntry crtState
test $(( ( ${crtState[1]} >>4 ) ${1:+*-1+1} )) -ne 0
}
delay=.1
unset switch
isVgaConnected || switch=not
while :;do
while isVgaConnected $switch;do
sleep $delay
done
if [ "$switch" ];then
unset switch
echo VGA IS connected
# doing something while VGA is connected
else
switch=not
echo VGA is NOT connected.
# doing something else, maybe.
fi
done
WARNINGS: lighter than xrandr
, but not unimportant with a delay smaller than 0.02 seconds, the Bash script will go to the top of resource eaters process (top
)!
While this costs ~0.001 sec:
$ time read -a </proc/stat crtStat
This requires ~0.030 sec:
$ read -a < /proc/acpi/video/VID/CRT0/state crtState
This is big! So depending on what you need, delay
could be reasonably set between 0.5
and 2
.
EDIT #2
I've finally found something, using this:
Important disclaimer: Playing with /proc
and /sys
entries could break your system!!! So don't try the following on production systems.
mapfile watchFileList < <(
find /sys /proc -type f 2>/dev/null |
grep -i acpi\\\|i91
)
prompt=("/" "|" '\' '-');
l=0
while :; do
mapfile watchStat < <(
grep -H . ${watchFileList[@]} 2>/dev/null
)
for ((i=0;i<=${#watchStat[@]};i++)); do
[ "${watchStat[i]}" == "${oldStat[i]}" ] || echo ${watchStat[i]}
done
oldStat=("${watchStat[@]}")
sleep .5
printf "\r%s\r" ${prompt[l++]}
[ $l -eq 4 ]&&l=0
done
... after some cleaning of unwanted entrys:
for ((i=0;i<=${#watchFileList[@]};i++)); do
[[ "${watchFileList[$i]}" =~ /sys/firmware/acpi/interrupts/sci ]] &&
unset watchFileList[$i] && echo $i
done
I've been able to read this:
/proc/acpi/video/VID/CRT0/state:state: 0x1d
/proc/acpi/video/VID/CRT0/state:state: 0x0d
/proc/acpi/video/VID/CRT0/state:state: 0x1d
When I plug, unplug, and replug in monitor cable.
Original AnswerWhen the config is inquired (running system/preferences/monitor
or xrandr
), graphics cards do a type of scan, so running xrandr -q
give you the info, but you have to poll the status.
I've scanned all logs, (kernel, daemon, X and so forth) searching through /proc
& /sys
, and clearly nothing seems to exist that satisfies your request.
I've tried this too:
export spc50="$(printf "%50s" "")"
watch -n1 '
find /proc/acpi/video -type f |
xargs grep -H . |
sed "s/^\([^:]*):/\1'$spc50'}:/;
s/^\(.\{50\}\) *:/\1 /"'
After all that, if you run System/Preferences/Monitor
while no new screen has just been plugged in, nor unplugged, the tool will appear simply (normally). But if you've plugged or unplugged a screen before, at times you'll run this tool and you'll see your desktop make a type of reset or refresh (same if you run xrandr
).
This seems to confirm that this tool asks for xrandr
(or works in the same manner) by polling status periodically, starting at the time it's run.
You could try yourself:
$ for ((i=10;i--;)); do xrandr -q | grep ' connected' | wc -l; sleep 1; done
1
1
1
2
2
2
1
1
1
1
This will display how many screens (displays) are connected, for 10 seconds.
While this runs, plug and/or unplug your screen/monitor and look what's happens. So you could create a little Bash test function:
isVgaConnected() {
local xRandr=$(xrandr -q)
[ "$xRandr" == "${xRandr#*VGA1 con}" ] || return 0
return 1
}
which would be useable as in:
$ if isVgaConnected; then echo yes; fi
But be careful, xrandr
takes about 0.140 sec to 0.200 sec while no change happens on plugs and up to 0.700 seconds whenever something was plugged or unplugged just before (NOTE: It seems not to be a resource eater).
EDIT #1
To ensure I'm not teaching something incorrect, I've searched around the Web and docs, but didn't find anything about DBus and Screens.
Finally, I've run in two different windows dbus-monitor --system
(I've been playing with options too) and the little script I wrote:
$ for ((i=1000;i--;)); do isVgaConnected && echo yes || echo no; sleep .5; done
... and again plugged, than unplugged the monitor, many times. So now I could say:
- In this configuration, using i915 driver, there is no other way than running
xrandr -q
to know if a monitor is plugged in or not.
But use caution, because there doesn't appear to be other ways. For instance, xrandr
seems to share this info, so my GNOME desktop would switch to xinerama
automatically... when I ran xrandr
.
Some docs
- Script to toggle internal/external display for laptops
- ®Intel HD Graphics OpenSource Programmer's Reference Manual
The following lines appeared in udevadm monitor
KERNEL[46578.184280] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm)
UDEV [46578.195887] change /devices/pci0000:00/0000:00:02.0/drm/card0 (drm)
when attaching a monitor to the VGA-Connector. So there might be a way to figure this out.
For those who, for whatever reason, don't want to take the hotplug route, it is still possible to not poll within a script using inotifywait:
#!/bin/bash SCREEN_LEFT=DP2 SCREEN_RIGHT=eDP1 START_DELAY=5 renice +19 $$ >/dev/null sleep $START_DELAY OLD_DUAL="dummy" while [ 1 ]; do DUAL=$(cat /sys/class/drm/card0-DP-2/status) if [ "$OLD_DUAL" != "$DUAL" ]; then if [ "$DUAL" == "connected" ]; then echo 'Dual monitor setup' xrandr --output $SCREEN_LEFT --auto --rotate normal --pos 0x0 --output $SCREEN_RIGHT --auto --rotate normal --below $SCREEN_LEFT else echo 'Single monitor setup' xrandr --auto fi OLD_DUAL="$DUAL" fi inotifywait -q -e close /sys/class/drm/card0-DP-2/status >/dev/null done
It is best invoked from your .xsessionrc, not forgetting the ending &. Polling with xrandr gave serious usability issues on my brand new laptop (mouse would stall periodically).