Cascos inalámbricos

julio 2nd, 2014

Hace tiempo me compré unos cascos inalámbricos, en concreto el modelo MDR-RF810R de Sony. Mi intención era utilizarlos con el ordenador, para olvidarme de los molestos cables.

Los cascos y el transmisor

Por desgracia, sólo puedo utilizarlos con la televisión porque tienen un serio defecto: a los cuatro minutos de no recibir señal acústica la base transmisora se apaga (supuestamente para ahorrar energía… lo que es ridículo porque no usa pilas sino un transformador; son los cascos en sí los que utilizan baterías, y ellos nunca se desconectan). En ese momento los cascos pegan un petardazo muy fuerte. Con la tele no es un problema porque cuando termino de verla, la apago y me quito los cascos, pero con el ordenador puedo ver un vídeo y, al terminar, seguir leyendo o trabajando con los cascos puestos, por lo que cuatro minutos después, estando todo en pleno silencio, los cascos me sueltan el petardazo sin previo aviso. Afortunadamente no es muy difícil resolver este problema, así que en esta entrada voy a explicar como lo hice.

Una mirada al interior

Al desmontar el transmisor nos encontramos con este circuito.

Un circuito bastante sencillo

A primera vista parece muy sencillo: un par de transistores que, probablemente, amplifiquen un poco la entrada, algunos condensadores de filtrado, y una caja metálica que, seguramente, contiene la parte de alta frecuencia. Pero en realidad tiene truco, porque si le damos la vuelta nos encontramos con el verdadero circuito:

Parece que hablamos demasiado pronto…

El primer gran problema que nos encontramos es que los números de los chips están borrados. Afortunadamente hay uno que sí se puede leer más o menos, y que resulta ser un chip con cuatro amplificadores operacionales. Analizando el circuito que lo acompaña se nota cierta estructura simétrica, lo que nos hace sospechar que puede tener que ver con algún tipo de proceso de señal estéreo.

Tras analizar con cuidado todo, llegamos a la conclusión de que la señal de sonido se divide en dos partes: por un lado se envía directamente al chip de la derecha, que resulta ser un modulador de FM, y por otro se envía a ese circuito que vimos antes, donde se detecta si hay o no señal. La salida del modulador de FM es una señal en banda base modulada en estéreo, la cual se pasa al transmisor localizado dentro de la caja metálica en el otro lado de la placa, que sube la frecuencia hasta la banda deseada y la envía a la antena. Este transmisor recibe su alimentación a través de un regulador de tensión diferente al del resto de la circuitería, el cual, además, está controlado desde el modulador de FM para poder encenderlo y apagarlo a voluntad.

Al principio se me ocurrió que si forzaba la alimentación del transmisor, de manera que siempre estuviese encendido, resolvería el problema. Por desgracia no funcionó. Sospecho que es porque se trata de un simple elevador de frecuencia, por lo que si no hay una señal desde el modulador de FM, la salida sigue siendo nula. Ante esto llevé mi investigación al resto del circuito.

Bloques principales del transmisor FM

El detector/amplificador de pulsos detecta cuando hay señal de audio en la entrada del transmisor, momento en que activa el transistor de conmutación. De esta manera éste conecta su salida a masa cuando hay una señal de audio, o la deja flotante cuando no la hay, en cuyo caso una resistencia de pull-up fija una tensión de entre 4,8 y 5,6 voltios. Esta salida está conectada directamente al modulador de FM (línea naranja), y es la que reinicia el contador de cuatro minutos de apagado cada vez que se detecta señal.

La deducción lógica de todo esto es que, para que el transmisor no se desactive nunca, basta con cortocircuitar dicho transistor, de manera que su salida esté siempre a cero voltios, haciendo creer así al modulador FM que hay una señal de audio en todo momento. Para ello basta con cortocircuitar los dos pines marcados en la imagen de abajo con la línea rosa (el círculo rodea al transistor de conmutación):

El circulo naranja rodea al transistor de conmutación; el trazo rosa indica los dos pines que hay que cortocircuitar

Y con esto resolvemos el problema y ya podemos usar los cascos sin miedo a sobresaltos.

Mejorando Debian sobre Android

junio 17th, 2014

En la entrada anterior expliqué como hice para arrancar una Debian en mi dispositivo AndroidTV. Hoy estuve retocando el código para hacerlo más flexible y corregir algunos problemas de tipo práctico que he encontrado al empezar a usarlo de manera regular.

Cambios en launch_debian

El primer cambio ha sido en el propio launch_debian. Este binario es el encargado de detectar cuando se enchufa un disco externo y de lanzar la aplicación runlinux.sh que esté en su directorio raíz. El primer problema que resolví en ella es permitir pasarle los directorios en donde montar la unidad externa, en donde buscar los dispositivos conectados, y el tipo de sistema de archivos que se quiere montar. Todo esto se puede especificar ahora desde la línea de comandos si se desea (si no se indica nada, se utilizan los valores por defecto que comenté en la anterior entrada).

El segundo cambio es añadir una FIFO para poder ejecutar comandos desde el entorno externo al chroot. El principal motivo es para poder ejecutar un apagado ordenado, pues desde dentro del entorno no se puede ejecutar, por ejemplo, un shutdown -h now. Para ello se crea la FIFO /dev/chroot_ext_control, que esperará a recibir algún comando. De momento admite dos:

  • halt mata todos los procesos que estén accediendo a algún fichero en la unidad, y una vez hecho esto, la vuelve a montar como sólo-lectura, para que no se corrompan los datos al apagar.
  • reboot lo mismo que halt, pero luego reinicia la máquina

Estos comandos se pueden emitir simplemente con echo reboot > /dev/chroot_ext_control o echo halt > /dev/chroot_ext_control.

El nuevo código se puede descargar con este enlace.

Los parámetros que acepta la nueva versión desde la línea de comandos son:

  • -o opciones de montaje. La cadena que sigue se añade tal cual al comando mount, junto con -o. Esto permite añadir opciones como noatime.
  • -m ruta de montaje. La cadena que sigue indica la ruta donde se montará la unidad externa. Por defecto es /system/debian.
  • -t tipo de sistema de ficheros. La cadena que sigue indica el tipo de sistema de ficheros que se pasará a mount. Por defecto es ext4.
  • -c ruta de dispositivos. La cadena que sigue indica la ruta donde aparecen los ficheros de dispositivo, como sda1. Por defecto es /dev/block.
  • -f ruta y fichero para la FIFO. La cadena que sigue indica la ruta y el fichero para la FIFO de control. Por defecto es /dev/chroot_ext_control.

El motivo de poner la FIFO de control en /dev es que, al ser un sistema de ficheros en RAM, no habrá interferencias con otros posibles ficheros, y además es accesible con el mismo nombre desde dentro y fuera del entorno chroot.

Cambios en el lanzador principal

El script situado en /system/etc/install-recovery.sh también ha cambiado ligeramente. Ahora es así:

#!/system/bin/sh

cp /system/bin/launch_debian /dev
/dev/launch_debian &

El motivo de hacer esto es que ahora launch_debian ya no muere al lanzar el entorno Debian, sino que sigue en marcha para responder a las peticiones de comandos emitidas a través de la FIFO. Eso significa que si queremos actualizarlo no lo podemos hacer directamente, porque el fichero estará bloqueado. Por eso primero copiamos el ejecutable a /dev (que tiene la ventaja de ser un disco RAM, y dado que el ejecutable es muy pequeño, no supone un desperdicio de memoria) y lo ejecutamos desde ahí: de esa forma el ejecutable original nunca se bloquea, y podemos reemplazarlo en caliente y luego reiniciar para que se utilice la nueva versión.

Cambios en el lanzador de la jaula chroot

El script situado en el disco duro, runlinux.sh, también ha cambiado un poco. Ahora es así:

#!/system/bin/sh

# stop the Android system
stop
sleep 1
# stop the daemons to ensure that
# they don't disturb the debian system
# (can't kill them because INIT would
#  relaunch them)
# Also allows to send them to SWAP
busybox killall -SIGSTOP netd
busybox killall -SIGSTOP vold
busybox killall -SIGSTOP displayd
busybox killall -SIGSTOP ueventd
busybox killall -SIGSTOP debuggerd
busybox killall -SIGSTOP rild
busybox killall -SIGSTOP drmserver
busybox killall -SIGSTOP mediaserver
busybox killall -SIGSTOP installd
busybox killall -SIGSTOP servicemanager
# undo changes to kernel variables
echo 0 > /proc/sys/kernel/panic_on_oops
echo 18000000 > /proc/sys/kernel/sched_latency_ns
echo 3000000 > /proc/sys/kernel/sched_wakeup_granularity_ns
echo 0 > /proc/sys/vm/overcommit_memory
echo 3000 > /proc/sys/vm/dirty_expire_centisecs
echo 1024 > /dev/cpuctl/apps/bg_non_interactive/cpu.shares
echo 900000 > /dev/cpuctl/apps/cpu.rt_runtime_us
echo 900000 > /dev/cpuctl/apps/bg_non_interactive/cpu.rt_runtime_us
# mount proc, sys, dev, dev/pts, dev/cpuctl and /system
mount -o bind /proc $1/proc
mount -o bind /sys $1/sys
mount -o bind /dev $1/dev
mount -o bind /dev/pts $1/dev/pts
mount -o bind /dev/cpuctl $1/dev/cpuctl
mount -o bind /system $1/android
export HOME=/root
export LD_LIBRARY_PATH=
export PATH=/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# set the Framebuffer devices where standard apps expect them
cp -a /dev/graphics/* /dev
# launch our Debian system
/system/bin/busybox chroot $1 /bin/system.sh

El primer cambio está en los distintos comandos echo XXXXXX >…. Estos comandos pretenden restaurar los valores originales en diversas variables del núcleo. Esto es porque Android está orientado hacia aplicaciones de usuario, por lo que, por ejemplo, le quita prioridad a las aplicaciones en segundo plano. Con estas opciones intento deshacer lo que se cambia en el fichero init.rc. Sin embargo es importante indicar que probablemente lo que cambie dependa de cada fabricante, así que cada uno debe mirar qué es lo que se modifica.

El siguiente cambio es que monto también /dev/cpuctl, algo que se me había pasado.

Por último, monto el directorio /system dentro del directorio android de la unidad montada. De esta manera se tiene acceso al sistema Android desde dentro de la jaula chroot, lo que permite, por ejemplo, actualizar el fichero launch_debian.

Reemplazando Android

junio 16th, 2014

(La segunda parte de este artículo está en http://blog.rastersoft.com/?p=1386)

Hace unas semanas me compré un cacharro de tipo AndroidTV (este en concreto). Sin embargo ya tenía claro que no quería tener Android, sino un linux como $DEITY manda. Por eso busqué uno con el mismo chip que mi tablet. Por desgracia esta vez no conseguí que arrancase desde una tarjeta externa (parece que han cambiado algo en el cargador). Pero como a cabezota no me gana nadie, decidí buscar alguna alternativa.

Por supuesto no quería eliminar el Android que ya trae, más que nada por si me lo cargo y acabo con un bonito pisapapeles. Por eso decidí intentar una ruta intermedia: una vez arrancado el sistema, lanzaría un programa que mataría todos los procesos de Android y lanzaría una Debian almacenada en un disco externo, desde un entorno chroot, con su entorno gráfico y todo. De esta manera, si no enchufo el disco el sistema arrancaría Android normalmente, y si lo enchufo, arrancaría Linux.

Obviamente la cosa no es tan sencilla. Para empezar, los diversos procesos de Android se vuelven a lanzar automáticamente si mueren, por lo que no podemos utilizar un simple kill pid. Afortunadamente existen los comandos start y stop que lanzan y detienen todo el entorno gráfico. Para probarlos utilicé adb, disponible en el entorno de desarrollo de Android. Para ello primero lancé el servidor con

    sudo ./adb start server

Y luego cada vez que necesitaba una consola en el dispositivo, lo conectaba con el cable USB y lanzaba:

./adb shell

Y efectivamente, si ejecutaba stop el entorno de Android desaparecía, quedando la pantalla congelada. Ejecutando start volvía a arrancar todo. Afortunadamente, aunque mi dispositivo no está completamente rooteado, si es lo suficientemente libre como para que, por defecto, entre como root desde esta shell, además de incluir un busybox con chroot. Con esto ya tengo resuelto el primer gran problema. Es cierto que todavía quedan varios servicios en marcha, como el gestor de red, pero es un problema secundario que ya resolveremos luego.

OJO: el servicio adb en el dispositivo Android tarda un poco en ser lanzado. Si acabáis de arrancarlo, sed pacientes. Por otro lado, a veces la lista de dispositivos USB con Android no está actualizada en Linux, así que es posible que haya que tocar un poco en udev para que reconozca el nuestro.

Autoarranque

Si sólo quisiese lanzar manualmente el nuevo entorno ya tendría el problema resuelto; pero yo quiero un dispositivo que arranque Debian incluso si no estoy (por ejemplo si se va la luz y luego vuelve), por lo que no queda más remedio que meter las narices en el proceso de arranque de Android.

Alguno puede pensar que ir a tan bajo nivel es exagerado, pero hay un motivo: la primera idea que se me ocurrió fue buscar algún programa nativo de Android que en el arranque lance automáticamente un script que yo le pase. Encontré varios, pero fue entonces cuando descubrí que mi cacharro no está rooteado del todo, por lo que ese script no podía lanzar un chroot ni nada por el estilo. Así pues, no queda más remedio que meter las narices más abajo.

Como en cualquier sistema Linux, una vez arrancado el núcleo se ejecuta el proceso init. Pero aquí acaban los paralelismos, porque en Android este proceso es muy diferente del SysV Init: aquí hay un conjunto de ficheros .rc con una serie de indicaciones de qué procesos hay que lanzar, en qué orden, con qué permisos, de qué dependen, y, sobre todo, si es necesario volver a lanzarlos si mueren. Estos ficheros .rc se encuentran en el raíz del sistema de ficheros, y ese es primer problema serio con el que nos encontramos: en general, el sistema raíz es de tipo rootfs. Este sistema de ficheros es un tipo de ramfs, que se inicializa durante el arranque con unos valores predeterminados, y además está montado como sólo lectura. Aunque en principio podemos hacer mount -o remount,rw / para permitir escritura, cualquier cambio que hagamos desaparecerá al reiniciar. La única manera de modificar el sistema de archivos es recompilarlo y grabar la partición entera, y eso es algo que no quiero hacer por el riesgo de cascar el sistema. Sin embargo, en /system tenemos una partición que sí es modificable, porque es de tipo ext4, así que intentaremos meter nuestro autoarranque ahí. En caso de que, por defecto, dicha partición esté montada como sólo lectura, podemos volverla de lectura/escritura con:

mount -o remount,rw /system

y devolverla a sólo lectura con

mount -o remount,ro /system

El formato de los ficheros .rc es relativamente sencillo: si una línea comienza por on XXXXX, las líneas siguientes definen los comandos a realizar cuando ocurra el evento XXXXX. Pero si empieza por service XXXXX, entonces las siguientes líneas definen a dicho servicio XXXXX, que se debe lanzar al inicio. Un ejemplo de servicio sería este:

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group log

Estas líneas definen el servicio console, que se lanza ejecutando el binario /system/bin/sh. Además, debe lanzarse como usuario shell y grupo log. Si estos dos parámetros no se incluyen se lanza como root.

Una línea especial, que nos interesa especialmente, es oneshot. Cuando un servicio la incluye significa que debe lanzarse una única vez durante el arranque, y si se muere no debe lanzarse de nuevo. En cambio, aquellos servicios que no incluyen oneshot se vuelven a lanzar si mueren. El motivo de explicar esto es que lo que vamos a hacer es buscar un servicio de tipo oneshot, y reemplazar su binario por un script que primero llame al binario original, y luego haga lo que a nosotros nos interese. En mi caso tuve más suerte, y encontré este servicio:

service flash_recovery /system/etc/install-recovery.sh
    class main
    oneshot

Por el nombre, tiene pinta de ser lo que se lanza cuando se borran las preferencias y se quiere dejar el sistema como recién comprado. Si vamos a /system/etc, vemos que no existe el fichero install-recovery.sh, supongo que porque sólo se creará cuando se quieran borrar los datos; pero eso significa que nosotros podemos meter en su lugar un ejecutable o un script y éste se ejecutará como root cada vez que arranque el sistema. Y además, al estar en /system podemos editarlo a nuestro antojo ¡Justo lo que estábamos buscando!

Lanzamiento condicional

Ahora que ya tenemos donde meter nuestro código, la primera idea que se nos viene a la cabeza es montar un sistema Debian en, por ejemplo, /system/debian, escribir un script que monte una copia de proc, sys y dev, y meterlo directamente en /system/etc/install-recovery.sh. Esto tiene varios problemas:

  • Las unidades en formato Ext2/3/4 no se montan automáticamente desde Android, por lo que tendríamos que hacerlo a mano.
  • Además, si hay varias unidades USB, tendríamos que ver cual es la correcta de todas ellas
  • Según como lo hagamos podemos perder por completo el acceso a Android.
  • Tendremos el sistema hardcoded, grabado en piedra, y para cambiar cualquier cosa tendremos que volver a entrar en la flash de nuestro dispositivo.

La opción por la que me he decantado es la de disponer de un disco duro externo con el sistema Debian, e incluir en él un pequeño fichero ejecutable con un nombre específico. De esa manera el único cambio que hay que hacer en nuestro sistema Android es añadir un pequeño programa que se lance durante el inicio y que compruebe todas las unidades USB en busca de dicho ejecutable, y si lo encuentra, que lo lance. De esa manera podemos meter ahí toda la magia y reducir al mínimo los cambios a realizar en nuestro dispositivo Android.

Lo primero que hice fue crear un pequeño programa que comprobase constantemente si aparecía un nuevo dispositivo USB; en caso de que así fuese, intentaría montarlo como una partición EXT4 en un punto concreto y ejecutar un fichero runlinux.sh en él. Al principio lo hice todo con comandos shell, pero el resultado era bastante chapucero porque lo que hacía era comprobar de manera periódica (cada cinco segundos) si había dispositivos. Además, cada comprobación implicaba ejecutar varios grep, sed y otros comandos, lo que aumentaba el consumo de CPU. Aunque una vez lanzado el sistema Debian esta comprobación se detenía, sí seguía en marcha mientras se estuviese trabajando en Android. Por eso al final lo reescribí en C y utilizando inotify, de manera que no hace falta realizar una espera activa, sino que el programa sólo se despierta cuando se inserta un nuevo dispositivo. El código de este programa se puede bajar desde aquí. Para compilarlo basta con utilizar el comando

arm-linux-androideabi-gcc launch_debian.c -o launch_debian

El compilador arm-linux-androideabi está disponible en Debian y derivadas en el paquete gcc-arm-linux-androideabi. A continuación hay que copiar el binario a /system/bin en nuestro dispositivo Android, y añadir el siguiente script en /system/etc/install-recovery.sh:

#!/system/bin/sh

/system/bin/launch_debian &

Este script lo que hace es lanzar launch_debian en segundo plano, y éste se quedará esperando a que se conecte un nuevo dispositivo USB con alguna partición EXT4. En caso de que eso ocurra, montará dicha partición (con las opciones noatime y nodiratime; para discos duros normales no es necesario, pero es interesante para unidades flash) e intentará ejecutar el fichero runlinux.sh, pasándole como primer parámetro la ruta donde está montada la partición (por defecto /system/debian), y como segundo parámetro el dispositivo que se ha montado (por ejemplo, sdb2). Una vez que termine de ejecutarse correctamente dicho fichero, launch_debian morirá. Pero si falla la ejecución o no existe dicho fichero, el programa seguirá probando con el resto de dispositivos.

El motivo de poner un script intermedio en lugar de meter el ejecutable directamente en /system/etc/install-recovery.sh es porque así, si por cualquier motivo el sistema Android sobreescribe dicho fichero, puedo recuperarlo fácilmente desde una consola, en lugar de necesitar transferir un binario.

Generando el sistema de ficheros

Ahora que ya tenemos todo listo en el sistema Android queda generar el entorno Debian, que será el que ejecutemos, así como un script que lo lance mediante una llamada a chroot.

Vamos a empezar con el entorno Debian. Para ello formatearemos un disco en EXT3 o en EXT4 y, desde un sistema Debian o derivado (Ubuntu, por ejemplo) bajamos el paquete debootstrap. A continuación montamos manualmente el disco donde queremos generar el sistema (porque, por defecto, los escritorios lo montan con las opciones nodev y noexec, y debootstrap se niega a trabajar en esas condiciones), y desde una línea de comandos teclearemos (asumiendo que el disco donde queremos instalar el sistema base está en /mnt):

sudo debootstrap --arch=armhf --foreign --variant=minbase wheezy /mnt http://ftp.us.debian.org/debian

En este caso estamos instalando Debian wheezy, que es la rama testing a la hora de escribir este artículo. En el futuro puede ser necesario cambiar a otra rama, según se desee.

Una vez ha terminado de instalarse, editamos el fichero etc/resolv.conf para añadir un DNS por defecto. En mi caso he utilizado los de google, pero se puede utilizar cualquiera. Mi fichero quedó así:

# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
#nameserver 127.0.1.1
nameserver 8.8.8.8
nameserver 8.8.4.4

Ahora desmontamos el disco y lo conectamos a nuestro sistema Android. Luego ejecutamos ./adb shell para entrar en él, y procederemos a montar la unidad donde instalamos el sistema en, por ejemplo, /mnt/usb_storage. Una vez hecho esto, y tras asegurarnos de que tenemos conexión a internet desde nuestro dispositivo Android, ejecutamos los siguientes comandos (también en nuestro dispositivo Android mediante adb shell) para entrar en el entorno chroot y terminar la instalación del sistema base:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH
export HOME=/root
export LD_LIBRARY_PATH=
mount -o bind /proc /mnt/usb_storage/proc
mount -o bind /sys /mnt/usb_storage/sys
mount -o bind /dev /mnt/usb_storage/dev
mount -o bind /dev/pts /mnt/usb_storage/dev/pts
busybox chroot /mnt/usb_storage /bin/bash -l
debootstrap/debootstrap --second-stage

Con esto terminamos la instalación del sistema base, pero ahora toca configurarlo. En primer lugar vamos a configurar APT para poder bajar paquetes y demás. Para ello salimos del entorno chroot, desmontamos el disco, lo montamos en nuestro ordenador y editamos el fichero /etc/apt/sources.list para añadir las siguientes líneas:

deb http://ftp.de.debian.org/debian wheezy main contrib non-free
deb http://ftp.de.debian.org/debian wheezy-updates main contrib non-free
deb http://security.debian.org/ wheezy/updates main contrib non-free

Ahora podemos volver a montar el disco en nuestro sistema Android y volver a lanzar el entorno chroot. Vamos a instalar ahora las herramientas básicas que nos faltan para terminar de configurar el sistema con autoarranque y poder dedicarnos a jugar con él. Para ello vamos a instalar el servicio de gestión de redes, para tener acceso a internet completo, el servidor SSH para poder gestionar todo de manera remota, ifconfig, ping, el cliente DHCP y un editor de textos (normalmente uso nano pero da problemas desde adb; por eso uso vim de manera temporal) para editar los últimos ficheros necesarios para que todo funcione automáticamente. Para ello, una vez que estamos de nuevo dentro de la jaula chroot (¡¡¡no olvidarse de los exports!!!), ejecutamos los siguientes comandos:

apt-get update
apt-get dist-upgrade -y
apt-get install ifupdown openssh-server net-tools vim iputils-ping isc-dhcp-client

El siguiente paso es crear el fichero runlinux.sh, que será ejecutado desde nuestro entorno Android por launch_debian. Este script preparará el entorno para lanzar una sesión chroot, y debe contener las siguientes líneas:

#!/system/bin/sh

# stop the Android system
stop
sleep 1
# stop the daemons to ensure that
# they don't disturb the debian system
# (can't kill them because INIT would
#  relaunch them)
# Also allows to send them to SWAP
busybox killall -SIGSTOP netd
busybox killall -SIGSTOP vold
busybox killall -SIGSTOP displayd
busybox killall -SIGSTOP ueventd
busybox killall -SIGSTOP debuggerd
busybox killall -SIGSTOP rild
busybox killall -SIGSTOP drmserver
busybox killall -SIGSTOP mediaserver
busybox killall -SIGSTOP installd
busybox killall -SIGSTOP servicemanager
# mount proc, sys, dev and dev/pts
mount -o bind /proc $1/proc
mount -o bind /sys $1/sys
mount -o bind /dev $1/dev
mount -o bind /dev/pts $1/dev/pts
export HOME=/
export LD_LIBRARY_PATH=
export PATH=/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# set the Framebuffer devices where standard apps expect them
cp -a /dev/graphics/* /dev
# launch our Debian system
/system/bin/busybox chroot $1 /bin/system.sh

Lo primero que hace este script es detener el entorno Android, de manera que liberamos la memoria consumida por éste. A continuación envía una señal SIGSTOP a diversos procesos de Android que siguen en marcha, como el gestor de red. Esto es necesario porque, si no, interferiría con las herramientas de Debian. Por otro lado, como ya expliqué antes, no podemos matarlos porque el sistema init los lanzaría de nuevo.

El siguiente paso consiste en montar proc, sys, dev y dev/pts en el sistema Debian. Vemos que utiliza el primer parámetro, pues launch_debian pasa ahí la ruta donde se montó la partición.

A continuación inicializamos las variables de entorno. Vemos que borramos LD_LIBRARY_PATH. Esto es así porque algunos Android la utilizan para añadir otras rutas con bibliotecas, pero en nuestro caso nos interferiría.

Luego se copian todos los ficheros de dispositivo situados en /dev/graphics a /dev. Esto es así porque en Android los dispositivos framebuffer están en esa ruta alternativa, por lo que tenemos que copiarlos a donde las aplicaciones de Linux esperan encontrarlos.

Por último ejecutamos nuestro chroot. Cabe indicar que es necesario poner la ruta completa de busybox porque hemos sobreescrito la variable de entorno PATH. Vemos que lanzamos el script /bin/system.sh. Este script será quien lance todo lo que queramos lanzar en nuestro sistema Debian (servidor X, demonios…). En el caso actual lo que hice fue poner en él las siguientes líneas:

#!/bin/bash

service networking restart
service ssh restart

Con esto inicializo la red (necesario si el disco estaba enchufado al encender el dispositivo Android, porque launch_debian lo detectará tan rápido que no dará tiempo a que arranque Android y configure la red), y lanzo el servidor ssh, que me permitirá entrar de manera remota. Para que estos dos comandos funcionen, sin embargo, es imprescindible configurar algunas cosas primero:

  • Para que ssh funcione, lo primero que es necesario es ponerle clave al usuario root. Para ello utilizamos el comando passwd. Una vez hecho esto editamos /etc/ssh/sshd_config para ajustar la configuración.

Con todo esto ya tenemos el sistema base configurado y listo. Salimos de nuestra jaula chroot, ejecutamos sync por si acaso, y reiniciamos nuestro dispositivo Android. Si todo va bien, el entorno Android no debería ni siquiera arrancar, y deberíamos poder entrar mediante ssh.

El siguiente paso será añadir BitTorrent, servidor FTP y algún cliente multimedia para utilizarlo como equipo de salón, pero eso lo dejo para futuras entradas.

Novedades varias

diciembre 6th, 2013

Esta semana ha sido bastante movida. En primer lugar hice una fuerte refactorización de Autovala. Con la nueva estructura el código es mucho más elegante y, sobre todo, mucho más mantenible. De hecho, gracias a los cambios he podido añadir en poco tiempo nuevas características, como soportar espacios de nombres anidados (necesario para utilizar la biblioteca de Plank), autodetectar en algunos casos GIO, mejorar el parseado de los comandos propios (ahora permite una gran libertad a la hora de meter espacios), y algunas cosas más.

Otra novedad ha sido la migración a Autovala de mi fork de Submarine, además de corregir un pequeño bug en el buscador de SubDB cuando el fichero mide menos de 128KBytes.

Por último hice un retoque en el driver GSLx680 para pantallas táctiles. Ahora permite hacer scroll con un único dedo, lo que resulta más cómodo. Para poder hacer Drag&Drop basta con mantener el dedo quieto durante un segundo, y luego ya se puede mover.

Multilenguas y mas

noviembre 10th, 2013

Acabo de subir nuevas versiones de TabletPlayer, TabletLauncher y TabletWM. Se trata de pequeños retoques para que funcionen bien con otras lenguas que no sean inglés, y para mejorar la usabilidad.

En TabletPlayer, un reproductor de películas basado en MPlayer, lo que hice fue cambiar el tamaño de los botones de la interfaz de reproducción, poniéndolos de 64×64 pixels para que sean más cómodos de pulsar en una pantalla táctil. También cambié la interfaz de selección de fichero por una más amigable en pantallas táctiles, pues el filechooserdialog tiene los elementos muy pequeños y además exige hacer doble click, que en una pantalla táctil no es nada cómodo. Ahora se parece mucho más a una aplicación de tablet.

En el caso de TabletWM tuve que hacer un ajuste para que no fallase el gráfico de la batería si la lengua utiliza ‘,’ en lugar de ‘.’ como separador de decimales. Además, añadí unas pequeñas notas que explican como cambiar la lengua y la zona horaria, de manera que el formato de fecha y la hora se muestren correctamente.

Respecto a TabletLauncher, básicamente traduje las cadenas al castellano.

Como de costumbre, los cambios están disponibles en mi página web.

Compilacion condicional

octubre 27th, 2013

Acabo de subir una nueva versión de Autovala y otra de Cronopete.

En el caso de Autovala he añadido una opción importante, que es la compilación condicional: ahora es posible pasar defines al código de Vala, mediante -Ddefine=ON, para compile o no distintas partes del código. Además, también es posible hacer condicionales otras partes del código de Autovala, de manera que se pueda escoger que un programa se compile con soporte de GTK2 o GTK3, con o sin una biblioteca, etc.

Este soporte es bastante flexible, pero aún no puede hacer condicional la compilación de un programa completo. Eso lo dejo para otra versión.

En cuanto a Cronopete, básicamente he hecho algunas correcciones, tales como evitar un par de cuelgues al formatear un disco, o que monte automáticamente la unidad de backup en el arranque. Además, ahora el sistema de compilación utiliza Autovala, gracias precisamente al soporte de compilación condicional.

Como de costumbre, se puede bajar de mi página web.

XCB y Cairo

septiembre 28th, 2013

Actualizado: Por fin he terminado la primera versión usable de TabletWM y TabletLauncher. Con ellos y el driver para la pantalla táctil he conseguido el gran objetivo de poder utilizar aplicaciones GNU/Linux nativas en una tablet sin teclado ni ratón.

Crear TabletWM fue, sin duda, la parte más compleja, porque supuso trabajar con un API completamente nuevo, como es XCB. Tenía claro que no quería utilizar XLib, así que me lié la manta a la cabeza, pero partiendo de un gestor de ventanas muy, pero que muy básico hecho por Cinolt a finales de 2011.

Lo primero que aprendí es que el protocolo X es lento, por lo que en un dispositivo como una tablet, con capacidades limitadas, no es recomendable pedir al servidor X las propiedades de una ventana cada vez que se quiere mostrar en pantalla o cambiar sus dimensiones o posición. Sin embargo no parece existir una manera de que el servidor avise al gestor de ventanas de que una propiedad ha cambiado, y dado que estas propiedades contienen información importante necesaria a la hora de mapear una ventana, la mejor solución que encontré fue hacer una caché de datos que relleno en el momento de hacer visible una ventana (en ese momento las propiedades ya tienen que estar definidas), aunque antes puedo haber almacenado otras operaciones que se hayan hecho en la ventana, como un cambio de tamaño o de posición.

Otra cosa que aprendí es que hay dos grupos de eventos de interés para un gestor de ventanas: request y notification. El primero se emite cuando una aplicación pide realizar alguna operación sobre una ventana suya, y la segunda si el resultado de dicha operación cambia algo realmente en la ventana. Así, si una aplicación pide cambiar el tamaño de una ventana, ejecutará un comando xcb_configure_window(). Si no existe un gestor de ventanas, dicho comando se ejecuta normalmente; pero si lo hay, se genera un evento xcb_configure_request en el gestor. Este puede decidir si ignorarlo, ejecutarlo tal cual o modificarlo. Si decide ejecutarlo y el tamaño de la ventana cambia realmente, se emite un evento xcb_configure_notification, que llegará a la aplicación.

Y aquí encontré otro de los problemas que tuve: mi intención es que las ventanas estén maximizadas siempre, por lo que cada vez que recibía un evento xcb_configure_request, emitía un comando xcb_configure_window() con el tamaño máximo de la pantalla. El problema ocurría con algunas ventanas que pedían ser más grandes que la pantalla, por ejemplo la ventana de configuración de Firefox. En este caso, se mostraba la ventana en la primera pestaña, y la pantalla es lo suficientemente grande como para contener todo; al recibir el evento, TabletWM modifica los valores pedidos por el navegador, ajustándolos al tamaño de la pantalla, y cambia las dimensiones de la ventana. Se emite el evento xcb_configure_notification y todo sigue perfectamente.

Pero cuando se escoge la pestaña de Seguridad, el nuevo contenido no entra en la pantalla, por lo que Firefox pide un tamaño más grande. El gestor de ventanas recibe el evento y cambia los parámetros por las dimensiones de la pantalla (que es el mismo tamaño que ya tiene la ventana), y da la orden de cambiar el tamaño. Pero como es el mismo, nunca se genera un evento xcb_configure_notification, pero Firefox espera que le llegue. El resultado es que la ventana queda sin refrescar.

La solución que apliqué fue ejecutar primero el comando de cambio de tamaño tal cual llega, y ejecutar luego un segundo comando con el tamaño que el gestor de ventanas desea. De esa manera la aplicación siempre recibe el evento que espera, y todos felices.

Otra curiosidad fue a la hora de leer ciertas cadenas de texto en las propiedades. En algunos casos, una propiedad (como por ejemplo _XKB_RULES_NAMES) contiene varias cadenas separadas por NUL (o sea, un byte a cero). Si pedimos el tamaño de ésta en la cookie (cookie->length) nos devolverá la longitud de la primera cadena exclusivamente. Si queremos obtener todas tenemos que utilizar xcb_get_property_value_length(), que sí nos dará el tamaño total. Por si fuera poco, no se garantiza que al final de la cadena haya un NUL, por lo que debemos tener en cuenta el tamaño para no pasarnos, en lugar de usar strcpy().

El teclado fue otro de los problemas serios que tuve: el método normal de entrada por teclado de X es relativamente rudimentario, por lo que hoy en día se utiliza la extensión XKB. Por desgracia esta extensión no está portada tal cual a XCB, sino que existe un proyecto separado, XCB-COMMON, que contiene aquellas partes no dependientes de un servidor X. Por si fuera poco, no puedo emular la pulsación de un carácter concreto, sino sólo la pulsación de una tecla en sí. Esto significa que los códigos que tengo que enviar a las X dependerán del idioma del teclado escogido por el usuario, además de tener que emular pulsaciones múltiples como la tecla mayúsculas, AltGR, etc.

Encima, esta biblioteca está orientada a obtener un carácter a partir de una pulsación, cuando yo necesitaba el proceso inverso. Aunque parte del trabajo lo hace (dado un carácter devuelve el código de la tecla), no devuelve el código de los modificadores necesarios. Así, si pido la arroba, me devuelve el código de la tecla ’2′, pero no me dice si necesito pulsar también las mayúsculas (caso del teclado norteamericano) o AtlGR (caso del teclado español). Esto me obligó a realizar una pequeña chapuza, consistente en probar todas las posibles combinaciones de teclas normales y teclas modificadoras (shift, control, etc) para ver qué caracteres produce cada una.

Y para rizar el rizo, no es posible siempre obtener la combinación correcta, por lo que en algunos casos no queda más remedio que redefinir un código de tecla que no se utilice con un carácter determinado (por ejemplo, la letra Ñ). Esto hay que hacerlo con las funciones de entrada clásicas, no con las de XKB, por lo que el resultado final es algo caótico, pero funciona, y permite definir un teclado cualquiera en pantalla a partir de los caracteres que se quieren mostrar en lugar de las teclas físicas que se deben pulsar.

Pasando a Cairo, decidí utilizar esta biblioteca para los elementos gráficos (como el teclado o la ventana de apagado) en lugar de las funciones de X por varios motivos:

  • Cairo ofrece antialiasing, tanto en fuentes como en primitivas gráficas.
  • Cairo es una biblioteca moderna con un API sencillo y potente.
  • Ya conozco Cairo, pero no las funciones gráficas de X.
  • No supone una carga extra porque el lanzador de aplicaciones utiliza GTK, que por debajo trabaja también con Cairo.

Trabajar con Cairo directamente desde XCB no es muy complicado. Para ello, primero se debe crear una ventana, y a continuación una superficie Cairo con cairo_xcb_surface_create(). La principal complicación es conocer el visual_type de la ventana, para poder pasárselo a la función. Esto lo podemos saber mediante el siguiente código (la variable conn es el handler de la conexión con el servidor X):

    xcb_screen_t *scr=xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
    xcb_visualtype_t *visual_type = NULL;
    xcb_depth_iterator_t depth_iter;

    for (depth_iter = xcb_screen_allowed_depths_iterator (scr); depth_iter.rem; xcb_depth_next (&depth_iter)) {
        xcb_visualtype_iterator_t visual_iter;

        visual_iter = xcb_depth_visuals_iterator (depth_iter.data);
        for (; visual_iter.rem; xcb_visualtype_next (&visual_iter)) {
            if (scr->root_visual == visual_iter.data->visual_id) {
                visual_type = visual_iter.data;
                break;
            }
        }
    }

Luego no tenemos más que engancharnos al evento expose de la ventana (XCB_EVENT_MASK_EXPOSURE) y, cada vez que se reciba, generar un contexto Cairo y repintar la ventana. Un detalle extra a tener en cuenta es que cada vez que se redimensione la ventana es necesario llamar a cairo_xcb_surface_set_size(), para cambiar el tamaño de la superficie Cairo.

Ah, y no olvidar hacer un xcb_flush() al terminar de pintar algo, para que efectivamente lo pinte en la ventana.

Gestion de ventanas

septiembre 5th, 2013

El siguiente paso lógico en el desarrollo de mi tablet era, obviamente, un gestor de ventanas adecuado. Y es que, aunque hay cienes y cienes de ellos, ninguno se adapta a lo que yo busco, que es:

  • debe maximizar las ventanas por defecto, para que aprovechen siempre toda la pantalla
  • sin decoraciones, para aprovechar al máximo la pantalla
  • que se pueda manejar tanto desde teclado como desde una pantalla táctil

Al final me lié la manta a la cabeza y escribí mi propio gestor: TabletWM. Este gestor está basado en XCB, la biblioteca ligera para XWindows, y sigue, en lo que puede, los estándares ICCCM y EWMH.

Entre las características importantes está la capacidad de distinguir entre las ventanas de una misma aplicación y las de otras, permitiendo recorrer, por ejemplo, sólo las ventanas de GIMP, aunque haya otras aplicaciones lanzadas. La idea es simplificar el uso cuando se trabaja en una tablet.

Ahora sólo me falta crear un lanzador/gestor de aplicaciones, y ya tendré mi sistema tablet listo.

Galletitas

agosto 31st, 2013

Parece que ha empezado la trasposición de la ley europea de cookies, y ahora es obligatorio que cualquier página web que haga uso de cookies lo avise claramente. Por eso a partir de ahora aparecerá un pequeño aviso en la parte inferior de mi página (aunque una vez eliminado, no volverá a aparecer).

Desde luego quiero dejar claro que YO (el autor del blog) no almaceno ni utilizo ninguna información personal de los usuarios que visitan esta página. No tengo Google Analytics ni ninguna otra herramienta de seguimiento ni publicidad, y hasta donde yo se, no se debería estar almacenando ningún tipo de información privada sin consentimiento expreso del usuario.

Actualización: tras hacer algunas pruebas, veo que el mero hecho de tener un vídeo de Youtube insertado hace que aparezcan sendas cookies, una de Youtube, y otra de Google. Por otro lado, el pulsar en los botones Me gusta de Facebook, o +1 de Google+ hace que se inserten cookies de estos dos sitios, como cabe suponer.

Actualización 2: la mera visita de mi blog hace que se almacene una simple cookie, que indica que ya se visitó esta página en alguna ocasión. Esta cookie permite, por ejemplo, evitar mostrar constantemente el aviso de que se almacenan cookies y demás.

Driver tactil

agosto 27th, 2013

Acabo de terminar la segunda versión completamente funcional del driver para el chip táctil GSL1680 que incorpora mi tablet. Entre las ventajas que tiene están que, además de procesar pulsaciones normales, permite también emular la rueda de desplazamiento del ratón para hacer scroll de una ventana, así como emular zoom-in y zoom-out con el gesto pinch-to-zoom. Por último, también permite hacer click derecho de manera puramente táctil.

El scroll se hace con dos dedos, moviéndolos de manera simultanea. Cuando el driver lo detecta emite eventos wheel y hwheel, que todas las aplicaciones interpretan como scroll. Por otro lado, al detectar un pinch-to-zoom, emite un evento wheel más la tecla CTRL. Esto se interpreta, en general, como zoom (al menos en Firefox, GIMP e InkScape).

El click derecho es el menos intuitivo, pero dado que no es algo se se utilice demasiado, no es tan problemático. Para hacerlo hay que:

  • tocar con un dedo en la pantalla, y mantenerlo.
  • tocar y soltar con otro dedo sin quitar el primer dedo de la pantalla. Esto hace entrar al driver en el modo click derecho
  • mover el primer dedo al punto donde queremos hacer click derecho (lo normal es que ya esté ahí, pero…)
  • tocar y soltar con otro dedo sin quitar el primer dedo de la pantalla.
  • mientras no despeguemos el primer dedo de la pantalla, podemos hacer tantos clicks derechos como queramos en cualquier parte de la pantalla.

Como es algo lioso, sugiero ver este vídeo de demostración del driver. El click derecho se hace en el segundo 5:

Para programar el driver decidí no meterme en fregados, y lo implementé como un driver en espacio de usuario mediante uinput. La principal ventaja es que resulta mucho más sencillo de depurar que un módulo para el kernel, además de permitir hacer cosas más avanzadas, como cargar el firmware directamente, sin necesidad de meter a udev por medio. A nivel técnico, comentar que, en realidad, se crean dos dispositivos, uno de tipo absoluto (pantalla táctil, touchpad…) y otro de tipo relativo (ratón). El motivo es que un mismo dispositivo no puede enviar eventos relativos y absolutos a la vez, pero para el cursor necesitaba eventos absolutos, mientras que para el scroll y el zoom necesitaba relativos.

De momento el código del driver está disponible únicamente en github.

 


Utilizamos cookies para garantizar que tenga la mejor experiencia en nuestro sitio web.