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?
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.
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.
And for those cases of Android displays which don't offer adb reverse port connections via TCP forwarding over adb stdio (disabling the connection-level escape key)
uxterm -e "ssh -R 5901:localhost:5901 -tt root@192.168.1.8 socat TCP-CONNECT:127.0.0.1:5901 SYSTEM:\\'adb -s 300427771550a700 shell -e none su -c /system/debian/enter.sh /socat.sh\\'" &
with the device-side script (disabling console escape processing and echo)
android# cat /socat.sh #!/bin/sh stty raw -echo exec socat TCP-LISTEN:5900 -
and with an explicit encoding list to xtightvncviewer, which otherwise figures the connection goes to "the same machine" and doesn't need compression
android# cat /vnc.sh echo 'VerySecret' | DISPLAY=:0 xtightvncviewer -fullscreen -autopass -encodings "tight copyrect" 127.0.0.1::5900
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'
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' &
Of course, you wouldn't want to run all those shell commands every time you needed a display. Nor is it particularly fun (after maybe the fifth time) to recalculate the required coordinates after physically shuffling devices.
To make my life easier, I thus created this management script.
And together with
assign [title="^displays$"] workspace 1
in the i3 config, this is quite comfortable.
Finally, to soft-switch the tablets on and off, it is useful to collect the required backlight control commands into a script as well.
All these screens belong to the same desktop session (even the weird one on the right, where I replaced a failed backlight with ... other luminous things).