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.
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
! (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:
xrandr via xrandr_setup.sh.internal, external, twin, clone) based on the internal and external states.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.
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
! 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'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)
! 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.
! 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