Escritorios Multipantalla con VNC

Hace unos dos años tuve una idea algo descabellada: ¿Sería posible crear un sistema de escritorio de "doble pantalla" utilizando dos ODROID, cada uno con una pantalla diferente, pero que actuasen como un escritorio unificado? La idea era tener un odroid "maestro" que fuera más potente en el que ejecutaríamos las aplicaciones (como un XU4 o N2) y un "esclavo", preferentemente más barato, que actuase únicamente como un terminal básico sin procesamiento (un C1/C2). Investigué un poco y encontré xdmx, que es un administrador de ventanas distribuido que podría funcionar, pero resultaba que había sido abandonado hace una década, de modo que no nos era factible.

Continúe investigando y creé un sistema algo enrevesado que básicamente hacia uso de Xpra (https://xpra.org) de un modo para el cual no estaba diseñado. Funciona, pero el rendimiento era pésimo: alrededor de 0.5 fps en todo el escritorio, lo cual era muy perceptible (tienes más detalles en: https://forum.odroid.com/viewtopic.php?t=35710). Después tuve la idea de experimentar con vnc, y esta vez los resultados fueron bastante mejores.

La idea

La idea es aparentemente muy simple. En el sistema maestro, inicias una sesión X11 normal, con lightdm incluido. Usas xrandr para extender el escritorio (una vez que hayas iniciado sesión) para que el nuevo tamaño del escritorio cubra ambas pantallas (yo utilicé pantallas idénticas, aunque debería funcionar con pantallas de diferentes resoluciones haciendo algunos ajustes). Luego, inicias una sesión X11vnc que se conecte a :0 y que tenga una resolución fija que es la suma de ambas pantallas.

El esclavo puede ejecutar una imagen mínima o de escritorio, pero necesita tener Xorg y debe soportar la resolución del monitor de destino. Éste Iniciará una sesión Xorg independiente que lee los comandos de inicio desde /root/.xinitrc. El archivo apunta a un script que intentará iniciar vncviewer en una especie de bucle infinito, conectándose a la sesión del maestro. Si todo va bien, terminaras con dos pantallas replicadas, que no es lo mismo que una configuración de doble pantalla. Aquí es donde entra en juego la magia. Una vez que se inicia vncviewer, éste se mueve con la ayuda de xdotool hacía la izquierda por el ancho de la pantalla. Esto hace que lo que normalmente sería la parte izquierda del escritorio se represente fuera de la pantalla (y se superponga lógicamente con lo que hay en la pantalla izquierda), dejando espacio para el contenido de la pantalla derecha. ¿Confuso? Aquí tienes un diagrama:

Figure 01 - The grayed-out parts are not visible physically on their respective screens
Figura 01: las partes atenuadas en gris no son visibles físicamente en las respectivas pantallas

Recuerda que el sistema maestro muestra ambas pantallas, pero solo la parte izquierda es físicamente visible, y el sistema esclavo también muestra ambas pantallas (a través de VNC), pero solo la parte derecha es físicamente visible. El resultado final es la ilusión de una configuración de doble pantalla.

Configuración principal

Mi banco de pruebas consiste en un ODROID-XU4 con Ubuntu 18.04 Mate como sistema maestro y un ODROID-N2 con Ubuntu 18.04 Mate como esclavo. Como he dicho anteriormente, el esclavo puede ser una placa menos potente (incluso de otra marcha), pero yo tenía un N2 a mano para experimentar. El maestro maneja el monitor izquierdo en la configuración mientras el esclavo está conectado al monitor derecho (este orden es importante). En mi caso, ambos monitores tienen una resolución de 1680x1050. Deberías poder llegar a 1080p por pantalla, pero no estoy seguro de si puedes tener dos pantallas 4K porque el escritorio parece estar limitado a 4096x4096 píxeles (aunque puede haber una forma de saltarse esta limitación: https://bit.ly/2JsDWMc).

El sonido debe estar conectado al maestro, pero el teclado y el ratón pueden conectarse a cualquiera de los sistemas una vez que se inicie VNC. Ten en cuenta que conectar el teclado y el ratón al esclavo puede provocar movimientos bruscos o ralentizaciones cuando haya mucha actividad en la pantalla, así que, para un mejor rendimiento, conéctalos también al maestro.

Con respecto a las redes, ambos sistemas deben estar en la misma LAN, conectados a través de Ethernet y con direcciones IP estáticas. El máximo consumo de la red que llegue a observar fue de aproximadamente unos 40 Mbps de tráfico VNC cuando reproducía video, de modo que la conexión Ethernet no debería provocar cuellos de botella.

Comencemos con la configuración del odroid maestro:

$ sudo su -
# apt-get install x11vnc pwgen git
Crearemos una contraseña aleatoria de 20 caracteres para VNC (aunque la documentación dice que solo se utilizan los primeros 8 caracteres), y la copiaremos a través de ssh en el sistema esclavo. Supongo que tu esclavo tiene una cuenta sin privilegios, como es odroid.
# pwgen 20 1
# x11vnc -storepasswd
# ssh odroid@slave mkdir /home/odroid/.vnc
# scp /root/.vnc/passwd odroid@slave:/home/odroid/.vnc/passwd
# ssh odroid@slave chmod -R odroid:odroid /home/odroid/.vnc/passwd
A continuación, creamos un servicio systemd para iniciar x11vnc en el arranque. Puedes utilizar y retocar la configuración de ejemplo que hay mi página git (https://github.com/mad-ady/vnc-multiscreen.git). Tendrás que cambiar la resolución combinada para que coincida con la de tu sistema (en mi caso era 3360x1050):
# git clone https://github.com/mad-ady/vnc-multiscreen.git
# cp vnc-multiscreen/master-left/etc/systemd/system/x11vnc.service \
/etc/systemd/system/
# systemctl daemon-reload
# systemctl enable x11vnc
# systemctl start x11vnc
Figure 02 - The X11vnc service, customized with the composed screen resolution
Figura 02 - El servicio X11vnc, personalizado con la resolución de pantalla compuesta

A continuación, debemos añadir un script que se ejecute una vez que inicies sesión (nuevamente, supongo que estás utilizando el usuario odroid para el inicio de sesión de la GUI) y que use xrandr para cambiar el tamaño de tu escritorio. Podríamos haber ejecutado el script desde lightdm (antes de iniciar sesión), pero por alguna razón el fondo del escritorio abarca una única pantalla. Ejecuta los siguientes comandos con el usuario con el que inicias sesión (en el maestro):

$ mkdir .config/autostart
$ cp vnc-multiscreen/master-left/home/odroid/.config/autostart/dual-screen.desktop .config/autostart
$ chmod a+x .config/autostart/dual-screen.desktop
$ gio set .config/autostart/dual-screen.desktop "metadata::trusted" yes
$ sudo cp vnc-multiscreen/master-left/usr/local/bin/set-dual-screen-resolution.sh /usr/local/bin/
Figure 03 - The set-dual-screen-resolution script (on the master) that extends the desktop
Figura 03 – El script set-dual-screen-resolution (en el maestro) que extiende el escritorio

Deberás editar el script /usr/local/bin/set-dual-screen-resolution.sh y fijar tu resolución total en el parámetro fb (que será el tamaño del escritorio) y ajustar la resolución de tu pantalla izquierda en el parámetro panning. Si lo configuras así, el escritorio no empezará a desplazarse cuando muevas el ratón hacia borde de la pantalla.

Eso es todo sobre la configuración básica en el sistema maestro, y ahora le toca el turno al esclavo. Desactivaremos el modo GUI y crearemos y habilitaremos un servicio xorg que inicie un simple servidor X11 y un script para conectarse a vnc:

# apt-get install xtightvncviewer xdotool git
# service lightdm stop
# systemctl set-default multi-user.target
# git clone https://github.com/mad-ady/vnc-multiscreen.git
# cp vnc-multiscreen/slave-right/etc/systemd/system/xorg.service /etc/systemd/system
# systemctl enable xorg
El servidor X analiza .xinitrc y lo ejecuta después del inicio. Así que haremos que inicie nuestro script de inicio VNC.
# cp vnc-multiscreen/slave-right/root/.xinitrc /root/.xinitrc
# chmod a+x /root/.xinitrc
Figure 04 - The Xorg startup service and script
Figura 04 - El servicio de inicio Xorg y el script

El script de inicio VNC iniciará el proceso vncviewer (en un bucle) y luego moverá la ventana a la izquierda. Todo se realiza con ayuda de estos dos scripts:

# cp vnc-multiscreen/slave-right/usr/local/bin/dual-screen-vnc-client.sh /usr/local/bin
# chmod a+x /usr/local/bin/dual-screen-vnc-client.sh
# cp vnc-multiscreen/slave-right/usr/local/bin/window-positioning.sh /usr/local/bin
# chmod a+x /usr/local/bin/window-positioning.sh
Figure 05 - The dual-screen-vnc-client script
Figura 05 - El script dual-screen-vnc-client

Figure 06 - The window-positioning script
Figura 06 - El script window-positioning

Si no estás utilizando pantallas idénticas, deberás editar los dos scripts anteriores y fijar los desfases correctos de tus pantallas.

Ya tenemos hechos los pasos básicos. Puedes reiniciar ambos sistemas y después de iniciar sesión en lightdm deberías tener un escritorio extendido. Sin embargo, hay algunas cosas que no funcionarán como cabría esperar, necesitamos hacer algunos ajustes.

Figure 07 - How your desktop should look
Figura 07 - Cómo debería verse tu escritorio

Retoques (solo en el sistema maestro)

Debes deshabilitar el mosaico del sistema de ventanas MATE (y la composición mientras estés en él), porque cuando intentes maximizar una ventana en la pantalla derecha arrastrando su barra de título hacia la parte superior, la ventana saltará a la pantalla izquierda. Puede hacerlo desde Menu -> Control Center -> Windows -> Placement -> Disable window tiling.

A continuación, debe deshabilitar el contenido de la ventana mientras la mueves, para conseguir así una experiencia más fluida: Menu -> Control Center -> Mate Tweaks -> Windows -> Do not show window content while moving windows.

Como tienes dos pantallas, sería bueno que las ventanas sepan dónde está la división entre las pantallas y permita maximizar las ventanas en cada pantalla. Para esto, necesitas adaptar lo que la librería xinerama le dice a tu servidor X sobre las pantallas disponibles. Afortunadamente, existen una librería "fakexinerama" que puedes instalar en el sistema maestro (https://www.xpra.org/trac/wiki/FakeXinerama).

$ wget https://www.xpra.org/trac/browser/xpra/trunk/fakexinerama/fakeXinerama.c?format=txt -O fakexinerama.c
$ sudo apt-get install libxinerama-dev libx11-dev
$ gcc -O2 -Wall Xinerama.c -fPIC -o libXinerama.so.1.0.0 -shared
$ sudo mv /usr/lib/arm-linux-gnueabihf/libXinerama.so.1.0.0 /usr/lib/arm-linux-gnueabihf/libXinerama.so.1.0.0--original
$ sudo cp libXinerama.so.1.0.0 /usr/lib/arm-linux-gnueabihf/
Si tienes una arquitectura diferente en el maestro (arm64, x86_64), modifica la ruta del archivo de la librería. Puedes encontrar esto en:
$ find /usr/lib -name libXinerama.so
Fake Xinerama lee la configuración de la pantalla desde el directorio de inicio del usuario de la GUI ~/.fakexinerama. El archivo empieza con el recuento de monitores (que es 2 en nuestro caso) y enumera cada monitor por línea con ajustes y resolución:
$ cat ~odroid/.fakexinerama
2
#left screen, starts at x=0 and y=0 and has a size of 1680x1050 pixels
0 0 1680 1050
#right screen, starts at x=1680 and y=0 and has a size of 1680x1050
1680 0 1680 1050
Una vez que reinicies tu sesión de escritorio, las ventanas deberían comportarse como si tuvieras dos pantallas físicas pudiendo maximizarlas en cada pantalla.

Nos falta una integración más: el protector de pantalla solo se activa en la pantalla del sistema maestro. Probablemente solo tenga en cuenta el tamaño de la pantalla, no el tamaño total del escritorio, de modo que queda visible la pantalla derecha. Puede usar gdbus para ver cuándo se activa el protector de pantalla y puedes conectarte por ssh al esclavo y apagar la pantalla con xset dpms force off. Cuando observes que el protector de pantalla se desactiva, puedes volver a habilitar la pantalla derecha.

Para hacer esto, primero debes ser capaz de pasar del maestro al esclavo sin una contraseña. Para ello crearemos una clave y copiaremos la parte pública al esclavo. Omite el primer paso si ya tiene creadas las claves.

$ ssh-keygen -t rsa -C "master key"
$ ssh-copy-id odroid@slave
A continuación, copia el script que vigila el estado del protector de pantalla e inícialo como parte de la sesión de escritorio del usuario. Afortunadamente, systemd también puede manejar servicios de usuario (nuevamente, en el maestro):
$ mkdir -p .config/systemd/user/
$ cp vnc-multiscreen/master-left/home/odroid/.config/systemd/user/screensaver-sync.service .config/systemd/user
$ systemctl --user enable screensaver-sync
$ sudo cp vnc-multiscreen/master-left/usr/local/bin/screensaver-sync.sh /usr/local/bin/
Asegúrese de editar (y probar) el script de sincronización del protector de pantalla, de modo que apunte a la dirección IP correcta del sistema esclavo.

Pros y Contras:

  • El maestro (izquierda) hace todo el trabajo pesado, además tiene el rendimiento más fluido.
  • El esclavo (derecha) se usa para renderizar media pantalla, pero necesita transferir y renderizar la pantalla combinada (incluso si solo ve la mitad), de modo que una actividad intensa en la pantalla izquierda provocará saltos/retrasos en la pantalla derecha
  • Debes encender y apagar ambos dispositivos de forma independiente. Es posible apagar automáticamente el esclavo y luego apagar el maestro, pero es necesario realizar algunos ajustes (por ejemplo, un servicio systemd que se conecte al esclavo para apagarlo).
  • Puedes convertir fácilmente al esclavo en un sistema autónomo deshabilitando el servicio xorg y volviendo a habilitar el sistema gráfico. El maestro puede permanecer como está sin afectar a su funcionalidad.
  • El rendimiento es nativo en la pantalla maestra/izquierda y varía de un par de FPS a ~ 15 FPS en la pantalla esclava/derecha, dependiendo de la actividad de la pantalla. La pantalla derecha es la más adecuada para trabajar con contenido estático, como una página web, un código o un terminal.

Puede ver una demostración aquí (mil disculpas por la mala calidad del video): https://www.youtube.com/watch?v=sSqXX5doCvo&feature=youtu.be

Ideas para mejorar

Perfectamente, todo esto se simplificaría enormemente si solo X11VNC (o quizás con una tecnología de escritorio remoto diferente) pudiera copiar únicamente la mitad de una pantalla de datos en lugar del escritorio completo. De esta manera, se reduciría la carga de trabajo tanto en maestro como en esclavo evitando tener que recurrir a trucos para el movimiento de las ventanas. Puedes mostrar la segunda pantalla en cualquier sistema (por ejemplo, dentro de un navegador en un televisor) sin muchos problemas.

Puede lograr mejor rendimiento si el maestro es un PC (por ejemplo, un ODROID H2) y el esclavo es un ODROID ARM/ARM64. Esto se debe a que la velocidad de lectura de framebuffer en un XU4 es de aproximadamente 45 MB/s (N2 es de unos 443 MB/s), mientras que la GPU Intel alcanza aproximadamente unos 961 MB/s. Más velocidad conlleva una velocidad de actualización más rápida, pero requerirá más ancho de banda de red.

Una cosa más: la técnica anterior se puede ampliar a más de 2 monitores porque puede conectar dos clientes VNC al mismo servidor. Sin embargo, el rendimiento disminuirá considerablemente. Avísame si encuentras alguna forma de mejorar todo esto en el hilo de soporte en https://forum.odroid.com/viewtopic.php?f=52&t=36411.

Be the first to comment

Leave a Reply