{"id":1327,"date":"2013-09-28T00:46:37","date_gmt":"2013-09-27T22:46:37","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=1327"},"modified":"2013-09-28T00:46:37","modified_gmt":"2013-09-27T22:46:37","slug":"xcb-y-cairo","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=1327","title":{"rendered":"XCB y Cairo"},"content":{"rendered":"<p><strong>Actualizado: <\/strong>Por fin he terminado la primera versi\u00f3n usable de <a href=\"http:\/\/www.rastersoft.com\/programas\/tabletwm_es.html\" target=\"_blank\">TabletWM<\/a> y <a href=\"http:\/\/www.rastersoft.com\/programas\/tabletlauncher_es.html\" target=\"_blank\">TabletLauncher<\/a>. Con ellos y el driver para la pantalla t\u00e1ctil he conseguido el gran objetivo de poder utilizar aplicaciones GNU\/Linux nativas en una tablet sin teclado ni rat\u00f3n.<\/p>\n<p>Crear TabletWM fue, sin duda, la parte m\u00e1s compleja, porque supuso trabajar con un API completamente nuevo, como es <a href=\"http:\/\/xcb.freedesktop.org\/\" target=\"_blank\">XCB<\/a>. Ten\u00eda claro que no quer\u00eda utilizar <a href=\"http:\/\/en.wikipedia.org\/wiki\/Xlib\" target=\"_blank\">XLib<\/a>, as\u00ed que me li\u00e9 la manta a la cabeza, pero partiendo de un gestor de ventanas muy, pero que muy b\u00e1sico hecho por <a href=\"http:\/\/lists.freedesktop.org\/archives\/xcb\/2011-November\/007375.html\" target=\"_blank\">Cinolt<\/a> a finales de 2011.<\/p>\n<p><a href=\"http:\/\/www.rastersoft.com\/imagen\/tabletwm.png\" rel=\"lightbox-0\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" src=\"http:\/\/www.rastersoft.com\/imagen\/tabletwm.png\" alt=\"\" width=\"400\" height=\"240\" \/><\/a><\/p>\n<p>Lo primero que aprend\u00ed 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\u00f3n. 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\u00f3n importante necesaria a la hora de mapear una ventana, la mejor soluci\u00f3n que encontr\u00e9 fue hacer una cach\u00e9 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\u00f1o o de posici\u00f3n.<\/p>\n<p>Otra cosa que aprend\u00ed es que hay dos grupos de eventos de inter\u00e9s para un gestor de ventanas: <em>request<\/em> y <em>notification<\/em>. El primero se emite cuando una aplicaci\u00f3n pide realizar alguna operaci\u00f3n sobre una ventana suya, y la segunda si el resultado de dicha operaci\u00f3n cambia algo realmente en la ventana. As\u00ed, si una aplicaci\u00f3n pide cambiar el tama\u00f1o de una ventana, ejecutar\u00e1 un comando <em>xcb_configure_window()<\/em>. Si no existe un gestor de ventanas, dicho comando se ejecuta normalmente; pero si lo hay, se genera un evento <em>xcb_configure_request<\/em> en el gestor. Este puede decidir si ignorarlo, ejecutarlo tal cual o modificarlo. Si decide ejecutarlo y el tama\u00f1o de la ventana cambia realmente, se emite un evento <em>xcb_configure_notification<\/em>, que llegar\u00e1 a la aplicaci\u00f3n.<\/p>\n<p>Y aqu\u00ed encontr\u00e9 otro de los problemas que tuve: mi intenci\u00f3n es que las ventanas est\u00e9n maximizadas siempre, por lo que cada vez que recib\u00eda un evento <em>xcb_configure_request<\/em>, emit\u00eda un comando <em>xcb_configure_window()<\/em> con el tama\u00f1o m\u00e1ximo de la pantalla. El problema ocurr\u00eda con algunas ventanas que ped\u00edan ser m\u00e1s grandes que la pantalla, por ejemplo la ventana de configuraci\u00f3n de Firefox. En este caso, se mostraba la ventana en la primera pesta\u00f1a, y la pantalla es lo suficientemente grande como para contener todo; al recibir el evento,\u00a0<em>TabletWM<\/em> modifica los valores pedidos por el navegador, ajust\u00e1ndolos al tama\u00f1o de la pantalla, y cambia las dimensiones de la ventana. Se emite el evento <em>xcb_configure_notification<\/em> y todo sigue perfectamente.<\/p>\n<p>Pero cuando se escoge la pesta\u00f1a de <em>Seguridad<\/em>, el nuevo contenido no entra en la pantalla, por lo que Firefox pide un tama\u00f1o m\u00e1s grande. El gestor de ventanas recibe el evento y cambia los par\u00e1metros por las dimensiones de la pantalla (que es el mismo tama\u00f1o que ya tiene la ventana), y da la orden de cambiar el tama\u00f1o. Pero como es el mismo, nunca se genera un evento <em>xcb_configure_notification<\/em>, pero Firefox espera que le llegue. El resultado es que la ventana queda sin refrescar.<\/p>\n<p>La soluci\u00f3n que apliqu\u00e9 fue ejecutar primero el comando de cambio de tama\u00f1o tal cual llega, y ejecutar luego un segundo comando con el tama\u00f1o que el gestor de ventanas desea. De esa manera la aplicaci\u00f3n siempre recibe el evento que espera, y todos felices.<\/p>\n<p>Otra curiosidad fue a la hora de leer ciertas cadenas de texto en las propiedades. En algunos casos, una propiedad (como por ejemplo <em>_XKB_RULES_NAMES<\/em>) contiene varias cadenas separadas por NUL (o sea, un byte a cero). Si pedimos el tama\u00f1o de \u00e9sta en la <em>cookie<\/em> (<em>cookie-&gt;length<\/em>) nos devolver\u00e1 la longitud de la primera cadena exclusivamente. Si queremos obtener todas tenemos que utilizar <em>xcb_get_property_value_length()<\/em>, que s\u00ed nos dar\u00e1 el tama\u00f1o 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\u00f1o para no pasarnos, en lugar de usar <em>strcpy()<\/em>.<\/p>\n<p>El teclado fue otro de los problemas serios que tuve: el m\u00e9todo normal de entrada por teclado de X es relativamente rudimentario, por lo que hoy en d\u00eda se utiliza la extensi\u00f3n XKB. Por desgracia esta extensi\u00f3n no est\u00e1 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\u00f3n de un car\u00e1cter concreto, sino s\u00f3lo la pulsaci\u00f3n de una tecla <em>en s\u00ed<\/em>. Esto significa que los c\u00f3digos que tengo que enviar a las X depender\u00e1n del idioma del teclado escogido por el usuario, adem\u00e1s de tener que emular pulsaciones m\u00faltiples como la tecla may\u00fasculas, AltGR, etc.<\/p>\n<p>Encima, esta biblioteca est\u00e1 orientada a obtener un car\u00e1cter a partir de una pulsaci\u00f3n, cuando yo necesitaba el proceso inverso. Aunque parte del trabajo lo hace (dado un car\u00e1cter devuelve el c\u00f3digo de la tecla), no devuelve el c\u00f3digo de los modificadores necesarios. As\u00ed, si pido la arroba, me devuelve el c\u00f3digo de la tecla &#8216;2&#8217;, pero no me dice si necesito pulsar tambi\u00e9n las may\u00fasculas (caso del teclado norteamericano) o AtlGR (caso del teclado espa\u00f1ol). Esto me oblig\u00f3 a realizar una peque\u00f1a chapuza, consistente en probar todas las posibles combinaciones de teclas normales y teclas modificadoras (shift, control, etc) para ver qu\u00e9 caracteres produce cada una.<\/p>\n<p>Y para rizar el rizo, no es posible siempre obtener la combinaci\u00f3n correcta, por lo que en algunos casos no queda m\u00e1s remedio que redefinir un c\u00f3digo de tecla que no se utilice con un car\u00e1cter determinado (por ejemplo, la letra \u00d1). Esto hay que hacerlo con las funciones de entrada <em>cl\u00e1sicas<\/em>, no con las de XKB, por lo que el resultado final es algo ca\u00f3tico, 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\u00edsicas que se deben pulsar.<\/p>\n<p>Pasando a Cairo, decid\u00ed utilizar esta biblioteca para los elementos gr\u00e1ficos (como el teclado o la ventana de apagado) en lugar de las funciones de X por varios motivos:<\/p>\n<ul>\n<li>Cairo ofrece antialiasing, tanto en fuentes como en primitivas gr\u00e1ficas.<\/li>\n<li>Cairo es una biblioteca moderna con un API sencillo y potente.<\/li>\n<li>Ya conozco Cairo, pero no las funciones gr\u00e1ficas de X.<\/li>\n<li>No supone una carga extra porque el lanzador de aplicaciones utiliza GTK, que por debajo trabaja tambi\u00e9n con Cairo.<\/li>\n<\/ul>\n<p>Trabajar con Cairo directamente desde XCB no es muy complicado. Para ello, primero se debe crear una ventana, y a continuaci\u00f3n una superficie Cairo con <em>cairo_xcb_surface_create()<\/em>. La principal complicaci\u00f3n es conocer el\u00a0<em>visual_type<\/em> de la ventana, para poder pas\u00e1rselo a la funci\u00f3n. Esto lo podemos saber mediante el siguiente c\u00f3digo (la variable <em>conn<\/em> es el <em>handler<\/em> de la conexi\u00f3n con el servidor X):<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">    xcb_screen_t *scr=xcb_setup_roots_iterator(xcb_get_setup(conn)).data;\n\u00a0\u00a0\u00a0 xcb_visualtype_t *visual_type = NULL;\n    xcb_depth_iterator_t depth_iter;\n\n\u00a0\u00a0 \u00a0for (depth_iter = xcb_screen_allowed_depths_iterator (scr); depth_iter.rem; xcb_depth_next (&amp;depth_iter)) {\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0xcb_visualtype_iterator_t visual_iter;\n\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0visual_iter = xcb_depth_visuals_iterator (depth_iter.data);\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0for (; visual_iter.rem; xcb_visualtype_next (&amp;visual_iter)) {\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (scr-&gt;root_visual == visual_iter.data-&gt;visual_id) {\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0visual_type = visual_iter.data;\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0break;\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\n\u00a0\u00a0 \u00a0}<\/pre>\n<\/div>\n<p>Luego no tenemos m\u00e1s que engancharnos al evento <em>expose<\/em> 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 <em>cairo_xcb_surface_set_size()<\/em>, para cambiar el tama\u00f1o de la superficie Cairo.<\/p>\n<p>Ah, y no olvidar hacer un <em>xcb_flush()<\/em> al terminar de pintar algo, para que efectivamente lo pinte en la ventana.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Actualizado: Por fin he terminado la primera versi\u00f3n usable de TabletWM y TabletLauncher. Con ellos y el driver para la pantalla t\u00e1ctil he conseguido el gran objetivo de poder utilizar aplicaciones GNU\/Linux nativas en una tablet sin teclado ni rat\u00f3n. Crear TabletWM fue, sin duda, la parte m\u00e1s compleja, porque supuso trabajar con un API &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=1327\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">XCB y Cairo<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,6,7],"tags":[],"class_list":["post-1327","post","type-post","status-publish","format-standard","hentry","category-programacion","category-trucos","category-tutoriales"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1327","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1327"}],"version-history":[{"count":0,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1327\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1327"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1327"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1327"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}