Difference between revisions of "Sample Fn-F7 script"
(how to run a RandR GUI using Fn-F7) |
(Debugging proposal to work with xrandr 1.2, with ATI card: only comment; needs comment out/ uncomment) |
||
Line 105: | Line 105: | ||
INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g") | INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g") | ||
EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g") | EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g") | ||
+ | # I recommend to replace these prior two statements, since otherwise with xrandr 1.2 it produces wrong results: | ||
+ | # a textportion "(normal" otherwise still remains when a screen is connected, but switched off (by e.g. toggling!) | ||
+ | # (comment out the prior two lines, and uncomment the following two lines:) | ||
+ | # INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" ) | ||
+ | # EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" ) | ||
+ | |||
if [ -z "$INTERNAL_STATE" ]; then | if [ -z "$INTERNAL_STATE" ]; then | ||
Line 121: | Line 127: | ||
function screen_external(){ | function screen_external(){ | ||
+ | # recommend exchange the order, since otherwise doesn't work | ||
+ | # with my ati adapter (please uncomment if needed, and | ||
+ | #comment out the other two): | ||
+ | # $SU "xrandr --output $EXTERNAL_OUTPUT --auto" | ||
+ | # $SU "xrandr --output $INTERNAL_OUTPUT --off" | ||
$SU "xrandr --output $INTERNAL_OUTPUT --off" | $SU "xrandr --output $INTERNAL_OUTPUT --off" | ||
$SU "xrandr --output $EXTERNAL_OUTPUT --auto" | $SU "xrandr --output $EXTERNAL_OUTPUT --auto" |
Revision as of 20:35, 27 February 2009
This guide will help you configure Fn-F7 key combination to toggle between internal, mirror, external, or both screens. This was tested on ThinkPad X60s running Fedora 8, please comment if it works or does not work for you.
Works like a charm on X61s with Xubuntu 7.10. Working with R60e with some modifications noted below.
The script does not work when using fglrx or a version of XRandR < 1.2, because there is no dynamic display switching support then. When fglrx is used, aticonfig can be used for the switching in this Script for Dynamic Display Management with fglrx.
Contents
configuring the virtual screen size
Add a "Virtual" statement to your /etc/X11/xorg.conf, the total resolution should be large enough to fit all your screens in the configuration you want, for example if you have 1600x1200 monitor to the left of your internal 1024x768 monitor, your total max resolution is 2624x1200 (See Xorg RandR 1.2 for more details):
Section "Screen" Identifier "Screen0" Device "Videocard0" DefaultDepth 24 SubSection "Display" Viewport 0 0 Depth 24 Virtual 2624 1200 EndSubSection EndSection
Restart X server at this point (i.e. logout and login).
configuring acpi
Create /etc/acpi/events/thinkpad.conf:
<bash> event=ibm/hotkey HKEY 00000080 00001007 action=/usr/local/sbin/thinkpad-fn-f7 </bash>
or you may (eg Ubuntu 7.10) already have /etc/acpi/events/ibm-videobtn <bash>
- /etc/acpi/events/ibm-videobtn
- This is called when the user presses the video button. It is currently
- a placeholder.
event=ibm/hotkey HKEY 00000080 00001007 action=/bin/true </bash> in which case modify the line 'action=/bin/true' to run the script as above.
It may also be necessary to enable acpi events as per How to get special keys to work with (in root terminal)
# echo enable,0x084e > /proc/acpi/ibm/hotkey
Note this command isn't persistent. so you will also need to add the line to /etc/rc.local to enable hotkeys at boot, and to re-enable the hotkeys after suspend to disk or RAM, create the file
/etc/acpi/resume.d/91-ibm-hotkey-enable.sh consisting of
<bash>
- !/bin/bash
- enable ibm-hotkeys (specifically Fn2, Fn7)
- 12 bit mask, little end is F1 default 0x080c = F12+F4+F3
echo enable,0x084e > /proc/acpi/ibm/hotkey </bash>
ref: [frosch.org.uk] and [ibm-acpi.sourceforge]
identify output devices
Note the names of your output devices as you will have to change EXTERNAL_OUTPUT and INTERNAL_OUTPUT to what xrandr shows, for example VGA and LVDS in this case:
$ xrandr -q VGA connected 1600x1200+0+0 (normal left inverted right x axis y axis) 432mm x 324mm ... LVDS connected (normal left inverted right x axis y axis)
The bash script
Create /usr/local/sbin/thinkpad-fn-f7, you can set EXTERNAL_LOCATION to one of: left, right, above, or below. <bash>
- !/bin/bash
- External output may be "VGA" or "VGA-0" or "DVI-0" or "TMDS-1"
EXTERNAL_OUTPUT="VGA" INTERNAL_OUTPUT="LVDS" EXTERNAL_LOCATION="left"
- Figure out which user and X11 display to work on
- TODO there has to be a better way to do this?
X_USER=$(w -h -s | grep ":[0-9]\W" | head -1 | awk '{print $1}') export DISPLAY=$(w -h -s | grep ":[0-9]\W" | head -1 | awk '{print $3}')
- Switch to X user if necessary
if [ "$X_USER" != "$USER" ]; then
SU="su $X_USER -c"
else
SU="sh -c"
fi
case "$EXTERNAL_LOCATION" in
left|LEFT) EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT" ;; right|RIGHT) EXTERNAL_LOCATION="--right-of $INTERNAL_OUTPUT" ;; top|TOP|above|ABOVE) EXTERNAL_LOCATION="--above $INTERNAL_OUTPUT" ;; bottom|BOTTOM|below|BELOW) EXTERNAL_LOCATION="--below $INTERNAL_OUTPUT" ;; *) EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT" ;;
esac
- Figure out current state
INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g") EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g")
- I recommend to replace these prior two statements, since otherwise with xrandr 1.2 it produces wrong results:
- a textportion "(normal" otherwise still remains when a screen is connected, but switched off (by e.g. toggling!)
- (comment out the prior two lines, and uncomment the following two lines:)
- INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" )
- EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" )
if [ -z "$INTERNAL_STATE" ]; then
STATE="external"
elif [ -z "$EXTERNAL_STATE" ]; then
STATE="internal"
else
INTERNAL_STATE=$(echo $INTERNAL_STATE | sed "s/[0-9]*x[0-9]*//") EXTERNAL_STATE=$(echo $EXTERNAL_STATE | sed "s/[0-9]*x[0-9]*//") if [ "$INTERNAL_STATE" = "$EXTERNAL_STATE" ]; then STATE="mirror" else STATE="both" fi
fi
function screen_external(){
- recommend exchange the order, since otherwise doesn't work
- with my ati adapter (please uncomment if needed, and
- comment out the other two):
- $SU "xrandr --output $EXTERNAL_OUTPUT --auto"
- $SU "xrandr --output $INTERNAL_OUTPUT --off"
$SU "xrandr --output $INTERNAL_OUTPUT --off" $SU "xrandr --output $EXTERNAL_OUTPUT --auto"
}
function screen_internal(){
$SU "xrandr --output $EXTERNAL_OUTPUT --off" $SU "xrandr --output $INTERNAL_OUTPUT --auto"
}
function screen_mirror(){
$SU "xrandr --output $INTERNAL_OUTPUT --auto" $SU "xrandr --output $EXTERNAL_OUTPUT --auto --same-as $INTERNAL_OUTPUT"
}
function screen_both(){
$SU "xrandr --output $INTERNAL_OUTPUT --auto" $SU "xrandr --output $EXTERNAL_OUTPUT --auto $EXTERNAL_LOCATION"
}
function screen_toggle(){
case "$STATE" in internal) screen_mirror ;; mirror) screen_external ;; external) screen_both ;; both) screen_internal ;; *) screen_internal ;; esac
}
- What should we do?
DO="$1" if [ -z "$DO" ]; then
if [ $(basename $0) = "thinkpad-fn-f7" ]; then DO="toggle" fi
fi
case "$DO" in
toggle) screen_toggle ;; internal) screen_internal ;; external) screen_external ;; mirror) screen_mirror ;; both) screen_both ;; status) echo "Current Fn-F7 state is: $STATE" echo echo "Attached monitors:" $SU xrandr | grep "\Wconnected" | sed "s/^/ /" ;; *) echo "usage: $0 <command>" >&2 echo >&2 echo " commands:" >&2 echo " status" >&2 echo " internal" >&2 echo " external" >&2 echo " mirror" >&2 echo " both" >&2 echo " toggle" >&2 echo >&2 ;;
esac </bash>
set permissions and restart acpi
As root, or using sudo run the following commands,
$ sudo chmod 755 /usr/local/sbin/thinkpad-fn-f7
$ sudo service acpid restart
OR$ sudo /etc/init.d/acpid restart
You should be ready to go, just press Fn-F7 to try.
Alternative script using .Xauthority rather than su
On systems where the previous script has trouble with the "su" command (I was getting "Can't open display -" errors in /var/log/acpid on Ubuntu 8.04), you can try getting the magic cookie out of the .Xauthority file.
Each user has an ~/.Xauthority file with one line for each X display containing a 'magic cookie' (Run $ xauth list
to see the contents). The Xserver reads the record in ~/.Xauthority matching its display. When an X client application starts it also looks for that record and passes the magic cookie to the server. If it matches, the connection to the Xserver is allowed. Use the following script as an alternative
action=/usr/local/sbin/toggle-display.sh
for /etc/acpi/events/ibm-videobtn
<bash>
- !/bin/bash
- From: http://www.thinkwiki.org/wiki/Sample_Fn-F7_script
- External output may be "VGA" or "VGA-0" or "DVI-0" or "TMDS-1"
EXTERNAL_OUTPUT="VGA" INTERNAL_OUTPUT="LVDS"
- EXTERNAL_LOCATION may be one of: left, right, above, or below
EXTERNAL_LOCATION="right"
case "$EXTERNAL_LOCATION" in
left|LEFT) EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT" ;; right|RIGHT) EXTERNAL_LOCATION="--right-of $INTERNAL_OUTPUT" ;; top|TOP|above|ABOVE) EXTERNAL_LOCATION="--above $INTERNAL_OUTPUT" ;; bottom|BOTTOM|below|BELOW) EXTERNAL_LOCATION="--below $INTERNAL_OUTPUT" ;; *) EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT" ;;
esac
function screen_external(){
xrandr --output $INTERNAL_OUTPUT --off xrandr --output $EXTERNAL_OUTPUT --auto
}
function screen_internal(){
xrandr --output $EXTERNAL_OUTPUT --off xrandr --output $INTERNAL_OUTPUT --auto
}
function screen_mirror(){
xrandr --output $INTERNAL_OUTPUT --auto xrandr --output $EXTERNAL_OUTPUT --auto --same-as $INTERNAL_OUTPUT
}
function screen_both(){
xrandr --output $INTERNAL_OUTPUT --auto xrandr --output $EXTERNAL_OUTPUT --auto $EXTERNAL_LOCATION
}
function screen_toggle(){
# Figure out current state INTERNAL_STATE=$(xrandr | grep ^$INTERNAL_OUTPUT | grep con | sed "s/.*connected //" | sed "s/(.*//") EXTERNAL_STATE=$(xrandr | grep ^$EXTERNAL_OUTPUT | grep con | sed "s/.*connected //" | sed "s/(.*//")
if [ -z "$INTERNAL_STATE" ]; then STATE="external" elif [ -z "$EXTERNAL_STATE" ]; then STATE="internal" else INTERNAL_STATE=$(echo $INTERNAL_STATE | sed "s/[0-9]*x[0-9]*//") EXTERNAL_STATE=$(echo $EXTERNAL_STATE | sed "s/[0-9]*x[0-9]*//") if [ "$INTERNAL_STATE" = "$EXTERNAL_STATE" ]; then STATE="mirror" else STATE="both" fi fi
case "$STATE" in internal) screen_mirror ;; mirror) screen_external ;; external) screen_both ;; both) screen_internal ;; *) screen_internal ;; esac
}
- based on /etc/acpi/screenblank.sh (Ubuntu 7.10)
- . /usr/share/acpi-support/power-funcs # for getXuser
getXuser() {
user=`finger| grep -m1 ":$displaynum " | awk '{print $1}'` if [ x"$user" = x"" ]; then user=`finger| grep -m1 ":$displaynum" | awk '{print $1}'` fi if [ x"$user" != x"" ]; then userhome=`getent passwd $user | cut -d: -f6` export XAUTHORITY=$userhome/.Xauthority else export XAUTHORITY="" fi
}
- end of getXuser from /usr/share/acpi-support/power-funcs
for x in /tmp/.X11-unix/*; do
displaynum=`echo $x | sed s#/tmp/.X11-unix/X##` getXuser; if [ x"$XAUTHORITY" != x"" ]; then export DISPLAY=":$displaynum" screen_toggle fi
done </bash>
A Python Toggle script
This is a somewhat over-elaborate script which could be cut down when run by the /usr/local/sbin/toggle-display.sh script above. It was written to explore all the possibilities rather than economy of execution. The functions 'toggle_full', 'position', 'toggle_limited' and the OptionParser in 'main' may be omitted with suitable changes to function7. The appropriate outputs can be specified on the command line and it is not necessary to call it via acpi.
eg you can run it as $ /usr/local/bin/toggle.py --help
<python>
#! /usr/bin/python # -*- coding: utf-8 -*- # # stinkpad(a)blueyonder.co.uk 2007-11-26 """Toggle internal and external displays (equivalent to ThinkPad Fn7) Simple; cloned: on+off, on+on, off+on. Full; as simple plus xinerama: right, below, left, above. Limited; as full but display will overlap if virtual screen is too small. Use 'xrandr -q' to determine output names. Further information: http://www.thinkwiki.org/wiki/Xorg_RandR_1.2 """ __usage__ = "usage: %prog [--help]|[[-i internal][-e external][-d displays]]" __version__ = "toggle [djclark.eu 2007-11-26]" # # Output names; Intel: LVDS VGA TV TMDS-1 TMDS-2 # ATI: LVDS VGA-0 S-video DVI-0 LAPTOP = 'LVDS' MONITOR = 'VGA' SEQUENCE = 'simple' # import sys import os import re
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm" REGEX_OUTPUT = re.compile(r""" (?x) # ignore whitespace ^ # start of string (?P<output>[A-Za-z0-9\-]*)[ ] # LVDS VGA etc (?P<connect>(dis)?connected)[ ] # dis/connected (( # a group (?P<width>\d+)x # either 1024x768+0+0 (?P<height>\d+)[+] (?P<horizontal>\d+)[+] (?P<vertical>\d+) )|[\D]) # or not a digit .* # ignore rest of line """) # "Screen 0: minimum 320 x 200, current 1024 x 768, maximum 2624 x 1968" REGEX_SCREEN = re.compile(r""" (?x) # ignore whitespace ^ # start of string Screen[ ] (?P<screen>\d)[: ]+ minimum[ ] (?P<minWidth>\d+)[ x]+ (?P<minHeight>\d+)[, ]+ current[ ]+ (?P<curWidth>\d+)[ x]+ (?P<curHeight>\d+)[, ]+ maximum[ ]+ (?P<maxWidth>\d+)[ x]+ (?P<maxHeight>\d+) """)
def toggle_simple(d0, d1): """Toggle display states: on+off, on+on, off+on""" if d1['connect'] == 'disconnected': # external unplugged return ('auto','off',"") # switch off external if d1['width'] is 0: # external off return ('auto','auto',"") # both on if d0['width'] is 0: # laptop off return ('auto','off',"") # laptop on return ('off','auto',"") # both on, laptop off
def toggle_full(d0, d1): """Toggle display states: 1+0, 1+1, 0+1, 1+E, 1+S, 1+W, 1+N""" if d1['connect'] == 'disconnected': # external unplugged return ('auto','off',"") # switch off external place = '--%s ' + d0['output'] if d1['width'] == 0: # external off return ('auto','auto',place%'same-as') # external on if d0['width'] == 0: # laptop off return ('auto','off',"") # laptop on if d1['horizontal'] > 0: # external to right return ('auto','auto',place%'below') # make below if d1['vertical'] > 0: # external below return ('auto','auto',place%'left-of') # make left if d0['horizontal'] > 0: # external left return ('auto','auto',place%'above') # make above if d0['vertical'] > 0: # external above return ('off','auto',"") # laptop off return ('auto','auto',place%'right-of') # is same, make right
def position(orientation, da, db, screen): """Calculate offset position of second display""" p = 'auto --pos %sx%s' if orientation == 'V': if (da['height'] + db['height']) <= screen['maxHeight']: return p%(0, da['height']) return p%(0, screen['maxHeight'] - db['height']) else: if (da['width'] + db['width']) <= screen['maxWidth']: return p%(da['width'],0) return p%(screen['maxWidth'] - db['width'],0)
def toggle_limited(d0, d1, sz): """Toggle display states (overlapped): 1+0,1+1,0+1,1+E,1+S,1+W,1+N""" if d1['connect'] == 'disconnected': # external unplugged return ('auto','off') # switch off external if d1['width'] == 0: # external off return ('auto --pos 0x0','auto --pos 0x0') # both on if d0['width'] == 0: # laptop off return ('auto --pos 0x0','off') # laptop on if d1['horizontal'] > 0: # external to right return ('auto --pos 0x0',position('V',d0,d1,sz)) # put *below if d1['vertical'] > 0: # external below return (position('H',d1,d0,sz),'auto --pos 0x0') # put *left if d0['horizontal'] > 0: # external left return (position('V',d1,d0,sz),'auto --pos 0x0') # put *above if d0['vertical'] > 0: # external above return ('off','auto --pos 0x0') # laptop off return ('auto --pos 0x0',position('H',d0,d1,sz)) # both, put*right
class DisplayNameError(UnboundLocalError): """Internal or External Display Name not found by xrandr -q """
def function7(disp0=LAPTOP, disp1=MONITOR, seq=SEQUENCE): """Use xrandr to read current display state and change state""" for line in os.popen('xrandr -q').read().splitlines(): if line.startswith(disp0,0) : d0_state = REGEX_OUTPUT.match(line).groupdict() elif line.startswith(disp1,0): d1_state = REGEX_OUTPUT.match(line).groupdict() elif line.startswith('Screen',0): screen_size = REGEX_SCREEN.match(line).groupdict() else: pass for i in ('width','height','horizontal','vertical'): try: d0_state[i] = int(d0_state[i]) except TypeError: d0_state[i] = 0 except UnboundLocalError: raise DisplayNameError, 'Internal Display: %s not found'% disp0 try: d1_state[i] = int(d1_state[i]) except TypeError: d1_state[i] = 0 except UnboundLocalError: raise DisplayNameError, 'External Display: %s not found'% disp1 for i in screen_size.keys(): try: screen_size[i] = int(screen_size[i]) except TypeError: screen_size[i] = 0 # toggle = toggle_simple xrandr ='xrandr --output '+disp0+' --%s --output '+disp1+' --%s %s' if seq == 'full': toggle = toggle_full if seq == 'limited': toggle = toggle_limited xrandr ='xrandr --output '+disp0+' --%s --output '+disp1+' --%s' os.popen(xrandr % toggle(d0_state, d1_state, screen_size)) else: os.popen(xrandr % toggle(d0_state, d1_state))
def main(): """ Command line options """ global LAPTOP,MONITOR,SEQUENCE from optparse import OptionParser p = OptionParser(usage=__usage__, version=__version__, description=__doc__) p.set_defaults(internal=LAPTOP, external=MONITOR, displays=SEQUENCE) p.add_option('-i','--internal', dest="internal", metavar=LAPTOP, help="internal display") p.add_option('-e','--external', dest="external", metavar=MONITOR, help="external display") p.add_option('-d','--displays', dest="displays", action="store", choices=('simple', 'limited', 'full'), metavar=SEQUENCE, help='simple/limited/full') (opt, args) = p.parse_args() try: function7(opt.internal, opt.external, opt.displays) except DisplayNameError, err: print '\n'+str(err)+'\n' print os.popen('xrandr -q').read() # if __name__ == '__main__': #only when run from cmd line main()
</python>
This modified thinkpad-fn-f7 calls the toggle python script:
<bash>
#!/bin/bash # usr/local/sbin/toggle-display.sh # based on /etc/acpi/screenblank.sh (Ubuntu 7.10) # # . /usr/share/acpi-support/power-funcs # for getXuser umask 022; PATH="$PATH:/usr/bin/X11" getXuser() { user=`finger| grep -m1 ":$displaynum " | awk '{print $1}'` if [ x"$user" = x"" ]; then user=`finger| grep -m1 ":$displaynum" | awk '{print $1}'` fi if [ x"$user" != x"" ]; then userhome=`getent passwd $user | cut -d: -f6` export XAUTHORITY=$userhome/.Xauthority else export XAUTHORITY="" fi } # end of getXuser from /usr/share/acpi-support/power-funcs # for x in /tmp/.X11-unix/*; do displaynum=`echo $x | sed s#/tmp/.X11-unix/X##` getXuser; if [ x"$XAUTHORITY" != x"" ]; then export DISPLAY=":$displaynum" ## . /usr/share/acpi-support/screenblank.sh /usr/local/bin/toggle.py fi done
</bash>
Python XRandR
[Python XRandR] is a set of python bindings to xrandr. The code is currently in development and the following code though much simpler than the python code above may not work reliably.
from xrandr import xrandr def toggle_simple(): """Toggle display states: on+off, off+on, on+on off+off""" screen = xrandr.get_current_screen() outputs = screen.get_outputs() for o in outputs: if o.is_connected(): if o.is_active(): o.disable() print 'disable %s'% o.name else: o.set_to_preferred_mode() print 'set to preferred mode %s'% o.name break screen.apply_output_config()
Having Fn-F7 run a RandR GUI
Instead of using it to toggle various screen(s) configurations, one may want to have it run a RandR GUI, such as grandr.
The following Debian wishlist bugs against the acpi-support package have patches attached that implement this behaviour using acpid; these patches are not Debian-specific, and actually provide example configuration for anyone interested into this:
- http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=515794
- http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=515796
References
[grandr] graphical interface to xrandr using GTK+ libraries.
[python-xrandr] Python bindings to xrandr which should enable a less clunky version of the python script above. Under development.