diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 3e00578ed..7fa351d2c 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -7,7 +7,7 @@ load-module libpipewire-module-spa-monitor v4l2/libspa-v4l2 v4l2-monitor v4l2 load-module libpipewire-module-spa-monitor bluez5/libspa-bluez5 bluez5-monitor bluez5 #load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow load-module libpipewire-module-client-node -load-module libpipewire-module-flatpak +load-module libpipewire-module-access load-module libpipewire-module-audio-dsp load-module libpipewire-module-link-factory exec build/src/examples/media-session diff --git a/src/examples/media-session.c b/src/examples/media-session.c index 50d93281c..d20b94822 100644 --- a/src/examples/media-session.c +++ b/src/examples/media-session.c @@ -64,6 +64,7 @@ struct impl { struct pw_map globals; + struct spa_list client_list; struct spa_list node_list; struct spa_list session_list; uint32_t seq; @@ -78,6 +79,15 @@ struct object { struct spa_hook listener; }; +struct client { + struct object obj; + + struct spa_list l; + + struct spa_hook listener; + struct pw_client_info *info; +}; + struct node { struct object obj; @@ -649,6 +659,73 @@ handle_port(struct impl *impl, uint32_t id, uint32_t parent_id, uint32_t type, return 0; } +static void client_event_info(void *object, struct pw_client_info *info) +{ + struct client *c = object; + int i; + + pw_log_debug(NAME" %p: info for client %d", c->obj.impl, c->obj.id); + c->info = pw_client_info_update(c->info, info); + for (i = 0; i < info->props->n_items; i++) + pw_log_debug(NAME" %p: %s = %s", c, + info->props->items[i].key, + info->props->items[i].value); + +} + +static const struct pw_client_proxy_events client_events = { + PW_VERSION_CLIENT_PROXY_EVENTS, + .info = client_event_info, +}; + +static void client_proxy_destroy(void *data) +{ + struct client *c = data; + + pw_log_debug(NAME " %p: proxy destroy client %d", c->obj.impl, c->obj.id); + + spa_list_remove(&c->l); + if (c->info) + pw_client_info_free(c->info); +} + +static const struct pw_proxy_events client_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = client_proxy_destroy, +}; + +static int +handle_client(struct impl *impl, uint32_t id, uint32_t parent_id, + uint32_t type, const struct spa_dict *props) +{ + struct pw_proxy *p; + struct client *client; + struct spa_dict_item items[2]; + + p = pw_registry_proxy_bind(impl->registry_proxy, + id, type, PW_VERSION_CLIENT, + sizeof(struct client)); + + client = pw_proxy_get_user_data(p); + client->obj.impl = impl; + client->obj.id = id; + client->obj.parent_id = parent_id; + client->obj.type = type; + client->obj.proxy = p; + + pw_proxy_add_listener(p, &client->obj.listener, &client_proxy_events, client); + pw_proxy_add_proxy_listener(p, &client->listener, &client_events, client); + add_object(impl, &client->obj); + spa_list_append(&impl->client_list, &client->l); + + items[0].key = PW_CORE_PROXY_PERMISSIONS_DEFAULT; + items[0].value = "rwx"; + + pw_client_proxy_update_permissions((struct pw_client_proxy*)p, + &SPA_DICT_INIT(items, 1)); + return 0; +} + static void registry_global(void *data,uint32_t id, uint32_t parent_id, uint32_t permissions, uint32_t type, uint32_t version, @@ -659,6 +736,10 @@ registry_global(void *data,uint32_t id, uint32_t parent_id, pw_log_debug(NAME " %p: new global '%d'", impl, id); switch (type) { + case PW_TYPE_INTERFACE_Client: + handle_client(impl, id, parent_id, type, props); + break; + case PW_TYPE_INTERFACE_Node: handle_node(impl, id, parent_id, type, props); break; @@ -931,8 +1012,19 @@ static int rescan_node(struct impl *impl, struct node *node) find.exclusive = exclusive; spa_list_for_each(session, &impl->session_list, l) find_session(&find, session); - if (find.sess == NULL) + + if (find.sess == NULL) { + struct client *client; + + pw_log_warn(NAME " %p: no session found for %d", impl, node->obj.id); + + client = find_object(impl, node->obj.parent_id); + if (client && client->obj.type == PW_TYPE_INTERFACE_Client) { + pw_client_proxy_error((struct pw_client_proxy*)client->obj.proxy, + node->obj.id, ENOENT, "no session available"); + } return -ENOENT; + } session = find.sess; @@ -1139,8 +1231,9 @@ int main(int argc, char *argv[]) pw_map_init(&impl.globals, 64, 64); - spa_list_init(&impl.session_list); + spa_list_init(&impl.client_list); spa_list_init(&impl.node_list); + spa_list_init(&impl.session_list); clock_gettime(CLOCK_MONOTONIC, &impl.now); diff --git a/src/modules/meson.build b/src/modules/meson.build index 9020f1b8e..4c891875b 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -5,15 +5,15 @@ pipewire_module_c_args = [ '-D_GNU_SOURCE', ] -if dbus_dep.found() -pipewire_module_flatpak = shared_library('pipewire-module-flatpak', [ 'module-flatpak.c' ], +pipewire_module_access = shared_library('pipewire-module-access', [ 'module-access.c' ], c_args : pipewire_module_c_args, include_directories : [configinc, spa_inc], install : true, install_dir : modules_install_dir, - dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], + dependencies : [mathlib, dl_lib, pipewire_dep], ) +if dbus_dep.found() pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rtkit.c' ], c_args : pipewire_module_c_args, include_directories : [configinc, spa_inc], diff --git a/src/modules/module-access.c b/src/modules/module-access.c new file mode 100644 index 000000000..4ead3013d --- /dev/null +++ b/src/modules/module-access.c @@ -0,0 +1,197 @@ +/* PipeWire + * Copyright (C) 2018 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "pipewire/core.h" +#include "pipewire/interfaces.h" +#include "pipewire/link.h" +#include "pipewire/log.h" +#include "pipewire/module.h" +#include "pipewire/utils.h" + +static const struct spa_dict_item module_props[] = { + { PW_MODULE_PROP_AUTHOR, "Wim Taymans " }, + { PW_MODULE_PROP_DESCRIPTION, "Perform access check" }, + { PW_MODULE_PROP_VERSION, PACKAGE_VERSION }, +}; + +struct impl { + struct pw_core *core; + struct pw_properties *properties; + + struct spa_hook core_listener; + struct spa_hook module_listener; +}; + +static int check_cmdline(struct pw_client *client, const struct ucred *ucred, const char *str) +{ + char path[2048]; + int fd; + + sprintf(path, "/proc/%u/cmdline", ucred->pid); + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (read(fd, path, 1024) <= 0) + return -EIO; + + if (strcmp(path, str) == 0) + return 1; + + return 0; +} + +static int check_sandboxed(struct pw_client *client) +{ + char root_path[2048]; + int root_fd, info_fd, res; + const struct ucred *ucred; + struct stat stat_buf; + + ucred = pw_client_get_ucred(client); + + if (ucred) { + pw_log_info("client has trusted pid %d", ucred->pid); + } else { + pw_log_info("no trusted pid found, assuming not sandboxed\n"); + return 0; + } + + if (check_cmdline(client, ucred, "paplay")) + return 1; + + sprintf(root_path, "/proc/%u/root", ucred->pid); + root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (root_fd == -1) { + /* Not able to open the root dir shouldn't happen. Probably the app died and + * we're failing due to /proc/$pid not existing. In that case fail instead + * of treating this as privileged. */ + res = -errno; + pw_log_error("failed to open \"%s\": %m", root_path); + return res; + } + info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); + close (root_fd); + if (info_fd == -1) { + if (errno == ENOENT) { + pw_log_debug("no .flatpak-info, client on the host"); + /* No file => on the host */ + return 0; + } + res = -errno; + pw_log_error("error opening .flatpak-info: %m"); + return res; + } + if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { + /* Some weird fd => failure, assume sandboxed */ + close(info_fd); + pw_log_error("error fstat .flatpak-info: %m"); + } + return 1; +} + +static void +core_global_added(void *data, struct pw_global *global) +{ + struct impl *impl = data; + struct pw_client *client; + int res; + + if (pw_global_get_type(global) != PW_TYPE_INTERFACE_Client) + return; + + client = pw_global_get_object(global); + + res = check_sandboxed(client); + if (res == 0) { + pw_log_debug("module %p: non sandboxed client %p", impl, client); + pw_client_set_permissions(client, PW_PERM_RWX); + return; + } + + if (res < 0) { + pw_log_warn("module %p: client %p sandbox check failed: %s", + impl, client, spa_strerror(res)); + } + else { + pw_log_debug("module %p: sandboxed client %p added", impl, client); + } + pw_client_set_busy(client, true); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .global_added = core_global_added, +}; + +static void module_destroy(void *data) +{ + struct impl *impl = data; + + spa_hook_remove(&impl->core_listener); + spa_hook_remove(&impl->module_listener); + + if (impl->properties) + pw_properties_free(impl->properties); + + free(impl); +} + +static const struct pw_module_events module_events = { + PW_VERSION_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static int module_init(struct pw_module *module, struct pw_properties *properties) +{ + struct pw_core *core = pw_module_get_core(module); + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -ENOMEM; + + pw_log_debug("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + + pw_core_add_listener(core, &impl->core_listener, &core_events, impl); + pw_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; +} + +int pipewire__module_init(struct pw_module *module, const char *args) +{ + return module_init(module, NULL); +} diff --git a/src/modules/module-flatpak.c b/src/modules/module-flatpak.c deleted file mode 100644 index eecc62e5e..000000000 --- a/src/modules/module-flatpak.c +++ /dev/null @@ -1,525 +0,0 @@ -/* PipeWire - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include - -#include - -#include "pipewire/core.h" -#include "pipewire/interfaces.h" -#include "pipewire/link.h" -#include "pipewire/log.h" -#include "pipewire/module.h" -#include "pipewire/utils.h" - -static const struct spa_dict_item module_props[] = { - { PW_MODULE_PROP_AUTHOR, "Wim Taymans " }, - { PW_MODULE_PROP_DESCRIPTION, "Perform portal queries to check permissions" }, - { PW_MODULE_PROP_VERSION, PACKAGE_VERSION }, -}; - -struct impl { - struct pw_core *core; - struct pw_properties *properties; - - struct spa_dbus_connection *conn; - DBusConnection *bus; - - struct spa_hook core_listener; - struct spa_hook module_listener; - - struct spa_list client_list; -}; - -struct client_info { - struct spa_list link; - struct impl *impl; - struct pw_client *client; - struct spa_list resources; - struct spa_list async_pending; - bool camera_allowed; -}; - -struct async_pending { - struct spa_list link; - struct client_info *cinfo; - bool handled; - char *handle; -}; - -static struct client_info *find_client_info(struct impl *impl, struct pw_client *client) -{ - struct client_info *info; - - spa_list_for_each(info, &impl->client_list, link) { - if (info->client == client) - return info; - } - return NULL; -} - -static void close_request(struct async_pending *p) -{ - DBusMessage *m = NULL; - struct impl *impl = p->cinfo->impl; - - pw_log_debug("pending %p: handle %s", p, p->handle); - - if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Request", - p->handle, - "org.freedesktop.portal.Request", "Close"))) { - pw_log_error("Failed to create message"); - return; - } - - if (!dbus_connection_send(impl->bus, m, NULL)) - pw_log_error("Failed to send message"); - - dbus_message_unref(m); -} - -static struct async_pending *find_pending(struct client_info *cinfo, const char *handle) -{ - struct async_pending *p; - - spa_list_for_each(p, &cinfo->async_pending, link) { - if (strcmp(p->handle, handle) == 0) - return p; - } - return NULL; -} - -static void free_pending(struct async_pending *p) -{ - if (!p->handled) - close_request(p); - - pw_log_debug("pending %p: handle %s", p, p->handle); - spa_list_remove(&p->link); - free(p->handle); - free(p); -} - -static void client_info_free(struct client_info *cinfo) -{ - struct async_pending *p, *tp; - - spa_list_for_each_safe(p, tp, &cinfo->async_pending, link) - free_pending(p); - - spa_list_remove(&cinfo->link); - free(cinfo); -} - -static int check_sandboxed(struct pw_client *client) -{ - char root_path[2048]; - int root_fd, info_fd, res; - const struct ucred *ucred; - struct stat stat_buf; - - ucred = pw_client_get_ucred(client); - - if (ucred) { - pw_log_info("client has trusted pid %d", ucred->pid); - } else { - pw_log_info("no trusted pid found, assuming not sandboxed\n"); - return 0; - } - - sprintf(root_path, "/proc/%u/root", ucred->pid); - root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - if (root_fd == -1) { - /* Not able to open the root dir shouldn't happen. Probably the app died and - * we're failing due to /proc/$pid not existing. In that case fail instead - * of treating this as privileged. */ - res = -errno; - pw_log_error("failed to open \"%s\": %m", root_path); - return res; - } - info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); - close (root_fd); - if (info_fd == -1) { - if (errno == ENOENT) { - pw_log_debug("no .flatpak-info, client on the host"); - /* No file => on the host */ - return 0; - } - res = -errno; - pw_log_error("error opening .flatpak-info: %m"); - return res; - } - if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { - /* Some weird fd => failure, assume sandboxed */ - close(info_fd); - pw_log_error("error fstat .flatpak-info: %m"); - } - return 1; -} - -static bool -check_global_owner(struct pw_client *client, struct pw_global *global) -{ - struct pw_client *owner; - const struct ucred *owner_ucred, *client_ucred; - - if (global == NULL) - return false; - - owner = pw_global_get_owner(global); - if (owner == NULL) - return false; - - owner_ucred = pw_client_get_ucred(owner); - client_ucred = pw_client_get_ucred(client); - - if (owner_ucred == NULL || client_ucred == NULL) - return false; - - /* same user can see eachothers objects */ - return owner_ucred->uid == client_ucred->uid; -} - -static int -set_global_permissions(void *data, struct pw_global *global) -{ - struct client_info *cinfo = data; - struct pw_client *client = cinfo->client; - const struct pw_properties *props; - const char *str; - struct spa_dict_item items[1]; - int n_items = 0; - char perms[16]; - bool allowed = false; - - props = pw_global_get_properties(global); - - switch (pw_global_get_type(global)) { - case PW_TYPE_INTERFACE_Core: - allowed = true; - break; - case PW_TYPE_INTERFACE_Factory: - if (props && (str = pw_properties_get(props, "factory.name"))) { - if (strcmp(str, "client-node") == 0) - allowed = true; - } - break; - case PW_TYPE_INTERFACE_Node: - if (props && (str = pw_properties_get(props, "media.class"))) { - if (strcmp(str, "Video/Source") == 0 && cinfo->camera_allowed) - allowed = true; - } - allowed |= check_global_owner(client, global); - break; - default: - allowed = check_global_owner(client, global); - break; - } - snprintf(perms, sizeof(perms), "%d:%c--", pw_global_get_id(global), allowed ? 'r' : '-'); - items[n_items++] = SPA_DICT_ITEM_INIT(PW_CORE_PROXY_PERMISSIONS_GLOBAL, perms); - pw_client_update_permissions(client, &SPA_DICT_INIT(items, n_items)); - - return 0; -} - -static DBusHandlerResult -portal_response(DBusConnection *connection, DBusMessage *msg, void *user_data) -{ - struct client_info *cinfo = user_data; - struct pw_client *client = cinfo->client; - - if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { - uint32_t response = 2; - DBusError error; - struct async_pending *p; - - dbus_error_init(&error); - - dbus_connection_remove_filter(connection, portal_response, cinfo); - - if (!dbus_message_get_args - (msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) { - pw_log_error("failed to parse Response: %s", error.message); - dbus_error_free(&error); - } - - p = find_pending(cinfo, dbus_message_get_path(msg)); - if (p == NULL) - return DBUS_HANDLER_RESULT_HANDLED; - - p->handled = true; - - pw_log_debug("portal check result: %d", response); - - if (response == 0) { - /* allowed */ - cinfo->camera_allowed = true; - pw_log_debug("camera access allowed"); - } else { - cinfo->camera_allowed = false; - pw_log_debug("camera access not allowed"); - } - pw_core_for_each_global(cinfo->impl->core, set_global_permissions, cinfo); - - free_pending(p); - pw_client_set_busy(client, false); - - return DBUS_HANDLER_RESULT_HANDLED; - } - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - - -static void do_portal_check(struct client_info *cinfo) -{ - struct impl *impl = cinfo->impl; - struct pw_client *client = cinfo->client; - DBusMessage *m = NULL, *r = NULL; - DBusError error; - pid_t pid; - DBusMessageIter msg_iter; - DBusMessageIter dict_iter; - const char *handle; - const char *device; - struct async_pending *p; - struct pw_resource *core_resource; - - pw_log_info("ask portal for client %p", client); - pw_client_set_busy(client, true); - - dbus_error_init(&error); - - if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.Device", "AccessDevice"))) - goto no_method_call; - - device = "camera"; - - pid = pw_client_get_ucred(client)->pid; - if (!dbus_message_append_args(m, DBUS_TYPE_UINT32, &pid, DBUS_TYPE_INVALID)) - goto message_failed; - - dbus_message_iter_init_append(m, &msg_iter); - dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter); - dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &device); - dbus_message_iter_close_container(&msg_iter, &dict_iter); - - dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); - dbus_message_iter_close_container(&msg_iter, &dict_iter); - - if (!(r = dbus_connection_send_with_reply_and_block(impl->bus, m, -1, &error))) - goto send_failed; - - dbus_message_unref(m); - - if (!dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID)) - goto parse_failed; - - dbus_message_unref(r); - - dbus_bus_add_match(impl->bus, - "type='signal',interface='org.freedesktop.portal.Request'", &error); - dbus_connection_flush(impl->bus); - if (dbus_error_is_set(&error)) - goto subscribe_failed; - - dbus_connection_add_filter(impl->bus, portal_response, cinfo, NULL); - - p = calloc(1, sizeof(struct async_pending)); - p->cinfo = cinfo; - p->handle = strdup(handle); - p->handled = false; - - pw_log_debug("pending %p: handle %s", p, handle); - spa_list_append(&cinfo->async_pending, &p->link); - - return; - - no_method_call: - pw_log_error("Failed to create message"); - goto not_allowed; - message_failed: - dbus_message_unref(m); - goto not_allowed; - send_failed: - pw_log_error("Failed to call portal: %s", error.message); - dbus_error_free(&error); - dbus_message_unref(m); - goto not_allowed; - parse_failed: - pw_log_error("Failed to parse AccessDevice result: %s", error.message); - dbus_error_free(&error); - dbus_message_unref(r); - goto not_allowed; - subscribe_failed: - pw_log_error("Failed to subscribe to Request signal: %s", error.message); - dbus_error_free(&error); - goto not_allowed; - not_allowed: - core_resource = pw_client_get_core_resource(client); - pw_resource_error(core_resource, 0, -EPERM, "not allowed"); - return; -} - -static void -core_global_added(void *data, struct pw_global *global) -{ - struct impl *impl = data; - struct client_info *cinfo; - int res; - - if (pw_global_get_type(global) == PW_TYPE_INTERFACE_Client) { - struct pw_client *client = pw_global_get_object(global); - - res = check_sandboxed(client); - if (res == 0) { - pw_log_debug("module %p: non sandboxed client %p", impl, client); - return; - } - - if (res < 0) { - pw_log_warn("module %p: client %p sandbox check failed: %s", - impl, client, spa_strerror(res)); - } - else { - pw_log_debug("module %p: sandboxed client %p added", impl, client); - } - - /* sandboxed clients are placed in a list and we do a portal check */ - cinfo = calloc(1, sizeof(struct client_info)); - cinfo->impl = impl; - cinfo->client = client; - - spa_list_init(&cinfo->async_pending); - - spa_list_append(&impl->client_list, &cinfo->link); - - do_portal_check(cinfo); - } - else { - spa_list_for_each(cinfo, &impl->client_list, link) - set_global_permissions(cinfo, global); - } -} - -static void -core_global_removed(void *data, struct pw_global *global) -{ - struct impl *impl = data; - - if (pw_global_get_type(global) == PW_TYPE_INTERFACE_Client) { - struct pw_client *client = pw_global_get_object(global); - struct client_info *cinfo; - - if ((cinfo = find_client_info(impl, client))) - client_info_free(cinfo); - - pw_log_debug("module %p: client %p removed", impl, client); - } -} - -static const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .global_added = core_global_added, - .global_removed = core_global_removed, -}; - -static void module_destroy(void *data) -{ - struct impl *impl = data; - struct client_info *info, *t; - - spa_hook_remove(&impl->core_listener); - spa_hook_remove(&impl->module_listener); - - spa_dbus_connection_destroy(impl->conn); - - spa_list_for_each_safe(info, t, &impl->client_list, link) - client_info_free(info); - - if (impl->properties) - pw_properties_free(impl->properties); - - free(impl); -} - -static const struct pw_module_events module_events = { - PW_VERSION_MODULE_EVENTS, - .destroy = module_destroy, -}; - -static int module_init(struct pw_module *module, struct pw_properties *properties) -{ - struct pw_core *core = pw_module_get_core(module); - struct impl *impl; - struct spa_dbus *dbus; - const struct spa_support *support; - uint32_t n_support; - - support = pw_core_get_support(core, &n_support); - - dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); - if (dbus == NULL) - return -ENOTSUP; - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -ENOMEM; - - pw_log_debug("module %p: new", impl); - - impl->core = core; - impl->properties = properties; - - impl->conn = spa_dbus_get_connection(dbus, SPA_DBUS_TYPE_SESSION); - if (impl->conn == NULL) - goto error; - - impl->bus = spa_dbus_connection_get(impl->conn); - - spa_list_init(&impl->client_list); - - pw_core_add_listener(core, &impl->core_listener, &core_events, impl); - pw_module_add_listener(module, &impl->module_listener, &module_events, impl); - - pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); - - return 0; - - error: - free(impl); - pw_log_error("Failed to connect to system bus"); - return -ENOMEM; -} - -int pipewire__module_init(struct pw_module *module, const char *args) -{ - return module_init(module, NULL); -}