So for a year now I’ve been using ati-drivers (fireglrx, ccc, catalyst, watever you want to call that garbage) and wishing every other day that I didn’t have (some versions simply doesn’t work, older versions won’t compile with newer kernels – which I sometimes need for other bugfixes wrt wireless and possibly due to external requirements – asterisk’s dahdi driver comes to mind). So finally during the week when the proverbial shit once more hit the fan where I wanted to upgrade my kernel to 2.6.32 for some intel wireless fixes and the newest ati-drivers simply exploded in my face and the version just down didn’t want to work … I decided it’s time. Time to try the OSS drivers that didn’t want to work slightly over a year back again. And this time the story is quite different …
It’s beautiful. DPI gets detected properly. Full RandR support. External monitor detection. Faster login times (No, I kid you not, ssh-agent’s askpass locks the screen, with fglrx this takes up to 20 seconds, with the OSS drivers it’s instantaneous). Heck, I haven’t yet found anything I don’t like. Don’t get me wrong, it wasn’t as easy as I’d like it to be. The radeonhd (xf86-drivers-radeonhd) driver which is what I though I needed works well, permitting I don’t connect up my external HDMI screen (also full HD at 1920×1200). It took me two days to figure out that if I just knock the external screen res down from max it works, and different modeline workarounds posed by some people just failed horribly. Eventually I also realized that the modelines for the other resolutions was wrong too (display offsets and stuff just didn’t look right) so I read some more and realized that there is also an “ati” driver (xf86-drivers-ati for those in the Gentoo world).
Essentially it boils down to this: enable AGP in your kernel, load up drm module, as well as the radeon kernel module, and set the driver in your device section to ati. Yes, it’s that simple. So what should have been a 20 minute configuration exercise took me the better part of the evenings of three days. The applicable snippets from my /etc/X11/xorg.conf (I have a nasty feeling you don’t even need this file, but then some of my other trickery won’t work):
Section "ServerLayout"
Identifier "DefaultLayout"
Screen "PrimaryScreen" 0 0
...
EndSection
Section "Monitor"
Identifier "LVDS"
Option "Position" "0 0"
Option "PreferredMode" "1920x1200"
EndSection
Section "Device"
Identifier "radeon"
BusID "PCI:1:0:0"
Driver "ati"
# enable some power saving at
# the potential cost of 3D accel
Option "DynamicClocks" "on"
EndSection
Section "Screen"
Identifier "PrimaryScreen"
Device "radeon"
Monitor "LVDS"
DefaultDepth 24
SubSection "Display"
Depth 24
# Set our virtual size to 2*1920 x 1200
Virtual 3840 1200
EndSubSection
EndSection
I’ve got other sections too for getting the synaptics touch pad to work “just right”, and for enabling composite on the keyboard and stuff, so I’m not sure that just copying the above file will result in a working X. All of the above is focused purely around the video. In spite of setting a Virtual screen size, this won’t enable panning, and neither will it allow your cursor to run off the screen, the initial desktop size will be 1920×1200 and if an external screen is connected it will likely mirror on startup (at least, that’s what it does when I restart X).
Now you can play a bit with the xrandr command. With no external screen connected I get this:
$ xrandr
Screen 0: minimum 320 x 200, current 1920 x 1200, maximum 3840 x 1200
VGA-0 disconnected (normal left inverted right x axis y axis)
LVDS connected 1920x1200+0+0 (normal left inverted right x axis y axis) 367mm x 229mm
1920x1200 61.7*+
1920x1080 60.0
1600x1200 59.9
1680x1050 60.0
1400x1050 60.0
1280x1024 59.9
1440x900 59.9
1280x960 59.9
1280x854 59.9
1280x800 59.8
1280x720 59.9
1152x768 59.8
1024x768 59.9
800x600 59.9
640x480 59.4
HDMI-0 disconnected (normal left inverted right x axis y axis)
Note the first line that basically defines the screen as 1920×1200 with the max we set above. This gives us space to once the external monitor is connected to HDMI-0 either mirror, or put it side-by side with the existing screen, so connect the screen, running xrandr now should change the HDMI-0 output to something like:
HDMI-0 connected (normal left inverted right x axis y axis)
1920x1200 60.0 +
1920x1080 60.0
1600x1200 60.0
1680x1050 59.9
1280x1024 60.0
1440x900 59.9
1280x960 60.0
1280x800 59.9
1024x768 60.0
800x600 60.3 56.2
640x480 59.9
Which is all good and well, to make use of that screen real-estate:
xrandr --output HDMI-0 --mode 1920x1200 --right-of LVDS
xrandr --output HDMI-0 --mode 1920x1200 --same-as LVDS
Depending on whether you want a side-by side or a mirrored setup. You can also do a –left-of but on my laptop at least that doesn’t make sense in my head (primary stuff all goes on the left-most screen and I prefer to use my panel as primary with the additional screen as secondary, but I guess for some people that would be preferred). Note that on my system at least both displays goes black for about 2 seconds during the mode switches and stuff.
Now, the one thing that does bug me is that there is no sensible handing of Fn-F3 (the external screen switch function). Fn-F2 (panel on/off) is handled by the BIOS, properly, but Fn-F3 is not. So what you do is firstly write a script that’ll do modeswitching for you when run from the shell. I have $HOME/bin in my path so I just wrote a script that I store in there called dualmode. It’s a relatively dumb script, but non-trivial none the less:
#! /bin/bash
modetime=7
export DISPLAY=:0
now=$(date +%s)
last=$([ -r $HOME/.dualmode ] && stat --format=%Y $HOME/.dualmode || echo 0)
last_time=$(( $now - $last ))
mode=$([ -r $HOME/.dualmode ] && cat $HOME/.dualmode || echo "same-as")
echo "last run $last_time seconds ago"
state=$(xrandr | awk '$1!="LVDS" && / connected [0-9]+x[0-9]+/ { on=1 } END { if(on) { print "on" } else { print "off" }}')
echo "Dual head is currently $state"
if [[ $state = "on" ]] && [[ $last_time -lt $modetime ]]; then
echo -n "Changing from mode $mode to "
[[ $mode = "same-as" ]] && mode="right-of" || mode="same-as"
echo $mode
fi
xrandr | awk '/^[^ ]/ && $1!="Screen" { conn=$1 } /+$/ { if (conn != "LVDS") { print conn" "$1 }}' | while read OUTP RES; do
if [[ $state = "on" ]] && [[ $last_time -ge $modetime ]]; then
echo "Switching off display $OUTP"
xrandr --output $OUTP --off
else
echo "Attempting to set $OUTP to $RES using $mode"
xrandr --output $OUTP --mode $RES --$mode LVDS
fi
done
killall -HUP icewm
echo "All done."
echo $mode > $HOME/.dualmode
Right at the top the modetime value is simply a setting that says “if the external screen is on and we haven’t touched ‘the button’ in less than this number of seconds, switch the external display off instead of switching between mirroring and side-by-side mode”. Next up we determine the duration from when the script was last run. mode= determines the last mode (right-of or mirror). The state= line uses xrandr+awk to try and determine whether an external display is active or not (if your primary display is not LVDS then change as required).
So, firstly we look at the state, and if the state is currently on and we have run this script recently, change the mode to be used.
Next up, for each non-LVDS screen, figure out it’s “preferred” resolution (marked by a + in the xrandr output), this has the side effect of giving us a list of connected displays. Once we have this information we can decide whether the display should be switched off (current state is on and active for longer than modetime) or switch it to the appropriate mirror/right-of mode as well as kick it into preferred resolution.
I also send a HUP signal to icewm (my window manager) to get it to reload and realize that the desktop dimensions has changed, and lastly I store the new mode in ~/.dualmode (both so I can know what I need to switch to, as well as to be able to measure the time between two runs of the script.
And if you’re wondering, the export DISPLAY line is because we’re going to hook this from ACPI :). I (obviously) use the non-root users and ACPI setup, and then in $HOME/.acpi/video I have this:
#! /bin/bash
case $2 in
VGA)
$HOME/bin/dualmode
;;
*)
echo $@ >> /tmp/video-event.txt
;;
esac
This just filters out some other video events (brighness comes through on video/LCD whereas the button comes through on video/VGA, so if the second parameter is VGA, run the dualmode script.
So far this works very well.