Nueva versión de Autovala

abril 7th, 2019

Ayer lancé la versión 1.14.0 de Autovala. Básicamente corrige varios errores de mesón, en concreto cuando se especifica una ruta de instalación no estándar para los ficheros .gir o para una biblioteca. También permite, por fin, hacer “includes” de ficheros en meson, para personalizar la instalación. Y, por último, ya no instala los ficheros CMakeList.txt cuando se utiliza ninja para instalar una aplicación.

Como de costumbre, se puede encontrar en mi página personal.

Un poco de modding

marzo 31st, 2019

,Hace tiempo me compré una caja Elite 110 de Cooler Master. Esta caja tiene la ventaja de ser muy pequeña y compacta gracias a que no tiene hueco para lector de CD/DVD, por lo que me gustaba mucho. Su inconveniente, sin embargo, es que sólo permite placas madre mini-ITX; esto es, de un máximo de 17x17cm, aunque para mí tampoco era un problema, pues mi placa madre era de ese formato. Sin embargo, recientemente se me estropeó, y ahí empezaron mis problemas, porque por más que buscaba no encontraba ninguna placa decente para socket Intel LGA 1150 en formato mini-ITX. Sí las había en formato micro-ATX, pero obviamente eran demasiado grandes y no entraban, o bien un par de modelos chinos a la venta en Amazon que me inspiraban poca o ninguna confianza.

Sin embargo, de pura casualidad, encontré que en una tienda de aquí vendían la Gigabyte GA-B85M-DS3H, que medía 22,6 x 19,3 centímetros. Armado con el metro vi que sí entraba, justita pero entraba, así que la compré. Cual no sería mi decepción cuando fui a montarla, al descubrir que no entraba por unos míseros tres milímetros.

Afortunadamente no tropezaba con el borde de la caja, sino con un tabique interno que permite montar discos duros o bien dos ventiladores extra, así que, tras verlo con calma, comprobé que era posible recortar parte del tabique, de manera que la placa entrase. Y dicho y hecho.

Lo primero fue desmontar el tabique. Para ello utilicé el taladro y la broca de 3 milímetros para romper los siete remaches que lo sujetaban (tres delante, dos debajo y dos detrás). Una vez retirado, usé la dremel para recortar la parte inferior. Primero corté el nervio central, como se ve en esta foto:

Luego corté por los laterales. En la parte trasera corté justo por debajo del agujero del remache, pero en la delantera corté primero en vertical y luego de manera perpendicular, para así dejar más sitio para la placa. En la siguiente foto se ve, marcado en rojo, la dirección de corte para cada lado:

Una vez hecho esto limamos la pieza con una lima de metal para quitar asperezas que puedan dañar los cables, y limpiamos a conciencia la pieza para eliminar restos de limaduras de aluminio que puedan causar cortocircuitos (yo suelo lavarla un poco y luego secarla cuidadosamente; por hacerlo una vez no es un problema).

Y llega el momento de volver a colocarla en su sitio: para ello colocamos primero los cinco remaches que quedan, pero sin apretarlos aún. Una vez introducidos, les damos un primer apretón a cada uno para asegurarnos de que la pieza se vaya situando en su lugar, y sólo entonces damos el segundo apretón, el que romperá el vástago interno del remache. Este es el resultado:

Ahora ya tenemos espacio suficiente para montar la placa sin que tropiece con nada, y teniendo incluso unos milímetros de espacio extra:

Sin embargo, ya que había desmontado la caja, decidí intentar mejorarla un poco. Por un lado, echaba de menos disponer de un conector eSATA, y por otro, en varias ocasiones me encontré con que necesitaba leer tarjetas SD y similares. Aunque disponía de un lector de tarjetas para montar en una bahía de 3 1/2 pulgadas, era bastante largo; afortunadamente, por dentro estaba prácticamente hueco, así que cogí la dremel y recorté su contenedor hasta el tamaño mínimo, y luego lo sujeté a la parte superior del frontal así:

Fijarlo no fue algo inmediato: el tornillo izquierdo fue relativamente sencillo, pues coincidía con un agujero del frontal y era suficiente con añadir una arandela, pero el tornillo derecho caía justo en la abertura grande que se ve junto al lector. La solución consistió en tomar un trozo de alambre de un clip y doblarlo en forma de gancho:

Este gancho lo metí en el tornillo derecho (también con una arandela) y luego lo doblé por uno de los agujeros del ventilador, situados debajo de la abertura grande, tal y como se ve en estas fotos:

Luego sólo fue necesario hacer un recorte con la dremel en la parte superior del frontal para que sobresaliese el lector.

El conector eSATA fue lo más sencillo de implementar: sólo tuve que coger el taladro y abrir un agujero rectangular del tamaño adecuado en la parte superior derecha del frontal, además de dos agujeros para los tornillos:

Por último, decidí montar el ventilador frontal en este lado del aluminio en lugar de hacerlo en la zona útil de la caja, para así ganar un par de centímetros extra. Por desgracia casi no había sitio, entre el lector de tarjetas y el interruptor de encendido; sin embargo, recortando una de las esquinas conseguí que entrase:

Y con esto ya está terminada: una caja compacta pero con la que no renuncio a nada.

Nueva versión de Terminus

marzo 28th, 2019

Acabo de lanzar una corrección rápida de Terminus, mi programa de terminal. Básicamente corrige la funcionalidad de COPY mediante el teclado. Por un error, sólo copiaba la selección al buffer primario, pero no al portapapeles, con lo que, en la práctica, no funcionaba.

También aproveché para traducir algunas frases que estaban pendientes.

Añadido https

marzo 18th, 2019

Acabo de descubrir que mi servidor tiene soporte de Let’s encrypt, un servicio que ofrece certificados https gratuitos, así que he aprovechado para activarlo. A partir de ahora ya se puede acceder tanto a mi página personal como a mi blog de manera segura.

Autovala 1.12

febrero 10th, 2019

Acabo de subir una nueva versión de Autovala, la 1.12. Los principales cambios son tres:

Para empezar, he completado el soporte de Meson. Ahora ya funciona completamente y soporta todas las funcionalidades. Y teniendo en cuenta la diferencia de velocidad a la hora de compilar, es algo que se agradece mucho.

Por otro lado, ahora ya compila los ficheros .gir a .typelib. Este era un paso necesario para que las bibliotecas con Gobject Introspection funcionasen, pero hasta que ayer intenté desarrollar una y utilizarla desde Gnome Shell, no supe que faltaba ese paso.

Por último, corrige un problema en el espacio de nombres. Hasta ahora, al hacer un programa, se creaban una serie de constantes (como el número de versión, o el prefijo de la ruta de instalación) bajo el namespace Constants. Sin embargo, en el caso de una biblioteca, dichas constantes se metían en un namespace diferente, llamado como la biblioteca pero con Constants añadido al final. Así, si la biblioteca se llamaba Pepito, se supone que tú utilizarás el namespace Pepito para el código de la biblioteca, pero las constantes estarán en el namespace PepitoConstants.

Originalmente esto se hizo para evitar conflictos con otros nombres; por desgracia, el compilador de .gir a .typelib exige que cada namespace tenga su propio fichero .gir; y, por otro lado, Vala se empeña en generar un único fichero .gir con todos los espacios de nombres juntos, uno detrás de otro. Además, no podía ser de otra manera si se quería mantener compatibilidad con Meson. El resultado es que era imposible compilar los ficheros .gir de una biblioteca desarrollada con Autovala.

La única solución fue cambiar eso y hacer que las constantes estén en el mismo espacio de nombres que el resto de código de la biblioteca. Es cierto que esto obligará a modificar el código que use dichas constantes, pero también es verdad que los cambios son inmediatos y directos, por lo que no es un problema real.

Como de costumbre, se puede descargar desde mi página web, estando disponible tanto en forma de código fuente (en gitlab) como en paquetes para Debian, Ubuntu, Fedora y Arch.

Juegos ALSA sobre PulseAudio

diciembre 31st, 2018

Hace unas semanas descubrí la saga de juegos de Deponia, y la verdad es que estoy enganchado. Además, hay versiones nativas para Linux en GOG games, y a un precio asequible, lo que es una maravilla.

Las tres primeras aventuras funcionaron en mi sistema Debian sin el más mínimo problema, pero la cuarta, sin embargo, sufría de un problema raro: al lanzarla se quedaba como colgada, con la pantalla en negro, y no aparecía el menú principal hasta que pulsaba ESC. Además, lo hacía sin sonido, lo que era muy sospechoso.

Al lanzarlo desde un terminal vi que salían una serie de errores referentes a que no podía abrir el dispositivo PCM de ALSA (el subsistema de sonido de Linux). Enseguida sospeché que el problema podía venir por tener PulseAudio gestionando todo el tema del audio. En concreto, el cuelgue del principio parecía que era porque intentaba mostrar un vídeo de presentación, pero se quedaba esperando a que la tarjeta de sonido estuviese lista (y por eso al pulsar ESC se desbloqueaba: porque cancelaba el vídeo y pasaba al siguiente paso).

La solución consistió en redireccionar el sonido PCM de los programas ALSA a través de PulseAudio. Para ello sólo tuve que crear un fichero llamado .asoundrc en mi directorio personal con este contenido:

pcm.!default {
type pulse
}

Gracias a él, ahora todos los programas que, por defecto, trabajan con ALSA, ahora enviarán su salida a través de PulseAudio. Esto es posible gracias al diseño de ALSA: mientras que en la vieja arquitectura OSS el programador abría el dispositivo de sonido directamente y utilizaba llamadas IOCTL para configurarlo, y llamadas read y write para capturar y emitir sonido, en ALSA se anima a utilizar únicamente una serie de bibliotecas de más alto nivel. Lo interesante es que esto permite que el programa se desentienda por completo de a donde se envía el sonido, pues eso lo decide el propio usuario mediante la configuración. Así, por defecto iría directamente al hardware, pero el sistema de plugins de ALSA permite enviarlo a cualquier otro sitio al poder definir tarjetas de sonido virtuales. Así, tenemos dos módulos especialmente interesantes que son pcm_pulse y pcm_jack, que permiten integrar de manera transparente cualquier aplicación ALSA con los demonios de sonido PulseAudio y JACK. El primero es justo el que utilizamos en el fichero de configuración de arriba.

Java vs. systemd

diciembre 26th, 2018

Pues parece que ya podemos añadir otra historia más a los problemas de systemd. Ahora resulta que un cambio inocente en sus políticas ha hecho que un bug serio en Java salga a la superficie: prácticamente todas las aplicaciones Java fallan en Debian SID por culpa de esto, dando un error de este estilo:

library initialization failed - unable to allocate file descriptor table - out of memoryAborted

En mi caso fueron MPlabX y SimplicityStudio las que me dieron problemas, pero parece que ocurre con muchas más aplicaciones Java, y en concreto Android Studio también lo da.

Según una entrada en github, el problema se debe a que Java toma el número máximo de descriptores que se pueden abrir y reserva un bloque de memoria lo suficientemente largo como para albergarlos a todos. Hasta ahora ese número era 4096, con lo que con 16 Kbytes tenía de sobra. Pero tras un cambio en Systemd (pedido, además, por la gente del kernel) dicho número se ha ampliado a 262144 (256 * 1024). En principio esto no debería ser problema (sigue siendo una cifra razonable, y con un mega puede almacenarlo todo), pero, por una serie de desafortunadas circunstancias, Java no recibe esa cifra en Debian SID, sino que, si no hay configurado ningún límite, recibe INFINITY (los detalles se pueden seguir en este hilo de github). El resultado, obviamente, es un error de memoria insuficiente.

Mientras el error no se corrige, es posible parchearlo temporalmente añadiendo esta línea a /etc/security/limits.conf:

    *   hard   nofile   4096

No olvidarse del asterisco al principio. Y en caso de que 4096 no sean suficientes descriptores, se puede aumentar. Un valor máximo razonable es 1048576. Una vez modificado es necesario cerrar la sesión y volver a entrar (o bien reiniciar) para que los cambios surtan efecto.

Por supuesto es bueno revisar de vez en cuando si ya han corregido el bug, y eliminar esa línea cuando ya no sea necesaria.

Cargando mis cascos (y 2)

diciembre 18th, 2018

Hace ya dos años modifiqué unos cascos BlueTooth para que sólo con colgarlos de un soporte se cargase su batería. Aunque funcionó bien, en este tiempo hice algunos cambios extra, y creo que ha llegado la hora de publicarlos.

Para empezar, en el artículo anterior comentaba que había utilizado malla de desoldar para hacer los contactos. Aunque funcionó bien durante unos cuantos meses, lo cierto es que el pegamento de la cinta de doble cara fue permeando a través de la malla de cobre y acabó por dar problemas de conductividad, por eso al final opté por comprar cinta adhesiva de cobre y crear con ella los nuevos contactos.

Lo primero fue pegar la cinta por la parte de arriba de la diadema, como se ve en la foto, dejando pegada sólo aproximadamente un tercio. Luego, con unas tijeras, procedí a dar cortes en la parte que quedaba colgando, cada dos centímetros aproximadamente. De esta manera pude doblarlo sobre el lateral y garantizar que quedase bien pegado y, a la vez, con continuidad eléctrica sobre toda la superficie. Este fue el resultado:

El último paso en los cascos fue soldar los cables que vienen del puente de diodos a los dos contactos (véase la entrada anterior donde explico las modificaciones a nivel eléctrico), y proteger todo con un par de vueltas de cinta aislante en cada lado.

El siguiente punto era el soporte. En el artículo anterior mostré la chapuza que hice con una alcayata y lengüetas sacadas de una pila de petaca, pero llevaba tiempo con ganas de hacer algo más serio, así que decidí darle uso a las impresoras 3D de A Industriosa para construir un soporte en condiciones. Para ello, primero hice el diseño con FreeCAD, que tiene esta pinta y cuyo fichero podéis descargar desde este enlace.

Una vez impresa llegó la cuestión de como hacer las lengüetas para los contactos. Para ello decidí hacerlas de plástico flexible, sacado de una tapa de tornillos, y luego recubrirlas con cinta adhesiva de cobre. En la foto siguiente se ven las dos lengüetas.

Tras cortarlas, las pegué en el soporte utilizando cola térmica, quedando como en la siguiente foto.

El siguiente paso fue recubrirlas con la cinta adhesiva de cobre y soldar los cables del transformador de alimentación. Sin embargo, aunque quedó bien, es cierto que habría sido mejor pegar primero la cinta adhesiva y soldar los cables, y sólo después pegar las lengüetas con la cola térmica, pues cuando apliqué el soldador me encontré con que se me despegaban.

Tras hacer todo esto, éste es el resultado del soporte, listo para acoger en su seno a los cascos. Vemos que está fijado a la balda con un tornillo, y que los cables de alimentación están soldados en la parte superior, dejando toda la superficie de las lengüetas para que haga contacto con los electrodos de los cascos.

Y este es el resultado final: un soporte con mucha mejor presencia y calidad, y que me garantiza que mis cascos siempre estarán adecuadamente cargados cada vez que los necesite.


Presentando CRUST

agosto 26th, 2018

Acabo de lanzar CRUST. Se trata de un analizador estático de C que permite disponer en C de una gestión de memoria similar a la de RUST.

Y es que RUST está de moda, pues al ofrecer seguridad en el acceso a memoria dinámica pero sin necesidad de un runtime (como un recolector de basura) o de otras técnicas (como el conteo de referencias), permite exprimir al máximo el rendimiento. El problema es que RUST es un lenguaje nuevo, con su sintaxis propia (que, además, diverge de la de C u otros lenguajes bastante), y que, por tanto, tiene una curva de aprendizaje.

Por otro lado, existen casos en los que no se puede utilizar (todavía) RUST, como el de un microcontrolador PIC, Atmel…, pues hace falta un compilador específico. En otros microcontroladores, como los basados en ARM, es posible utilizarlo, pero sigue teniendo el problema de que no es un compilador oficial, y por tanto hay que hacer algún que otro malabar para integrarlo en la toolchain del fabricante.

Es aquí donde CRUST hace su aparición: como ya dije se trata de un analizador estático de C que permite disponer de (más o menos, claro) las mismas comprobaciones de seguridad que ofrece RUST para la gestión de memoria dinámica, de manera que es más difícil que un programa sufra referencias colgantes o dangling pointers, o pérdidas de memoria.

A la hora de diseñar CRUST tenía una cosa muy clara en mente: no podía crear un nuevo lenguaje parecido a C, sino que tenía que seguir siendo C puro, compilable con absolutamente cualquier compilador estándar. Eso eliminaba cualquier tipo de preprocesador del estilo de Metaobject o similares. También suponía rechazar cualquier tipo de conjunto de macros que pudiese alterar el código de la más mínima manera. Y por supuesto, el uso de bibliotecas estaba completamente descartado.

La solución consistió en crear una serie de calificadores específicos, similares en funcionamiento a los calificadores volatile o const ya disponibles en C, que permitan al analizador saber si un puntero concreto es gestionado o no-gestionado, así como otras propiedades importantes para el analizador. Estos calificadores comienzan todos con el prefijo __crust_ para evitar interferencias con nombres de variables o futuras adiciones al lenguaje C. La clave de estos calificadores es que no son necesarios en absoluto para compilar el código.

Por supuesto, ningún compilador aceptaría un código con dichos calificadores, y por eso es necesario incluir un fichero de cabecera (que se incluye con el analizador estático) que define dichos nuevos calificadores como espacios en blanco para el preprocesador de C. De esta manera, a la hora de compilar estos calificadores simplemente “desaparecen”, y sólo son tenidos en cuenta cuando se utiliza el analizador. Este es un trozo de dicho fichero de cabecera, para que se entienda mejor:

#ifndef ENABLE_CRUST_TAGS

#ifndef __crust__
#define __crust__
#endif

#ifndef __crust_borrow__
#define __crust_borrow__
#endif

#ifndef __crust_recycle__
#define __crust_recycle__
#endif

#ifndef __crust_alias__
#define __crust_alias__
#endif

#ifndef __crust_no_0__
#define __crust_no_0__
#endif

...

Como se ve, se define cada posible calificador como una cadena vacía, lo que hace que el preprocesador se encargue de limpiar el código y dejarlo listo para el compilador, sin necesidad de modificar nada. Esto permite programar como de costumbre, simplemente etiquetando aquellos punteros que deben ser gestionados como un bloque CRUST, y compilando el código normalmente con el toolchain habitual, y sólo de vez en cuando pasar el analizador estático para comprobar si hemos cometido algún error al liberar o utilizar uno de estos bloques. Por supuesto, no es necesario escribir este fichero a mano, sino que se puede generar automáticamente simplemente llamando al analizador estático con el comando crust –headers, con lo que generará dicho fichero en el directorio actual.

La base de las reglas de gestión de memoria de CRUST (y, por extensión, de RUST) es que cada función es responsable de todos los bloques de memoria que genera o recibe. Así, si una función pide un bloque de memoria dinámica (por ejemplo con malloc), es su responsabilidad liberarlo o asegurarse que sea liberado. Esto puede ocurrir de tres maneras diferentes:

  • Puede liberar el bloque directamente ella misma
  • Puede llamar a otra función pasando dicho bloque como un parámetro, de manera que pase a ser responsabilidad de la nueva función garantizar que se libere dicho bloque
  • Puede devolver el bloque a la función llamante, de manera que ésta recibe la responsabilidad de liberarlo

No hay mucho más. Por supuesto existen, a mayores, otros detalles que hacen que la cosa no sea tan sencilla, por lo que para una explicación más en profundidad recomiendo leer como es el modelo de memoria de RUST.

Un ejemplo sencillo de como trabaja CRUST se puede ver en este trozo de código:

// SIEMPRE añadimos crust.h al principio
// El fichero tiene que estar en el proyecto
#include "crust.h"
#include <unistd.h>

// Definimos una estructura como "gestionada"
// simplemente añadiendo __crust__ a su definición
// Utilizamos un typedef para ahorrarnos tener que poner
// __crust__ en todos los sitios donde se utiliza
typedef __crust__ struct {
	int member;
	int p1;
	int p2;
} *un_tipo_t;

// esta función crea un nuevo bloque "gestionado" y lo devuelve

un_tipo_t funcion1();

// esta función recibe un bloque "gestionado",
// pero no lo libera antes de salir

void funcion2(un_tipo_t __crust_borrow__ parametro);

// esta función recibe un puntero a un bloque "gestionado",
// y además lo libera antes de salir

uint32_t funcion3(un_tipo_t parametro);

void main() {

	un_tipo_t bloque = funcion1();

	funcion2(bloque);
	funcion3(bloque);
}

Aquí vemos varias cosas:

  • Primero hacemos un typedef de un puntero a una estructura, y además incluimos el calificador __crust__. Esto significa que absolutamente cualquier variable de tipo un_tipo_t será gestionada, y por tanto sujeta a las reglas de CRUST.
  • Luego tenemos tres definiciones de funciones que “hacen cosas” con tipos un_tipo_t.
  • Finalmente, tenemos el bloque main. En él creamos un puntero de tipo un_tipo_t y le asignamos el bloque que nos devuelve funcion2.
  • A continuación llamamos con dicho bloque a funcion3. Como dicho parámetro está marcado como __crust_borrow__, sabemos que dicha función nunca liberará dicho bloque, por lo que después de llamarla seguirá estando disponible y podemos seguir utilizándolo.
  • Finalmente llamamos también con dicho bloque a funcion1. Como el parámetro de dicha función no está marcado como __crust_borrow__, sabemos a ciencia cierta que ese bloque que estamos pasando va a ser liberado dentro de ella, por lo que a partir de este punto no podemos volver a utilizarlo.
  • Llegamos al final de la función, y como la variable bloque ya no apunta a nada (pues el bloque fue liberado al llamar a funcion3), no hay riesgo de que tengamos una fuga de memoria.

Este código no devolvería ningún error al pasar por el analizador estático CRUST precisamente porque cumple con precisión las reglas de gestión de memoria. Sin embargo, si hiciésemos un cambio tan sencillo como invertir el orden de las llamadas a funcion2() y funcion3(), obtendríamos un error:

ERROR: Argument 1 when calling function 'funcion2' at line 41 was freed at line 40
Total: 1 errors.

El motivo es que funcion2() libera el bloque de memoria que recibe, lo que significa que cuando llamamos después a funcion3() con él, CRUST sabe que ese bloque de memoria ya no existe, y nos avisa.

Algo similar ocurre si sólo llamamos a funcion2() (que sabemos que no libera el bloque) pero no llamamos a funcion3():

ERROR: Memory block 'bloque', initialized at line 38, is still in use at exit point in line 41
Total: 1 errors.

Aquí CRUST se da cuenta de que el bloque que hemos inicializado no ha sido liberado al llegar al final de la función. Si lo dejásemos así tendríamos una fuga de memoria, y por eso nos avisa diligentemente.

Por supuesto CRUST es lo suficientemente inteligente como para seguir las posibles ramas de ejecución del código. Probemos a modificar la función main() anterior y dejémosla así:

void main() {
	// "tmp" tiene un valor que desconocemos
	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 5) {
		return;
	}

	if (tmp == 8) {
		bloque = NULL;
	}

	if (tmp == 7) {
		bloque = funcion1();
	}

	if (tmp != 3) {
		funcion3(bloque);
	}
	funcion2(bloque);
}

Al pasar este código a través de CRUST obtenemos el siguiente resultado:

ERROR: Memory block 'bloque', initialized at line 42, is still in use at exit point in line 45
ERROR: Assignment to 'bloque' at line 49, which was already assigned at line 42
ERROR: Argument 1 when calling function 'funcion2' at line 59 was freed at line 57
ERROR: Memory block 'bloque', initialized at line 53, is still in use at exit point in line 60
ERROR: Assignment to 'bloque' at line 53, which was already assigned at line 42
ERROR: Memory block 'bloque', initialized at line 42, is still in use at exit point in line 60
Total: 6 errors.

Aquí nos está avisando de todos los errores que hemos cometido, que son:

  • Si tmp vale 5 saldremos en el return de la primera comparación, con lo que el bloque que inicializamos en la línea 42 no se libera y tendremos una fuga de memoria.
  • Si tmp vale 7 u 8 estaremos sobreescribiendo un puntero que apunta a un bloque válido en la línea 49, con lo que tendremos una fuga de memoria.
  • Si tmp tiene un valor diferente de 3 liberaremos el bloque en la línea 49, con lo que al llamar a funcion2() tendremos una referencia colgante.
  • Si tmp vale 3 todo parecerá funcionar correctamente hasta llegar al final de la función, donde nos encontraremos con que el bloque nunca se libera y tendremos una fuga de memoria. Este error nos aparece dos veces porque en una de las ramas de ejecución no liberamos el bloque recibido al principio (línea 42) y en la otra no liberamos el bloque obtenido cuando tmp vale 7.

Por supuesto, CRUST tiene algunas limitaciones. Por ejemplo, sólo recuerda si una variable es NULL (vale 0) o no (valor distinto de 0), pero no valores concretos. Esto significa que este código será analizado correctamente:

void main() {

	un_tipo_t bloque = funcion1();

	if (bloque != NULL) {
		funcion3(bloque);
		bloque = NULL;
	}

	if (bloque != NULL) {
		funcion2(bloque);
	}
}

CRUST sabe que bloque, tal cual es devuelto por funcion1() puede ser NULL o no NULL, pero cuando llega al primer if y analiza ambas posibles ramas, en la de dentro del if marca a bloque como no NULL, y en la de fuera como NULL. Cuando llama a funcion3() el bloque es liberado, y por eso no devuelve un error al asignar NULL a dicha variable. A partir de aquí ambas ramas de ejecución tienen NULL como valor de bloque, y CRUST es capaz de detectar correctamente que jamás se llamará a funcion2(), y por eso no devuelve ningún error.

Sin embargo, este bloque sí daría errores, pues CRUST no llega a tener un nivel de control tan fino de los valores de las variables:

void main() {

	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 3) {
		funcion3(bloque);
	}

	if (tmp != 3) {
		funcion2(bloque);
		funcion3(bloque);
	}
}

Este código devolvería estos errores:

ERROR: Argument 1 when calling function 'funcion2' at line 48 was freed at line 44
ERROR: Argument 1 when calling function 'funcion3' at line 49 was freed at line 44
ERROR: Memory block 'bloque', initialized at line 41, is still in use at exit point in line 51
Total: 3 errors.

Por supuesto, la forma correcta de hacer lo anterior sería esta:

void main() {

	uint8_t tmp;

	un_tipo_t bloque = funcion1();

	if (tmp == 3) {
		funcion3(bloque);
	} else {
		funcion2(bloque);
		funcion3(bloque);
	}
}

La cual sí sería analizada correctamente por CRUST.

Todo esto no son más que unas pinceladas, pues hay mucho más en CRUST (por ejemplo el prestamo de bloques, igual que en RUST), por lo que lo mejor es leerse la documentación completa, que viene en formato PDF.

Como de costumbre, se puede encontrar en mi página web y en el respositorio de CRUST en GitLab.

Trust Flex Graphics Tablet

agosto 20th, 2018

El otro día me compré una tableta gráfica Trust Flex Graphics Tablet. No es que suela dibujar a menudo, pero de vez en cuando me gusta hacer alguna cosa, y según qué tareas me dejan la muñeca fatal si las hago con el ratón.

Por desgracia ya al principio empezaron los problemas, pues Linux no me la detectaba. Sin embargo, lo raro era que sí había un dispositivo en /dev/input de la tableta, simplemente no se reconocía como un dispositivo digitalizador.

Empecé a rebuscar y encontré, por fin, gracias a una entrada del gitlab de freedesktop, que el problema se debe a que la tableta no entrega la resolución en unidades físicas; esto es, no se puede saber directamente a cuantos milímetros se corresponde unas coordenadas de posición. La solución consiste en añadir estas líneas en el fichero /etc/udev/hwdb.d/60-evdev.hwdb (creándolo si no existe):

#########################################
# Trust
#########################################

# Trust Flex Graphics Tablet
evdev:input:b0003v2179p0004*
 EVDEV_ABS_00=::234
 EVDEV_ABS_01=::328

Estas líneas añaden la entrada EVDEV_ABS_XX a los datos devueltos por el driver, de manera que libinput puede saber la resolución física de la tableta.

Una vez hecho esto hay que compilar el fichero mediante el comando

sudo udevadm hwdb --update

Y finalmente reiniciar para que el sistema aplique los cambios (sí, es necesario). Una vez hecho esto, la tableta funcionará perfectamente, apareciendo en pantalla un segundo cursor que seguirá al bolígrafo.


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