#include "spa/utils/list.h" #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "pipewire" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../module.h" #include "../particle.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "../yml.h" #define ARRAY_LENGTH(x) (sizeof((x)) / sizeof((x)[0])) /* clang-format off */ #define X_FREE_SET(t, v) do { free((t)); (t) = (v); } while (0) /* clang-format on */ #define X_STRDUP(s) ((s) != NULL ? strdup((s)) : NULL) struct output_informations { /* internal */ uint32_t device_id; uint32_t card_profile_device_id; /* information */ bool muted; uint16_t linear_volume; /* classic volume */ uint16_t cubic_volume; /* volume a la pulseaudio */ char *name; char *description; char *form_factor; /* headset, headphone, speaker, ..., can be null */ char *bus; /* alsa, bluetooth, etc */ char *icon; }; static struct output_informations const output_informations_null; static void output_informations_destroy(struct output_informations *output_informations) { free(output_informations->name); free(output_informations->description); free(output_informations->icon); free(output_informations->form_factor); free(output_informations->bus); } struct data; struct private { struct particle *label; struct data *data; int left_spacing; int right_spacing; /* pipewire related */ struct output_informations sink_informations; struct output_informations source_informations; }; /* This struct is needed because when param event occur, the function * `node_events_param` will receive the corresponding event about the node * but there's no simple way of knowing from which node the event come from */ struct node_data { struct data *data; /* otherwise is_source */ bool is_sink; }; /* struct data */ struct node; struct data { /* yambar module */ struct module *module; char *target_sink; char *target_source; struct node *binded_sink; struct node *binded_source; struct node_data node_data_sink; struct node_data node_data_source; /* proxies */ void *metadata; void *node_sink; void *node_source; /* main struct */ struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; /* listener */ struct spa_hook registry_listener; struct spa_hook core_listener; struct spa_hook metadata_listener; struct spa_hook node_sink_listener; struct spa_hook node_source_listener; /* list */ struct spa_list node_list; struct spa_list device_list; int sync; }; /* struct Route */ struct route { struct device *device; struct spa_list link; enum spa_direction direction; /* direction */ int profile_device_id; /* device */ char *form_factor; /* info.type */ char *icon_name; /* info.icon-name */ }; static void route_free(struct route *route) { free(route->form_factor); free(route->icon_name); spa_list_remove(&route->link); free(route); } /* struct Device */ struct device { struct data *data; struct spa_list link; uint32_t id; struct spa_list routes; void *proxy; struct spa_hook listener; }; static void device_free(struct device *device, struct data *data) { struct route *route = NULL; spa_list_consume(route, &device->routes, link) route_free(route); spa_hook_remove(&device->listener); pw_proxy_destroy((struct pw_proxy *)device->proxy); spa_list_remove(&device->link); free(device); } static struct route * route_find_or_create(struct device *device, uint32_t profile_device_id) { struct route *route = NULL; spa_list_for_each(route, &device->routes, link) { if (route->profile_device_id == profile_device_id) return route; } /* route not found, let's create it */ route = calloc(1, sizeof(struct route)); assert(route != NULL); route->device = device; route->profile_device_id = profile_device_id; spa_list_append(&device->routes, &route->link); return route; } struct node { struct spa_list link; uint32_t id; char *name; }; /* struct node */ static struct route * node_find_route(struct data *data, bool is_sink) { struct private *private = data->module->private; struct output_informations *output_informations = NULL; if (is_sink) { if (data->node_sink == NULL) return NULL; output_informations = &private->sink_informations; } else { if (data->node_source == NULL) return NULL; output_informations = &private->source_informations; } struct device *device = NULL; spa_list_for_each(device, &data->device_list, link) { if (device->id != output_informations->device_id) continue; struct route *route = NULL; spa_list_for_each(route, &device->routes, link) { if (route->profile_device_id == output_informations->card_profile_device_id) return route; } } return NULL; } static void node_unhook_binded_node(struct data *data, bool is_sink) { struct private *private = data->module->private; struct node **target_node = NULL; struct spa_hook *target_listener = NULL; void **target_proxy = NULL; struct output_informations *output_informations = NULL; if (is_sink) { target_node = &data->binded_sink; target_listener = &data->node_sink_listener; target_proxy = &data->node_sink; output_informations = &private->sink_informations; } else { target_node = &data->binded_source; target_listener = &data->node_source_listener; target_proxy = &data->node_source; output_informations = &private->source_informations; } if (*target_node == NULL) return; spa_hook_remove(target_listener); pw_proxy_destroy(*target_proxy); *target_node = NULL; *target_proxy = NULL; output_informations_destroy(output_informations); *output_informations = output_informations_null; } static void node_free(struct node *node, struct data *data) { if (data->binded_sink == node) node_unhook_binded_node(data, true); else if (data->binded_source == node) node_unhook_binded_node(data, false); spa_list_remove(&node->link); free(node->name); free(node); } /* Device events */ static void device_events_info(void *userdata, const struct pw_device_info *info) { struct device *device = userdata; /* We only want the "Route" param, which is in Params */ if (!(info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS)) return; for (size_t i = 0; i < info->n_params; ++i) { if (info->params[i].id == SPA_PARAM_Route) { pw_device_enum_params(device->proxy, 0, info->params[i].id, 0, -1, NULL); break; } } } static void device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { /* We should only receive ParamRoute */ assert(spa_pod_is_object_type(param, SPA_TYPE_OBJECT_ParamRoute)); struct route data = {0}; struct spa_pod_prop const *prop = NULL; /* device must be present otherwise I can't do anything with the data */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_device); if (prop == NULL) return; spa_pod_get_int(&prop->value, &data.profile_device_id); /* same for direction, required too */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_direction); if (prop == NULL) return; char const *direction = NULL; spa_pod_get_string(&prop->value, &direction); if (spa_streq(direction, "Output")) data.direction = SPA_DIRECTION_OUTPUT; else data.direction = SPA_DIRECTION_INPUT; /* same for info, it's required */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_info); if (prop == NULL) return; struct spa_pod *iter = NULL; char const *header = NULL; SPA_POD_STRUCT_FOREACH(&prop->value, iter) { /* no previous header */ if (header == NULL) { /* headers are always string */ if (spa_pod_is_string(iter)) spa_pod_get_string(iter, &header); /* otherwise it's the first iteration (number of elements in the struct) */ continue; } /* Values needed: * - (string) device.icon_name [icon_name] * - (string) port.type [form_factor] */ if (spa_pod_is_string(iter)) { if (spa_streq(header, "device.icon_name")) spa_pod_get_string(iter, (char const **)&data.icon_name); else if (spa_streq(header, "port.type")) { spa_pod_get_string(iter, (char const **)&data.form_factor); } } header = NULL; } struct device *device = userdata; struct route *route = route_find_or_create(device, data.profile_device_id); X_FREE_SET(route->form_factor, X_STRDUP(data.form_factor)); X_FREE_SET(route->icon_name, X_STRDUP(data.icon_name)); route->direction = data.direction; /* set missing information if possible */ struct private *private = device->data->module->private; struct node *binded_node = NULL; struct output_informations *output_informations = NULL; if (route->direction == SPA_DIRECTION_INPUT) { binded_node = private->data->binded_source; output_informations = &private->source_informations; } else { binded_node = private->data->binded_sink; output_informations = &private->sink_informations; } /* Node not binded */ if (binded_node == NULL) return; /* Node's device is the same as route's device */ if (output_informations->device_id != route->device->id) return; /* Route is not the Node's device route */ if (output_informations->card_profile_device_id != route->profile_device_id) return; /* Update missing information */ X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor)); X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name)); device->data->module->bar->refresh(device->data->module->bar); } static struct pw_device_events const device_events = { PW_VERSION_DEVICE_EVENTS, .info = device_events_info, .param = device_events_param, }; /* Node events */ static void node_events_info(void *userdata, struct pw_node_info const *info) { struct node_data *node_data = userdata; struct data *data = node_data->data; struct private *private = data->module->private; if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { /* We only need the Props param, so let's try to find it */ for (size_t i = 0; i < info->n_params; ++i) { if (info->params[i].id == SPA_PARAM_Props) { void *target_node = (node_data->is_sink ? data->node_sink : data->node_source); /* Found it, will emit a param event, the param will then be handled * in node_events_param */ pw_node_enum_params(target_node, 0, info->params[i].id, 0, -1, NULL); break; } } } if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { struct output_informations *output_informations = (node_data->is_sink ? &private->sink_informations : &private->source_informations); struct spa_dict_item const *item = NULL; item = spa_dict_lookup_item(info->props, "node.name"); X_FREE_SET(output_informations->name, item != NULL ? X_STRDUP(item->value) : NULL); item = spa_dict_lookup_item(info->props, "node.description"); X_FREE_SET(output_informations->description, item != NULL ? X_STRDUP(item->value) : NULL); item = spa_dict_lookup_item(info->props, "device.id"); if (item != NULL) { uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->device_id = value; } else { output_informations->device_id = 0; } item = spa_dict_lookup_item(info->props, "card.profile.device"); if (item != NULL) { uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->card_profile_device_id = value; } else { output_informations->card_profile_device_id = 0; } /* Device's information has an more important priority than node's information */ /* icon_name */ struct route *route = node_find_route(data, node_data->is_sink); if (route != NULL && route->icon_name != NULL) X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name)); else { item = spa_dict_lookup_item(info->props, "device.icon-name"); X_FREE_SET(output_informations->icon, item != NULL ? X_STRDUP(item->value) : NULL); } /* form_factor */ if (route != NULL && route->form_factor != NULL) X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor)); else { item = spa_dict_lookup_item(info->props, "device.form-factor"); X_FREE_SET(output_informations->form_factor, item != NULL ? X_STRDUP(item->value) : NULL); } item = spa_dict_lookup_item(info->props, "device.bus"); X_FREE_SET(output_informations->bus, item != NULL ? X_STRDUP(item->value) : NULL); data->module->bar->refresh(data->module->bar); } } static void node_events_param(void *userdata, __attribute__((unused)) int seq, __attribute__((unused)) uint32_t id, __attribute__((unused)) uint32_t index, __attribute__((unused)) uint32_t next, const struct spa_pod *param) { struct node_data *node_data = userdata; struct data *data = node_data->data; struct private *private = data->module->private; struct output_informations *output_informations = (node_data->is_sink ? &private->sink_informations : &private->source_informations); struct spa_pod_prop const *prop = NULL; prop = spa_pod_find_prop(param, NULL, SPA_PROP_mute); if (prop != NULL) { bool value = false; spa_pod_get_bool(&prop->value, &value); output_informations->muted = value; } prop = spa_pod_find_prop(param, NULL, SPA_PROP_channelVolumes); if (prop != NULL) { uint32_t n_values = 0; float *values = spa_pod_get_array(&prop->value, &n_values); float total = 0.0f; /* Array can be empty some times, assume that values have not changed */ if (n_values != 0) { for (uint32_t i = 0; i < n_values; ++i) total += values[i]; float base_volume = total / n_values; output_informations->linear_volume = roundf(base_volume * 100); output_informations->cubic_volume = roundf(cbrtf(base_volume) * 100); } } data->module->bar->refresh(data->module->bar); } static struct pw_node_events const node_events = { PW_VERSION_NODE_EVENTS, .info = node_events_info, .param = node_events_param, }; /* Metadata events */ static int metadata_property(void *userdata, __attribute__((unused)) uint32_t id, char const *key, __attribute__((unused)) char const *type, char const *value) { struct data *data = userdata; bool is_sink = false; // true for source mode char **target_name = NULL; /* We only want default.audio.sink or default.audio.source */ if (spa_streq(key, "default.audio.sink")) { is_sink = true; target_name = &data->target_sink; } else if (spa_streq(key, "default.audio.source")) { is_sink = false; /* just to be explicit */ target_name = &data->target_source; } else return 0; /* Value is NULL when the profile is set to `off`. */ if (value == NULL) { node_unhook_binded_node(data, is_sink); free(*target_name); *target_name = NULL; data->module->bar->refresh(data->module->bar); return 0; } struct json_object *json = json_tokener_parse(value); struct json_object_iterator json_it = json_object_iter_begin(json); struct json_object_iterator json_it_end = json_object_iter_end(json); while (!json_object_iter_equal(&json_it, &json_it_end)) { char const *key = json_object_iter_peek_name(&json_it); if (!spa_streq(key, "name")) { json_object_iter_next(&json_it); continue; } /* Found name */ struct json_object *value = json_object_iter_peek_value(&json_it); assert(json_object_is_type(value, json_type_string)); char const *name = json_object_get_string(value); /* `auto_null` is the same as `value == NULL` see lines above. */ if (spa_streq(name, "auto_null")) { node_unhook_binded_node(data, is_sink); free(*target_name); *target_name = NULL; data->module->bar->refresh(data->module->bar); break; } /* target_name is the same */ if (spa_streq(name, *target_name)) break; /* Unhook the binded_node */ node_unhook_binded_node(data, is_sink); /* Update the target */ free(*target_name); *target_name = strdup(name); /* Sync the core, core_events_done will then try to bind the good node */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); break; } json_object_put(json); return 0; } static struct pw_metadata_events const metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property, }; /* Registry events */ static void registry_event_global(void *userdata, uint32_t id, __attribute__((unused)) uint32_t permissions, char const *type, __attribute__((unused)) uint32_t version, struct spa_dict const *props) { struct data *data = userdata; /* New device */ if (spa_streq(type, PW_TYPE_INTERFACE_Device)) { struct device *device = calloc(1, sizeof(struct device)); assert(device != NULL); device->data = data; device->id = id; spa_list_init(&device->routes); device->proxy = pw_registry_bind(data->registry, id, type, PW_VERSION_DEVICE, 0); assert(device->proxy != NULL); pw_device_add_listener(device->proxy, &device->listener, &device_events, device); spa_list_append(&data->device_list, &device->link); } /* New node */ else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { /* Fill a new node */ struct node *node = calloc(1, sizeof(struct node)); assert(node != NULL); node->id = id; node->name = strdup(spa_dict_lookup(props, PW_KEY_NODE_NAME)); /* Store it */ spa_list_append(&data->node_list, &node->link); } /* New metadata */ else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { /* A metadata has already been bind */ if (data->metadata != NULL) return; /* Target only metadata which has "default" key */ char const *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); if (name == NULL || !spa_streq(name, "default")) return; /* Bind metadata */ data->metadata = pw_registry_bind(data->registry, id, type, PW_VERSION_METADATA, 0); assert(data->metadata != NULL); pw_metadata_add_listener(data->metadata, &data->metadata_listener, &metadata_events, data); } /* `core_events_done` will then try to bind the good node */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); } static void registry_event_global_remove(void *userdata, uint32_t id) { struct data *data = userdata; /* Try to find a node with the same `id` */ struct node *node = NULL, *node_temp = NULL; spa_list_for_each_safe(node, node_temp, &data->node_list, link) { if (node->id == id) { node_free(node, data); return; } } /* No node with this `id` maybe it's a device */ struct device *device = NULL, *device_temp = NULL; spa_list_for_each_safe(device, device_temp, &data->device_list, link) { if (device->id == id) { device_free(device, data); return; } } } static struct pw_registry_events const registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void try_to_bind_node(struct node_data *node_data, char const *target_name, struct node **target_node, void **target_proxy, struct spa_hook *target_listener) { /* profile deactivated */ if (target_name == NULL) return; struct data *data = node_data->data; struct node *node = NULL; spa_list_for_each(node, &data->node_list, link) { if (!spa_streq(target_name, node->name)) continue; /* Found good node */ *target_node = node; *target_proxy = pw_registry_bind(data->registry, node->id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); assert(*target_proxy != NULL); pw_node_add_listener(*target_proxy, target_listener, &node_events, node_data); break; } } /* Core events */ static void core_events_done(void *userdata, uint32_t id, int seq) { struct data *data = userdata; if (id != PW_ID_CORE) return; /* Not our seq */ if (data->sync != seq) return; /* Sync ended, try to bind the node which has the targeted sink or the targeted source */ /* Node sink not binded and target_sink is set */ if (data->binded_sink == NULL && data->target_sink != NULL) try_to_bind_node(&data->node_data_sink, data->target_sink, &data->binded_sink, &data->node_sink, &data->node_sink_listener); /* Node source not binded and target_source is set */ if (data->binded_source == NULL && data->target_source != NULL) try_to_bind_node(&data->node_data_source, data->target_source, &data->binded_source, &data->node_source, &data->node_source_listener); } static void core_events_error(void *userdata, uint32_t id, int seq, int res, char const *message) { pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) { struct data *data = userdata; pw_main_loop_quit(data->loop); } } static struct pw_core_events const core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_done, .error = core_events_error, }; /* init, deinit */ static struct data * pipewire_init(struct module *module) { pw_init(NULL, NULL); /* Data */ struct data *data = calloc(1, sizeof(struct data)); assert(data != NULL); spa_list_init(&data->node_list); spa_list_init(&data->device_list); /* Main loop */ data->loop = pw_main_loop_new(NULL); if (data->loop == NULL) { LOG_ERR("failed to instantiate main loop"); goto err; } /* Context */ data->context = pw_context_new(pw_main_loop_get_loop(data->loop), NULL, 0); if (data->context == NULL) { LOG_ERR("failed to instantiate pipewire context"); goto err; } /* Core */ data->core = pw_context_connect(data->context, NULL, 0); if (data->core == NULL) { LOG_ERR("failed to connect to pipewire"); goto err; } pw_core_add_listener(data->core, &data->core_listener, &core_events, data); /* Registry */ data->registry = pw_core_get_registry(data->core, PW_VERSION_REGISTRY, 0); if (data->registry == NULL) { LOG_ERR("failed to get core registry"); goto err; } pw_registry_add_listener(data->registry, &data->registry_listener, ®istry_events, data); /* Sync */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); data->module = module; /* node_events_param_data */ data->node_data_sink.data = data; data->node_data_sink.is_sink = true; data->node_data_source.data = data; data->node_data_source.is_sink = false; return data; err: if (data->registry != NULL) pw_proxy_destroy((struct pw_proxy *)data->registry); if (data->core != NULL) pw_core_disconnect(data->core); if (data->context != NULL) pw_context_destroy(data->context); if (data->loop != NULL) pw_main_loop_destroy(data->loop); free(data); return NULL; } static void pipewire_deinit(struct data *data) { if (data == NULL) return; struct node *node = NULL; spa_list_consume(node, &data->node_list, link) node_free(node, data); struct device *device = NULL; spa_list_consume(device, &data->device_list, link) device_free(device, data); if (data->metadata) pw_proxy_destroy((struct pw_proxy *)data->metadata); spa_hook_remove(&data->registry_listener); pw_proxy_destroy((struct pw_proxy *)data->registry); spa_hook_remove(&data->core_listener); spa_hook_remove(&data->metadata_listener); pw_core_disconnect(data->core); pw_context_destroy(data->context); pw_main_loop_destroy(data->loop); free(data->target_sink); free(data->target_source); pw_deinit(); } static void destroy(struct module *module) { struct private *private = module->private; pipewire_deinit(private->data); private->label->destroy(private->label); output_informations_destroy(&private->sink_informations); output_informations_destroy(&private->source_informations); free(private); module_default_destroy(module); } static char const * description(const struct module *module) { return "pipewire"; } static struct exposable * content(struct module *module) { struct private *private = module->private; if (private->data == NULL) return dynlist_exposable_new(NULL, 0, 0, 0); mtx_lock(&module->lock); struct exposable *exposables[2]; size_t exposables_length = ARRAY_LENGTH(exposables); struct output_informations const *output_informations = NULL; /* sink */ output_informations = (private->data->target_sink == NULL ? &output_informations_null : &private->sink_informations); struct tag_set sink_tag_set = (struct tag_set){ .tags = (struct tag *[]){ tag_new_string(module, "type", "sink"), tag_new_string(module, "name", output_informations->name), tag_new_string(module, "description", output_informations->description), tag_new_string(module, "icon", output_informations->icon), tag_new_string(module, "form_factor", output_informations->form_factor), tag_new_string(module, "bus", output_informations->bus), tag_new_bool(module, "muted", output_informations->muted), tag_new_int_range(module, "linear_volume", output_informations->linear_volume, 0, 100), tag_new_int_range(module, "cubic_volume", output_informations->cubic_volume, 0, 100), }, .count = 9, }; /* source */ output_informations = (private->data->target_source == NULL ? &output_informations_null : &private->source_informations); struct tag_set source_tag_set = (struct tag_set){ .tags = (struct tag *[]){ tag_new_string(module, "type", "source"), tag_new_string(module, "name", output_informations->name), tag_new_string(module, "description", output_informations->description), tag_new_string(module, "icon", output_informations->icon), tag_new_string(module, "form_factor", output_informations->form_factor), tag_new_string(module, "bus", output_informations->bus), tag_new_bool(module, "muted", output_informations->muted), tag_new_int_range(module, "linear_volume", output_informations->linear_volume, 0, 100), tag_new_int_range(module, "cubic_volume", output_informations->cubic_volume, 0, 100), }, .count = 9, }; exposables[0] = private->label->instantiate(private->label, &sink_tag_set); exposables[1] = private->label->instantiate(private->label, &source_tag_set); tag_set_destroy(&sink_tag_set); tag_set_destroy(&source_tag_set); mtx_unlock(&module->lock); return dynlist_exposable_new(exposables, exposables_length, private->left_spacing, private->right_spacing); } static int run(struct module *module) { struct private *private = module->private; if (private->data == NULL) return 1; struct pw_loop *pw_loop = pw_main_loop_get_loop(private->data->loop); struct pollfd pollfds[] = { /* abort_fd */ (struct pollfd){.fd = module->abort_fd, .events = POLLIN}, /* pipewire */ (struct pollfd){.fd = pw_loop_get_fd(pw_loop), .events = POLLIN}, }; while (true) { if (poll(pollfds, ARRAY_LENGTH(pollfds), -1) == -1) { if (errno == EINTR) continue; LOG_ERRNO("Unable to poll: %s", strerror(errno)); break; } /* abort_fd */ if (pollfds[0].revents & POLLIN) break; /* pipewire */ if (!(pollfds[1].revents & POLLIN)) /* issue happened */ break; int result = pw_loop_iterate(pw_loop, 0); if (result < 0) { LOG_ERRNO("Unable to iterate pipewire loop: %s", spa_strerror(result)); break; } } return 0; } static struct module * pipewire_new(struct particle *label, int left_spacing, int right_spacing) { struct private *private = calloc(1, sizeof(struct private)); assert(private != NULL); private->label = label; private->left_spacing = left_spacing; private->right_spacing = right_spacing; struct module *module = module_common_new(); module->private = private; module->run = &run; module->destroy = &destroy; module->content = &content; module->description = &description; private->data = pipewire_init(module); return module; } static struct module * from_conf(struct yml_node const *node, struct conf_inherit inherited) { struct yml_node const *content = yml_get_value(node, "content"); struct yml_node const *spacing = yml_get_value(node, "spacing"); struct yml_node const *left_spacing = yml_get_value(node, "left-spacing"); struct yml_node const *right_spacing = yml_get_value(node, "right-spacing"); int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int right = spacing != NULL ? yml_value_as_int(spacing) : right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; return pipewire_new(conf_to_particle(content, inherited), left, right); } static bool verify_conf(keychain_t *keychain, struct yml_node const *node) { static struct attr_info const attrs[] = { {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(keychain, node, attrs); } struct module_iface const module_pipewire_iface = { .from_conf = &from_conf, .verify_conf = &verify_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern struct module_iface const iface __attribute__((weak, alias("module_pipewire_iface"))); #endif