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 ++++++++++++++++++++++++++++++++- src/modules/raop/module-raop-discover.c | 2 +- 4 files changed, 89 insertions(+), 5 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); } diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c index f083044..465e9cb 100644 --- a/src/modules/raop/module-raop-discover.c +++ b/src/modules/raop/module-raop-discover.c @@ -341,7 +341,7 @@ int pa__init(pa_module*m) { u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { - pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); goto fail; } -- 2.7.4