Horizontally Scaling Displays

I had old hardware, but I wanted more display space.

The first three monitors were easy (all code examples are adopted from the actual script, expect minor syntax errors)

xrandr --fb 3920x1024 \
  --output DVI-D-0 --pos 0x0 --panning 1280x1024+0+0 \
  --output HDMI-0 --pos 1280x0 --panning 1360x768+1280+0 \
  --output VGA-0 --pos $((1280 + 1360))x0 --panning 1280x1024+$((1280 + 1360))+0

But then the video card had no more output ports. And I still had a laptop which was uselessly sitting around. And it came with a screen. Surely there must be a way...

( while sleep 0.1; do x11vnc -forever -viewonly -localhost -clip 1366x768+0+0; done ) &
( ssh -R 5900:localhost:5900 drahflow@laptop 'export DISPLAY=:0; vncviewer FullScreen=1 ViewOnly=1 UseLocalCursor=0 geometry=1366x768+0+0 localhost' ) &

... ok, but the laptop is left of the other three monitors, and anyway I'd not like a copy, but more space. And also, someone gave me an old monitor. Maybe I can attach this to the laptop? Oh, and here is another old laptop, I wonder... Ah yeah, and some tablets. And while I'm at it, my phone has, technically, a screen, right?

So what features can we ducktape together to make this work?

Xorg framebuffer setup

The XRandR extension allows to define a virtual framebuffer to near-arbitrary dimensions (depending on how many pixels the video card + driver feel like they can handle). My GeForce GT710 is willing to do 16384x16384, i.e. plenty.

So let's order some.

xrandr --fb 5286x3248

Furthermore each output can be put at a specific position with —pos:

  xrandr --fb 5286x3248 \
    --output DVI-D-0 --pos 1366x1024 \
    --output HDMI-0 --pos $((1366 + 1280))x1024 \
    --output VGA-0 --pos $((1366 + 1280 + 1360))x$((2048 - 1024 - 600))

Unfortunately, Xorg will be very clever and make sure the mouse pointer cannot hide in the dark corners where "no display" can ever go. Usually, a larger framebuffer would be used with panning displays which follow the mouse. But of course, in a true multi-monitor setup, each monitor should display the same area. But, the —panning parameter takes (optionally) both an area where the display may pan to, and an area where the display will actually track the pointer. The mouse pointer hiding logic looks at the first, so it is possible to have the mouse in areas the display could, but won't, pan to. It might be necessary to move the mouse pointer around a bit to pan the displays until they all arrive at their final panning position, though.

xrandr --fb 5286x3248 \
  --output DVI-D-0 --pos 1366x1024 --panning $((1366+1280))x3248+0+0/1280x1024+1366+1024 \
  --output HDMI-0 --pos $((1366 + 1280))x1024 --panning 1360x$((768 + 600))+$((1366 + 1280))+$((2048 - 768 - 600))/1360x768+$((1366+1280))+$((2048 - 768 - 600)) \
  --output VGA-0 --pos $((1366 + 1280 + 1360))x$((2048 - 1024 - 600)) --panning 1280x$((1024 + 600))+$((1366 + 1280 + 1360))+$((2048 - 1024 - 600))/1280x1024+$((1366 + 1280 + 1360))+$((2048 - 1024 - 600))

Now, x11vnc can copy pixels from outside the panable area and forward them to whereever. But my window manager (i3) will believe that I have very large monitors now, and the workspaces will not correctly lie on the actually displayed parts.

However, there is a fix:

xrandr --setmonitor DVI-D-0-FIXED 1280/376x1024/301+1366+1024 DVI-D-0
xrandr --setmonitor HDMI-0-FIXED 1360/160x768/90+$((1366+1280))+$((2048-768-600)) HDMI-0
xrandr --setmonitor VGA-0-FIXED 1280/338x1024/270+$((1366+1280+1360))+$((2048-1024-600)) VGA-0

(and restart i3)

Now, there are three workspaces exactly where configured. But what about those other areas planned for x11vnc forwarding? Turns out the output argument at the end of —setmonitor can be "none":

xrandr --setmonitor VIRTUAL-0 1366/376x768/211+0+$((2048-768)) none
xrandr --setmonitor VIRTUAL-1 1024/100x600/50+$((1024))+$((2048)) none
xrandr --setmonitor VIRTUAL-2 1280/376x1024/301+1366+0 none
xrandr --setmonitor VIRTUAL-3 1366/376x768/211+0+$((2048-768-768)) none
xrandr --setmonitor VIRTUAL-4 800/100x480/50+$((1366+1280+1024))+$((2048-600)) none
xrandr --setmonitor VIRTUAL-5 1024/100x600/50+0+$((2048)) none
xrandr --setmonitor VIRTUAL-6 1024/100x600/50+0+$((2048+600)) none

Seven more "monitors". And (after i3 restart) corresponding workspaces. The numbers after the slashes are DPI, and some of them guaranteed to be guessed wrong, but so far it did not seem to matter.

Networking

It would be nice to have each x11vnc on a fixed, separate port from the others. And also not blocking script invocation:

uxterm -e "while sleep 0.1; do x11vnc -forever -passwd VerySecret -rfbport 5903 -viewonly -localhost -clip 1366x768+0+$((2048-768-768)); done" &
uxterm -e "ssh -R 5903:localhost:5903 root@display0 'export DISPLAY=:0; vncviewer -fullscreen -viewonly -encoding \"tight copyrect\" localhost:5903'" &

(per virtual monitor, obviously).

Or, less securely though, to offer a VNC endpoint in another WiFi network, by SSHing to the router and forwarding from there (note the missing -viewonly to allow touch-input from the phone, and the explicit listening address on the port forwarding ssh):

uxterm -e "while sleep 0.1; do x11vnc -forever -passwd VerySecret -rfbport 5903 -localhost -clip 1366x768+0+$((2048-768-768)); done" &
uxterm -e "ssh -R 192.168.1.1:5903:localhost:5903 root@wifirouter" &

... and open the android VNC viewer on the phone manually.

Comandeering i3

For the laptop driving two devices, each of which will be showing a different vncviewer, a simple -fullscreen is not going to cut it. Instead I'm opening vncviewers and then push them exactly to the right spot via i3-msg:

ssh drahflow@laptop 'DISPLAY=:0 xrandr --output HDMI1 --auto --right-of eDP1'
uxterm -e "ssh -R 5900:localhost:5900 drahflow@laptop 'export DISPLAY=:0; ~/i3.install/bin/i3-msg workspace 1; ~/i3.install/bin/i3-msg move workspace to output eDP1; vncviewer FullScreen=0 ViewOnly=1 UseLocalCursor=0 geometry=1366x768+0+0 localhost'" &

sleep 2

ssh drahflow@laptop 'DISPLAY=:0 ~/i3.install/bin/i3-msg floating enable'
ssh drahflow@laptop 'DISPLAY=:0 ~/i3.install/bin/i3-msg resize shrink up 10px'
ssh drahflow@laptop 'DISPLAY=:0 ~/i3.install/bin/i3-msg resize set 1368 770'

... and to get the pesky client-side curser out-of-view

ssh drahflow@laptop 'DISPLAY=:0 xdotool mousemove 0 767'

... or, where possible, start Xorg with

Xorg -nocursor ...

Also DPMS really likes to get in the way of uninterrupted display enjoyment:

ssh drahflow@laptop 'DISPLAY=:0 xset b off'
ssh drahflow@laptop 'DISPLAY=:0 xset dpms 0 0 0'
ssh drahflow@laptop 'DISPLAY=:0 xset s 0 0'
ssh drahflow@laptop 'DISPLAY=:0 xset dpms force on'

More Workspaces on i3

For completeness sake, I have these workspace bindings in .i3/config

bindsym Mod4+1 workspace 1
bindsym Mod4+2 workspace 2
bindsym Mod4+3 workspace 3
bindsym Mod4+4 workspace 4
bindsym Mod4+5 workspace 5
bindsym Mod4+6 workspace 6
bindsym Mod4+7 workspace 7
bindsym Mod4+8 workspace 8
bindsym Mod4+9 workspace 9
bindsym Mod4+0 workspace 10
bindsym Mod4+F1 workspace 11
bindsym Mod4+F2 workspace 12
bindsym Mod4+F3 workspace 13
bindsym Mod4+F4 workspace 14
bindsym Mod4+F5 workspace 15
bindsym Mod4+F6 workspace 16
bindsym Mod4+F7 workspace 17
bindsym Mod4+F8 workspace 18
bindsym Mod4+F9 workspace 19
bindsym Mod4+F10 workspace 20
bindsym Mod4+F11 workspace 21
bindsym Mod4+F12 workspace 22

bindsym Mod4+Shift+1 move container to workspace 1
bindsym Mod4+Shift+2 move container to workspace 2
bindsym Mod4+Shift+3 move container to workspace 3
bindsym Mod4+Shift+4 move container to workspace 4
bindsym Mod4+Shift+5 move container to workspace 5
bindsym Mod4+Shift+6 move container to workspace 6
bindsym Mod4+Shift+7 move container to workspace 7
bindsym Mod4+Shift+8 move container to workspace 8
bindsym Mod4+Shift+9 move container to workspace 9
bindsym Mod4+Shift+0 move container to workspace 10
bindsym Mod4+Shift+F1 move container to workspace 11
bindsym Mod4+Shift+F2 move container to workspace 12
bindsym Mod4+Shift+F3 move container to workspace 13
bindsym Mod4+Shift+F4 move container to workspace 14
bindsym Mod4+Shift+F5 move container to workspace 15
bindsym Mod4+Shift+F6 move container to workspace 16
bindsym Mod4+Shift+F7 move container to workspace 17
bindsym Mod4+Shift+F8 move container to workspace 18
bindsym Mod4+Shift+F9 move container to workspace 19
bindsym Mod4+Shift+F10 move container to workspace 20
bindsym Mod4+Shift+F11 move container to workspace 21
bindsym Mod4+Shift+F12 move container to workspace 22

And I created a script which orchestrates correct workspace to output assignment and start some programs in "expected" locations (but I read that i3 has actually better features for this):

i3.install/bin/i3-msg workspace 2
i3.install/bin/i3-msg move workspace to output VGA-0

xterm -e "fetchmail -N" &
xterm -e 'cd c/intercom; ./receiver 4000' &

sleep 2

i3.install/bin/i3-msg workspace 6
i3.install/bin/i3-msg move workspace to output HDMI-0

xterm -e 'LIBV4LCONTROL_FLAGS=1 LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libv4l/v4l2convert.so firefox/firefox' &

TODO

Writing a script to generate the setup script from an easy specification of the current physical layout and required port forwarding incantations.

Aktion UBERWACH! Don't be intimidated. The image referenced will only log accesses by our most beloved government.