Uso de D-Bus desde Vala

Vimos en el post anterior el funcionamiento del sistema D-Bus, que permitía a las aplicaciones conectarse e intercambiar información. Vamos a ver como podemos usar las funcionalidades de D-Bus usando el lenguaje Vala con dos sencillos programas.

El lenguaje Vala nos permite usar D-Bus de forma sencilla haciendo uso de la API GDBus incluida en GLib/GIO.

Antes de la versión 2.26 los desarrolladores tenían que hacer uso de la librería de bajo nivel libdbus de freedesktop.org o la librería de alto nivel dbus-glib. Esta última tenía problemas de diseño que se hicieron patentes cuando se extendió el uso de D-Bus y fue reescrita como GDBus e incluida en GLib/GIO. Por otro lado el uso de la librería de bajo nivel no esta recomendado

Los tipos de la especificación de D-Bus que vimos en una entrada anterior, tienen su equivalente en los siguientes tipos del lenguaje Vala:

D-Bus Vala Descripción Ejemplo
b bool boleano  
y uint8 byte  
i int entrero  
u uint entero sin signo  
n int16 entero de 16bit  
q uint16 entero sin signo de 16bit  
x int64 entero de 64bit  
t uint64 entero sin signo de 64bit  
d double doble precisión  
s string cadena  
v GLib.Variant variant  
o GLib.ObjectPath Object Path  
a [ ] Array ai se mapea a int[]
a{ } Glib.HashTable<,> Diccionario a{sv} se mapea a Foo[] donde Foo podía definirse como:
struct Foo { public int a; public int b };
( ) tipo estructura Estructura Una estructura representando a(tsav) podría parecerse a:
struct Bar { public uint64 a; public string b; public Variant[ ] c;}

D-Bus Servidor

Vamos a ver primero la versión server de nuestra aplicación. Esta publicará un servicio y también tendrá una función de difusión cada cierto tiempo en la que los clientes se pueden poner en escucha y lanzar un evento al recibir la señal.

D-Bus está estrechamente integrado en el lenguaje Vala y como he comentado es muy sencillo de usar. Para exportar una clase personalizada como un servicio D-Bus solo es necesario anotarla en el código con el atributo D-Bus:

[DBus (name = "org.comunidadlibre.dembus")] 
public class DbusServer : Object { 
 
    private int contador; 
 
    public string upercase (string msg, GLib.BusName sender) throws GLib.Error { 
        string nvar; 
        nvar = msg.up (); 
        contador++; 
        stdout.printf ("[%d] Recibimos: %s de %s\nEnvamos: %s\n", 
                        contador, msg, sender, nvar); 
        return nvar; 
    } 
 
    public void reporte () throws GLib.Error { 
        reportar (contador); 
    } 
 
    public signal void reportar (int count); 
}  

En este caso estamos exportando la función upercase() que realiza la conversión a mayúsculas de lo que le enviemos como parámetro, y la función reporte() que lanza la señal reportar en la que envía el contador de reportes procesados.

Ahora tenemos que registrar una instancia de esta clase con la sesión local de D-Bus:

 public MiApp () { 
        dbus_name = "org.comunidadlibre.dembus"; 
        dbus_type = BusType.SESSION; 
        Bus.own_name (dbus_type, dbus_name, BusNameOwnerFlags.NONE, 
                     on_bus_acquired, on_name_acquired, on_name_lost); 
    } 
 
    private void on_bus_acquired (DBusConnection conn) { 
        print (@"Conectado al bus [$dbus_type]\n"); 
        try { 
            dbus_server = new DbusServer (); 
            conn.register_object ("/org/comunidadlibre/dembus", dbus_server); 
        } catch (IOError e) { 
            stderr.printf ("No se ha podido registrar el servicio [%s]\n", e.message); 
        } 
    }  

Aquí se intenta registrar el nombre del servicio en el bus de sesión, en caso de poder adquirir el bus se lanza la función on_bus_acquired() que instancia la clase DbusServer() y la registra en D-Bus.

Este es el resultado de nuestras funciones en D-Bus visto desde d-feet:

Vista desde d-feet

Solo nos queda el contador que cada 5 segundos lanzará la señal reportar:

 public void setup_timeout () { 
        Timeout.add_seconds (5, () => { 
            try { 
                dbus_server.reporte (); 
            } catch (Error e) { 
                stderr.printf ("Error en la función reporte ()\n"); 
            } 
            return true; 
        }); 
    } 

Esta es la aplicación server completa:

/*  
 * gdbus-server-dem.vala  
 *  
 * (c) Author: David Quiroga  
 * e-mail: david [at] comunidadlibre [dot] org  
 *  
 ****************************************************************  
 * Descripción:  
 *  
 * Servidor de datos por DBUS  
 *  
 * SPDX-License-Identifier: GPL-3.0  
 * 
 valac --pkg gio-2.0 gdbus-server-dem.vala 
*/ 
 
[DBus (name = "org.comunidadlibre.dembus")] 
public class DbusServer : Object { 
 
    private int contador; 
 
    public string upercase (string msg, GLib.BusName sender) throws GLib.Error { 
        string nvar; 
        nvar = msg.up (); 
        contador++; 
        stdout.printf ("[%d] Recibimos: %s de %s\nEnvamos: %s\n", 
                        contador, msg, sender, nvar); 
        return nvar; 
    } 
 
    public void reporte () throws GLib.Error { 
        reportar (contador); 
    } 
 
    public signal void reportar (int count); 
} 
 
public class MiApp : Object { 
 
    private DbusServer dbus_server; 
    private string dbus_name; 
    private BusType dbus_type; 
 
    public MiApp () { 
        dbus_name = "org.comunidadlibre.dembus"; 
        dbus_type = BusType.SESSION; 
        Bus.own_name (dbus_type, dbus_name, BusNameOwnerFlags.NONE, 
                     on_bus_acquired, on_name_acquired, on_name_lost); 
    } 
 
    private void on_bus_acquired (DBusConnection conn) { 
        print (@"Conectado al bus [$dbus_type]\n"); 
        try { 
            dbus_server = new DbusServer (); 
            conn.register_object ("/org/comunidadlibre/dembus", dbus_server); 
        } catch (IOError e) { 
            stderr.printf ("No se ha podido registrar el servicio [%s]\n", e.message); 
        } 
    } 
 
    private void on_name_acquired () { 
        print (@"BusName [$dbus_name] adquirido\n"); 
    } 
 
    private void on_name_lost () { 
        print (@"No se ha podido adquirir el BusName [$dbus_name].\n" + 
                "El interface DBus no esta dispoble\n"); 
    } 
 
    public void setup_timeout () { 
        Timeout.add_seconds (5, () => { 
            try { 
                dbus_server.reporte (); 
            } catch (Error e) { 
                stderr.printf ("Error en la funciónreporte ()\n"); 
            } 
            return true; 
        }); 
    } 
} 
 
void main () { 
    var n_app = new MiApp (); 
    n_app.setup_timeout (); 
    new MainLoop ().run (); 
} 

Para construir la aplicación:

$ valac --pkg gio-2.0 gdbus-server-dem.vala

Si lanzamos la aplicación tendremos algo como esto:

$ ./gdbus-server-dem 
Conectado al bus [G_BUS_TYPE_SESSION]
BusName [org.comunidadlibre.dembus] adquirido

Y se queda esperando conexiones y lanzando cada 5 segundos la señal. Podemos ver que esta se esta enviando con dbus-monitor:

$ dbus-monitor
signal time=1594566870.022231 sender=:1.237 -> destination=(null destination) serial=19 path=/org/comunidadlibre/dembus; interface=org.comunidadlibre.dembus; member=Reportar
int32 0
signal time=1594566875.022507 sender=:1.237 -> destination=(null destination) serial=20 path=/org/comunidadlibre/dembus; interface=org.comunidadlibre.dembus; member=Reportar
int32 0

D-Bus cliente

La parte cliente es igualmente muy sencilla. Anotamos en el código con el atributo D-Bus el interface donde se importarán las funciones y señales de D-Bus:

[DBus (name = "org.comunidadlibre.dembus")] 
interface DemoDbus.DbusDemo : Object { 
    public abstract string upercase (string msg) throws GLib.Error; 
    public signal void reportar (int count); 
}  

Dentro del interface las funciones que importaremos de D-Bus las declaramos como abstract.

Conectamos al bus nuestro interface:

 private void connect_dbus () { 
        try { 
            dbdemo = Bus.get_proxy_sync (BusType.SESSION, "org.comunidadlibre.dembus", 
                                                            "/org/comunidadlibre/dembus"); 
            /* conectamos a la señal reportar del dbus */ 
            dbdemo.reportar.connect ( (conta) => { 
                this.label_report.label = @"Total procesado $conta"; 
            }); 
        } catch (GLib.Error e) { 
            warning ("Error al conectar a DBUS [%s]\n", e.message); 
        } 
    }  

Dentro de esta función conectamos la señal reportar, que modificara el contenido de label_report con el valor del contador.

En la clase Window conectamos el botón para que llame a la función upercase() del servidor y reciba la respuesta:

 // Conectamos las acciones al pulsar los botones 
        button.clicked.connect (() => { 
            string val_entry = entry_proce.get_text (); 
            try { 
                string reply_uper = dbdemo.upercase (val_entry); 
                entry_proce.set_text (reply_uper); 
            } catch (GLib.Error e) { 
                warning ("Error en upercase [%s]\n", e.message); 
            } 
        }); 

Este es el programa cliente completo:

/*  
 * gdbus-client-gtk.vala  
 *  
 * (c) Author: David Quiroga  
 * e-mail: david [at] clibre [dot] io  
 *  
 ****************************************************************  
 * Descripción:  
 *  
 * Cliente para probar DBUS desde GTK  
 *  
 * SPDX-License-Identifier: GPL-3.0  
 * 
 valac --pkg gtk+-3.0 gdbus-client-gtk.vala 
 */ 
 
[DBus (name = "org.comunidadlibre.dembus")] 
interface DemoDbus.DbusDemo : Object { 
    public abstract string upercase (string msg) throws GLib.Error; 
    public signal void reportar (int count); 
} 
 
public class DemoDbus.Window : Gtk.ApplicationWindow { 
 
    private Gtk.Label label_report; 
    private Gtk.Entry entry_proce; 
    private DbusDemo dbdemo = null; 
 
    public Window (Gtk.Application app) { 
        Object (application: app); 
 
        this.default_height = 140; 
        this.default_width = 350; 
 
        this.connect_dbus (); 
 
        // El contenedor 
        Gtk.Box box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 
 
        entry_proce = new Gtk.Entry (); 
        box.pack_start (entry_proce, true, true, 0); 
 
        // Creamos los botones 
        var button = new Gtk.Button.with_label ("Enviar por DBUS"); 
        var salir = new Gtk.Button.with_label ("Salir"); 
 
        // Conectamos las acciones al pulsar los botones 
        button.clicked.connect (() => { 
            string val_entry = entry_proce.get_text (); 
            try { 
                string reply_uper = dbdemo.upercase (val_entry); 
                entry_proce.set_text (reply_uper); 
            } catch (GLib.Error e) { 
                warning ("Error en upercase [%s]\n", e.message); 
            } 
        }); 
        // y el boton salir 
        salir.clicked.connect (() => { 
            this.destroy (); 
        }); 
 
        label_report = new Gtk.Label ("Total procesado: "); 
        box.pack_start (button, true, true, 0); 
        box.pack_start (salir, true, true, 0); 
        box.pack_start (label_report, true, true, 0); 
 
        this.add (box); 
        this.show_all (); 
    } 
 
    private void connect_dbus () { 
        try { 
            dbdemo = Bus.get_proxy_sync (BusType.SESSION, "org.comunidadlibre.dembus", 
                                                            "/org/comunidadlibre/dembus"); 
            /* conectamos a la señal reportar del dbus */ 
            dbdemo.reportar.connect ( (conta) => { 
                this.label_report.label = @"Total procesado $conta"; 
            }); 
        } catch (GLib.Error e) { 
            warning ("Error al conectar a DBUS [%s]\n", e.message); 
        } 
    } 
} 
 
int main (string[] args) { 
    var app = new Gtk.Application ("org.comunidadlibre.appdbus", ApplicationFlags.FLAGS_NONE); 
    app.activate.connect (() => { 
        var win = app.active_window; 
        win = new DemoDbus.Window (app); 
    }); 
 
    return app.run (args); 
}  

Construimos el ejecutable, esta vez con la librería gtk:

$ valac --pkg gtk+-3.0 gdbus-client-gtk.vala

Si lanzamos varios clientes podemos ver que podemos ejecutar la función upercase() del server en cada uno de ellos y cada 5 segundos se actualiza el contador de ambos:

gtk-client-gtk

Conclusión

El uso de D-Bus es una parte importante de la integración de las aplicaciones en el escritorio y su funcionamiento dentro del lenguaje Vala esta plenamente integrado. Tanto la parte cliente como la parte servidor se puede diseñar siguiendo la lógica del lenguaje sin necesidad de preocuparse del intrincado de D-Bus, lo que facilita enormemente su uso.

 

Modificado por última vez enViernes, 14 Agosto 2020 15:50
(1 Voto)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.