[pulseaudio-discuss] [PATCH v3] Use local icon for zeroconf shared devices

Sylvain Baubeau lebauce at gmail.com
Wed Aug 31 16:14:38 UTC 2016


systemd-hostnamed provides an icon for the machine it is running on.
If it is running, module-zeroconf-publish uses this icon for the
'icon-name' attribute in the Avahi properties. module-zeroconf-discover
passes this icon to module-tunnel using the module parameter
{sink/source}_properties.

This allows to display a portable, desktop or phone instead of
the generic sound card icon.
---
 src/Makefile.am                        |  4 +-
 src/modules/module-zeroconf-discover.c |  9 ++++
 src/modules/module-zeroconf-publish.c  | 79 +++++++++++++++++++++++++++++++++-
 3 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 7b19497..2d5bdd4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1899,8 +1899,8 @@ module_solaris_la_LIBADD = $(MODULE_LIBADD)
 
 module_zeroconf_publish_la_SOURCES = modules/module-zeroconf-publish.c
 module_zeroconf_publish_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_zeroconf_publish_la_LIBADD = $(MODULE_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libprotocol-native.la
-module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)
+module_zeroconf_publish_la_LIBADD = $(MODULE_LIBADD) $(AVAHI_LIBS) $(DBUS_LIBS) libavahi-wrap.la libprotocol-native.la
+module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) $(DBUS_CFLAGS)
 
 module_zeroconf_discover_la_SOURCES = modules/module-zeroconf-discover.c
 module_zeroconf_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c
index a165579..bd7e6ab 100644
--- a/src/modules/module-zeroconf-discover.c
+++ b/src/modules/module-zeroconf-discover.c
@@ -149,6 +149,7 @@ static void resolver_cb(
         const char *t;
         char *if_suffix = NULL;
         char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
+        char *properties = NULL;
         pa_sample_spec ss;
         pa_channel_map cm;
         AvahiStringList *l;
@@ -172,6 +173,8 @@ static void resolver_cb(
                 ss.channels = (uint8_t) atoi(value);
             else if (pa_streq(key, "format"))
                 ss.format = pa_parse_sample_format(value);
+            else if (pa_streq(key, "icon-name"))
+                properties = pa_sprintf_malloc("device.icon_name=%s", value);
             else if (pa_streq(key, "channel_map")) {
                 pa_channel_map_parse(&cm, value);
                 channel_map_set = true;
@@ -187,12 +190,14 @@ static void resolver_cb(
         if (!pa_sample_spec_valid(&ss)) {
             pa_log("Service '%s' contains an invalid sample specification.", name);
             avahi_free(device);
+            pa_xfree(properties);
             goto finish;
         }
 
         if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) {
             pa_log("Service '%s' contains an invalid channel map.", name);
             avahi_free(device);
+            pa_xfree(properties);
             goto finish;
         }
 
@@ -205,6 +210,7 @@ static void resolver_cb(
             pa_log("Cannot construct valid device name from credentials of service '%s'.", dname);
             avahi_free(device);
             pa_xfree(dname);
+            pa_xfree(properties);
             goto finish;
         }
 
@@ -220,6 +226,7 @@ static void resolver_cb(
                                  "format=%s "
                                  "channels=%u "
                                  "rate=%u "
+                                 "%s_properties=%s "
                                  "%s_name=%s "
                                  "channel_map=%s",
                                  avahi_address_snprint(at, sizeof(at), a),
@@ -228,6 +235,7 @@ static void resolver_cb(
                                  pa_sample_format_to_string(ss.format),
                                  ss.channels,
                                  ss.rate,
+                                 t, properties ? properties : "",
                                  t, dname,
                                  pa_channel_map_snprint(cmt, sizeof(cmt), &cm));
 
@@ -243,6 +251,7 @@ static void resolver_cb(
         pa_xfree(dname);
         pa_xfree(args);
         pa_xfree(if_suffix);
+        pa_xfree(properties);
         avahi_free(device);
     }
 
diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c
index 6ca0369..07f38ec 100644
--- a/src/modules/module-zeroconf-publish.c
+++ b/src/modules/module-zeroconf-publish.c
@@ -45,6 +45,7 @@
 #include <pulsecore/modargs.h>
 #include <pulsecore/avahi-wrap.h>
 #include <pulsecore/protocol-native.h>
+#include <pulsecore/dbus-shared.h>
 
 #include "module-zeroconf-publish-symdef.h"
 
@@ -63,6 +64,9 @@ PA_MODULE_LOAD_ONCE(true);
 #define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE
 #define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE
 
+#define HOSTNAME_DBUS_INTERFACE "org.freedesktop.hostname1"
+#define HOSTNAME_DBUS_PATH "/org/freedesktop/hostname1"
+#define HOSTNAME_DBUS_ICON_PROPERTY "IconName"
 /*
  * Note: Because the core avahi-client calls result in synchronous D-Bus
  * communication, calling any of those functions in the PA mainloop context
@@ -127,12 +131,14 @@ struct userdata {
     pa_module *module;
     pa_mainloop_api *api;
     pa_threaded_mainloop *mainloop;
+    pa_dbus_connection *bus;
 
     AvahiPoll *avahi_poll;
     AvahiClient *client;
 
     pa_hashmap *services; /* protect with mainloop lock */
     char *service_name;
+    char *icon_name;
 
     AvahiEntryGroup *main_entry_group;
 
@@ -308,8 +314,6 @@ static void publish_service(pa_mainloop_api *api PA_GCC_UNUSED, void *service) {
 
     if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)))
         txt = avahi_string_list_add_pair(txt, "description", t);
-    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_ICON_NAME)))
-        txt = avahi_string_list_add_pair(txt, "icon-name", t);
     if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_VENDOR_NAME)))
         txt = avahi_string_list_add_pair(txt, "vendor-name", t);
     if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
@@ -319,6 +323,12 @@ static void publish_service(pa_mainloop_api *api PA_GCC_UNUSED, void *service) {
     if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_FORM_FACTOR)))
         txt = avahi_string_list_add_pair(txt, "form-factor", t);
 
+    if (s->userdata->icon_name) {
+        txt = avahi_string_list_add_pair(txt, "icon-name", s->userdata->icon_name);
+    } else if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_ICON_NAME))) {
+        txt = avahi_string_list_add_pair(txt, "icon-name", t);
+    }
+
     if (avahi_entry_group_add_service_strlst(
                 s->entry_group,
                 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
@@ -653,6 +663,8 @@ static int avahi_process_msg(pa_msgobject *o, int code, void *data, int64_t offs
     return 0;
 }
 
+static char *get_icon_name(pa_module*m);
+
 /* Runs in Avahi mainloop context */
 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
     struct userdata *u = userdata;
@@ -666,6 +678,10 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
         case AVAHI_CLIENT_S_RUNNING:
             /* Collect all sinks/sources, and publish them */
             pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_PUBLISH_ALL, u, 0, NULL, NULL);
+
+            /* Request icon name through D-BUS */
+            u->icon_name = get_icon_name(u->module);
+
             break;
 
         case AVAHI_CLIENT_S_COLLISION:
@@ -724,6 +740,64 @@ fail:
     pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_SHUTDOWN_START, u, 0, NULL, NULL);
 }
 
+static char *get_icon_name(pa_module*m) {
+    const char *interface = HOSTNAME_DBUS_INTERFACE;
+    const char *property = HOSTNAME_DBUS_ICON_PROPERTY;
+    char *icon_name;
+    pa_dbus_connection *bus;
+    DBusError error;
+    DBusMessageIter args;
+    DBusMessage *msg = NULL;
+    DBusMessage *reply = NULL;
+    DBusConnection *conn = NULL;
+    DBusMessageIter sub;
+
+    if (!(bus = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error))) {
+        pa_log("Failed to get system bus connection: %s", error.message);
+        goto out;
+    }
+
+    conn = pa_dbus_connection_get(bus);
+
+    msg = dbus_message_new_method_call(HOSTNAME_DBUS_INTERFACE,
+                                       HOSTNAME_DBUS_PATH,
+                                       "org.freedesktop.DBus.Properties",
+                                       "Get");
+    dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
+
+    dbus_error_init(&error);
+    if ((reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &error)) == NULL) {
+        pa_log("Failed to send: %s:%s\n", error.name, error.message);
+        dbus_error_free(&error);
+        goto out;
+    }
+
+    dbus_message_iter_init(reply, &args);
+    if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_VARIANT) {
+        pa_log("Incorrect reply type\n");
+        goto out;
+    }
+
+    dbus_message_iter_recurse(&args, &sub);
+
+    if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
+        pa_log("Incorrect value type\n");
+        goto out;
+    }
+
+    dbus_message_iter_get_basic(&sub, &icon_name);
+    icon_name = pa_xstrdup(icon_name);
+
+out:
+    if (reply)
+        dbus_message_unref(reply);
+
+    if (msg)
+        dbus_message_unref(msg);
+
+    return icon_name;
+}
+
 int pa__init(pa_module*m) {
 
     struct userdata *u;
@@ -844,5 +918,6 @@ void pa__done(pa_module*m) {
 
     pa_xfree(u->msg);
     pa_xfree(u->service_name);
+    pa_xfree(u->icon_name);
     pa_xfree(u);
 }
-- 
2.7.4



More information about the pulseaudio-discuss mailing list