Archivo de la categoría: Trucos

Trucos generales para distribuciones

Pintando en el Spectrum (15)

En la última entrada hablé del gestor de tareas. Sin embargo, desde entonces la cosa ha evolucionado mucho y el código actualmente es algo diferente. En concreto, es este:

old_stack:
    DEFW 0
task_pointer:
    DEFW 0

task_run:
    ld (old_stack), SP
    ld iy, task_list
task_loop:
    ld a, (iy+0)
    and a
    jr z, task_end
    ld l, (iy+1)
    ld h, (iy+2)
    ld sp, hl
    ld (task_pointer), iy
    ret
task_end:
    ld sp, (old_stack)
    ret
task_yield:
    ld iy, (task_pointer)
    ld hl, 0
    add hl, sp ; get the current stack
    ld (iy+1), l
    ld (iy+2), h
    ld e, (iy+0)
    ld d, 0
    add iy, de ; jump to next entry
    jp task_loop

task_list:
    TASK_ENTRY 36, task1
    TASK_ENTRY 10, task2
    TASK_ENTRY 14, task3
    DEFB 0 ; fin de la lista de tareas

El primer cambio es que ahora guardo el valor de IY, de manera que puede modificarse dentro de la tarea sin problemas. El segundo es que ahora, cada tarea puede tener un tamaño de pila diferente, lo que permite ahorrar mucha memoria, pues hay tareas que casi no hacen llamadas anidadas, mientras que otras sí.

El segundo cambio fue crear una macro que simplifica crear la tabla de tareas. Al final, en la etiqueta task_list, tengo una lista de tareas de ejemplo con un total de tres tasks, donde el código de la primera empieza en task1, el de la segunda en task2, y el de la tercera en task3, y sus stacks respectivos tienen un tamaño de 36, 10 y 14 bytes. TASK_ENTRY es una macro con el siguiente formato:

MACRO TASK_ENTRY, STACK_SIZE, TASK_FUNCTION
    DEFB STACK_SIZE+3
    DEFW $+STACK_SIZE
    DEFS STACK_SIZE-2
    DEFW TASK_FUNCTION
ENDM

El primer byte indica el tamaño completo de la entrada, que será de tres bytes más que el tamaño de pila que queremos asignar a esta task. Los dos siguientes bytes contienen el valor del registro SP de esta tarea, o sea, el actual puntero de pila. Se inicializa de manera que apunte al final del bloque reservado para la pila. Luego vienen tantos bytes como tamaño de pila queramos menos 2, de manera que reservamos los dos últimos bytes para poner la dirección de inicio de la tarea. De esta manera, cuando se crea la entrada para una tarea, la pila sólo contiene la dirección de inicio de dicha tarea, y el puntero de pila apunta justo a ella.

Por último, añadí una función optativa para depurar las tareas. Es ésta:

    ld hl, (task_pointer)
    inc hl
    inc hl
    inc hl ; jump over the size and the stored SP pointer
    ld d, 0
task_check_size:
    ld a, (hl)
    and a
    jr nz, task_end_check_size
    inc d
    inc hl
    jr task_check_size
task_end_check_size:
    ld a, d
    ld SP, (old_stack)
    call do_debug

Este bloque se puede ejecutar justo después de que una tarea haya vuelto (llamando a task_yield), y lo que hace es comprobar cuanto espacio se ha llegado a ocupar en la pila. Para ello se basa en que el bloque de la pila se inicializó a cero, y que cuando se saca un valor de la pila sólo se incrementa SP, pero el valor sigue estando ahí. De esta manera, si vemos cuantos bytes seguidos cuyo valor sea cero hay desde el principio, sabremos hasta donde ha llegado a crecer la pila. Si este tamaño es cero significa que esa pila se ha llenado del todo en algún momento (y hasta es posible que haya sobreescrito alguna posición extra), por lo que en ese caso hay que aumentar el tamaño.

La llamada a CALL do_debug se supone que muestra, de alguna manera, el contenido del registro A.

Qué pinta tiene

Pues el juego ya lo he terminado y ahora mismo estoy puliendo los textos y la traducción al inglés, y los petatesters están buscando los bugs y pifias varias. Pero tiene esta pinta:

Acceso remoto con IP dinámica

Poder acceder a tus datos en cualquier momento, aunque no estés es casa, es una maravilla: abrir un FTP y poder copiar cualquier cosa desde tu ordenador siempre es muy práctico. Por desgracia, en la práctica suele haber dos problemas:

  • Si tu ordenador está apagado y no hay nadie en casa, no puedes acceder
  • Si tienes IP dinámica es probable que no sepas cual tienes en un momento determinado

Hay gente que nunca apaga su PC, pero ese no es mi caso: prefiero tenerlo apagado cuando no lo utilizo. Por otro lado, aunque mi proveedor de internet es bastante razonable en cuanto a las IPs dinámicas (intenta darte siempre la misma), alguna que otra vez cambia. Por eso quería encontrar una solución que me permitiese acceder de manera cómoda a mi equipo siempre que quiera. Y la encontré en Telegram.

Básicamente se me ocurrió crear un pequeño bot de Telegram que corre en una Raspberry Pi que utilizo como servidor multimedia en casa y que, por tanto, siempre está encendida. Este bot acepta dos comandos:

  • ¿Cual es la IP externa? Para ello el bot, que corre en local, utiliza uPnP para preguntar al router cual es la dirección IP actual, y responde con un mensaje indicándola.
  • Encender ordenador principal, cosa que hace mediante WakeOnLan.

Con estas dos opciones ya tengo acceso constante a mi ordenador desde el exterior sin necesidad de tenerlo encendido todo el rato ni de jugar a la ruleta para ver si conservo la misma IP que la última vez que me conecté. Este es el código:

#!/usr/bin/env python3

import telegram
from telegram.ext import Updater, CommandHandler
import miniupnpc
from wakeonlan import send_magic_packet

# la MAC del equipo a encender
mad_addr = 'XX:XX:XX:XX:XX:XX'

# El token de nuestro bot
updater = Updater("XXXXXXX:YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY")

# Solo nos autorizamos a nosotros, para evitar que se nos cuelen hasta la cocina
def check_id(update):
    """ Incluir comprobación de identidad. Si es correcta, devolver TRUE;
        si no, devolver FALSE """
    if update.message.from_user.id != "XXXXXXXXXXX":
        return False
    return True


# Devuelve un mensaje de Telegram con la IP actual
def get_ip(bot, update):

    if check_id(update):
        u = miniupnpc.UPnP()
        u.discoverdelay = 200
        u.discover()
        u.selectigd()
        update.message.reply_text("IP externa: {:s}".format(u.externalipaddress()))
    else:
        update.message.reply_text("Acceso denegado")


# Utiliza WakeOnLan para encender el equipo
def encender(bot, update):
    if check_id(update):
        send_magic_packet(mac_addr)
        update.message.reply_text("Encendiendo")
    else:
        update.message.reply_text("Acceso denegado")


# Configuramos nuestro bot con los comandos nuevos
updater.dispatcher.add_handler(CommandHandler('ip', get_ip))
updater.dispatcher.add_handler(CommandHandler('encender', encender))

# y lo lanzamos
updater.start_polling()
updater.idle()

Utiliza los módulos python-telegram-bot, wakeonlan y miniupnpc. Como vemos, se definen dos comandos, ip y encender. Eso sí, hay que configurar algunas cosas:

  • En primer lugar, necesitamos obtener un token de Telegram para nuestro bot.
  • En segundo lugar, hay que poner la dirección MAC del equipo que queremos encender.
  • En tercer lugar, hay que configurar qué usuario tiene derecho a ejecutar los comandos, en la función check_id(). Esto es necesario para evitar que cualquiera se pueda abrir una conversación con nuestro bot y «tocarnos las narices».

Por motivos obvios he eliminado los valores de mi token, dirección MAC, y los parámetros que compruebo para certificar la validez del usuario. Cada uno debe poner los suyos propios.

Una vez hecho esto queda la segunda parte: garantizar que se lance siempre que se arranque la placa. En teoría parecería que no sería necesario si siempre va a estar encendida, pero ya sabemos que, a veces, se va la luz, o hay que reiniciarla para instalar actualizaciones… por eso hay que añadir un fichero para convertirlo en un servicio systemd, de manera que se arranque siempre automáticamente. Este es el fichero que utilizo yo:

[Unit]
Description = Lanzar telegrambot
After = network.target
[Service]
Restart = always
RestartSec = 1
ExecStart = /usr/bin/encenderbot.py
[Install]
WantedBy = multi-user.target

El cual, obviamente, asume que he copiado el código anterior en /usr/bin/encenderbot.py. Este fichero se llamará encenderbot.service y se copiará en /etc/systemd/system. Una vez copiados los ficheros basta con ejecutar:

systemctl start encenderbot.service

para lanzarlo, y

systemctl enable encenderbot.service

para que se lance automáticamente en cada arranque.

Por último, queda un detalle importante por resolver, y es que GMD3, por defecto, tiene activado el ahorro de energía, lo que significa que si encendemos el ordenador pero no nos identificamos, al cabo de unos minutos (en torno a 20) el ordenador entrará en suspensión. Esto es un problema, pues nos obligaría a enviar de vez en cuando la señal de WakeOnLan, además de cortar cualquier conexión que tengamos si dura más de 20 minutos. Para resolverlo tenemos que desactivar dicha suspensión. Sin embargo, no basta con quitarla en nuestro panel de configuración, porque eso sólo influye en nuestra sesión. Tenemos que hacerlo para GDM3, que se ejecuta como un usuario diferente, y para ello utilizaremos estos comandos:

xhost +
sudo -u gdm dbus-launch gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
sudo -u gdm dbus-launch gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'
xhost -

El primer comando desactiva la seguridad en X11 y es necesario si estamos en Wayland, fundamentalmente. El segundo configura GDM3 para que no entre en suspensión cuando el equipo está conectado a la corriente, y el tercero para lo mismo pero cuando está alimentado con baterías. El cuarto reactiva la seguridad en X11. Un detalle importante es que, en Debian, el usuario no es gdm, sino Debian-gdm, por lo que si utilizamos ese sistema operativo o uno derivado, lo más probable es que haga falta que los comandos empiecen con sudo -u Debian-gdm dbus-launch…

Y con esto ya tenemos una manera de conocer en todo momento nuestra IP externa y de encender nuestro ordenador de manera remota. Ahora sólo nos queda ir a nuestro Telegram, buscar a nuestro bot con el nombre que le dimos al crearlo, e iniciar un chat con él para poder enviarle los comandos.

Pintando en el Spectrum (11)

Estaba buscando una buena fuente tipográfica para mi juego, y encontré esta página con más de trescientas… ¡Una maravilla! Y están disponibles en formato ASM, para integrar en el código.

https://damieng.com/typography/zx-origins/

Por otro lado, aunque al principio comenté que estaba utilizando Z80ASM, encontré algunos bugs, así que he cambiado al ensamblador PASMO, que funciona mejor y tiene algunas opciones extra muy interesantes, como generar un .TAP.

Pintando en el Spectrum (4)

Ha llegado el momento de empezar a pintar cosas en la pantalla. Obviamente lo que queremos pintar serán Sprites. Como explica la wikipedia, se trata de gráficos 2D que se integran con una escena de fondo. Como sabemos, el Spectrum no tiene soporte de sprites por hardware, lo que significa que nos toca a nosotros hacer todo el trabajo de pintarlos. Afortunadamente, en las entradas anteriores vimos cómo hacer una rutina que copie a la pantalla una imagen completa desde un buffer organizado de manera secuencial, y esto nos va a simplificar la tarea, como veremos.

Lo primero que necesitamos saber para pintar algo en pantalla es a partir de qué dirección de memoria tenemos que hacerlo. Si recordamos, la pantalla del Spectrum está formada por una matriz de 256 píxels de ancho por 192 de alto, y cada píxel puede tener dos colores, por lo que cada uno ocupa un bit. Esto significa que cada fila o scanline de la pantalla ocupa 32 bytes, y la parte de píxeles de la pantalla ocupa en total 6 144 bytes o 6 Kbytes exactos.

Sin embargo, justo a continuación viene una segunda zona denominada atributos, que define una matriz de 32 por 24 atributos y asigna un byte a cada uno. Este byte de atributos especifica qué colores tendrán un grupo de píxeles. En concreto, cada byte define dos colores para cada grupo de 8×8 píxeles de la pantalla: uno será el color que se mostrará cuando el bit correspondiente al píxel esté a cero, y el otro cuando esté a uno. Esta zona empieza en la dirección de memoria 22 528 y ocupa un total de 768 bytes. Este sistema permite al Spectrum mostrar hasta 16 colores simultáneos en pantalla pero consumiendo muy poca memoria.

Como ya vimos en la primera entrada, la organización de la pantalla es algo caótica. Veámoslo en un gráfico:

Vemos que los bytes situados en las direcciones de memoria en hexadecimal 0x4000, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500, 0x4600 y 0x4700 se corresponden con el bloque de 8×8 píxeles superior izquierdo de la pantalla, y que los atributos de dicho bloque están almacenados en la dirección 0x5800, que especifica que los dos colores de ese grupo de píxeles son blanco y negro. El siguiente bloque de 8×8 píxeles está en las direcciones 0x4001, 0x4101, 0x4201, 0x4301, 0x4401, 0x4501, 0x4601 y 0x4701, y sus atributos, que indican que los dos colores mostrados serán celeste y rojo, están en la dirección 0x5801. Y así sucesivamente. Esta disposición puede parecer absurda, pero si nos fijamos, vemos que la dirección de memoria de un scanline de píxeles y la de los atributos que le corresponden tienen el mismo valor en los ocho bits inferiores. Gracias a esta característica, es posible leer los dos bytes necesarios para mostrar los ocho píxeles en sólo tres ciclos de reloj, en lugar de los cuatro que se necesitarían si ambas direcciones no tuviesen siempre un byte idéntico (lo que se denomina fast-page). Y si tenemos en cuenta que cada bloque de 8 píxeles tarda cuatro ciclos de reloj en ser pintado, salta a la vista de donde sale el ciclo de contención de memoria que vimos en el primer artículo: la circuitería «compacta» la lectura de dos grupos de 8 píxeles consecutivos, de manera que en lugar de bloquear al procesador durante tres ciclos y liberarlo uno, lo bloquea durante seis ciclos y lo libera durante dos consecutivos. Además, de esta manera se garantiza también que los siete bits bajos se recorren completos aproximadamente cada milisegundo, lo que es más que de sobra para garantizar el refresco de la RAM.

Obviamente este sistema se implementó porque permite abaratar y simplificar el hardware, pero tiene el inconveniente de que, a la hora de programar para él, es bastante engorroso calcular la dirección de memoria que se corresponde con cada coordenada. Así, si dividimos la pantalla en caracteres (bloques de 8×8 píxeles) y queremos encontrar las ocho direcciones de memoria de sus ocho bytes, la operación que hay que hacer es la siguiente (gráficamente):

En azul tenemos la coordenada X, que puede valer entre 0 y 31 para las 32 columnas del Spectrum. En verde y magenta tenemos la coordenada Y en líneas: en la parte verde estaría la coordenada Y en caracteres (de 0 a 23), y en magenta sería el scanline dentro de ese carácter, todo de arriba a abajo y de izquierda a derecha. A mayores es necesario poner los tres bits superiores a 010 para que apunte al bloque de memoria concreto. Como vemos, la cosa es bastante complicada y requiere rotaciones y máscaras. Sin embargo, gracias a las rutinas que vimos en las entradas anteriores esta complicación desaparece completamente. Si las utilizamos, la conversión es tan sencilla como:

Ponemos los tres bits superiores a 100 porque, para simplificar, colocamos el buffer en la dirección 0x8000 (32768). De esta manera, si ponemos la coordenada X en el byte inferior y la coordenada Y en la superior, sólo tendremos que poner a uno el bit 7 del byte inferior y ya tendremos la dirección de memoria del scanline superior del carácter con dichas coordenadas; para pasar al siguiente carácter sólo tenemos que sumar uno; para pasar al anterior, restar uno; para pasar al que está encima, restar 32, y para pasar al que está debajo, sumar 32. Tan simple como esto. ¿Y si tenemos una dirección de píxeles, cómo calculamos la de sus atributos? Pues sólo tenemos que hacer esto:

Sencillo ¿no? Aunque para los que no se quieran complicar, aquí está un trozo de código que lo hace:

; Asumimos que HL contiene la dirección de memoria
; de un byte de pantalla
    ld a, l
    and 0x1F
    ld l, a
    ld a, h
    rrca
    rrca
    rrca
    ld h, a
    and 0xE0
    or l
    ld l, a
    ld a, h
    and 3
    or 0x98
    ld h, a
; Ahora HL contiene la dirección de memoria del atributo
; que corresponde con el byte de pantalla inicial

Y como esta entrada quedó ya algo larga, seguiré en la siguiente.

A ritmo de conga (12)

Hasta ahora he estado utilizando la web app para controlar mi aspiradora robot tanto desde el ordenador como desde el móvil. El problema es que es bastante pesado, en el móvil, tener que:

  • Abrir el navegador
  • Abrir una pestaña en blanco
  • Escribir la IP del servidor número a número

Así que decidí que tenía que hacer una app para Android. Obviamente no me iba a matar repitiendo todo el trabajo que ya había hecho en JavaScript, así que la solución obvia era hacer una aplicación que simplemente tuviese un WebView (que no es más que un widget con un navegador web completo) y que cargase automáticamente la web app de OpenDoñita automáticamente cada vez que se abriese. Así cualquier cambio que hiciese a la web app aparecería automáticamente también en la app de Android.

Sin embargo, antes que eso tenía que resolver un problema nada trivial: ¿cómo saber la IP del servidor de OpenDoñita? Sí, en mi casa se cual es, pero obviamente si quiero que otras personas puedan usarlo, no es plan de tener que poner la IP «a mano». Además ¿y si el DHCP hace de las suyas en un reinicio y cambia la IP?

La solución vino de la mano de uPnP. Se trata de un estándar para que dispositivos multimedia puedan anunciarse en una red doméstica, y que otros dispositivos puedan identificarlos y comunicarse con ellos de manera estandarizada. También sirve (y será lo que les suene a muchos) para poder abrir puertos externos en el router cuando usamos NAT.

El protocolo uPnP es, en esencia, relativamente sencillo: se utiliza la dirección multicast 239.255.255.250 y el puerto 1900 para enviar y recibir paquetes UDP. Así, si un dispositivo quiere anunciar que cumple con el estándar uPnP, emitirá una serie de paquetes NOTIFY a dicha dirección y puerto, y aquellos dispositivos interesados estarán suscritos a ella para detectar dichos mensajes. Otra manera es que un dispositivo envíe a dicha dirección y puerto un paquete M-SEARCH, y los dispositivos responderán cada uno indicando sus capacidades y demás.

Por supuesto, cuando entramos en detalles nos encontramos con que el protocolo es mucho más rico y complejo de lo que parece. Pero afortunadamente existe el módulo de python iot-upnp, que permite de manera sencilla configurar un dispositivo como servidor uPnP. Precisamente ella ha sido el motivo de que convirtiese el código a asyncio. Básicamente basta con asignar un UUID y un par de cosas más a un diccionario, y nuestro programa ya es un servidor uPnP y responde a los anuncios. Este código está añadido en la última versión del servidor OpenDoñita.

La segunda parte es conseguir que la app de Android pida a los dispositivos uPnP que se identifiquen. En este caso no me compliqué y me limité a crear un socket UDP y enviar directamente una petición uPnP de tipo M-SEARCH, que es un paquete con estos datos:

M-SEARCH * HTTP/1.1\r\n
HOST: 239.255.255.250:1900\r\n
MAN: \"ssdp:discover\"\r\n
MX: 2\r\n
ST: upnp:rootdevice\r\n\r\n

Con eso recibiré un paquete UDP por cada dispositivo raíz uPnP directamente al mismo socket desde el que envié el paquete. Y como sólo quiero conocer la IP y nada más, lo único que necesito hacer es esperar a que aparezca uno que contenga el UUID que envía mi web app, y ese será.

Por supuesto, las cosas no son tan sencillas, pues la clase de sockets, DatagramSocket, es síncrona, lo que significa que no la podemos utilizar desde el bucle principal de Android sino que necesitamos crear un thread. Para ello utilicé una clase que extiende AsyncTask. Aunque es un método obsoleto, lo es a partir de la API 30, la cual fue lanzada ayer como quien dice (pertenece a Android 11), por lo que prefiero utilizarla y garantizar que mi código va a funcionar en móviles antiguos. Ahora simplemente implemento el método doInBackground() y dentro hago un bucle en el que envío el paquete anterior, me pongo a esperar respuestas con un timeout de 2 segundos (que coincide con el valor de MX de mi petición), y cuando salte éste, si no he conseguido la IP, repito el proceso. Pero si en alguno de los paquetes venía el UUID correcto, salgo del bucle y retorno del método. Es entonces cuando se ejecutará el método onPostExecute() recibiendo como parámetro el valor que devolví en doInBackground(). Lo interesante es que mientras que ésta última se ejecutaba en otro thread, onPostExecute() se ejecuta en el mismo thread desde el que se creó el objeto, o sea, desde el bucle principal en mi caso, con lo que ahí podré llamar al método loadUrl() del WebView para que cargue la página.

La otra cuestión importante es poder capturar el botón de Atras de Android para poder ocultar la pantalla de configuración si se pulsa, pero salir de la app si se pulsa desde la pantalla principal. Para ello sobreescribo en la actividad principal el método onBackPressed(), que es el que se llama cuando el usuario pulsa el botón, y dentro de él utilizo evaluateJavascript() para llamar a la función back_android() de la web app. Esta función hará lo que tenga que hacer y devolverá el valor 0 si no se debe hacer nada, o 1 si se debe salir de la aplicación.

Y el resto no es más que el típico código para gestionar el ciclo de vida de una aplicación de Android.

El código está disponible en mi repositorio gitlab de OpenDoñita para Android.

Parte 13

Memes animados de hoy y de siempre

Estoy preparando una entrada nueva y me apeteció añadir memes en el texto, porque ayudan a leerlo al permitir descansar la vista entre párrafo y párrafo, y le dan un toque más divertido y agradable.

Por desgracia, la cosa no es tan sencilla. Una primera opción es buscar GIFs animados, pero esto tiene varios problemas:

  • El tamaño de un GIF es bastante grande
  • El número de colores es limitado

La solución obvia es utilizar vídeos en su lugar, algo que WordPress soporta perfectamente. Para ello hay que embeberlo, activar la reproducción automática, el bucle, quitar los controles y, sobre todo, quitar el sonido. Esto último es muy importante, pues si no, el navegador se negará a reproducirlo hasta que el usuario haya interaccionado con la página (lo que obliga a haber hecho click en ella, pero no sirve con hacer scroll). El motivo para esto es evitar que una página recién cargada empiece a reproducir sonido por sorpresa, pues es algo muy molesto. Si no se hace, el vídeo no se reproducirá hasta que el usuario lo ponga en marcha a mano.

Aunque es muy fácil, por desgracia tiene el problema de que obliga a acordarse de todos los pasos, con lo que es bastante sencillo que, por despiste, se nos cuele alguno y nos quede el meme mal. Por eso decidí hacer un poco de JavaScript para solucionar esto de manera sencilla.

Lo primero es bajarse algún plugin que permita añadir JavaScript a nuestra plantilla. Yo uso WordPress, por lo que escogí Simple Custom CSS and JS. Una vez instalado y activado, sólo tenemos que añadir en nuestra plantilla este trozo de código:

let memeList = [];
function setPlayStatus(element) {
// check if it is visible
var rect = element.getBoundingClientRect();
let isVisible = (
rect.bottom >= 0 &&
rect.right >= 0 &&
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.left <= (window.innerWidth || document.documentElement.clientWidth)
);
if (isVisible != element.memeData.isVisible) {
if (isVisible) {
element.memeData.video.play();
} else {
element.memeData.video.pause();
}
element.memeData.isVisible = isVisible;
}
}

function checkForMemes() {
let elements = document.getElementsByTagName(«figure»);
for (let element of elements) {
if (!element.classList.contains(‘meme’)) {
continue;
}
memeList.push(element);
element.memeData = {};
for (let child of element.childNodes) {
if (‘controls’ in child) {
element.memeData.video = child;
element.memeData.isVisible = false;
child.muted = true;
child.controls = false;
child.loop = true;
child.autoplay = true;
setPlayStatus(element);
}
}
}
}

function refreshMemesStatus() {
for (let meme of memeList) {
setPlayStatus(meme);
}
return true;
}

window.addEventListener(«load», checkForMemes);
window.addEventListener(«DOMContentLoaded», refreshMemesStatus, false);
window.addEventListener(«scroll», refreshMemesStatus, false);
window.addEventListener(«resize», refreshMemesStatus, false);

Con esto lo único que tenemos que hacer es añadir al elemento video la clase CSS meme, y automáticamente se establecerán los parámetros necesarios para que el vídeo funcione a la primera. A mayores, cuando el meme/vídeo se sale de la zona visible de la pantalla, se le da la orden de pausa para que no consuma nada de procesador. Aunque es probable que los navegadores lo tengan en cuenta, no es mala idea dejar indicado de manera explícita que nos da igual que el vídeo no siga avanzando mientras no lo vemos.

Resolución nativa en QEmu

Como ya comenté anteriormente, estoy pasando de VirtualBox a Gnome-Boxes, el cual utiliza KVMQEmu por debajo. Esto lo hice en origen porque tenía problemas con los drivers gráficos de VirtualBox en Fedora, mientras que en Boxes, Fedora funcionaba a resolución nativa (esto es, ajustando la resolución de la máquina virtual al tamaño de la ventana) sin necesidad de instalar nada. Sin embargo, algo raro ocurría con Debian Buster (la actual estable), pues no conseguía dicho efecto.

La solución, después de buscar por ahí, fue instalar el paquete spice-vdagent y reiniciar. Con él, la resolución del sistema gráfico se ajusta automáticamente al tamaño de la ventana, con lo que trabajar en dichas máquinas virtuales es tan cómodo como en las de VirtualBox.

Multipackager, Fedora y Debian

Desde hace tiempo puedo generar paquetes fácilmente para varias distribuciones gracias a un pequeño programa que me hice, multipackager, que a partir de las fuentes, me hace paquetes para Debian estable y SID, Ubuntu LTS y actual, Fedora y Arch. Para ello genera un contenedor con la distribución concreta, añade los paquetes necesarios y ejecuta el programa que genera el paquete.

Por desgracia, ya llevaba una temporada con problemas con Fedora: no tenía manera de generar una imagen para Fedora 31. Afortunadamente, echando un vistazo a cómo lo hacía LXC pude encontrar una manera, algo chapucera, para qué negarlo.

Por desgracia, hace unos días terminó de romper del todo: hasta ahora, para generar las imágenes de contenedores utilizaba yum, la herramienta de paquetería de Fedora/Red Hat, que, afortunadamente, estaba disponible en Debian SID. Pero por desgracia, hace unos días fue eliminada de los repositorios. Algunos dirán que esa herramienta está obsoleta, y que hoy en día se utiliza dnf, y es verdad, pero por desgracia esa nunca estuvo disponible en Debian, y no fui capaz de instalarla a partir de las fuentes.

Ante esto, necesitaba una solución. Decidí echar un vistazo de nuevo a ver qué hacía LXC, y me encontré con que partía de la imagen Live ISO de Fedora, montándola, extrayendo el fichero con la imagen squashfs, extrayendo de ella el sistema de archivos, y configurando un sistema nuevo con ellos. Lo veía bastante lioso, pero no veía otra opción, así que empecé a ver cómo automatizar la descarga de la ISO… y de pronto encontré un interesante directorio en el servidor web llamado Containers… y efectivamente, contenía imágenes del sistema de archivos básicas, listas para ser utilizadas. Lo único que tenía que hacer era procesar la página web que listaba el contenido de la carpeta para obtener el nombre del fichero correcto, y a correr.

Y gracias a eso, multipackager ya vuelve a funcionar a pleno rendimiento, y he podido sacar un nuevo paquete de Cronopete que funciona en Fedora 32.

Marcadores en una nueva pestaña, y portando todo a una nueva sesión de Firefox

Hace tiempo empecé a utilizar una extensión de Firefox llamada newtab-bookmarks, pues me permitía disponer de los marcadores directamente de fondo de cualquier nueva pestaña. Sin embargo, recientemente me encontré con que mi sesión de Firefox se ha… degradado. Básicamente me ocurrían «cosas raras», cosas que en otro equipo con el mismo sistema operativo (Debian SID) y configuración no ocurrían. Probé a crear una nueva sesión desde cero y los problemas desaparecieron, por lo que decidí migrarlo todo desde la vieja a la nueva. A fin de cuentas, esta configuración fue pasando de versión en versión desde hace más de una década, por lo que tiene sentido que pueda haber algún problemilla. Y aquí empezaron mis problemas: para empezar, al ir a descargar esa extensión me encontré con que ya no está soportada, aunque afortunadamente redirige a Perfect Home, otra extensión que hace más o menos lo mismo… excepto utilizar favicons en los enlaces.

Una vez resuelto este problema, exportar los marcadores fue sencillo. Y entonces llegó el verdadero quebradero de cabeza: ¿como exporto las contraseñas guardadas? Suponía que sería algo tan sencillo como utilizar la opción de exportar, pero, sorprendentemente, dicha opción no existe. Y no es que lo diga yo, es que en Mozilla lo reconocen abiertamente.

Vemos que la opción que dan es utilizar un exportador de terceros, pero es algo que no me convence nada; a fin de cuentas, estamos hablando de dar acceso a mis contraseñas… y siendo más de cien, no me apetecía ponerme a copiarlas a mano. Afortunadamente, tras rebuscar un poco más encontré que basta con copiar los ficheros key4.db y logins.json al nuevo perfil, y dispondremos de todas nuestras contraseñas guardadas.

Compilando Gnome Shell

Estos últimos meses he estado preparando un parche para Mutter, y he necesitado instalar la rama de desarrollo de Gnome Shell. Y aunque hay bastante documentación, hay detalles que están algo incompletos, así que voy a poner aquí lo que he ido aprendiendo.

Para empezar, toda la compilación se hace mediante jhbuild, lo que simplifica muchísimo el trabajo. Y aunque es mucho más sencillo que bajarse a mano todo, aún tiene algunos detalles peliagudos.

Lo primero, si queréis tener éxito en compilar una versión de desarrollo, es mejor que utilicéis Fedora, en concreto la versión de desarrollo (o beta) también. De esta manera no tendréis problemas de versiones con las dependencias. Para ello, nada mejor que utilizar una máquina virtual. Aunque VirtualBox es una buena opción, yo tuve serios problemas con los controladores de pantalla (y es raro… con Debian o Ubuntu siempre fueron perfectamente), por lo que al final me decanté por Gnome Boxes, una herramienta más sencilla, pero con la que no tuve ningún problema.

Una vez instalada, bajamos jhbuild y lo instalamos con:

git clone https://gitlab.gnome.org/GNOME/jhbuild.git
cd jhbuild
./autogen.sh
make
make install

Una vez hecho esto debemos añadir (si no lo tenemos ya) a nuestro PATH el directorio ~/.local/bin. Y ya tendremos jhbuild instalado.

El siguiente paso es pedir que nos construya Gnome Shell. Para ello escribimos:

jhbuild build gnome-shell

Sin embargo, lo más probable es que no funcione y nos diga que nos faltan un montón de dependencias. Para instalarlas, basta con ejecutar:

jhbuild sysdeps – -install gnome-shell

y tomarse un café (o dos), porque tarda un buen rato en bajar e instalar las dependencias. Cuando termine volvemos a ejecutar el comando previo, y probablemente nos dirá que nos falta aún libavcodec y compañía. Por suerte es posible compilarlo aún así, mediante:

jhbuild build – -nodeps gnome-shell

Ahí empezará a compilar por fin, y tardará, tardará mucho (y requerirá mucho disco duro, por cierto. Aseguraos de que vuestra máquina virtual tiene suficiente). Pero lo importante que debemos saber es que el código lo baja en ~/jhbuild/checkout, por lo que si queremos aplicar parches, es ahí donde hay que tocar el código.

Cuando termina de compilar, instala todo en ~/jhbuild/install, y ya podemos probarlo. Yo lo que suelo hacer es cerrar la sesión gráfica, irme a una consola de texto (con Alt+F2), y ahí ejecutar primero:

jhbuild shell

para tener una shell dentro del entorno, y luego ejecutar:

dbus-run-session – – ~/jhbuild/install/bin/gnome-shell – -wayland – -display-server

y se arranca la sesión. Un problema que yo no he conseguido resolver es que la opción de Cerrar sesión no funciona. Yo lo resuelvo abriendo una terminal y matando el proceso.