diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c index f8ad9212b..6a420b2af 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -15,14 +15,7 @@ #include "../module.h" #include "../pulse-server.h" #include "../server.h" -#include "../../zeroconf-utils/avahi-poll.h" - -#include -#include -#include -#include -#include -#include +#include "../../zeroconf-utils/zeroconf.h" /** \page page_pulse_module_zeroconf_publish Zeroconf Publish * @@ -52,32 +45,17 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define SERVICE_DATA_ID "module-zeroconf-publish.service" -enum service_subtype { - SUBTYPE_HARDWARE, - SUBTYPE_VIRTUAL, - SUBTYPE_MONITOR -}; - struct service { struct spa_list link; struct module_zeroconf_publish_data *userdata; - AvahiEntryGroup *entry_group; - AvahiStringList *txt; struct server *server; - const char *service_type; - enum service_subtype subtype; - - char *name; - bool is_sink; - struct sample_spec ss; struct channel_map cm; struct pw_properties *props; - char service_name[AVAHI_LABEL_MAX]; unsigned published:1; }; @@ -91,8 +69,8 @@ struct module_zeroconf_publish_data { struct spa_hook manager_listener; struct spa_hook impl_listener; - AvahiPoll *avahi_poll; - AvahiClient *client; + struct pw_zeroconf *zeroconf; + struct spa_hook zeroconf_listener; /* lists of services */ struct spa_list pending; @@ -116,47 +94,43 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; -static void get_service_name(struct pw_manager_object *o, char *buf, size_t length) +static void unpublish_service(struct service *s) { - const char *hn, *un, *n; + const char *device; - hn = pw_get_host_name(); - un = pw_get_user_name(); - n = pw_properties_get(o->props, PW_KEY_NODE_DESCRIPTION); + spa_list_remove(&s->link); + spa_list_append(&s->userdata->pending, &s->link); + s->published = false; + s->server = NULL; - snprintf(buf, length, "%s@%s: %s", un, hn, n); + device = pw_properties_get(s->props, "device"); + + pw_log_info("unpublished service: %s", device); + + pw_zeroconf_set_announce(s->userdata->zeroconf, s, NULL); +} + +static void unpublish_all_services(struct module_zeroconf_publish_data *d) +{ + struct service *s; + spa_list_consume(s, &d->published, link) + unpublish_service(s); } static void service_free(struct service *s) { pw_log_debug("service %p: free", s); - if (s->entry_group) - avahi_entry_group_free(s->entry_group); - - if (s->name) - free(s->name); + if (s->published) + unpublish_service(s); pw_properties_free(s->props); - avahi_string_list_free(s->txt); spa_list_remove(&s->link); + /* no need to free, the service is added as custom + * data on the object */ } -static void unpublish_service(struct service *s) -{ - spa_list_remove(&s->link); - spa_list_append(&s->userdata->pending, &s->link); - s->published = false; - s->server = NULL; -} - -static void unpublish_all_services(struct module_zeroconf_publish_data *d) -{ - struct service *s; - - spa_list_consume(s, &d->published, link) - unpublish_service(s); -} +#define PA_CHANNEL_MAP_SNPRINT_MAX (CHANNELS_MAX * 32) static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map) { @@ -188,6 +162,39 @@ static char* channel_map_snprint(char *s, size_t l, const struct channel_map *ma return s; } +static void txt_record_server_data(struct pw_core_info *info, struct pw_properties *props) +{ + struct utsname u; + + spa_assert(info); + + pw_properties_set(props, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); + pw_properties_set(props, "user-name", pw_get_user_name()); + pw_properties_set(props, "fqdn", pw_get_host_name()); + pw_properties_setf(props, "cookie", "0x%08x", info->cookie); + if (uname(&u) >= 0) + pw_properties_setf(props, "uname", "%s %s %s", u.sysname, u.machine, u.release); +} + +static void fill_service_txt(const struct service *s, const struct pw_properties *props) +{ + static const struct mapping { + const char *pw_key, *txt_key; + } mappings[] = { + { PW_KEY_NODE_DESCRIPTION, "description" }, + { PW_KEY_DEVICE_VENDOR_NAME, "vendor-name" }, + { PW_KEY_DEVICE_PRODUCT_NAME, "product-name" }, + { PW_KEY_DEVICE_CLASS, "class" }, + { PW_KEY_DEVICE_FORM_FACTOR, "form-factor" }, + { PW_KEY_DEVICE_ICON_NAME, "icon-name" }, + }; + SPA_FOR_EACH_ELEMENT_VAR(mappings, m) { + const char *value = pw_properties_get(props, m->pw_key); + if (value != NULL) + pw_properties_set(s->props, m->txt_key, value); + } +} + static void fill_service_data(struct module_zeroconf_publish_data *d, struct service *s, struct pw_manager_object *o) { @@ -200,6 +207,10 @@ static void fill_service_data(struct module_zeroconf_publish_data *d, struct ser struct card_info card_info = CARD_INFO_INIT; struct device_info dev_info; uint32_t flags = 0; + const char *service_type, *subtype, *subtype_service[2]; + uint32_t n_subtype = 0; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + if (info == NULL || info->props == NULL) return; @@ -228,19 +239,49 @@ static void fill_service_data(struct module_zeroconf_publish_data *d, struct ser s->ss = dev_info.ss; s->cm = dev_info.map; - s->name = strdup(name); - s->props = pw_properties_copy(o->props); + + s->props = pw_properties_new(NULL, NULL); + + txt_record_server_data(s->userdata->manager->info, s->props); if (is_sink) { - s->is_sink = true; - s->service_type = SERVICE_TYPE_SINK; - s->subtype = flags & SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; + service_type = SERVICE_TYPE_SINK; + if (flags & SINK_HARDWARE) { + subtype = "hardware"; + subtype_service[n_subtype++] = SERVICE_SUBTYPE_SINK_HARDWARE; + } else { + subtype = "virtual"; + subtype_service[n_subtype++] = SERVICE_SUBTYPE_SINK_VIRTUAL; + } } else if (is_source) { - s->is_sink = false; - s->service_type = SERVICE_TYPE_SOURCE; - s->subtype = flags & SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; + service_type = SERVICE_TYPE_SOURCE; + if (flags & SOURCE_HARDWARE) { + subtype = "hardware"; + subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_HARDWARE; + } else { + subtype = "virtual"; + subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_VIRTUAL; + } + subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_NON_MONITOR; } else spa_assert_not_reached(); + + pw_properties_set(s->props, "device", name); + pw_properties_setf(s->props, "rate", "%u", s->ss.rate); + pw_properties_setf(s->props, "channels", "%u", s->ss.channels); + pw_properties_set(s->props, "format", format_id2paname(s->ss.format)); + pw_properties_set(s->props, "channel_map", channel_map_snprint(cm, sizeof(cm), &s->cm)); + pw_properties_set(s->props, "subtype", subtype); + + pw_properties_setf(s->props, "zeroconf.session", "%s@%s: %s", + pw_get_user_name(), pw_get_host_name(), desc); + pw_properties_set(s->props, "zeroconf.service", service_type); + pw_properties_setf(s->props, "zeroconf.subtypes", "[ %s%s%s ]", + n_subtype > 0 ? subtype_service[0] : "", + n_subtype > 1 ? ", " : "", + n_subtype > 1 ? subtype_service[1] : ""); + + fill_service_txt(s, o->props); } static struct service *create_service(struct module_zeroconf_publish_data *d, struct pw_manager_object *o) @@ -252,8 +293,6 @@ static struct service *create_service(struct module_zeroconf_publish_data *d, st return NULL; s->userdata = d; - s->entry_group = NULL; - get_service_name(o, s->service_name, sizeof(s->service_name)); spa_list_append(&d->pending, &s->link); fill_service_data(d, s, o); @@ -263,127 +302,6 @@ static struct service *create_service(struct module_zeroconf_publish_data *d, st return s; } -static AvahiStringList* txt_record_server_data(struct pw_core_info *info, AvahiStringList *l) -{ - const char *t; - struct utsname u; - - spa_assert(info); - - l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); - - t = pw_get_user_name(); - l = avahi_string_list_add_pair(l, "user-name", t); - - if (uname(&u) >= 0) { - char sysname[sizeof(u.sysname) + sizeof(u.machine) + sizeof(u.release)]; - - snprintf(sysname, sizeof(sysname), "%s %s %s", u.sysname, u.machine, u.release); - l = avahi_string_list_add_pair(l, "uname", sysname); - } - - t = pw_get_host_name(); - l = avahi_string_list_add_pair(l, "fqdn", t); - l = avahi_string_list_add_printf(l, "cookie=0x%08x", info->cookie); - - return l; -} - -static void clear_entry_group(struct service *s) -{ - if (s->entry_group == NULL) - return; - - avahi_entry_group_free(s->entry_group); - s->entry_group = NULL; -} - -static void publish_service(struct service *s); - -static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) -{ - struct service *s = userdata; - - spa_assert(s); - if (!s->published) { - pw_log_info("cancel unpublished service: %s", s->service_name); - clear_entry_group(s); - return; - } - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - pw_log_info("established service: %s", s->service_name); - break; - case AVAHI_ENTRY_GROUP_COLLISION: - { - char *t; - - t = avahi_alternative_service_name(s->service_name); - pw_log_info("service name collision: renaming '%s' to '%s'", s->service_name, t); - snprintf(s->service_name, sizeof(s->service_name), "%s", t); - avahi_free(t); - - unpublish_service(s); - publish_service(s); - break; - } - case AVAHI_ENTRY_GROUP_FAILURE: - pw_log_error("failed to establish service '%s': %s", - s->service_name, - avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); - unpublish_service(s); - clear_entry_group(s); - break; - - case AVAHI_ENTRY_GROUP_UNCOMMITED: - case AVAHI_ENTRY_GROUP_REGISTERING: - break; - } -} - -#define PA_CHANNEL_MAP_SNPRINT_MAX (CHANNELS_MAX * 32) - -static AvahiStringList *get_service_txt(const struct service *s) -{ - static const char * const subtype_text[] = { - [SUBTYPE_HARDWARE] = "hardware", - [SUBTYPE_VIRTUAL] = "virtual", - [SUBTYPE_MONITOR] = "monitor" - }; - - static const struct mapping { - const char *pw_key, *txt_key; - } mappings[] = { - { PW_KEY_NODE_DESCRIPTION, "description" }, - { PW_KEY_DEVICE_VENDOR_NAME, "vendor-name" }, - { PW_KEY_DEVICE_PRODUCT_NAME, "product-name" }, - { PW_KEY_DEVICE_CLASS, "class" }, - { PW_KEY_DEVICE_FORM_FACTOR, "form-factor" }, - { PW_KEY_DEVICE_ICON_NAME, "icon-name" }, - }; - - char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; - AvahiStringList *txt = NULL; - - txt = txt_record_server_data(s->userdata->manager->info, txt); - - txt = avahi_string_list_add_pair(txt, "device", s->name); - txt = avahi_string_list_add_printf(txt, "rate=%u", s->ss.rate); - txt = avahi_string_list_add_printf(txt, "channels=%u", s->ss.channels); - txt = avahi_string_list_add_pair(txt, "format", format_id2paname(s->ss.format)); - txt = avahi_string_list_add_pair(txt, "channel_map", channel_map_snprint(cm, sizeof(cm), &s->cm)); - txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[s->subtype]); - - SPA_FOR_EACH_ELEMENT_VAR(mappings, m) { - const char *value = pw_properties_get(s->props, m->pw_key); - if (value != NULL) - txt = avahi_string_list_add_pair(txt, m->txt_key, value); - } - - return txt; -} - static struct server *find_server(struct service *s, int *proto, uint16_t *port) { struct module_zeroconf_publish_data *d = s->userdata; @@ -392,109 +310,47 @@ static struct server *find_server(struct service *s, int *proto, uint16_t *port) spa_list_for_each(server, &impl->servers, link) { if (server->addr.ss_family == AF_INET) { - *proto = AVAHI_PROTO_INET; + *proto = 4; *port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port); return server; } else if (server->addr.ss_family == AF_INET6) { - *proto = AVAHI_PROTO_INET6; + *proto = 6; *port = ntohs(((struct sockaddr_in6*) &server->addr)->sin6_port); return server; } } - return NULL; } static void publish_service(struct service *s) { struct module_zeroconf_publish_data *d = s->userdata; - int proto; + int proto, res; uint16_t port; - struct server *server = find_server(s, &proto, &port); + const char *device; + if (!server) return; + device = pw_properties_get(s->props, "device"); + pw_log_debug("found server:%p proto:%d port:%d", server, proto, port); - if (!d->client || avahi_client_get_state(d->client) != AVAHI_CLIENT_S_RUNNING) + pw_properties_setf(s->props, "zeroconf.proto", "%d", proto); + pw_properties_setf(s->props, "zeroconf.port", "%d", port); + + if ((res = pw_zeroconf_set_announce(s->userdata->zeroconf, s, &s->props->dict)) < 0) { + pw_log_error("failed to announce service %s: %s", device, spa_strerror(res)); return; - - s->published = true; - if (!s->entry_group) { - s->entry_group = avahi_entry_group_new(d->client, service_entry_group_callback, s); - if (s->entry_group == NULL) { - pw_log_error("avahi_entry_group_new(): %s", - avahi_strerror(avahi_client_errno(d->client))); - goto error; - } - } else { - avahi_entry_group_reset(s->entry_group); - } - - if (s->txt == NULL) - s->txt = get_service_txt(s); - - if (avahi_entry_group_add_service_strlst( - s->entry_group, - AVAHI_IF_UNSPEC, proto, - 0, - s->service_name, - s->service_type, - NULL, - NULL, - port, - s->txt) < 0) { - pw_log_error("avahi_entry_group_add_service_strlst(): %s", - avahi_strerror(avahi_client_errno(d->client))); - goto error; - } - - if (avahi_entry_group_add_service_subtype( - s->entry_group, - AVAHI_IF_UNSPEC, proto, - 0, - s->service_name, - s->service_type, - NULL, - s->is_sink ? (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) : - (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) { - - pw_log_error("avahi_entry_group_add_service_subtype(): %s", - avahi_strerror(avahi_client_errno(d->client))); - goto error; - } - - if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) { - if (avahi_entry_group_add_service_subtype( - s->entry_group, - AVAHI_IF_UNSPEC, proto, - 0, - s->service_name, - SERVICE_TYPE_SOURCE, - NULL, - SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) { - pw_log_error("avahi_entry_group_add_service_subtype(): %s", - avahi_strerror(avahi_client_errno(d->client))); - goto error; - } - } - - if (avahi_entry_group_commit(s->entry_group) < 0) { - pw_log_error("avahi_entry_group_commit(): %s", - avahi_strerror(avahi_client_errno(d->client))); - goto error; } spa_list_remove(&s->link); spa_list_append(&d->published, &s->link); + s->published = true; s->server = server; - pw_log_info("created service: %s", s->service_name); - return; - -error: - s->published = false; + pw_log_info("published service: %s", device); return; } @@ -506,62 +362,6 @@ static void publish_pending(struct module_zeroconf_publish_data *data) publish_service(s); } -static void clear_pending_entry_groups(struct module_zeroconf_publish_data *data) -{ - struct service *s; - - spa_list_for_each(s, &data->pending, link) - clear_entry_group(s); -} - -static void client_callback(AvahiClient *c, AvahiClientState state, void *d) -{ - struct module_zeroconf_publish_data *data = d; - - spa_assert(c); - spa_assert(data); - - data->client = c; - - switch (state) { - case AVAHI_CLIENT_S_RUNNING: - pw_log_info("the avahi daemon is up and running"); - publish_pending(data); - break; - case AVAHI_CLIENT_S_COLLISION: - pw_log_error("host name collision"); - unpublish_all_services(d); - break; - case AVAHI_CLIENT_FAILURE: - { - int err = avahi_client_errno(data->client); - - pw_log_error("avahi client failure: %s", avahi_strerror(err)); - - unpublish_all_services(data); - clear_pending_entry_groups(data); - avahi_client_free(data->client); - data->client = NULL; - - if (err == AVAHI_ERR_DISCONNECTED) { - data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, data, &err); - if (data->client == NULL) - pw_log_error("failed to create avahi client: %s", avahi_strerror(err)); - } - - if (data->client == NULL) - module_schedule_unload(data->module); - - break; - } - case AVAHI_CLIENT_CONNECTING: - pw_log_info("connecting to the avahi daemon..."); - break; - default: - break; - } -} - static void manager_removed(void *d, struct pw_manager_object *o) { if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o)) @@ -624,7 +424,6 @@ static void impl_server_stopped(void *data, struct server *server) if (s->server == server) unpublish_service(s); } - publish_pending(d); } @@ -634,10 +433,18 @@ static const struct impl_events impl_events = { .server_stopped = impl_server_stopped, }; +static void on_zeroconf_error(void *data, int err, const char *message) +{ + pw_log_error("got zeroconf error %d: %s", err, message); +} +static const struct pw_zeroconf_events zeroconf_events = { + PW_VERSION_ZEROCONF_EVENTS, + .error = on_zeroconf_error, +}; + static int module_zeroconf_publish_load(struct module *module) { struct module_zeroconf_publish_data *data = module->user_data; - int error; data->core = pw_context_connect(module->impl->context, NULL, 0); if (data->core == NULL) { @@ -649,24 +456,22 @@ static int module_zeroconf_publish_load(struct module *module) &data->core_listener, &core_events, data); - data->avahi_poll = pw_avahi_poll_new(module->impl->context); - - data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL, - client_callback, data, &error); - if (!data->client) { - pw_log_error("failed to create avahi client: %s", avahi_strerror(error)); - return -errno; - } - data->manager = pw_manager_new(data->core); if (data->manager == NULL) { pw_log_error("failed to create pipewire manager: %m"); return -errno; } - pw_manager_add_listener(data->manager, &data->manager_listener, &manager_events, data); + data->zeroconf = pw_zeroconf_new(module->impl->context, NULL); + if (!data->zeroconf) { + pw_log_error("failed to create zeroconf: %m"); + return -errno; + } + pw_zeroconf_add_listener(data->zeroconf, &data->zeroconf_listener, + &zeroconf_events, data); + impl_add_listener(module->impl, &data->impl_listener, &impl_events, data); return 0; @@ -684,22 +489,18 @@ static int module_zeroconf_publish_unload(struct module *module) spa_list_consume(s, &d->pending, link) service_free(s); - if (d->client) - avahi_client_free(d->client); - - if (d->avahi_poll) - pw_avahi_poll_free(d->avahi_poll); - + if (d->zeroconf) { + spa_hook_remove(&d->zeroconf_listener); + pw_zeroconf_destroy(d->zeroconf); + } if (d->manager != NULL) { spa_hook_remove(&d->manager_listener); pw_manager_destroy(d->manager); } - if (d->core != NULL) { spa_hook_remove(&d->core_listener); pw_core_disconnect(d->core); } - return 0; }