Archivo por años: 2013

Novedades varias

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

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

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

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

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

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

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.

 

Pantalla tactil

Por fin he conseguido acceder a la pantalla táctil. Esta tablet lleva un controlador GSL1680, para el cual no hay casi documentación. Sin embargo, pude encontrar un presunto driver, y de él sacar mucha información.

Después de muchas pruebas fui capaz de leer las coordenadas, pero sólo si arrancaba Linux en caliente (o sea, si arrancaba primero Android y luego reiniciaba). Si apagaba a machete la tablet y encendía directamente en Linux, no funcionaba.

Tras investigar, descubrí que el motivo es que el chip necesita que le envíen el firmware para funcionar. Si arrancaba Android, éste le metía dicho firmware, y al reiniciar todo seguía funcionando. Pero si apagaba la tablet, al quedarse sin corriente, el firmware se borraba, y es necesario volver a cargarlo.

Encima, parece que el firmware es específico para cada modelo. Afortunadamente, en el caso de mi tablet éste estaba disponible en un fichero de texto, en /system/etc.

Los detalles específicos de programación los he escrito en la página sobre el GSL1680 en linux-sunxi, y como no me apetece repetirlo todo, me limitaré a poner un enlace.

Gestion de energia

Nuevos cambios en la configuración del núcleo. Para que la gestión de energía funcione y la tablet se apague realmente al utilizar shutdown -h now o halt, y que apm -v devuelva el nivel actual de batería, es necesario activar las siguientes opciones:

    Power management options --->
        Run-time PM core functionality=Y
        Advanced Power Management Emulation=y
    Device Drivers --->
        Power supply class support=Y --->
            AXP Power drivers=Y --->
                AXP PMU type=AXP20 driver
                AXP initial charging environment set=Y
                AXP charging current set when suspendresumeshutdown=Y
            APM emulation for class batteries=Y
        Voltage and Current Regulator Support=Y --->

La última es necesaria para que aparezcan las de AXP Power drivers.

Teclados Logitech

Para trabajar con más comodidad con la tablet decidí utilizar un teclado con touchpad integrado. En casa tenía un modelo Logitech, así que lo conecté; sin embargo, no funcionó. Lo curioso es que desde Android sí funcionaba, así como en mi PC, así que decidí hacer algunas pruebas extra. Al final descubrí que para que estos dispositivos, que utilizan el Logitech Unifying Receiver, funcionen, es necesario activar no sólo el soporte HID completo en el núcleo, sino también las opciones siguientes opciones:

    Device Drivers --->
        HID devices=Y --->
            Generic HID support=Y
                /dev/hidraw raw HID device support=Y
            USB Human Interface Device (full HID) support=Y
            /dev/hiddev raw HID device support=Y