The function g_dbus_connection_signal_subscribe works great for telling me when new DBus objects appear (or vanish) with InterfacesAdded signal (or InterfacesRemoved signal). But I need for to know about pre-existing objects/interfaces.
I wrote the following piece of C code to provide callbacks when DBus objects are added/removed from the bus. Error-checking omitted for simplicity.
#include <stdio.h>
#include <stdlib.h>
#include <gio/gio.h>
static void signal_cb(GDBusConnection *connection,
const gchar *sender_name,const gchar *object_path,
const gchar *interface_name,const gchar *signal_name,
GVariant *parameters,gpointer user_data)
{
printf("%s: %s.%s %s\n",object_path,interface_name,signal_name,
g_variant_print(parameters,TRUE));
}
int main(int argc,char *argv[])
{
GDBusConnection *c;
GMainLoop *loop;
int filter_id;
c = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err);
loop = g_main_loop_new(NULL,0);
filter_id = g_dbus_connection_signal_subscribe(c,
"org.bluez",NULL,NULL,NULL,NULL,
G_DBUS_SIGNAL_FLAGS_NONE,signal_cb,NULL,NULL);
g_main_loop_run (loop);
g_main_loop_unref (loop);
exit(0);
}
So what I'm trying to do is keep track of all the DBus objects that exist under the org.bluez branch of the tree. (These represent pluggable Bluetooth controllers and the devices that are discovered by each controller). I need to know about DBus objects that were already there before my program started, and I need to know about new objects that appear after my program starts.
My code above tells me about new objects, but nothing about the objects that are already there. Is there a way in the gdbus API to get an "InterfacesCreated" signal for objects that already exist? I suppose could read the entire DBus object hierarchy and then subscribe to changes, but that leads to race conditions where if an object appears between the time I read the object hierarchy and the time I subscribe, then I would miss those objects....
What's the best-practice way to accomplish this with the gdbus API?
In case anyone happens upon this, as Parthiban noted, the solution (for D-Bus services which implement the org.freedesktop.DBus.ObjectManager
interface, such as BlueZ) is to call the GetManagedObjects
method. This is extremely painful to code in C with GDBus since it requires detailed understanding of GVariant
typing; see the documentation. Also check out the docs on GVariant
data type strings. But here's how it's done:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <gio/gio.h>
/* The GVariant must be of type "a{sa{sv}}" (array of interfaces, where each */
/* interface has an array of properties). */
/* This type what DBus provides with InterfacesAdded signal and is also */
/* part of the return value from the GetManagedObjects method. */
static void proc_interface_var(const gchar *objpath,GVariant *iflist)
{
GVariantIter *iter,*iter2;
gchar *ifname,*propname,*proptext;
GVariant *propvalue;
g_variant_get(iflist,"a{sa{sv}}",&iter);
while (g_variant_iter_loop(iter,"{sa{sv}}",&ifname,&iter2)) {
if (strcmp(ifname,"org.bluez.Adatper1") != 0 &&
strcmp(ifname,"org.bluez.Device1") != 0) {
/* we only care about the Adatper1 and Device1 interfaces */
continue;
} /* if */
printf("Interface %s added to object %s\n",ifname,objpath);
while (g_variant_iter_loop(iter2,"{sv}",&propname,&propvalue)) {
proptext = g_variant_print(propvalue,0);
printf("\t%s=%s\n",propname,proptext);
g_free(proptext);
} /* while */
} /* while */
return;
} /* proc_interface_var */
/* The GVariant must be of type "a{sv}" (an array of properties). */
static void proc_property_var(const gchar *objpath,
gchar *ifname,GVariant *proplist)
{
GVariantIter *iter;
gchar *propname,*proptext;
GVariant *propvalue;
g_variant_get(proplist,"a{sv}",&iter);
while (g_variant_iter_loop(iter,"{sv}",&propname,&propvalue)) {
proptext = g_variant_print(propvalue,0);
printf("\tProperty changed on object %s interface %s: %s=%s\n",
objpath,ifname,propname,proptext);
g_free(proptext);
} /* while */
return;
} /* proc_property_var */
static void signal_cb(GDBusConnection *c,
const gchar *sender_name,const gchar *object_path,
const gchar *interface_name,const gchar *signal_name,
GVariant *parameters,gpointer user_data)
{
char fullsignal[200];
gchar *s,*objpath,*ifname,*propname,*proptext;
GVariant *ifvar,*propvalue;
GVariantIter *iter,*iter2;
snprintf(fullsignal,200,"%s.%s",interface_name,signal_name);
if (strcmp(fullsignal,
"org.freedesktop.DBus.ObjectManager.InterfacesAdded") == 0) {
g_variant_get(parameters,"(o*)",&objpath,&ifvar);
proc_interface_var(objpath,ifvar);
} else if (strcmp(fullsignal,
"org.freedesktop.DBus.Properties.PropertiesChanged") == 0) {
g_variant_get(parameters,"(s*as)",&ifname,&propvalue,&iter2);
proc_property_var(object_path,ifname,propvalue);
while (g_variant_iter_loop(iter2,"s",&propname)) {
printf("\tProperty changed on object %s interface %s: "
"%s is nil\n",object_path,ifname,propname);
} /* while */
} else {
printf("Ignoring unsupported signal for object %s, "
"signal=%s.%s, param type=%s\n",
object_path,interface_name,signal_name,
g_variant_get_type_string(parameters));
s = g_variant_print(parameters,TRUE);
printf("Unsupported signal: parameters %s\n",s);
g_free(s);
} /* else */
return;
} /* signal_cb */
static void bt_discover(GDBusConnection *c,const char *ctlname,int on_off)
{
GError *err=NULL;
GVariant *result;
const char *method;
char ctlpath[80];
snprintf(ctlpath,80,"/org/bluez/%s",ctlname);
method = on_off ? "StartDiscovery" : "StopDiscovery";
result = g_dbus_connection_call_sync(c,"org.bluez",ctlpath,
"org.bluez.Adapter1",method,NULL,
G_VARIANT_TYPE("()"), /* return-type */
G_DBUS_CALL_FLAGS_NONE,5000,NULL,&err);
if (result==NULL) {
if (err) fprintf(stderr,"g_dbus_connection_call error: %s\n",
err->message);
exit(1);
} /* if */
g_variant_unref(result);
return;
} /* bt_discover */
static void *receive_dbus_signals(void *arg)
{
GMainLoop *loop;
printf("Receiving DBus signals...\n");
loop = g_main_loop_new(NULL,0);
g_main_loop_run(loop);
g_main_loop_unref(loop);
return NULL;
} /* receive_dbus_signals */
int main(int argc,char *argv[])
{
GError *err=NULL;
GVariant *result,*ifvar;
GVariantIter *iter;
GDBusConnection *c;
GDBusNodeInfo *node;
gchar *objpath;
pthread_t handle;
int filter_id;
if ((c = g_bus_get_sync(G_BUS_TYPE_SYSTEM,NULL,&err)) == NULL) {
if (err) fprintf(stderr,"g_bus_get error: %s\n",err->message);
exit(1);
} /* if */
filter_id = g_dbus_connection_signal_subscribe(c,
"org.bluez",NULL,NULL,NULL,NULL,
G_DBUS_SIGNAL_FLAGS_NONE,signal_cb,NULL,NULL);
if (pthread_create(&handle,NULL,receive_dbus_signals,NULL) != 0) {
fprintf(stderr,"Failed to create DBus listen thread\n");
exit(1);
} /* if */
result = g_dbus_connection_call_sync(c,"org.bluez","/",
"org.freedesktop.DBus.ObjectManager","GetManagedObjects",NULL,
G_VARIANT_TYPE("(a{oa{sa{sv}}})"), /* return-type */
G_DBUS_CALL_FLAGS_NONE,5000,NULL,&err);
if (result==NULL) {
if (err) fprintf(stderr,"g_dbus_connection_call error: %s\n",
err->message);
exit(1);
} /* if */
g_variant_get(result,"(a{oa{sa{sv}}})",&iter);
/* below we replace 'a{sa{sv}}' with '*' to get it as a GVariant */
while (g_variant_iter_loop(iter,"{o*}",&objpath,&ifvar)) {
proc_interface_var(objpath,ifvar);
} /* while */
g_variant_unref(result);
bt_discover(c,"hci0",1);
sleep(5);
bt_discover(c,"hci0",0);
sleep(5);
exit(0);
}
For D-Bus services which don’t implement the org.freedesktop.DBus.ObjectManager
interface, you need to use D-Bus introspection and parse the introspection XML to find the paths of the existing object nodes.