Extending your X11 Desktop using VNC
Published: March 12, 2016
I love multihead setups and I am a thrifty grad student. Today, this combination lead to an interesting and fun Saturday morning project. I figured out how to add an additional monitor to my dual-monitor set-up, which is already a little convoluted.
Let me clear things up: My main setup currently consists of a Thinkpad with its lid closed connected to two monitors (one over VGA, one over DisplayPort). Since my laptop has no third physical display connector, I had to get creative in order to add a third physical monitor to my set-up.
I use a cheap DisplayLink adapter at home for exactly this reason, but unfortunately the official dirvers are buggy and the whole set-up is generally very unreliable. So, with me being the thrifty grad student that I am, I did not want to shell out another $30 for a piece of hardware that doesn't even really work.
What I ended up doing is pretty neat.
The high-level description of my set-up is the following: I have a desktop machine that I was using headless until today. It is now connected to my third monitor and runs the TightVNC client, which is being served by x11vnc. In other words, I'm using xrandr to create a virtual output device on my laptop, I make it available for "remote" viewing using x11vnc, and use the TightVNC client on my desktop machine to view it over the network. My desktop machine and my laptop are connected via a small switch behind my desk, which means that the connection is fast enough to even watch YouTube videos on my networked monitor.
I was heavily inspired by this post on the arch forums and added some tricks of my own to make this set-up comfortable.
For this blog post, I will follow along the lines of the forum post above and describe how this can be replicated. I have most of the code below sitting in a bash script, which I invoke whenever I get to the office in the morning.
Step 1: Create a temporary directory to hold PIDs and logs
This is mostly for debugging purposes and will hold most of the relevant information for my SSH tunnel and the VNC server.
TMPD=/tmp/extra_screen if ! [[ -d $TMPD ]]; then mkdir -p $TMPD fi
Step 2: Force XRandR to connect the VIRTUAL1
device to our X11 desktop
The below part is still a little messy, but it gets the job done for now. I'm
using the new monitor in a portrait orientation, so I'm creating a output of
dimension 1024x1280
. I'm also telling xrandr to put this output to the right
of the output DP2
.
# the local virtual xrandr device DEVICE="VIRTUAL1" # get the modeline from # gtf 1024 1280 60 | sed -n 's/.*Modeline "\([^" ]\+\)" \(.*\)/\1 \2/p' MODELINE="1024x1280_60.00 110.66 1024 1096 1208 1392 1280 1281 1284 1325 -HSync +Vsync" NAME="1024x1280_60.00" # FIXME get rid of error messages xrandr --delmode "$DEVICE" "${NAME}" xrandr --rmmode "${NAME}" xrandr --newmode ${MODELINE} xrandr --addmode "$DEVICE" "${NAME}" xrandr --output $DEVICE --mode $NAME --right-of DP2
The above commands extend my desktop over a—as of right now—invisible area
of 1024x1280
pixels. We could theoretically drag windows in this area. Next,
I'm using VNC to make this area available to a client on my desktop machine.
Step 3: Generate a random VNC password and store it on the desktop machine
From now on, any reference to $REMOTE
should be replaced with SSH username
and hostname, e.g. [email protected]
.
PW=$(openssl rand -hex 50 | vncpasswd -f | tee $TMPD/vncpw) scp $TMPD/vncpw $REMOTE:~/.ava_vncpw
Step 4: Create a reverse SSH tunnel to the desktop machine
Since this is the 21st century, we want to encrypt any traffic that we send through a network. Here, we set up a reverse SSH Tunnel, which forwards port 5900 on my laptop to port 5900 on my desktop machine through an encrypted pipe. I'm also using the nohup command to send the process in the background and have it persist even after I close my terminal.
PORT=5900 nohup ssh -2tnNv -R $PORT:localhost:$PORT $REMOTE > $TMPD/tunnel_log 2>&1 & echo -n $! > $TMPD/tunnel_pid # write the pid of the sshtunnel to kill later
Step 5: Launch x11vnc
, clipped to the invisible region
x11vnc has a bunch of configuration options and I spent a significant amount of time reading tweaking its configuration to get the best performance.
CLIP=$(xrandr | grep "^$DEVICE.*$" | grep -o '[0-9]*x[0-9]*+[0-9]*+[0-9]*') nohup x11vnc -clip $CLIP -noxinerama -noxrandr \ -repeat -localhost -nevershared -forever \ -rfbauth $TMPD/vncpw \ -nowf -noncache -wait 1 -defer 1 > $TMPD/x11vnc_log.log 2>&1 & echo -n $! > $TMPD/x11vnc_pid # write the pid of x11vnc to kill later
-clip
defines the area which is exported. This coincides with the area of the output created using the mode line aboveI had to set
-noxinerama
to get rid of some nasty letterboxingWithout setting
-repeat
, I couldn't enter a letter more than once by long-pressing a key on the keyboard. That was annoying-localhost
letsx11vnc
only accept connections fromlocalhost
, which is fine since we're using a SSH tunnel.-forever
makesx11vnc
not quit after the first client quitswithout the
-noncache
switch,x11vnc
exported a much larger area filled with mostly black pixels
Step 4: Be lazy and write a script to kill x11vnc
and the SSH tunnel
This saves a few keystrokes during debugging.
KILL=$TMPD/kill.sh echo "#!/bin/sh" > $KILL echo -n "kill $(cat $TMPD/tunnel_pid) && " >> $KILL echo -n "kill $(cat $TMPD/x11vnc_pid) && " >> $KILL echo "echo \"Killed x11vnc and SSH.\"" >> $KILL chmod +x $KILL
Step 5: Start a VNC client on the desktop machine
Since I'm not doing anything else on this machine other than running the VNC
client, I have an infinite loop in my .xinitrc
file which attempts to restart
the client if it is not running.
# rotate the display xrandr --output DVI-0 --rotate left # disable screen sleep xset s off xset -dpms # unclutter removes the local mouse pointer unclutter & force reloading of the vnc viewer to simulate plug & play while true; do if ! ps aux | grep -v grep | grep -q vncviewer; then vncviewer localhost:0 -passwd ~/.ava_vncpw -viewonly -fullscreen -encodings "copyrect tight" else sleep 0.5 fi done
Step 6: Celebrate!
This was fun. I hope this inspires someone to do their own thrifty grad student multihead set-up.