Switching Monitor Profiles
It's funny, when your home office is your couch, you tend to forget how nice it can be when you dock a laptop and have all the extra screen real estate a monitor brings. For many years, I left my work laptop docked at work, and when I worked from home, I just VPNed in with a personal laptop. Lately though, I've recognized the benefits of splitting personal life and work, so I've taken to carrying my laptop with me when I go to and from the office. Because we invested in a docking station, it's relatively simple to transition between a laptop on my lap and a laptop on a desk with an extra monitor—except for one little thing: my external monitor is in portrait mode.
It must have been about two years ago that I started favoring widescreen monitors in portrait mode (Figure 1). Really, all I need to get work done is a Web browser and a few terminals, and I found if I keep the Web browser on the laptop screen, I can fit a nice large screen session or two in all the vertical space of a portrait-mode monitor. This makes reading man pages and other documentation nice, plus I always can split my screens vertically if I need to compare the contents of two terminals (see my "Do the Splits" column in the September 2008 issue for more information on how to do that: http://www.linuxjournal.com/article/10159). The only problem with portrait mode is that all the GUI monitor configuration tools tend not to handle portrait-mode monitors well, particularly if you want to combine them with a landscape-mode laptop screen. So, I found I needed to run a special xrandr command to set up the monitor and make sure it lined up correctly with my laptop screen. Plus, every time I transition between docked and undocked modes, I need to move my terminal windows from the large portrait-mode monitor over to a second desktop on my laptop screen. This all seemed like something a script could figure out for me, so in this article, I explain the script I use to transition from docked to undocked mode.
Figure 1. Kyle's Current Desktop Setup
Basically, my script needs to do two things when it's run. First, it needs to run the appropriate xrandr command to enable or disable the external display, and second, it needs to reset all of my windows to their default location. Although I could just have one script I run when I'm docked and another when I'm undocked, I can find out my state from the system itself, so I can keep everything within one script. I've set up a script like this on my last two work-provided laptops and on the ThinkPad X220, I was able to use a /sys file to gauge the state of the dock:
#!/bin/bash
DOCKED=$(cat /sys/devices/platform/dock.0/docked)
case "$DOCKED" in
"0")
echo undocked
;;
"1")
echo docked
;;
esac
Unfortunately, on my new laptop (a ThinkPad X230) this file no longer can detect the dock state. At first I was annoyed, but when writing this column, I realized that this made the script potentially more useful for everyone who doesn't have a docking station. My workaround was to use xrandr itself to check for the connection state of a video device my external monitor was connected to that was present only when I was docked. If you run xrandr with no other arguments, you will see a list of a number of different potential video devices on your system:
$ xrandr
Screen 0: minimum 320 x 200, current 1366 x 768, maximum 8192 x 8192
LVDS1 connected 1366x768+0+0 (normal left inverted right x axis y axis)
↪277mm x 156mm
1366x768 60.0*+
1360x768 59.8 60.0
1024x768 60.0
800x600 60.3 56.2
640x480 59.9
VGA1 disconnected (normal left inverted right x axis y axis)
HDMI1 disconnected (normal left inverted right x axis y axis)
DP1 disconnected (normal left inverted right x axis y axis)
HDMI2 disconnected (normal left inverted right x axis y axis)
HDMI3 disconnected (normal left inverted right x axis y axis)
DP2 disconnected (normal left inverted right x axis y axis)
DP3 disconnected (normal left inverted right x axis y axis)
In the above case, the laptop is not docked, so only the primary monitor (LVDS1) is connected. When I docked the device and ran the same command, I noticed that my monitor was connected to HDMI3, so I could grep for the connection state of HDMI3 to detect when I'm docked. My new skeleton script looks more like this:
#!/bin/bash
xrandr | grep -q "HDMI3 disconnected"
case "$?" in
"0")
echo undocked
;;
"1")
echo docked
;;
esac
In your case, you would compare the output of xrandr when docked (or when an external monitor is connected) and when undocked, and use that to determine which device it corresponds to.
Now that I can detect whether I'm docked, I should do something about it. The first thing I need to do is to enable output on my external monitor (HDMI3), tell xrandr that it's to the right of my laptop screen, and set it to portrait mode by telling xrandr to rotate it left:
/usr/bin/xrandr --output HDMI3 --auto --right-of LVDS1 --rotate left
This works fine; however, the way that the portrait-mode monitor and my laptop line up on the desktop makes moving a mouse between the two rather awkward. When I move from the top of the laptop screen to the far right edge, the mouse pointer moves a foot up to the top of the external monitor. Ideally, I'd like the mouse pointer to more or less be lined up when it crosses between screens, but because one monitor is landscape and the other is portrait, I need to tell xrandr to place my laptop monitor lower in the virtual desktop. Depending on your respective resolutions, this position takes some tinkering, but I found the following command lined up my two displays well:
/usr/bin/xrandr --output LVDS1 --pos 0x1152
This takes care of my screen when I'm docked, so when I'm undocked, I basically have to undo any of the above changes I've made. This means turning the HDMI3 output off and moving the position of LVDS1 back to the 0x0 coordinates:
/usr/bin/xrandr --output HDMI3 --off
/usr/bin/xrandr --output LVDS1 --pos 0x0
The complete case statement turns out to be:
#!/bin/bash
xrandr | grep -q "HDMI3 disconnected"
case "$?" in
"0") # undocked
/usr/bin/xrandr --output HDMI3 --off
/usr/bin/xrandr --output LVDS1 --pos 0x0
;;
"1") # docked
/usr/bin/xrandr --output HDMI3 --auto --right-of LVDS1
↪--rotate left
/usr/bin/xrandr --output LVDS1 --pos 0x1152
;;
esac
After I saved the script, I bound a key combination on my desktop I could press to execute it whenever I docked or undocked. Of course, ideally I would set up some sort of udev script or something like it to run the script automatically, but so far, I haven't found the right hook that worked on my laptop. The only other addition I've made is after the above case statement, I sleep for a second and then call a reset_windows shell script that uses wmctrl, much like I discussed in my November 2008 Hack and / column "Memories of the Way Windows Were" (http://www.linuxjournal.com/article/10213), only it also contains the same case statement so it moves windows one way when docked and another when not:
#!/bin/bash
xrandr | grep -q "HDMI3 disconnected"
case "$?" in
"0") # undocked
wmctrl -r 'kyle-ThinkPad-X230' -t 1
wmctrl -r 'kyle-ThinkPad-X230' -e '0,2,24,1362,362'
wmctrl -r snowball -t 1
wmctrl -r snowball -e '0,2,410,1362,328'
;;
"1") # docked
wmctrl -r 'kyle-ThinkPad-X230' -t 0
wmctrl -r 'kyle-ThinkPad-X230' -e '0,1368,0,1080,1365'
wmctrl -r snowball -t 0
wmctrl -r snowball -e '0,1368,1387,1080,512'
;;
esac
Of course, the above wmctrl commands are completely custom to my terminal titles, but it should serve as an okay guide for getting started on your own. In my case, I want to move two terminals to the second desktop when in laptop mode and to the external monitor on the first desktop when docked. Why not just combine the two scripts? Well, I want to be able to reset my windows sometimes outside of docking or undocking (this script also is bound to a different key combo). In the end, I have a simple, easy-to-modify set of scripts I can use to keep windows and my desktops exactly how I want them.