domingo, 8 de agosto de 2010

Jugando con DBus

Cuando los usuarios tienen la razón
Hace unos días recibí un mail de un usuario de Kdropbox diciendo que el comportamiento de la aplicación al hacer clic sobre el icono de la bandeja de sistema no era el habitual en KDE. Según me contaba al hacer clic se abría la ventana por tanto al volver a hacerlo debería cerrarse.
 Es curioso pero tantos años usando este escritorio y no me había fijado. Lo comprobé y es cierto que no todos los programas lo cumplen, pero aquellos que se distribuyen con KDE sí.  Kdropbox abría (y la versión estable la abre aún en el momento de escribir esto) una ventana de un navegador de archivos (dolphin o konqueror) cada vez que se hacía clic en el icono, por lo que era una buena manera de lanzar rápidamente 377 navegadores de archivos, dependiendo de la velocidad de nuestra mano :D
En todo caso terminaba siendo una cuestión de usabilidad y el usuario tenía razón así que había que cambiarlo. Sin embargo había un problema bastante importante en todo esto: hacer un control de ese tipo en una ventana propia de la aplicación hubiera sido relativamente sencillo, pero ¿que pasa si lo que hemos lanzado es un proceso independiente? Bueno, una solución podría ser la primera que se me ocurrió: lanzar el proceso, guardarme el pid y matarlo con el segundo clic. Error, Dolphin no se lanza como instancia nueva si ya está corriendo, o sea que si ya hay una ventana de Dolphin abierta cuando hacemos clic en el icono no tendremos un pid independiente y lo que es peor, matar ese proceso terminaría con todas las ventanas del gestor de archivos que tuviera abiertas el usuario.
DBus y qdbusviewer
 La solución para estos casos es hacer uso de DBus que es el estándar de Linux para la comunicación entre  aplicaciones. Si no tenemos experiencia previa lo mejor es trastear un poco con qdbusviewer el cual nos permitira escudriñar por todas las aplicaciones que utilizan este protocolo e incluso, llamar a sus métodos.
 Por ejemplo podemos abrir una ventana nueva de Dolphin indicando la URL que queremos mostrar.La condición previa necesaria es que Dolphin esté en ejecución ya que de otro modo no aparecerá en el listado de servicios de qdbusviewer (panel izquierdo)
 En cuanto le demos a OK en la ventana en la que se solicitan los parámetros veremos que en el panel inferior se muestra la respuesta al método o los posibles errores que ocurran.
Escribiendo el código para hacer llamadas
Pero ¿cómo hacemos esto desde nuestra aplicación? Hay bastantes ejemplos en la web para contestar a esta pregunta, un ejemplo rápido usando QT para el caso anterior sería este... bueno, no tan rápido. Un poquito de teoría antes.
Siempre necesitaremos saber lo siguiente:
  1. El nombre del servicio con el que el proceso se identifica en DBus, en la práctica se asemeja a una URL
  2. La ruta hasta el objeto al que queremos llamar
  3. El interfaz declarado por la aplicación para el método que buscamos
  4. El método al que queremos llamar y sus parámetros
Salta a la vista que todo esto lo obtenemos fácilmente con qdbusviewer. En nuestro caso:
  1. Nombre del servicio: org.kde.dolphin
  2. Ruta: /MainApplication
  3. Interfaz: org.kde.dolphin.Aplication
  4. Método: openWindow
Teniendo todo lo necesario QT lo pone muy fácil gracias a la clase QDBusInterface. Sólo tenemos que incluir las clases que necesitamos y unas cuantas líneas de código para hacer la llamada.

En este ejemplo se necesitan las siguientes clases relativas a DBus:

#include <qdbusinterface>
#include <qdbusreply>

El código necesario para abrir nuestra ventana de Dolphin es el siguiente, teniendo en cuenta que path debería contener alguna ruta válida:
QDBusInterface remoteApp("org.kde.dolphin","/MainApplication","org.kde.dolphin.Application",QDBusConnection::sessionBus());
if (remoteApp.isValid()){
QDBusReply<int> reply =remoteApp.call("openWindow",path);
int winid=(reply.value());
}
Hemos intentando la conexión con la aplicación en la ruta e interfaz deseado, después hemos comprobado que realmente hemos contactado con ella y así hemos podido llamar al método openWindow con la tura deseada. Por último nos hemos guardado la respuesta que nos devuelve el método que no es otra cosa que el identificador de la nueva ventana.

Un último ejemplo, vamos a ver como cerrar esa misma ventana ya que la forma de proceder varía ligeramente. Para entender esto hay que observar como se transforma el árbol de Dolphin en DBus, en la siguiente captura se pude ver como ha aparecido un nuevo nodo identificando la nueva ventana, de forma que si el identificador que nos ha devuelto el método anterior es el 3 tendremos la ruta
/dolphin/MainWindow3

Esto es importante a la hora de cerrar la ventana ya que es la ruta que utilizaremos:
QDBusInterface remoteApp("org.kde.dolphin","/dolphin/MainWindow"+QString::number(getWindowId()), "org.kde.dolphin.MainWindow", QDBusConnection::sessionBus());
if (remoteApp.connection().isConnected())
    QDBusReply<int> reply =remoteApp.call("quit");
 Hemos concatenado la cadena /dolphin/MainWindow  con el identificador de la ventana que nos habíamos guarda. En este caso el método no tiene parámetros y no devuelve tampoco ningún valor.
 Esto no ha sido nada
Esto solo han sido un par de ejemplos básicos, como se puede suponer cada aplicación será un mundo y habrá que investigar un poco los métodos que contiene para ver lo que podemos hacer con ella. Por supuesto que no todas las aplicaciones están preparadas para ser utilizadas mediante DBus. En fin, todo esto no es más que la punta del iceberg ya que por ejemplo se pueden usar señales y slots entre aplicaciones distintas de forma muy parecida a como lo hace QT. En mi caso no me ha hecho falta de momento nada más que esto pero al que le interese le vale la pena profundizar más.