Difference between revisions of "Sample Fn-F7 script"

From ThinkWiki
Jump to: navigation, search
m (A Python Toggle script)
m
 
(44 intermediate revisions by 14 users not shown)
Line 3: Line 3:
 
Works like a charm on X61s with Xubuntu 7.10.
 
Works like a charm on X61s with Xubuntu 7.10.
 
Working with R60e with some modifications noted below.
 
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]].
 +
 +
{{NOTE|On a modern distribution like Fedora 12, the Fn-F7 key works out-of-the-box, and no additional scripts are needed to switch between external display modes}}
  
 
==configuring the virtual screen size==
 
==configuring the virtual screen size==
Line 23: Line 27:
 
Create /etc/acpi/events/thinkpad.conf:
 
Create /etc/acpi/events/thinkpad.conf:
  
event=ibm/hotkey HKEY 00000080 00001007
+
<syntaxhighlight lang="bash">
action=/usr/local/sbin/thinkpad-fn-f7
+
# bash script
 +
event=ibm/hotkey HKEY 00000080 00001007
 +
action=/usr/local/sbin/thinkpad-fn-f7
 +
</syntaxhighlight>
  
or you may (eg Ubuntu 7.10) already have /etc/acpi/ibm-videobtn
+
 
# /etc/acpi/events/ibmvideobtn
+
or you may (eg Ubuntu 7.10) already have /etc/acpi/events/ibm-videobtn
# This is called when the user presses the video button. It is currently
+
<syntaxhighlight lang="bash">
# a placeholder.
+
# /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
 +
</syntaxhighlight>
 
in which case modify the line 'action=/bin/true' to run the script as above.
 
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
+
 
$echo enable,0xffff > /proc/acpi/ibm/hotkey
+
It may also be necessary to enable acpi events as per [[How to get special keys to work]] with (in root terminal)
 +
{{cmdroot|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
 +
 
 +
<syntaxhighlight lang="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
 +
</syntaxhighlight>
 +
 
 +
ref: [[http://tilmanfrosch.de/wp/index.php/2007/05/05/howto-make-a-ubuntu-linux-on-an-ibm-t41-thinkpad-work-with-an-external-widescreen-wxga-display/  frosch.org.uk]] and [[http://ibm-acpi.sourceforge.net/README ibm-acpi.sourceforge]]
  
 
==identify output devices==
 
==identify output devices==
Line 44: Line 68:
 
==The bash script==
 
==The bash script==
 
Create /usr/local/sbin/thinkpad-fn-f7, you can set EXTERNAL_LOCATION to one of: left, right, above, or below.
 
Create /usr/local/sbin/thinkpad-fn-f7, you can set EXTERNAL_LOCATION to one of: left, right, above, or below.
 +
<syntaxhighlight lang="bash">
 +
#!/bin/bash
  
#!/bin/bash
+
# External output may be "VGA" or "VGA-0" or "DVI-0" or "TMDS-1"
+
EXTERNAL_OUTPUT="VGA"
# External output may be "VGA" or "VGA-0" or "DVI-0"
+
INTERNAL_OUTPUT="LVDS"
EXTERNAL_OUTPUT="'''VGA'''"
+
EXTERNAL_LOCATION="left"
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?
# Figure out which user and X11 display to work on
+
X_USER=$(w -h -s | grep ":[0-9]\W" | head -1 | awk '{print $1}')
# TODO there has to be a better way to do this?
+
export DISPLAY=$(w -h -s | grep ":[0-9]\W" | head -1 | awk '{print $3}')
X_USER=$(w -h -s | grep ":[0-9]" | head -1 | awk '{print $1}')
+
 
export DISPLAY=$(w -h -s | grep ":[0-9]" | head -1 | awk '{print $3}')
+
# Switch to X user if necessary
+
if [ "$X_USER" != "$USER" ]; then
# Switch to X user if necessary
+
      SU="su $X_USER -c"
if [ "$X_USER" != "$USER" ]; then
+
else
        SU="su $X_USER -c"
+
      SU="sh -c"
fi
+
fi
+
 
case "$EXTERNAL_LOCATION" in
+
case "$EXTERNAL_LOCATION" in
        left|LEFT)
+
      left|LEFT)
                EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT"
+
              EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT"
                ;;
+
              ;;
        right|RIGHT)
+
      right|RIGHT)
                EXTERNAL_LOCATION="--right-of $INTERNAL_OUTPUT"
+
              EXTERNAL_LOCATION="--right-of $INTERNAL_OUTPUT"
                ;;
+
              ;;
        top|TOP|above|ABOVE)
+
      top|TOP|above|ABOVE)
                EXTERNAL_LOCATION="--above $INTERNAL_OUTPUT"
+
              EXTERNAL_LOCATION="--above $INTERNAL_OUTPUT"
                ;;
+
              ;;
        bottom|BOTTOM|below|BELOW)
+
      bottom|BOTTOM|below|BELOW)
                EXTERNAL_LOCATION="--below $INTERNAL_OUTPUT"
+
              EXTERNAL_LOCATION="--below $INTERNAL_OUTPUT"
                ;;
+
              ;;
        *)
+
      *)
                EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT"
+
              EXTERNAL_LOCATION="--left-of $INTERNAL_OUTPUT"
                ;;
+
              ;;
esac
+
esac
+
 
# Figure out current state
+
# Figure out current state
INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep con | sed "s/.*connected //" | sed "s/(.*//")
+
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/(.*//")
+
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:
if [ -z "$INTERNAL_STATE" ]; then
+
# a textportion "(normal" otherwise still remains when a screen is connected, but switched off (by e.g. toggling!)
        STATE="external"
+
# (comment out the prior two lines, and uncomment the following two lines:)
elif [ -z "$EXTERNAL_STATE" ]; then
+
# INTERNAL_STATE=$($SU xrandr | grep ^$INTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" )
        STATE="internal"
+
# EXTERNAL_STATE=$($SU xrandr | grep ^$EXTERNAL_OUTPUT | grep " con" | sed "s/.*connected//" | sed "s/ //" | sed "s/ .*//g"| sed "s/(normal//g" )
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 [ -z "$INTERNAL_STATE" ]; then
        if [ "$INTERNAL_STATE" = "$EXTERNAL_STATE" ]; then
+
      STATE="external"
                STATE="mirror"
+
elif [ -z "$EXTERNAL_STATE" ]; then
        else
+
      STATE="internal"
                STATE="both"
+
else
        fi
+
      INTERNAL_STATE=$(echo $INTERNAL_STATE | sed "s/[0-9]*x[0-9]*//")
fi
+
      EXTERNAL_STATE=$(echo $EXTERNAL_STATE | sed "s/[0-9]*x[0-9]*//")
+
      if [ "$INTERNAL_STATE" = "$EXTERNAL_STATE" ]; then
function screen_external(){
+
              STATE="mirror"
        $SU "xrandr --output $INTERNAL_OUTPUT --off"
+
      else
        $SU "xrandr --output $EXTERNAL_OUTPUT --auto"
+
              STATE="both"
}
+
      fi
+
fi
function screen_internal(){
+
 
        $SU "xrandr --output $EXTERNAL_OUTPUT --off"
+
function screen_external(){
        $SU "xrandr --output $INTERNAL_OUTPUT --auto"
+
# recommend exchange the order, since otherwise doesn't work
}
+
# with my ati adapter (please uncomment if needed, and
+
#comment out the other two):
function screen_mirror(){
+
#      $SU "xrandr --output $EXTERNAL_OUTPUT --auto"
        $SU "xrandr --output $INTERNAL_OUTPUT --auto"
+
#      $SU "xrandr --output $INTERNAL_OUTPUT --off"
        $SU "xrandr --output $EXTERNAL_OUTPUT --auto --same-as $INTERNAL_OUTPUT"
+
      $SU "xrandr --output $INTERNAL_OUTPUT --off"
}
+
      $SU "xrandr --output $EXTERNAL_OUTPUT --auto"
+
}
function screen_both(){
+
 
        $SU "xrandr --output $INTERNAL_OUTPUT --auto"
+
function screen_internal(){
        $SU "xrandr --output $EXTERNAL_OUTPUT --auto $EXTERNAL_LOCATION"
+
      $SU "xrandr --output $EXTERNAL_OUTPUT --off"
}
+
      $SU "xrandr --output $INTERNAL_OUTPUT --auto"
+
}
function screen_toggle(){
+
 
        case "$STATE" in
+
function screen_mirror(){
                internal)
+
      $SU "xrandr --output $INTERNAL_OUTPUT --auto"
                        screen_mirror
+
      $SU "xrandr --output $EXTERNAL_OUTPUT --auto --same-as $INTERNAL_OUTPUT"
                        ;;
+
}
                mirror)
+
 
                        screen_external
+
function screen_both(){
                        ;;
+
      $SU "xrandr --output $INTERNAL_OUTPUT --auto"
                external)
+
      $SU "xrandr --output $EXTERNAL_OUTPUT --auto $EXTERNAL_LOCATION"
                        screen_both
+
}
                        ;;
+
 
                both)
+
function screen_toggle(){
                        screen_internal
+
      case "$STATE" in
                        ;;
+
              internal)
                *)
+
                      screen_mirror
                        screen_internal
+
                      ;;
                        ;;
+
              mirror)
        esac
+
                      screen_external
}
+
                      ;;
+
              external)
# What should we do?
+
                      screen_both
DO="$1"
+
                      ;;
if [ -z "$DO" ]; then
+
              both)
        if [ $(basename $0) = "thinkpad-fn-f7" ]; then
+
                      screen_internal
                DO="toggle"
+
                      ;;
        fi
+
              *)
fi
+
                      screen_internal
+
                      ;;
case "$DO" in
+
      esac
        toggle)
+
}
                screen_toggle
+
 
                ;;
+
# What should we do?
        internal)
+
DO="$1"
                screen_internal
+
if [ -z "$DO" ]; then
                ;;
+
      if [ $(basename $0) = "thinkpad-fn-f7" ]; then
        external)
+
              DO="toggle"
                screen_external
+
      fi
                ;;
+
fi
        mirror)
+
 
                screen_mirror
+
case "$DO" in
                ;;
+
      toggle)
        both)
+
              screen_toggle
                screen_both
+
              ;;
                ;;
+
      internal)
        status)
+
              screen_internal
                echo "Current Fn-F7 state is: $STATE"
+
              ;;
                echo
+
      external)
                echo "Attached monitors:"
+
              screen_external
                $SU xrandr | grep "\Wconnected" | sed "s/^/ /"
+
              ;;
                ;;
+
      mirror)
        *)
+
              screen_mirror
                echo "usage: $0 <command>" >&2
+
              ;;
                echo >&2
+
      both)
                echo "  commands:" >&2
+
              screen_both
                echo "          status" >&2
+
              ;;
                echo "          internal" >&2
+
      status)
                echo "          external" >&2
+
              echo "Current Fn-F7 state is: $STATE"
                echo "          mirror" >&2
+
              echo
                echo "          both" >&2
+
              echo "Attached monitors:"
                echo "          toggle" >&2
+
              $SU xrandr | grep "\Wconnected" | sed "s/^/ /"
                echo >&2
+
              ;;
                ;;
+
      *)
esac
+
              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
 +
</syntaxhighlight>
  
 
== set permissions and restart acpi ==
 
== set permissions and restart acpi ==
Line 193: Line 231:
  
 
==Alternative script using .Xauthority rather than su ==
 
==Alternative script using .Xauthority rather than su ==
Use this script as an alternative  
+
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 {{cmduser| 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
 
  action=/usr/local/sbin/toggle-display.sh
 
for /etc/acpi/events/ibm-videobtn
 
for /etc/acpi/events/ibm-videobtn
  
#!/bin/bash
+
<syntaxhighlight lang="bash">
# usr/local/sbin/toggle-display.sh
+
#!/bin/bash
# stinkpad(a)blueyonder.co.uk    2007-11-24
+
# From: http://www.thinkwiki.org/wiki/Sample_Fn-F7_script
# based on /etc/acpi/screenblank.sh (Ubuntu 7.10)
+
#
#
+
# External output may be "VGA" or "VGA-0" or "DVI-0" or "TMDS-1"
# . /usr/share/acpi-support/power-funcs        # for getXuser
+
EXTERNAL_OUTPUT="VGA"
umask 022;
+
INTERNAL_OUTPUT="LVDS"
PATH="$PATH:/usr/bin/X11"
+
# EXTERNAL_LOCATION may be one of: left, right, above, or below
getXuser() {
+
EXTERNAL_LOCATION="right"
        user=`finger| grep -m1 ":$displaynum " | awk '{print $1}'`
+
 
        if [ x"$user" = x"" ]; then
+
case "$EXTERNAL_LOCATION" in
                user=`finger| grep -m1 ":$displaynum" | awk '{print $1}'`
+
      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
 +
</syntaxhighlight>
 +
 
 +
==Script to switch between Internal, VGA and S-video==
 +
This highly overdone script allows to switch between all combinations of Internal, VGA and S-video out. VGA and and S-video at the same time are not allowed on my hardware (I have a T41 with Radeon 7500), so I masked these combinations out. Instead of disabling the internal display with xrandr, I turn off the backlight. One of the allowed combinations is all displays off, and xorg crashes if turning off all displays with xrandr. The script depends on the debian package acpi-support, which is also found in ubuntu and crunchbang (which is what I am using). The dependency can easily be broken out though. Can easily be modified to fulfill other needs, i.e. if you turn off your backlight with something else than radeontool. There is also an OSD displayed in the upper left corner of all active displays.
 +
 
 +
<syntaxhighlight lang="bash">
 +
#/bin/sh
 +
 
 +
. /usr/share/acpi-support/power-funcs        # for getXuser
 +
. /etc/default/acpi-support                  # for RADEON_LIGHT
 +
 
 +
# First prevent concurrent execution
 +
 
 +
LOCK_FILE=/tmp/swapscreenslock
 +
WAITING_TIME=1
 +
LOGGER="`which logger 2>/dev/null` -i"
 +
if (set -C; : > $LOCK_FILE) 2>/dev/null; then : ; else
 +
    # Lock file exists - wait as long as theoretically is needed for the other script to finish and retry
 +
    sleep $WAITING_TIME
 +
    if (set -C; : > $LOCK_FILE) 2>/dev/null; then : ; else
 +
        # Lock file still exists - we should never end up here! Consider removing the lock file.
 +
        $LOGGER "Lock file exists - earlier call to script stalled? Consider removing lock file \"$LOCK_FILE\". Exiting."
 +
        exit 1
 +
    fi
 +
fi
 +
trap 'rm $LOCK_FILE' EXIT
 +
 
 +
XRANDR=`which xrandr 2>/dev/null`
 +
OSD_CAT=`which osd_cat 2>/dev/null`
 +
LAPTOP="LVDS"
 +
VGA="VGA-0"
 +
SVIDEO="S-video"
 +
VGA_POSITION="--right-of $LAPTOP"
 +
#VGA_POSITION="--same-as $LAPTOP"    # Mirror
 +
SVIDEO_POSITION="--right-of $LAPTOP"
 +
 
 +
OSD_GENERICARGS="--delay=3 --age=0 --font=-adobe-helvetica-bold-r-normal-*-*-180-*-*-p-*-*-* --colour=green --shadow=1"
 +
OSD_HOFFSET=25
 +
OSD_VOFFSET=25
 +
 
 +
function screen_toggle {
 +
    # Figure out current state
 +
    XRANDR_STATUS=`su $user -c "$XRANDR -q"`
 +
 
 +
    # Laptop is of course always connected, however I'll leave the script this
 +
    # way for making it easier to make the script a little more generic.
 +
    LAPTOP_CONN=`echo "$XRANDR_STATUS" | grep ^$LAPTOP | grep " connected"`
 +
    VGA_CONN=`echo "$XRANDR_STATUS" | grep ^$VGA | grep " connected"`
 +
    SVIDEO_CONN=`echo "$XRANDR_STATUS" | grep ^$SVIDEO | grep " connected"`
 +
 
 +
    LAPTOP_STATE=`radeontool light | grep 'looks on'`
 +
    VGA_STATE=`echo $VGA_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 +
    SVIDEO_STATE=`echo $SVIDEO_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 +
 
 +
    CONNMASK=0
 +
    [ -n "$LAPTOP_CONN" ] && CONNMASK=$(( CONNMASK + 1 ))
 +
    if [ -n "$VGA_CONN" ]; then
 +
        CONNMASK=$(( CONNMASK + 2 ))
 +
        [ -n "$SVIDEO_CONN" ] && CONNMASK=$(( CONNMASK + 4 ))
 +
    else
 +
        # If VGA is not connected, TV will overtake its bit
 +
        [ -n "$SVIDEO_CONN" ] && CONNMASK=$(( CONNMASK + 2 ))
 +
    fi
 +
 
 +
    STATE=0
 +
    if [ -n "$LAPTOP_STATE" ]; then STATE=$(( STATE + 1 )); fi
 +
    if [ -n "$VGA_CONN" ]; then
 +
        [ -n "$VGA_STATE" ] && STATE=$(( STATE + 2 ))
 +
        [ -n "$SVIDEO_STATE" ] && STATE=$(( STATE + 4 ))
 +
    else
 +
        # If VGA is not connected, TV will overtake its bit
 +
        [ -n "$SVIDEO_STATE" ] && STATE=$(( STATE + 2 ))
 +
    fi
 +
 
 +
    # Go to next state
 +
    STATE=$(( (STATE+1) % (CONNMASK+1) ))
 +
 
 +
    # Forbidden states are TV and VGA at the same time, regardless of internal state
 +
    STATE=$(( STATE % 6 ))
 +
 
 +
    # If no screen is listed as connected, probably xrandr failed - do nothing.
 +
    if [ $CONNMASK -ne 0 ]; then
 +
        OSDTEXT=
 +
 
 +
        if [ -n "$VGA_CONN" ]; then
 +
            # Turn on or off external VGA
 +
            if [ $(( (STATE >> 1) % 2 )) -ne 0 ]; then
 +
                OSDTEXT="$OSDTEXT $VGA"
 +
                su $user -c "$XRANDR --output $VGA $VGA_POSITION --auto"
 +
            else
 +
                su $user -c "$XRANDR --output $VGA --off"
 +
            fi
 +
 
 +
            # Turn on or off S-Video output
 +
            if [ $(( (STATE >> 2) % 2 )) -ne 0 ]; then
 +
                OSDTEXT="$OSDTEXT $SVIDEO"
 +
                su $user -c "$XRANDR --output $SVIDEO $SVIDEO_POSITION --auto"
 +
            else
 +
                [ -n "$SVIDEO_CONN" ] && su $user -c "$XRANDR --output $SVIDEO --off"
 +
            fi
 +
        else
 +
            # If VGA is not connected, TV will overtake its bit
 +
 
 +
            # Turn on or off S-Video output
 +
            if [ $(( (STATE >> 1) % 2 )) -ne 0 ]; then
 +
                OSDTEXT="$OSDTEXT $SVIDEO"
 +
                su $user -c "$XRANDR --output $SVIDEO $SVIDEO_POSITION --auto"
 +
            else
 +
                [ -n "$SVIDEO_CONN" ] && su $user -c "$XRANDR --output $SVIDEO --off"
 +
            fi
 
         fi
 
         fi
         if [ x"$user" != x"" ]; then
+
 
                 userhome=`getent passwd $user | cut -d: -f6`
+
        # Never turn on or off internal screen - only turn on or off the backlight.
                export XAUTHORITY=$userhome/.Xauthority
+
        # Do this after the xrandr calls above, during which the backlight
 +
        # might go on.
 +
         if [ $(( STATE % 2 )) -ne 0 ]; then
 +
            if pidof xscreensaver >/dev/null; then
 +
                su $user -c "xscreensaver-command -unthrottle"
 +
            fi 
 +
            if [ x$RADEON_LIGHT = xtrue ]; then
 +
                [ -x /usr/sbin/radeontool ] && radeontool light on
 +
            fi 
 +
            if pidof xscreensaver >/dev/null; then
 +
                 su $user -c "xscreensaver-command -deactivate"
 +
            fi 
 +
            su $user -c "xset dpms force on"
 +
            OSDTEXT="$OSDTEXT $LAPTOP"
 
         else
 
         else
                 export XAUTHORITY=""
+
            if [ `pidof xscreensaver` ]; then
 +
                 su $user -c "(xscreensaver-command -throttle)"
 +
            fi
 +
            # Not calling xset to turn off dpms prohibits key presses from
 +
            # turning the backlight back on all the time. Do make sure to have
 +
            # RADEON_LIGHT enabled though. The rest of the script works even
 +
            # though RADEON_LIGHT left disabled, the only effect is that the
 +
            # laptop panel will always be considered "on".
 +
            #su $user -c "xset dpms force off"
 +
            if [ x$RADEON_LIGHT = xtrue ]; then
 +
                [ -x /usr/sbin/radeontool ] && radeontool light off
 +
            fi
 +
        fi
 +
 
 +
        $LOGGER Currently active GPU outputs: $OSDTEXT
 +
 
 +
        # Wait a little for the hardware to reach the new state
 +
        #sleep $WAITING_TIME
 +
 
 +
        # Figure out current state again, to put the OSD on the right places.
 +
        XRANDR_STATUS=`su $user -c "$XRANDR -q"`
 +
 
 +
        VGA_CONN=`echo "$XRANDR_STATUS" | grep ^$VGA | grep " connected"`
 +
        SVIDEO_CONN=`echo "$XRANDR_STATUS" | grep ^$SVIDEO | grep " connected"`
 +
 
 +
        VGA_STATE=`echo $VGA_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 +
        SVIDEO_STATE=`echo $SVIDEO_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 +
 
 +
        VGA_HOFFSET=$(( `echo $VGA_STATE | sed 's/[0-9x]*\([+-][0-9]*\).*/\1/'` + OSD_HOFFSET ))
 +
        VGA_VOFFSET=$(( `echo $VGA_STATE | sed 's/[0-9x]*[+-][0-9]*\([+-][0-9]*\)/\1/'` + OSD_VOFFSET ))
 +
        SVIDEO_HOFFSET=$(( `echo $SVIDEO_STATE | sed 's/[0-9x]*\([+-][0-9]*\).*/\1/'` + OSD_HOFFSET ))
 +
        SVIDEO_VOFFSET=$(( `echo $SVIDEO_STATE | sed 's/[0-9x]*[+-][0-9]*\([+-][0-9]*\)/\1/'` + OSD_VOFFSET ))
 +
 
 +
        # On screen display.
 +
        if [ -n "$OSD_CAT" ] ; then
 +
            su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$OSD_HOFFSET --indent=$OSD_VOFFSET - &"
 +
            [ -n "$VGA_CONN" ] && su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$VGA_VOFFSET --indent=$VGA_HOFFSET - &"
 +
            [ -n "$SVIDEO_CONN" ] && su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$SVIDEO_HOFFSET --indent=$SVIDEO_VOFFSET - &"
 
         fi
 
         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
 
     fi
done
+
}
  
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. The above example  runs the python script below but could run a modified version of /usr/local/sbin/thinkpad-fn-f7 without the USER, and DISPLAY detection SU commands.
+
# Snippet borrowed from /etc/acpi/lid.sh
 +
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
 +
</syntaxhighlight>
 +
 
 +
If neither VGA nor S-video are connected, the script will only flip on and off the backlight. If one output is connected, be it VGA or S-video, the sequence will be 1) Internal, 2) VGA/S-video (backlight off), 3) Internal+VGA/S-video, 0) all off. If both a monitor and a TV are connected, the sequence will be 1) Internal, 2) VGA (backlight off), 3) Internal+VGA, 4) S-video (backlight off), 5) Internal+S-video, 0) all off.
  
 
== A Python Toggle script ==
 
== A Python Toggle script ==
Line 235: Line 534:
 
eg you can run it as {{cmduser|/usr/local/bin/toggle.py --help}}
 
eg you can run it as {{cmduser|/usr/local/bin/toggle.py --help}}
  
 +
<syntaxhighlight lang="python">
 
  #! /usr/bin/python
 
  #! /usr/bin/python
 
  # -*- coding: utf-8 -*-
 
  # -*- coding: utf-8 -*-
 
  #
 
  #
  # stinkpad@blueyonder.co.uk 2007-11-23
+
  # stinkpad(a)blueyonder.co.uk 2007-11-26
 
  """Toggle internal and external displays (equivalent to ThinkPad Fn7)
 
  """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.  
+
  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.  
 
  Limited; as full but display will overlap if virtual screen is too small.  
 
  Use 'xrandr -q' to determine output names.  
 
  Use 'xrandr -q' to determine output names.  
Line 246: Line 547:
 
  """
 
  """
 
  __usage__ = "usage: %prog [--help]|[[-i internal][-e external][-d displays]]"
 
  __usage__ = "usage: %prog [--help]|[[-i internal][-e external][-d displays]]"
  __version__ = "toggle [thinkwiki(a)djclark.co.uk 2007-11-23]"
+
  __version__ = "toggle [djclark.eu 2007-11-26]"
 
  #
 
  #
 
  # Output names; Intel: LVDS VGA TV TMDS-1 TMDS-2
 
  # Output names; Intel: LVDS VGA TV TMDS-1 TMDS-2
Line 257: Line 558:
 
  import os
 
  import os
 
  import re
 
  import re
#
+
 
 
  # "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
 
  # "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
  REGEX_OUTPUT = re.compile(r'''
+
  REGEX_OUTPUT = re.compile(r"""
 
  (?x) # ignore whitespace
 
  (?x) # ignore whitespace
 
  ^ # start of string
 
  ^ # start of string
Line 271: Line 572:
 
  )|[\D]) # or not a digit
 
  )|[\D]) # or not a digit
 
  .* # ignore rest of line
 
  .* # ignore rest of line
  ''')
+
  """)
  #
+
   
 
  # "Screen 0: minimum 320 x 200, current 1024 x 768, maximum 2624 x 1968"
 
  # "Screen 0: minimum 320 x 200, current 1024 x 768, maximum 2624 x 1968"
  REGEX_SCREEN = re.compile(r'''
+
  REGEX_SCREEN = re.compile(r"""
 
  (?x) # ignore whitespace
 
  (?x) # ignore whitespace
 
  ^ # start of string
 
  ^ # start of string
Line 288: Line 589:
 
  (?P<maxWidth>\d+)[ x]+
 
  (?P<maxWidth>\d+)[ x]+
 
  (?P<maxHeight>\d+)
 
  (?P<maxHeight>\d+)
  ''')
+
  """)
#
+
 
 
  def toggle_simple(d0, d1):
 
  def toggle_simple(d0, d1):
 
     """Toggle display states: on+off, on+on, off+on"""
 
     """Toggle display states: on+off, on+on, off+on"""
 
     if d1['connect'] == 'disconnected': # external unplugged
 
     if d1['connect'] == 'disconnected': # external unplugged
         return ('auto','off','') #    switch off external
+
         return ('auto','off',"") #    switch off external
 
     if d1['width'] is 0: # external off
 
     if d1['width'] is 0: # external off
         return ('auto','auto','') #    both on
+
         return ('auto','auto',"") #    both on
 
     if d0['width'] is 0: # laptop off
 
     if d0['width'] is 0: # laptop off
         return ('auto','off','') #    laptop on
+
         return ('auto','off',"") #    laptop on
     return ('off','auto','') # both on, laptop off
+
     return ('off','auto',"") # both on, laptop off
  
 
  def toggle_full(d0, d1):
 
  def toggle_full(d0, d1):
 
     """Toggle display states: 1+0, 1+1, 0+1, 1+E, 1+S, 1+W, 1+N"""  
 
     """Toggle display states: 1+0, 1+1, 0+1, 1+E, 1+S, 1+W, 1+N"""  
 
     if d1['connect'] == 'disconnected': # external unplugged
 
     if d1['connect'] == 'disconnected': # external unplugged
         return ('auto','off','') #    switch off external
+
         return ('auto','off',"") #    switch off external
 
     place = '--%s ' + d0['output']
 
     place = '--%s ' + d0['output']
 
     if d1['width'] == 0: # external off
 
     if d1['width'] == 0: # external off
 
         return ('auto','auto',place%'same-as') #    external on
 
         return ('auto','auto',place%'same-as') #    external on
 
     if d0['width'] == 0: # laptop off
 
     if d0['width'] == 0: # laptop off
         return ('auto','off','') #    laptop on
+
         return ('auto','off',"") #    laptop on
 
     if d1['horizontal'] > 0: # external to right
 
     if d1['horizontal'] > 0: # external to right
 
         return ('auto','auto',place%'below') #    make below
 
         return ('auto','auto',place%'below') #    make below
Line 316: Line 617:
 
         return ('auto','auto',place%'above') #    make above
 
         return ('auto','auto',place%'above') #    make above
 
     if d0['vertical'] > 0: # external above
 
     if d0['vertical'] > 0: # external above
         return ('off','auto','')  #    laptop off
+
         return ('off','auto',"")  #    laptop off
 
     return ('auto','auto',place%'right-of') # is same, make right
 
     return ('auto','auto',place%'right-of') # is same, make right
  
Line 323: Line 624:
 
     p = 'auto --pos %sx%s'
 
     p = 'auto --pos %sx%s'
 
     if orientation == 'V':
 
     if orientation == 'V':
         if (da['height'] + db['height']) <= screen['maxHeight']:
+
         if da['height'] + db['height'] <= screen['maxHeight']:
 
             return p%(0, da['height'])
 
             return p%(0, da['height'])
 
         return p%(0, screen['maxHeight'] - db['height'])
 
         return p%(0, screen['maxHeight'] - db['height'])
 
     else:
 
     else:
         if (da['width'] + db['width']) <= screen['maxWidth']:
+
         if da['width'] + db['width'] <= screen['maxWidth']:
 
             return p%(da['width'],0)
 
             return p%(da['width'],0)
 
         return p%(screen['maxWidth'] - db['width'],0)
 
         return p%(screen['maxWidth'] - db['width'],0)
Line 348: Line 649:
 
         return ('off','auto --pos 0x0')  #    laptop off
 
         return ('off','auto --pos 0x0')  #    laptop off
 
     return ('auto --pos 0x0',position('H',d0,d1,sz)) # both, put*right
 
     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):
 
  def function7(disp0=LAPTOP, disp1=MONITOR, seq=SEQUENCE):
 
     """Use xrandr to read current display state and change state"""
 
     """Use xrandr to read current display state and change state"""
     for line in os.popen('xrandr -q').read().splitlines():
+
     for line in os.popen('xrandr -q'):
 
         if line.startswith(disp0,0) :
 
         if line.startswith(disp0,0) :
 
             d0_state = REGEX_OUTPUT.match(line).groupdict()
 
             d0_state = REGEX_OUTPUT.match(line).groupdict()
Line 358: Line 662:
 
         elif line.startswith('Screen',0):
 
         elif line.startswith('Screen',0):
 
             screen_size = REGEX_SCREEN.match(line).groupdict()
 
             screen_size = REGEX_SCREEN.match(line).groupdict()
        else:
 
            pass
 
 
     for i in ('width','height','horizontal','vertical'):
 
     for i in ('width','height','horizontal','vertical'):
 
         try:
 
         try:
Line 366: Line 668:
 
             d0_state[i] = 0
 
             d0_state[i] = 0
 
         except UnboundLocalError:
 
         except UnboundLocalError:
             return 'Internal Display output %s not found'% disp0
+
             raise DisplayNameError, 'Internal Display: %s not found'% disp0
 
         try:
 
         try:
 
             d1_state[i] = int(d1_state[i])
 
             d1_state[i] = int(d1_state[i])
Line 372: Line 674:
 
             d1_state[i] = 0
 
             d1_state[i] = 0
 
         except UnboundLocalError:
 
         except UnboundLocalError:
             return 'External Display output %s not found'% disp1
+
             raise DisplayNameError, 'External Display: %s not found'% disp1
     for i in screen_size.keys():
+
     for i in screen_size:
 
         try:
 
         try:
 
             screen_size[i] = int(screen_size[i])
 
             screen_size[i] = int(screen_size[i])
Line 389: Line 691:
 
     else:
 
     else:
 
         os.popen(xrandr % toggle(d0_state, d1_state))
 
         os.popen(xrandr % toggle(d0_state, d1_state))
    return None
 
  
 
  def main():
 
  def main():
 
     """ Command line options """
 
     """ Command line options """
    global LAPTOP,MONITOR,SEQUENCE
 
 
     from optparse import OptionParser
 
     from optparse import OptionParser
 
     p = OptionParser(usage=__usage__, version=__version__, description=__doc__)   
 
     p = OptionParser(usage=__usage__, version=__version__, description=__doc__)   
 
     p.set_defaults(internal=LAPTOP, external=MONITOR, displays=SEQUENCE)
 
     p.set_defaults(internal=LAPTOP, external=MONITOR, displays=SEQUENCE)
    p.set_defaults(separator='comma', column=0)
 
 
     p.add_option('-i','--internal', dest="internal", metavar=LAPTOP,
 
     p.add_option('-i','--internal', dest="internal", metavar=LAPTOP,
 
  help="internal display")
 
  help="internal display")
Line 406: Line 705:
 
  help='simple/limited/full')
 
  help='simple/limited/full')
 
     (opt, args) = p.parse_args()
 
     (opt, args) = p.parse_args()
     failed = function7(opt.internal, opt.external, opt.displays)
+
     try:
     if failed:
+
        function7(opt.internal, opt.external, opt.displays)
         print '\n'+failed+'\n'
+
     except DisplayNameError, err:
 +
         print '\n'+str(err)+'\n'
 
         print os.popen('xrandr -q').read()
 
         print os.popen('xrandr -q').read()
 
  #
 
  #
 
  if __name__ == '__main__': #only when run from cmd line
 
  if __name__ == '__main__': #only when run from cmd line
 
     main()
 
     main()
 +
</syntaxhighlight>
 +
 +
This modified thinkpad-fn-f7 calls the toggle python script:
 +
 +
<syntaxhighlight lang="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
 +
</syntaxhighlight>
 +
 +
===Python XRandR===
 +
[[http://www.glatzor.de/blog/blog-details/article/initial-release-of-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.
 +
 +
<syntaxhighlight lang="python">
 +
  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()
 +
</syntaxhighlight>
 +
 +
== 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 ==
 +
[[http://gitweb.freedesktop.org/?p=xorg/app/grandr.git grandr]] graphical interface to xrandr  using GTK+ libraries.
 +
 +
[[https://edge.launchpad.net/python-xrandr python-xrandr]] Python bindings to xrandr which should enable a less clunky version of the python script above. Under development.
 +
 +
[[Category:Scripts]]

Latest revision as of 16:17, 21 May 2013

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.

NOTE!
On a modern distribution like Fedora 12, the Fn-F7 key works out-of-the-box, and no additional scripts are needed to switch between external display modes

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 script
event=ibm/hotkey HKEY 00000080 00001007
action=/usr/local/sbin/thinkpad-fn-f7


or you may (eg Ubuntu 7.10) already have /etc/acpi/events/ibm-videobtn

# /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

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

#!/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

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.

#!/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

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

#!/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

Script to switch between Internal, VGA and S-video

This highly overdone script allows to switch between all combinations of Internal, VGA and S-video out. VGA and and S-video at the same time are not allowed on my hardware (I have a T41 with Radeon 7500), so I masked these combinations out. Instead of disabling the internal display with xrandr, I turn off the backlight. One of the allowed combinations is all displays off, and xorg crashes if turning off all displays with xrandr. The script depends on the debian package acpi-support, which is also found in ubuntu and crunchbang (which is what I am using). The dependency can easily be broken out though. Can easily be modified to fulfill other needs, i.e. if you turn off your backlight with something else than radeontool. There is also an OSD displayed in the upper left corner of all active displays.

#/bin/sh
 
. /usr/share/acpi-support/power-funcs         # for getXuser
. /etc/default/acpi-support                   # for RADEON_LIGHT
 
# First prevent concurrent execution
 
LOCK_FILE=/tmp/swapscreenslock
WAITING_TIME=1
LOGGER="`which logger 2>/dev/null` -i"
if (set -C; : > $LOCK_FILE) 2>/dev/null; then : ; else
    # Lock file exists - wait as long as theoretically is needed for the other script to finish and retry
    sleep $WAITING_TIME
    if (set -C; : > $LOCK_FILE) 2>/dev/null; then : ; else
        # Lock file still exists - we should never end up here! Consider removing the lock file.
        $LOGGER "Lock file exists - earlier call to script stalled? Consider removing lock file \"$LOCK_FILE\". Exiting."
        exit 1
    fi
fi
trap 'rm $LOCK_FILE' EXIT
 
XRANDR=`which xrandr 2>/dev/null`
OSD_CAT=`which osd_cat 2>/dev/null`
LAPTOP="LVDS"
VGA="VGA-0"
SVIDEO="S-video"
VGA_POSITION="--right-of $LAPTOP"
#VGA_POSITION="--same-as $LAPTOP"    # Mirror
SVIDEO_POSITION="--right-of $LAPTOP"
 
OSD_GENERICARGS="--delay=3 --age=0 --font=-adobe-helvetica-bold-r-normal-*-*-180-*-*-p-*-*-* --colour=green --shadow=1"
OSD_HOFFSET=25
OSD_VOFFSET=25
 
function screen_toggle {
    # Figure out current state
    XRANDR_STATUS=`su $user -c "$XRANDR -q"`
 
    # Laptop is of course always connected, however I'll leave the script this
    # way for making it easier to make the script a little more generic.
    LAPTOP_CONN=`echo "$XRANDR_STATUS" | grep ^$LAPTOP | grep " connected"`
    VGA_CONN=`echo "$XRANDR_STATUS" | grep ^$VGA | grep " connected"`
    SVIDEO_CONN=`echo "$XRANDR_STATUS" | grep ^$SVIDEO | grep " connected"`
 
    LAPTOP_STATE=`radeontool light | grep 'looks on'`
    VGA_STATE=`echo $VGA_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
    SVIDEO_STATE=`echo $SVIDEO_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 
    CONNMASK=0
    [ -n "$LAPTOP_CONN" ] && CONNMASK=$(( CONNMASK + 1 ))
    if [ -n "$VGA_CONN" ]; then 
        CONNMASK=$(( CONNMASK + 2 ))
        [ -n "$SVIDEO_CONN" ] && CONNMASK=$(( CONNMASK + 4 ))
    else
        # If VGA is not connected, TV will overtake its bit
        [ -n "$SVIDEO_CONN" ] && CONNMASK=$(( CONNMASK + 2 ))
    fi
 
    STATE=0
    if [ -n "$LAPTOP_STATE" ]; then STATE=$(( STATE + 1 )); fi
    if [ -n "$VGA_CONN" ]; then 
        [ -n "$VGA_STATE" ] && STATE=$(( STATE + 2 ))
        [ -n "$SVIDEO_STATE" ] && STATE=$(( STATE + 4 ))
    else
        # If VGA is not connected, TV will overtake its bit
        [ -n "$SVIDEO_STATE" ] && STATE=$(( STATE + 2 ))
    fi
 
    # Go to next state
    STATE=$(( (STATE+1) % (CONNMASK+1) ))
 
    # Forbidden states are TV and VGA at the same time, regardless of internal state
    STATE=$(( STATE % 6 ))
 
    # If no screen is listed as connected, probably xrandr failed - do nothing.
    if [ $CONNMASK -ne 0 ]; then
        OSDTEXT=
 
        if [ -n "$VGA_CONN" ]; then 
            # Turn on or off external VGA
            if [ $(( (STATE >> 1) % 2 )) -ne 0 ]; then 
                OSDTEXT="$OSDTEXT $VGA"
                su $user -c "$XRANDR --output $VGA $VGA_POSITION --auto"
            else
                su $user -c "$XRANDR --output $VGA --off"
            fi
 
            # Turn on or off S-Video output
            if [ $(( (STATE >> 2) % 2 )) -ne 0 ]; then 
                OSDTEXT="$OSDTEXT $SVIDEO"
                su $user -c "$XRANDR --output $SVIDEO $SVIDEO_POSITION --auto"
            else
                [ -n "$SVIDEO_CONN" ] && su $user -c "$XRANDR --output $SVIDEO --off"
            fi
        else
            # If VGA is not connected, TV will overtake its bit
 
            # Turn on or off S-Video output
            if [ $(( (STATE >> 1) % 2 )) -ne 0 ]; then 
                OSDTEXT="$OSDTEXT $SVIDEO"
                su $user -c "$XRANDR --output $SVIDEO $SVIDEO_POSITION --auto"
            else
                [ -n "$SVIDEO_CONN" ] && su $user -c "$XRANDR --output $SVIDEO --off"
            fi
        fi
 
        # Never turn on or off internal screen - only turn on or off the backlight.
        # Do this after the xrandr calls above, during which the backlight
        # might go on.
        if [ $(( STATE % 2 )) -ne 0 ]; then
            if pidof xscreensaver >/dev/null; then 
                su $user -c "xscreensaver-command -unthrottle"
            fi  
            if [ x$RADEON_LIGHT = xtrue ]; then
                [ -x /usr/sbin/radeontool ] && radeontool light on
            fi  
            if pidof xscreensaver >/dev/null; then
                su $user -c "xscreensaver-command -deactivate"
            fi  
            su $user -c "xset dpms force on"
            OSDTEXT="$OSDTEXT $LAPTOP"
        else
            if [ `pidof xscreensaver` ]; then
                su $user -c "(xscreensaver-command -throttle)"
            fi
            # Not calling xset to turn off dpms prohibits key presses from
            # turning the backlight back on all the time. Do make sure to have
            # RADEON_LIGHT enabled though. The rest of the script works even
            # though RADEON_LIGHT left disabled, the only effect is that the
            # laptop panel will always be considered "on".
            #su $user -c "xset dpms force off"
            if [ x$RADEON_LIGHT = xtrue ]; then
                [ -x /usr/sbin/radeontool ] && radeontool light off
            fi
        fi
 
        $LOGGER Currently active GPU outputs: $OSDTEXT
 
        # Wait a little for the hardware to reach the new state
        #sleep $WAITING_TIME
 
        # Figure out current state again, to put the OSD on the right places.
        XRANDR_STATUS=`su $user -c "$XRANDR -q"`
 
        VGA_CONN=`echo "$XRANDR_STATUS" | grep ^$VGA | grep " connected"`
        SVIDEO_CONN=`echo "$XRANDR_STATUS" | grep ^$SVIDEO | grep " connected"`
 
        VGA_STATE=`echo $VGA_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
        SVIDEO_STATE=`echo $SVIDEO_CONN | sed 's/.*connected\s\([0-9x+-]*\).*/\1/'`
 
        VGA_HOFFSET=$(( `echo $VGA_STATE | sed 's/[0-9x]*\([+-][0-9]*\).*/\1/'` + OSD_HOFFSET ))
        VGA_VOFFSET=$(( `echo $VGA_STATE | sed 's/[0-9x]*[+-][0-9]*\([+-][0-9]*\)/\1/'` + OSD_VOFFSET ))
        SVIDEO_HOFFSET=$(( `echo $SVIDEO_STATE | sed 's/[0-9x]*\([+-][0-9]*\).*/\1/'` + OSD_HOFFSET ))
        SVIDEO_VOFFSET=$(( `echo $SVIDEO_STATE | sed 's/[0-9x]*[+-][0-9]*\([+-][0-9]*\)/\1/'` + OSD_VOFFSET ))
 
        # On screen display. 
        if [ -n "$OSD_CAT" ] ; then 
            su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$OSD_HOFFSET --indent=$OSD_VOFFSET - &"
            [ -n "$VGA_CONN" ] && su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$VGA_VOFFSET --indent=$VGA_HOFFSET - &"
            [ -n "$SVIDEO_CONN" ] && su $user -c "echo $OSDTEXT | $OSD_CAT $OSD_GENERICARGS --offset=$SVIDEO_HOFFSET --indent=$SVIDEO_VOFFSET - &"
        fi
    fi
}
 
# Snippet borrowed from /etc/acpi/lid.sh
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

If neither VGA nor S-video are connected, the script will only flip on and off the backlight. If one output is connected, be it VGA or S-video, the sequence will be 1) Internal, 2) VGA/S-video (backlight off), 3) Internal+VGA/S-video, 0) all off. If both a monitor and a TV are connected, the sequence will be 1) Internal, 2) VGA (backlight off), 3) Internal+VGA, 4) S-video (backlight off), 5) Internal+S-video, 0) all off.

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

 #! /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'):
        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()
    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:
        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 """
    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()

This modified thinkpad-fn-f7 calls the toggle python script:

 #!/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

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:

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.