Screen Toggle (Fn+F8)

The screen toggle functionality can be accessed by pressing the function key and F8. In Windows this works “out-of-the-box”, but in Linux this turns out to be more of a problem. However, since you will have to configure every step of the process, you get much more control over what happens and can customize it to your liking. After you installed ACPI and got it to react to the proper keys it is time to encapsulate the functionality in a screen_toggle.sh script. The script will rely on XRandR to do the actual screen switching and “all” it needs to do is keep track of the current setting and calling xrandr with the proper settings. However, nothing is easy when you have two GPUs in your netbook. While the Intel chipset works nicely with xrandr and you can see all connected devices as separate devices1), the binary driver from nVidia likes to keep things under wraps and therefore does not expose TwinView settings to xrandr. This forces you to have two separate screen_toggle.sh scripts and use each with its card. Once you have these two scripts you simply need to call them at the appropriate place in the asus.sh ACPI file.

Common Setup

Before you get to create these files though you need to make sure xrandr works when executed in a script, which was called from a non-interactive shell. You see xrandr depends on two global variables, XAUTHORITY pointing to a valid .Xauthority file and DISPLAY telling it which is the active display (usually :0.0), to work. These variables exist and are set in a normal shell within the X window system, however, the ACPI environment lacks them.

In order to solve this problem you need a xrandr_setup.sh script which, inelegantly, sets those variables within any script that includes it2). The values of both variables can be determined by querying the who command which lists all users that are currently logged in. The list will contain their user names and the displays that are active. A creative application of grep and sed yields the correct results.

#!/bin/bash
# /usr/local/src/xrandr_setup.sh
# Sets up the USER and DISPLAY variables to be used by xrandr

CURRENT_USER=$(who -s | grep "(:[0-9]\W" | head -1 | sed "s/ .*//g")
CURRENT_DISPLAY=$(who -s | grep "(:[0-9]" | head -1 | sed "s/.*(//g" | sed "s/)//g")

# Set up xrandr
export XAUTHORITY="/home/${CURRENT_USER}/.Xauthority" 
export DISPLAY=${CURRENT_DISPLAY} #":0.0"

The .Xauthority is located in the user's home directory. Unfortunately, while most “normal” users' home is located in /home/<user name>/, the root's home directory is simply /root/. To solve this discrepancy without a lot of if/else-statements in the script file create a symbolic link named /home/root which will point to /root:

ln -s /root/ /home/root

The best to way to handle the two different screen_toggle.sh scripts for the two GPUs is also via a symbolic link. asus.sh should always call /usr/local/src/screen_toggle.sh which will be symbolic link to either intel_screen_toggle.sh or nvidia_screen_toggle.sh. Of course you can set the link the same way you set the xorg.conf symbolic link from the X-server setup FIXME! (that actually does not have the symbolic link yet), i.e. in the init of the initramfs (detailed here).

The script's general idea is as follows:

  1. Set up xrandr via xrandr_setup.sh.
  2. Determine the internal and external states.
  3. Select one of four configuration states (internal, external, twin, clone) based on the internal and external states.
  4. Call one of five functions that switch states (one for each configuration state plus a toggle() function) according to the parameter that was passed in.

The changes between the Intel and nVidia versions of the script have to do with detecting the internal and external states and switching between states.

Intel Screen Toggle

The Intel script is the “correct” one since xrandr can nicely detect the connected displays:

Screen 0: minimum 320 x 200, current 1024 x 600, maximum 1920 x 1800
VGA connected (normal left inverted right x axis y axis)
   1920x1200      60.0 +
   1680x1050      60.0  
   1400x1050      60.0     60.0  
   1280x1024      60.0     60.0  
   1440x900       60.0  
   1280x960       60.0     60.0  
   1024x768       60.0  
   800x600        60.3     56.2  
   640x480        60.0     59.9  
LVDS connected 1024x600+0+0 (normal left inverted right x axis y axis) 222mm x 130mm
   1024x600       60.0*+
   800x600        85.1     72.2     75.0     60.3     56.2  
   640x480        85.0     72.8     75.0     59.9  
   720x400        85.0  
   640x400        85.1  
   640x350        85.1  

You can then simply query xrandr to find out which monitors are connected (i.e. listed) and on (containing a * next to the resolution in use). Similarly in order to switch between the monitors you simply tell xrandr which one (–output VGA or LVDS) should do what (–auto or a specific resolution). The clone configuration is achieved by turning both outputs on and passing –same-as LVDS to the external output. The twin configuration needs to know the position of the two outputs, in this case use –above LVDS. While –left-of LVDS would be logical, the Virtual parameter in Intel's xorg.conf (defined here FIXME! make link directly to nvidia conf) unfortunately must not be greater than 2048×2048, otherwise DRI will not work on the Intel 945GME chipset. Since I am using a 24” monitor with a resolution of 1920×1200, having both monitors next to each other would require a larger-than-allowed virtual desktop width (1024+1920>2048). However, when the two desktop heights are added, the limit can be still met (600+1200<2048).

#!/bin/bash
# /usr/local/src/intel_screen_toggle.sh
# Toggles between internal, external, twin, and clone screen setups for the Intel GPU

# Set up xrandr
source /usr/local/src/xrandr_setup.sh

# Output may be "LVDS" or "VGA"
INTERNAL_OUTPUT="LVDS"
EXTERNAL_OUTPUT="VGA"

CLONE_FILE=/tmp/.clone

# Set the position for twin view
DO=${1}
if [[ "${DO}" == "twin" ]]; then
	EXTERNAL_LOCATION=${2}
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}"
	;;
	*)
		# Default has to be above because the max. virtual desktop size for Intel 945GME is 2048x2048 and 1920+1024>2048, but 1200+600<2048 
		EXTERNAL_LOCATION="--above ${INTERNAL_OUTPUT}"
	;;
esac

# Figure out the current state
INTERNAL_STATE=$( xrandr | grep "^${INTERNAL_OUTPUT}" | grep "con" | sed "s/.*connected //" | sed "s/[+(].*//g")
EXTERNAL_STATE=$( xrandr | grep "^${EXTERNAL_OUTPUT}" | grep "con" | sed "s/.*connected //" | sed "s/[+(].*//g")

if [[ -z "${INTERNAL_STATE}" ]]; then
	STATE="external"
elif [[ -z "${EXTERNAL_STATE}" ]]; then
	STATE="internal"
else
	if [[ -f ${CLONE_FILE} ]]; then
		STATE="clone"
	else
		STATE="twin"
	fi
fi

# External screen only
function screen_external()
{
	xrandr --output ${INTERNAL_OUTPUT} --off
	xrandr --output ${EXTERNAL_OUTPUT} --auto

	rm -f ${CLONE_FILE}
}

# Internal screen only
function screen_internal()
{
	xrandr --output ${INTERNAL_OUTPUT} --auto
	xrandr --output ${EXTERNAL_OUTPUT} --off
	
	rm -f ${CLONE_FILE}
}

# Clone view
function screen_clone()
{
	xrandr --output ${INTERNAL_OUTPUT} --auto --output ${EXTERNAL_OUTPUT} --auto --same-as ${INTERNAL_OUTPUT}

	touch ${CLONE_FILE}
}

# Twin view
function screen_twin()
{
		xrandr --output ${INTERNAL_OUTPUT} --auto --output ${EXTERNAL_OUTPUT} --auto ${EXTERNAL_LOCATION}

	rm -f ${CLONE_FILE}
}

# Toggle function
function screen_toggle()
{
	case "${STATE}" in
		internal)
			screen_clone
		;;
		clone)
			screen_twin
		;;
		twin)
			screen_external
		;;
		external)
			screen_internal
		;;
		*)
			screen_internal
		;;
	esac
}

# What should we do?
if [[ -z "${DO}" ]]; then
	if [[ $(basename ${0}) == "intel_screen_toggle.sh" ]]; then
		DO="toggle"
	fi
fi

case "${DO}" in
	toggle)
		screen_toggle
	;;
	internal)
		screen_internal
	;;
	external)
		screen_external
	;;
	clone)
		screen_clone
	;;
	twin)
		screen_twin
	;;
	state)
		echo "${STATE}"
	;;
	status)
		echo "Current Fn+F8 state is: ${STATE}"
		echo 
		echo "Attached monitors:"
		xrandr | grep "\Wconnected" | sed "s/^/ / "
	;;
	*)
		echo "Usage: ${0} <command>" >&2
		echo >&2
		echo "    Commands:" >&2
		echo "        state" >&2
		echo "        status" >&2
		echo "        internal" >&2
		echo "        external" >&2
		echo "        clone" >&2
		echo "        twin" >&2
		echo "        toggle" >&2
		echo >&2
	;;
esac

nVidia Screen Toggle

nVidia's script is similar to Intel's, but it supports only three configurations: internal, external, and clone. Because the GPU's binary driver manages all TwinView states you can switch only between the ones it provides … and the twin state as known from the Intel chipset is not among them. This is also the reason why xrandr does not list multiple outputs when the nVidia GPU is enabled:

Screen 0: minimum 1024 x 600, current 1024 x 600, maximum 1920 x 1200
default connected 1024x600+0+0 0mm x 0mm
   1024x600       50.0* 
   1920x1200      51.0     52.0     50.0  

You will not get the driver and xrandr to communicate until nVidia decides to incorporate xrandr in their driver, so you will have to do with a work around for now. The trick is to allow all configurations in the xorg.conf as metamodes (as you did here) FIXME! make link directly to nvidia conf. xrandr will then be able to differentiate between the settings based on inaccurate frame rate values. As you could see in xrandr's output above, there are three frame rates, 50.0, 51.0, and 52.0. They all correspond to a different configurations, based on the xorg.conf.

Option		"metamodes"	"DFP-0: 1024x600_60, DFP-1: NULL; DFP-0: NULL, DFP-1: nvidia-auto-select; DFP-0: 1024x600_60, DFP-1: nvidia-auto-select"
# translates to xrandr's         50.0                             51.0                                    52.0

In order to switch between the configurations simply ask xrandr to select the appropriate refresh rate via -r <rate> -s <size>. As you have no doubt realized, some refresh rates are assigned to both resolutions, which is why you also need to provide the size (-s) parameter. This is of course not ideal because it requires you to hard code the resolution of your external screen into the script. FIXME! add logic to script to remove this Similary this whole process alters the way the configuration states are ascertained, hence the separate script file.

#!/bin/bash
# /usr/local/src/nvidia_screen_toggle.sh
# Toggles between internal, external, and clone screen setups for the nVidia GPU

# Set up xrandr
source /usr/local/src/xrandr_setup.sh

# Set up xrandr constants
INTERNAL=50.0
EXTERNAL=51.0
CLONE=52.0

INTERNAL_RESOLUTION="1024x600"
EXTERNAL_RESOLUTION="1920x1200"

DO=${1}

# Figure out the current state
CURRENT_STATE=$( xrandr | grep "\*" | sed "s/.* \([^*]\{1,\}\)\*.*/\1/" )

if [[ ${CURRENT_STATE} == ${EXTERNAL} ]]; then
	STATE="external"
elif [[ ${CURRENT_STATE} == ${INTERNAL} ]]; then
	STATE="internal"
else
	STATE="clone"
fi

# External screen only
function screen_external()
{
	xrandr -r ${EXTERNAL} -s ${EXTERNAL_RESOLUTION}
}

# Internal screen only
function screen_internal()
{
	xrandr -r ${INTERNAL} -s ${INTERNAL_RESOLUTION}
}

# Clone view
function screen_clone()
{
	xrandr -r ${CLONE} -s ${EXTERNAL_RESOLUTION}
}

# Toggle function
function screen_toggle()
{
	case "${STATE}" in
		internal)
			screen_clone
		;;
		clone)
			screen_external
		;;
		external)
			screen_internal
		;;
		*)
			screen_internal
		;;
	esac
}

# What should we do?
if [[ -z "${DO}" ]]; then
	if [[ $(basename ${0}) == "nvidia_screen_toggle.sh" ]]; then
		DO="toggle"
	fi
fi

case "${DO}" in
	toggle)
		screen_toggle
	;;
	internal)
		screen_internal
	;;
	external)
		screen_external
	;;
	clone)
		screen_clone
	;;
	state)
		echo "${STATE}"
	;;
	status)
		echo "Current Fn+F8 state is: ${STATE}"
		echo 
		echo "Attached monitors:"
		xrandr
	;;
	*)
		echo "Usage: ${0} <command>" >&2
		echo >&2
		echo "    Commands:" >&2
		echo "        state" >&2
		echo "        status" >&2
		echo "        internal" >&2
		echo "        external" >&2
		echo "        clone" >&2
		echo "        toggle" >&2
		echo >&2
	;;
esac
1) Please be advised that only the D-Sub port is available for the Intel GPU. The HDMI port can be accessed only by the nVidia card.
2) Note that you will be including this file via the source command and not calling/executing the script - the execution would not set the variables properly.
 
setup/graphics/metakeys/screentoggle.txt · Last modified: 2009/05/25 20:30 by marco1475
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki