diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dc01d6dc7..2f645eca8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -317,13 +317,6 @@ doccheck: - .build_on_fedora stage: analysis script: - # Check that each media session module has a \subpage entry - - git grep -h -o -e "\\\page page_media_session_module_\w\+" | cut -f2 -d' ' > media_session_pages - - cat media_session_pages - - | - for page in $(cat media_session_pages); do - git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/media-session.dox" && false) - done # Check that each pipewire module has a \subpage entry - git grep -h -o -e "\\\page page_module_\w\+" | cut -f2 -d' ' > pipewire_module_pages - cat pipewire_module_pages diff --git a/doc/media-session.dox b/doc/media-session.dox deleted file mode 100644 index c15a1b606..000000000 --- a/doc/media-session.dox +++ /dev/null @@ -1,67 +0,0 @@ -/** - -\page page_media_session Media Session - -PipeWire Media Session is the reference/example session manager provided by -the PipeWire project. - -On startup, Media Session reads the `media-session.conf` -configuration file to configure itself. The following directories are searched -for this file: - -- in `$XDG_CONFIG_HOME/pipewire/media-session.d/` (usually -`$HOME/.config/pipewire/media-session.d/`) -- `$sysconfdir/pipewire/media-session.d` (usually -`/etc/pipewire/media-session.d/`) -- `$datadir/pipewire/media-session.d/` (usually -`/usr/share/pipewire/media-session.d/`) - -The environment variable `MEDIA_SESSION_CONFIG_DIR` can be used to -specify an alternative config directory. - -## Access management - -The \ref page_media_session_module_access_flatpak module handles clients -that have \ref PW_KEY_ACCESS set to "flatpak". Other clients are -ignored. - -The module sets the permissions of all objects to `RX`. This limits the -flatpaks from doing modifications to other objects. - -Because this will also set the core object permission `R`, the client will -resume with the new permissions. - -`pipewire-media-session` implements \ref PW_KEY_MEDIA_CATEGORY type -"Manager" applications by simply setting the client permissions to ALL. No -additional checks are performed yet. - -## Modules - -List of Media Session modules: - -- \subpage page_media_session_module_access_flatpak -- \subpage page_media_session_module_access_portal -- \subpage page_media_session_module_alsa_endpoint -- \subpage page_media_session_module_alsa_midi -- \subpage page_media_session_module_alsa_monitor -- \subpage page_media_session_module_bluez_autoswitch -- \subpage page_media_session_module_bluez_endpoint -- \subpage page_media_session_module_bluez_monitor -- \subpage page_media_session_module_default_nodes -- \subpage page_media_session_module_default_profile -- \subpage page_media_session_module_default_routes -- \subpage page_media_session_module_libcamera_monitor -- \subpage page_media_session_module_logind -- \subpage page_media_session_module_metadata -- \subpage page_media_session_module_no_dsp -- \subpage page_media_session_module_policy_endpoint -- \subpage page_media_session_module_policy_node -- \subpage page_media_session_module_restore_stream -- \subpage page_media_session_module_session_manager -- \subpage page_media_session_module_stream_endpoint -- \subpage page_media_session_module_stream_follow_default -- \subpage page_media_session_module_suspend_node -- \subpage page_media_session_module_v4l2_endpoint -- \subpage page_media_session_module_v4l2_monitor - -*/ diff --git a/doc/meson.build b/doc/meson.build index 139b00109..135aed414 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -30,7 +30,6 @@ extra_docs = [ 'pipewire-session-manager.dox', 'pipewire-objects-design.dox', 'pipewire-audio.dox', - 'media-session.dox', 'tutorial.dox', 'tutorial1.dox', 'tutorial2.dox', @@ -67,9 +66,6 @@ endforeach foreach h : module_sources inputs += meson.source_root() / 'src' / 'modules' / h endforeach -foreach h : media_session_sources - inputs += meson.source_root() / 'src' / 'media-session' / h -endforeach inputs += meson.source_root() / 'test' / 'pwtest.h' input_dirs = [ meson.source_root() / 'spa' / 'include' / 'spa' ] diff --git a/doc/pipewire-session-manager.dox b/doc/pipewire-session-manager.dox index c0ec929b4..e4869a4ce 100644 --- a/doc/pipewire-session-manager.dox +++ b/doc/pipewire-session-manager.dox @@ -10,7 +10,8 @@ consistent of Devices, Nodes and Ports. The session manager is the one that decides on the links between those elements. Two prominent session managers currently exist: -- \ref page_media_session - the reference session manager provided by PipeWire +- [PipeWire Media Session](https://gitlab.freedesktop.org/pipewire/pipewire-media-session), the +example session manager - [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber), a modular session manager based on GObject diff --git a/meson.build b/meson.build index 58e3e1b4a..f2370e4a9 100644 --- a/meson.build +++ b/meson.build @@ -491,8 +491,6 @@ if meson.version().version_compare('>=0.58.0') devenv.set('PIPEWIRE_CONFIG_DIR', builddir / 'src' / 'daemon') devenv.set('PIPEWIRE_MODULE_DIR', builddir / 'src' / 'modules') - devenv.set('MEDIA_SESSION_CONFIG_DIR', builddir / 'src' / 'media-session' / 'media-session.d') - devenv.set('SPA_PLUGIN_DIR', builddir / 'spa' / 'plugins') devenv.set('SPA_DATA_DIR', srcdir / 'spa' / 'plugins') diff --git a/pw-uninstalled.sh b/pw-uninstalled.sh index e9b01f809..7bc948ddd 100755 --- a/pw-uninstalled.sh +++ b/pw-uninstalled.sh @@ -36,7 +36,6 @@ fi # the config file read by the daemon export PIPEWIRE_CONFIG_DIR="${BUILDDIR}/src/daemon" -export MEDIA_SESSION_CONFIG_DIR="${BUILDDIR}/src/media-session/media-session.d" # the directory with SPA plugins export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins" export SPA_DATA_DIR="${SCRIPT_DIR}/spa/plugins" @@ -57,6 +56,9 @@ export PKG_CONFIG_PATH="${BUILDDIR}/meson-uninstalled/:${PKG_CONFIG_PATH}" if [ -d "${BUILDDIR}/subprojects/wireplumber" ]; then # FIXME: find a nice, shell-neutral way to specify a prompt "${SCRIPT_DIR}"/subprojects/wireplumber/wp-uninstalled.sh -b"${BUILDDIR}"/subprojects/wireplumber "${SHELL}" +elif [ -d "${BUILDDIR}/subprojects/media-session" ]; then + # FIXME: find a nice, shell-neutral way to specify a prompt + "${SCRIPT_DIR}"/subprojects/media-session/media-session-uninstalled.sh -b"${BUILDDIR}"/subprojects/media-session "${SHELL}" else # FIXME: find a nice, shell-neutral way to specify a prompt ${SHELL} diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 8ccb956c9..0973e64ba 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -36,11 +36,20 @@ summary({'Build media-session': build_ms, if build_wp wp_proj = subproject('wireplumber', required : true) endif +if build_ms + ms_proj = subproject('media-session', required : true) +endif if default_sm == '' summary({'No session manager': 'pw-uninstalled.sh will not work out of the box!'}) elif default_sm == 'media-session' - conf_config_uninstalled.set('session_manager_path', pipewire_media_session.full_path()) + ms_bindir = ms_proj.get_variable('media_session_bin_dir', pipewire_bindir) + conf_config.set('session_manager_path', ms_bindir / 'pipewire-media-session') + + conf_config_uninstalled.set('session_manager_path', + meson.source_root() / 'subprojects' / 'pipewire-media-session' / 'media-session-uninstalled.sh') + conf_config_uninstalled.set('session_manager_args', + '-b ' + meson.build_root() / 'subprojects' / 'pipewire-media-session' + ' pipewire-media-session') conf_config_uninstalled.set('sm_comment', '') elif default_sm == 'wireplumber' wp_bindir = wp_proj.get_variable('wireplumber_bin_dir', pipewire_bindir) diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build index 944c211fe..1b33a322f 100644 --- a/src/daemon/systemd/system/meson.build +++ b/src/daemon/systemd/system/meson.build @@ -5,16 +5,8 @@ install_data(sources : 'pipewire.socket', systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') -systemd_config.set('PW_MEDIA_SESSION_BINARY', pipewire_bindir / 'pipewire-media-session') configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, install_dir : systemd_system_services_dir) - -if get_option('session-managers').contains('media-session') - configure_file(input : 'pipewire-media-session.service.in', - output : 'pipewire-media-session.service', - configuration : systemd_config, - install_dir : systemd_system_services_dir) -endif diff --git a/src/daemon/systemd/system/pipewire-media-session.service.in b/src/daemon/systemd/system/pipewire-media-session.service.in deleted file mode 100644 index 910d80b7c..000000000 --- a/src/daemon/systemd/system/pipewire-media-session.service.in +++ /dev/null @@ -1,21 +0,0 @@ -[Unit] -Description=PipeWire Media Session Manager -After=pipewire.service -BindsTo=pipewire.service - -[Service] -LockPersonality=yes -MemoryDenyWriteExecute=yes -NoNewPrivileges=yes -RestrictNamespaces=yes -SystemCallArchitectures=native -SystemCallFilter=@system-service -Type=simple -ExecStart=@PW_MEDIA_SESSION_BINARY@ -Restart=on-failure -User=pipewire -Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire - -[Install] -WantedBy=pipewire.service -Alias=pipewire-session-manager.service diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build index e4f996057..5bd134537 100644 --- a/src/daemon/systemd/user/meson.build +++ b/src/daemon/systemd/user/meson.build @@ -10,7 +10,6 @@ install_data( systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') -systemd_config.set('PW_MEDIA_SESSION_BINARY', pipewire_bindir / 'pipewire-media-session') configure_file(input : 'pipewire.service.in', output : 'pipewire.service', @@ -21,10 +20,3 @@ configure_file(input : 'pipewire-pulse.service.in', output : 'pipewire-pulse.service', configuration : systemd_config, install_dir : systemd_user_services_dir) - -if get_option('session-managers').contains('media-session') - configure_file(input : 'pipewire-media-session.service.in', - output : 'pipewire-media-session.service', - configuration : systemd_config, - install_dir : systemd_user_services_dir) -endif diff --git a/src/daemon/systemd/user/pipewire-media-session.service.in b/src/daemon/systemd/user/pipewire-media-session.service.in deleted file mode 100644 index 4174d134f..000000000 --- a/src/daemon/systemd/user/pipewire-media-session.service.in +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=PipeWire Media Session Manager -After=pipewire.service -BindsTo=pipewire.service - -[Service] -LockPersonality=yes -MemoryDenyWriteExecute=yes -NoNewPrivileges=yes -RestrictNamespaces=yes -SystemCallArchitectures=native -SystemCallFilter=@system-service -Type=simple -ExecStart=@PW_MEDIA_SESSION_BINARY@ -Restart=on-failure -Slice=session.slice - -[Install] -WantedBy=pipewire.service -Alias=pipewire-session-manager.service diff --git a/src/media-session/access-flatpak.c b/src/media-session/access-flatpak.c deleted file mode 100644 index 46958ac86..000000000 --- a/src/media-session/access-flatpak.c +++ /dev/null @@ -1,216 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_access_flatpak Media Session Module: Access Flatpak - * - * The Access Flatpak module manages permissions for flatpak clients. It - * monitors clients with a \ref PW_KEY_ACCESS value of `"flatpak"` and - * restricts those clients to the \ref PW_PERM_R and \ref PW_PERM_X - * permissions. - * - * Clients of \ref PW_KEY_MEDIA_CATEGORY type "Manager" are permitted full - * access (\ref PW_PERM_ALL). - * - * The value "flatpak" is typically assigned by the \ref page_module_access. - * - * ## Module Properties - * - * This module requires the following properties on the client object: - * - \ref PW_KEY_ACCESS - * - \ref PW_KEY_MEDIA_CATEGORY (optional, only matters for "Manager" objects) - */ -#define NAME "access-flatpak" -#define SESSION_KEY "access-flatpak" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - - struct spa_list client_list; -}; - -struct client { - struct sm_client *obj; - - uint32_t id; - struct impl *impl; - - struct spa_list link; /**< link in impl client_list */ - - struct spa_hook listener; - unsigned int active:1; -}; - -static void object_update(void *data) -{ - struct client *client = data; - struct impl *impl = client->impl; - const char *str; - - pw_log_debug("%p: client %p %08x", impl, client, client->obj->obj.changed); - - if (client->obj->obj.avail & SM_CLIENT_CHANGE_MASK_INFO && - !client->active) { - struct pw_permission permissions[1]; - uint32_t perms; - - if (client->obj->info == NULL || client->obj->info->props == NULL || - (str = spa_dict_lookup(client->obj->info->props, PW_KEY_ACCESS)) == NULL || - !spa_streq(str, "flatpak")) - return; - - if ((str = spa_dict_lookup(client->obj->info->props, PW_KEY_MEDIA_CATEGORY)) != NULL && - (spa_streq(str, "Manager"))) { - /* FIXME, use permission store to check if this app is allowed to - * be a manager app */ - perms = PW_PERM_ALL; - } else { - /* limited access for everything else */ - perms = PW_PERM_R | PW_PERM_X; - } - - pw_log_info("%p: flatpak client %d granted 0x%08x permissions" - , impl, client->id, perms); - permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, perms); - pw_client_update_permissions(client->obj->obj.proxy, - 1, permissions); - client->active = true; - } -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static int -handle_client(struct impl *impl, struct sm_object *object) -{ - struct client *client; - - pw_log_debug("%p: new client '%u'", impl, object->id); - - client = sm_object_add_data(object, SESSION_KEY, sizeof(struct client)); - client->obj = (struct sm_client*)object; - client->id = object->id; - client->impl = impl; - spa_list_append(&impl->client_list, &client->link); - - client->obj->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO; - sm_object_add_listener(&client->obj->obj, &client->listener, &object_events, client); - - return 1; -} - -static void destroy_client(struct impl *impl, struct client *client) -{ - spa_list_remove(&client->link); - spa_hook_remove(&client->listener); - sm_object_remove_data((struct sm_object*)client->obj, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - pw_log_debug("%p: create global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) - res = handle_client(impl, object); - else - res = 0; - - if (res < 0) - pw_log_warn("%p: can't handle global %d", impl, object->id); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - pw_log_debug("%p: remove global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) { - struct client *client; - - if ((client = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_client(impl, client); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - struct client *client; - - spa_list_consume(client, &impl->client_list, link) - destroy_client(impl, client); - - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_access_flatpak_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - - spa_list_init(&impl->client_list); - - sm_media_session_add_listener(impl->session, - &impl->listener, - &session_events, impl); - return 0; -} diff --git a/src/media-session/access-portal.c b/src/media-session/access-portal.c deleted file mode 100644 index 92d95a26b..000000000 --- a/src/media-session/access-portal.c +++ /dev/null @@ -1,669 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include - -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_access_portal Media Session Module: Access Portal - * - * The Access Portal module manages media roles for clients - * started through a portal (see \ref page_module_portal). Clients must have a - * \ref PW_KEY_ACCESS or \ref PW_KEY_CLIENT_ACCESS property value of - * `"portal"`, all other clients are ignored. The portal is expected to assign - *`"pipewire.access.portal.media_roles"` to this client, these roles are - * checked against the - * [org.freedesktop.impl.portal.PermissionStore](https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.impl.portal.PermissionStore). - * Where permitted, the resulting client media role becomes the permitted - * set of roles. - * - * @note Currently only the "Camera" media role is supported. - * - * The Permission Store entry table used by this module is `"devices"`, the - * resource ID is `"camera"`. - * - * ## Module Properties - * - * This module requires the following properties on the client object: - * - * - `"pipewire.access.portal.is_portal"`: set to `"true"` for the portal - * client itself, empty or `"false"` otherwise - * - `"pipewire.access.portal.app_id"`: the application ID of the client - * - `"pipewire.access.portal.media_roles"` the media roles that should be - * assigned to this client (if permitted by the PermissionStore). - * - * The above properties must be set by the portal initiating the client - * connection. - * - * See e.g. the [xdg-desktop-portal](github.com/flatpak/xdg-desktop-portal) - * for an implementation that sets the above properties. It creates multiple - * connections to pipewire, one with `is_portal` set to true for the portal - * itself and one connection per application request to open the camera. The - * latter have `app_id` and `media_roles` set accordingly. - */ -#define NAME "access-portal" -#define SESSION_KEY "access-portal" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -enum media_role { - MEDIA_ROLE_INVALID = -1, - MEDIA_ROLE_NONE = 0, - MEDIA_ROLE_CAMERA = 1 << 0, -}; -#define MEDIA_ROLE_ALL (MEDIA_ROLE_CAMERA) - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - - struct spa_list client_list; - - DBusConnection *bus; -}; - -struct client { - struct impl *impl; - - struct sm_client *obj; - struct spa_hook listener; - - struct spa_list link; /**< link in impl client_list */ - - uint32_t id; - unsigned int portal_managed:1; - unsigned int setup_complete:1; - unsigned int is_portal:1; - char *app_id; - enum media_role media_roles; - enum media_role allowed_media_roles; -}; - -static DBusConnection *get_dbus_connection(struct impl *impl); - -static void client_info_changed(struct client *client, const struct pw_client_info *info); - -static enum media_role media_role_from_string(const char *media_role_str) -{ - if (spa_streq(media_role_str, "Camera")) - return MEDIA_ROLE_CAMERA; - else - return MEDIA_ROLE_INVALID; -} - -static enum media_role parse_media_roles(const char *media_types_str) -{ - enum media_role media_roles = 0; - char *buf_orig; - char *buf; - - buf_orig = strdup(media_types_str); - buf = buf_orig; - while (buf) { - char *media_role_str; - enum media_role media_role; - - media_role_str = buf; - strsep(&buf, ","); - - media_role = media_role_from_string(media_role_str); - if (media_role != MEDIA_ROLE_INVALID) { - media_roles |= MEDIA_ROLE_CAMERA; - } - else { - pw_log_debug("Client specified unknown media role '%s'", - media_role_str); - } - } - free(buf_orig); - - return media_roles; -} - -static enum media_role media_role_from_properties(const struct pw_properties *props) -{ - const char *media_class_str; - const char *media_role_str; - - media_class_str = pw_properties_get(props, "media.class"); - media_role_str = pw_properties_get(props, "media.role"); - - if (media_class_str == NULL) - return MEDIA_ROLE_INVALID; - - if (media_role_str == NULL) - return MEDIA_ROLE_INVALID; - - if (!spa_streq(media_class_str, "Video/Source")) - return MEDIA_ROLE_INVALID; - - return media_role_from_string(media_role_str); -} - -static void object_update(void *data) -{ - struct client *client = data; - struct impl *impl = client->impl; - - pw_log_debug("%p: client %p %08x", impl, client, client->obj->obj.changed); - - if (client->obj->obj.avail & SM_CLIENT_CHANGE_MASK_INFO) - client_info_changed(client, client->obj->info); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static int -handle_client(struct impl *impl, struct sm_object *object) -{ - struct client *client; - const char *str; - - pw_log_debug("%p: new client '%u'", impl, object->id); - - client = sm_object_add_data(object, SESSION_KEY, sizeof(struct client)); - client->obj = (struct sm_client*)object; - client->id = object->id; - client->impl = impl; - spa_list_append(&impl->client_list, &client->link); - - client->obj->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO; - sm_object_add_listener(&client->obj->obj, &client->listener, &object_events, client); - - if (((str = pw_properties_get(client->obj->obj.props, PW_KEY_ACCESS)) != NULL || - (str = pw_properties_get(client->obj->obj.props, PW_KEY_CLIENT_ACCESS)) != NULL) && - spa_streq(str, "portal")) { - client->portal_managed = true; - pw_log_info("%p: portal-managed client %d added", - impl, client->id); - } - return 1; -} - -static int -set_global_permissions(void *data, struct sm_object *object) -{ - struct client *client = data; - struct impl *impl = client->impl; - struct pw_permission permissions[1]; - const struct pw_properties *props; - int n_permissions = 0; - bool set_permission; - bool allowed = false; - - if ((props = object->props) == NULL) - return 0; - - pw_log_debug("%p: object %d type:%s", impl, object->id, object->type); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) { - set_permission = allowed = object->id == client->id; - } else if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) { - enum media_role media_role; - - media_role = media_role_from_properties(props); - - if (media_role == MEDIA_ROLE_INVALID) { - set_permission = false; - } - else if (client->allowed_media_roles & media_role) { - set_permission = true; - allowed = true; - } - else if (client->media_roles & media_role) { - set_permission = true; - allowed = false; - } - else { - set_permission = false; - } - } - else { - set_permission = false; - } - - if (set_permission) { - permissions[n_permissions++] = - PW_PERMISSION_INIT(object->id, allowed ? PW_PERM_ALL : 0); - pw_log_info("%p: object %d allowed:%d", impl, object->id, allowed); - pw_client_update_permissions(client->obj->obj.proxy, - n_permissions, permissions); - } - return 0; -} - - - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - - pw_log_debug("%p: create global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) { - handle_client(impl, object); - } else { - struct client *client; - - spa_list_for_each(client, &impl->client_list, link) { - if (client->portal_managed && - !client->is_portal) - set_global_permissions(client, object); - } - } -} - -static void destroy_client(struct impl *impl, struct client *client) -{ - spa_list_remove(&client->link); - spa_hook_remove(&client->listener); - free(client->app_id); - sm_object_remove_data((struct sm_object*)client->obj, SESSION_KEY); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - pw_log_debug("%p: remove global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) { - struct client *client; - - if ((client = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_client(impl, client); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - struct client *client; - - spa_list_consume(client, &impl->client_list, link) - destroy_client(impl, client); - - spa_hook_remove(&impl->listener); - free(impl); -} - -static void session_dbus_disconnected(void *data) -{ - struct impl *impl = data; - if (impl->bus) - dbus_connection_unref(impl->bus); - impl->bus = NULL; -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, - .dbus_disconnected = session_dbus_disconnected, -}; - -static bool -check_permission_allowed(DBusMessageIter *iter) -{ - bool allowed = false; - - while (dbus_message_iter_get_arg_type (iter) != DBUS_TYPE_INVALID) { - const char *permission_value; - - dbus_message_iter_get_basic(iter, &permission_value); - - if (spa_streq(permission_value, "yes")) { - allowed = true; - break; - } - dbus_message_iter_next(iter); - } - - return allowed; -} - -static void do_permission_store_check(struct client *client) -{ - struct impl *impl = client->impl; - DBusMessage *m = NULL, *r = NULL; - DBusError error; - DBusMessageIter msg_iter; - const char *table; - const char *id; - DBusMessageIter r_iter; - DBusMessageIter permissions_iter; - DBusConnection *bus; - - if (client->app_id == NULL) { - pw_log_debug("Ignoring portal check for broken portal managed client %p", - client); - goto err_not_allowed; - } - - if (client->media_roles == 0) { - pw_log_debug("Ignoring portal check for portal client %p with static permissions", - client); - sm_media_session_for_each_object(impl->session, - set_global_permissions, - client); - return; - } - - if (spa_streq(client->app_id, "")) { - pw_log_debug("Ignoring portal check for non-sandboxed portal client %p", - client); - client->allowed_media_roles = MEDIA_ROLE_ALL; - sm_media_session_for_each_object(impl->session, - set_global_permissions, - client); - return; - } - bus = get_dbus_connection(impl); - if (bus == NULL) { - pw_log_debug("Ignoring portal check for client %p: no dbus", - client); - client->allowed_media_roles = MEDIA_ROLE_ALL; - sm_media_session_for_each_object(impl->session, - set_global_permissions, - client); - return; - } - - client->allowed_media_roles = MEDIA_ROLE_NONE; - - dbus_error_init(&error); - - m = dbus_message_new_method_call("org.freedesktop.impl.portal.PermissionStore", - "/org/freedesktop/impl/portal/PermissionStore", - "org.freedesktop.impl.portal.PermissionStore", - "Lookup"); - - dbus_message_iter_init_append(m, &msg_iter); - table = "devices"; - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &table); - id = "camera"; - dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &id); - - if (!(r = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) { - pw_log_error("Failed to call permission store: %s", error.message); - dbus_error_free(&error); - goto err_not_allowed; - } - - dbus_message_unref(m); - - dbus_message_iter_init(r, &r_iter); - dbus_message_iter_recurse(&r_iter, &permissions_iter); - while (dbus_message_iter_get_arg_type(&permissions_iter) != - DBUS_TYPE_INVALID) { - DBusMessageIter permissions_entry_iter; - const char *app_id; - DBusMessageIter permission_values_iter; - bool camera_allowed; - - dbus_message_iter_recurse(&permissions_iter, - &permissions_entry_iter); - dbus_message_iter_get_basic(&permissions_entry_iter, &app_id); - - pw_log_info("permissions %s", app_id); - if (!spa_streq(app_id, client->app_id)) { - dbus_message_iter_next(&permissions_iter); - continue; - } - - dbus_message_iter_next(&permissions_entry_iter); - dbus_message_iter_recurse(&permissions_entry_iter, - &permission_values_iter); - - camera_allowed = check_permission_allowed(&permission_values_iter); - pw_log_info("allowed %d", camera_allowed); - client->allowed_media_roles |= - camera_allowed ? MEDIA_ROLE_CAMERA : MEDIA_ROLE_NONE; - - sm_media_session_for_each_object(impl->session, - set_global_permissions, - client); - break; - } - - dbus_message_unref(r); - - return; - -err_not_allowed: - return; -} - -static void client_info_changed(struct client *client, const struct pw_client_info *info) -{ - struct impl *impl = client->impl; - const struct spa_dict *props; - const char *is_portal; - const char *app_id; - const char *media_roles; - - if (!client->portal_managed || client->is_portal) - return; - - if (client->setup_complete) - return; - - if ((props = info->props) == NULL) { - pw_log_error("Portal managed client didn't have any properties"); - return; - } - - is_portal = spa_dict_lookup(props, "pipewire.access.portal.is_portal"); - if (spa_streq(is_portal, "yes") || pw_properties_parse_bool(is_portal)) { - pw_log_info("%p: client %d is the portal itself", - impl, client->id); - client->is_portal = true; - return; - }; - - app_id = spa_dict_lookup(props, "pipewire.access.portal.app_id"); - if (app_id == NULL) { - pw_log_error("%p: Portal managed client %d didn't set app_id", - impl, client->id); - return; - } - media_roles = spa_dict_lookup(props, "pipewire.access.portal.media_roles"); - if (media_roles == NULL) { - pw_log_error("%p: Portal managed client %d didn't set media_roles", - impl, client->id); - return; - } - - client->app_id = strdup(app_id); - client->media_roles = parse_media_roles(media_roles); - - pw_log_info("%p: client %d with app_id '%s' set to portal access", - impl, client->id, client->app_id); - - do_permission_store_check(client); - - client->setup_complete = true; -} - -static DBusHandlerResult permission_store_changed_handler(DBusConnection *connection, - DBusMessage *message, - void *user_data) -{ - struct impl *impl = user_data; - struct client *client; - DBusMessageIter iter; - const char *table; - const char *id; - dbus_bool_t deleted; - DBusMessageIter permissions_iter; - - if (!dbus_message_is_signal(message, "org.freedesktop.impl.portal.PermissionStore", - "Changed")) - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - spa_list_for_each(client, &impl->client_list, link) { - if (!client->portal_managed) - continue; - - client->allowed_media_roles = MEDIA_ROLE_NONE; - } - - dbus_message_iter_init(message, &iter); - dbus_message_iter_get_basic(&iter, &table); - - dbus_message_iter_next(&iter); - dbus_message_iter_get_basic(&iter, &id); - - if (!spa_streq(table, "devices") || !spa_streq(id, "camera")) - return DBUS_HANDLER_RESULT_HANDLED; - - dbus_message_iter_next(&iter); - dbus_message_iter_get_basic(&iter, &deleted); - - dbus_message_iter_next(&iter); - /* data variant (ignored) */ - - dbus_message_iter_next(&iter); - dbus_message_iter_recurse(&iter, &permissions_iter); - while (dbus_message_iter_get_arg_type(&permissions_iter) != - DBUS_TYPE_INVALID) { - DBusMessageIter permissions_entry_iter; - const char *app_id; - DBusMessageIter permission_values_iter; - bool camera_allowed; - - dbus_message_iter_recurse(&permissions_iter, - &permissions_entry_iter); - dbus_message_iter_get_basic(&permissions_entry_iter, &app_id); - - dbus_message_iter_next(&permissions_entry_iter); - dbus_message_iter_recurse(&permissions_entry_iter, - &permission_values_iter); - - camera_allowed = check_permission_allowed(&permission_values_iter); - - spa_list_for_each(client, &impl->client_list, link) { - if (!client->portal_managed) - continue; - - if (client->is_portal) - continue; - - if (client->app_id == NULL || - !spa_streq(client->app_id, app_id)) - continue; - - if (!(client->media_roles & MEDIA_ROLE_CAMERA)) - continue; - - if (camera_allowed) - client->allowed_media_roles |= MEDIA_ROLE_CAMERA; - - sm_media_session_for_each_object(impl->session, - set_global_permissions, - client); - } - - dbus_message_iter_next(&permissions_iter); - } - - return DBUS_HANDLER_RESULT_HANDLED; -} - -static DBusConnection *get_dbus_connection(struct impl *impl) -{ - struct sm_media_session *session = impl->session; - DBusError error; - - if (impl->bus) - return impl->bus; - - if (session->dbus_connection) - impl->bus = spa_dbus_connection_get(session->dbus_connection); - if (impl->bus == NULL) { - pw_log_warn("no dbus connection, portal access disabled"); - return NULL; - } - pw_log_debug("got dbus connection %p", impl->bus); - - dbus_error_init(&error); - - dbus_bus_add_match(impl->bus, - "type='signal',\ - sender='org.freedesktop.impl.portal.PermissionStore',\ - interface='org.freedesktop.impl.portal.PermissionStore',\ - member='Changed'", - &error); - if (dbus_error_is_set(&error)) { - pw_log_error("Failed to add permission store changed listener: %s", - error.message); - dbus_error_free(&error); - impl->bus = NULL; - return NULL; - } - dbus_connection_ref(impl->bus); - dbus_connection_add_filter(impl->bus, permission_store_changed_handler, - impl, NULL); - return impl->bus; -} - -int sm_access_portal_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - spa_list_init(&impl->client_list); - - impl->session = session; - - get_dbus_connection(impl); - - sm_media_session_add_listener(impl->session, - &impl->listener, - &session_events, impl); - return 0; -} diff --git a/src/media-session/alsa-endpoint.c b/src/media-session/alsa-endpoint.c deleted file mode 100644 index 0a2bc93d3..000000000 --- a/src/media-session/alsa-endpoint.c +++ /dev/null @@ -1,774 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include - -#include "media-session.h" - -/** \page page_media_session_module_alsa_endpoint Media Session Module: ALSA Endpoint - * - * The ALSA endpoint module creates an endpoint and corresponding endpoint - * stream for each Node on ALSA devices. - * - * ALSA devices are defined as devices with a \ref PW_KEY_MEDIA_CLASS - * starting with `"Audio/"` and a \ref PW_KEY_DEVICE_API of `"alsa"`. - */ - -#define NAME "alsa-endpoint" -#define SESSION_KEY "alsa-endpoint" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct endpoint { - struct spa_list link; - - struct impl *impl; - - struct pw_properties *props; - - struct node *node; - struct spa_hook listener; - - struct pw_client_endpoint *client_endpoint; - struct spa_hook proxy_listener; - struct spa_hook client_endpoint_listener; - struct pw_endpoint_info info; - - struct spa_param_info params[5]; - - struct endpoint *monitor; - - unsigned int use_ucm:1; - snd_use_case_mgr_t *ucm; - - struct spa_audio_info format; - - struct spa_list stream_list; -}; - -struct stream { - struct spa_list link; - struct endpoint *endpoint; - - struct pw_properties *props; - struct pw_endpoint_stream_info info; - - struct spa_audio_info format; - - unsigned int active:1; -}; - -struct node { - struct impl *impl; - struct sm_node *node; - - struct device *device; - - struct endpoint *endpoint; -}; - -struct device { - struct impl *impl; - uint32_t id; - struct sm_device *device; - struct spa_hook listener; - - struct spa_list endpoint_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; -}; - -static int client_endpoint_set_session_id(void *object, uint32_t id) -{ - struct endpoint *endpoint = object; - endpoint->info.session_id = id; - return 0; -} - -static int client_endpoint_set_param(void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - pw_log_debug("%p: endpoint %p set param %d", impl, endpoint, id); - return pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, - id, flags, param); -} - - -static int client_endpoint_stream_set_param(void *object, uint32_t stream_id, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - return -ENOTSUP; -} - -static int stream_set_active(struct endpoint *endpoint, struct stream *stream, bool active) -{ - char buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - - if (stream->active == active) - return 0; - - if (active) { - stream->format.info.raw.rate = 48000; - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &stream->format.info.raw); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(endpoint->info.direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(true), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, - SPA_PARAM_PortConfig, 0, param); - } - stream->active = active; - return 0; -} - -static int client_endpoint_create_link(void *object, const struct spa_dict *props) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - struct pw_properties *p; - int res; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (props == NULL) - return -EINVAL; - - p = pw_properties_new_dict(props); - if (p == NULL) - return -errno; - - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - const char *str; - struct sm_object *obj; - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT); - if (str == NULL) { - pw_log_warn("%p: no target endpoint given", impl); - res = -EINVAL; - goto exit; - } - obj = sm_media_session_find_object(impl->session, atoi(str)); - if (obj == NULL || !spa_streq(obj->type, PW_TYPE_INTERFACE_Endpoint)) { - pw_log_warn("%p: could not find endpoint %s (%p)", impl, str, obj); - res = -EINVAL; - goto exit; - } - - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1"); - - pw_endpoint_create_link((struct pw_endpoint*)obj->proxy, &p->dict); - } else { - pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1"); - - sm_media_session_create_links(impl->session, &p->dict); - } - - res = 0; -exit: - pw_properties_free(p); - - return res; -} - -static const struct pw_client_endpoint_events client_endpoint_events = { - PW_VERSION_CLIENT_ENDPOINT_EVENTS, - .set_session_id = client_endpoint_set_session_id, - .set_param = client_endpoint_set_param, - .stream_set_param = client_endpoint_stream_set_param, - .create_link = client_endpoint_create_link, -}; - -static struct stream *endpoint_add_stream(struct endpoint *endpoint) -{ - struct stream *s; - const char *str; - - s = calloc(1, sizeof(*s)); - if (s == NULL) - return NULL; - - s->props = pw_properties_new(NULL, NULL); - s->endpoint = endpoint; - - if ((str = pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS)) != NULL) - pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str); - if ((str = pw_properties_get(endpoint->props, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(s->props, PW_KEY_PRIORITY_SESSION, str); - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - if (endpoint->monitor != NULL) - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Monitor"); - else - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Capture"); - } else { - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Playback"); - } - - s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - s->info.id = endpoint->info.n_streams; - s->info.endpoint_id = endpoint->info.id; - s->info.name = (char*)pw_properties_get(s->props, PW_KEY_ENDPOINT_STREAM_NAME); - s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; - s->info.props = &s->props->dict; - s->format = endpoint->format; - - pw_log_debug("stream %d", s->info.id); - pw_client_endpoint_stream_update(endpoint->client_endpoint, - s->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, - 0, NULL, - &s->info); - - spa_list_append(&endpoint->stream_list, &s->link); - endpoint->info.n_streams++; - - return s; -} - -static void destroy_stream(struct stream *stream) -{ - struct endpoint *endpoint = stream->endpoint; - - pw_client_endpoint_stream_update(endpoint->client_endpoint, - stream->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, - 0, NULL, - &stream->info); - - spa_list_remove(&stream->link); - endpoint->info.n_streams--; - - pw_properties_free(stream->props); - free(stream); -} - -static void update_params(void *data) -{ - uint32_t n_params; - const struct spa_pod **params; - struct endpoint *endpoint = data; - struct sm_node *node = endpoint->node->node; - struct sm_param *p; - - pw_log_debug("%p: endpoint", endpoint); - - params = alloca(sizeof(struct spa_pod *) * node->n_params); - n_params = 0; - spa_list_for_each(p, &node->param_list, link) { - switch (p->id) { - case SPA_PARAM_Props: - case SPA_PARAM_PropInfo: - params[n_params++] = p->param; - break; - default: - break; - } - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_PARAMS | - PW_CLIENT_ENDPOINT_UPDATE_INFO, - n_params, params, - &endpoint->info); -} - -static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor); - -static void object_update(void *data) -{ - struct endpoint *endpoint = data; - struct impl *impl = endpoint->impl; - struct sm_node *node = endpoint->node->node; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (node->obj.changed & SM_NODE_CHANGE_MASK_PARAMS) - update_params(endpoint); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void complete_endpoint(void *data) -{ - struct endpoint *endpoint = data; - struct stream *stream; - struct sm_param *p; - - pw_log_debug("endpoint %p: complete", endpoint); - - spa_list_for_each(p, &endpoint->node->node->param_list, link) { - struct spa_audio_info info = { 0, }; - - if (p->id != SPA_PARAM_EnumFormat) - continue; - - if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) - continue; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - continue; - - spa_pod_object_fixate((struct spa_pod_object*)p->param); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - if (spa_format_audio_raw_parse(p->param, &info.info.raw) < 0) - continue; - - if (endpoint->format.info.raw.channels < info.info.raw.channels) - endpoint->format = info; - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_INFO, - 0, NULL, - &endpoint->info); - - stream = endpoint_add_stream(endpoint); - - if (endpoint->info.direction == PW_DIRECTION_INPUT) { - struct endpoint *monitor; - - /* make monitor for sinks */ - monitor = create_endpoint(endpoint->node, endpoint); - if (monitor == NULL) - return; - - endpoint_add_stream(monitor); - } - stream_set_active(endpoint, stream, true); - - sm_object_add_listener(&endpoint->node->node->obj, &endpoint->listener, &object_events, endpoint); -} - -static void proxy_destroy(void *data) -{ - struct endpoint *endpoint = data; - struct stream *s; - - spa_list_consume(s, &endpoint->stream_list, link) - destroy_stream(s); - - pw_properties_free(endpoint->props); - spa_list_remove(&endpoint->link); - spa_hook_remove(&endpoint->proxy_listener); - spa_hook_remove(&endpoint->client_endpoint_listener); - endpoint->client_endpoint = NULL; -} - -static void proxy_bound(void *data, uint32_t id) -{ - struct endpoint *endpoint = data; - endpoint->info.id = id; -} - -static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = proxy_destroy, - .bound = proxy_bound, -}; - -static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor) -{ - struct impl *impl = node->impl; - struct device *device = node->device; - struct pw_properties *props; - struct endpoint *endpoint; - struct pw_proxy *proxy; - const char *str, *media_class = NULL, *name = NULL; - uint32_t subscribe[4], n_subscribe = 0; - struct pw_properties *pr = node->node->obj.props; - enum pw_direction direction; - - if (pr == NULL) { - errno = EINVAL; - return NULL; - } - - if ((media_class = pw_properties_get(pr, PW_KEY_MEDIA_CLASS)) == NULL) { - errno = EINVAL; - return NULL; - } - - if (strstr(media_class, "Source") != NULL) { - direction = PW_DIRECTION_OUTPUT; - } else if (strstr(media_class, "Sink") != NULL) { - direction = PW_DIRECTION_INPUT; - } else { - errno = EINVAL; - return NULL; - } - - props = pw_properties_new(NULL, NULL); - if (props == NULL) - return NULL; - - if (monitor != NULL) { - pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); - direction = PW_DIRECTION_OUTPUT; - } else { - pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class); - } - - if ((str = pw_properties_get(pr, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(props, PW_KEY_PRIORITY_SESSION, str); - if ((name = pw_properties_get(pr, PW_KEY_NODE_DESCRIPTION)) != NULL) { - if (monitor != NULL) { - pw_properties_setf(props, PW_KEY_ENDPOINT_NAME, "Monitor of %s", monitor->info.name); - pw_properties_setf(props, PW_KEY_ENDPOINT_MONITOR, "%d", monitor->info.id); - } else { - pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name); - } - } - if ((str = pw_properties_get(pr, PW_KEY_DEVICE_ICON_NAME)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str); - - proxy = sm_media_session_create_object(impl->session, - "client-endpoint", - PW_TYPE_INTERFACE_ClientEndpoint, - PW_VERSION_CLIENT_ENDPOINT, - &props->dict, sizeof(*endpoint)); - if (proxy == NULL) { - pw_properties_free(props); - return NULL; - } - - endpoint = pw_proxy_get_user_data(proxy); - endpoint->impl = impl; - endpoint->node = node; - endpoint->monitor = monitor; - endpoint->props = props; - endpoint->client_endpoint = (struct pw_client_endpoint *) proxy; - endpoint->info.version = PW_VERSION_ENDPOINT_INFO; - endpoint->info.name = (char*)pw_properties_get(endpoint->props, PW_KEY_ENDPOINT_NAME); - endpoint->info.media_class = (char*)pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS); - endpoint->info.session_id = impl->session->session->obj.id; - endpoint->info.direction = direction; - endpoint->info.flags = 0; - endpoint->info.change_mask = - PW_ENDPOINT_CHANGE_MASK_STREAMS | - PW_ENDPOINT_CHANGE_MASK_SESSION | - PW_ENDPOINT_CHANGE_MASK_PROPS | - PW_ENDPOINT_CHANGE_MASK_PARAMS; - endpoint->info.n_streams = 0; - endpoint->info.props = &endpoint->props->dict; - endpoint->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - endpoint->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - endpoint->info.params = endpoint->params; - endpoint->info.n_params = 2; - spa_list_init(&endpoint->stream_list); - - pw_log_debug("%p: new endpoint %p for alsa node %p", impl, endpoint, node); - pw_proxy_add_listener(proxy, - &endpoint->proxy_listener, - &proxy_events, endpoint); - - pw_client_endpoint_add_listener(endpoint->client_endpoint, - &endpoint->client_endpoint_listener, - &client_endpoint_events, - endpoint); - - subscribe[n_subscribe++] = SPA_PARAM_EnumFormat; - subscribe[n_subscribe++] = SPA_PARAM_Props; - subscribe[n_subscribe++] = SPA_PARAM_PropInfo; - pw_log_debug("%p: endpoint %p proxy %p subscribe %d params", impl, - endpoint, node->node->obj.proxy, n_subscribe); - pw_node_subscribe_params((struct pw_node*)node->node->obj.proxy, - subscribe, n_subscribe); - - spa_list_append(&device->endpoint_list, &endpoint->link); - - if (monitor == NULL) - sm_media_session_sync(impl->session, complete_endpoint, endpoint); - - return endpoint; -} - -static void destroy_endpoint(struct endpoint *endpoint) -{ - if (endpoint->client_endpoint) - pw_proxy_destroy((struct pw_proxy*)endpoint->client_endpoint); -} - -/** fallback, one stream for each node */ -static int setup_alsa_fallback_endpoint(struct device *device) -{ - struct impl *impl = device->impl; - struct sm_node *n; - struct sm_device *d = device->device; - - pw_log_debug("%p: device %p fallback", impl, d); - - spa_list_for_each(n, &d->node_list, link) { - struct node *node; - - pw_log_debug("%p: device %p has node %p", impl, d, n); - - node = sm_object_add_data(&n->obj, SESSION_KEY, sizeof(struct node)); - node->device = device; - node->node = n; - node->impl = impl; - node->endpoint = create_endpoint(node, NULL); - if (node->endpoint == NULL) - return -errno; - } - return 0; -} - -/** UCM. - * - * We create 1 stream for each verb + modifier combination - */ -static int setup_alsa_ucm_endpoint(struct device *device) -{ - const char *str, *card_name = NULL; - char *name_free = NULL; - int i, res, num_verbs; - const char **verb_list = NULL; - struct spa_dict *props = device->device->info->props; - snd_use_case_mgr_t *ucm; - - card_name = spa_dict_lookup(props, SPA_KEY_API_ALSA_CARD_NAME); - if (card_name == NULL && - (str = spa_dict_lookup(props, SPA_KEY_API_ALSA_CARD)) != NULL) { - snd_card_get_name(atoi(str), &name_free); - card_name = name_free; - pw_log_debug("got card name %s for index %s", card_name, str); - } - if (card_name == NULL) { - pw_log_error("can't get card name for index %s", str); - res = -ENOTSUP; - goto exit; - } - - if ((res = snd_use_case_mgr_open(&ucm, card_name)) < 0) { - pw_log_error("can not open UCM for %s: %s", card_name, snd_strerror(res)); - goto exit; - } - - num_verbs = snd_use_case_verb_list(ucm, &verb_list); - if (num_verbs < 0) { - res = num_verbs; - pw_log_error("UCM verb list not found for %s: %s", card_name, snd_strerror(num_verbs)); - goto close_exit; - } - - for (i = 0; i < num_verbs; i++) { - pw_log_debug("verb: %s", verb_list[i]); - } - - /* FIXME: implement this */ - - snd_use_case_free_list(verb_list, num_verbs); - - res = -ENOTSUP; - -close_exit: - snd_use_case_mgr_close(ucm); -exit: - free(name_free); - return res; -} - -static int activate_device(struct device *device) -{ - int res; - - if ((res = setup_alsa_ucm_endpoint(device)) < 0) - res = setup_alsa_fallback_endpoint(device); - - return res; -} - -static int deactivate_device(struct device *device) -{ - struct endpoint *e; - spa_list_consume(e, &device->endpoint_list, link) - destroy_endpoint(e); - return 0; -} - -static void device_update(void *data) -{ - struct device *device = data; - struct impl *impl = device->impl; - - pw_log_debug("%p: device %p %08x %08x", impl, device, - device->device->obj.avail, device->device->obj.changed); - - if (!SPA_FLAG_IS_SET(device->device->obj.avail, - SM_DEVICE_CHANGE_MASK_INFO | - SM_DEVICE_CHANGE_MASK_NODES | - SM_DEVICE_CHANGE_MASK_PARAMS)) - return; - - if (SPA_FLAG_IS_SET(device->device->obj.changed, - SM_DEVICE_CHANGE_MASK_NODES | - SM_DEVICE_CHANGE_MASK_PARAMS)) { - activate_device(device); - } -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .update = device_update -}; - -static int -handle_device(struct impl *impl, struct sm_object *obj) -{ - const char *media_class, *str; - struct device *device; - - if (obj->props == NULL) - return 0; - - media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS); - str = pw_properties_get(obj->props, PW_KEY_DEVICE_API); - - pw_log_debug("%p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); - - if (!spa_strstartswith(media_class, "Audio/")) - return 0; - if (!spa_streq(str, "alsa")) - return 0; - - device = sm_object_add_data(obj, SESSION_KEY, sizeof(struct device)); - device->impl = impl; - device->id = obj->id; - device->device = (struct sm_device*)obj; - spa_list_init(&device->endpoint_list); - pw_log_debug("%p: found alsa device %d media_class %s", impl, obj->id, media_class); - - sm_object_add_listener(obj, &device->listener, &device_events, device); - - return 0; -} - -static void destroy_device(struct impl *impl, struct device *device) -{ - deactivate_device(device); - spa_hook_remove(&device->listener); - sm_object_remove_data((struct sm_object*)device->device, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) - res = handle_device(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d: %s", impl, - object->id, spa_strerror(res)); - } -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) { - struct device *device; - if ((device = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_device(impl, device); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_alsa_endpoint_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - sm_media_session_add_listener(session, &impl->listener, &session_events, impl); - return 0; -} diff --git a/src/media-session/alsa-midi.c b/src/media-session/alsa-midi.c deleted file mode 100644 index 743e379e0..000000000 --- a/src/media-session/alsa-midi.c +++ /dev/null @@ -1,219 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -#define SND_PATH "/dev/snd" -#define SEQ_NAME "seq" -#define SND_SEQ_PATH SND_PATH"/"SEQ_NAME - -/** \page page_media_session_module_alsa_midi Media Session Module: ALSA MIDI - */ - -#define NAME "alsa-midi" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define DEFAULT_NAME "Midi-Bridge" - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_properties *props; - struct pw_proxy *proxy; - - struct spa_source *notify; -}; - -static int do_create(struct impl *impl) -{ - impl->proxy = sm_media_session_create_object(impl->session, - "spa-node-factory", - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE, - &impl->props->dict, - 0); - if (impl->proxy == NULL) - return -errno; - - return 0; -} - -static int check_access(struct impl *impl) -{ - return access(SND_SEQ_PATH, R_OK|W_OK) >= 0; -} - -static void stop_inotify(struct impl *impl) -{ - struct pw_loop *main_loop = impl->session->loop; - if (impl->notify != NULL) { - pw_log_info("stop inotify"); - pw_loop_destroy_source(main_loop, impl->notify); - impl->notify = NULL; - } -} - -static void on_notify_events(void *data, int fd, uint32_t mask) -{ - struct impl *impl = data; - bool remove = false; - struct { - struct inotify_event e; - char name[NAME_MAX+1]; - } buf; - - while (true) { - ssize_t len; - const struct inotify_event *event; - void *p, *e; - - len = read(fd, &buf, sizeof(buf)); - if (len < 0 && errno != EAGAIN) - break; - if (len <= 0) - break; - - e = SPA_PTROFF(&buf, len, void); - - for (p = &buf; p < e; - p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) { - event = (const struct inotify_event *) p; - - if ((event->mask & IN_ATTRIB)) { - if (strncmp(event->name, SEQ_NAME, event->len) != 0) - continue; - if (impl->proxy == NULL && - check_access(impl) && - do_create(impl) >= 0) - remove = true; - } - if ((event->mask & (IN_DELETE_SELF | IN_MOVE_SELF))) - remove = true; - } - } - if (remove) - stop_inotify(impl); -} - -static int start_inotify(struct impl *impl) -{ - int notify_fd, res; - struct pw_loop *main_loop = impl->session->loop; - - if ((notify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK)) < 0) - return -errno; - - res = inotify_add_watch(notify_fd, SND_PATH, - IN_ATTRIB | IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MOVE_SELF); - if (res < 0) { - res = -errno; - close(notify_fd); - pw_log_error("inotify_add_watch() '%s' failed: %s", - SND_PATH, spa_strerror(res)); - return res; - } - pw_log_info("start inotify"); - - impl->notify = pw_loop_add_io(main_loop, notify_fd, SPA_IO_IN | SPA_IO_ERR, - true, on_notify_events, impl); - return 0; -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - if (impl->proxy) - pw_proxy_destroy(impl->proxy); - stop_inotify(impl); - pw_properties_free(impl->props); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_alsa_midi_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - const char *name; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - if ((name = pw_properties_get(session->props, "alsa.seq.name")) == NULL) - name = DEFAULT_NAME; - - impl->session = session; - impl->props = pw_properties_new( - SPA_KEY_FACTORY_NAME, SPA_NAME_API_ALSA_SEQ_BRIDGE, - SPA_KEY_NODE_NAME, name, - NULL); - if (impl->props == NULL) { - res = -errno; - goto cleanup; - } - - sm_media_session_add_listener(session, &impl->listener, &session_events, impl); - - if (check_access(impl)) { - res = do_create(impl); - } else { - res = start_inotify(impl); - } - if (res < 0) - goto cleanup_props; - - return 0; - -cleanup_props: - pw_properties_free(impl->props); -cleanup: - free(impl); - return res; -} diff --git a/src/media-session/alsa-monitor.c b/src/media-session/alsa-monitor.c deleted file mode 100644 index 21aca3c3c..000000000 --- a/src/media-session/alsa-monitor.c +++ /dev/null @@ -1,1238 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "media-session.h" - -/** \page page_media_session_module_alsa_monitor Media Session Module: ALSA Monitor - * - * This module monitors udev for ALSA devices and creates the required - * PipeWire Device objects for each ALSA device. - * - * Devices advertised by udev are reserved using the [DBus ReserveDevice - * API](http://git.0pointer.net/reserve.git/tree/reserve.txt) and exported as - * \ref SPA_TYPE_INTERFACE_Device in the \ref pw_core. For each device, - * objects of type \ref SPA_TYPE_INTERFACE_Node are then created as required. - * - * Additionally, extra configuration is applied as shown below. This - * configuration is applied before the device is exported. - * - * ## Configuration - * - * This module loads the `alsa-monitor.conf` configuration file. The main - * component in that file is the `rules = []` array that consists of multiple - * dictionaries that `matches` a device and specifying `actions` to take. - * - * The following `actions` are supported: - * - `update-props`: update properties on the matched object - * - * For example: - * ``` - * rules = [ - * { - * # Matches is an array of dictionaries. For a dictionary to match, **all** - * # key/value matches must apply. For a match to be successful, **any** - * # dictionary must apply. - * matches = [ - * { - * # A regular expression is prefixed with ~ - * device.name = "~alsa_card.*" - * } - * { - * # standard string comparisons - * device.name = "alsa_card.abcdef" - * node.name = "alsa_input.12345" - * } - * { - * # 'null' matches if the property is unset - * some.random.property = "null" - * } - * ] - * actions = { - * update-props = { - * api.alsa.use-acp = true - * } - * } - * } - * ] - * ``` - * - * ## Module-specific properties: - * - * This modules supports the following entries in the `properties` dictionary: - * - `alsa.reserve = false`: disable device reservation (default: enabled) - * - `alsa.jack-device = true`: create a JACK device (default: disabled), see - * the comment in the example configuration file. - * - * See the `alsa-monitor.conf` provided by your installation for details on - * possible actions and matches. - */ - -#define NAME "alsa-monitor" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#include "reserve.c" - -#define SESSION_CONF "alsa-monitor.conf" - -#define DEFAULT_JACK_SECONDS 1 - -struct node { - struct impl *impl; - enum pw_direction direction; - struct device *device; - struct spa_list link; - uint32_t id; - - struct pw_properties *props; - - struct spa_node *node; - - struct sm_node *snode; - unsigned int acquired:1; -}; - -struct device { - struct impl *impl; - struct spa_list link; - uint32_t id; - uint32_t device_id; - - char *factory_name; - - struct rd_device *reserve; - struct spa_hook sync_listener; - int seq; - int priority; - - int profile; - int pending_profile; - - struct pw_properties *props; - - struct spa_handle *handle; - struct spa_device *device; - struct spa_hook device_listener; - - struct sm_device *sdevice; - struct spa_hook listener; - - uint32_t n_acquired; - - unsigned int first:1; - unsigned int appeared:1; - unsigned int probed:1; - unsigned int use_acp:1; - struct spa_list node_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook session_listener; - - struct pw_properties *conf; - struct pw_properties *props; - - struct spa_handle *handle; - - struct spa_device *monitor; - struct spa_hook listener; - - struct spa_list device_list; - - struct spa_source *jack_timeout; - struct pw_proxy *jack_device; - unsigned int reserve:1; -}; - -#undef NAME -#define NAME "alsa-monitor" - -static int probe_device(struct device *device); -static int do_device_acquire(struct device *device); - -static struct node *alsa_find_node(struct device *device, uint32_t id, const char *name) -{ - struct node *node; - const char *str; - - spa_list_for_each(node, &device->node_list, link) { - if (node->id == id) - return node; - if (name != NULL && - (str = pw_properties_get(node->props, PW_KEY_NODE_NAME)) != NULL && - spa_streq(name, str)) - return node; - } - return NULL; -} - -static void alsa_update_node(struct device *device, struct node *node, - const struct spa_device_object_info *info) -{ - pw_log_debug("update node %u", node->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(node->props, info->props); -} - -static int node_acquire(void *data) -{ - struct node *node = data; - struct device *device = node->device; - - pw_log_debug("acquire %u", node->id); - - if (node->acquired) - return 0; - - node->acquired = true; - - if (device && device->n_acquired++ == 0) - return do_device_acquire(device); - else - return 0; -} - -static int node_release(void *data) -{ - struct node *node = data; - struct device *device = node->device; - - pw_log_debug("release %u", node->id); - - if (!node->acquired) - return 0; - - node->acquired = false; - - if (device && --device->n_acquired == 0 && device->reserve) - rd_device_release(device->reserve); - return 0; -} - -static const struct sm_object_methods node_methods = { - SM_VERSION_OBJECT_METHODS, - .acquire = node_acquire, - .release = node_release, -}; - -static void update_icon_name(struct pw_properties *p, bool is_sink) -{ - const char *s, *d = NULL, *bus; - - if ((s = pw_properties_get(p, PW_KEY_DEVICE_FORM_FACTOR))) { - if (spa_streq(s, "microphone")) - d = "audio-input-microphone"; - else if (spa_streq(s, "webcam")) - d = "camera-web"; - else if (spa_streq(s, "computer")) - d = "computer"; - else if (spa_streq(s, "handset")) - d = "phone"; - else if (spa_streq(s, "portable")) - d = "multimedia-player"; - else if (spa_streq(s, "tv")) - d = "video-display"; - else if (spa_streq(s, "headset")) - d = "audio-headset"; - else if (spa_streq(s, "headphone")) - d = "audio-headphones"; - else if (spa_streq(s, "speaker")) - d = "audio-speakers"; - else if (spa_streq(s, "hands-free")) - d = "audio-handsfree"; - } - if (!d) - if ((s = pw_properties_get(p, PW_KEY_DEVICE_CLASS))) - if (spa_streq(s, "modem")) - d = "modem"; - - if (!d) { - if (is_sink) - d = "audio-card"; - else - d = "audio-input-microphone"; - } - - if ((s = pw_properties_get(p, "device.profile.name")) != NULL) { - if (strstr(s, "analog")) - s = "-analog"; - else if (strstr(s, "iec958")) - s = "-iec958"; - else if (strstr(s, "hdmi")) - s = "-hdmi"; - else - s = NULL; - } - - bus = pw_properties_get(p, PW_KEY_DEVICE_BUS); - - pw_properties_setf(p, PW_KEY_DEVICE_ICON_NAME, - "%s%s%s%s", d, s ? s : "", bus ? "-" : "", bus ? bus : ""); -} - -static struct node *alsa_create_node(struct device *device, uint32_t id, - const struct spa_device_object_info *info) -{ - struct node *node; - struct impl *impl = device->impl; - int res; - const char *dev, *subdev, *stream, *profile, *profile_desc, *rules, *str; - char tmp[1024]; - int i, priority; - - pw_log_debug("new node %u", id); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) { - errno = EINVAL; - return NULL; - } - node = calloc(1, sizeof(*node)); - if (node == NULL) { - res = -errno; - goto exit; - } - - node->props = pw_properties_new_dict(info->props); - - pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", device->device_id); - - pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); - - if (!device->use_acp && pw_properties_get(node->props, PW_KEY_AUDIO_CHANNELS) == NULL) - pw_properties_setf(node->props, PW_KEY_AUDIO_CHANNELS, "%d", SPA_AUDIO_MAX_CHANNELS); - - if ((dev = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_DEVICE)) == NULL) - if ((dev = pw_properties_get(node->props, "alsa.device")) == NULL) - dev = "0"; - if ((subdev = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_SUBDEVICE)) == NULL) - if ((subdev = pw_properties_get(node->props, "alsa.subdevice")) == NULL) - subdev = "0"; - if ((stream = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_STREAM)) == NULL) - stream = "unknown"; - if ((profile = pw_properties_get(node->props, "device.profile.name")) == NULL) - profile = "unknown"; - profile_desc = pw_properties_get(node->props, "device.profile.description"); - - if (spa_streq(stream, "capture")) - node->direction = PW_DIRECTION_OUTPUT; - else - node->direction = PW_DIRECTION_INPUT; - - if (device->first) { - if (atol(dev) != 0) - device->priority -= 256; - device->first = false; - } - - priority = device->priority; - if (node->direction == PW_DIRECTION_OUTPUT) - priority += 1000; - priority -= atol(dev) * 16; - priority -= atol(subdev); - - if (spa_strstartswith(profile, "analog-")) - priority += 9; - else if (spa_strstartswith(profile, "iec958-")) - priority += 8; - - if (pw_properties_get(node->props, PW_KEY_PRIORITY_DRIVER) == NULL) { - pw_properties_setf(node->props, PW_KEY_PRIORITY_DRIVER, "%d", priority); - pw_properties_setf(node->props, PW_KEY_PRIORITY_SESSION, "%d", priority); - } - - if (pw_properties_get(node->props, SPA_KEY_MEDIA_CLASS) == NULL) { - if (node->direction == PW_DIRECTION_OUTPUT) - pw_properties_setf(node->props, SPA_KEY_MEDIA_CLASS, "Audio/Source"); - else - pw_properties_setf(node->props, SPA_KEY_MEDIA_CLASS, "Audio/Sink"); - } - if (pw_properties_get(node->props, PW_KEY_NODE_NICK) == NULL) { - const char *s; - - s = pw_properties_get(device->props, PW_KEY_DEVICE_NICK); - if (s == NULL) - s = pw_properties_get(device->props, SPA_KEY_API_ALSA_CARD_NAME); - if (s == NULL) - s = pw_properties_get(device->props, "alsa.card_name"); - - pw_properties_set(node->props, PW_KEY_NODE_NICK, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s", s)); - - } - if (pw_properties_get(node->props, SPA_KEY_NODE_NAME) == NULL) { - const char *devname, *d; - - if ((devname = pw_properties_get(device->props, SPA_KEY_DEVICE_NAME)) == NULL) - devname = "unnamed-device"; - if (spa_strstartswith(devname, "alsa_card.")) - devname += 10; - - pw_properties_set(node->props, SPA_KEY_NODE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "%s.%s.%s", - node->direction == PW_DIRECTION_OUTPUT ? - "alsa_input" : "alsa_output", devname, profile)); - - for (i = 2; i <= 99; i++) { - if ((d = pw_properties_get(node->props, PW_KEY_NODE_NAME)) == NULL) - break; - - if (alsa_find_node(device, SPA_ID_INVALID, d) == NULL) - break; - - pw_properties_set(node->props, SPA_KEY_NODE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "%s.%s.%s.%d", - node->direction == PW_DIRECTION_OUTPUT ? - "alsa_input" : "alsa_output", devname, profile, i)); - } - } - if (pw_properties_get(node->props, PW_KEY_NODE_DESCRIPTION) == NULL) { - const char *desc, *name = NULL; - - if ((desc = pw_properties_get(device->props, SPA_KEY_DEVICE_DESCRIPTION)) == NULL) - desc = "unknown"; - - name = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_NAME); - if (name == NULL) - name = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_ID); - if (name == NULL) - name = dev; - - if (profile_desc != NULL) { - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s %s", desc, profile_desc)); - } else if (!spa_streq(subdev, "0")) { - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s (%s %s)", desc, name, subdev)); - } else if (!spa_streq(dev, "0")) { - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s (%s)", desc, name)); - } else { - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s", desc)); - } - } - if (pw_properties_get(node->props, PW_KEY_DEVICE_ICON_NAME) == NULL) - update_icon_name(node->props, node->direction == PW_DIRECTION_INPUT); - - if ((str = pw_properties_get(device->props, PW_KEY_DEVICE_BUS)) != NULL) - pw_properties_set(node->props, PW_KEY_DEVICE_BUS, str); - if ((str = pw_properties_get(device->props, PW_KEY_DEVICE_BUS_PATH)) != NULL) - pw_properties_set(node->props, PW_KEY_DEVICE_BUS_PATH, str); - if ((str = pw_properties_get(device->props, PW_KEY_DEVICE_FORM_FACTOR)) != NULL) - pw_properties_set(node->props, PW_KEY_DEVICE_FORM_FACTOR, str); - - node->impl = impl; - node->device = device; - node->id = id; - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), node->props); - - node->snode = sm_media_session_create_node(impl->session, - "adapter", - &node->props->dict); - if (node->snode == NULL) { - res = -errno; - goto clean_node; - } - - node->snode->obj.methods = SPA_CALLBACKS_INIT(&node_methods, node); - - spa_list_append(&device->node_list, &node->link); - - return node; - -clean_node: - pw_properties_free(node->props); - free(node); -exit: - errno = -res; - return NULL; -} - -static void alsa_remove_node(struct device *device, struct node *node) -{ - pw_log_debug("remove node %u", node->id); - spa_list_remove(&node->link); - sm_object_destroy(&node->snode->obj); - pw_properties_free(node->props); - free(node); -} - -static void alsa_device_info(void *data, const struct spa_device_info *info) -{ - struct device *device = data; - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(device->props, info->props); -} - -static void alsa_device_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct device *device = data; - struct node *node; - - node = alsa_find_node(device, id, NULL); - - if (info == NULL) { - if (node == NULL) { - pw_log_warn("device %p: unknown node %u", device, id); - return; - } - alsa_remove_node(device, node); - } else if (node == NULL) { - alsa_create_node(device, id, info); - } else { - alsa_update_node(device, node, info); - } -} - -static void alsa_device_event(void *data, const struct spa_event *event) -{ - struct device *device = data; - struct node *node; - uint32_t id, type; - struct spa_pod *props = NULL; - - if (spa_pod_parse_object(&event->pod, - SPA_TYPE_EVENT_Device, &type, - SPA_EVENT_DEVICE_Object, SPA_POD_Int(&id), - SPA_EVENT_DEVICE_Props, SPA_POD_OPT_Pod(&props)) < 0) - return; - - if ((node = alsa_find_node(device, id, NULL)) == NULL) - return; - - switch (type) { - case SPA_DEVICE_EVENT_ObjectConfig: - if (props && !node->snode->obj.destroyed) - pw_node_set_param((struct pw_node*)node->snode->obj.proxy, - SPA_PARAM_Props, 0, props); - break; - default: - break; - } -} - -static const struct spa_device_events alsa_device_events = { - SPA_VERSION_DEVICE_EVENTS, - .info = alsa_device_info, - .object_info = alsa_device_object_info, - .event = alsa_device_event, -}; - -static struct device *alsa_find_device(struct impl *impl, uint32_t id, const char *name) -{ - struct device *device; - const char *str; - - spa_list_for_each(device, &impl->device_list, link) { - if (device->id == id) - return device; - if (name != NULL && - (str = pw_properties_get(device->props, PW_KEY_DEVICE_NAME)) != NULL && - spa_streq(str, name)) - return device; - } - return NULL; -} - -static void alsa_update_device(struct impl *impl, struct device *device, - const struct spa_device_object_info *info) -{ - pw_log_debug("update device %u", device->id); - - if (info->change_mask & SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS) { - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(device->props, info->props); - } -} - -static int update_device_props(struct device *device) -{ - struct pw_properties *p = device->props; - const char *s, *d; - char temp[32], tmp[1024]; - int i; - - s = pw_properties_get(p, SPA_KEY_DEVICE_NAME); - if (s == NULL) - s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_ID); - if (s == NULL) - s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_PATH); - if (s == NULL) { - snprintf(temp, sizeof(temp), "%d", device->id); - s = temp; - } - pw_properties_set(p, PW_KEY_DEVICE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "alsa_card.%s", s)); - - for (i = 2; i <= 99; i++) { - if ((d = pw_properties_get(p, PW_KEY_DEVICE_NAME)) == NULL) - break; - - if (alsa_find_device(device->impl, SPA_ID_INVALID, d) == NULL) - break; - - pw_properties_set(p, PW_KEY_DEVICE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "alsa_card.%s.%d", s, i)); - } - if (i == 99) - return -EEXIST; - - if (pw_properties_get(p, PW_KEY_DEVICE_DESCRIPTION) == NULL) { - d = NULL; - - if ((s = pw_properties_get(p, PW_KEY_DEVICE_FORM_FACTOR))) - if (spa_streq(s, "internal")) - d = _("Built-in Audio"); - if (!d) - if ((s = pw_properties_get(p, PW_KEY_DEVICE_CLASS))) - if (spa_streq(s, "modem")) - d = _("Modem"); - if (!d) - d = pw_properties_get(p, PW_KEY_DEVICE_PRODUCT_NAME); - - if (!d) - d = pw_properties_get(p, SPA_KEY_API_ALSA_CARD_NAME); - if (!d) - d = pw_properties_get(p, "alsa.card_name"); - if (!d) - d = _("Unknown device"); - - pw_properties_set(p, PW_KEY_DEVICE_DESCRIPTION, d); - } - - if (pw_properties_get(p, PW_KEY_DEVICE_NICK) == NULL) { - s = pw_properties_get(p, SPA_KEY_API_ALSA_CARD_NAME); - if (s != NULL) - pw_properties_set(p, PW_KEY_DEVICE_NICK, s); - } - - if (pw_properties_get(p, PW_KEY_DEVICE_ICON_NAME) == NULL) - update_icon_name(device->props, true); - - return 1; -} - -static void set_profile(struct device *device, int index) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - if (device->use_acp) - return; - - pw_log_debug("%p: set profile %d id:%d", device, index, device->device_id); - - if (device->device_id != 0) { - device->profile = index; - spa_device_set_param(device->device, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); - } -} - -static void set_jack_profile(struct impl *impl, int index) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - if (impl->jack_device == NULL) - return; - - pw_device_set_param((struct pw_device*)impl->jack_device, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); -} - -static void remove_jack_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = impl->session->loop; - - if (impl->jack_timeout) { - pw_loop_destroy_source(main_loop, impl->jack_timeout); - impl->jack_timeout = NULL; - } -} - -static void jack_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - remove_jack_timeout(impl); - set_jack_profile(impl, 1); -} - -static void add_jack_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = impl->session->loop; - - if (impl->jack_timeout == NULL) - impl->jack_timeout = pw_loop_add_timer(main_loop, jack_timeout, impl); - - value.tv_sec = DEFAULT_JACK_SECONDS; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->jack_timeout, &value, NULL, false); -} - -static void do_reserve_acquired(struct device *device) -{ - if (!device->probed) - probe_device(device); - - if (device->n_acquired == 0 && device->reserve) - rd_device_release(device->reserve); -} - -static void reserve_acquired(void *data, struct rd_device *d) -{ - struct device *device = data; - pw_log_info("%p: reserve acquired %d", device, device->n_acquired); - do_reserve_acquired(device); -} - -static void complete_release(struct device *device) -{ - if (device->reserve) - rd_device_complete_release(device->reserve, true); -} - -static void sync_complete_done(void *data, int seq) -{ - struct device *device = data; - - pw_log_debug("%d %d", device->seq, seq); - if (seq != device->seq) - return; - - spa_hook_remove(&device->sync_listener); - device->seq = 0; - - complete_release(device); -} - -static void sync_destroy(void *data) -{ - struct device *device = data; - if (device->seq != 0) - sync_complete_done(data, device->seq); -} - -static const struct pw_proxy_events sync_complete_release = { - PW_VERSION_PROXY_EVENTS, - .destroy = sync_destroy, - .done = sync_complete_done -}; - -static void reserve_release(void *data, struct rd_device *d, int forced) -{ - struct device *device = data; - - pw_log_info("%p: reserve release", device); - if (device->sdevice == NULL || device->sdevice->obj.proxy == NULL) { - complete_release(device); - return; - } - - set_profile(device, 0); - - if (device->seq == 0) - pw_proxy_add_listener(device->sdevice->obj.proxy, - &device->sync_listener, - &sync_complete_release, device); - device->seq = pw_proxy_sync(device->sdevice->obj.proxy, 0); -} - -static void reserve_busy(void *data, struct rd_device *d, const char *name, int32_t prio) -{ - struct device *device = data; - struct impl *impl = device->impl; - - pw_log_info("%p: reserve busy %s", device, name); - if (device->sdevice == NULL) - return ; - - device->sdevice->locked = true; - - if (spa_streq(name, "jack")) { - add_jack_timeout(impl); - } else { - remove_jack_timeout(impl); - } -} - -static void reserve_available(void *data, struct rd_device *d, const char *name) -{ - struct device *device = data; - struct impl *impl = device->impl; - - pw_log_info("%p: reserve available %s", device, name); - if (device->sdevice == NULL) - return ; - - device->sdevice->locked = false; - - remove_jack_timeout(impl); - if (spa_streq(name, "jack")) { - set_jack_profile(impl, 0); - } -} - -static const struct rd_device_callbacks reserve_callbacks = { - .acquired = reserve_acquired, - .release = reserve_release, - .busy = reserve_busy, - .available = reserve_available, -}; - -static int do_device_acquire(struct device *device) -{ - struct impl *impl = device->impl; - struct sm_media_session *session = impl->session; - int res; - - if (!impl->reserve) - goto done; - - if (device->reserve == NULL) { - const char *reserve; - const char *path = pw_properties_get(device->props, SPA_KEY_API_ALSA_PATH); - DBusConnection *conn = NULL; - - if (session->dbus_connection) - conn = spa_dbus_connection_get(session->dbus_connection); - if (conn == NULL) { - pw_log_warn("no dbus connection, device reservation disabled"); - goto done; - } - pw_log_debug("got dbus connection %p", conn); - - reserve = pw_properties_get(device->props, "api.dbus.ReserveDevice1"); - if (reserve == NULL) - goto done; - - device->reserve = rd_device_new(conn, reserve, - "PipeWire", -10, - &reserve_callbacks, device); - - if (device->reserve == NULL) { - pw_log_warn("can't create device reserve for %s: %m", reserve); - goto done; - } else if (path) { - rd_device_set_application_device_name(device->reserve, path); - } else { - pw_log_warn("empty reserve device path for %s", reserve); - } - } - res = rd_device_acquire(device->reserve); - if (res < 0 && res != -EBUSY) { - pw_log_warn("device reserve failed, disabling: %s", spa_strerror(res)); - goto done; - } - return res; - -done: - do_reserve_acquired(device); - if (device->reserve != NULL) { - rd_device_destroy(device->reserve); - device->reserve = NULL; - } - return 0; -} - -static void device_destroy(void *data) -{ - struct device *device = data; - struct node *node; - - pw_log_debug("device %p destroy", device); - spa_list_remove(&device->link); - - spa_list_consume(node, &device->node_list, link) - alsa_remove_node(device, node); - - if (device->appeared) - spa_hook_remove(&device->device_listener); - if (device->seq != 0) - spa_hook_remove(&device->sync_listener); - if (device->reserve) - rd_device_destroy(device->reserve); -} - -static void device_free(void *data) -{ - struct device *device = data; - pw_log_debug("device %p free", device); - spa_hook_remove(&device->listener); - free(device->factory_name); - pw_unload_spa_handle(device->handle); - pw_properties_free(device->props); - sm_object_discard(&device->sdevice->obj); - free(device); -} - -static void device_update(void *data) -{ - struct device *device = data; - - pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); - - if (!device->appeared) { - device->device_id = device->sdevice->obj.id; - device->appeared = true; - - spa_device_add_listener(device->device, - &device->device_listener, - &alsa_device_events, device); - sm_object_sync_update(&device->sdevice->obj); - } - if (device->pending_profile != device->profile && !device->sdevice->locked) - set_profile(device, device->pending_profile); -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .destroy = device_destroy, - .free = device_free, - .update = device_update, -}; - -static int probe_device(struct device *device) -{ - struct impl *impl = device->impl; - struct pw_context *context = impl->session->context; - struct spa_handle *handle; - void *iface; - int res; - - if (device->probed) - return 0; - - handle = pw_context_load_spa_handle(context, - device->factory_name, &device->props->dict); - if (handle == NULL) { - res = -errno; - pw_log_error("can't make factory instance: %m"); - goto exit; - } - - if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { - pw_log_error("can't get %s interface: %s", SPA_TYPE_INTERFACE_Device, - spa_strerror(res)); - goto unload_handle; - } - - device->handle = handle; - device->device = iface; - - device->sdevice = sm_media_session_export_device(impl->session, - &device->props->dict, device->device); - if (device->sdevice == NULL) { - res = -errno; - goto unload_handle; - } - sm_object_add_listener(&device->sdevice->obj, - &device->listener, - &device_events, device); - - device->probed = true; - - return 0; - -unload_handle: - pw_unload_spa_handle(handle); -exit: - return res; -} - -static struct device *alsa_create_device(struct impl *impl, uint32_t id, - const struct spa_device_object_info *info) -{ - struct device *device; - int res; - const char *card, *rules; - - pw_log_debug("new device %u", id); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { - errno = EINVAL; - return NULL; - } - - device = calloc(1, sizeof(*device)); - if (device == NULL) { - res = -errno; - goto exit; - } - - device->impl = impl; - device->id = id; - device->props = pw_properties_new_dict(info->props); - device->priority = 1000; - device->first = true; - spa_list_init(&device->node_list); - update_device_props(device); - device->pending_profile = 1; - spa_list_append(&impl->device_list, &device->link); - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), device->props); - - device->use_acp = pw_properties_get_bool(device->props, "api.alsa.use-acp", true); - if (device->use_acp) - device->factory_name = strdup(SPA_NAME_API_ALSA_ACP_DEVICE); - else - device->factory_name = strdup(info->factory_name); - - if ((card = spa_dict_lookup(info->props, SPA_KEY_API_ALSA_CARD)) != NULL) { - device->priority -= atol(card) * 64; - pw_properties_setf(device->props, "api.dbus.ReserveDevice1", "Audio%s", card); - } - do_device_acquire(device); - - return device; -exit: - errno = -res; - return NULL; -} - -static void alsa_remove_device(struct impl *impl, struct device *device) -{ - pw_log_debug("%p: remove device %u", device, device->id); - if (device->sdevice) - sm_object_destroy(&device->sdevice->obj); -} - -static void alsa_udev_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct impl *impl = data; - struct device *device; - - device = alsa_find_device(impl, id, NULL); - - if (info == NULL) { - if (device == NULL) - return; - alsa_remove_device(impl, device); - } else if (device == NULL) { - if ((device = alsa_create_device(impl, id, info)) == NULL) - return; - } else { - alsa_update_device(impl, device, info); - } - sm_media_session_schedule_rescan(impl->session); -} - -static const struct spa_device_events alsa_udev_events = -{ - SPA_VERSION_DEVICE_EVENTS, - .object_info = alsa_udev_object_info, -}; - -static int alsa_start_jack_device(struct impl *impl) -{ - struct pw_properties *props; - int res = 0; - - props = pw_properties_new( - SPA_KEY_FACTORY_NAME, SPA_NAME_API_JACK_DEVICE, - SPA_KEY_NODE_NAME, "JACK-Device", - NULL); - - impl->jack_device = sm_media_session_create_object(impl->session, - "spa-device-factory", - PW_TYPE_INTERFACE_Device, - PW_VERSION_DEVICE, - &props->dict, - 0); - - if (impl->jack_device == NULL) { - pw_log_error("can't create JACK Device: %m"); - res = -errno; - } - - pw_properties_free(props); - - return res; -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_jack_timeout(impl); - spa_hook_remove(&impl->session_listener); - spa_hook_remove(&impl->listener); - if (impl->jack_device) - pw_proxy_destroy(impl->jack_device); - pw_unload_spa_handle(impl->handle); - pw_properties_free(impl->props); - pw_properties_free(impl->conf); - free(impl); -} - -static void session_dbus_disconnected(void *data) -{ - struct device *d; - struct impl *impl = data; - - spa_list_for_each(d, &impl->device_list, link) { - if (d->reserve != NULL) - rd_device_destroy(d->reserve); - d->reserve = NULL; - } -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, - .dbus_disconnected = session_dbus_disconnected, -}; - -int sm_alsa_monitor_start(struct sm_media_session *session) -{ - struct pw_context *context = session->context; - struct impl *impl; - void *iface; - int res; - const char *str; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->conf = pw_properties_new(NULL, NULL); - if (impl->conf == NULL) { - res = -errno; - goto out_free; - } - - if ((res = sm_media_session_load_conf(impl->session, - SESSION_CONF, impl->conf)) < 0) - pw_log_info("can't load "SESSION_CONF" config: %s", spa_strerror(res)); - - if ((impl->props = pw_properties_new(NULL, NULL)) == NULL) { - res = -errno; - goto out_free; - } - if ((str = pw_properties_get(impl->conf, "properties")) != NULL) - pw_properties_update_string(impl->props, str, strlen(str)); - - impl->reserve = pw_properties_get_bool(impl->props, "alsa.reserve", true); - - impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_ALSA_ENUM_UDEV, NULL); - if (impl->handle == NULL) { - res = -errno; - pw_log_info("can't load %s: %m", SPA_NAME_API_ALSA_ENUM_UDEV); - goto out_free; - } - - if ((res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { - pw_log_error("can't get udev Device interface: %d", res); - goto out_free; - } - impl->monitor = iface; - spa_list_init(&impl->device_list); - spa_device_add_listener(impl->monitor, &impl->listener, &alsa_udev_events, impl); - - if (pw_properties_get_bool(impl->props, "alsa.jack-device", true)) { - if ((res = alsa_start_jack_device(impl)) < 0) - goto out_free; - } - - sm_media_session_add_listener(session, &impl->session_listener, &session_events, impl); - - return 0; - -out_free: - if (impl->handle) - pw_unload_spa_handle(impl->handle); - pw_properties_free(impl->conf); - pw_properties_free(impl->props); - free(impl); - return res; -} diff --git a/src/media-session/alsa-no-dsp.c b/src/media-session/alsa-no-dsp.c deleted file mode 100644 index b990e0a40..000000000 --- a/src/media-session/alsa-no-dsp.c +++ /dev/null @@ -1,49 +0,0 @@ -/* PipeWire - * - * Copyright © 2021 Collabora Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_no_dsp Media Session Module: No DSP - * - * Instruct \ref page_media_session_module_policy_node to not configure audio - * adapter nodes in DSP mode. Device nodes will always be configured in - * passthrough mode. If a client node wants to be linked with a device node - * that has a different format, then the policy will configure the client node - * in convert mode so that both nodes have the same format. - * - * This is done by just setting a session property flag, and policy-node does the rest. - */ - -#define KEY_NAME "policy-node.alsa-no-dsp" - -int sm_alsa_no_dsp_start(struct sm_media_session *session) -{ - pw_properties_set(session->props, KEY_NAME, "true"); - return 0; -} diff --git a/src/media-session/bluez-autoswitch.c b/src/media-session/bluez-autoswitch.c deleted file mode 100644 index babc3641a..000000000 --- a/src/media-session/bluez-autoswitch.c +++ /dev/null @@ -1,701 +0,0 @@ -/* PipeWire - * - * Copyright © 2021 Pauli Virtanen - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_bluez_autoswitch Media Session Module: BlueZ Auto-Switch - * - * Switch profiles of Bluetooth devices trying to enable an input route, - * if input streams are active while default output is directed to - * the device. Profiles are restored once there are no active input streams. - * - * Not all input streams are considered, with behavior depending on - * configuration file settings. - */ - -#define NAME "bluez-autoswitch" -#define SESSION_KEY "bluez-autoswitch" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define RESTORE_DELAY_SEC 3 - -#define DEFAULT_AUDIO_SINK_KEY "default.audio.sink" - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - - struct spa_hook meta_listener; - - unsigned int record_count; - unsigned int communication_count; - - struct pw_context *context; - struct spa_source *restore_timeout; - - char *default_sink; - - struct pw_properties *properties; - bool switched; -}; - -struct node { - struct sm_node *obj; - struct impl *impl; - struct spa_hook listener; - unsigned char active:1; - unsigned char communication:1; - unsigned char was_active:1; -}; - -struct find_data { - const char *type; - const char *name; - uint32_t id; - struct sm_object *obj; -}; - -static int find_check(void *data, struct sm_object *object) -{ - struct find_data *d = data; - - if (!spa_streq(object->type, d->type) || !object->props) - return 0; - - if (d->id != SPA_ID_INVALID && d->id == object->id) { - d->obj = object; - return 1; - } - - if (d->name != NULL && - spa_streq(pw_properties_get(object->props, PW_KEY_NODE_NAME), d->name)) { - d->obj = object; - return 1; - } - - return 0; -} - -static struct sm_object *find_by_name(struct impl *impl, const char *type, const char *name) -{ - struct find_data d = { type, name, SPA_ID_INVALID, NULL }; - if (name != NULL) - sm_media_session_for_each_object(impl->session, find_check, &d); - return d.obj; -} - -static struct sm_object *find_by_id(struct impl *impl, const char *type, uint32_t id) -{ - struct find_data d = { type, NULL, id, NULL }; - if (id != SPA_ID_INVALID) - sm_media_session_for_each_object(impl->session, find_check, &d); - return d.obj; -} - -static struct sm_device *find_default_output_device(struct impl *impl) -{ - struct sm_object *obj; - uint32_t device_id; - - if ((obj = find_by_name(impl, PW_TYPE_INTERFACE_Node, impl->default_sink)) == NULL || - !obj->props) - return NULL; - - if (pw_properties_fetch_uint32(obj->props, PW_KEY_DEVICE_ID, &device_id) < 0) - return NULL; - - if ((obj = find_by_id(impl, PW_TYPE_INTERFACE_Device, device_id)) == NULL) - return NULL; - - if (!spa_streq(obj->type, PW_TYPE_INTERFACE_Device) || !obj->props) - return NULL; - - return SPA_CONTAINER_OF(obj, struct sm_device, obj); -} - -static int find_profile(struct sm_device *dev, int32_t index, const char *name, - int32_t *out_index, const char **out_name, int32_t *out_priority) -{ - struct sm_param *p; - - spa_list_for_each(p, &dev->param_list, link) { - int32_t idx; - int32_t prio = 0; - const char *str; - - if (p->id != SPA_PARAM_EnumProfile || !p->param) - continue; - - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamProfile, NULL, - SPA_PARAM_PROFILE_index, SPA_POD_Int(&idx), - SPA_PARAM_PROFILE_name, SPA_POD_String(&str), - SPA_PARAM_PROFILE_priority, SPA_POD_OPT_Int(&prio)) < 0) - continue; - - if ((index < 0 || idx == index) && (name == NULL || spa_streq(str, name))) { - if (out_index) - *out_index = idx; - if (out_name) - *out_name = str; - if (out_priority) - *out_priority = prio; - return 0; - } - } - - return -ENOENT; -} - -static int set_profile(struct sm_device *dev, const char *profile_name) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - int32_t index = -1; - int ret; - - if (!profile_name) - return -EINVAL; - - if (!dev->obj.proxy) - return -ENOENT; - - if ((ret = find_profile(dev, -1, profile_name, &index, NULL, NULL)) < 0) - return ret; - - pw_log_info("switching device %d to profile %s", dev->obj.id, profile_name); - - return pw_device_set_param((struct pw_device *)dev->obj.proxy, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); -} - -const char *get_saved_profile(struct impl *impl, const char *dev_name) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:profile", dev_name); - return pw_properties_get(impl->properties, saved_profile_key); -} - -void set_saved_profile(struct impl *impl, const char *dev_name, const char *profile_name) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:profile", dev_name); - pw_properties_set(impl->properties, saved_profile_key, profile_name); -} - -bool get_pending_save(struct impl *impl, const char *dev_name) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:pending-save", dev_name); - return pw_properties_get_bool(impl->properties, saved_profile_key, false); -} - -void set_pending_save(struct impl *impl, const char *dev_name, bool pending) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:pending-save", dev_name); - pw_properties_set(impl->properties, saved_profile_key, pending ? "true" : NULL); -} - -const char *get_saved_headset_profile(struct impl *impl, const char *dev_name) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:headset-profile", dev_name); - return pw_properties_get(impl->properties, saved_profile_key); -} - -void set_saved_headset_profile(struct impl *impl, const char *dev_name, const char *profile_name) -{ - char saved_profile_key[512]; - spa_scnprintf(saved_profile_key, sizeof(saved_profile_key), "%s:headset-profile", dev_name); - pw_properties_set(impl->properties, saved_profile_key, profile_name); -} - -static int do_restore_profile(void *data, struct sm_object *obj) -{ - struct impl *impl = data; - struct sm_device *dev; - const char *dev_name; - const char *profile_name; - struct sm_param *p; - - /* Find old profile and restore it */ - - if (!spa_streq(obj->type, PW_TYPE_INTERFACE_Device) || !obj->props) - goto next; - if ((dev_name = pw_properties_get(obj->props, PW_KEY_DEVICE_NAME)) == NULL) - goto next; - if ((profile_name = get_saved_profile(impl, dev_name)) == NULL) - goto next; - - dev = SPA_CONTAINER_OF(obj, struct sm_device, obj); - - /* Save user-selected headset profile */ - if (get_pending_save(impl, dev_name)) { - spa_list_for_each(p, &dev->param_list, link) { - const char *name; - if (p->id != SPA_PARAM_Profile || !p->param) - continue; - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamProfile, NULL, - SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) - continue; - set_saved_headset_profile(impl, dev_name, name); - set_pending_save(impl, dev_name, false); - break; - } - } - - /* Restore previous profile */ - set_profile(dev, profile_name); - set_saved_profile(impl, dev_name, NULL); - -next: - return 0; -} - -static void remove_restore_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - if (impl->restore_timeout) { - pw_loop_destroy_source(main_loop, impl->restore_timeout); - impl->restore_timeout = NULL; - } -} - -static void restore_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - int res; - - remove_restore_timeout(impl); - - /* - * Switching profiles may make applications remove existing input streams - * and create new ones. To avoid getting into a rapidly spinning loop, - * restoring profiles has to be done with a timeout. - */ - - /* Restore previous profiles to devices */ - sm_media_session_for_each_object(impl->session, do_restore_profile, impl); - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->properties)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - - impl->switched = false; -} - -static void add_restore_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (!impl->switched) - return; - - if (impl->restore_timeout == NULL) - impl->restore_timeout = pw_loop_add_timer(main_loop, restore_timeout, impl); - - value.tv_sec = RESTORE_DELAY_SEC; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->restore_timeout, &value, NULL, false); -} - -static void switch_profile_if_needed(struct impl *impl) -{ - struct sm_device *dev; - struct sm_param *p; - int headset_profile_priority = -1; - const char *current_profile_name = NULL; - const char *headset_profile_name = NULL; - enum spa_direction direction; - const char *dev_name; - const char *saved_headset_profile = NULL; - const char *str; - int res; - - if (impl->record_count == 0) - goto inactive; - - pw_log_debug("considering switching device profiles"); - - if ((dev = find_default_output_device(impl)) == NULL) - goto inactive; - - /* Handle only bluez devices */ - if (!spa_streq(pw_properties_get(dev->obj.props, PW_KEY_DEVICE_API), "bluez5")) - goto inactive; - if ((dev_name = pw_properties_get(dev->obj.props, PW_KEY_DEVICE_NAME)) == NULL) - goto inactive; - - /* Check autoswitch setting (default: role) */ - if ((str = pw_properties_get(dev->obj.props, "bluez5.autoswitch-profile")) == NULL) - str = "role"; - if (spa_atob(str)) { - /* ok */ - } else if (spa_streq(str, "role")) { - if (impl->communication_count == 0) - goto inactive; - } else { - goto inactive; - } - - /* BT microphone is wanted */ - - remove_restore_timeout(impl); - - if (get_saved_profile(impl, dev_name)) { - /* We already switched this device */ - return; - } - - /* Find saved headset profile, if any */ - saved_headset_profile = get_saved_headset_profile(impl, dev_name); - - /* Find current profile, and highest-priority profile with input route */ - spa_list_for_each(p, &dev->param_list, link) { - const char *name; - int32_t idx; - struct spa_pod *profiles = NULL; - - if (!p->param) - continue; - - switch (p->id) { - case SPA_PARAM_Route: - case SPA_PARAM_EnumRoute: - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamRoute, NULL, - SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction), - SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Pod(&profiles)) < 0) - continue; - if (direction != SPA_DIRECTION_INPUT) - continue; - - if (p->id == SPA_PARAM_Route) { - /* There's already an input route, no need to switch */ - return; - } else if (profiles) { - /* Take highest-priority (or first) profile in the input route */ - uint32_t *vals, n_vals, n; - vals = spa_pod_get_array(profiles, &n_vals); - if (vals == NULL) - continue; - for (n = 0; n < n_vals; ++n) { - int32_t i = vals[n]; - int32_t prio = -1; - const char *name = NULL; - if (find_profile(dev, i, NULL, NULL, &name, &prio) < 0) - continue; - if (headset_profile_priority < prio) { - headset_profile_priority = prio; - headset_profile_name = name; - } - } - } - break; - case SPA_PARAM_Profile: - case SPA_PARAM_EnumProfile: - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamProfile, NULL, - SPA_PARAM_PROFILE_index, SPA_POD_Int(&idx), - SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) - continue; - if (p->id == SPA_PARAM_Profile) { - current_profile_name = name; - } else if (spa_streq(name, saved_headset_profile)) { - /* Saved headset profile takes priority */ - headset_profile_priority = INT32_MAX; - headset_profile_name = name; - } - break; - } - } - - if (set_profile(dev, headset_profile_name) < 0) - return; - - set_saved_profile(impl, dev_name, current_profile_name); - set_pending_save(impl, dev_name, true); - - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->properties)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - - impl->switched = true; - return; - -inactive: - add_restore_timeout(impl); - return; -} - -static void change_node_state(struct node *node, bool active, bool communication) -{ - bool need_switch = false; - struct impl *impl = node->impl; - - node->was_active = node->was_active || active; - - if (node->active != active) { - impl->record_count += active ? 1 : -1; - node->active = active; - need_switch = true; - } - - if (node->communication != communication) { - impl->communication_count += communication ? 1 : -1; - node->communication = communication; - need_switch = true; - } - - if (need_switch) - switch_profile_if_needed(impl); -} - -static void check_node(struct node *node) -{ - const char *str; - bool communication = false; - - if (!node->obj || !node->obj->obj.props || !node->obj->info || !node->obj->info->props) - goto inactive; - - if (!spa_streq(pw_properties_get(node->obj->obj.props, PW_KEY_MEDIA_CLASS), "Stream/Input/Audio")) - goto inactive; - - if ((str = spa_dict_lookup(node->obj->info->props, PW_KEY_NODE_AUTOCONNECT)) == NULL || - !spa_atob(str)) - goto inactive; - - if ((str = spa_dict_lookup(node->obj->info->props, PW_KEY_STREAM_MONITOR)) != NULL && - spa_atob(str)) - goto inactive; - - /* - * XXX: This is not fully the right thing to do --- the node may be - * XXX: idle/suspended also because it's linked to a source that is not - * XXX: generating data. However, this seems the closest approximation - * XXX: to Pulse corked stream status. - */ - if (node->was_active && (node->obj->info->state == PW_NODE_STATE_SUSPENDED || - node->obj->info->state == PW_NODE_STATE_IDLE || - node->obj->info->state == PW_NODE_STATE_ERROR)) - goto inactive; - - if (spa_streq(pw_properties_get(node->obj->obj.props, PW_KEY_MEDIA_ROLE), "Communication")) - communication = true; - - change_node_state(node, true, communication); - return; - -inactive: - change_node_state(node, false, false); -} - -static void object_update(void *data) -{ - struct node *node = data; - if (node->obj->obj.avail & (SM_NODE_CHANGE_MASK_PARAMS | SM_NODE_CHANGE_MASK_INFO)) - check_node(node); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct node *node; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device) && object->props) { - const char *str; - - if ((str = pw_properties_get(object->props, PW_KEY_DEVICE_NAME)) != NULL) - set_pending_save(impl, str, false); - - impl->switched = true; - add_restore_timeout(impl); - return; - } - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Node) || !object->props) - return; - - if (!spa_streq(pw_properties_get(object->props, PW_KEY_MEDIA_CLASS), "Stream/Input/Audio")) - return; - - pw_log_debug("input stream %d added", object->id); - - node = sm_object_add_data(object, SESSION_KEY, sizeof(struct node)); - if (!node->obj) { - node->obj = (struct sm_node *)object; - node->impl = impl; - sm_object_add_listener(&node->obj->obj, &node->listener, &object_events, node); - } - - check_node(node); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct node *node; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Node)) - return; - - if ((node = sm_object_get_data(object, SESSION_KEY)) == NULL) - return; - - change_node_state(node, false, false); - - if (node->obj) { - pw_log_debug("input stream %d removed", object->id); - spa_hook_remove(&node->listener); - node->obj = NULL; - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_restore_timeout(impl); - spa_hook_remove(&impl->listener); - if (impl->session->metadata) - spa_hook_remove(&impl->meta_listener); - pw_properties_free(impl->properties); - free(impl->default_sink); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -static int json_object_find(const char *obj, const char *key, char *value, size_t len) -{ - struct spa_json it[2]; - const char *v; - char k[128]; - - spa_json_init(&it[0], obj, strlen(obj)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) { - if (spa_streq(k, key)) { - if (spa_json_get_string(&it[1], value, len) <= 0) - continue; - return 0; - } else { - if (spa_json_next(&it[1], &v) <= 0) - break; - } - } - return -ENOENT; -} - -static int metadata_property(void *object, uint32_t subject, - const char *key, const char *type, const char *value) -{ - struct impl *impl = object; - if (subject == PW_ID_CORE) { - char name[1024]; - - if (key && value && json_object_find(value, "name", name, sizeof(name)) < 0) - return 0; - - if (key == NULL || spa_streq(key, DEFAULT_AUDIO_SINK_KEY)) { - free(impl->default_sink); - impl->default_sink = (key && value) ? strdup(name) : NULL; - - /* Switch also when default output changes */ - switch_profile_if_needed(impl); - } - } - return 0; -} - -static const struct pw_metadata_events metadata_events = { - PW_VERSION_METADATA_EVENTS, - .property = metadata_property, -}; - -int sm_bluez5_autoswitch_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->properties = pw_properties_new(NULL, NULL); - if (impl->properties == NULL) { - free(impl); - return -ENOMEM; - } - - if ((res = sm_media_session_load_state(impl->session, SESSION_KEY, impl->properties)) < 0) - pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - if (session->metadata) { - pw_metadata_add_listener(session->metadata, - &impl->meta_listener, - &metadata_events, impl); - } - - return 0; -} diff --git a/src/media-session/bluez-endpoint.c b/src/media-session/bluez-endpoint.c deleted file mode 100644 index ef6e5afbf..000000000 --- a/src/media-session/bluez-endpoint.c +++ /dev/null @@ -1,702 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include - -#include "media-session.h" - -/** \page page_media_session_module_bluez_endpoint Media Session Module: BlueZ Endpoint - */ -#define NAME "bluez-endpoint" -#define SESSION_KEY "bluez-endpoint" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct endpoint { - struct spa_list link; - - struct impl *impl; - - struct pw_properties *props; - - struct node *node; - struct spa_hook listener; - - struct pw_client_endpoint *client_endpoint; - struct spa_hook proxy_listener; - struct spa_hook client_endpoint_listener; - struct pw_endpoint_info info; - - struct spa_param_info params[5]; - - struct endpoint *monitor; - - struct spa_audio_info format; - - struct spa_list stream_list; -}; - -struct stream { - struct spa_list link; - struct endpoint *endpoint; - - struct pw_properties *props; - struct pw_endpoint_stream_info info; - - struct spa_audio_info format; - - unsigned int active:1; -}; - -struct node { - struct impl *impl; - struct sm_node *node; - - struct device *device; - - struct endpoint *endpoint; -}; - -struct device { - struct impl *impl; - uint32_t id; - struct sm_device *device; - struct spa_hook listener; - - struct spa_list endpoint_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; -}; - -static int client_endpoint_set_session_id(void *object, uint32_t id) -{ - struct endpoint *endpoint = object; - endpoint->info.session_id = id; - return 0; -} - -static int client_endpoint_set_param(void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - pw_log_debug("%p: endpoint %p set param %d", impl, endpoint, id); - return pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, - id, flags, param); -} - - -static int client_endpoint_stream_set_param(void *object, uint32_t stream_id, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - return -ENOTSUP; -} - -static int stream_set_active(struct endpoint *endpoint, struct stream *stream, bool active) -{ - char buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - - if (stream->active == active) - return 0; - - if (active) { - stream->format.info.raw.rate = 48000; - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &stream->format.info.raw); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(endpoint->info.direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(true), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, - SPA_PARAM_PortConfig, 0, param); - } - stream->active = active; - return 0; -} - -static int client_endpoint_create_link(void *object, const struct spa_dict *props) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - struct pw_properties *p; - int res; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (props == NULL) - return -EINVAL; - - p = pw_properties_new_dict(props); - if (p == NULL) - return -errno; - - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - const char *str; - struct sm_object *obj; - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT); - if (str == NULL) { - pw_log_warn("%p: no target endpoint given", impl); - res = -EINVAL; - goto exit; - } - obj = sm_media_session_find_object(impl->session, atoi(str)); - if (obj == NULL || !spa_streq(obj->type, PW_TYPE_INTERFACE_Endpoint)) { - pw_log_warn("%p: could not find endpoint %s (%p)", impl, str, obj); - res = -EINVAL; - goto exit; - } - - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1"); - - pw_endpoint_create_link((struct pw_endpoint*)obj->proxy, &p->dict); - } else { - pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1"); - - sm_media_session_create_links(impl->session, &p->dict); - } - - res = 0; -exit: - pw_properties_free(p); - - return res; -} - -static const struct pw_client_endpoint_events client_endpoint_events = { - PW_VERSION_CLIENT_ENDPOINT_EVENTS, - .set_session_id = client_endpoint_set_session_id, - .set_param = client_endpoint_set_param, - .stream_set_param = client_endpoint_stream_set_param, - .create_link = client_endpoint_create_link, -}; - -static struct stream *endpoint_add_stream(struct endpoint *endpoint) -{ - struct stream *s; - const char *str; - - s = calloc(1, sizeof(*s)); - if (s == NULL) - return NULL; - - s->props = pw_properties_new(NULL, NULL); - s->endpoint = endpoint; - - if ((str = pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS)) != NULL) - pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str); - if ((str = pw_properties_get(endpoint->props, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(s->props, PW_KEY_PRIORITY_SESSION, str); - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - if (endpoint->monitor != NULL) - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Monitor"); - else - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Capture"); - } else { - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Playback"); - } - - s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - s->info.id = endpoint->info.n_streams; - s->info.endpoint_id = endpoint->info.id; - s->info.name = (char*)pw_properties_get(s->props, PW_KEY_ENDPOINT_STREAM_NAME); - s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; - s->info.props = &s->props->dict; - s->format = endpoint->format; - - pw_log_debug("stream %d", s->info.id); - pw_client_endpoint_stream_update(endpoint->client_endpoint, - s->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, - 0, NULL, - &s->info); - - spa_list_append(&endpoint->stream_list, &s->link); - endpoint->info.n_streams++; - - return s; -} - -static void destroy_stream(struct stream *stream) -{ - struct endpoint *endpoint = stream->endpoint; - - pw_client_endpoint_stream_update(endpoint->client_endpoint, - stream->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, - 0, NULL, - &stream->info); - - spa_list_remove(&stream->link); - endpoint->info.n_streams--; - - pw_properties_free(stream->props); - free(stream); -} - -static void update_params(void *data) -{ - uint32_t n_params; - const struct spa_pod **params; - struct endpoint *endpoint = data; - struct sm_node *node = endpoint->node->node; - struct sm_param *p; - - pw_log_debug("%p: endpoint", endpoint); - - params = alloca(sizeof(struct spa_pod *) * node->n_params); - n_params = 0; - spa_list_for_each(p, &node->param_list, link) { - switch (p->id) { - case SPA_PARAM_Props: - case SPA_PARAM_PropInfo: - params[n_params++] = p->param; - break; - default: - break; - } - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_PARAMS | - PW_CLIENT_ENDPOINT_UPDATE_INFO, - n_params, params, - &endpoint->info); -} - -static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor); - -static void object_update(void *data) -{ - struct endpoint *endpoint = data; - struct impl *impl = endpoint->impl; - struct sm_node *node = endpoint->node->node; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (node->obj.changed & SM_NODE_CHANGE_MASK_PARAMS) - update_params(endpoint); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void complete_endpoint(void *data) -{ - struct endpoint *endpoint = data; - struct stream *stream; - struct sm_param *p; - - pw_log_debug("endpoint %p: complete", endpoint); - - spa_list_for_each(p, &endpoint->node->node->param_list, link) { - struct spa_audio_info info = { 0, }; - - if (p->id != SPA_PARAM_EnumFormat) - continue; - - if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) - continue; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - continue; - - spa_pod_object_fixate((struct spa_pod_object*)p->param); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - if (spa_format_audio_raw_parse(p->param, &info.info.raw) < 0) - continue; - - if (endpoint->format.info.raw.channels < info.info.raw.channels) - endpoint->format = info; - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_INFO, - 0, NULL, - &endpoint->info); - - stream = endpoint_add_stream(endpoint); - - if (endpoint->info.direction == PW_DIRECTION_INPUT) { - struct endpoint *monitor; - - /* make monitor for sinks */ - monitor = create_endpoint(endpoint->node, endpoint); - if (monitor == NULL) - return; - - endpoint_add_stream(monitor); - } - stream_set_active(endpoint, stream, true); - - sm_object_add_listener(&endpoint->node->node->obj, &endpoint->listener, &object_events, endpoint); -} - -static void proxy_destroy(void *data) -{ - struct endpoint *endpoint = data; - struct stream *s; - - spa_list_consume(s, &endpoint->stream_list, link) - destroy_stream(s); - - pw_properties_free(endpoint->props); - spa_list_remove(&endpoint->link); - spa_hook_remove(&endpoint->proxy_listener); - spa_hook_remove(&endpoint->client_endpoint_listener); - endpoint->client_endpoint = NULL; -} - -static void proxy_bound(void *data, uint32_t id) -{ - struct endpoint *endpoint = data; - endpoint->info.id = id; -} - -static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = proxy_destroy, - .bound = proxy_bound, -}; - -static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor) -{ - struct impl *impl = node->impl; - struct device *device = node->device; - struct pw_properties *props; - struct endpoint *endpoint; - struct pw_proxy *proxy; - const char *str, *media_class = NULL, *name = NULL; - uint32_t subscribe[4], n_subscribe = 0; - struct pw_properties *pr = node->node->obj.props; - enum pw_direction direction; - - if (pr == NULL) { - errno = EINVAL; - return NULL; - } - - if ((media_class = pw_properties_get(pr, PW_KEY_MEDIA_CLASS)) == NULL) { - errno = EINVAL; - return NULL; - } - - if (strstr(media_class, "Source") != NULL) { - direction = PW_DIRECTION_OUTPUT; - } else if (strstr(media_class, "Sink") != NULL) { - direction = PW_DIRECTION_INPUT; - } else { - errno = EINVAL; - return NULL; - } - - props = pw_properties_new(NULL, NULL); - if (props == NULL) - return NULL; - - if (monitor != NULL) { - pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); - direction = PW_DIRECTION_OUTPUT; - } else { - pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class); - } - - if ((str = pw_properties_get(pr, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(props, PW_KEY_PRIORITY_SESSION, str); - if ((name = pw_properties_get(pr, PW_KEY_NODE_DESCRIPTION)) != NULL) { - if (monitor != NULL) { - pw_properties_setf(props, PW_KEY_ENDPOINT_NAME, "Monitor of %s", monitor->info.name); - pw_properties_setf(props, PW_KEY_ENDPOINT_MONITOR, "%d", monitor->info.id); - } else { - pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name); - } - } - if ((str = pw_properties_get(pr, PW_KEY_DEVICE_ICON_NAME)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str); - - proxy = sm_media_session_create_object(impl->session, - "client-endpoint", - PW_TYPE_INTERFACE_ClientEndpoint, - PW_VERSION_CLIENT_ENDPOINT, - &props->dict, sizeof(*endpoint)); - if (proxy == NULL) { - pw_properties_free(props); - return NULL; - } - - endpoint = pw_proxy_get_user_data(proxy); - endpoint->impl = impl; - endpoint->node = node; - endpoint->monitor = monitor; - endpoint->props = props; - endpoint->client_endpoint = (struct pw_client_endpoint *) proxy; - endpoint->info.version = PW_VERSION_ENDPOINT_INFO; - endpoint->info.name = (char*)pw_properties_get(endpoint->props, PW_KEY_ENDPOINT_NAME); - endpoint->info.media_class = (char*)pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS); - endpoint->info.session_id = impl->session->session->obj.id; - endpoint->info.direction = direction; - endpoint->info.flags = 0; - endpoint->info.change_mask = - PW_ENDPOINT_CHANGE_MASK_STREAMS | - PW_ENDPOINT_CHANGE_MASK_SESSION | - PW_ENDPOINT_CHANGE_MASK_PROPS | - PW_ENDPOINT_CHANGE_MASK_PARAMS; - endpoint->info.n_streams = 0; - endpoint->info.props = &endpoint->props->dict; - endpoint->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - endpoint->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - endpoint->info.params = endpoint->params; - endpoint->info.n_params = 2; - spa_list_init(&endpoint->stream_list); - - pw_log_debug("%p: new endpoint %p for bluez node %p", impl, endpoint, node); - pw_proxy_add_listener(proxy, - &endpoint->proxy_listener, - &proxy_events, endpoint); - - pw_client_endpoint_add_listener(endpoint->client_endpoint, - &endpoint->client_endpoint_listener, - &client_endpoint_events, - endpoint); - - subscribe[n_subscribe++] = SPA_PARAM_EnumFormat; - subscribe[n_subscribe++] = SPA_PARAM_Props; - subscribe[n_subscribe++] = SPA_PARAM_PropInfo; - pw_log_debug("%p: endpoint %p proxy %p subscribe %d params", impl, - endpoint, node->node->obj.proxy, n_subscribe); - pw_node_subscribe_params((struct pw_node*)node->node->obj.proxy, - subscribe, n_subscribe); - - spa_list_append(&device->endpoint_list, &endpoint->link); - - if (monitor == NULL) - sm_media_session_sync(impl->session, complete_endpoint, endpoint); - - return endpoint; -} - -static void destroy_endpoint(struct endpoint *endpoint) -{ - if (endpoint->client_endpoint) - pw_proxy_destroy((struct pw_proxy*)endpoint->client_endpoint); -} - -/** fallback, one stream for each node */ -static int setup_bluez_endpoint(struct device *device) -{ - struct impl *impl = device->impl; - struct sm_node *n; - struct sm_device *d = device->device; - - pw_log_debug("%p: device %p fallback", impl, d); - - spa_list_for_each(n, &d->node_list, link) { - struct node *node; - - pw_log_debug("%p: device %p has node %p", impl, d, n); - - node = sm_object_add_data(&n->obj, SESSION_KEY, sizeof(struct node)); - node->device = device; - node->node = n; - node->impl = impl; - node->endpoint = create_endpoint(node, NULL); - if (node->endpoint == NULL) - return -errno; - } - return 0; -} - -static int activate_device(struct device *device) -{ - return setup_bluez_endpoint(device); -} - -static int deactivate_device(struct device *device) -{ - struct endpoint *e; - spa_list_consume(e, &device->endpoint_list, link) - destroy_endpoint(e); - return 0; -} - -static void device_update(void *data) -{ - struct device *device = data; - struct impl *impl = device->impl; - - pw_log_debug("%p: device %p %08x %08x", impl, device, - device->device->obj.avail, device->device->obj.changed); - - if (!SPA_FLAG_IS_SET(device->device->obj.avail, - SM_DEVICE_CHANGE_MASK_INFO | - SM_DEVICE_CHANGE_MASK_NODES | - SM_DEVICE_CHANGE_MASK_PARAMS)) - return; - -// if (SPA_FLAG_IS_SET(device->device->obj.changed, -// SM_DEVICE_CHANGE_MASK_NODES | -// SM_DEVICE_CHANGE_MASK_PARAMS)) { - activate_device(device); -// } -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .update = device_update -}; - -static int -handle_device(struct impl *impl, struct sm_object *obj) -{ - const char *media_class, *str; - struct device *device; - - if (obj->props == NULL) - return 0; - - media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS); - str = pw_properties_get(obj->props, PW_KEY_DEVICE_API); - - pw_log_debug("%p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); - - if (!spa_strstartswith(media_class, "Audio/")) - return 0; - if (!spa_streq(str, "bluez5")) - return 0; - - device = sm_object_add_data(obj, SESSION_KEY, sizeof(struct device)); - device->impl = impl; - device->id = obj->id; - device->device = (struct sm_device*)obj; - spa_list_init(&device->endpoint_list); - pw_log_debug("%p: found bluez device %d media_class %s", impl, obj->id, media_class); - - sm_object_add_listener(obj, &device->listener, &device_events, device); - - return 0; -} - -static void destroy_device(struct impl *impl, struct device *device) -{ - deactivate_device(device); - spa_hook_remove(&device->listener); - sm_object_remove_data((struct sm_object*)device->device, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) - res = handle_device(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d: %s", impl, - object->id, spa_strerror(res)); - } -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) { - struct device *device; - if ((device = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_device(impl, device); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_bluez5_endpoint_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - sm_media_session_add_listener(session, &impl->listener, &session_events, impl); - return 0; -} diff --git a/src/media-session/bluez-monitor.c b/src/media-session/bluez-monitor.c deleted file mode 100644 index 19c242890..000000000 --- a/src/media-session/bluez-monitor.c +++ /dev/null @@ -1,766 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/impl.h" -#include "media-session.h" - -/** \page page_media_session_module_bluez_monitor Media Session Module: BlueZ Monitor - */ - -#define NAME "bluez5-monitor" -#define SESSION_CONF "bluez-monitor.conf" -#define FEATURES_CONF "bluez-hardware.conf" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct device; - -struct node { - struct impl *impl; - enum pw_direction direction; - struct device *device; - struct spa_list link; - uint32_t id; - - struct pw_properties *props; - - struct pw_impl_node *adapter; - - struct sm_node *snode; -}; - -struct device { - struct impl *impl; - struct spa_list link; - uint32_t id; - uint32_t device_id; - - int priority; - int profile; - - struct pw_properties *props; - - struct spa_handle *handle; - struct spa_device *device; - struct spa_hook device_listener; - - struct sm_device *sdevice; - struct spa_hook listener; - - unsigned int appeared:1; - struct spa_list node_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook session_listener; - - bool have_info; - bool seat_active; - - struct pw_properties *conf; - struct pw_properties *props; - - struct spa_handle *handle; - - struct spa_device *monitor; - struct spa_hook listener; - - struct spa_list device_list; -}; - -static struct node *bluez5_find_node(struct device *device, uint32_t id) -{ - struct node *node; - - spa_list_for_each(node, &device->node_list, link) { - if (node->id == id) - return node; - } - return NULL; -} - -static void update_icon_name(struct pw_properties *p, bool is_sink) -{ - const char *s, *d = NULL, *bus; - - if ((s = pw_properties_get(p, PW_KEY_DEVICE_FORM_FACTOR))) { - if (spa_streq(s, "microphone")) - d = "audio-input-microphone"; - else if (spa_streq(s, "webcam")) - d = "camera-web"; - else if (spa_streq(s, "computer")) - d = "computer"; - else if (spa_streq(s, "handset")) - d = "phone"; - else if (spa_streq(s, "portable")) - d = "multimedia-player"; - else if (spa_streq(s, "tv")) - d = "video-display"; - else if (spa_streq(s, "headset")) - d = "audio-headset"; - else if (spa_streq(s, "headphone")) - d = "audio-headphones"; - else if (spa_streq(s, "speaker")) - d = "audio-speakers"; - else if (spa_streq(s, "hands-free")) - d = "audio-handsfree"; - } - if (!d) - if ((s = pw_properties_get(p, PW_KEY_DEVICE_CLASS))) - if (spa_streq(s, "modem")) - d = "modem"; - - if (!d) { - if (is_sink) - d = "audio-card"; - else - d = "audio-input-microphone"; - } - - if ((s = pw_properties_get(p, "device.profile.name")) != NULL) { - if (strstr(s, "analog")) - s = "-analog"; - else if (strstr(s, "iec958")) - s = "-iec958"; - else if (strstr(s, "hdmi")) - s = "-hdmi"; - else - s = NULL; - } - - bus = pw_properties_get(p, PW_KEY_DEVICE_BUS); - - pw_properties_setf(p, PW_KEY_DEVICE_ICON_NAME, - "%s%s%s%s", d, s ? s : "", bus ? "-" : "", bus ? bus : ""); -} - -static void bluez5_update_node(struct device *device, struct node *node, - const struct spa_device_object_info *info) -{ - pw_log_debug("update node %u", node->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); -} - -static struct node *bluez5_create_node(struct device *device, uint32_t id, - const struct spa_device_object_info *info) -{ - struct node *node; - struct impl *impl = device->impl; - struct pw_context *context = impl->session->context; - struct pw_impl_factory *factory; - int res; - const char *prefix, *str, *profile, *rules; - int priority; - char tmp[1024]; - bool is_sink; - - pw_log_debug("new node %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) { - errno = EINVAL; - return NULL; - } - node = calloc(1, sizeof(*node)); - if (node == NULL) { - res = -errno; - goto exit; - } - - node->props = pw_properties_new_dict(info->props); - - if (pw_properties_get(node->props, PW_KEY_DEVICE_FORM_FACTOR) == NULL) - pw_properties_set(node->props, PW_KEY_DEVICE_FORM_FACTOR, - pw_properties_get(device->props, PW_KEY_DEVICE_FORM_FACTOR)); - if (pw_properties_get(node->props, PW_KEY_DEVICE_BUS) == NULL) - pw_properties_set(node->props, PW_KEY_DEVICE_BUS, - pw_properties_get(device->props, PW_KEY_DEVICE_BUS)); - - str = pw_properties_get(device->props, SPA_KEY_DEVICE_DESCRIPTION); - if (str == NULL) - str = pw_properties_get(device->props, SPA_KEY_DEVICE_NAME); - if (str == NULL) - str = pw_properties_get(device->props, SPA_KEY_DEVICE_NICK); - if (str == NULL) - str = pw_properties_get(device->props, SPA_KEY_DEVICE_ALIAS); - if (str == NULL) - str = "bluetooth-device"; - - pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", device->device_id); - - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s", str)); - - profile = pw_properties_get(node->props, SPA_KEY_API_BLUEZ5_PROFILE); - if (profile == NULL) - profile = "unknown"; - str = pw_properties_get(node->props, SPA_KEY_API_BLUEZ5_ADDRESS); - if (str == NULL) - str = pw_properties_get(device->props, SPA_KEY_DEVICE_NAME); - - is_sink = strstr(info->factory_name, "sink") != NULL; - if (is_sink) - prefix = "bluez_output"; - else if (strstr(info->factory_name, "source") != NULL) - prefix = "bluez_input"; - else - prefix = info->factory_name; - - pw_properties_set(node->props, PW_KEY_NODE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "%s.%s.%s", prefix, str, profile)); - - pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); - - if (pw_properties_get(node->props, PW_KEY_PRIORITY_DRIVER) == NULL) { - priority = device->priority + 10; - - if (strstr(info->factory_name, "source") != NULL) - priority += 1000; - - pw_properties_setf(node->props, PW_KEY_PRIORITY_DRIVER, "%d", priority); - pw_properties_setf(node->props, PW_KEY_PRIORITY_SESSION, "%d", priority); - } - if (pw_properties_get(node->props, PW_KEY_DEVICE_ICON_NAME) == NULL) - update_icon_name(node->props, is_sink); - - node->impl = impl; - node->device = device; - node->id = id; - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), node->props); - - factory = pw_context_find_factory(context, "adapter"); - if (factory == NULL) { - pw_log_error("no adapter factory found"); - res = -EIO; - goto clean_node; - } - node->adapter = pw_impl_factory_create_object(factory, - NULL, - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE, - pw_properties_copy(node->props), - 0); - if (node->adapter == NULL) { - res = -errno; - goto clean_node; - } - node->snode = sm_media_session_export_node(impl->session, - &node->props->dict, - node->adapter); - if (node->snode == NULL) { - res = -errno; - goto clean_node; - } - - spa_list_append(&device->node_list, &node->link); - - bluez5_update_node(device, node, info); - - return node; - -clean_node: - pw_properties_free(node->props); - free(node); -exit: - errno = -res; - return NULL; -} - -static void bluez5_remove_node(struct device *device, struct node *node) -{ - pw_log_debug("remove node %u", node->id); - spa_list_remove(&node->link); - sm_object_destroy(&node->snode->obj); - pw_impl_node_destroy(node->adapter); - pw_properties_free(node->props); - free(node); -} - -static void bluez5_device_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct device *device = data; - struct node *node; - - node = bluez5_find_node(device, id); - - if (info == NULL) { - if (node == NULL) { - pw_log_warn("device %p: unknown node %u", device, id); - return; - } - bluez5_remove_node(device, node); - } else if (node == NULL) { - bluez5_create_node(device, id, info); - } else { - bluez5_update_node(device, node, info); - } - -} - -static void bluez_device_event(void *data, const struct spa_event *event) -{ - struct device *device = data; - struct node *node; - uint32_t id, type; - struct spa_pod *props = NULL; - - if (spa_pod_parse_object(&event->pod, - SPA_TYPE_EVENT_Device, &type, - SPA_EVENT_DEVICE_Object, SPA_POD_Int(&id), - SPA_EVENT_DEVICE_Props, SPA_POD_OPT_Pod(&props)) < 0) - return; - - if ((node = bluez5_find_node(device, id)) == NULL) { - pw_log_warn("device %p: unknown node %d", device, id); - return; - } - - switch (type) { - case SPA_DEVICE_EVENT_ObjectConfig: - if (props != NULL) { - struct spa_node *adapter; - adapter = pw_impl_node_get_implementation(node->adapter); - spa_node_set_param(adapter, SPA_PARAM_Props, 0, props); - } - break; - default: - break; - } -} - -static const struct spa_device_events bluez5_device_events = { - SPA_VERSION_DEVICE_EVENTS, - .object_info = bluez5_device_object_info, - .event = bluez_device_event, -}; - -static struct device *bluez5_find_device(struct impl *impl, uint32_t id) -{ - struct device *device; - - spa_list_for_each(device, &impl->device_list, link) { - if (device->id == id) - return device; - } - return NULL; -} - -static int update_device_props(struct device *device) -{ - struct pw_properties *p = device->props; - const char *s; - char temp[32], tmp[1024]; - - s = pw_properties_get(p, SPA_KEY_DEVICE_NAME); - if (s == NULL) - s = pw_properties_get(p, SPA_KEY_API_BLUEZ5_ADDRESS); - if (s == NULL) - s = pw_properties_get(p, SPA_KEY_DEVICE_DESCRIPTION); - if (s == NULL) { - snprintf(temp, sizeof(temp), "%d", device->id); - s = temp; - } - if (spa_strstartswith(s, "bluez_card.")) - s += strlen("bluez_card."); - - pw_properties_set(p, PW_KEY_DEVICE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "bluez_card.%s", s)); - - if (pw_properties_get(p, SPA_KEY_DEVICE_ICON_NAME) == NULL) - update_icon_name(p, true); - - return 0; -} - -static void device_destroy(void *data) -{ - struct device *device = data; - struct node *node; - - pw_log_debug("device %p destroy", device); - - spa_hook_remove(&device->listener); - - if (device->appeared) { - device->appeared = false; - spa_hook_remove(&device->device_listener); - } - - spa_list_consume(node, &device->node_list, link) - bluez5_remove_node(device, node); -} - -static void device_update(void *data) -{ - struct device *device = data; - - pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); - - if (device->appeared) - return; - - device->device_id = device->sdevice->obj.id; - device->appeared = true; - - spa_device_add_listener(device->device, - &device->device_listener, - &bluez5_device_events, device); - - sm_object_sync_update(&device->sdevice->obj); -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .destroy = device_destroy, - .update = device_update, -}; - -static struct device *bluez5_create_device(struct impl *impl, uint32_t id, - const struct spa_device_object_info *info) -{ - struct pw_context *context = impl->session->context; - struct device *device; - struct spa_handle *handle; - int res; - void *iface; - const char *rules, *str; - - pw_log_debug("new device %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { - errno = EINVAL; - return NULL; - } - - device = calloc(1, sizeof(*device)); - if (device == NULL) { - res = -errno; - goto exit; - } - - device->impl = impl; - device->id = id; - device->priority = 1000; - device->props = pw_properties_new_dict(info->props); - update_device_props(device); - - spa_list_init(&device->node_list); - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), device->props); - - /* Propagate the msbc-support global property if it exists and is not - * overloaded by a device specific one */ - if ((str = pw_properties_get(impl->props, "bluez5.msbc-support")) != NULL && - pw_properties_get(device->props, "bluez5.msbc-support") == NULL) - pw_properties_set(device->props, "bluez5.msbc-support", str); - - handle = pw_context_load_spa_handle(context, - info->factory_name, - &device->props->dict); - if (handle == NULL) { - res = -errno; - pw_log_error("can't make factory instance: %m"); - goto clean_device; - } - - if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { - pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); - goto unload_handle; - } - - device->handle = handle; - device->device = iface; - - spa_list_append(&impl->device_list, &device->link); - - return device; - -unload_handle: - pw_unload_spa_handle(handle); -clean_device: - pw_properties_free(device->props); - free(device); -exit: - errno = -res; - return NULL; -} - -static void bluez5_device_free(struct device *device) -{ - if (device->sdevice) { - sm_object_destroy(&device->sdevice->obj); - device->sdevice = NULL; - } - spa_list_remove(&device->link); - pw_unload_spa_handle(device->handle); - pw_properties_free(device->props); - free(device); -} - -static void bluez5_remove_device(struct impl *impl, struct device *device) -{ - - pw_log_debug("remove device %u", device->id); - bluez5_device_free(device); -} - -static void bluez5_update_device(struct impl *impl, struct device *device, - const struct spa_device_object_info *info) -{ - bool connected; - const char *str; - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_log_debug("update device %u", device->id); - - pw_properties_update(device->props, info->props); - update_device_props(device); - - str = spa_dict_lookup(info->props, SPA_KEY_API_BLUEZ5_CONNECTION); - connected = str != NULL && spa_streq(str, "connected"); - - /* Export device after bluez profiles get connected */ - if (device->sdevice == NULL && connected) { - device->sdevice = sm_media_session_export_device(impl->session, - &device->props->dict, device->device); - if (device->sdevice == NULL) { - bluez5_device_free(device); - return; - } - - sm_object_add_listener(&device->sdevice->obj, - &device->listener, - &device_events, device); - } else if (device->sdevice != NULL && !connected) { - sm_object_destroy(&device->sdevice->obj); - device->sdevice = NULL; - } -} - -static void bluez5_enum_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct impl *impl = data; - struct device *device; - - device = bluez5_find_device(impl, id); - - if (info == NULL) { - if (device == NULL) - return; - bluez5_remove_device(impl, device); - } else if (device == NULL) { - if (bluez5_create_device(impl, id, info) == NULL) - return; - } else { - bluez5_update_device(impl, device, info); - } -} - -static const struct spa_device_events bluez5_enum_callbacks = -{ - SPA_VERSION_DEVICE_EVENTS, - .object_info = bluez5_enum_object_info, -}; - -static void unload_bluez_handle(struct impl *impl) -{ - struct device *device; - - if (impl->handle == NULL) - return; - - spa_list_consume(device, &impl->device_list, link) - bluez5_device_free(device); - - spa_hook_remove(&impl->listener); - - pw_unload_spa_handle(impl->handle); - impl->handle = NULL; -} - -static int load_bluez_handle(struct impl *impl) -{ - struct pw_context *context = impl->session->context; - void *iface; - int res; - - if (impl->handle != NULL || !impl->seat_active || !impl->have_info) - return 0; - - impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_BLUEZ5_ENUM_DBUS, &impl->props->dict); - if (impl->handle == NULL) { - res = -errno; - pw_log_info("can't load %s: %m", SPA_NAME_API_BLUEZ5_ENUM_DBUS); - goto fail; - } - if ((res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { - pw_log_error("can't get Device interface: %s", spa_strerror(res)); - goto fail; - } - impl->monitor = iface; - - spa_device_add_listener(impl->monitor, &impl->listener, - &bluez5_enum_callbacks, impl); - - return 0; - -fail: - if (impl->handle) - pw_unload_spa_handle(impl->handle); - impl->handle = NULL; - return res; -} - -static void session_info(void *data, const struct pw_core_info *info) -{ - struct impl *impl = data; - - if (info && (info->change_mask & PW_CORE_CHANGE_MASK_PROPS)) { - const char *str; - - if ((str = spa_dict_lookup(info->props, "default.clock.rate")) != NULL && - pw_properties_get(impl->props, "bluez5.default.rate") == NULL) { - pw_properties_set(impl->props, "bluez5.default.rate", str); - } - - impl->have_info = true; - load_bluez_handle(impl); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - - spa_hook_remove(&impl->session_listener); - - unload_bluez_handle(impl); - - pw_properties_free(impl->props); - pw_properties_free(impl->conf); - free(impl); -} - -static void seat_active(void *data, bool active) -{ - struct impl *impl = data; - - impl->seat_active = active; - - if (impl->seat_active) { - pw_log_info("seat active, starting bluetooth"); - load_bluez_handle(impl); - } else { - pw_log_info("seat not active, stopping bluetooth"); - unload_bluez_handle(impl); - } -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .info = session_info, - .destroy = session_destroy, - .seat_active = seat_active, -}; - -int sm_bluez5_monitor_start(struct sm_media_session *session) -{ - int res; - struct impl *impl; - const char *str; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) { - res = -errno; - goto out; - } - impl->session = session; - impl->seat_active = true; - - spa_list_init(&impl->device_list); - - if ((impl->conf = pw_properties_new(NULL, NULL)) == NULL) { - res = -errno; - goto out_free; - } - if ((res = sm_media_session_load_conf(impl->session, - SESSION_CONF, impl->conf)) < 0) - pw_log_info("can't load "SESSION_CONF" config: %s", spa_strerror(res)); - - if ((impl->props = pw_properties_new(NULL, NULL)) == NULL) { - res = -errno; - goto out_free; - } - if ((str = pw_properties_get(impl->conf, "properties")) != NULL) - pw_properties_update_string(impl->props, str, strlen(str)); - - pw_properties_set(impl->props, "api.bluez5.connection-info", "true"); - - sm_media_session_add_listener(session, &impl->session_listener, - &session_events, impl); - return 0; - -out_free: - pw_properties_free(impl->conf); - pw_properties_free(impl->props); - free(impl); -out: - return res; -} diff --git a/src/media-session/default-nodes.c b/src/media-session/default-nodes.c deleted file mode 100644 index 9004e8b20..000000000 --- a/src/media-session/default-nodes.c +++ /dev/null @@ -1,207 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_default_nodes Media Session Module: Default Nodes - */ - -#define NAME "default-nodes" -#define SESSION_KEY "default-nodes" - -#define SAVE_INTERVAL 1 - -#define DEFAULT_CONFIG_AUDIO_SINK_KEY "default.configured.audio.sink" -#define DEFAULT_CONFIG_AUDIO_SOURCE_KEY "default.configured.audio.source" -#define DEFAULT_CONFIG_VIDEO_SOURCE_KEY "default.configured.video.source" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - struct spa_source *idle_timeout; - - struct spa_hook meta_listener; - - struct pw_properties *properties; -}; - -static bool is_default_key(const char *key) -{ - return spa_streq(key, DEFAULT_CONFIG_AUDIO_SINK_KEY) || - spa_streq(key, DEFAULT_CONFIG_AUDIO_SOURCE_KEY) || - spa_streq(key, DEFAULT_CONFIG_VIDEO_SOURCE_KEY); -} - -static void remove_idle_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - int res; - - if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, - SESSION_KEY, impl->properties)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - pw_loop_destroy_source(main_loop, impl->idle_timeout); - impl->idle_timeout = NULL; - } -} - -static void idle_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - pw_log_debug("%p: idle timeout", impl); - remove_idle_timeout(impl); -} - -static void add_idle_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (impl->idle_timeout == NULL) - impl->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, impl); - - value.tv_sec = SAVE_INTERVAL; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->idle_timeout, &value, NULL, false); -} - -static int metadata_property(void *object, uint32_t subject, - const char *key, const char *type, const char *value) -{ - struct impl *impl = object; - int changed = 0; - - if (subject == PW_ID_CORE) { - if (key == NULL) { - pw_properties_clear(impl->properties); - changed++; - } else { - if (!is_default_key(key)) - return 0; - - changed += pw_properties_set(impl->properties, key, value); - } - } - if (changed) - add_idle_timeout(impl); - - return 0; -} - -static const struct pw_metadata_events metadata_events = { - PW_VERSION_METADATA_EVENTS, - .property = metadata_property, -}; - -static void load_metadata(struct impl *impl) -{ - const struct spa_dict_item *item; - - spa_dict_for_each(item, &impl->properties->dict) { - if (!is_default_key(item->key)) - continue; - - if (impl->session->metadata != NULL) { - pw_log_info("restoring %s=%s", item->key, item->value); - pw_metadata_set_property(impl->session->metadata, - PW_ID_CORE, item->key, "Spa:String:JSON", item->value); - } - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_idle_timeout(impl); - spa_hook_remove(&impl->listener); - if (impl->session->metadata) - spa_hook_remove(&impl->meta_listener); - pw_properties_free(impl->properties); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_default_nodes_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->properties = pw_properties_new(NULL, NULL); - if (impl->properties == NULL) { - free(impl); - return -ENOMEM; - } - - if ((res = sm_media_session_load_state(impl->session, - SESSION_KEY, impl->properties)) < 0) - pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - if (session->metadata) { - pw_metadata_add_listener(session->metadata, - &impl->meta_listener, - &metadata_events, impl); - } - - load_metadata(impl); - - return 0; -} diff --git a/src/media-session/default-profile.c b/src/media-session/default-profile.c deleted file mode 100644 index 9a4637842..000000000 --- a/src/media-session/default-profile.c +++ /dev/null @@ -1,514 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_default_profile Media Session Module: Default Profile - * - * The default profile module restores a previously saved profile - * or otherwise the best available profile. - * - * The module tracks the \ref SPA_PARAM_Profile parameter on devices - * (excluding Bluetooth devices). - * When the profile is changed by an external party (e.g. `pavucontrol`), that - * profile is written to the state file. In the future, when the active - * profile is `"off"`, the previously saved profile (if available) is restored. - * - * If no saved profile exists, the best profile is restored. The rules for - * determining the best profile are: - * - the highest-priority available profile, or, if no profiles are available, - * - the highest-priority profile with availability unknown, or, if no such - * profile exists, - * - the `"off"` profile. - * - * \note The special profile named `"pro-audio"` is excluded from the above search. - * - * ## Module-specific properties - * - * This module stores its state in - * `$XDG_CONFIG_HOME/pipewire/media-sesssion.d/default-profile`: - * - * - `default.profile.$devicename = { "name": "$profilename" }`: stores the default - * profile for `$devicename` - * - * ## See also - * See \ref spa_param_availability for availability values. - */ - -#define NAME "default-profile" -#define SESSION_KEY "default-profile" -#define PREFIX "default.profile." - -#define SAVE_INTERVAL 1 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - struct spa_source *idle_timeout; - - struct spa_hook meta_listener; - - struct pw_properties *properties; - - unsigned int restore_bluetooth:1; -}; - -struct device { - struct sm_device *obj; - - uint32_t id; - struct impl *impl; - char *name; - char *key; - - struct spa_hook listener; - - unsigned int restore_saved_profile:1; - uint32_t best_profile; - uint32_t active_profile; -}; - -static void remove_idle_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - int res; - - if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, - SESSION_KEY, impl->properties)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - pw_loop_destroy_source(main_loop, impl->idle_timeout); - impl->idle_timeout = NULL; - } -} - -static void idle_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - pw_log_debug("%p: idle timeout", impl); - remove_idle_timeout(impl); -} - -static void add_idle_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (impl->idle_timeout == NULL) - impl->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, impl); - - value.tv_sec = SAVE_INTERVAL; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->idle_timeout, &value, NULL, false); -} - -struct profile { - struct sm_param *p; - uint32_t index; - const char *name; - uint32_t prio; - uint32_t available; - bool save; -}; - -static int parse_profile(struct sm_param *p, struct profile *pr) -{ - pr->p = p; - pr->prio = 0; - pr->available = SPA_PARAM_AVAILABILITY_unknown; - pr->save = false; - return spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamProfile, NULL, - SPA_PARAM_PROFILE_index, SPA_POD_Int(&pr->index), - SPA_PARAM_PROFILE_name, SPA_POD_String(&pr->name), - SPA_PARAM_PROFILE_priority, SPA_POD_OPT_Int(&pr->prio), - SPA_PARAM_PROFILE_available, SPA_POD_OPT_Id(&pr->available), - SPA_PARAM_PROFILE_save, SPA_POD_OPT_Bool(&pr->save)); -} - -static int find_current_profile(struct device *dev, struct profile *pr) -{ - struct sm_param *p; - spa_list_for_each(p, &dev->obj->param_list, link) { - if (p->id == SPA_PARAM_Profile && - parse_profile(p, pr) >= 0) - return 0; - } - return -ENOENT; -} - -static int find_best_profile(struct device *dev, struct profile *pr) -{ - struct sm_param *p; - struct profile best, best_avail, best_unk, off; - - spa_zero(best); - spa_zero(best_avail); - spa_zero(best_unk); - spa_zero(off); - - spa_list_for_each(p, &dev->obj->param_list, link) { - struct profile t; - - if (p->id != SPA_PARAM_EnumProfile || - parse_profile(p, &t) < 0) - continue; - - if (t.name && spa_streq(t.name, "pro-audio")) - continue; - - if (t.name && spa_streq(t.name, "off")) { - off = t; - } - else if (t.available == SPA_PARAM_AVAILABILITY_yes) { - if (best_avail.name == NULL || t.prio > best_avail.prio) - best_avail = t; - } - else if (t.available != SPA_PARAM_AVAILABILITY_no) { - if (best_unk.name == NULL || t.prio > best_unk.prio) - best_unk = t; - } - } - best = best_avail; - if (best.name == NULL) - best = best_unk; - if (best.name == NULL) - best = off; - if (best.name == NULL) - return -ENOENT; - *pr = best; - return 0; -} - -static int find_saved_profile(struct device *dev, struct profile *pr) -{ - struct spa_json it[2]; - struct impl *impl = dev->impl; - const char *json, *value; - char name[1024] = "\0", key[128]; - struct sm_param *p; - - json = pw_properties_get(impl->properties, dev->key); - if (json == NULL) - return -ENODEV; - - spa_json_init(&it[0], json, strlen(json)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - if (spa_streq(key, "name")) { - if (spa_json_get_string(&it[1], name, sizeof(name)) <= 0) - continue; - } else { - if (spa_json_next(&it[1], &value) <= 0) - break; - } - } - pw_log_debug("device '%s': find profile '%s'", dev->name, name); - - spa_list_for_each(p, &dev->obj->param_list, link) { - if (p->id != SPA_PARAM_EnumProfile || - parse_profile(p, pr) < 0) - continue; - - if (spa_streq(pr->name, name)) - return 0; - } - return -ENOENT; -} - -static int set_profile(struct device *dev, struct profile *pr) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - if (dev->active_profile == pr->index) - return 0; - - pw_device_set_param((struct pw_device*)dev->obj->obj.proxy, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(pr->index), - SPA_PARAM_PROFILE_save, SPA_POD_Bool(pr->save))); - - sm_media_session_schedule_rescan(dev->impl->session); - - return 0; -} - -static int handle_active_profile(struct device *dev) -{ - struct impl *impl = dev->impl; - struct profile pr; - int res; - - /* check if current profile changed */ - if ((res = find_current_profile(dev, &pr)) < 0) - return res; - - /* when the active profile is off, always try to restore the saved - * profile again */ - if (spa_streq(pr.name, "off")) - dev->restore_saved_profile = true; - - if (dev->active_profile == pr.index) { - /* no change, we're done */ - pw_log_info("device '%s': active profile '%s'", dev->name, pr.name); - return 0; - } - - /* we get here when we had configured a profile but something - * else changed it, in that case, save it when asked. */ - pw_log_info("device '%s': active profile changed to '%s'", dev->name, pr.name); - dev->active_profile = pr.index; - - if (!pr.save) - return 0; - - if (pw_properties_setf(impl->properties, dev->key, "{ \"name\": \"%s\" }", pr.name)) { - pw_log_info("device '%s': active profile saved as '%s'", dev->name, pr.name); - add_idle_timeout(impl); - } - return 0; -} - -static int handle_profile_switch(struct device *dev) -{ - struct profile saved, best; - int res; - bool changed = false; - - /* try to find the next best profile */ - res = find_best_profile(dev, &best); - if (res < 0) { - pw_log_info("device '%s': can't find best profile: %s", - dev->name, spa_strerror(res)); - best.index = SPA_ID_INVALID; - } else { - changed = dev->best_profile != best.index; - dev->best_profile = best.index; - pw_log_info("device '%s': found best profile '%s' changed:%d", - dev->name, best.name, changed); - } - if (dev->restore_saved_profile) { - /* try to restore our saved profile */ - res = find_saved_profile(dev, &saved); - if (res >= 0) { - /* we found a saved profile */ - if (saved.available == SPA_PARAM_AVAILABILITY_no) { - pw_log_info("device '%s': saved profile '%s' unavailable", - dev->name, saved.name); - } else { - pw_log_info("device '%s': found saved profile '%s'", - dev->name, saved.name); - /* make sure we save again */ - saved.save = true; - best = saved; - changed = true; - } - } else { - pw_log_info("device '%s': no saved profile: %s", - dev->name, spa_strerror(res)); - } - dev->restore_saved_profile = false; - } - - if (best.index != SPA_ID_INVALID && changed) { - if (dev->active_profile == best.index) { - pw_log_info("device '%s': best profile '%s' is already active", - dev->name, best.name); - } else { - pw_log_info("device '%s': restore best profile '%s' index %d", - dev->name, best.name, best.index); - set_profile(dev, &best); - } - } else if (res < 0) { - pw_log_warn("device '%s': can't restore profile: %s", dev->name, - spa_strerror(res)); - } else { - pw_log_info("device '%s': no profile switch needed", dev->name); - } - return 0; -} - -static int handle_profile(struct device *dev) -{ - /* check if current profile changed */ - handle_active_profile(dev); - - /* check if we need to switch profile */ - handle_profile_switch(dev); - - return 0; -} - -static void object_update(void *data) -{ - struct device *dev = data; - struct impl *impl = dev->impl; - const char *str; - - pw_log_debug("%p: device %p %08x/%08x", impl, dev, - dev->obj->obj.changed, dev->obj->obj.avail); - - if (dev->obj->info && dev->obj->info->props && - (str = spa_dict_lookup(dev->obj->info->props, PW_KEY_DEVICE_BUS)) != NULL && - spa_streq(str, "bluetooth") && !impl->restore_bluetooth) - return; - - if (dev->obj->obj.changed & SM_DEVICE_CHANGE_MASK_PARAMS) - handle_profile(dev); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct device *dev; - const char *name; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Device) || - object->props == NULL || - (name = pw_properties_get(object->props, PW_KEY_DEVICE_NAME)) == NULL) - return; - - pw_log_debug("%p: add device '%d' %s", impl, object->id, name); - - dev = sm_object_add_data(object, SESSION_KEY, sizeof(struct device)); - dev->obj = (struct sm_device*)object; - dev->id = object->id; - dev->impl = impl; - dev->name = strdup(name); - dev->key = spa_aprintf(PREFIX"%s", name); - dev->active_profile = SPA_ID_INVALID; - dev->best_profile = SPA_ID_INVALID; - - dev->obj->obj.mask |= SM_DEVICE_CHANGE_MASK_PARAMS; - sm_object_add_listener(&dev->obj->obj, &dev->listener, &object_events, dev); -} - -static void destroy_device(struct impl *impl, struct device *dev) -{ - spa_hook_remove(&dev->listener); - free(dev->name); - free(dev->key); - sm_object_remove_data((struct sm_object*)dev->obj, SESSION_KEY); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct device *dev; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Device)) - return; - - pw_log_debug("%p: remove device '%d'", impl, object->id); - - if ((dev = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_device(impl, dev); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_idle_timeout(impl); - spa_hook_remove(&impl->listener); - pw_properties_free(impl->properties); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_default_profile_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - const char *str; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->properties = pw_properties_new(NULL, NULL); - if (impl->properties == NULL) { - free(impl); - return -ENOMEM; - } - - if ((str = pw_properties_get(session->props, "default-profile.restore-bluetooth")) != NULL) - impl->restore_bluetooth = pw_properties_parse_bool(str); - - if ((res = sm_media_session_load_state(impl->session, - SESSION_KEY, impl->properties)) < 0) - pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - return 0; -} diff --git a/src/media-session/default-routes.c b/src/media-session/default-routes.c deleted file mode 100644 index 5a842faf7..000000000 --- a/src/media-session/default-routes.c +++ /dev/null @@ -1,990 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_default_routes Media Session Module: Default Routes - */ -#define NAME "default-routes" -#define SESSION_KEY "default-routes" -#define PREFIX "default.route." - -#define SAVE_INTERVAL 1 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - struct spa_source *idle_timeout; - - struct spa_hook meta_listener; - - struct pw_properties *to_restore; -}; - -struct device { - struct sm_device *obj; - - uint32_t id; - struct impl *impl; - char *name; - - struct spa_hook listener; - - uint32_t active_profile; - - uint32_t generation; - struct pw_array route_info; -}; - -static void remove_idle_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - int res; - - if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, - SESSION_KEY, impl->to_restore)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - pw_loop_destroy_source(main_loop, impl->idle_timeout); - impl->idle_timeout = NULL; - } -} - -static void idle_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - pw_log_debug("%p: idle timeout", impl); - remove_idle_timeout(impl); -} - -static void add_idle_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (impl->idle_timeout == NULL) - impl->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, impl); - - value.tv_sec = SAVE_INTERVAL; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->idle_timeout, &value, NULL, false); -} - -static uint32_t channel_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) - return spa_type_audio_channel[i].type; - } - return SPA_AUDIO_CHANNEL_UNKNOWN; -} - -static const char *channel_to_name(uint32_t channel) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_type_audio_channel[i].type == channel) - return spa_debug_type_short_name(spa_type_audio_channel[i].name); - } - return "UNK"; -} - -static uint32_t iec958Codec_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_iec958_codec[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_iec958_codec[i].name))) - return spa_type_audio_iec958_codec[i].type; - } - return SPA_AUDIO_IEC958_CODEC_UNKNOWN; -} - -static const char *iec958Codec_to_name(uint32_t codec) -{ - int i; - for (i = 0; spa_type_audio_iec958_codec[i].name; i++) { - if (spa_type_audio_iec958_codec[i].type == codec) - return spa_debug_type_short_name(spa_type_audio_iec958_codec[i].name); - } - return "UNKNOWN"; -} - -struct route_info { - uint32_t index; - uint32_t generation; - enum spa_param_availability available; - enum spa_param_availability prev_available; - enum spa_direction direction; - char name[64]; - unsigned int save:1; - unsigned int prev_active:1; - unsigned int active:1; -}; - -struct route { - struct sm_param *p; - uint32_t index; - uint32_t device_id; - enum spa_direction direction; - const char *name; - uint32_t priority; - enum spa_param_availability available; - struct spa_pod *props; - struct spa_pod *profiles; - bool save; -}; - -#define ROUTE_INIT(__p) (struct route) { \ - .p = (__p), \ - .available = SPA_PARAM_AVAILABILITY_unknown, \ - } - -static struct route_info *find_route_info(struct device *dev, const struct route *r) -{ - struct route_info *i; - - pw_array_for_each(i, &dev->route_info) { - if (i->index == r->index) - return i; - } - i = pw_array_add(&dev->route_info, sizeof(*i)); - if (i == NULL) - return NULL; - - pw_log_info("device %d: new route %d '%s' found", dev->id, r->index, r->name); - spa_zero(*i); - i->index = r->index; - snprintf(i->name, sizeof(i->name), "%s", r->name); - i->direction = r->direction; - i->generation = dev->generation; - i->available = r->available; - i->prev_available = r->available; - - return i; -} - -static int parse_route(struct sm_param *p, struct route *r) -{ - *r = ROUTE_INIT(p); - return spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamRoute, NULL, - SPA_PARAM_ROUTE_index, SPA_POD_Int(&r->index), - SPA_PARAM_ROUTE_direction, SPA_POD_Id(&r->direction), - SPA_PARAM_ROUTE_device, SPA_POD_Int(&r->device_id), - SPA_PARAM_ROUTE_name, SPA_POD_String(&r->name), - SPA_PARAM_ROUTE_priority, SPA_POD_OPT_Int(&r->priority), - SPA_PARAM_ROUTE_available, SPA_POD_OPT_Id(&r->available), - SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&r->props), - SPA_PARAM_ROUTE_save, SPA_POD_OPT_Bool(&r->save)); -} - -static bool array_contains(const struct spa_pod *pod, uint32_t val) -{ - uint32_t *vals, n_vals; - uint32_t n; - - if (pod == NULL) - return false; - vals = spa_pod_get_array(pod, &n_vals); - if (vals == NULL || n_vals == 0) - return false; - for (n = 0; n < n_vals; n++) - if (vals[n] == val) - return true; - return false; -} - -static int parse_enum_route(struct sm_param *p, uint32_t device_id, struct route *r) -{ - struct spa_pod *devices = NULL; - int res; - - *r = ROUTE_INIT(p); - if ((res = spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamRoute, NULL, - SPA_PARAM_ROUTE_index, SPA_POD_Int(&r->index), - SPA_PARAM_ROUTE_direction, SPA_POD_Id(&r->direction), - SPA_PARAM_ROUTE_name, SPA_POD_String(&r->name), - SPA_PARAM_ROUTE_priority, SPA_POD_OPT_Int(&r->priority), - SPA_PARAM_ROUTE_available, SPA_POD_OPT_Id(&r->available), - SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Pod(&devices), - SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Pod(&r->profiles))) < 0) - return res; - - if (device_id != SPA_ID_INVALID && !array_contains(devices, device_id)) - return -ENOENT; - - r->device_id = device_id; - return 0; -} - -static char *serialize_props(const struct device *dev, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - bool comma = false; - char *ptr; - size_t size; - FILE *f; - - f = open_memstream(&ptr, &size); - fprintf(f, "{"); - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_volume: - { - float val; - if (spa_pod_get_float(&prop->value, &val) < 0) - continue; - fprintf(f, "%s \"volume\": %f", (comma ? "," : ""), val); - break; - } - case SPA_PROP_mute: - { - bool b; - if (spa_pod_get_bool(&prop->value, &b) < 0) - continue; - fprintf(f, "%s \"mute\": %s", (comma ? "," : ""), b ? "true" : "false"); - break; - } - case SPA_PROP_channelVolumes: - { - uint32_t i, n_vals; - float vals[SPA_AUDIO_MAX_CHANNELS]; - - n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vals, SPA_AUDIO_MAX_CHANNELS); - if (n_vals == 0) - continue; - - fprintf(f, "%s \"volumes\": [", (comma ? "," : "")); - for (i = 0; i < n_vals; i++) - fprintf(f, "%s %f", (i == 0 ? "" : ","), vals[i]); - fprintf(f, " ]"); - break; - } - case SPA_PROP_channelMap: - { - uint32_t i, n_vals; - uint32_t map[SPA_AUDIO_MAX_CHANNELS]; - - n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - map, SPA_AUDIO_MAX_CHANNELS); - if (n_vals == 0) - continue; - - fprintf(f, "%s \"channels\": [", (comma ? "," : "")); - for (i = 0; i < n_vals; i++) - fprintf(f, "%s \"%s\"", (i == 0 ? "" : ","), channel_to_name(map[i])); - fprintf(f, " ]"); - break; - } - case SPA_PROP_latencyOffsetNsec: - { - int64_t delay; - if (spa_pod_get_long(&prop->value, &delay) < 0) - continue; - fprintf(f, "%s \"latencyOffsetNsec\": %"PRIi64, (comma ? "," : ""), delay); - break; - } - case SPA_PROP_iec958Codecs: - { - uint32_t i, codecs[64], n_codecs; - - n_codecs = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - codecs, sizeof(codecs)); - if (n_codecs == 0) - continue; - - fprintf(f, "%s \"iec958Codecs\": [", (comma ? "," : "")); - for (i = 0; i < n_codecs; i++) - fprintf(f, "%s \"%s\"", (i == 0 ? "" : ","), iec958Codec_to_name(codecs[i])); - fprintf(f, " ]"); - break; - } - default: - continue; - } - comma = true; - } - fprintf(f, " }"); - fclose(f); - return ptr; -} - -static int restore_route_params(struct device *dev, const char *val, const struct route *r) -{ - struct spa_json it[3]; - char buf[1024], key[128]; - const char *value; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - struct spa_pod_frame f[2]; - struct spa_pod *param; - - spa_json_init(&it[0], val, strlen(val)); - - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route); - spa_pod_builder_add(&b, - SPA_PARAM_ROUTE_index, SPA_POD_Int(r->index), - SPA_PARAM_ROUTE_device, SPA_POD_Int(r->device_id), - 0); - spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0); - spa_pod_builder_push_object(&b, &f[1], - SPA_TYPE_OBJECT_Props, SPA_PARAM_Route); - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - if (spa_streq(key, "volume")) { - float vol; - if (spa_json_get_float(&it[1], &vol) <= 0) - continue; - spa_pod_builder_prop(&b, SPA_PROP_volume, 0); - spa_pod_builder_float(&b, vol); - } - else if (spa_streq(key, "mute")) { - bool mute; - if (spa_json_get_bool(&it[1], &mute) <= 0) - continue; - spa_pod_builder_prop(&b, SPA_PROP_mute, 0); - spa_pod_builder_bool(&b, mute); - } - else if (spa_streq(key, "volumes")) { - uint32_t n_vols; - float vols[SPA_AUDIO_MAX_CHANNELS]; - - if (spa_json_enter_array(&it[1], &it[2]) <= 0) - continue; - - for (n_vols = 0; n_vols < SPA_AUDIO_MAX_CHANNELS; n_vols++) { - if (spa_json_get_float(&it[2], &vols[n_vols]) <= 0) - break; - } - if (n_vols == 0) - continue; - - spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0); - spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, - n_vols, vols); - } - else if (spa_streq(key, "channels")) { - uint32_t n_ch; - uint32_t map[SPA_AUDIO_MAX_CHANNELS]; - - if (spa_json_enter_array(&it[1], &it[2]) <= 0) - continue; - - for (n_ch = 0; n_ch < SPA_AUDIO_MAX_CHANNELS; n_ch++) { - char chname[16]; - if (spa_json_get_string(&it[2], chname, sizeof(chname)) <= 0) - break; - map[n_ch] = channel_from_name(chname); - } - if (n_ch == 0) - continue; - - spa_pod_builder_prop(&b, SPA_PROP_channelMap, 0); - spa_pod_builder_array(&b, sizeof(uint32_t), SPA_TYPE_Id, - n_ch, map); - } - else if (spa_streq(key, "latencyOffsetNsec")) { - float delay; - if (spa_json_get_float(&it[1], &delay) <= 0) - continue; - spa_pod_builder_prop(&b, SPA_PROP_latencyOffsetNsec, 0); - spa_pod_builder_long(&b, (int64_t)SPA_CLAMP(delay, INT64_MIN, INT64_MAX)); - } - else if (spa_streq(key, "iec958Codecs")) { - uint32_t n_codecs; - uint32_t codecs[64]; - - if (spa_json_enter_array(&it[1], &it[2]) <= 0) - continue; - - for (n_codecs = 0; n_codecs < 64; n_codecs++) { - char name[16]; - if (spa_json_get_string(&it[2], name, sizeof(name)) <= 0) - break; - codecs[n_codecs] = iec958Codec_from_name(name); - } - if (n_codecs == 0) - continue; - - spa_pod_builder_prop(&b, SPA_PROP_iec958Codecs, 0); - spa_pod_builder_array(&b, sizeof(uint32_t), SPA_TYPE_Id, - n_codecs, codecs); - } else { - if (spa_json_next(&it[1], &value) <= 0) - break; - } - } - spa_pod_builder_pop(&b, &f[1]); - spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_save, 0); - spa_pod_builder_bool(&b, r->save); - param = spa_pod_builder_pop(&b, &f[0]); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_device_set_param((struct pw_node*)dev->obj->obj.proxy, - SPA_PARAM_Route, 0, param); - - sm_media_session_schedule_rescan(dev->impl->session); - - return 0; -} - -struct profile { - uint32_t index; - const char *name; - struct spa_pod *classes; -}; - -static int parse_profile(const struct sm_param *p, struct profile *pr) -{ - int res; - spa_zero(*pr); - if ((res = spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamProfile, NULL, - SPA_PARAM_PROFILE_index, SPA_POD_Int(&pr->index), - SPA_PARAM_PROFILE_name, SPA_POD_String(&pr->name), - SPA_PARAM_PROFILE_classes, SPA_POD_OPT_Pod(&pr->classes))) < 0) - return res; - return 0; -} - -static int find_current_profile(const struct device *dev, struct profile *pr) -{ - struct sm_param *p; - spa_list_for_each(p, &dev->obj->param_list, link) { - if (p->id == SPA_PARAM_Profile && - parse_profile(p, pr) >= 0) - return 0; - } - return -ENOENT; -} - -static int restore_route(struct device *dev, const struct route *r) -{ - struct impl *impl = dev->impl; - char key[1024]; - const char *val; - struct route_info *ri; - - if ((ri = find_route_info(dev, r)) == NULL) - return -errno; - - snprintf(key, sizeof(key), PREFIX"%s:%s:%s", dev->name, - r->direction == SPA_DIRECTION_INPUT ? "input" : "output", r->name); - - val = pw_properties_get(impl->to_restore, key); - if (val == NULL) - val = "{ \"volumes\": [ 0.4 ], \"mute\": false }"; - - pw_log_info("device %d: restore route %d '%s' to %s", dev->id, r->index, key, val); - - restore_route_params(dev, val, r); - ri->prev_active = true; - ri->active = true; - ri->generation = dev->generation; - ri->save = r->save; - - return 0; -} - -static int save_route(const struct device *dev, const struct route *r) -{ - struct impl *impl = dev->impl; - char key[1024], *val; - - if (r->props == NULL) - return -EINVAL; - - snprintf(key, sizeof(key), PREFIX"%s:%s:%s", dev->name, - r->direction == SPA_DIRECTION_INPUT ? "input" : "output", r->name); - - val = serialize_props(dev, r->props); - if (pw_properties_set(impl->to_restore, key, val)) { - pw_log_info("device %d: route properties changed %s %s", dev->id, key, val); - add_idle_timeout(impl); - } - free(val); - return 0; -} - -static char *serialize_routes(const struct device *dev) -{ - char *ptr; - size_t size; - FILE *f; - const struct route_info *ri; - int count = 0; - - f = open_memstream(&ptr, &size); - fprintf(f, "["); - - pw_array_for_each(ri, &dev->route_info) { - if (ri->save) { - fprintf(f, "%s \"%s\"", count++ == 0 ? "" : ",", ri->name); - } - } - fprintf(f, " ]"); - fclose(f); - return ptr; -} - -static int save_profile(struct device *dev, const char *profile_name) -{ - struct impl *impl = dev->impl; - char key[1024], *val; - - if (pw_array_get_len(&dev->route_info, struct route_info) == 0) - return 0; - - snprintf(key, sizeof(key), PREFIX"%s:profile:%s", dev->name, profile_name); - - val = serialize_routes(dev); - if (pw_properties_set(impl->to_restore, key, val)) { - pw_log_info("device %d: profile %s routes changed %s %s", - dev->id, profile_name, key, val); - add_idle_timeout(impl); - } else { - pw_log_info("device %d: profile %s unchanged (%s)", - dev->id, profile_name, val); - } - free(val); - return 0; -} - -static int find_best_route(struct device *dev, uint32_t device_id, struct route *r) -{ - struct sm_param *p; - struct route best, best_avail, best_unk; - - spa_zero(best_avail); - spa_zero(best_unk); - - spa_list_for_each(p, &dev->obj->param_list, link) { - struct route t; - - if (p->id != SPA_PARAM_EnumRoute || - parse_enum_route(p, device_id, &t) < 0) - continue; - - if (t.available == SPA_PARAM_AVAILABILITY_yes || t.available == SPA_PARAM_AVAILABILITY_unknown) { - struct route_info *ri; - if ((ri = find_route_info(dev, &t)) && ri->direction == SPA_DIRECTION_OUTPUT && - ri->available != ri->prev_available) { - /* If route availability changed, that means a user just - * plugged in something like headphones, and they probably - * expect to hear sound from it. Switch to it immediately. - * - * TODO: switch INPUT ports without source and the input - * ports their source->active_port is part of a group of - * ports (see module-switch-on-port-available.c in PulseAudio). - */ - best_avail = t; - ri->save = true; - break; - } - else if (t.available == SPA_PARAM_AVAILABILITY_yes) { - if (best_avail.name == NULL || t.priority > best_avail.priority) - best_avail = t; - } else { // SPA_PARAM_AVAILABILITY_unknown - if (best_unk.name == NULL || t.priority > best_unk.priority) - best_unk = t; - } - } - } - best = best_avail; - if (best.name == NULL) - best = best_unk; - if (best.name == NULL) - return -ENOENT; - *r = best; - return 0; -} - -static int find_route(const struct device *dev, uint32_t device_id, const char *name, struct route *r) -{ - struct sm_param *p; - - spa_list_for_each(p, &dev->obj->param_list, link) { - if (p->id != SPA_PARAM_EnumRoute || - parse_enum_route(p, device_id, r) < 0) - continue; - if (!spa_streq(r->name, name)) - continue; - return 0; - } - return -ENOENT; -} - -static int find_saved_route(struct device *dev, const char *val, uint32_t device_id, struct route *r) -{ - struct spa_json it[2]; - char key[128]; - - if (val == NULL) - return -ENOENT; - - spa_json_init(&it[0], val, strlen(val)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - if (find_route(dev, device_id, key, r) >= 0) - return 0; - } - return -ENOENT; -} - -static int restore_device_route(struct device *dev, const char *val, uint32_t device_id, bool restore) -{ - int res = -ENOENT; - struct route t; - - pw_log_info("device %d: restoring device %u", dev->id, device_id); - - if (restore) { - res = find_saved_route(dev, val, device_id, &t); - if (res >= 0) { - /* we found a saved route */ - if (t.available == SPA_PARAM_AVAILABILITY_no) { - pw_log_info("device %d: saved route '%s' not available", dev->id, - t.name); - /* not available, try to find next best port */ - res = -ENOENT; - } else { - pw_log_info("device %d: found saved route '%s'", dev->id, - t.name); - /* make sure we save it again */ - t.save = true; - } - } - } - if (res < 0) { - /* we could not find a saved route, try to find a new best */ - res = find_best_route(dev, device_id, &t); - if (res < 0) { - pw_log_info("device %d: can't find best route", dev->id); - } else { - pw_log_info("device %d: found best route '%s'", dev->id, - t.name); - } - } - if (res >= 0) - restore_route(dev, &t); - - return res; -} - -static int reconfigure_profile(struct device *dev, struct profile *pr, bool restore) -{ - struct impl *impl = dev->impl; - char key[1024]; - const char *json; - - pw_log_info("device %s: restore routes for profile '%s'", - dev->name, pr->name); - dev->active_profile = pr->index; - - snprintf(key, sizeof(key), PREFIX"%s:profile:%s", dev->name, pr->name); - json = pw_properties_get(impl->to_restore, key); - - if (pr->classes != NULL) { - struct spa_pod *iter; - - SPA_POD_STRUCT_FOREACH(pr->classes, iter) { - struct spa_pod_parser prs; - struct spa_pod_frame f[1]; - struct spa_pod *val; - char *key; - - spa_pod_parser_pod(&prs, iter); - if (spa_pod_parser_push_struct(&prs, &f[0]) < 0) - continue; - - while (spa_pod_parser_get(&prs, - SPA_POD_String(&key), - SPA_POD_Pod(&val), - NULL) >= 0) { - if (key == NULL || val == NULL) - break; - if (spa_streq(key, "card.profile.devices")) { - uint32_t *devices, n_devices, i; - - devices = spa_pod_get_array(val, &n_devices); - if (devices == NULL || n_devices == 0) - continue; - - for (i = 0; i < n_devices; i++) - restore_device_route(dev, json, devices[i], restore); - } - } - spa_pod_parser_pop(&prs, &f[0]); - } - } - return 0; -} - -static void prune_route_info(struct device *dev) -{ - struct route_info *i; - - for (i = pw_array_first(&dev->route_info); - pw_array_check(&dev->route_info, i);) { - if (i->generation != dev->generation) { - pw_log_info("device %d: route '%s' unused", dev->id, i->name); - pw_array_remove(&dev->route_info, i); - } else - i++; - } -} - -static int handle_route(struct device *dev, const struct route *r) -{ - struct route_info *ri; - - pw_log_info("device %d: port '%s'", dev->id, r->name); - if ((ri = find_route_info(dev, r)) == NULL) - return -errno; - - ri->active = true; - ri->save = r->save; - - if (!ri->prev_active) { - /* a new port has been found, restore the volume and make sure we - * save this as a preferred port */ - pw_log_info("device %d: new active port found '%s'", dev->id, r->name); - restore_route(dev, r); - } else if (r->props && r->save) { - /* just save port properties */ - save_route(dev, r); - } - return 0; -} - -static int handle_device(struct device *dev) -{ - struct profile pr; - struct sm_param *p; - bool route_changed = false; - - dev->generation++; - - if (find_current_profile(dev, &pr) < 0) - pr.index = SPA_ID_INVALID; - - /* first look at all routes and update */ - spa_list_for_each(p, &dev->obj->param_list, link) { - struct route r; - struct route_info *ri; - - if (p->id != SPA_PARAM_EnumRoute || - parse_enum_route(p, SPA_ID_INVALID, &r) < 0) - continue; - - if ((ri = find_route_info(dev, &r)) == NULL) - continue; - - ri->prev_available = ri->available; - if (ri->available != r.available) { - pw_log_info("device %d: route %s available changed %d -> %d", - dev->id, r.name, ri->available, r.available); - ri->available = r.available; - if (array_contains(r.profiles, pr.index)) - route_changed = true; - } - ri->generation = dev->generation; - ri->prev_active = ri->active; - ri->active = false; - ri->save = false; - } - /* then check for changes in the active ports */ - spa_list_for_each(p, &dev->obj->param_list, link) { - struct route r; - if (p->id != SPA_PARAM_Route || - parse_route(p, &r) < 0) - continue; - handle_route(dev, &r); - } - - prune_route_info(dev); - - if (pr.index != SPA_ID_INVALID) { - bool restore = dev->active_profile != pr.index; - if (restore || route_changed) - reconfigure_profile(dev, &pr, restore); - - save_profile(dev, pr.name); - } - return 0; -} - -static void object_update(void *data) -{ - struct device *dev = data; - struct impl *impl = dev->impl; - - pw_log_debug("%p: device %p %08x/%08x", impl, dev, - dev->obj->obj.changed, dev->obj->obj.avail); - - if (dev->obj->obj.changed & SM_DEVICE_CHANGE_MASK_PARAMS) - handle_device(dev); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct device *dev; - const char *name; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Device) || - object->props == NULL || - (name = pw_properties_get(object->props, PW_KEY_DEVICE_NAME)) == NULL) - return; - - pw_log_debug("%p: add device '%d' %s", impl, object->id, name); - - dev = sm_object_add_data(object, SESSION_KEY, sizeof(struct device)); - dev->obj = (struct sm_device*)object; - dev->id = object->id; - dev->impl = impl; - dev->name = strdup(name); - dev->active_profile = SPA_ID_INVALID; - dev->generation = 0; - pw_array_init(&dev->route_info, sizeof(struct route_info) * 16); - - dev->obj->obj.mask |= SM_DEVICE_CHANGE_MASK_PARAMS; - sm_object_add_listener(&dev->obj->obj, &dev->listener, &object_events, dev); -} - -static void destroy_device(struct impl *impl, struct device *dev) -{ - spa_hook_remove(&dev->listener); - pw_array_clear(&dev->route_info); - free(dev->name); - sm_object_remove_data((struct sm_object*)dev->obj, SESSION_KEY); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct device *dev; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Device)) - return; - - pw_log_debug("%p: remove device '%d'", impl, object->id); - - if ((dev = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_device(impl, dev); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_idle_timeout(impl); - spa_hook_remove(&impl->listener); - pw_properties_free(impl->to_restore); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_default_routes_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->to_restore = pw_properties_new(NULL, NULL); - if (impl->to_restore == NULL) { - res = -errno; - goto exit_free; - } - - if ((res = sm_media_session_load_state(impl->session, - SESSION_KEY, impl->to_restore)) < 0) - pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - return 0; - -exit_free: - free(impl); - return res; -} diff --git a/src/media-session/libcamera-monitor.c b/src/media-session/libcamera-monitor.c deleted file mode 100644 index c2fd0c030..000000000 --- a/src/media-session/libcamera-monitor.c +++ /dev/null @@ -1,511 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_libcamera_monitor Media Session Module: libCamera Monitor - */ - -#define NAME "libcamera-monitor" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct device; - -struct node { - struct impl *impl; - struct device *device; - struct spa_list link; - uint32_t id; - - struct pw_properties *props; - - struct pw_proxy *proxy; - struct spa_node *node; -}; - -struct device { - struct impl *impl; - struct spa_list link; - uint32_t id; - uint32_t device_id; - - int priority; - int profile; - - struct pw_properties *props; - - struct spa_handle *handle; - struct spa_device *device; - struct spa_hook device_listener; - - struct sm_device *sdevice; - struct spa_hook listener; - - unsigned int appeared:1; - struct spa_list node_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook session_listener; - - struct spa_handle *handle; - struct spa_device *monitor; - struct spa_hook listener; - - struct spa_list device_list; -}; - -static struct node *libcamera_find_node(struct device *dev, uint32_t id) -{ - struct node *node; - - spa_list_for_each(node, &dev->node_list, link) { - if (node->id == id) - return node; - } - return NULL; -} - -static void libcamera_update_node(struct device *dev, struct node *node, - const struct spa_device_object_info *info) -{ - pw_log_debug("update node %u", node->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(node->props, info->props); -} - -static struct node *libcamera_create_node(struct device *dev, uint32_t id, - const struct spa_device_object_info *info) -{ - struct node *node; - struct impl *impl = dev->impl; - int res; - const char *str; - - pw_log_debug("new node %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) { - errno = EINVAL; - return NULL; - } - node = calloc(1, sizeof(*node)); - if (node == NULL) { - res = -errno; - goto exit; - } - - node->props = pw_properties_new_dict(info->props); - - pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", dev->device_id); - - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NAME); - if (str == NULL) - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NICK); - if (str == NULL) - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_ALIAS); - if (str == NULL) - str = "libcamera-device"; - pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s", info->factory_name, str); - - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_DESCRIPTION); - if (str == NULL) - str = "libcamera-device"; - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, str); - - pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); - - node->impl = impl; - node->device = dev; - node->id = id; - node->proxy = sm_media_session_create_object(impl->session, - "spa-node-factory", - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE, - &node->props->dict, - 0); - if (node->proxy == NULL) { - res = -errno; - goto clean_node; - } - - spa_list_append(&dev->node_list, &node->link); - - return node; - -clean_node: - pw_properties_free(node->props); - free(node); -exit: - errno = -res; - return NULL; -} - -static void libcamera_remove_node(struct device *dev, struct node *node) -{ - pw_log_debug("remove node %u", node->id); - spa_list_remove(&node->link); - pw_proxy_destroy(node->proxy); - pw_properties_free(node->props); - free(node); -} - -static void libcamera_device_info(void *data, const struct spa_device_info *info) -{ - struct device *dev = data; - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(dev->props, info->props); -} - -static void libcamera_device_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct device *dev = data; - struct node *node; - - node = libcamera_find_node(dev, id); - - if (info == NULL) { - if (node == NULL) { - pw_log_warn("device %p: unknown node %u", dev, id); - return; - } - libcamera_remove_node(dev, node); - } else if (node == NULL) { - libcamera_create_node(dev, id, info); - } else { - libcamera_update_node(dev, node, info); - } -} - -static const struct spa_device_events libcamera_device_events = { - SPA_VERSION_DEVICE_EVENTS, - .info = libcamera_device_info, - .object_info = libcamera_device_object_info -}; - -static struct device *libcamera_find_device(struct impl *impl, uint32_t id) -{ - struct device *dev; - - spa_list_for_each(dev, &impl->device_list, link) { - if (dev->id == id) - return dev; - } - return NULL; -} - -static void libcamera_update_device(struct impl *impl, struct device *dev, - const struct spa_device_object_info *info) -{ - pw_log_debug("update device %u", dev->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(dev->props, info->props); -} - -static int libcamera_update_device_props(struct device *dev) -{ - struct pw_properties *p = dev->props; - const char *s, *d; - char temp[32]; - - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_NAME)) == NULL) { - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_ID)) == NULL) { - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_PATH)) == NULL) { - snprintf(temp, sizeof(temp), "%d", dev->id); - s = temp; - } - } - } - pw_properties_setf(p, PW_KEY_DEVICE_NAME, "libcamera_device.%s", s); - - if (pw_properties_get(p, PW_KEY_DEVICE_DESCRIPTION) == NULL) { - d = pw_properties_get(p, PW_KEY_DEVICE_PRODUCT_NAME); - if (!d) - d = "Unknown device"; - - pw_properties_set(p, PW_KEY_DEVICE_DESCRIPTION, d); - } - return 0; -} - -static void set_profile(struct device *device, int index) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - pw_log_debug("%p: set profile %d id:%d", device, index, device->device_id); - - device->profile = index; - if (device->device_id != 0) { - spa_device_set_param(device->device, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); - } -} - -static void device_destroy(void *data) -{ - struct device *device = data; - struct node *node; - - pw_log_debug("device %p destroy", device); - - spa_list_consume(node, &device->node_list, link) - libcamera_remove_node(device, node); -} - -static void device_free(void *data) -{ - struct device *dev = data; - pw_log_debug("remove device %u", dev->id); - spa_list_remove(&dev->link); - if (dev->appeared) - spa_hook_remove(&dev->device_listener); - sm_object_discard(&dev->sdevice->obj); - spa_hook_remove(&dev->listener); - pw_unload_spa_handle(dev->handle); - pw_properties_free(dev->props); - free(dev); -} - -static void device_update(void *data) -{ - struct device *device = data; - - pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); - - if (device->appeared) - return; - - device->device_id = device->sdevice->obj.id; - device->appeared = true; - - spa_device_add_listener(device->device, - &device->device_listener, - &libcamera_device_events, device); - - set_profile(device, 1); - sm_object_sync_update(&device->sdevice->obj); -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .destroy = device_destroy, - .free = device_free, - .update = device_update, -}; - - -static struct device *libcamera_create_device(struct impl *impl, uint32_t id, - const struct spa_device_object_info *info) -{ - struct pw_context *context = impl->session->context; - struct device *dev; - struct spa_handle *handle; - int res; - void *iface; - - pw_log_debug("new device %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { - errno = EINVAL; - return NULL; - } - - handle = pw_context_load_spa_handle(context, - info->factory_name, - info->props); - if (handle == NULL) { - res = -errno; - pw_log_error("can't make factory instance: %m"); - goto exit; - } - - if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { - pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); - goto unload_handle; - } - - dev = calloc(1, sizeof(*dev)); - if (dev == NULL) { - res = -errno; - goto unload_handle; - } - - dev->impl = impl; - dev->id = id; - dev->handle = handle; - dev->device = iface; - dev->props = pw_properties_new_dict(info->props); - libcamera_update_device_props(dev); - - dev->sdevice = sm_media_session_export_device(impl->session, - &dev->props->dict, dev->device); - - if (dev->sdevice == NULL) { - res = -errno; - goto clean_device; - } - - pw_log_debug("got object %p", &dev->sdevice->obj); - - sm_object_add_listener(&dev->sdevice->obj, - &dev->listener, - &device_events, dev); - - spa_list_init(&dev->node_list); - spa_list_append(&impl->device_list, &dev->link); - - return dev; - -clean_device: - free(dev); -unload_handle: - pw_unload_spa_handle(handle); -exit: - errno = -res; - return NULL; -} - -static void libcamera_remove_device(struct impl *impl, struct device *dev) -{ - sm_object_destroy(&dev->sdevice->obj); -} - -static void libcamera_udev_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct impl *impl = data; - struct device *dev = NULL; - - dev = libcamera_find_device(impl, id); - - if (info == NULL) { - if (dev == NULL) - return; - libcamera_remove_device(impl, dev); - } else if (dev == NULL) { - if (libcamera_create_device(impl, id, info) == NULL) - return; - } else { - libcamera_update_device(impl, dev, info); - } -} - -static const struct spa_device_events libcamera_udev_callbacks = -{ - SPA_VERSION_DEVICE_EVENTS, - .object_info = libcamera_udev_object_info, -}; - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->session_listener); - spa_hook_remove(&impl->listener); - pw_unload_spa_handle(impl->handle); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_libcamera_monitor_start(struct sm_media_session *sess) -{ - struct pw_context *context = sess->context; - struct impl *impl; - int res; - void *iface; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = sess; - - impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_LIBCAMERA_ENUM_CLIENT, NULL); - if (impl->handle == NULL) { - res = -errno; - goto out_free; - } - - if ((res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { - pw_log_error("can't get MONITOR interface: %d", res); - goto out_unload; - } - - impl->monitor = iface; - spa_list_init(&impl->device_list); - - spa_device_add_listener(impl->monitor, &impl->listener, - &libcamera_udev_callbacks, impl); - - sm_media_session_add_listener(sess, &impl->session_listener, &session_events, impl); - - return 0; - -out_unload: - pw_unload_spa_handle(impl->handle); -out_free: - free(impl); - return res; -} diff --git a/src/media-session/logind.c b/src/media-session/logind.c deleted file mode 100644 index 7514df299..000000000 --- a/src/media-session/logind.c +++ /dev/null @@ -1,143 +0,0 @@ -/* PipeWire - * - * Copyright © 2021 Pauli Virtanen - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * Monitor systemd-logind events for changes in session/seat status, and keep session - * manager up-to-date on whether the current session is active. - */ - -#include "config.h" - -#include -#include - -#include - -#include -#include -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_logind Media Session Module: Logind - * - * The logind module uses systemd logind to keep track of the user's session - * and updates the media session's seat state accordingly. - * - * The session state may be used by other modules, e.g. the \ref - * page_media_session_module_bluez_monitor module enables/disables - * Bluetooth whenever the session changes between active and inactive. - */ - -#define NAME "logind" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; - struct pw_context *context; - - sd_login_monitor *monitor; - struct spa_source source; -}; - -static void update_seat_active(struct impl *impl) -{ - char *state; - bool active; - - if (sd_uid_get_state(getuid(), &state) < 0) - return; - - active = spa_streq(state, "active"); - free(state); - - sm_media_session_seat_active_changed(impl->session, active); -} - -static void monitor_event(struct spa_source *source) -{ - struct impl *impl = source->data; - sd_login_monitor_flush(impl->monitor); - update_seat_active(impl); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - if (impl->monitor) { - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - pw_loop_remove_source(main_loop, &impl->source); - sd_login_monitor_unref(impl->monitor); - impl->monitor = NULL; - } - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_logind_start(struct sm_media_session *session) -{ - struct impl *impl; - struct pw_loop *main_loop; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - if ((res = sd_login_monitor_new(NULL, &impl->monitor)) < 0) - goto fail; - - main_loop = pw_context_get_main_loop(impl->context); - - impl->source.data = impl; - impl->source.fd = sd_login_monitor_get_fd(impl->monitor); - impl->source.func = monitor_event; - impl->source.mask = sd_login_monitor_get_events(impl->monitor); - impl->source.rmask = 0; - pw_loop_add_source(main_loop, &impl->source); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - update_seat_active(impl); - - return 0; - -fail: - pw_log_error(": failed to start systemd logind monitor: %d (%s)", res, spa_strerror(res)); - free(impl); - return res; -} diff --git a/src/media-session/match-rules.c b/src/media-session/match-rules.c deleted file mode 100644 index d4652def8..000000000 --- a/src/media-session/match-rules.c +++ /dev/null @@ -1,145 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include - -#include -#include "media-session.h" - -PW_LOG_TOPIC_EXTERN(ms_topic); -#define PW_LOG_TOPIC_DEFAULT ms_topic - -static bool find_match(struct spa_json *arr, struct pw_properties *props) -{ - struct spa_json match_obj; - - while (spa_json_enter_object(arr, &match_obj) > 0) { - char key[256], val[1024]; - const char *str, *value; - int match = 0, fail = 0; - int len; - - while (spa_json_get_string(&match_obj, key, sizeof(key)-1) > 0) { - bool success = false; - - if ((len = spa_json_next(&match_obj, &value)) <= 0) - break; - - str = pw_properties_get(props, key); - - if (spa_json_is_null(value, len)) { - success = str == NULL; - } else { - spa_json_parse_string(value, SPA_MIN(len, 1023), val); - value = val; - len = strlen(val); - } - if (str != NULL) { - if (value[0] == '~') { - regex_t preg; - if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) { - if (regexec(&preg, str, 0, NULL, 0) == 0) - success = true; - regfree(&preg); - } - } else if (strncmp(str, value, len) == 0 && - strlen(str) == (size_t)len) { - success = true; - } - } - if (success) { - match++; - pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value); - } - else - fail++; - } - if (match > 0 && fail == 0) - return true; - } - return false; -} - -int sm_media_session_match_rules(const char *rules, size_t size, struct pw_properties *props) -{ - const char *val; - struct spa_json actions; - struct spa_json it_rules; /* the rules = [] array */ - struct spa_json it_rules_obj; /* one object within that array */ - struct spa_json it_element; /* key/value element within that object */ - - spa_json_init(&it_rules, rules, size); - if (spa_json_enter_array(&it_rules, &it_rules_obj) < 0) - return 0; - - while (spa_json_enter_object(&it_rules_obj, &it_element) > 0) { - char key[64]; - bool have_match = false, have_actions = false; - - while (spa_json_get_string(&it_element, key, sizeof(key)-1) > 0) { - if (spa_streq(key, "matches")) { - struct spa_json it_matches_array; - if (spa_json_enter_array(&it_element, &it_matches_array) < 0) - break; - - have_match = find_match(&it_matches_array, props); - } - else if (spa_streq(key, "actions")) { - if (spa_json_enter_object(&it_element, &actions) > 0) - have_actions = true; - } - else if (spa_json_next(&it_element, &val) <= 0) - break; - } - if (!have_match || !have_actions) - continue; - - while (spa_json_get_string(&actions, key, sizeof(key)-1) > 0) { - int len; - pw_log_debug("action %s", key); - if (spa_streq(key, "update-props")) { - if ((len = spa_json_next(&actions, &val)) <= 0) - continue; - if (!spa_json_is_object(val, len)) - continue; - len = spa_json_container_len(&actions, val, len); - - pw_properties_update_string(props, val, len); - } - else if (spa_json_next(&actions, &val) <= 0) - break; - } - } - return 1; -} diff --git a/src/media-session/media-session.c b/src/media-session/media-session.c deleted file mode 100644 index a0e9c773b..000000000 --- a/src/media-session/media-session.c +++ /dev/null @@ -1,2621 +0,0 @@ -/* PipeWire - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_PWD_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/private.h" -#include "pipewire/conf.h" -#include "pipewire/extensions/session-manager.h" -#include "pipewire/extensions/client-node.h" - -#include - -#include "media-session.h" - -#define NAME "media-session" -#define SESSION_PREFIX "media-session.d" -#define SESSION_CONF "media-session.conf" - -PW_LOG_TOPIC(ms_topic, "ms.core"); -#define PW_LOG_TOPIC_DEFAULT ms_topic - -#define sm_object_emit(o,m,v,...) spa_hook_list_call(&(o)->hooks, struct sm_object_events, m, v, ##__VA_ARGS__) - -#define sm_object_emit_update(s) sm_object_emit(s, update, 0) -#define sm_object_emit_destroy(s) sm_object_emit(s, destroy, 0) -#define sm_object_emit_free(s) sm_object_emit(s, free, 0) - -#define sm_media_session_emit(s,m,v,...) spa_hook_list_call(&(s)->hooks, struct sm_media_session_events, m, v, ##__VA_ARGS__) - -#define sm_media_session_emit_info(s,i) sm_media_session_emit(s, info, 0, i) -#define sm_media_session_emit_create(s,obj) sm_media_session_emit(s, create, 0, obj) -#define sm_media_session_emit_remove(s,obj) sm_media_session_emit(s, remove, 0, obj) -#define sm_media_session_emit_rescan(s,seq) sm_media_session_emit(s, rescan, 0, seq) -#define sm_media_session_emit_shutdown(s) sm_media_session_emit(s, shutdown, 0) -#define sm_media_session_emit_destroy(s) sm_media_session_emit(s, destroy, 0) -#define sm_media_session_emit_seat_active(s,...) sm_media_session_emit(s, seat_active, 0, __VA_ARGS__) -#define sm_media_session_emit_dbus_disconnected(s) sm_media_session_emit(s, dbus_disconnected, 0) - -int sm_access_flatpak_start(struct sm_media_session *sess); -int sm_access_portal_start(struct sm_media_session *sess); -int sm_default_nodes_start(struct sm_media_session *sess); -int sm_default_profile_start(struct sm_media_session *sess); -int sm_default_routes_start(struct sm_media_session *sess); -int sm_restore_stream_start(struct sm_media_session *sess); -int sm_streams_follow_default_start(struct sm_media_session *sess); -int sm_alsa_no_dsp_start(struct sm_media_session *sess); -int sm_alsa_midi_start(struct sm_media_session *sess); -int sm_v4l2_monitor_start(struct sm_media_session *sess); -int sm_libcamera_monitor_start(struct sm_media_session *sess); -int sm_bluez5_monitor_start(struct sm_media_session *sess); -int sm_bluez5_autoswitch_start(struct sm_media_session *sess); -int sm_alsa_monitor_start(struct sm_media_session *sess); -int sm_suspend_node_start(struct sm_media_session *sess); -#ifdef HAVE_SYSTEMD -int sm_logind_start(struct sm_media_session *sess); -#endif - -int sm_policy_node_start(struct sm_media_session *sess); - -int sm_session_manager_start(struct sm_media_session *sess); - -/** user data to add to an object */ -struct data { - struct spa_list link; - const char *id; - size_t size; -}; - -struct param { - struct sm_param this; -}; - -struct sync { - struct spa_list link; - int seq; - void (*callback) (void *data); - void *data; -}; - -struct impl { - struct sm_media_session this; - - const char *config_dir; - - struct pw_properties *conf; - struct pw_properties *modules; - - struct pw_main_loop *loop; - struct spa_dbus *dbus; - struct spa_hook dbus_connection_listener; - - struct pw_core *monitor_core; - struct spa_hook monitor_listener; - int monitor_seq; - - struct pw_core *policy_core; - struct spa_hook policy_listener; - struct spa_hook proxy_policy_listener; - - struct pw_registry *registry; - struct spa_hook registry_listener; - - struct pw_registry *monitor_registry; - struct spa_hook monitor_registry_listener; - - struct pw_map globals; - struct spa_list object_list; /**< all sm_objects */ - - struct spa_list registry_event_list; /**< pending registry events */ - - struct spa_hook_list hooks; - - struct spa_list endpoint_link_list; /** list of struct endpoint_link */ - struct pw_map endpoint_links; /** map of endpoint_link */ - - struct spa_list link_list; /** list of struct link */ - - struct spa_list sync_list; /** list of struct sync */ - int rescan_seq; - int last_seq; - - unsigned int scanning:1; - unsigned int rescan_pending:1; - unsigned int seat_active:1; -}; - -struct endpoint_link { - uint32_t id; - - struct pw_endpoint_link_info info; - - struct impl *impl; - - struct spa_list link; /**< link in struct impl endpoint_link_list */ - struct spa_list link_list; /**< list of struct link */ -}; - -struct link { - struct pw_proxy *proxy; /**< proxy for link */ - struct spa_hook listener; /**< proxy listener */ - - uint32_t output_node; - uint32_t output_port; - uint32_t input_node; - uint32_t input_port; - - struct endpoint_link *endpoint_link; - struct spa_list link; /**< link in struct endpoint_link link_list or - * struct impl link_list */ -}; - -struct object_info { - const char *type; - uint32_t version; - const void *events; - size_t size; - int (*init) (void *object); - void (*destroy) (void *object); -}; - -struct registry_event { - uint32_t id; - uint32_t permissions; - const char *type; - uint32_t version; - const struct spa_dict *props; - - struct pw_proxy *proxy; - - int seq; - struct pw_properties *props_store; - - struct spa_list link; - unsigned int monitor:1; - unsigned int allocated:1; -}; - -static void add_object(struct impl *impl, struct sm_object *obj, uint32_t id) -{ - size_t size = pw_map_get_size(&impl->globals); - obj->id = id; - pw_log_debug("add global '%u' %p monitor:%d", obj->id, obj, obj->monitor_global); - while (obj->id > size) - pw_map_insert_at(&impl->globals, size++, NULL); - pw_map_insert_at(&impl->globals, obj->id, obj); - sm_media_session_emit_create(impl, obj); -} - -static void remove_object(struct impl *impl, struct sm_object *obj) -{ - pw_log_debug("remove global '%u' %p monitor:%d", obj->id, obj, obj->monitor_global); - pw_map_insert_at(&impl->globals, obj->id, NULL); - sm_media_session_emit_remove(impl, obj); - obj->id = SPA_ID_INVALID; -} - -static void *find_object(struct impl *impl, uint32_t id, const char *type) -{ - struct sm_object *obj; - if ((obj = pw_map_lookup(&impl->globals, id)) == NULL) - return NULL; - if (type != NULL && !spa_streq(obj->type, type)) - return NULL; - return obj; -} - -static struct data *object_find_data(struct sm_object *obj, const char *id) -{ - struct data *d; - spa_list_for_each(d, &obj->data, link) { - if (spa_streq(d->id, id)) - return d; - } - return NULL; -} - -void *sm_object_add_data(struct sm_object *obj, const char *id, size_t size) -{ - struct data *d; - - d = object_find_data(obj, id); - if (d != NULL) { - if (d->size == size) - goto done; - spa_list_remove(&d->link); - free(d); - } - d = calloc(1, sizeof(struct data) + size); - d->id = id; - d->size = size; - - spa_list_append(&obj->data, &d->link); -done: - return SPA_PTROFF(d, sizeof(struct data), void); -} - -void *sm_object_get_data(struct sm_object *obj, const char *id) -{ - struct data *d; - d = object_find_data(obj, id); - if (d == NULL) - return NULL; - return SPA_PTROFF(d, sizeof(struct data), void); -} - -int sm_object_remove_data(struct sm_object *obj, const char *id) -{ - struct data *d; - d = object_find_data(obj, id); - if (d == NULL) - return -ENOENT; - spa_list_remove(&d->link); - free(d); - return 0; -} - -static int sm_object_destroy_maybe_free(struct sm_object *obj) -{ - struct impl *impl = SPA_CONTAINER_OF(obj->session, struct impl, this); - struct data *d; - - pw_log_debug("%p: destroy object %p id:%d proxy:%p handle:%p monitor:%d destroyed:%d discarded:%d", obj->session, - obj, obj->id, obj->proxy, obj->handle, obj->monitor_global, obj->destroyed, obj->discarded); - - if (obj->destroyed) - goto unref; - - obj->destroyed = true; - - sm_object_emit_destroy(obj); - - if (SPA_FLAG_IS_SET(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER)) { - SPA_FLAG_CLEAR(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER); - spa_hook_remove(&obj->object_listener); - } - - if (obj->id != SPA_ID_INVALID) - remove_object(impl, obj); - - if (obj->destroy) - obj->destroy(obj); - - spa_hook_remove(&obj->handle_listener); - - if (obj->proxy) { - spa_hook_remove(&obj->proxy_listener); - if (obj->proxy != obj->handle) - pw_proxy_destroy(obj->proxy); - obj->proxy = NULL; - } - - pw_proxy_ref(obj->handle); - pw_proxy_destroy(obj->handle); - - sm_object_emit_free(obj); - -unref: - if (!obj->discarded) - return 0; - - pw_properties_free(obj->props); - obj->props = NULL; - - spa_list_consume(d, &obj->data, link) { - spa_list_remove(&d->link); - free(d); - } - - spa_list_remove(&obj->link); - pw_proxy_unref(obj->handle); /* frees obj */ - - return 0; -} - -int sm_object_destroy(struct sm_object *obj) -{ - sm_object_discard(obj); - return sm_object_destroy_maybe_free(obj); -} - -static struct param *add_param(struct spa_list *param_list, - int seq, int *param_seq, uint32_t id, const struct spa_pod *param) -{ - struct param *p; - - if (param == NULL || !spa_pod_is_object(param)) { - errno = EINVAL; - return NULL; - } - if (id == SPA_ID_INVALID) - id = SPA_POD_OBJECT_ID(param); - - if (id >= SM_MAX_PARAMS) { - pw_log_error("too big param id %d", id); - errno = EINVAL; - return NULL; - } - - if (seq != param_seq[id]) { - pw_log_debug("ignoring param %d, seq:%d != current_seq:%d", - id, seq, param_seq[id]); - errno = EBUSY; - return NULL; - } - - p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); - if (p == NULL) - return NULL; - - p->this.id = id; - p->this.param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod); - memcpy(p->this.param, param, SPA_POD_SIZE(param)); - - spa_list_append(param_list, &p->this.link); - - return p; -} - - -static uint32_t clear_params(struct spa_list *param_list, uint32_t id) -{ - struct param *p, *t; - uint32_t count = 0; - - spa_list_for_each_safe(p, t, param_list, this.link) { - if (id == SPA_ID_INVALID || p->this.id == id) { - spa_list_remove(&p->this.link); - free(p); - count++; - } - } - return count; -} - -/** - * Core - */ -static const struct object_info core_object_info = { - .type = PW_TYPE_INTERFACE_Core, - .version = PW_VERSION_CORE, - .size = sizeof(struct sm_object), - .init = NULL, -}; - -/** - * Module - */ -static const struct object_info module_info = { - .type = PW_TYPE_INTERFACE_Module, - .version = PW_VERSION_MODULE, - .size = sizeof(struct sm_object), - .init = NULL, -}; - -/** - * Factory - */ -static const struct object_info factory_info = { - .type = PW_TYPE_INTERFACE_Factory, - .version = PW_VERSION_FACTORY, - .size = sizeof(struct sm_object), - .init = NULL, -}; - -/** - * Clients - */ -static void client_event_info(void *object, const struct pw_client_info *info) -{ - struct sm_client *client = object; - struct impl *impl = SPA_CONTAINER_OF(client->obj.session, struct impl, this); - - pw_log_debug("%p: client %d info", impl, client->obj.id); - client->info = pw_client_info_merge(client->info, info, client->obj.changed == 0); - - client->obj.avail |= SM_CLIENT_CHANGE_MASK_INFO; - client->obj.changed |= SM_CLIENT_CHANGE_MASK_INFO; - sm_object_sync_update(&client->obj); -} - -static const struct pw_client_events client_events = { - PW_VERSION_CLIENT_EVENTS, - .info = client_event_info, -}; - -static void client_destroy(void *object) -{ - struct sm_client *client = object; - if (client->info) - pw_client_info_free(client->info); -} - -static const struct object_info client_info = { - .type = PW_TYPE_INTERFACE_Client, - .version = PW_VERSION_CLIENT, - .events = &client_events, - .size = sizeof(struct sm_client), - .init = NULL, - .destroy = client_destroy, -}; - -/** - * Device - */ -static void device_event_info(void *object, const struct pw_device_info *info) -{ - struct sm_device *device = object; - struct impl *impl = SPA_CONTAINER_OF(device->obj.session, struct impl, this); - uint32_t i; - - pw_log_debug("%p: device %d info", impl, device->obj.id); - info = device->info = pw_device_info_merge(device->info, info, device->obj.changed == 0); - - device->obj.avail |= SM_DEVICE_CHANGE_MASK_INFO; - device->obj.changed |= SM_DEVICE_CHANGE_MASK_INFO; - - if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { - for (i = 0; i < info->n_params; i++) { - uint32_t id = info->params[i].id; - - if (info->params[i].user == 0) - continue; - - if (id >= SM_MAX_PARAMS) { - pw_log_error("%p: too big param id %d", impl, id); - continue; - } - - device->n_params -= clear_params(&device->param_list, id); - - if (info->params[i].flags & SPA_PARAM_INFO_READ) { - int res; - res = pw_device_enum_params((struct pw_device*)device->obj.proxy, - ++device->param_seq[id], id, 0, UINT32_MAX, NULL); - if (SPA_RESULT_IS_ASYNC(res)) - device->param_seq[id] = res; - pw_log_debug("%p: device %d enum params %d seq:%d", impl, - device->obj.id, id, device->param_seq[id]); - } - info->params[i].user = 0; - } - } - sm_object_sync_update(&device->obj); - sm_media_session_schedule_rescan(&impl->this); -} - -static void device_event_param(void *object, int seq, - uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param) -{ - struct sm_device *device = object; - struct impl *impl = SPA_CONTAINER_OF(device->obj.session, struct impl, this); - - pw_log_debug("%p: device %p param %d index:%d seq:%d", impl, device, id, index, seq); - if (add_param(&device->param_list, seq, device->param_seq, id, param) != NULL) - device->n_params++; - - device->obj.avail |= SM_DEVICE_CHANGE_MASK_PARAMS; - device->obj.changed |= SM_DEVICE_CHANGE_MASK_PARAMS; -} - -static const struct pw_device_events device_events = { - PW_VERSION_DEVICE_EVENTS, - .info = device_event_info, - .param = device_event_param, -}; - -static int device_init(void *object) -{ - struct sm_device *device = object; - spa_list_init(&device->node_list); - spa_list_init(&device->param_list); - return 0; -} - -static void device_destroy(void *object) -{ - struct sm_device *device = object; - struct sm_node *node; - - spa_list_consume(node, &device->node_list, link) { - node->device = NULL; - spa_list_remove(&node->link); - } - clear_params(&device->param_list, SPA_ID_INVALID); - device->n_params = 0; - - if (device->info) - pw_device_info_free(device->info); - device->info = NULL; -} - -static const struct object_info device_info = { - .type = PW_TYPE_INTERFACE_Device, - .version = PW_VERSION_DEVICE, - .events = &device_events, - .size = sizeof(struct sm_device), - .init = device_init, - .destroy = device_destroy, -}; - -static const struct object_info spa_device_info = { - .type = SPA_TYPE_INTERFACE_Device, - .version = SPA_VERSION_DEVICE, - .size = sizeof(struct sm_device), - .init = device_init, - .destroy = device_destroy, -}; - -/** - * Node - */ -static void node_event_info(void *object, const struct pw_node_info *info) -{ - struct sm_node *node = object; - struct impl *impl = SPA_CONTAINER_OF(node->obj.session, struct impl, this); - uint32_t i; - - pw_log_debug("%p: node %d info", impl, node->obj.id); - info = node->info = pw_node_info_merge(node->info, info, node->obj.changed == 0); - - node->obj.avail |= SM_NODE_CHANGE_MASK_INFO; - node->obj.changed |= SM_NODE_CHANGE_MASK_INFO; - - if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS && - (node->obj.mask & SM_NODE_CHANGE_MASK_PARAMS)) { - for (i = 0; i < info->n_params; i++) { - uint32_t id = info->params[i].id; - - if (info->params[i].user == 0) - continue; - - if (id >= SM_MAX_PARAMS) { - pw_log_error("%p: too big param id %d", impl, id); - continue; - } - - node->n_params -= clear_params(&node->param_list, id); - - if (info->params[i].flags & SPA_PARAM_INFO_READ) { - int res; - res = pw_node_enum_params((struct pw_node*)node->obj.proxy, - ++node->param_seq[id], id, 0, UINT32_MAX, NULL); - if (SPA_RESULT_IS_ASYNC(res)) - node->param_seq[id] = res; - pw_log_debug("%p: node %d enum params %d seq:%d", impl, - node->obj.id, id, node->param_seq[id]); - } - info->params[i].user = 0; - } - } - sm_object_sync_update(&node->obj); - sm_media_session_schedule_rescan(&impl->this); -} - -static void node_event_param(void *object, int seq, - uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param) -{ - struct sm_node *node = object; - struct impl *impl = SPA_CONTAINER_OF(node->obj.session, struct impl, this); - - pw_log_debug("%p: node %p param %d index:%d seq:%d", impl, node, id, index, seq); - if (add_param(&node->param_list, seq, node->param_seq, id, param) != NULL) - node->n_params++; - - node->obj.avail |= SM_NODE_CHANGE_MASK_PARAMS; - node->obj.changed |= SM_NODE_CHANGE_MASK_PARAMS; -} - -static const struct pw_node_events node_events = { - PW_VERSION_NODE_EVENTS, - .info = node_event_info, - .param = node_event_param, -}; - -static int node_init(void *object) -{ - struct sm_node *node = object; - struct impl *impl = SPA_CONTAINER_OF(node->obj.session, struct impl, this); - struct pw_properties *props = node->obj.props; - - spa_list_init(&node->port_list); - spa_list_init(&node->param_list); - - if (props) { - uint32_t id = SPA_ID_INVALID; - - if (pw_properties_fetch_uint32(props, PW_KEY_DEVICE_ID, &id) == 0) - node->device = find_object(impl, id, NULL); - pw_log_debug("%p: node %d parent device %d (%p)", impl, - node->obj.id, id, node->device); - if (node->device) { - spa_list_append(&node->device->node_list, &node->link); - node->device->obj.avail |= SM_DEVICE_CHANGE_MASK_NODES; - node->device->obj.changed |= SM_DEVICE_CHANGE_MASK_NODES; - } - } - return 0; -} - -static void node_destroy(void *object) -{ - struct sm_node *node = object; - struct sm_port *port; - - spa_list_consume(port, &node->port_list, link) { - port->node = NULL; - spa_list_remove(&port->link); - } - clear_params(&node->param_list, SPA_ID_INVALID); - node->n_params = 0; - - if (node->device) { - spa_list_remove(&node->link); - node->device->obj.changed |= SM_DEVICE_CHANGE_MASK_NODES; - } - if (node->info) { - pw_node_info_free(node->info); - node->info = NULL; - } - free(node->target_node); - node->target_node = NULL; -} - -static const struct object_info node_info = { - .type = PW_TYPE_INTERFACE_Node, - .version = PW_VERSION_NODE, - .events = &node_events, - .size = sizeof(struct sm_node), - .init = node_init, - .destroy = node_destroy, -}; - -/** - * Port - */ -static void port_event_info(void *object, const struct pw_port_info *info) -{ - struct sm_port *port = object; - struct impl *impl = SPA_CONTAINER_OF(port->obj.session, struct impl, this); - - pw_log_debug("%p: port %d info", impl, port->obj.id); - port->info = pw_port_info_merge(port->info, info, port->obj.changed == 0); - - port->obj.avail |= SM_PORT_CHANGE_MASK_INFO; - port->obj.changed |= SM_PORT_CHANGE_MASK_INFO; - sm_object_sync_update(&port->obj); -} - -static const struct pw_port_events port_events = { - PW_VERSION_PORT_EVENTS, - .info = port_event_info, -}; - -static enum spa_audio_channel find_channel(const char *name) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) - return spa_type_audio_channel[i].type; - } - return SPA_AUDIO_CHANNEL_UNKNOWN; -} - -static int port_init(void *object) -{ - struct sm_port *port = object; - struct impl *impl = SPA_CONTAINER_OF(port->obj.session, struct impl, this); - struct pw_properties *props = port->obj.props; - const char *str; - - if (props) { - uint32_t id = SPA_ID_INVALID; - - if ((str = pw_properties_get(props, PW_KEY_PORT_DIRECTION)) != NULL) - port->direction = spa_streq(str, "out") ? - PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT; - if ((str = pw_properties_get(props, PW_KEY_FORMAT_DSP)) != NULL) { - if (spa_streq(str, "32 bit float mono audio")) - port->type = SM_PORT_TYPE_DSP_AUDIO; - else if (spa_streq(str, "8 bit raw midi")) - port->type = SM_PORT_TYPE_DSP_MIDI; - } - if ((str = pw_properties_get(props, PW_KEY_AUDIO_CHANNEL)) != NULL) - port->channel = find_channel(str); - if (pw_properties_fetch_uint32(props, PW_KEY_NODE_ID, &id) == 0) - port->node = find_object(impl, id, PW_TYPE_INTERFACE_Node); - - pw_log_debug("%p: port %d parent node %s (%p) direction:%d type:%d", impl, - port->obj.id, str, port->node, port->direction, port->type); - if (port->node) { - spa_list_append(&port->node->port_list, &port->link); - port->node->obj.avail |= SM_NODE_CHANGE_MASK_PORTS; - port->node->obj.changed |= SM_NODE_CHANGE_MASK_PORTS; - } - } - return 0; -} - -static void port_destroy(void *object) -{ - struct sm_port *port = object; - if (port->info) - pw_port_info_free(port->info); - if (port->node) { - spa_list_remove(&port->link); - port->node->obj.changed |= SM_NODE_CHANGE_MASK_PORTS; - } -} - -static const struct object_info port_info = { - .type = PW_TYPE_INTERFACE_Port, - .version = PW_VERSION_PORT, - .events = &port_events, - .size = sizeof(struct sm_port), - .init = port_init, - .destroy = port_destroy, -}; - -/** - * Session - */ -static void session_event_info(void *object, const struct pw_session_info *info) -{ - struct sm_session *sess = object; - struct impl *impl = SPA_CONTAINER_OF(sess->obj.session, struct impl, this); - struct pw_session_info *i = sess->info; - - pw_log_debug("%p: session %d info", impl, sess->obj.id); - if (i == NULL && info) { - i = sess->info = calloc(1, sizeof(struct pw_session_info)); - i->version = PW_VERSION_SESSION_INFO; - i->id = info->id; - } - if (info) { - i->change_mask = info->change_mask; - if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS) { - pw_properties_free ((struct pw_properties *)i->props); - i->props = (struct spa_dict *) pw_properties_new_dict (info->props); - } - } - - sess->obj.avail |= SM_SESSION_CHANGE_MASK_INFO; - sess->obj.changed |= SM_SESSION_CHANGE_MASK_INFO; - sm_object_sync_update(&sess->obj); -} - -static const struct pw_session_events session_events = { - PW_VERSION_SESSION_EVENTS, - .info = session_event_info, -}; - -static int session_init(void *object) -{ - struct sm_session *sess = object; - struct impl *impl = SPA_CONTAINER_OF(sess->obj.session, struct impl, this); - - if (sess->obj.id == impl->this.session_id) - impl->this.session = sess; - - spa_list_init(&sess->endpoint_list); - return 0; -} - -static void session_destroy(void *object) -{ - struct sm_session *sess = object; - struct sm_endpoint *endpoint; - struct pw_session_info *i = sess->info; - - spa_list_consume(endpoint, &sess->endpoint_list, link) { - endpoint->session = NULL; - spa_list_remove(&endpoint->link); - } - if (i) { - pw_properties_free ((struct pw_properties *)i->props); - free(i); - } - -} - -static const struct object_info session_info = { - .type = PW_TYPE_INTERFACE_Session, - .version = PW_VERSION_SESSION, - .events = &session_events, - .size = sizeof(struct sm_session), - .init = session_init, - .destroy = session_destroy, -}; - -/** - * Endpoint - */ -static void endpoint_event_info(void *object, const struct pw_endpoint_info *info) -{ - struct sm_endpoint *endpoint = object; - struct impl *impl = SPA_CONTAINER_OF(endpoint->obj.session, struct impl, this); - struct pw_endpoint_info *i = endpoint->info; - const char *str; - - pw_log_debug("%p: endpoint %d info", impl, endpoint->obj.id); - if (i == NULL && info) { - i = endpoint->info = calloc(1, sizeof(struct pw_endpoint_info)); - i->id = info->id; - i->name = info->name ? strdup(info->name) : NULL; - i->media_class = info->media_class ? strdup(info->media_class) : NULL; - i->direction = info->direction; - i->flags = info->flags; - } - if (info) { - i->change_mask = info->change_mask; - if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) { - i->session_id = info->session_id; - } - if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) { - pw_properties_free ((struct pw_properties *)i->props); - i->props = (struct spa_dict *) pw_properties_new_dict (info->props); - if ((str = spa_dict_lookup(i->props, PW_KEY_PRIORITY_SESSION)) != NULL) - endpoint->priority = pw_properties_parse_int(str); - } - } - - endpoint->obj.avail |= SM_ENDPOINT_CHANGE_MASK_INFO; - endpoint->obj.changed |= SM_ENDPOINT_CHANGE_MASK_INFO; - sm_object_sync_update(&endpoint->obj); -} - -static const struct pw_endpoint_events endpoint_events = { - PW_VERSION_ENDPOINT_EVENTS, - .info = endpoint_event_info, -}; - -static int endpoint_init(void *object) -{ - struct sm_endpoint *endpoint = object; - struct impl *impl = SPA_CONTAINER_OF(endpoint->obj.session, struct impl, this); - struct pw_properties *props = endpoint->obj.props; - - if (props) { - uint32_t id = SPA_ID_INVALID; - - if (pw_properties_fetch_uint32(props, PW_KEY_SESSION_ID, &id) == 0) - endpoint->session = find_object(impl, id, PW_TYPE_INTERFACE_Session); - pw_log_debug("%p: endpoint %d parent session %d", impl, - endpoint->obj.id, id); - if (endpoint->session) { - spa_list_append(&endpoint->session->endpoint_list, &endpoint->link); - endpoint->session->obj.avail |= SM_SESSION_CHANGE_MASK_ENDPOINTS; - endpoint->session->obj.changed |= SM_SESSION_CHANGE_MASK_ENDPOINTS; - } - } - spa_list_init(&endpoint->stream_list); - - return 0; -} - -static void endpoint_destroy(void *object) -{ - struct sm_endpoint *endpoint = object; - struct sm_endpoint_stream *stream; - struct pw_endpoint_info *i = endpoint->info; - - spa_list_consume(stream, &endpoint->stream_list, link) { - stream->endpoint = NULL; - spa_list_remove(&stream->link); - } - if (endpoint->session) { - endpoint->session = NULL; - spa_list_remove(&endpoint->link); - } - if (i) { - pw_properties_free ((struct pw_properties *)i->props); - free(i->name); - free(i->media_class); - free(i); - } -} - -static const struct object_info endpoint_info = { - .type = PW_TYPE_INTERFACE_Endpoint, - .version = PW_VERSION_ENDPOINT, - .events = &endpoint_events, - .size = sizeof(struct sm_endpoint), - .init = endpoint_init, - .destroy = endpoint_destroy, -}; - - -/** - * Endpoint Stream - */ -static void endpoint_stream_event_info(void *object, const struct pw_endpoint_stream_info *info) -{ - struct sm_endpoint_stream *stream = object; - struct impl *impl = SPA_CONTAINER_OF(stream->obj.session, struct impl, this); - - pw_log_debug("%p: endpoint stream %d info", impl, stream->obj.id); - if (stream->info == NULL && info) { - stream->info = calloc(1, sizeof(struct pw_endpoint_stream_info)); - stream->info->version = PW_VERSION_ENDPOINT_STREAM_INFO; - stream->info->id = info->id; - stream->info->endpoint_id = info->endpoint_id; - stream->info->name = info->name ? strdup(info->name) : NULL; - } - if (info) { - stream->info->change_mask = info->change_mask; - } - - stream->obj.avail |= SM_ENDPOINT_CHANGE_MASK_INFO; - stream->obj.changed |= SM_ENDPOINT_CHANGE_MASK_INFO; - sm_object_sync_update(&stream->obj); -} - -static const struct pw_endpoint_stream_events endpoint_stream_events = { - PW_VERSION_ENDPOINT_STREAM_EVENTS, - .info = endpoint_stream_event_info, -}; - -static int endpoint_stream_init(void *object) -{ - struct sm_endpoint_stream *stream = object; - struct impl *impl = SPA_CONTAINER_OF(stream->obj.session, struct impl, this); - struct pw_properties *props = stream->obj.props; - - if (props) { - uint32_t id = SPA_ID_INVALID; - - if (pw_properties_fetch_uint32(props, PW_KEY_ENDPOINT_ID, &id) == 0) - stream->endpoint = find_object(impl, id, PW_TYPE_INTERFACE_Endpoint); - pw_log_debug("%p: stream %d parent endpoint %d", impl, - stream->obj.id, id); - if (stream->endpoint) { - spa_list_append(&stream->endpoint->stream_list, &stream->link); - stream->endpoint->obj.avail |= SM_ENDPOINT_CHANGE_MASK_STREAMS; - stream->endpoint->obj.changed |= SM_ENDPOINT_CHANGE_MASK_STREAMS; - } - } - spa_list_init(&stream->link_list); - - return 0; -} - -static void endpoint_stream_destroy(void *object) -{ - struct sm_endpoint_stream *stream = object; - - if (stream->info) { - free(stream->info->name); - free(stream->info); - } - if (stream->endpoint) { - stream->endpoint = NULL; - spa_list_remove(&stream->link); - } -} - -static const struct object_info endpoint_stream_info = { - .type = PW_TYPE_INTERFACE_EndpointStream, - .version = PW_VERSION_ENDPOINT_STREAM, - .events = &endpoint_stream_events, - .size = sizeof(struct sm_endpoint_stream), - .init = endpoint_stream_init, - .destroy = endpoint_stream_destroy, -}; - -/** - * Endpoint Link - */ -static void endpoint_link_event_info(void *object, const struct pw_endpoint_link_info *info) -{ - struct sm_endpoint_link *link = object; - struct impl *impl = SPA_CONTAINER_OF(link->obj.session, struct impl, this); - - pw_log_debug("%p: endpoint link %d info", impl, link->obj.id); - if (link->info == NULL && info) { - link->info = calloc(1, sizeof(struct pw_endpoint_link_info)); - link->info->version = PW_VERSION_ENDPOINT_LINK_INFO; - link->info->id = info->id; - link->info->session_id = info->session_id; - link->info->output_endpoint_id = info->output_endpoint_id; - link->info->output_stream_id = info->output_stream_id; - link->info->input_endpoint_id = info->input_endpoint_id; - link->info->input_stream_id = info->input_stream_id; - } - if (info) { - link->info->change_mask = info->change_mask; - } - - link->obj.avail |= SM_ENDPOINT_LINK_CHANGE_MASK_INFO; - link->obj.changed |= SM_ENDPOINT_LINK_CHANGE_MASK_INFO; - sm_object_sync_update(&link->obj); -} - -static const struct pw_endpoint_link_events endpoint_link_events = { - PW_VERSION_ENDPOINT_LINK_EVENTS, - .info = endpoint_link_event_info, -}; - -static void endpoint_link_destroy(void *object) -{ - struct sm_endpoint_link *link = object; - - if (link->info) { - free(link->info->error); - free(link->info); - } - if (link->output) { - link->output = NULL; - spa_list_remove(&link->output_link); - } - if (link->input) { - link->input = NULL; - spa_list_remove(&link->input_link); - } -} - -static const struct object_info endpoint_link_info = { - .type = PW_TYPE_INTERFACE_EndpointLink, - .version = PW_VERSION_ENDPOINT_LINK, - .events = &endpoint_link_events, - .size = sizeof(struct sm_endpoint_link), - .init = NULL, - .destroy = endpoint_link_destroy, -}; - -/** - * Proxy - */ -static void done_proxy(void *data, int seq) -{ - struct sm_object *obj = data; - - pw_log_debug("done %p proxy %p avail:%08x update:%08x %d/%d", obj, - obj->proxy, obj->avail, obj->changed, obj->pending, seq); - - if (obj->pending == seq) { - obj->pending = SPA_ID_INVALID; - if (obj->changed) - sm_object_emit_update(obj); - obj->changed = 0; - } -} - -static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .done = done_proxy, -}; - -static void bound_handle(void *data, uint32_t id) -{ - struct sm_object *obj = data; - struct impl *impl = SPA_CONTAINER_OF(obj->session, struct impl, this); - - pw_log_debug("bound %p proxy %p handle %p id:%d->%d", - obj, obj->proxy, obj->handle, obj->id, id); - - if (obj->id == SPA_ID_INVALID) { - struct sm_object *old_obj = find_object(impl, id, NULL); - - if (old_obj != NULL) { - /* - * Monitor core is always more up-to-date in object creation - * events (see registry_global), so in case of duplicate objects - * we should prefer monitor globals. - */ - if (obj->monitor_global) - sm_object_destroy_maybe_free(old_obj); - else { - sm_object_destroy_maybe_free(obj); - return; - } - } - - add_object(impl, obj, id); - } -} - -static const struct pw_proxy_events handle_events = { - PW_VERSION_PROXY_EVENTS, - .bound = bound_handle, -}; - -int sm_object_sync_update(struct sm_object *obj) -{ - obj->pending = pw_proxy_sync(obj->proxy, 1); - pw_log_debug("sync %p proxy %p %d", obj, obj->proxy, obj->pending); - return obj->pending; -} - -static const struct object_info *get_object_info(struct impl *impl, const char *type) -{ - const struct object_info *info; - - if (spa_streq(type, PW_TYPE_INTERFACE_Core)) - info = &core_object_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Module)) - info = &module_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Factory)) - info = &factory_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Client)) - info = &client_info; - else if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) - info = &spa_device_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Device)) - info = &device_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) - info = &node_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) - info = &port_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Session)) - info = &session_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_Endpoint)) - info = &endpoint_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_EndpointStream)) - info = &endpoint_stream_info; - else if (spa_streq(type, PW_TYPE_INTERFACE_EndpointLink)) - info = &endpoint_link_info; - else - info = NULL; - - return info; -} - -static struct sm_object *init_object(struct impl *impl, const struct object_info *info, - struct pw_proxy *proxy, struct pw_proxy *handle, uint32_t id, - const struct spa_dict *props, bool monitor_global) -{ - struct sm_object *obj; - - obj = pw_proxy_get_user_data(handle); - obj->session = &impl->this; - obj->id = id; - obj->type = info->type; - obj->props = props ? pw_properties_new_dict(props) : pw_properties_new(NULL, NULL); - obj->proxy = proxy; - obj->handle = handle; - obj->destroy = info->destroy; - obj->mask |= SM_OBJECT_CHANGE_MASK_PROPERTIES | SM_OBJECT_CHANGE_MASK_BIND; - obj->avail |= obj->mask; - obj->monitor_global = monitor_global; - spa_hook_list_init(&obj->hooks); - spa_list_init(&obj->data); - - spa_list_append(&impl->object_list, &obj->link); - - if (proxy) { - pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); - if (info->events != NULL) - pw_proxy_add_object_listener(obj->proxy, &obj->object_listener, info->events, obj); - SPA_FLAG_UPDATE(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER, info->events != NULL); - } - pw_proxy_add_listener(obj->handle, &obj->handle_listener, &handle_events, obj); - - if (info->init) - info->init(obj); - - return obj; -} - -static struct sm_object * -create_object(struct impl *impl, struct pw_proxy *proxy, struct pw_proxy *handle, - const struct spa_dict *props, bool monitor_global) -{ - const char *type; - const struct object_info *info; - struct sm_object *obj; - - type = pw_proxy_get_type(handle, NULL); - - if (spa_streq(type, PW_TYPE_INTERFACE_ClientNode)) - type = PW_TYPE_INTERFACE_Node; - - info = get_object_info(impl, type); - if (info == NULL) { - pw_log_error("%p: unknown object type %s", impl, type); - errno = ENOTSUP; - return NULL; - } - obj = init_object(impl, info, proxy, handle, SPA_ID_INVALID, props, monitor_global); - - pw_log_debug("%p: created new object %p proxy:%p handle:%p", impl, - obj, obj->proxy, obj->handle); - - return obj; -} - -static struct sm_object * -bind_object(struct impl *impl, const struct object_info *info, struct registry_event *re) -{ - struct pw_proxy *proxy; - struct sm_object *obj; - - proxy = re->proxy; - re->proxy = NULL; - - obj = init_object(impl, info, proxy, proxy, re->id, re->props, false); - sm_object_discard(obj); - add_object(impl, obj, re->id); - - pw_log_debug("%p: bound new object %p proxy %p id:%d", impl, obj, obj->proxy, obj->id); - - return obj; -} - -static int -update_object(struct impl *impl, const struct object_info *info, struct sm_object *obj, - struct registry_event *re) -{ - struct pw_proxy *proxy; - - pw_properties_update(obj->props, re->props); - - if (obj->proxy != NULL) - return 0; - - pw_log_debug("%p: update type:%s", impl, obj->type); - - proxy = re->proxy; - re->proxy = NULL; - - obj->proxy = proxy; - obj->type = info->type; - - pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); - if (info->events) - pw_proxy_add_object_listener(obj->proxy, &obj->object_listener, info->events, obj); - - SPA_FLAG_UPDATE(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER, info->events != NULL); - - sm_media_session_emit_create(impl, obj); - - return 0; -} - -static void registry_event_free(struct registry_event *re) -{ - if (re->proxy) - pw_proxy_destroy(re->proxy); - pw_properties_free(re->props_store); - if (re->allocated) { - spa_list_remove(&re->link); - free(re); - } else { - spa_zero(*re); - } -} - -static int handle_registry_event(struct impl *impl, struct registry_event *re) -{ - struct sm_object *obj; - const struct object_info *info = NULL; - - obj = find_object(impl, re->id, NULL); - - pw_log_debug("%p: new global '%d' %s/%d obj:%p monitor:%d seq:%d", - impl, re->id, re->type, re->version, obj, re->monitor, re->seq); - - info = get_object_info(impl, re->type); - if (info == NULL) - return 0; - - if (obj == NULL && !re->monitor) { - /* - * Only policy core binds new objects. - * - * The monitor core event corresponding to this one has already been - * processed. If monitor doesn't have the id now, the object either has - * not been created there, or there is a race condition and it was already - * removed. In that case, we create a zombie object here, but its remove - * event is already queued and arrives soon. - */ - bind_object(impl, info, re); - } else if (obj != NULL && obj->monitor_global == re->monitor) { - /* Each core handles their own object updates */ - update_object(impl, info, obj, re); - } - - sm_media_session_schedule_rescan(&impl->this); - return 0; -} - -static int handle_postponed_registry_events(struct impl *impl, int seq) -{ - struct registry_event *re, *t; - - spa_list_for_each_safe(re, t, &impl->registry_event_list, link) { - if (re->seq == seq) { - handle_registry_event(impl, re); - registry_event_free(re); - } - } - return 0; -} - -static int monitor_sync(struct impl *impl) -{ - pw_core_set_paused(impl->policy_core, true); - impl->monitor_seq = pw_core_sync(impl->monitor_core, 0, impl->monitor_seq); - pw_log_debug("%p: monitor sync start %d", impl, impl->monitor_seq); - sm_media_session_schedule_rescan(&impl->this); - return impl->monitor_seq; -} - -static void -registry_global(void *data, uint32_t id, - uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props) -{ - struct impl *impl = data; - const struct object_info *info; - struct registry_event *re = NULL; - static bool warned_about_wireplumber = false; - - info = get_object_info(impl, type); - if (info == NULL) - return; - - pw_log_debug("%p: registry event (policy) for new global '%d'", impl, id); - - if (!warned_about_wireplumber && props && - spa_streq(info->type, PW_TYPE_INTERFACE_Client)) { - const char *name = spa_dict_lookup(props, PW_KEY_APP_NAME); - if (spa_streq(name, "WirePlumber")) { - pw_log_error("WirePlumber appears to be running; " - "please stop it before starting pipewire-media-session"); - warned_about_wireplumber = true; - } - } - - /* - * Handle policy core events after monitor core ones. - * - * Monitor sync pauses policy core, so the event will be handled before - * further registry or proxy events are received via policy core. - */ - re = calloc(1, sizeof(struct registry_event)); - if (re == NULL) - goto error; - - re->allocated = true; - spa_list_append(&impl->registry_event_list, &re->link); - - re->id = id; - re->monitor = false; - re->permissions = permissions; - re->type = info->type; - re->version = version; - - /* Bind proxy now */ - re->proxy = pw_registry_bind(impl->registry, id, type, info->version, info->size); - if (re->proxy == NULL) - goto error; - - if (props) { - re->props_store = pw_properties_new_dict(props); - if (re->props_store == NULL) - goto error; - re->props = &re->props_store->dict; - } - - re->seq = monitor_sync(impl); - - return; - -error: - if (re) - registry_event_free(re); - pw_log_warn("%p: can't handle global %d: %s", impl, id, spa_strerror(-errno)); -} - -static void -registry_global_remove(void *data, uint32_t id) -{ - struct impl *impl = data; - struct sm_object *obj; - - obj = find_object(impl, id, NULL); - obj = (obj && !obj->monitor_global) ? obj : NULL; - - pw_log_debug("%p: registry event (policy) for remove global '%d' obj:%p", - impl, id, obj); - - if (obj) - sm_object_destroy_maybe_free(obj); -} - -static const struct pw_registry_events registry_events = { - PW_VERSION_REGISTRY_EVENTS, - .global = registry_global, - .global_remove = registry_global_remove, -}; - -static void -monitor_registry_global(void *data, uint32_t id, - uint32_t permissions, const char *type, uint32_t version, - const struct spa_dict *props) -{ - struct impl *impl = data; - const struct object_info *info; - struct registry_event re = { - .id = id, .permissions = permissions, .type = type, .version = version, - .props = props, .monitor = true - }; - - pw_log_debug("%p: registry event (monitor) for new global '%d'", impl, id); - - info = get_object_info(impl, type); - if (info == NULL) - return; - - /* Bind proxy now from policy core */ - re.proxy = pw_registry_bind(impl->registry, id, type, info->version, 0); - if (re.proxy) - handle_registry_event(impl, &re); - else - pw_log_warn("%p: can't handle global %d: %s", impl, id, spa_strerror(-errno)); - - registry_event_free(&re); - return; -} - -static void -monitor_registry_global_remove(void *data, uint32_t id) -{ - struct impl *impl = data; - struct sm_object *obj; - - obj = find_object(impl, id, NULL); - obj = (obj && obj->monitor_global) ? obj : NULL; - - pw_log_debug("%p: registry event (monitor) for remove global '%d' obj:%p", impl, id, obj); - - if (obj) - sm_object_destroy_maybe_free(obj); -} - -static const struct pw_registry_events monitor_registry_events = { - PW_VERSION_REGISTRY_EVENTS, - .global = monitor_registry_global, - .global_remove = monitor_registry_global_remove, -}; - -int sm_object_add_listener(struct sm_object *obj, struct spa_hook *listener, - const struct sm_object_events *events, void *data) -{ - spa_hook_list_append(&obj->hooks, listener, events, data); - return 0; -} - -int sm_media_session_add_listener(struct sm_media_session *sess, struct spa_hook *listener, - const struct sm_media_session_events *events, void *data) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct spa_hook_list save; - struct sm_object *obj; - - spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); - - spa_list_for_each(obj, &impl->object_list, link) { - if (obj->id == SPA_ID_INVALID) - continue; - sm_media_session_emit_create(impl, obj); - } - - spa_hook_list_join(&impl->hooks, &save); - - return 0; -} - -struct sm_object *sm_media_session_find_object(struct sm_media_session *sess, uint32_t id) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - return find_object(impl, id, NULL); -} - -int sm_media_session_destroy_object(struct sm_media_session *sess, uint32_t id) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - pw_registry_destroy(impl->registry, id); - return 0; -} - -int sm_media_session_for_each_object(struct sm_media_session *sess, - int (*callback) (void *data, struct sm_object *object), - void *data) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_object *obj; - int res; - - spa_list_for_each(obj, &impl->object_list, link) { - if (obj->id == SPA_ID_INVALID) - continue; - if ((res = callback(data, obj)) != 0) - return res; - } - return 0; -} - -int sm_media_session_schedule_rescan(struct sm_media_session *sess) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - - if (impl->scanning) { - impl->rescan_pending = true; - return impl->rescan_seq; - } - if (impl->policy_core) - impl->rescan_seq = pw_core_sync(impl->policy_core, 0, impl->last_seq); - return impl->rescan_seq; -} - -int sm_media_session_sync(struct sm_media_session *sess, - void (*callback) (void *data), void *data) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sync *sync; - - sync = calloc(1, sizeof(struct sync)); - if (sync == NULL) - return -errno; - - spa_list_append(&impl->sync_list, &sync->link); - sync->callback = callback; - sync->data = data; - sync->seq = pw_core_sync(impl->policy_core, 0, impl->last_seq); - return sync->seq; -} - -static void roundtrip_callback(void *data) -{ - int *done = data; - *done = 1; -} - -int sm_media_session_roundtrip(struct sm_media_session *sess) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct pw_loop *loop = impl->this.loop; - int done, res, seq; - - if (impl->policy_core == NULL) - return -EIO; - - done = 0; - if ((seq = sm_media_session_sync(sess, roundtrip_callback, &done)) < 0) - return seq; - - pw_log_debug("%p: roundtrip %d", impl, seq); - - pw_loop_enter(loop); - while (!done) { - if ((res = pw_loop_iterate(loop, -1)) < 0) { - if (res == -EINTR) - continue; - pw_log_warn("%p: iterate error %d (%s)", - loop, res, spa_strerror(res)); - break; - } - } - pw_loop_leave(loop); - - pw_log_debug("%p: roundtrip %d done", impl, seq); - - return 0; -} - -struct pw_proxy *sm_media_session_export(struct sm_media_session *sess, - const char *type, const struct spa_dict *props, - void *object, size_t user_data_size) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct pw_proxy *handle; - - pw_log_debug("%p: object %s %p", impl, type, object); - - handle = pw_core_export(impl->monitor_core, type, - props, object, user_data_size); - - monitor_sync(impl); - - return handle; -} - -struct sm_node *sm_media_session_export_node(struct sm_media_session *sess, - const struct spa_dict *props, struct pw_impl_node *object) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_node *node; - struct pw_proxy *handle; - - pw_log_debug("%p: node %p", impl, object); - - handle = pw_core_export(impl->monitor_core, PW_TYPE_INTERFACE_Node, - props, object, sizeof(struct sm_node)); - - node = (struct sm_node *) create_object(impl, NULL, handle, props, true); - - monitor_sync(impl); - - return node; -} - -struct sm_device *sm_media_session_export_device(struct sm_media_session *sess, - const struct spa_dict *props, struct spa_device *object) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_device *device; - struct pw_proxy *handle; - - pw_log_debug("%p: device %p", impl, object); - - handle = pw_core_export(impl->monitor_core, SPA_TYPE_INTERFACE_Device, - props, object, sizeof(struct sm_device)); - - device = (struct sm_device *) create_object(impl, NULL, handle, props, true); - - monitor_sync(impl); - - return device; -} - -struct pw_proxy *sm_media_session_create_object(struct sm_media_session *sess, - const char *factory_name, const char *type, uint32_t version, - const struct spa_dict *props, size_t user_data_size) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - return pw_core_create_object(impl->policy_core, - factory_name, type, version, props, user_data_size); -} - -struct sm_node *sm_media_session_create_node(struct sm_media_session *sess, - const char *factory_name, const struct spa_dict *props) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_node *node; - struct pw_proxy *proxy; - - pw_log_debug("%p: node '%s'", impl, factory_name); - - proxy = pw_core_create_object(impl->policy_core, - factory_name, - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE, - props, - sizeof(struct sm_node)); - - node = (struct sm_node *)create_object(impl, proxy, proxy, props, false); - - return node; -} - -static void check_endpoint_link(struct endpoint_link *link) -{ - if (!spa_list_is_empty(&link->link_list)) - return; - - if (link->impl) { - spa_list_remove(&link->link); - pw_map_remove(&link->impl->endpoint_links, link->id); - - pw_client_session_link_update(link->impl->this.client_session, - link->id, - PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED, - 0, NULL, NULL); - - link->impl = NULL; - free(link); - } -} - -static void proxy_link_error(void *data, int seq, int res, const char *message) -{ - struct link *l = data; - pw_log_info("can't link %d:%d -> %d:%d: %s", - l->output_node, l->output_port, - l->input_node, l->input_port, message); - pw_proxy_destroy(l->proxy); -} - -static void proxy_link_removed(void *data) -{ - struct link *l = data; - pw_proxy_destroy(l->proxy); -} - -static void proxy_link_destroy(void *data) -{ - struct link *l = data; - - spa_list_remove(&l->link); - spa_hook_remove(&l->listener); - - if (l->endpoint_link) { - check_endpoint_link(l->endpoint_link); - l->endpoint_link = NULL; - } -} - -static const struct pw_proxy_events proxy_link_events = { - PW_VERSION_PROXY_EVENTS, - .error = proxy_link_error, - .removed = proxy_link_removed, - .destroy = proxy_link_destroy -}; - -static bool channel_is_aux(uint32_t channel) -{ - return channel >= SPA_AUDIO_CHANNEL_START_Aux && - channel <= SPA_AUDIO_CHANNEL_LAST_Aux; -} - -static int score_ports(struct sm_port *out, struct sm_port *in) -{ - int score = 0; - - if (in->direction != PW_DIRECTION_INPUT || out->direction != PW_DIRECTION_OUTPUT) - return 0; - - if (out->type != SM_PORT_TYPE_UNKNOWN && in->type != SM_PORT_TYPE_UNKNOWN && - in->type != out->type) - return 0; - - if (out->channel == in->channel) - score += 100; - else if ((out->channel == SPA_AUDIO_CHANNEL_SL && in->channel == SPA_AUDIO_CHANNEL_RL) || - (out->channel == SPA_AUDIO_CHANNEL_RL && in->channel == SPA_AUDIO_CHANNEL_SL) || - (out->channel == SPA_AUDIO_CHANNEL_SR && in->channel == SPA_AUDIO_CHANNEL_RR) || - (out->channel == SPA_AUDIO_CHANNEL_RR && in->channel == SPA_AUDIO_CHANNEL_SR)) - score += 60; - else if ((out->channel == SPA_AUDIO_CHANNEL_FC && in->channel == SPA_AUDIO_CHANNEL_MONO) || - (out->channel == SPA_AUDIO_CHANNEL_MONO && in->channel == SPA_AUDIO_CHANNEL_FC)) - score += 50; - else if (in->channel == SPA_AUDIO_CHANNEL_UNKNOWN || - in->channel == SPA_AUDIO_CHANNEL_MONO || - out->channel == SPA_AUDIO_CHANNEL_UNKNOWN || - out->channel == SPA_AUDIO_CHANNEL_MONO) - score += 10; - else if (channel_is_aux(in->channel) != channel_is_aux(out->channel)) - score += 7; - if (score > 0 && !in->visited) - score += 5; - if (score <= 10) - score = 0; - return score; -} - -static struct sm_port *find_input_port(struct impl *impl, struct sm_node *outnode, - struct sm_port *outport, struct sm_node *innode) -{ - struct sm_port *inport, *best_port = NULL; - int score, best_score = 0; - - spa_list_for_each(inport, &innode->port_list, link) { - score = score_ports(outport, inport); - if (score > best_score) { - best_score = score; - best_port = inport; - } - } - return best_port; -} - -static int link_nodes(struct impl *impl, struct endpoint_link *link, - struct sm_node *outnode, struct sm_node *innode) -{ - struct pw_properties *props; - struct sm_port *outport, *inport; - int count = 0; - bool passive = false; - const char *str; - - pw_log_debug("%p: linking %d -> %d", impl, outnode->obj.id, innode->obj.id); - - if ((str = spa_dict_lookup(outnode->info->props, PW_KEY_NODE_PASSIVE)) != NULL) - passive |= (pw_properties_parse_bool(str) || spa_streq(str, "out")); - if ((str = spa_dict_lookup(innode->info->props, PW_KEY_NODE_PASSIVE)) != NULL) - passive |= (pw_properties_parse_bool(str) || spa_streq(str, "in")); - - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", outnode->obj.id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", innode->obj.id); - pw_properties_setf(props, PW_KEY_LINK_PASSIVE, "%s", passive ? "true" : "false"); - - spa_list_for_each(inport, &innode->port_list, link) - inport->visited = false; - - spa_list_for_each(outport, &outnode->port_list, link) { - struct link *l; - struct pw_proxy *p; - - if (outport->direction != PW_DIRECTION_OUTPUT) - continue; - - inport = find_input_port(impl, outnode, outport, innode); - if (inport == NULL) { - pw_log_debug("%p: port %d:%d can't be linked", impl, - outport->direction, outport->obj.id); - continue; - } - inport->visited = true; - - pw_log_debug("%p: port %d:%d -> %d:%d", impl, - outport->direction, outport->obj.id, - inport->direction, inport->obj.id); - - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", outport->obj.id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", inport->obj.id); - - p = pw_core_create_object(impl->policy_core, - "link-factory", - PW_TYPE_INTERFACE_Link, - PW_VERSION_LINK, - &props->dict, sizeof(struct link)); - if (p == NULL) - return -errno; - - l = pw_proxy_get_user_data(p); - l->proxy = p; - l->output_node = outnode->obj.id; - l->output_port = outport->obj.id; - l->input_node = innode->obj.id; - l->input_port = inport->obj.id; - pw_proxy_add_listener(p, &l->listener, &proxy_link_events, l); - count++; - - if (link) { - l->endpoint_link = link; - spa_list_append(&link->link_list, &l->link); - } else { - spa_list_append(&impl->link_list, &l->link); - } - } - pw_properties_free(props); - - return count; -} - - -int sm_media_session_create_links(struct sm_media_session *sess, - const struct spa_dict *dict) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_object *obj; - struct sm_node *outnode = NULL, *innode = NULL; - struct sm_endpoint *outendpoint = NULL, *inendpoint = NULL; - struct sm_endpoint_stream *outstream = NULL, *instream = NULL; - struct endpoint_link *link = NULL; - const char *str; - int res; - - sm_media_session_roundtrip(sess); - - /* find output node */ - if ((str = spa_dict_lookup(dict, PW_KEY_LINK_OUTPUT_NODE)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Node)) != NULL) - outnode = (struct sm_node*)obj; - - /* find input node */ - if ((str = spa_dict_lookup(dict, PW_KEY_LINK_INPUT_NODE)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Node)) != NULL) - innode = (struct sm_node*)obj; - - /* find endpoints and streams */ - if ((str = spa_dict_lookup(dict, PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Endpoint)) != NULL) - outendpoint = (struct sm_endpoint*)obj; - - if ((str = spa_dict_lookup(dict, PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_EndpointStream)) != NULL) - outstream = (struct sm_endpoint_stream*)obj; - - if ((str = spa_dict_lookup(dict, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Endpoint)) != NULL) - inendpoint = (struct sm_endpoint*)obj; - - if ((str = spa_dict_lookup(dict, PW_KEY_ENDPOINT_LINK_INPUT_STREAM)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_EndpointStream)) != NULL) - instream = (struct sm_endpoint_stream*)obj; - - if (outendpoint != NULL && inendpoint != NULL) { - link = calloc(1, sizeof(struct endpoint_link)); - if (link == NULL) - return -errno; - - link->id = pw_map_insert_new(&impl->endpoint_links, link); - link->impl = impl; - spa_list_init(&link->link_list); - spa_list_append(&impl->endpoint_link_list, &link->link); - - link->info.version = PW_VERSION_ENDPOINT_LINK_INFO; - link->info.id = link->id; - link->info.session_id = impl->this.session->obj.id; - link->info.output_endpoint_id = outendpoint->info->id; - link->info.output_stream_id = outstream ? outstream->info->id : SPA_ID_INVALID; - link->info.input_endpoint_id = inendpoint->info->id; - link->info.input_stream_id = instream ? instream->info->id : SPA_ID_INVALID; - link->info.change_mask = - PW_ENDPOINT_LINK_CHANGE_MASK_STATE | - PW_ENDPOINT_LINK_CHANGE_MASK_PROPS; - link->info.state = PW_ENDPOINT_LINK_STATE_ACTIVE; - link->info.props = (struct spa_dict*) dict; - } - - /* link the nodes, record the link proxies in the endpoint_link */ - if (outnode != NULL && innode != NULL) - res = link_nodes(impl, link, outnode, innode); - else - res = 0; - - if (link != NULL) { - /* now create the endpoint link */ - pw_client_session_link_update(impl->this.client_session, - link->id, - PW_CLIENT_SESSION_UPDATE_INFO, - 0, NULL, - &link->info); - } - return res; -} - -int sm_media_session_remove_links(struct sm_media_session *sess, - const struct spa_dict *dict) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - struct sm_object *obj; - struct sm_node *outnode = NULL, *innode = NULL; - const char *str; - struct link *l, *t; - - /* find output node */ - if ((str = spa_dict_lookup(dict, PW_KEY_LINK_OUTPUT_NODE)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Node)) != NULL) - outnode = (struct sm_node*)obj; - - /* find input node */ - if ((str = spa_dict_lookup(dict, PW_KEY_LINK_INPUT_NODE)) != NULL && - (obj = find_object(impl, atoi(str), PW_TYPE_INTERFACE_Node)) != NULL) - innode = (struct sm_node*)obj; - - if (innode == NULL || outnode == NULL) - return -EINVAL; - - spa_list_for_each_safe(l, t, &impl->link_list, link) { - if (l->output_node == outnode->obj.id && l->input_node == innode->obj.id) { - pw_proxy_destroy(l->proxy); - } - } - return 0; -} - -int sm_media_session_load_conf(struct sm_media_session *sess, const char *name, - struct pw_properties *conf) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - return pw_conf_load_conf(impl->config_dir, name, conf); -} - -int sm_media_session_load_state(struct sm_media_session *sess, - const char *name, struct pw_properties *props) -{ - return pw_conf_load_state(SESSION_PREFIX, name, props); -} - -int sm_media_session_save_state(struct sm_media_session *sess, - const char *name, const struct pw_properties *props) -{ - return pw_conf_save_state(SESSION_PREFIX, name, props); -} - -char *sm_media_session_sanitize_name(char *name, int size, char sub, const char *fmt, ...) -{ - char *p; - va_list varargs; - int res; - - va_start(varargs, fmt); - res = vsnprintf(name, size, fmt, varargs); - va_end(varargs); - - if (res < 0) - return NULL; - - for (p = name; *p; p++) { - switch(*p) { - case '0' ... '9': - case 'a' ... 'z': - case 'A' ... 'Z': - case '.': case '-': case '_': - break; - default: - *p = sub; - break; - } - } - return name; -} - -char *sm_media_session_sanitize_description(char *name, int size, char sub, const char *fmt, ...) -{ - char *p; - va_list varargs; - int res; - - va_start(varargs, fmt); - res = vsnprintf(name, size, fmt, varargs); - va_end(varargs); - - if (res < 0) - return NULL; - - for (p = name; *p; p++) { - switch(*p) { - case ':': - *p = sub; - break; - } - } - return name; -} - -int sm_media_session_seat_active_changed(struct sm_media_session *sess, bool active) -{ - struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); - if (active != impl->seat_active) { - impl->seat_active = active; - sm_media_session_emit_seat_active(impl, active); - } - return 0; -} - -static void monitor_core_done(void *data, uint32_t id, int seq) -{ - struct impl *impl = data; - - if (id == 0) - handle_postponed_registry_events(impl, seq); - - if (seq == impl->monitor_seq) { - pw_log_debug("%p: monitor sync stop %d", impl, seq); - pw_core_set_paused(impl->policy_core, false); - } -} - -static const struct pw_core_events monitor_core_events = { - PW_VERSION_CORE_EVENTS, - .done = monitor_core_done, -}; - -static int start_session(struct impl *impl) -{ - impl->monitor_core = pw_context_connect(impl->this.context, NULL, 0); - if (impl->monitor_core == NULL) { - pw_log_error("can't start monitor: %m"); - return -errno; - } - - pw_core_add_listener(impl->monitor_core, - &impl->monitor_listener, - &monitor_core_events, impl); - - impl->monitor_registry = pw_core_get_registry(impl->monitor_core, - PW_VERSION_REGISTRY, 0); - pw_registry_add_listener(impl->monitor_registry, - &impl->monitor_registry_listener, - &monitor_registry_events, impl); - - return 0; -} - -static void core_info(void *data, const struct pw_core_info *info) -{ - struct impl *impl = data; - pw_log_debug("%p: info", impl); - impl->this.info = pw_core_info_merge(impl->this.info, info, true); - - if (impl->this.info->change_mask != 0) - sm_media_session_emit_info(impl, impl->this.info); - impl->this.info->change_mask = 0; -} - -static void core_done(void *data, uint32_t id, int seq) -{ - struct impl *impl = data; - struct sync *s, *t; - impl->last_seq = seq; - - spa_list_for_each_safe(s, t, &impl->sync_list, link) { - if (s->seq == seq) { - spa_list_remove(&s->link); - s->callback(s->data); - free(s); - } - } - if (impl->rescan_seq == seq) { - struct sm_object *obj, *to; - - if (!impl->scanning) { - pw_log_trace("%p: rescan %u %d", impl, id, seq); - impl->scanning = true; - sm_media_session_emit_rescan(impl, seq); - impl->scanning = false; - if (impl->rescan_pending) { - impl->rescan_pending = false; - sm_media_session_schedule_rescan(&impl->this); - } - } - - spa_list_for_each_safe(obj, to, &impl->object_list, link) { - if (obj->id == SPA_ID_INVALID) - continue; - pw_log_trace("%p: obj %p %08x", impl, obj, obj->changed); - if (obj->changed) - sm_object_emit_update(obj); - obj->changed = 0; - } - } -} - -static void core_error(void *data, uint32_t id, int seq, int res, const char *message) -{ - struct impl *impl = data; - - pw_log(res == -ENOENT || res == -EINVAL ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_WARN, - "error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); - - if (id == PW_ID_CORE) { - if (res == -EPIPE) - pw_main_loop_quit(impl->loop); - } -} - - -static const struct pw_core_events policy_core_events = { - PW_VERSION_CORE_EVENTS, - .info = core_info, - .done = core_done, - .error = core_error -}; - -static void policy_core_destroy(void *data) -{ - struct impl *impl = data; - pw_log_debug("%p: policy core destroy", impl); - impl->policy_core = NULL; -} - -static const struct pw_proxy_events proxy_core_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = policy_core_destroy, -}; - -static int start_policy(struct impl *impl) -{ - impl->policy_core = pw_context_connect(impl->this.context, NULL, 0); - if (impl->policy_core == NULL) { - pw_log_error("can't start policy: %m"); - return -errno; - } - - pw_core_add_listener(impl->policy_core, - &impl->policy_listener, - &policy_core_events, impl); - pw_proxy_add_listener((struct pw_proxy*)impl->policy_core, - &impl->proxy_policy_listener, - &proxy_core_events, impl); - - impl->registry = pw_core_get_registry(impl->policy_core, - PW_VERSION_REGISTRY, 0); - pw_registry_add_listener(impl->registry, - &impl->registry_listener, - ®istry_events, impl); - - return 0; -} - -static void session_shutdown(struct impl *impl) -{ - struct sm_object *obj; - struct registry_event *re; - struct spa_list free_list; - - pw_log_info("%p", impl); - sm_media_session_emit_shutdown(impl); - - /* - * Monitors may still hold references to objects, which they - * drop in session destroy event, so don't free undiscarded - * objects yet. Destroy event handlers may remove any objects - * in the list, so iterate carefully. - */ - spa_list_init(&free_list); - spa_list_consume(obj, &impl->object_list, link) { - if (obj->destroyed) { - spa_list_remove(&obj->link); - spa_list_append(&free_list, &obj->link); - } else { - sm_object_destroy_maybe_free(obj); - } - } - - spa_list_consume(re, &impl->registry_event_list, link) - registry_event_free(re); - - impl->this.metadata = NULL; - - sm_media_session_emit_destroy(impl); - - spa_list_consume(obj, &free_list, link) - sm_object_destroy(obj); - spa_list_consume(obj, &impl->object_list, link) - sm_object_destroy(obj); /* in case emit_destroy created new objects */ - - if (impl->registry) { - spa_hook_remove(&impl->registry_listener); - pw_proxy_destroy((struct pw_proxy*)impl->registry); - } - if (impl->monitor_registry) { - spa_hook_remove(&impl->monitor_registry_listener); - pw_proxy_destroy((struct pw_proxy*)impl->monitor_registry); - } - if (impl->policy_core) { - spa_hook_remove(&impl->policy_listener); - spa_hook_remove(&impl->proxy_policy_listener); - pw_core_disconnect(impl->policy_core); - } - if (impl->monitor_core) { - spa_hook_remove(&impl->monitor_listener); - pw_core_disconnect(impl->monitor_core); - } - if (impl->this.info) - pw_core_info_free(impl->this.info); -} - -static int sm_metadata_start(struct sm_media_session *sess) -{ - sess->metadata = sm_media_session_export_metadata(sess, "default"); - if (sess->metadata == NULL) - return -errno; - return 0; -} - -static int sm_pulse_bridge_start(struct sm_media_session *sess) -{ - if (pw_context_load_module(sess->context, - "libpipewire-module-protocol-pulse", - NULL, NULL) == NULL) - return -errno; - return 0; -} - -static void dbus_connection_disconnected(void *data) -{ - struct impl *impl = data; - pw_log_info("DBus disconnected"); - sm_media_session_emit_dbus_disconnected(impl); -} - -static const struct spa_dbus_connection_events dbus_connection_events = { - SPA_VERSION_DBUS_CONNECTION_EVENTS, - .disconnected = dbus_connection_disconnected -}; - -static void do_quit(void *data, int signal_number) -{ - struct impl *impl = data; - pw_main_loop_quit(impl->loop); -} - -static int collect_modules(struct impl *impl, const char *str) -{ - struct spa_json it[3]; - char key[512], value[512]; - const char *dir, *prefix = NULL, *val; - char check_path[PATH_MAX]; - struct stat statbuf; - int count = 0; - - dir = getenv("MEDIA_SESSION_CONFIG_DIR"); - if (dir == NULL) { - prefix = SESSION_PREFIX; - if ((dir = getenv("PIPEWIRE_CONFIG_DIR")) == NULL) - dir = PIPEWIRE_CONFDATADIR; - } - if (dir == NULL) - return -ENOENT; - -again: - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) < 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - bool add = false; - - if (pw_properties_get(impl->modules, key) != NULL) { - add = true; - } else { - snprintf(check_path, sizeof(check_path), - "%s%s%s/%s", dir, prefix ? "/" : "", prefix ? prefix : "", key); - add = (stat(check_path, &statbuf) == 0); - } - if (add) { - if (spa_json_enter_array(&it[1], &it[2]) < 0) - continue; - - while (spa_json_get_string(&it[2], value, sizeof(value)-1) > 0) { - pw_properties_set(impl->modules, value, "true"); - } - } - else if (spa_json_next(&it[1], &val) <= 0) - break; - } - /* twice to resolve groups in module list */ - if (count++ == 0) - goto again; - - return 0; -} - -static const struct { - const char *name; - const char *desc; - int (*start)(struct sm_media_session *sess); - const char *props; - -} modules[] = { - { "flatpak", "manage flatpak access", sm_access_flatpak_start, NULL }, - { "portal", "manage portal permissions", sm_access_portal_start, NULL }, - { "metadata", "export metadata API", sm_metadata_start, NULL }, - { "default-nodes", "restore default nodes", sm_default_nodes_start, NULL }, - { "default-profile", "restore default profiles", sm_default_profile_start, NULL }, - { "default-routes", "restore default route", sm_default_routes_start, NULL }, - { "restore-stream", "restore stream settings", sm_restore_stream_start, NULL }, - { "streams-follow-default", "move streams when default changes", sm_streams_follow_default_start, NULL }, - { "alsa-no-dsp", "do not configure audio nodes in DSP mode", sm_alsa_no_dsp_start, NULL }, - { "alsa-seq", "alsa seq midi support", sm_alsa_midi_start, NULL }, - { "alsa-monitor", "alsa card udev detection", sm_alsa_monitor_start, NULL }, - { "v4l2", "video for linux udev detection", sm_v4l2_monitor_start, NULL }, - { "libcamera", "libcamera udev detection", sm_libcamera_monitor_start, NULL }, - { "bluez5", "bluetooth support", sm_bluez5_monitor_start, NULL }, - { "bluez5-autoswitch", "switch bluetooth profiles automatically", sm_bluez5_autoswitch_start, NULL }, - { "suspend-node", "suspend inactive nodes", sm_suspend_node_start, NULL }, - { "policy-node", "configure and link nodes", sm_policy_node_start, NULL }, - { "pulse-bridge", "accept pulseaudio clients", sm_pulse_bridge_start, NULL }, -#ifdef HAVE_SYSTEMD - { "logind", "systemd-logind seat support", sm_logind_start, NULL }, -#endif -}; - -static bool is_module_enabled(struct impl *impl, const char *val) -{ - return pw_properties_get_bool(impl->modules, val, false); -} - -static void show_help(const char *name, struct impl *impl, const char *config_name) -{ - size_t i; - - fprintf(stdout, "%s [options]\n" - " -h, --help Show this help\n" - " --version Show version\n" - " -c, --config Load config (Default %s)\n", - name, config_name); - - fprintf(stdout, "\noptions: (*=enabled)\n"); - for (i = 0; i < SPA_N_ELEMENTS(modules); i++) { - fprintf(stdout, "\t %c %-15.15s: %s\n", - is_module_enabled(impl, modules[i].name) ? '*' : ' ', - modules[i].name, modules[i].desc); - } -} - -int main(int argc, char *argv[]) -{ - struct impl impl = { .seat_active = true }; - const struct spa_support *support; - const char *str, *config_name = SESSION_CONF; - bool do_show_help = false; - uint32_t n_support; - int res = 0, c; - static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "config", required_argument, NULL, 'c' }, - { "verbose", no_argument, NULL, 'v' }, - { NULL, 0, NULL, 0} - }; - size_t i; - const struct spa_dict_item *item; - enum spa_log_level level = pw_log_level; - const char *config_dir; - - pw_init(&argc, &argv); - - PW_LOG_TOPIC_INIT(ms_topic); - - while ((c = getopt_long(argc, argv, "hVc:v", long_options, NULL)) != -1) { - switch (c) { - case 'v': - if (level < SPA_LOG_LEVEL_TRACE) - pw_log_set_level(++level); - break; - case 'h': - do_show_help = true; - break; - case 'V': - fprintf(stdout, "%s\n" - "Compiled with libpipewire %s\n" - "Linked with libpipewire %s\n", - argv[0], - pw_get_headers_version(), - pw_get_library_version()); - return 0; - case 'c': - config_name = optarg; - break; - default: - return 1; - } - } - - config_dir = getenv("MEDIA_SESSION_CONFIG_DIR"); - impl.config_dir = config_dir ? config_dir : SESSION_PREFIX; - impl.this.props = pw_properties_new( - PW_KEY_CONFIG_PREFIX, impl.config_dir, - PW_KEY_CONFIG_NAME, config_name, - NULL); - if (impl.this.props == NULL) - return 1; - - if ((impl.conf = pw_properties_new(NULL, NULL)) == NULL) - return 1; - - pw_conf_load_conf(impl.config_dir, config_name, impl.conf); - - if ((str = pw_properties_get(impl.conf, "context.properties")) != NULL) - pw_properties_update_string(impl.this.props, str, strlen(str)); - - if ((impl.modules = pw_properties_new("default", "true", NULL)) == NULL) - return 1; - if ((str = pw_properties_get(impl.conf, "session.modules")) != NULL) - collect_modules(&impl, str); - - if (do_show_help) { - show_help(argv[0], &impl, config_name); - return 0; - } - - pw_log_info("media-session context properties:"); - spa_dict_for_each(item, &impl.this.props->dict) - pw_log_info(" '%s' = '%s'", item->key, item->value); - - impl.loop = pw_main_loop_new(NULL); - if (impl.loop == NULL) - return 1; - impl.this.loop = pw_main_loop_get_loop(impl.loop); - - pw_loop_add_signal(impl.this.loop, SIGINT, do_quit, &impl); - pw_loop_add_signal(impl.this.loop, SIGTERM, do_quit, &impl); - - impl.this.context = pw_context_new(impl.this.loop, - pw_properties_copy(impl.this.props), - 0); - - if (impl.this.context == NULL) - return 1; - - pw_context_set_object(impl.this.context, SM_TYPE_MEDIA_SESSION, &impl); - - pw_map_init(&impl.globals, 64, 64); - spa_list_init(&impl.object_list); - spa_list_init(&impl.registry_event_list); - spa_list_init(&impl.link_list); - pw_map_init(&impl.endpoint_links, 64, 64); - spa_list_init(&impl.endpoint_link_list); - spa_list_init(&impl.sync_list); - spa_hook_list_init(&impl.hooks); - - support = pw_context_get_support(impl.this.context, &n_support); - - impl.dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); - if (impl.dbus) { - impl.this.dbus_connection = spa_dbus_get_connection(impl.dbus, SPA_DBUS_TYPE_SESSION); - if (impl.this.dbus_connection == NULL) - pw_log_warn("no dbus connection"); - else { - pw_log_debug("got dbus connection %p", impl.this.dbus_connection); - spa_dbus_connection_add_listener(impl.this.dbus_connection, - &impl.dbus_connection_listener, - &dbus_connection_events, &impl); - } - } else { - pw_log_info("dbus disabled"); - } - - if ((res = start_session(&impl)) < 0) - goto exit; - if ((res = start_policy(&impl)) < 0) - goto exit; - - for (i = 0; i < SPA_N_ELEMENTS(modules); i++) { - const char *name = modules[i].name; - if (is_module_enabled(&impl, name)) { - pw_log_info("enabling media session module: %s", name); - modules[i].start(&impl.this); - } - } - -// sm_session_manager_start(&impl.this); - - pw_main_loop_run(impl.loop); - -exit: - session_shutdown(&impl); - - pw_context_destroy(impl.this.context); - pw_main_loop_destroy(impl.loop); - - pw_map_clear(&impl.endpoint_links); - pw_map_clear(&impl.globals); - pw_properties_free(impl.this.props); - pw_properties_free(impl.conf); - pw_properties_free(impl.modules); - - pw_deinit(); - - return res; -} diff --git a/src/media-session/media-session.d/alsa-monitor.conf b/src/media-session/media-session.d/alsa-monitor.conf deleted file mode 100644 index 1fd812b88..000000000 --- a/src/media-session/media-session.d/alsa-monitor.conf +++ /dev/null @@ -1,126 +0,0 @@ -# ALSA monitor config file for PipeWire version @VERSION@ # -# -# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@/media-session.d/ -# for system-wide changes or in -# ~/.config/pipewire/media-session.d/ for local changes. - -properties = { - # Create a JACK device. This is not enabled by default because - # it requires that the PipeWire JACK replacement libraries are - # not used by the session manager, in order to be able to - # connect to the real JACK server. - #alsa.jack-device = false - - # Reserve devices. - #alsa.reserve = true -} - -rules = [ - # An array of matches/actions to evaluate. - { - # Rules for matching a device or node. Each dictionary in this array - # specifies the property to match as key and a string or regex match - # as value. A successful match requires all dictionary keys (i.e. - # properties) to match. - # - # Actions are are executed for the object if at least one successful - # match exists. - # - # Regular expressions are prefixed with the ~ (tilde) character, - # otherwise a standard string comparison is used. - # The special value "null" matches against empty properties. - matches = [ - { - # This matches all cards. These are regular expressions - # so "." matches one character and ".*" matches many. - device.name = "~alsa_card.*" - } - ] - actions = { - # Actions can update properties on the matched object. - update-props = { - # Use ALSA-Card-Profile devices. They use UCM or - # the profile configuration to configure the device - # and mixer settings. - api.alsa.use-acp = true - - # Use UCM instead of profile when available. Can be - # disabled to skip trying to use the UCM profile. - #api.alsa.use-ucm = true - - # Don't use the hardware mixer for volume control. It - # will only use software volume. The mixer is still used - # to mute unused paths based on the selected port. - #api.alsa.soft-mixer = false - - # Ignore decibel settings of the driver. Can be used to - # work around buggy drivers that report wrong values. - #api.alsa.ignore-dB = false - - # The profile set to use for the device. Usually this is - # "default.conf" but can be changed with a udev rule - # or here. - #device.profile-set = "profileset-name.conf" - - # The default active profile. Is by default set to "Off". - #device.profile = "default profile name" - - # Automatically select the best profile. This is the - # highest priority available profile. This is disabled - # here and instead implemented in the session manager - # where it can save and load previous preferences. - api.acp.auto-profile = false - - # Automatically switch to the highest priority available - # port. This is disabled here and implemented in the - # session manager instead. - api.acp.auto-port = false - - # Other properties can be set here. - #device.nick = "My Device" - } - } - } - { - matches = [ - { - # Matches all sources. These are regular expressions - # so "." matches one character and ".*" matches many. - node.name = "~alsa_input.*" - } - { - # Matches all sinks. - node.name = "~alsa_output.*" - } - ] - actions = { - update-props = { - #node.nick = "My Node" - #node.nick = null - #priority.driver = 100 - #priority.session = 100 - node.pause-on-idle = false - #resample.quality = 4 - #channelmix.normalize = false - #channelmix.mix-lfe = false - #audio.channels = 2 - #audio.format = "S16LE" - #audio.rate = 44100 - #audio.position = "FL,FR" - #session.suspend-timeout-seconds = 5 # 0 disables suspend - #monitor.channel-volumes = false - - #latency.internal.rate = 0 # internal latency in samples - #latency.internal.ns = 0 # internal latency in nanoseconds - - #api.alsa.period-size = 1024 - #api.alsa.headroom = 0 - #api.alsa.start-delay = 0 - #api.alsa.disable-mmap = false - #api.alsa.disable-batch = false - #api.alsa.use-chmap = false - #iec958.codecs = [ PCM DTS AC3 MPEG MPEG2-AAC EAC3 TrueHD DTS-HD ] - } - } - } -] diff --git a/src/media-session/media-session.d/bluez-monitor.conf b/src/media-session/media-session.d/bluez-monitor.conf deleted file mode 100644 index 754c779c6..000000000 --- a/src/media-session/media-session.d/bluez-monitor.conf +++ /dev/null @@ -1,139 +0,0 @@ -# Bluez monitor config file for PipeWire version @VERSION@ # -# -# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@/media-session.d/ -# for system-wide changes or in -# ~/.config/pipewire/media-session.d/ for local changes. - -properties = { - # These features do not work on all headsets, so they are enabled - # by default based on the hardware database. They can also be - # forced on/off for all devices by the following options: - - #bluez5.enable-sbc-xq = true - #bluez5.enable-msbc = true - #bluez5.enable-hw-volume = true - #bluez5.enable-faststream = true - - # See bluez-hardware.conf for the hardware database. - - # Enabled headset roles (default: [ hsp_hs hfp_ag ]), this - # property only applies to native backend. Currently some headsets - # (Sony WH-1000XM3) are not working with both hsp_ag and hfp_ag - # enabled, disable either hsp_ag or hfp_ag to work around it. - # - # Supported headset roles: hsp_hs (HSP Headset), - # hsp_ag (HSP Audio Gateway), - # hfp_hf (HFP Hands-Free), - # hfp_ag (HFP Audio Gateway) - #bluez5.headset-roles = [ hsp_hs hsp_ag hfp_hf hfp_ag ] - - # Enabled A2DP codecs (default: all). - #bluez5.codecs = [ sbc sbc_xq aac ldac aptx aptx_hd aptx_ll aptx_ll_duplex faststream faststream_duplex ] - - # HFP/HSP backend (default: native). - # Available values: any, none, hsphfpd, ofono, native - #bluez5.hfphsp-backend = native - - # Properties for the A2DP codec configuration - #bluez5.default.rate = 48000 - #bluez5.default.channels = 2 - - # Register dummy AVRCP player, required for AVRCP volume function. - # Disable if you are running mpris-proxy or equivalent. - #bluez5.dummy-avrcp-player = true -} - -rules = [ - # An array of matches/actions to evaluate. - { - # Rules for matching a device or node. It is an array of - # properties that all need to match the regexp. If any of the - # matches work, the actions are executed for the object. - matches = [ - { - # This matches all cards. - device.name = "~bluez_card.*" - } - ] - actions = { - # Actions can update properties on the matched object. - update-props = { - - # Auto-connect device profiles on start up or when only partial - # profiles have connected. Disabled by default if the property - # is not specified. - #bluez5.auto-connect = [ - # hfp_hf - # hsp_hs - # a2dp_sink - # hfp_ag - # hsp_ag - # a2dp_source - #] - bluez5.auto-connect = [ hfp_hf hsp_hs a2dp_sink ] - - # Hardware volume control (default: all) - #bluez5.hw-volume = [ - # hfp_hf - # hsp_hs - # a2dp_sink - # hfp_ag - # hsp_ag - # a2dp_source - #] - - # LDAC encoding quality - # Available values: auto (Adaptive Bitrate, default) - # hq (High Quality, 990/909kbps) - # sq (Standard Quality, 660/606kbps) - # mq (Mobile use Quality, 330/303kbps) - #bluez5.a2dp.ldac.quality = auto - - # AAC variable bitrate mode - # Available values: 0 (cbr, default), 1-5 (quality level) - #bluez5.a2dp.aac.bitratemode = 0 - - # Profile connected first - # Available values: a2dp-sink (default), headset-head-unit - #bluez5.profile = a2dp-sink - - # A2DP <-> HFP profile auto-switching (when device is default output) - # Available values: false, role (default), true - # 'role' will switch the profile if the recording application - # specifies Communication (or "phone" in PA) as the stream role. - #bluez5.autoswitch-profile = role - } - } - } - { - matches = [ - { - # Matches all sources. - node.name = "~bluez_input.*" - } - { - # Matches all sinks. - node.name = "~bluez_output.*" - } - ] - actions = { - update-props = { - #node.nick = "My Node" - #node.nick = null - #priority.driver = 100 - #priority.session = 100 - node.pause-on-idle = false - #resample.quality = 4 - #channelmix.normalize = false - #channelmix.mix-lfe = false - #session.suspend-timeout-seconds = 5 # 0 disables suspend - #monitor.channel-volumes = false - - # A2DP source role, "input" or "playback" - # Defaults to "playback", playing stream to speakers - # Set to "input" to use as an input for apps - #bluez5.a2dp-source-role = input - } - } - } -] diff --git a/src/media-session/media-session.d/media-session.conf b/src/media-session/media-session.d/media-session.conf deleted file mode 100644 index 0693a8d8b..000000000 --- a/src/media-session/media-session.d/media-session.conf +++ /dev/null @@ -1,117 +0,0 @@ -# Media session config file for PipeWire version @VERSION@ # -# -# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@/media-session.d/ -# for system-wide changes or in -# ~/.config/pipewire/media-session.d/ for local changes. - -context.properties = { - # Properties to configure the session and some - # modules. - #mem.mlock-all = false - #support.dbus = true - #log.level = 2 - #alsa.seq.name = Midi-Bridge - #default-profile.restore-bluetooth = false -} - -context.spa-libs = { - # Mapping from factory name to library. - api.bluez5.* = bluez5/libspa-bluez5 - api.alsa.* = alsa/libspa-alsa - api.v4l2.* = v4l2/libspa-v4l2 - api.libcamera.* = libcamera/libspa-libcamera -} - -context.modules = [ - #{ name = - # [ args = { = ... } ] - # [ flags = [ [ ifexists ] [ nofail ] ] - #} - # - # Loads a module with the given parameters. - # If ifexists is given, the module is ignored when it is not found. - # If nofail is given, module initialization failures are ignored. - # - # Uses RTKit to boost the data thread priority. - { name = libpipewire-module-rtkit - args = { - #nice.level = -11 - #rt.prio = 88 - #rt.time.soft = 2000000 - #rt.time.hard = 2000000 - } - flags = [ ifexists nofail ] - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # Allows creating devices that run in the context of the - # client. Is used by the session manager. - { name = libpipewire-module-client-device } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - - # Allows applications to create metadata objects. It creates - # a factory for Metadata objects. - { name = libpipewire-module-metadata } - - # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } -] - -session.modules = { - # These are the modules that are enabled when a file with - # the key name is found in the media-session.d config directory. - # the default bundle is always enabled. - - default = [ - flatpak # manages flatpak access - portal # manage portal permissions - v4l2 # video for linux udev detection - #libcamera # libcamera udev detection - suspend-node # suspend inactive nodes - policy-node # configure and link nodes - #metadata # export metadata API - #default-nodes # restore default nodes - #default-profile # restore default profiles - #default-routes # restore default route - #streams-follow-default # move streams when default changes - #alsa-no-dsp # do not configure audio nodes in DSP mode - #alsa-seq # alsa seq midi support - #alsa-monitor # alsa udev detection - #bluez5 # bluetooth support - #bluez5-autoswitch # automatic bluetooth HSP/HFP profile switch - #restore-stream # restore stream settings - #logind # systemd-logind seat support - ] - with-audio = [ - metadata - default-nodes - default-profile - default-routes - alsa-seq - alsa-monitor - ] - with-alsa = [ - with-audio - ] - with-jack = [ - with-audio - ] - with-pulseaudio = [ - with-audio - bluez5 - bluez5-autoswitch - logind - restore-stream - streams-follow-default - ] -} diff --git a/src/media-session/media-session.d/meson.build b/src/media-session/media-session.d/meson.build deleted file mode 100644 index 9acfaef7d..000000000 --- a/src/media-session/media-session.d/meson.build +++ /dev/null @@ -1,25 +0,0 @@ -conf_config = configuration_data() -conf_config.set('VERSION', '"@0@"'.format(pipewire_version)) -conf_config.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) - -conf_files = [ - [ 'bluez-monitor.conf', 'bluez-monitor.conf' ], - [ 'v4l2-monitor.conf', 'v4l2-monitor.conf' ], - [ 'media-session.conf', 'media-session.conf' ], - [ 'alsa-monitor.conf', 'alsa-monitor.conf' ], - [ 'with-jack', 'with-jack' ], - [ 'with-pulseaudio', 'with-pulseaudio' ], -] - -foreach c : conf_files - configure_file(input : c.get(0), - output : c.get(1), - configuration : conf_config, - install_dir : pipewire_confdatadir / 'media-session.d') -endforeach - -install_data( - sources : [ - 'with-jack', - 'with-pulseaudio' ], - install_dir : pipewire_confdatadir / 'media-session.d') diff --git a/src/media-session/media-session.d/v4l2-monitor.conf b/src/media-session/media-session.d/v4l2-monitor.conf deleted file mode 100644 index e9e28b3cb..000000000 --- a/src/media-session/media-session.d/v4l2-monitor.conf +++ /dev/null @@ -1,50 +0,0 @@ -# V4L2 monitor config file for PipeWire version @VERSION@ # -# -# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@/media-session.d/ -# for system-wide changes or in -# ~/.config/pipewire/media-session.d/ for local changes. - -properties = { } - -rules = [ - # An array of matches/actions to evaluate. - { - # Rules for matching a device or node. It is an array of - # properties that all need to match the regexp. If any of the - # matches work, the actions are executed for the object. - matches = [ - { - # This matches all devices. - device.name = "~v4l2_device.*" - } - ] - actions = { - # Actions can update properties on the matched object. - update-props = { - #device.nick = "My Device" - } - } - } - { - matches = [ - { - # Matches all sources. - node.name = "~v4l2_input.*" - } - { - # Matches all sinks. - node.name = "~v4l2_output.*" - } - ] - actions = { - update-props = { - #node.nick = "My Node" - #node.nick = null - #priority.driver = 100 - #priority.session = 100 - node.pause-on-idle = false - #session.suspend-timeout-seconds = 5 # 0 disables suspend - } - } - } -] diff --git a/src/media-session/media-session.d/with-jack b/src/media-session/media-session.d/with-jack deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/media-session/media-session.d/with-pulseaudio b/src/media-session/media-session.d/with-pulseaudio deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/media-session/media-session.h b/src/media-session/media-session.h deleted file mode 100644 index c7711c45a..000000000 --- a/src/media-session/media-session.h +++ /dev/null @@ -1,328 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - - -#ifndef SM_MEDIA_SESSION_H -#define SM_MEDIA_SESSION_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define SM_TYPE_MEDIA_SESSION PW_TYPE_INFO_OBJECT_BASE "SessionManager" - -#define SM_MAX_PARAMS 32 - -struct sm_media_session; - -struct sm_object_events { -#define SM_VERSION_OBJECT_EVENTS 0 - uint32_t version; - - void (*update) (void *data); - void (*destroy) (void *data); - void (*free) (void *data); -}; - -struct sm_object_methods { -#define SM_VERSION_OBJECT_METHODS 0 - uint32_t version; - - int (*acquire) (void *data); - int (*release) (void *data); -}; - -struct sm_object { - uint32_t id; - const char *type; - - struct spa_list link; - struct sm_media_session *session; - -#define SM_OBJECT_CHANGE_MASK_LISTENER (1<<1) -#define SM_OBJECT_CHANGE_MASK_PROPERTIES (1<<2) -#define SM_OBJECT_CHANGE_MASK_BIND (1<<3) -#define SM_OBJECT_CHANGE_MASK_LAST (1<<8) - uint32_t mask; /**< monitored info */ - uint32_t avail; /**< available info */ - uint32_t changed; /**< changed since last update */ - struct pw_properties *props; /**< global properties */ - - struct pw_proxy *proxy; - struct spa_hook proxy_listener; - struct spa_hook object_listener; - pw_destroy_t destroy; - int pending; - - struct pw_proxy *handle; - struct spa_hook handle_listener; - struct spa_hook_list hooks; - - struct spa_callbacks methods; - - struct spa_list data; - - unsigned int monitor_global:1; /**< whether handle is from monitor core */ - unsigned int destroyed:1; /**< whether proxies have been destroyed */ - unsigned int discarded:1; /**< whether monitors hold no references */ -}; - -int sm_object_add_listener(struct sm_object *obj, struct spa_hook *listener, - const struct sm_object_events *events, void *data); - -#define sm_object_call(o,...) spa_callbacks_call(&(o)->methods, struct sm_object_methods, __VA_ARGS__) -#define sm_object_call_res(o,...) spa_callbacks_call_res(&(o)->methods, struct sm_object_methods, 0, __VA_ARGS__) - -#define sm_object_acquire(o) sm_object_call(o, acquire, 0) -#define sm_object_release(o) sm_object_call(o, release, 0) - -struct sm_param { - uint32_t id; - struct spa_list link; /**< link in param_list */ - struct spa_pod *param; -}; - -/** get user data with \a id and \a size to an object */ -void *sm_object_add_data(struct sm_object *obj, const char *id, size_t size); -void *sm_object_get_data(struct sm_object *obj, const char *id); -int sm_object_remove_data(struct sm_object *obj, const char *id); - -int sm_object_sync_update(struct sm_object *obj); - -int sm_object_destroy(struct sm_object *obj); - -#define sm_object_discard(o) do { (o)->discarded = true; } while (0) - -struct sm_client { - struct sm_object obj; - -#define SM_CLIENT_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) -#define SM_CLIENT_CHANGE_MASK_PERMISSIONS (SM_OBJECT_CHANGE_MASK_LAST<<1) - struct pw_client_info *info; -}; - -struct sm_device { - struct sm_object obj; - - unsigned int locked:1; /**< if the device is locked by someone else right now */ - -#define SM_DEVICE_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) -#define SM_DEVICE_CHANGE_MASK_PARAMS (SM_OBJECT_CHANGE_MASK_LAST<<1) -#define SM_DEVICE_CHANGE_MASK_NODES (SM_OBJECT_CHANGE_MASK_LAST<<2) - uint32_t n_params; - struct spa_list param_list; /**< list of sm_param */ - int param_seq[SM_MAX_PARAMS]; - struct pw_device_info *info; - struct spa_list node_list; -}; - -struct sm_node { - struct sm_object obj; - - struct sm_device *device; /**< optional device */ - struct spa_list link; /**< link in device node_list */ - -#define SM_NODE_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) -#define SM_NODE_CHANGE_MASK_PARAMS (SM_OBJECT_CHANGE_MASK_LAST<<1) -#define SM_NODE_CHANGE_MASK_PORTS (SM_OBJECT_CHANGE_MASK_LAST<<2) - uint32_t n_params; - struct spa_list param_list; /**< list of sm_param */ - int param_seq[SM_MAX_PARAMS]; - struct pw_node_info *info; - struct spa_list port_list; - - char *target_node; /**< desired target node */ - unsigned int fixed_target:1; /**< target_node has priority over node.target */ -}; - -struct sm_port { - struct sm_object obj; - - enum pw_direction direction; -#define SM_PORT_TYPE_UNKNOWN 0 -#define SM_PORT_TYPE_DSP_AUDIO 1 -#define SM_PORT_TYPE_DSP_MIDI 2 - uint32_t type; - uint32_t channel; - struct sm_node *node; - struct spa_list link; /**< link in node port_list */ - -#define SM_PORT_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) - struct pw_port_info *info; - - unsigned int visited:1; -}; - -struct sm_session { - struct sm_object obj; - -#define SM_SESSION_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) -#define SM_SESSION_CHANGE_MASK_ENDPOINTS (SM_OBJECT_CHANGE_MASK_LAST<<1) - struct pw_session_info *info; - struct spa_list endpoint_list; -}; - -struct sm_endpoint { - struct sm_object obj; - - int32_t priority; - - struct sm_session *session; - struct spa_list link; /**< link in session endpoint_list */ - -#define SM_ENDPOINT_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) -#define SM_ENDPOINT_CHANGE_MASK_STREAMS (SM_OBJECT_CHANGE_MASK_LAST<<1) - struct pw_endpoint_info *info; - struct spa_list stream_list; -}; - -struct sm_endpoint_stream { - struct sm_object obj; - - int32_t priority; - - struct sm_endpoint *endpoint; - struct spa_list link; /**< link in endpoint stream_list */ - - struct spa_list link_list; /**< list of links */ - -#define SM_ENDPOINT_STREAM_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) - struct pw_endpoint_stream_info *info; -}; - -struct sm_endpoint_link { - struct sm_object obj; - - struct spa_list link; /**< link in session link_list */ - - struct spa_list output_link; - struct sm_endpoint_stream *output; - struct spa_list input_link; - struct sm_endpoint_stream *input; - -#define SM_ENDPOINT_LINK_CHANGE_MASK_INFO (SM_OBJECT_CHANGE_MASK_LAST<<0) - struct pw_endpoint_link_info *info; -}; - -struct sm_media_session_events { -#define SM_VERSION_MEDIA_SESSION_EVENTS 0 - uint32_t version; - - void (*info) (void *data, const struct pw_core_info *info); - - void (*create) (void *data, struct sm_object *object); - void (*remove) (void *data, struct sm_object *object); - - void (*rescan) (void *data, int seq); - void (*shutdown) (void *data); - void (*destroy) (void *data); - - void (*seat_active) (void *data, bool active); - - void (*dbus_disconnected) (void *data); -}; - -struct sm_media_session { - struct sm_session *session; /** session object managed by this session */ - - struct pw_properties *props; - - uint32_t session_id; - struct pw_client_session *client_session; - - struct pw_loop *loop; /** the main loop */ - struct pw_context *context; - - struct spa_dbus_connection *dbus_connection; - struct pw_metadata *metadata; - - struct pw_core_info *info; -}; - -int sm_media_session_add_listener(struct sm_media_session *sess, struct spa_hook *listener, - const struct sm_media_session_events *events, void *data); - -int sm_media_session_roundtrip(struct sm_media_session *sess); - -int sm_media_session_sync(struct sm_media_session *sess, - void (*callback) (void *data), void *data); - -struct sm_object *sm_media_session_find_object(struct sm_media_session *sess, uint32_t id); -int sm_media_session_destroy_object(struct sm_media_session *sess, uint32_t id); - -int sm_media_session_for_each_object(struct sm_media_session *sess, - int (*callback) (void *data, struct sm_object *object), - void *data); - -int sm_media_session_schedule_rescan(struct sm_media_session *sess); - -struct pw_metadata *sm_media_session_export_metadata(struct sm_media_session *sess, - const char *name); -struct pw_proxy *sm_media_session_export(struct sm_media_session *sess, - const char *type, const struct spa_dict *props, - void *object, size_t user_data_size); - -struct sm_node *sm_media_session_export_node(struct sm_media_session *sess, - const struct spa_dict *props, struct pw_impl_node *node); -struct sm_device *sm_media_session_export_device(struct sm_media_session *sess, - const struct spa_dict *props, struct spa_device *device); - -struct pw_proxy *sm_media_session_create_object(struct sm_media_session *sess, - const char *factory_name, const char *type, uint32_t version, - const struct spa_dict *props, size_t user_data_size); - -struct sm_node *sm_media_session_create_node(struct sm_media_session *sess, - const char *factory_name, const struct spa_dict *props); - -int sm_media_session_create_links(struct sm_media_session *sess, - const struct spa_dict *dict); -int sm_media_session_remove_links(struct sm_media_session *sess, - const struct spa_dict *dict); - -int sm_media_session_load_conf(struct sm_media_session *sess, - const char *name, struct pw_properties *conf); - -int sm_media_session_load_state(struct sm_media_session *sess, - const char *name, struct pw_properties *props); -int sm_media_session_save_state(struct sm_media_session *sess, - const char *name, const struct pw_properties *props); - -int sm_media_session_match_rules(const char *rules, size_t size, - struct pw_properties *props); - -char *sm_media_session_sanitize_name(char *name, int size, char sub, - const char *fmt, ...) SPA_PRINTF_FUNC(4, 5); -char *sm_media_session_sanitize_description(char *name, int size, char sub, - const char *fmt, ...) SPA_PRINTF_FUNC(4, 5); - -int sm_media_session_seat_active_changed(struct sm_media_session *sess, bool active); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/media-session/meson.build b/src/media-session/meson.build deleted file mode 100644 index f5a1ac655..000000000 --- a/src/media-session/meson.build +++ /dev/null @@ -1,42 +0,0 @@ -media_session_sources = [] -if get_option('session-managers').contains('media-session') - sm_logind_src = [] - sm_logind_dep = [] - if systemd.found() and systemd_dep.found() - sm_logind_src = ['logind.c'] - sm_logind_dep = [systemd_dep] - endif - media_session_sources += [ - 'access-flatpak.c', - 'access-portal.c', - 'alsa-no-dsp.c', - 'alsa-midi.c', - 'alsa-monitor.c', - 'alsa-endpoint.c', - 'bluez-monitor.c', - 'bluez-endpoint.c', - 'bluez-autoswitch.c', - 'default-nodes.c', - 'default-profile.c', - 'default-routes.c', - 'media-session.c', - 'session-manager.c', - 'match-rules.c', - 'metadata.c', - 'stream-endpoint.c', - 'restore-stream.c', - 'policy-ep.c', - 'policy-node.c', - 'streams-follow-default.c', - 'v4l2-monitor.c', - 'v4l2-endpoint.c', - 'libcamera-monitor.c', - 'suspend-node.c', - ] + sm_logind_src - pipewire_media_session = executable('pipewire-media-session', - media_session_sources, - install: true, - dependencies : [dbus_dep, pipewire_dep, alsa_dep, mathlib, sm_logind_dep, libinotify_dep], - ) - subdir('media-session.d') -endif diff --git a/src/media-session/metadata.c b/src/media-session/metadata.c deleted file mode 100644 index ad3839d75..000000000 --- a/src/media-session/metadata.c +++ /dev/null @@ -1,109 +0,0 @@ -/* Metadata API - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "pipewire/pipewire.h" -#include "pipewire/array.h" - -#include - -#include - -#include "media-session.h" - -/** \page page_media_session_module_metadata Media Session Module: Metadata - */ - -#define NAME "metadata" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct metadata { - struct pw_impl_metadata *impl; - struct pw_metadata *metadata; - - struct sm_media_session *session; - struct spa_hook session_listener; - struct pw_proxy *proxy; -}; - -static void session_destroy(void *data) -{ - struct metadata *this = data; - - spa_hook_remove(&this->session_listener); - pw_proxy_destroy(this->proxy); - - pw_impl_metadata_destroy(this->impl); - free(this); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -struct pw_metadata *sm_media_session_export_metadata(struct sm_media_session *sess, - const char *name) -{ - struct metadata *this; - int res; - struct spa_dict_item items[1]; - - PW_LOG_TOPIC_INIT(mod_topic); - - this = calloc(1, sizeof(*this)); - if (this == NULL) - goto error_errno; - - this->impl = pw_context_create_metadata(sess->context, - name, NULL, 0); - if (this->impl == NULL) - goto error_errno; - - this->metadata = pw_impl_metadata_get_implementation(this->impl); - - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_METADATA_NAME, name); - - this->session = sess; - this->proxy = sm_media_session_export(sess, - PW_TYPE_INTERFACE_Metadata, - &SPA_DICT_INIT_ARRAY(items), - this->metadata, 0); - if (this->proxy == NULL) - goto error_errno; - - sm_media_session_add_listener(sess, &this->session_listener, - &session_events, this); - - return this->metadata; - -error_errno: - res = -errno; - goto error_free; -error_free: - free(this); - errno = -res; - return NULL; -} diff --git a/src/media-session/policy-ep.c b/src/media-session/policy-ep.c deleted file mode 100644 index 01e442b5e..000000000 --- a/src/media-session/policy-ep.c +++ /dev/null @@ -1,534 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/session-manager.h" - -#include "media-session.h" - -/** \page page_media_session_module_policy_endpoint Media Session Module: Policy Endpoint - */ - -#define NAME "policy-ep" -#define SESSION_KEY "policy-endpoint" - -#define DEFAULT_CHANNELS 2 -#define DEFAULT_SAMPLERATE 48000 - -#define DEFAULT_IDLE_SECONDS 3 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - - struct spa_list endpoint_list; - int seq; -}; - -struct endpoint { - struct sm_endpoint *obj; - - uint32_t id; - struct impl *impl; - - struct spa_list link; /**< link in impl endpoint_list */ - enum pw_direction direction; - - uint32_t linked; - - uint32_t client_id; - int32_t priority; - -#define ENDPOINT_TYPE_UNKNOWN 0 -#define ENDPOINT_TYPE_STREAM 1 -#define ENDPOINT_TYPE_DEVICE 2 - uint32_t type; - char *media; - - uint32_t media_type; - uint32_t media_subtype; - struct spa_audio_info_raw format; - - uint64_t plugged; - unsigned int exclusive:1; - unsigned int enabled:1; - unsigned int busy:1; -}; - -struct stream { - struct sm_endpoint_stream *obj; - - uint32_t id; - struct impl *impl; - - struct endpoint *endpoint; -}; - -static int -handle_endpoint(struct impl *impl, struct sm_object *object) -{ - const char *media_class; - enum pw_direction direction; - struct endpoint *ep; - uint32_t client_id = SPA_ID_INVALID; - - if (object->props) { - pw_properties_fetch_uint32(object->props, PW_KEY_CLIENT_ID, &client_id); - } - - media_class = object->props ? pw_properties_get(object->props, PW_KEY_MEDIA_CLASS) : NULL; - - pw_log_debug("%p: endpoint "PW_KEY_MEDIA_CLASS" %s", impl, media_class); - - if (media_class == NULL) - return 0; - - ep = sm_object_add_data(object, SESSION_KEY, sizeof(struct endpoint)); - ep->obj = (struct sm_endpoint*)object; - ep->id = object->id; - ep->impl = impl; - ep->client_id = client_id; - ep->type = ENDPOINT_TYPE_UNKNOWN; - ep->enabled = true; - spa_list_append(&impl->endpoint_list, &ep->link); - - if (spa_strstartswith(media_class, "Stream/")) { - media_class += strlen("Stream/"); - - if (spa_strstartswith(media_class, "Output/")) { - direction = PW_DIRECTION_OUTPUT; - media_class += strlen("Output/"); - } - else if (spa_strstartswith(media_class, "Input/")) { - direction = PW_DIRECTION_INPUT; - media_class += strlen("Input/"); - } - else - return 0; - - ep->direction = direction; - ep->type = ENDPOINT_TYPE_STREAM; - ep->media = strdup(media_class); - pw_log_debug("%p: endpoint %d is stream %s", impl, object->id, ep->media); - } - else { - const char *media; - if (spa_strstartswith(media_class, "Audio/")) { - media_class += strlen("Audio/"); - media = "Audio"; - } - else if (spa_strstartswith(media_class, "Video/")) { - media_class += strlen("Video/"); - media = "Video"; - } - else - return 0; - - if (spa_streq(media_class, "Sink")) - direction = PW_DIRECTION_INPUT; - else if (spa_streq(media_class, "Source")) - direction = PW_DIRECTION_OUTPUT; - else - return 0; - - ep->direction = direction; - ep->type = ENDPOINT_TYPE_DEVICE; - ep->media = strdup(media); - - pw_log_debug("%p: endpoint %d '%s' prio:%d", impl, - object->id, ep->media, ep->priority); - } - return 1; -} - -static void destroy_endpoint(struct impl *impl, struct endpoint *ep) -{ - spa_list_remove(&ep->link); - free(ep->media); - sm_object_remove_data((struct sm_object*)ep->obj, SESSION_KEY); -} - -static int -handle_stream(struct impl *impl, struct sm_object *object) -{ - struct sm_endpoint_stream *stream = (struct sm_endpoint_stream*)object; - struct stream *s; - struct endpoint *ep; - - if (stream->endpoint == NULL) - return 0; - - ep = sm_object_get_data(&stream->endpoint->obj, SESSION_KEY); - if (ep == NULL) - return 0; - - s = sm_object_add_data(object, SESSION_KEY, sizeof(struct stream)); - s->obj = (struct sm_endpoint_stream*)object; - s->id = object->id; - s->impl = impl; - s->endpoint = ep; - - return 0; -} - -static void destroy_stream(struct impl *impl, struct stream *s) -{ - sm_object_remove_data((struct sm_object*)s->obj, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Endpoint)) - res = handle_endpoint(impl, object); - else if (spa_streq(object->type, PW_TYPE_INTERFACE_EndpointStream)) - res = handle_stream(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d", impl, object->id); - } - else - sm_media_session_schedule_rescan(impl->session); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - pw_log_debug("%p: remove global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Endpoint)) { - struct endpoint *ep; - if ((ep = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_endpoint(impl, ep); - } - else if (spa_streq(object->type, PW_TYPE_INTERFACE_EndpointStream)) { - struct stream *s; - if ((s = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_stream(impl, s); - } - - sm_media_session_schedule_rescan(impl->session); -} - -struct find_data { - struct impl *impl; - struct endpoint *ep; - struct endpoint *endpoint; - bool exclusive; - int priority; - uint64_t plugged; -}; - -static int find_endpoint(void *data, struct endpoint *endpoint) -{ - struct find_data *find = data; - struct impl *impl = find->impl; - int priority = 0; - uint64_t plugged = 0; - - pw_log_debug("%p: looking at endpoint '%d' enabled:%d busy:%d exclusive:%d", - impl, endpoint->id, endpoint->enabled, endpoint->busy, endpoint->exclusive); - - if (!endpoint->enabled) - return 0; - - if (endpoint->direction == find->ep->direction) { - pw_log_debug(".. same direction"); - return 0; - } - if (!spa_streq(endpoint->media, find->ep->media)) { - pw_log_debug(".. incompatible media %s <-> %s", endpoint->media, find->ep->media); - return 0; - } - - plugged = endpoint->plugged; - priority = endpoint->priority; - - if ((find->exclusive && endpoint->busy) || endpoint->exclusive) { - pw_log_debug("%p: endpoint '%d' in use", impl, endpoint->id); - return 0; - } - - pw_log_debug("%p: found endpoint '%d' %"PRIu64" prio:%d", impl, - endpoint->id, plugged, priority); - - if (find->endpoint == NULL || - priority > find->priority || - (priority == find->priority && plugged > find->plugged)) { - pw_log_debug("%p: new best %d %" PRIu64, impl, priority, plugged); - find->endpoint = endpoint; - find->priority = priority; - find->plugged = plugged; - } - return 0; -} - -static int link_endpoints(struct endpoint *endpoint, struct endpoint *peer) -{ - struct impl *impl = endpoint->impl; - struct pw_properties *props; - - pw_log_debug("%p: link endpoints %d %d", impl, endpoint->id, peer->id); - - if (endpoint->direction == PW_DIRECTION_INPUT) { - struct endpoint *t = endpoint; - endpoint = peer; - peer = t; - } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT, "%d", endpoint->id); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM, "%d", -1); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT, "%d", peer->id); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_INPUT_STREAM, "%d", -1); - pw_log_debug("%p: endpoint %d -> endpoint %d", impl, - endpoint->id, peer->id); - - pw_endpoint_create_link((struct pw_endpoint*)endpoint->obj->obj.proxy, - &props->dict); - - pw_properties_free(props); - - endpoint->linked++; - peer->linked++; - - return 0; -} - -static int link_node(struct endpoint *endpoint, struct sm_node *peer) -{ - struct impl *impl = endpoint->impl; - struct pw_properties *props; - - pw_log_debug("%p: link endpoint %d to node %d", impl, endpoint->id, peer->obj.id); - - props = pw_properties_new(NULL, NULL); - - if (endpoint->direction == PW_DIRECTION_INPUT) { - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", peer->obj.id); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT, "%d", endpoint->id); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_INPUT_STREAM, "%d", -1); - pw_log_debug("%p: node %d -> endpoint %d", impl, - peer->obj.id, endpoint->id); - } else { - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT, "%d", endpoint->id); - pw_properties_setf(props, PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM, "%d", -1); - pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", peer->obj.id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1); - pw_log_debug("%p: endpoint %d -> node %d", impl, - endpoint->id, peer->obj.id); - } - - pw_endpoint_create_link((struct pw_endpoint*)endpoint->obj->obj.proxy, - &props->dict); - - pw_properties_free(props); - - endpoint->linked++; - - return 0; -} - -static int rescan_endpoint(struct impl *impl, struct endpoint *ep) -{ - struct spa_dict *props; - const char *str; - bool exclusive; - struct find_data find; - struct pw_endpoint_info *info; - struct endpoint *peer; - struct sm_object *obj; - struct sm_node *node; - - if (ep->type == ENDPOINT_TYPE_DEVICE) - return 0; - - if (ep->obj->info == NULL || ep->obj->info->props == NULL) { - pw_log_debug("%p: endpoint %d has no properties", impl, ep->id); - return 0; - } - - if (ep->linked > 0) { - pw_log_debug("%p: endpoint %d is already linked", impl, ep->id); - return 0; - } - - info = ep->obj->info; - props = info->props; - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_AUTOCONNECT); - if (str == NULL || !pw_properties_parse_bool(str)) { - pw_log_debug("%p: endpoint %d does not need autoconnect", impl, ep->id); - return 0; - } - - if (ep->media == NULL) { - pw_log_debug("%p: endpoint %d has unknown media", impl, ep->id); - return 0; - } - - spa_zero(find); - - if ((str = spa_dict_lookup(props, PW_KEY_NODE_EXCLUSIVE)) != NULL) - exclusive = pw_properties_parse_bool(str); - else - exclusive = false; - - find.impl = impl; - find.ep = ep; - find.exclusive = exclusive; - - pw_log_debug("%p: exclusive:%d", impl, exclusive); - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_TARGET); - if (str == NULL) - str = spa_dict_lookup(props, PW_KEY_NODE_TARGET); - if (str != NULL) { - uint32_t path_id = atoi(str); - pw_log_debug("%p: target:%d", impl, path_id); - - if ((obj = sm_media_session_find_object(impl->session, path_id)) != NULL) { - if (spa_streq(obj->type, PW_TYPE_INTERFACE_Endpoint)) { - if ((peer = sm_object_get_data(obj, SESSION_KEY)) != NULL) - goto do_link; - } - else if (spa_streq(obj->type, PW_TYPE_INTERFACE_Node)) { - node = (struct sm_node*)obj; - goto do_link_node; - } - } - } - - spa_list_for_each(peer, &impl->endpoint_list, link) - find_endpoint(&find, peer); - - if (find.endpoint == NULL) { - struct sm_object *obj; - - pw_log_warn("%p: no endpoint found for %d", impl, ep->id); - - str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT); - if (str != NULL && pw_properties_parse_bool(str)) { -// pw_registry_destroy(impl->registry, ep->id); - } - - obj = sm_media_session_find_object(impl->session, ep->client_id); - if (obj && spa_streq(obj->type, PW_TYPE_INTERFACE_Client)) { - pw_client_error((struct pw_client*)obj->proxy, - ep->id, -ENOENT, "no endpoint available"); - } - return -ENOENT; - } - peer = find.endpoint; - - if (exclusive && peer->busy) { - pw_log_warn("%p: endpoint %d busy, can't get exclusive access", impl, peer->id); - return -EBUSY; - } - peer->exclusive = exclusive; - - pw_log_debug("%p: linking to endpoint '%d'", impl, peer->id); - - peer->busy = true; - -do_link: - link_endpoints(ep, peer); - return 1; -do_link_node: - link_node(ep, node); - return 1; -} - -static void session_rescan(void *data, int seq) -{ - struct impl *impl = data; - struct endpoint *ep; - - clock_gettime(CLOCK_MONOTONIC, &impl->now); - pw_log_debug("%p: rescan", impl); - - spa_list_for_each(ep, &impl->endpoint_list, link) - rescan_endpoint(impl, ep); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .rescan = session_rescan, - .destroy = session_destroy, -}; - -int sm_policy_ep_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - spa_list_init(&impl->endpoint_list); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - return 0; -} diff --git a/src/media-session/policy-node.c b/src/media-session/policy-node.c deleted file mode 100644 index 10026e27e..000000000 --- a/src/media-session/policy-node.c +++ /dev/null @@ -1,1443 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_policy_node Media Session Module: Policy Node - */ - -#define NAME "policy-node" -#define SESSION_KEY "policy-node" - -#define DEFAULT_IDLE_SECONDS 3 - -#define DEFAULT_AUDIO_SINK_KEY "default.audio.sink" -#define DEFAULT_AUDIO_SOURCE_KEY "default.audio.source" -#define DEFAULT_VIDEO_SOURCE_KEY "default.video.source" -#define DEFAULT_CONFIG_AUDIO_SINK_KEY "default.configured.audio.sink" -#define DEFAULT_CONFIG_AUDIO_SOURCE_KEY "default.configured.audio.source" -#define DEFAULT_CONFIG_VIDEO_SOURCE_KEY "default.configured.video.source" - -#define DEFAULT_AUDIO_SINK 0 -#define DEFAULT_AUDIO_SOURCE 1 -#define DEFAULT_VIDEO_SOURCE 2 - -#define MAX_LINK_RETRY 5 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct default_node { - char *key; - char *key_config; - char *value; - char *config; -}; - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct spa_hook meta_listener; - - struct pw_context *context; - - uint32_t sample_rate; - - struct spa_list node_list; - unsigned int node_list_changed:1; - unsigned int linking_node_removed:1; - int seq; - - struct default_node defaults[4]; - - bool streams_follow_default; - bool alsa_no_dsp; -}; - -struct node { - struct sm_node *obj; - - uint32_t id; - struct impl *impl; - - struct spa_list link; /**< link in impl node_list */ - enum pw_direction direction; - - struct spa_hook listener; - - struct node *peer; - struct node *failed_peer; - - uint32_t client_id; - int32_t priority; - -#define NODE_TYPE_UNKNOWN 0 -#define NODE_TYPE_STREAM 1 -#define NODE_TYPE_DEVICE 2 - uint32_t type; - char *media; - - struct spa_audio_info format; - - int connect_count; - int failed_count; - uint64_t plugged; - unsigned int active:1; - unsigned int exclusive:1; - unsigned int enabled:1; - unsigned int configured:1; - unsigned int dont_remix:1; - unsigned int monitor:1; - unsigned int capture_sink:1; - unsigned int virtual:1; - unsigned int linking:1; - unsigned int have_passthrough:1; - unsigned int passthrough_only:1; - unsigned int passthrough:1; - unsigned int want_passthrough:1; - unsigned int unpositioned:1; -}; - -static bool is_unpositioned(struct spa_audio_info *info) -{ - uint32_t i; - if (SPA_FLAG_IS_SET(info->info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) - return true; - for (i = 0; i < info->info.raw.channels; i++) - if (info->info.raw.position[i] >= SPA_AUDIO_CHANNEL_START_Aux && - info->info.raw.position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux) - return true; - return false; -} - -static bool find_format(struct node *node) -{ - struct impl *impl = node->impl; - struct sm_param *p; - bool have_format = false; - - node->have_passthrough = false; - node->passthrough_only = false; - - spa_list_for_each(p, &node->obj->param_list, link) { - struct spa_audio_info info = { 0, }; - struct spa_pod *position = NULL; - uint32_t n_position = 0; - - if (p->id != SPA_PARAM_EnumFormat) - continue; - - if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) - continue; - - if (info.media_type != SPA_MEDIA_TYPE_audio) - continue; - - switch (info.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - spa_pod_object_fixate((struct spa_pod_object*)p->param); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - /* defaults */ - info.info.raw.format = SPA_AUDIO_FORMAT_F32; - info.info.raw.rate = impl->sample_rate; - info.info.raw.channels = 2; - info.info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; - info.info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; - - spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_Id(&info.info.raw.format), - SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info.info.raw.rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&info.info.raw.channels), - SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); - - if (position != NULL) - n_position = spa_pod_copy_array(position, SPA_TYPE_Id, - info.info.raw.position, SPA_AUDIO_MAX_CHANNELS); - if (n_position == 0 || n_position != info.info.raw.channels) - SPA_FLAG_SET(info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED); - - if (node->format.info.raw.channels < info.info.raw.channels) { - node->format = info; - if (is_unpositioned(&info)) - node->unpositioned = true; - } - have_format = true; - break; - - case SPA_MEDIA_SUBTYPE_iec958: - case SPA_MEDIA_SUBTYPE_dsd: - pw_log_info("passthrough node %d found", node->id); - node->have_passthrough = true; - break; - } - } - if (!have_format && node->have_passthrough) { - pw_log_info("passthrough only node %d found", node->id); - node->passthrough_only = true; - have_format = true; - } - return have_format; -} - -static bool check_passthrough(struct node *node, struct node *peer) -{ - struct sm_param *p1, *p2; - char buffer[1024]; - struct spa_pod_builder b; - struct spa_pod *res; - - if (peer->obj->info == NULL) - return false; - - if (peer->obj->info->state == PW_NODE_STATE_RUNNING) - return false; - - if (!node->have_passthrough || !peer->have_passthrough) - return false; - - spa_list_for_each(p1, &node->obj->param_list, link) { - if (p1->id != SPA_PARAM_EnumFormat) - continue; - - spa_list_for_each(p2, &peer->obj->param_list, link) { - if (p2->id != SPA_PARAM_EnumFormat) - continue; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &res, p1->param, p2->param) >= 0) - return true; - } - } - return false; -} - -static void ensure_suspended(struct node *node) -{ - struct spa_command *cmd = &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); - - if (node->obj->info->state < PW_NODE_STATE_IDLE) - return; - - pw_node_send_command((struct pw_node*)node->obj->obj.proxy, cmd); -} - -static int configure_passthrough(struct node *node) -{ - char buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - - pw_log_info("node %d passthrough", node->id); - - ensure_suspended(node); - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(node->direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_passthrough), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false)); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)node->obj->obj.proxy, - SPA_PARAM_PortConfig, 0, param); - - node->configured = true; - node->passthrough = true; - - return 0; -} - -static int configure_node(struct node *node, struct spa_audio_info *info, bool force) -{ - struct impl *impl = node->impl; - char buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - struct spa_audio_info format; - enum pw_direction direction; - uint32_t mode; - - if (node->configured && !force) { - pw_log_debug("node %d is configured passthrough:%d", node->id, node->passthrough); - return 0; - } - - if (!spa_streq(node->media, "Audio")) - return 0; - - ensure_suspended(node); - - format = node->format; - - if (impl->alsa_no_dsp) { - if ((info != NULL && memcmp(&node->format, info, sizeof(node->format)) == 0) || - node->type == NODE_TYPE_DEVICE) - mode = SPA_PARAM_PORT_CONFIG_MODE_passthrough; - else - mode = SPA_PARAM_PORT_CONFIG_MODE_convert; - } else { - mode = SPA_PARAM_PORT_CONFIG_MODE_dsp; - } - - if (mode != SPA_PARAM_PORT_CONFIG_MODE_passthrough && - info != NULL && info->info.raw.channels > 0) { - pw_log_info("node %d monitor:%d channelmix %d->%d", - node->id, node->monitor, format.info.raw.channels, - info->info.raw.channels); - format = *info; - } else { - pw_log_info("node %d monitor:%d channelmix %d", - node->id, node->monitor, format.info.raw.channels); - } - format.info.raw.rate = impl->sample_rate; - - if (node->virtual) - direction = pw_direction_reverse(node->direction); - else - direction = node->direction; - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &format.info.raw); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(true), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)node->obj->obj.proxy, - SPA_PARAM_PortConfig, 0, param); - - node->configured = true; - node->passthrough = false; - - if (node->type == NODE_TYPE_DEVICE) { - /* Schedule rescan in case we need to move streams */ - sm_media_session_schedule_rescan(impl->session); - } - - return 0; -} - -static void object_update(void *data) -{ - struct node *node = data; - struct impl *impl = node->impl; - const char *str; - - pw_log_debug("%p: node %d %08x", impl, node->id, node->obj->obj.changed); - - if (node->obj->obj.avail & SM_NODE_CHANGE_MASK_INFO && - node->obj->info != NULL && node->obj->info->props != NULL) { - str = spa_dict_lookup(node->obj->info->props, PW_KEY_NODE_EXCLUSIVE); - node->exclusive = str ? pw_properties_parse_bool(str) : false; - } - - if (!node->active) { - if (node->obj->obj.avail & SM_NODE_CHANGE_MASK_PARAMS) { - if (!find_format(node)) { - pw_log_debug("%p: node %d can't find format", impl, node->id); - return; - } - node->active = true; - } - if (node->active) - sm_media_session_schedule_rescan(impl->session); - } -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static int -handle_node(struct impl *impl, struct sm_object *object) -{ - const char *media_class = NULL, *role; - enum pw_direction direction; - struct node *node; - uint32_t client_id = SPA_ID_INVALID; - - if (object->props) { - pw_properties_fetch_uint32(object->props, PW_KEY_CLIENT_ID, &client_id); - - media_class = pw_properties_get(object->props, PW_KEY_MEDIA_CLASS); - role = pw_properties_get(object->props, PW_KEY_MEDIA_ROLE); - } - - pw_log_debug("%p: node "PW_KEY_MEDIA_CLASS" %s", impl, media_class); - - if (media_class == NULL) - return 0; - - node = sm_object_add_data(object, SESSION_KEY, sizeof(struct node)); - node->obj = (struct sm_node*)object; - node->id = object->id; - node->impl = impl; - node->client_id = client_id; - node->type = NODE_TYPE_UNKNOWN; - spa_list_append(&impl->node_list, &node->link); - impl->node_list_changed = true; - - if (role && spa_streq(role, "DSP")) - node->active = node->configured = true; - - if (spa_strstartswith(media_class, "Stream/")) { - media_class += strlen("Stream/"); - - if (spa_strstartswith(media_class, "Output/")) { - direction = PW_DIRECTION_OUTPUT; - media_class += strlen("Output/"); - } - else if (spa_strstartswith(media_class, "Input/")) { - direction = PW_DIRECTION_INPUT; - media_class += strlen("Input/"); - } - else - return 0; - - if (spa_strstartswith(media_class, "Video")) { - if (direction == PW_DIRECTION_OUTPUT) { - node->plugged = pw_properties_get_uint64(object->props, PW_KEY_NODE_PLUGGED, - SPA_TIMESPEC_TO_NSEC(&impl->now)); - } - node->active = node->configured = true; - } - else if (spa_strstartswith(media_class, "Unknown")) { - node->active = node->configured = true; - } - - node->direction = direction; - node->type = NODE_TYPE_STREAM; - node->media = strdup(media_class); - pw_log_debug("%p: node %d is stream %s", impl, object->id, node->media); - } - else { - const char *media; - bool virtual = false; - - if (spa_strstartswith(media_class, "Audio/")) { - media_class += strlen("Audio/"); - media = "Audio"; - } - else if (spa_strstartswith(media_class, "Video/")) { - media_class += strlen("Video/"); - media = "Video"; - node->active = node->configured = true; - } - else - return 0; - - if (spa_streq(media_class, "Sink") || - spa_streq(media_class, "Duplex")) - direction = PW_DIRECTION_INPUT; - else if (spa_streq(media_class, "Source")) - direction = PW_DIRECTION_OUTPUT; - else if (spa_streq(media_class, "Source/Virtual")) { - virtual = true; - direction = PW_DIRECTION_OUTPUT; - } else - return 0; - - node->plugged = pw_properties_get_uint64(object->props, PW_KEY_NODE_PLUGGED, - SPA_TIMESPEC_TO_NSEC(&impl->now)); - node->priority = pw_properties_get_uint32(object->props, PW_KEY_PRIORITY_SESSION, 0); - - node->direction = direction; - node->virtual = virtual; - node->type = NODE_TYPE_DEVICE; - node->media = strdup(media); - - pw_log_debug("%p: node %d '%s' prio:%d", impl, - object->id, node->media, node->priority); - } - - node->enabled = true; - node->obj->obj.mask |= SM_NODE_CHANGE_MASK_PARAMS; - sm_object_add_listener(&node->obj->obj, &node->listener, &object_events, node); - - return 1; -} - -static void unpeer_node(struct node *node) -{ - struct impl *impl = node->impl; - pw_log_debug("unpeer id:%d exclusive:%d", node->id, node->exclusive); - if (node->passthrough) { - node->passthrough = false; - node->configured = false; - sm_media_session_schedule_rescan(impl->session); - } -} - -static void destroy_node(struct impl *impl, struct node *node) -{ - pw_log_debug("destroy %d %p", node->id, node->peer); - spa_list_remove(&node->link); - if (node->linking) - impl->linking_node_removed = true; - impl->node_list_changed = true; - if (node->enabled) - spa_hook_remove(&node->listener); - free(node->media); - if (node->peer) - unpeer_node(node->peer); - if (node->peer && node->peer->peer == node) - node->peer->peer = NULL; - sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY); -} - -static int json_object_find(const char *obj, const char *key, char *value, size_t len) -{ - struct spa_json it[2]; - const char *v; - char k[128]; - - spa_json_init(&it[0], obj, strlen(obj)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) { - if (spa_streq(k, key)) { - if (spa_json_get_string(&it[1], value, len) <= 0) - continue; - return 0; - } else { - if (spa_json_next(&it[1], &v) <= 0) - break; - } - } - return -ENOENT; -} - -static bool check_node_name(struct node *node, const char *name) -{ - const char *str; - if ((str = pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME)) != NULL && - name != NULL && spa_streq(str, name)) - return true; - return false; -} - -static struct node *find_node_by_id_name(struct impl *impl, uint32_t id, const char *name) -{ - struct node *node; - uint32_t name_id = name ? (uint32_t)atoi(name) : SPA_ID_INVALID; - - spa_list_for_each(node, &impl->node_list, link) { - if (node->id == id || node->id == name_id) - return node; - if (check_node_name(node, name)) - return node; - } - return NULL; -} - -static bool can_link_check(struct impl *impl, const char *link_group, struct node *target, int hops) -{ - const char *g, *ng; - struct node *n; - - if (hops == 8) - return false; - - pw_log_debug("link group %s", link_group); - - if (target->obj->info == NULL || target->obj->info->props == NULL) - return true; - g = spa_dict_lookup(target->obj->info->props, PW_KEY_NODE_LINK_GROUP); - if (g == NULL) - return true; - if (spa_streq(g, link_group)) - return false; - - spa_list_for_each(n, &impl->node_list, link) { - if (n == target || n->direction != target->direction) - continue; - if (n->obj->info == NULL || n->obj->info->props == NULL) - return true; - ng = spa_dict_lookup(n->obj->info->props, PW_KEY_NODE_LINK_GROUP); - if (ng == NULL || !spa_streq(ng, g)) - continue; - if (n->peer != NULL && !can_link_check(impl, link_group, n->peer, hops + 1)) - return false; - } - return true; -} - -static bool can_link(struct impl *impl, struct node *node, struct node *target) -{ - const char *link_group; - - link_group = spa_dict_lookup(node->obj->info->props, PW_KEY_NODE_LINK_GROUP); - if (link_group == NULL) - return true; - - return can_link_check(impl, link_group, target, 0); -} - -static const char *get_device_name(struct node *node) -{ - if (node->type != NODE_TYPE_DEVICE || - node->obj->obj.props == NULL) - return NULL; - return pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME); -} - -static uint32_t find_device_for_name(struct impl *impl, const char *name) -{ - struct node *node; - const char *str; - uint32_t id = atoi(name); - - spa_list_for_each(node, &impl->node_list, link) { - if (id == node->obj->obj.id) - return id; - if ((str = get_device_name(node)) == NULL) - continue; - if (spa_streq(str, name)) - return node->obj->obj.id; - } - return SPA_ID_INVALID; -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - clock_gettime(CLOCK_MONOTONIC, &impl->now); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) - res = handle_node(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d", impl, object->id); - } else - sm_media_session_schedule_rescan(impl->session); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - pw_log_debug("%p: remove global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) { - struct node *n, *node; - - if ((node = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_node(impl, node); - - spa_list_for_each(n, &impl->node_list, link) { - if (n->peer == node) - n->peer = NULL; - if (n->failed_peer == node) - n->failed_peer = NULL; - } - } - sm_media_session_schedule_rescan(impl->session); -} - -static bool array_contains(const struct spa_pod *pod, uint32_t val) -{ - uint32_t *vals, n_vals; - uint32_t n; - - if (pod == NULL) - return false; - vals = spa_pod_get_array(pod, &n_vals); - if (vals == NULL || n_vals == 0) - return false; - for (n = 0; n < n_vals; n++) - if (vals[n] == val) - return true; - return false; -} - -static bool have_available_route(struct node *node, struct sm_device *dev) -{ - struct sm_param *p; - const char *str; - uint32_t card_profile_device; - int found = 0, avail = 0; - - if (node->obj->info == NULL || node->obj->info->props == NULL || - (str = spa_dict_lookup(node->obj->info->props, "card.profile.device")) == NULL) - return 1; - - if (!spa_atou32(str, &card_profile_device, 0)) - return 1; - - spa_list_for_each(p, &dev->param_list, link) { - uint32_t device_id; - enum spa_param_availability available; - - if (p->id != SPA_PARAM_Route) - continue; - - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamRoute, NULL, - SPA_PARAM_ROUTE_device, SPA_POD_Int(&device_id), - SPA_PARAM_ROUTE_available, SPA_POD_Id(&available)) < 0) - continue; - - /* we found the route for the device */ - if (device_id != card_profile_device) - continue; - if (available == SPA_PARAM_AVAILABILITY_no) - return 0; - return 1; - } - /* Route is not found so no active profile. Check if there is a route that - * is available */ - spa_list_for_each(p, &dev->param_list, link) { - struct spa_pod *devices = NULL; - enum spa_param_availability available; - - if (p->id != SPA_PARAM_EnumRoute) - continue; - - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_ParamRoute, NULL, - SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Pod(&devices), - SPA_PARAM_ROUTE_available, SPA_POD_Id(&available)) < 0) - continue; - - if (!array_contains(devices, card_profile_device)) - continue; - found++; - if (available != SPA_PARAM_AVAILABILITY_no) - avail++; - } - if (found == 0) - return 1; - if (avail > 0) - return 1; - return 0; -} - -struct find_data { - struct impl *impl; - struct node *result; - struct node *node; - - const char *media; - const char *link_group; - bool capture_sink; - enum pw_direction direction; - - bool exclusive; - bool have_passthrough; - bool passthrough_only; - bool can_passthrough; - int priority; - uint64_t plugged; -}; - -static int find_node(void *data, struct node *node) -{ - struct find_data *find = data; - struct impl *impl = find->impl; - int priority = 0; - uint64_t plugged = 0; - struct sm_device *device = node->obj->device; - bool is_default = false, can_passthrough = false; - - if (node->obj->info == NULL) { - pw_log_debug("%p: skipping node '%d' with no node info", impl, node->id); - return 0; - } - - pw_log_debug("%p: looking at node '%d' enabled:%d state:%d peer:%p exclusive:%d", - impl, node->id, node->enabled, node->obj->info->state, node->peer, node->exclusive); - - if (!node->enabled || node->type == NODE_TYPE_UNKNOWN) - return 0; - - if (device && device->locked) { - pw_log_debug(".. device locked"); - return 0; - } - - if (node->media && !spa_streq(node->media, find->media)) { - pw_log_debug(".. incompatible media %s <-> %s", node->media, find->media); - return 0; - } - if (find->link_group && !can_link_check(impl, find->link_group, node, 0)) { - pw_log_debug(".. connecting link-group %s", find->link_group); - return 0; - } - - plugged = node->plugged; - priority = node->priority; - - if (node->media) { - if (spa_streq(node->media, "Audio")) { - if (node->direction == PW_DIRECTION_INPUT) { - if (find->direction == PW_DIRECTION_OUTPUT) - is_default |= check_node_name(node, - impl->defaults[DEFAULT_AUDIO_SINK].config); - else if (find->direction == PW_DIRECTION_INPUT) - is_default |= check_node_name(node, - impl->defaults[DEFAULT_AUDIO_SOURCE].config); - } else if (node->direction == PW_DIRECTION_OUTPUT && - find->direction == PW_DIRECTION_INPUT) - is_default |= check_node_name(node, - impl->defaults[DEFAULT_AUDIO_SOURCE].config); - } else if (spa_streq(node->media, "Video")) { - if (node->direction == PW_DIRECTION_OUTPUT && - find->direction == PW_DIRECTION_INPUT) - is_default |= check_node_name(node, - impl->defaults[DEFAULT_VIDEO_SOURCE].config); - } - if (is_default) - priority += 10000; - } - if (device != NULL && !is_default && !have_available_route(node, device)) { - pw_log_debug(".. no available routes"); - return 0; - } - - if ((find->capture_sink && node->direction != PW_DIRECTION_INPUT) || - (!find->capture_sink && !is_default && node->direction == find->direction)) { - pw_log_debug(".. same direction"); - return 0; - } - if ((find->exclusive && node->obj->info->state == PW_NODE_STATE_RUNNING) || - (node->peer && node->peer->exclusive)) { - pw_log_debug("%p: node '%d' in use", impl, node->id); - return 0; - } - if (find->node && find->have_passthrough && node->have_passthrough) - can_passthrough = check_passthrough(find->node, node); - - if ((find->passthrough_only || node->passthrough_only) && - !can_passthrough) { - pw_log_debug("%p: node '%d' passthrough required", impl, node->id); - return 0; - } - - pw_log_debug("%p: found node '%d' %"PRIu64" prio:%d", impl, - node->id, plugged, priority); - - if (find->result == NULL || - priority > find->priority || - (priority == find->priority && plugged > find->plugged)) { - pw_log_debug("%p: new best %d %" PRIu64, impl, priority, plugged); - find->result = node; - find->priority = priority; - find->plugged = plugged; - find->can_passthrough = can_passthrough; - } - return 0; -} - -static struct node *find_auto_default_node(struct impl *impl, const struct default_node *def) -{ - struct node *node; - struct find_data find; - - spa_zero(find); - find.impl = impl; - find.capture_sink = false; - find.exclusive = false; - - if (spa_streq(def->key, DEFAULT_AUDIO_SINK_KEY)) { - find.media = "Audio"; - find.direction = PW_DIRECTION_OUTPUT; - } else if (spa_streq(def->key, DEFAULT_AUDIO_SOURCE_KEY)) { - find.media = "Audio"; - find.direction = PW_DIRECTION_INPUT; - } else if (spa_streq(def->key, DEFAULT_VIDEO_SOURCE_KEY)) { - find.media = "Video"; - find.direction = PW_DIRECTION_INPUT; - } else { - return NULL; - } - - spa_list_for_each(node, &impl->node_list, link) - find_node(&find, node); - - return find.result; -} - -static int link_nodes(struct node *node, struct node *peer) -{ - struct impl *impl = node->impl; - struct pw_properties *props; - struct node *output, *input; - int res; - uint32_t node_id = node->id; - - pw_log_debug("%p: link nodes %d %d remix:%d", impl, - node->id, peer->id, !node->dont_remix); - - if (node->want_passthrough) { - configure_passthrough(node); - configure_passthrough(peer); - } else { - if (node->dont_remix || peer->unpositioned) - configure_node(node, NULL, peer->unpositioned); - else - configure_node(node, &peer->format, true); - } - - if (node->direction == PW_DIRECTION_INPUT) { - output = peer; - input = node; - } else { - output = node; - input = peer; - } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", output->id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", input->id); - pw_log_info("linking node %d to node %d", output->id, input->id); - - node->linking = true; - res = sm_media_session_create_links(impl->session, &props->dict); - pw_properties_free(props); - - if (impl->linking_node_removed) { - impl->linking_node_removed = false; - pw_log_info("linking node %d was removed", node_id); - return -ENOENT; - } - node->linking = false; - - pw_log_info("linked %d to %p", res, peer); - if (res > 0) { - node->peer = peer; - node->connect_count++; - } - return res; -} - -static int unlink_nodes(struct node *node, struct node *peer) -{ - struct impl *impl = node->impl; - struct pw_properties *props; - - pw_log_debug("%p: unlink nodes %d %d", impl, node->id, peer->id); - - if (peer->peer == node) - peer->peer = NULL; - node->peer = NULL; - - if (node->direction == PW_DIRECTION_INPUT) { - struct node *t = node; - node = peer; - peer = t; - } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", node->id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", peer->id); - pw_log_info("unlinking node %d from peer node %d", node->id, peer->id); - - sm_media_session_remove_links(impl->session, &props->dict); - - pw_properties_free(props); - - return 0; -} - -static int relink_node(struct impl *impl, struct node *n, struct node *peer) -{ - int res; - - if (peer == n->failed_peer && n->failed_count > MAX_LINK_RETRY) { - /* Break rescan -> failed link -> rescan loop. */ - pw_log_debug("%p: tried to link '%d' on last rescan, not retrying", - impl, peer->id); - return -EBUSY; - } - - if (n->failed_peer != peer) - n->failed_count = 0; - n->failed_peer = peer; - n->failed_count++; - - if (!can_link(impl, n, peer)) { - pw_log_debug("can't link node %d to %d: same link-group", n->id, - peer->id); - return -EPERM; - } - - if (n->peer != NULL) - if ((res = unlink_nodes(n, n->peer)) < 0) - return res; - - pw_log_debug("%p: linking node %d to node %d", impl, n->id, peer->id); - - /* NB. if link_nodes returns error, n may have been invalidated */ - if ((res = link_nodes(n, peer)) > 0) { - n->failed_peer = NULL; - n->failed_count = 0; - } - return res; -} - -static int rescan_node(struct impl *impl, struct node *n) -{ - struct spa_dict *props; - const char *str; - bool reconnect, autoconnect, can_passthrough = false; - struct pw_node_info *info; - struct node *peer; - struct sm_object *obj; - uint32_t path_id; - - if (!n->active) { - pw_log_debug("%p: node %d is not active", impl, n->id); - return 0; - } - - if (n->type == NODE_TYPE_DEVICE) { - configure_node(n, NULL, false); - return 0; - } - - if (n->obj->info == NULL || n->obj->info->props == NULL) { - pw_log_debug("%p: node %d has no properties", impl, n->id); - return 0; - } - - info = n->obj->info; - props = info->props; - - str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT); - reconnect = str ? !pw_properties_parse_bool(str) : true; - - if ((str = spa_dict_lookup(props, PW_KEY_STREAM_DONT_REMIX)) != NULL) - n->dont_remix = pw_properties_parse_bool(str); - - if ((str = spa_dict_lookup(props, PW_KEY_STREAM_MONITOR)) != NULL) - n->monitor = pw_properties_parse_bool(str); - - if (n->direction == PW_DIRECTION_INPUT && - (str = spa_dict_lookup(props, PW_KEY_STREAM_CAPTURE_SINK)) != NULL) - n->capture_sink = pw_properties_parse_bool(str); - - autoconnect = false; - if ((str = spa_dict_lookup(props, PW_KEY_NODE_AUTOCONNECT)) != NULL) - autoconnect = pw_properties_parse_bool(str); - - if ((str = spa_dict_lookup(props, PW_KEY_DEVICE_API)) != NULL && - spa_streq(str, "bluez5")) - autoconnect = true; - - if (!autoconnect) { - pw_log_debug("%p: node %d does not need autoconnect", impl, n->id); - configure_node(n, NULL, false); - return 0; - } - - if (n->media == NULL) { - pw_log_debug("%p: node %d has unknown media", impl, n->id); - return 0; - } - - pw_log_debug("%p: exclusive:%d", impl, n->exclusive); - - /* honor target node set by user or asked for by the client */ - path_id = SPA_ID_INVALID; - if (n->obj->target_node != NULL) - path_id = find_device_for_name(impl, n->obj->target_node); - if (!n->obj->fixed_target && - (str = spa_dict_lookup(props, PW_KEY_NODE_TARGET)) != NULL) { - bool has_target = ((uint32_t)atoi(str) != SPA_ID_INVALID); - path_id = find_device_for_name(impl, str); - if (!reconnect && has_target && path_id == SPA_ID_INVALID) { - /* don't use fallbacks for non-reconnecting nodes */ - peer = NULL; - goto do_link; - } - } - - if (n->peer != NULL) { - /* Do we need to check again where to link to? */ - bool target_found = (path_id != SPA_ID_INVALID); - bool peer_is_target = (target_found && n->peer->obj->obj.id == path_id); - bool follows_default = (impl->streams_follow_default && - n->type == NODE_TYPE_STREAM); - bool recheck = !peer_is_target && (follows_default || target_found) && - reconnect && !n->passthrough; - if (!recheck) { - pw_log_debug("%p: node %d is already linked, peer-is-target:%d " - "follows-default:%d", impl, n->id, peer_is_target, - follows_default); - return 0; - } - } - - pw_log_info("trying to link node %d exclusive:%d reconnect:%d target:%d, peer %p", - n->id, n->exclusive, reconnect, path_id, n->peer); - - if (path_id != SPA_ID_INVALID) { - pw_log_debug("%p: target:%d", impl, path_id); - - if (!reconnect) - n->obj->target_node = NULL; - - if ((obj = sm_media_session_find_object(impl->session, path_id)) == NULL) { - pw_log_warn("node %d target:%d not found, find fallback:%d", n->id, - path_id, reconnect); - path_id = SPA_ID_INVALID; - goto fallback; - } - path_id = SPA_ID_INVALID; - - if (!spa_streq(obj->type, PW_TYPE_INTERFACE_Node)) - goto fallback; - - peer = sm_object_get_data(obj, SESSION_KEY); - pw_log_debug("%p: found target:%d type:%s %d:%d", impl, - peer->id, obj->type, n->passthrough_only, peer->have_passthrough); - - can_passthrough = check_passthrough(n, peer); - if (n->passthrough_only && !can_passthrough) { - pw_log_info("%p: peer has no passthrough", impl); - goto fallback; - } - - goto do_link; - } -fallback: - if (path_id == SPA_ID_INVALID && (reconnect || n->connect_count == 0)) { - /* find fallback */ - struct find_data find; - - spa_zero(find); - find.impl = impl; - find.node = n; - find.media = n->media; - find.capture_sink = n->capture_sink; - find.direction = n->direction; - find.exclusive = n->exclusive; - find.have_passthrough = n->have_passthrough; - find.passthrough_only = n->passthrough_only; - find.link_group = n->peer == NULL ? spa_dict_lookup(props, PW_KEY_NODE_LINK_GROUP) : NULL; - - spa_list_for_each(peer, &impl->node_list, link) - find_node(&find, peer); - - peer = find.result; - if (peer) - can_passthrough = find.can_passthrough; - - if (n->passthrough_only && !can_passthrough) - peer = NULL; - } else { - peer = NULL; - } - -do_link: - if (peer == NULL) { - if (!reconnect) { - pw_log_info("don-reconnect target node destroyed: destroy %d", n->id); - sm_media_session_destroy_object(impl->session, n->id); - } else if (reconnect && n->connect_count > 0) { - /* Don't error the stream on reconnects */ - pw_log_info("%p: no node found for %d, waiting reconnect", impl, n->id); - if (n->peer != NULL) - unlink_nodes(n, n->peer); - return 0; - } else { - pw_log_warn("%p: no node found for %d, stream error", impl, n->id); - } - - obj = sm_media_session_find_object(impl->session, n->client_id); - pw_log_debug("%p: client_id:%d object:%p type:%s", impl, - n->client_id, obj, obj ? obj->type : "None"); - - if (obj && spa_streq(obj->type, PW_TYPE_INTERFACE_Client)) { - pw_client_error((struct pw_client*)obj->proxy, - n->id, -ENOENT, "no node available"); - } - return -ENOENT; - } else if (peer == n->peer) { - pw_log_debug("%p: node %d already linked to %d (not changing)", - impl, n->id, peer->id); - return 0; - } else { - n->want_passthrough = can_passthrough; - } - - if ((n->exclusive || n->want_passthrough) && - peer->obj->info->state == PW_NODE_STATE_RUNNING) { - pw_log_warn("node %d busy, can't get exclusive/passthrough access", peer->id); - return -EBUSY; - } - - return relink_node(impl, n, peer); -} - -static void session_info(void *data, const struct pw_core_info *info) -{ - struct impl *impl = data; - - if (info && (info->change_mask & PW_CORE_CHANGE_MASK_PROPS)) { - const char *str; - - if ((str = spa_dict_lookup(info->props, "default.clock.rate")) != NULL) - impl->sample_rate = atoi(str); - - pw_log_debug("%p: props changed sample_rate:%d", impl, impl->sample_rate); - } -} - -static void refresh_auto_default_nodes(struct impl *impl) -{ - struct default_node *def; - - if (impl->session->metadata == NULL) - return; - - pw_log_debug("%p: refresh", impl); - - /* Auto set default nodes */ - for (def = impl->defaults; def->key != NULL; ++def) { - struct node *node; - node = find_auto_default_node(impl, def); - if (node == NULL && def->value != NULL) { - def->value = NULL; - pw_metadata_set_property(impl->session->metadata, - PW_ID_CORE, def->key, NULL, NULL); - } else if (node != NULL) { - const char *name = pw_properties_get(node->obj->obj.props, PW_KEY_NODE_NAME); - char buf[1024]; - - if (name == NULL || spa_streq(name, def->value)) - continue; - - free(def->value); - def->value = strdup(name); - - snprintf(buf, sizeof(buf), "{ \"name\": \"%s\" }", name); - pw_metadata_set_property(impl->session->metadata, - PW_ID_CORE, def->key, - "Spa:String:JSON", buf); - } - } -} - -static void session_rescan(void *data, int seq) -{ - struct impl *impl = data; - struct node *node; - - pw_log_debug("%p: rescan", impl); - -again: - impl->node_list_changed = false; - spa_list_for_each(node, &impl->node_list, link) { - rescan_node(impl, node); - if (impl->node_list_changed) - goto again; - } - - refresh_auto_default_nodes(impl); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - struct default_node *def; - for (def = impl->defaults; def->key != NULL; ++def) { - free(def->config); - free(def->value); - } - spa_hook_remove(&impl->listener); - if (impl->session->metadata) - spa_hook_remove(&impl->meta_listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .info = session_info, - .create = session_create, - .remove = session_remove, - .rescan = session_rescan, - .destroy = session_destroy, -}; - -static int metadata_property(void *object, uint32_t subject, - const char *key, const char *type, const char *value) -{ - struct impl *impl = object; - - if (subject == PW_ID_CORE) { - struct default_node *def; - bool changed = false; - char *val = NULL; - char name[1024]; - - if (key != NULL && value != NULL) { - pw_log_info("meta %s: %s", key, value); - if (json_object_find(value, "name", name, sizeof(name)) < 0) - return 0; - pw_log_info("meta name: %s", name); - val = name; - } - for (def = impl->defaults; def->key != NULL; ++def) { - if (key == NULL || spa_streq(key, def->key_config)) { - if (!spa_streq(def->config, val)) - changed = true; - free(def->config); - def->config = val ? strdup(val) : NULL; - } - if (key == NULL || spa_streq(key, def->key)) { - bool eff_changed = !spa_streq(def->value, val); - free(def->value); - def->value = val ? strdup(val) : NULL; - - /* The effective value was changed. In case it was changed by - * someone else than us, reset the value to avoid confusion. */ - if (eff_changed) - refresh_auto_default_nodes(impl); - } - } - if (changed) - sm_media_session_schedule_rescan(impl->session); - } else if (key == NULL || spa_streq(key, "target.node")) { - struct node *src_node; - - src_node = find_node_by_id_name(impl, subject, NULL); - if (!src_node) - return 0; - - /* Set target and schedule rescan */ - if (key == NULL || value == NULL) { - free(src_node->obj->target_node); - src_node->obj->target_node = NULL; - src_node->obj->fixed_target = false; - } else { - const char *str; - struct node *dst_node; - - dst_node = find_node_by_id_name(impl, SPA_ID_INVALID, value); - if (dst_node) { - str = get_device_name(dst_node); - if (!str) - return 0; - } else if ((uint32_t)atoi(value) == SPA_ID_INVALID) { - str = NULL; - } else { - return 0; - } - - free(src_node->obj->target_node); - src_node->obj->target_node = str ? strdup(str) : NULL; - src_node->obj->fixed_target = true; - } - - sm_media_session_schedule_rescan(impl->session); - } - return 0; -} - -static const struct pw_metadata_events metadata_events = { - PW_VERSION_METADATA_EVENTS, - .property = metadata_property, -}; - -int sm_policy_node_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->sample_rate = 48000; - - impl->defaults[DEFAULT_AUDIO_SINK] = (struct default_node){ - DEFAULT_AUDIO_SINK_KEY, DEFAULT_CONFIG_AUDIO_SINK_KEY, NULL, NULL - }; - impl->defaults[DEFAULT_AUDIO_SOURCE] = (struct default_node){ - DEFAULT_AUDIO_SOURCE_KEY, DEFAULT_CONFIG_AUDIO_SOURCE_KEY, NULL, NULL - }; - impl->defaults[DEFAULT_VIDEO_SOURCE] = (struct default_node){ - DEFAULT_VIDEO_SOURCE_KEY, DEFAULT_CONFIG_VIDEO_SOURCE_KEY, NULL, NULL - }; - impl->defaults[3] = (struct default_node){ NULL, NULL, NULL, NULL }; - - - impl->streams_follow_default = pw_properties_get_bool(session->props, NAME ".streams-follow-default", false); - impl->alsa_no_dsp = pw_properties_get_bool(session->props, NAME ".alsa-no-dsp", false); - - spa_list_init(&impl->node_list); - - sm_media_session_add_listener(impl->session, - &impl->listener, - &session_events, impl); - - if (session->metadata) { - pw_metadata_add_listener(session->metadata, - &impl->meta_listener, - &metadata_events, impl); - } - return 0; -} diff --git a/src/media-session/restore-stream.c b/src/media-session/restore-stream.c deleted file mode 100644 index bdde8b75f..000000000 --- a/src/media-session/restore-stream.c +++ /dev/null @@ -1,593 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_restore_stream Media Session Module: Restore Stream - * - * The Restore Stream modules monitors \ref pw_node - * of media class `"Stream/..."` and `"Audio/..."` and saves the \ref - * SPA_PROP_volume, \ref SPA_PROP_mute, and \ref SPA_PROP_channelMap - * parameters to the `route-settings` state file and the `route-settings` - * metadata objects. - * - * When a stream re-appears and matches a saved state, the parameters are - * restored to their respective values. Additionally, the target node is saved - * so the stream can be re-associated with that node, if possible. - * - * To match a stream with a previously saved state, this module uses one of - * \ref PW_KEY_MEDIA_ROLE, \ref PW_KEY_APP_ID, - * \ref PW_KEY_APP_NAME, \ref PW_KEY_MEDIA_NAME, and \ref PW_KEY_NODE_NAME, - * whichever applies. - * - * The state file is `$XDG_STATE_HOME/pipewire/media-session.d/restore-stream`. - * - * The `route-settings` metadata object is owned by this module. - */ - -#define NAME "restore-stream" -#define SESSION_KEY "restore-stream" -#define PREFIX "restore.stream." - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define SAVE_INTERVAL 1 - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - struct spa_source *idle_timeout; - - struct pw_metadata *metadata; - struct spa_hook metadata_listener; - - struct pw_properties *props; - - unsigned int sync:1; -}; - -struct stream { - struct sm_node *obj; - - uint32_t id; - struct impl *impl; - char *media_class; - char *key; - unsigned int restored:1; - - struct spa_hook listener; -}; - -static void remove_idle_timeout(struct impl *impl) -{ - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - int res; - - if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, - SESSION_KEY, impl->props)) < 0) - pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); - pw_loop_destroy_source(main_loop, impl->idle_timeout); - impl->idle_timeout = NULL; - } -} - -static void idle_timeout(void *data, uint64_t expirations) -{ - struct impl *impl = data; - pw_log_debug("%p: idle timeout", impl); - remove_idle_timeout(impl); -} - -static void add_idle_timeout(struct impl *impl) -{ - struct timespec value; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (impl->idle_timeout == NULL) - impl->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, impl); - - value.tv_sec = SAVE_INTERVAL; - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, impl->idle_timeout, &value, NULL, false); -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - remove_idle_timeout(impl); - spa_hook_remove(&impl->listener); - pw_properties_free(impl->props); - free(impl); -} - -static uint32_t channel_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) - return spa_type_audio_channel[i].type; - } - return SPA_AUDIO_CHANNEL_UNKNOWN; -} - -static const char *channel_to_name(uint32_t channel) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_type_audio_channel[i].type == channel) - return spa_debug_type_short_name(spa_type_audio_channel[i].name); - } - return "UNK"; -} - -static char *serialize_props(struct stream *str, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - float val = 0.0f; - bool b = false, comma = false; - char *ptr; - size_t size; - FILE *f; - - f = open_memstream(&ptr, &size); - fprintf(f, "{ "); - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_volume: - if (spa_pod_get_float(&prop->value, &val) < 0) - continue; - fprintf(f, "%s\"volume\": %f", (comma ? ", " : ""), val); - break; - case SPA_PROP_mute: - if (spa_pod_get_bool(&prop->value, &b) < 0) - continue; - fprintf(f, "%s\"mute\": %s", (comma ? ", " : ""), b ? "true" : "false"); - break; - case SPA_PROP_channelVolumes: - { - uint32_t i, n_vals; - float vals[SPA_AUDIO_MAX_CHANNELS]; - - n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vals, SPA_AUDIO_MAX_CHANNELS); - if (n_vals == 0) - continue; - - fprintf(f, "%s\"volumes\": [", (comma ? ", " : "")); - for (i = 0; i < n_vals; i++) - fprintf(f, "%s%f", (i == 0 ? " ":", "), vals[i]); - fprintf(f, " ]"); - break; - } - case SPA_PROP_channelMap: - { - uint32_t i, n_ch; - uint32_t map[SPA_AUDIO_MAX_CHANNELS]; - - n_ch = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - map, SPA_AUDIO_MAX_CHANNELS); - if (n_ch == 0) - continue; - - fprintf(f, "%s\"channels\": [", (comma ? ", " : "")); - for (i = 0; i < n_ch; i++) - fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), channel_to_name(map[i])); - fprintf(f, " ]"); - break; - } - default: - continue; - } - comma = true; - } - if (str->obj->target_node != NULL) - fprintf(f, "%s\"target-node\": \"%s\"", - (comma ? ", " : ""), str->obj->target_node); - - fprintf(f, " }"); - fclose(f); - - if (strlen(ptr) < 5) { - free(ptr); - ptr = NULL; - } - return ptr; -} - -static void sync_metadata(struct impl *impl) -{ - const struct spa_dict_item *it; - - impl->sync = true; - spa_dict_for_each(it, &impl->props->dict) - pw_metadata_set_property(impl->metadata, - PW_ID_CORE, it->key, "Spa:String:JSON", it->value); - impl->sync = false; -} - -static int metadata_property(void *object, uint32_t subject, - const char *key, const char *type, const char *value) -{ - struct impl *impl = object; - int changed = 0; - - if (impl->sync) - return 0; - - if (subject == PW_ID_CORE) { - if (key == NULL) { - pw_properties_clear(impl->props); - changed = 1; - } - else if (spa_strstartswith(key, PREFIX)) { - changed += pw_properties_set(impl->props, key, value); - } - } - if (changed > 0) - add_idle_timeout(impl); - - return 0; -} - -static const struct pw_metadata_events metadata_events = { - PW_VERSION_METADATA_EVENTS, - .property = metadata_property, -}; - -static int handle_props(struct stream *str, struct sm_param *p) -{ - struct impl *impl = str->impl; - const char *key; - int changed = 0; - - if ((key = str->key) == NULL) - return -EBUSY; - - if (p->param) { - char *val = serialize_props(str, p->param); - if (val) { - pw_log_info("stream %d: save props %s %s", str->id, key, val); - changed += pw_properties_set(impl->props, key, val); - free(val); - add_idle_timeout(impl); - } - } - if (changed) - sync_metadata(impl); - return 0; -} - -static int restore_stream(struct stream *str) -{ - struct impl *impl = str->impl; - struct spa_json it[3]; - const char *val, *value; - char buf[1024], key[128]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - struct spa_pod_frame f[2]; - struct spa_pod *param; - - if (str->key == NULL) - return -EBUSY; - - val = pw_properties_get(impl->props, str->key); - if (val == NULL) - return -ENOENT; - - pw_log_info("stream %d: restoring '%s' to %s", str->id, str->key, val); - - spa_json_init(&it[0], val, strlen(val)); - - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - - while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) { - if (spa_streq(key, "volume")) { - float vol; - if (spa_json_get_float(&it[1], &vol) <= 0) - continue; - spa_pod_builder_prop(&b, SPA_PROP_volume, 0); - spa_pod_builder_float(&b, vol); - } - else if (spa_streq(key, "mute")) { - bool mute; - if (spa_json_get_bool(&it[1], &mute) <= 0) - continue; - spa_pod_builder_prop(&b, SPA_PROP_mute, 0); - spa_pod_builder_bool(&b, mute); - } - else if (spa_streq(key, "volumes")) { - uint32_t n_vols; - float vols[SPA_AUDIO_MAX_CHANNELS]; - - if (spa_json_enter_array(&it[1], &it[2]) <= 0) - continue; - - for (n_vols = 0; n_vols < SPA_AUDIO_MAX_CHANNELS; n_vols++) { - if (spa_json_get_float(&it[2], &vols[n_vols]) <= 0) - break; - } - if (n_vols == 0) - continue; - - spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0); - spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, - n_vols, vols); - } - else if (spa_streq(key, "channels")) { - uint32_t n_ch; - uint32_t map[SPA_AUDIO_MAX_CHANNELS]; - - if (spa_json_enter_array(&it[1], &it[2]) <= 0) - continue; - - for (n_ch = 0; n_ch < SPA_AUDIO_MAX_CHANNELS; n_ch++) { - char chname[16]; - if (spa_json_get_string(&it[2], chname, sizeof(chname)) <= 0) - break; - map[n_ch] = channel_from_name(chname); - } - if (n_ch == 0) - continue; - - spa_pod_builder_prop(&b, SPA_PROP_channelMap, 0); - spa_pod_builder_array(&b, sizeof(uint32_t), SPA_TYPE_Id, - n_ch, map); - } - else if (spa_streq(key, "target-node")) { - char name[1024]; - - if (spa_json_get_string(&it[1], name, sizeof(name)) <= 0) - continue; - - pw_log_info("stream %d: target '%s'", str->obj->obj.id, name); - if (!str->obj->fixed_target) { - free(str->obj->target_node); - str->obj->target_node = strdup(name); - } - } else { - if (spa_json_next(&it[1], &value) <= 0) - break; - } - } - param = spa_pod_builder_pop(&b, &f[0]); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)str->obj->obj.proxy, - SPA_PARAM_Props, 0, param); - - sm_media_session_schedule_rescan(str->impl->session); - - return 0; -} - -static int save_stream(struct stream *str) -{ - struct sm_param *p; - spa_list_for_each(p, &str->obj->param_list, link) { - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - switch (p->id) { - case SPA_PARAM_Props: - handle_props(str, p); - break; - default: - break; - } - } - return 0; -} - -static void update_stream(struct stream *str) -{ - static const char * const keys[] = { - PW_KEY_MEDIA_ROLE, - PW_KEY_APP_ID, - PW_KEY_APP_NAME, - PW_KEY_MEDIA_NAME, - PW_KEY_NODE_NAME, - }; - - struct impl *impl = str->impl; - uint32_t i; - const char *p; - char *key; - struct sm_object *obj = &str->obj->obj; - - key = NULL; - for (i = 0; i < SPA_N_ELEMENTS(keys); i++) { - if ((p = pw_properties_get(obj->props, keys[i]))) { - key = spa_aprintf(PREFIX"%s.%s:%s", str->media_class, keys[i], p); - break; - } - } - if (key == NULL) - return; - - pw_log_debug("%p: stream %p key '%s'", impl, str, key); - free(str->key); - str->key = key; - - if (!str->restored) { - restore_stream(str); - str->restored = true; - } else { - save_stream(str); - } -} - -static void object_update(void *data) -{ - struct stream *str = data; - struct impl *impl = str->impl; - - pw_log_info("%p: stream %p %08x/%08x", impl, str, - str->obj->obj.changed, str->obj->obj.avail); - - if (str->obj->obj.changed & (SM_NODE_CHANGE_MASK_INFO | SM_NODE_CHANGE_MASK_PARAMS)) - update_stream(str); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct stream *str; - const char *media_class, *routes; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Node) || - object->props == NULL || - (media_class = pw_properties_get(object->props, PW_KEY_MEDIA_CLASS)) == NULL) - return; - - if (spa_strstartswith(media_class, "Stream/")) { - media_class += strlen("Stream/"); - pw_log_debug("%p: add stream '%d' %s", impl, object->id, media_class); - } else if (spa_strstartswith(media_class, "Audio/") && - ((routes = pw_properties_get(object->props, "device.routes")) == NULL || - atoi(routes) == 0)) { - pw_log_debug("%p: add node '%d' %s", impl, object->id, media_class); - } else { - return; - } - - str = sm_object_add_data(object, SESSION_KEY, sizeof(struct stream)); - str->obj = (struct sm_node*)object; - str->id = object->id; - str->impl = impl; - str->media_class = strdup(media_class); - - str->obj->obj.mask |= SM_OBJECT_CHANGE_MASK_PROPERTIES | SM_NODE_CHANGE_MASK_PARAMS; - sm_object_add_listener(&str->obj->obj, &str->listener, &object_events, str); -} - -static void destroy_stream(struct impl *impl, struct stream *str) -{ - remove_idle_timeout(impl); - spa_hook_remove(&str->listener); - free(str->media_class); - free(str->key); - sm_object_remove_data((struct sm_object*)str->obj, SESSION_KEY); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - struct stream *str; - - if (!spa_streq(object->type, PW_TYPE_INTERFACE_Node)) - return; - - pw_log_debug("%p: remove node '%d'", impl, object->id); - - if ((str = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_stream(impl, str); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_restore_stream_start(struct sm_media_session *session) -{ - struct impl *impl; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - impl->props = pw_properties_new(NULL, NULL); - if (impl->props == NULL) - goto exit_errno; - - impl->metadata = sm_media_session_export_metadata(session, "route-settings"); - if (impl->metadata == NULL) - goto exit_errno; - - pw_metadata_add_listener(impl->metadata, &impl->metadata_listener, - &metadata_events, impl); - - if ((res = sm_media_session_load_state(impl->session, - SESSION_KEY, impl->props)) < 0) - pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); - - sync_metadata(impl); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - return 0; - -exit_errno: - res = -errno; - pw_properties_free(impl->props); - free(impl); - return res; -} diff --git a/src/media-session/session-manager.c b/src/media-session/session-manager.c deleted file mode 100644 index e99d76cdb..000000000 --- a/src/media-session/session-manager.c +++ /dev/null @@ -1,178 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/session-manager.h" - -#include "media-session.h" - -/** \page page_media_session_module_session_manager Media Session Module: Session Manager - */ - -#define NAME "session-manager" -#define SESSION_KEY "session-manager" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -int sm_stream_endpoint_start(struct sm_media_session *sess); -int sm_v4l2_endpoint_start(struct sm_media_session *sess); -int sm_bluez5_endpoint_start(struct sm_media_session *sess); -int sm_alsa_endpoint_start(struct sm_media_session *sess); -int sm_policy_ep_start(struct sm_media_session *sess); - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - - struct spa_hook proxy_client_session_listener; - struct spa_hook client_session_listener; -}; - -/** - * Session implementation - */ -static int client_session_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *impl = object; - pw_proxy_error((struct pw_proxy*)impl->session->client_session, - -ENOTSUP, "Session:SetParam not supported"); - return -ENOTSUP; -} - -static int client_session_link_set_param(void *object, uint32_t link_id, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *impl = object; - pw_proxy_error((struct pw_proxy*)impl->session->client_session, - -ENOTSUP, "Session:LinkSetParam not supported"); - return -ENOTSUP; -} - -static int client_session_link_request_state(void *object, uint32_t link_id, uint32_t state) -{ - return -ENOTSUP; -} - -static const struct pw_client_session_events client_session_events = { - PW_VERSION_CLIENT_SESSION_METHODS, - .set_param = client_session_set_param, - .link_set_param = client_session_link_set_param, - .link_request_state = client_session_link_request_state, -}; - -static void proxy_client_session_bound(void *data, uint32_t id) -{ - struct impl *impl = data; - struct pw_session_info info; - - impl->session->session_id = id; - - spa_zero(info); - info.version = PW_VERSION_SESSION_INFO; - info.id = id; - - pw_log_debug("got session id:%d", id); - - pw_client_session_update(impl->session->client_session, - PW_CLIENT_SESSION_UPDATE_INFO, - 0, NULL, - &info); - - /* start endpoints */ - sm_bluez5_endpoint_start(impl->session); - sm_alsa_endpoint_start(impl->session); - sm_v4l2_endpoint_start(impl->session); - sm_stream_endpoint_start(impl->session); - - sm_policy_ep_start(impl->session); -} - -static const struct pw_proxy_events proxy_client_session_events = { - PW_VERSION_PROXY_EVENTS, - .bound = proxy_client_session_bound, -}; - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_session_manager_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - session->client_session = (struct pw_client_session *) - sm_media_session_create_object(impl->session, - "client-session", - PW_TYPE_INTERFACE_ClientSession, - PW_VERSION_CLIENT_SESSION, - NULL, 0); - - pw_proxy_add_listener((struct pw_proxy*)session->client_session, - &impl->proxy_client_session_listener, - &proxy_client_session_events, impl); - - pw_client_session_add_listener(session->client_session, - &impl->client_session_listener, - &client_session_events, impl); - - return 0; -} diff --git a/src/media-session/stream-endpoint.c b/src/media-session/stream-endpoint.c deleted file mode 100644 index ac87ee361..000000000 --- a/src/media-session/stream-endpoint.c +++ /dev/null @@ -1,619 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "pipewire/extensions/session-manager.h" -#include "media-session.h" - -/** \page page_media_session_module_stream_endpoint Media Session Module: Stream Endpoint - */ - -#define NAME "stream-endpoint" -#define SESSION_KEY "stream-endpoint" - -#define DEFAULT_CHANNELS 2 -#define DEFAULT_SAMPLERATE 48000 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct endpoint; - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; -}; - -struct node { - struct sm_node *obj; - struct spa_hook listener; - - struct impl *impl; - - uint32_t id; - enum pw_direction direction; - char *media; - - struct endpoint *endpoint; - - struct spa_audio_info format; -}; - -struct stream { - struct endpoint *endpoint; - struct spa_list link; - - struct pw_properties *props; - struct pw_endpoint_stream_info info; - - struct spa_audio_info format; - - unsigned int active:1; -}; - -struct endpoint { - struct impl *impl; - - struct pw_properties *props; - struct node *node; - - struct pw_client_endpoint *client_endpoint; - struct spa_hook client_endpoint_listener; - struct spa_hook proxy_listener; - struct pw_endpoint_info info; - - struct spa_param_info params[5]; - - struct spa_list stream_list; -}; - -static int client_endpoint_set_session_id(void *object, uint32_t id) -{ - struct endpoint *endpoint = object; - endpoint->info.session_id = id; - return 0; -} - -static int client_endpoint_set_param(void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - struct node *node = endpoint->node; - - pw_log_debug("%p: node %d set param %d", impl, node->obj->obj.id, id); - return pw_node_set_param((struct pw_node*)node->obj->obj.proxy, - id, flags, param); -} - - -static int client_endpoint_stream_set_param(void *object, uint32_t stream_id, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - return -ENOTSUP; -} - -static int stream_set_active(struct stream *stream, bool active) -{ - struct endpoint *endpoint = stream->endpoint; - struct node *node = endpoint->node; - char buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - - if (stream->active == active) - return 0; - - if (active) { - stream->format = node->format; - - switch (stream->format.media_type) { - case SPA_MEDIA_TYPE_audio: - switch (stream->format.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - stream->format.info.raw.rate = 48000; - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &stream->format.info.raw); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(endpoint->info.direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, param); - - pw_node_set_param((struct pw_node*)node->obj->obj.proxy, - SPA_PARAM_PortConfig, 0, param); - break; - default: - break; - } - default: - break; - } - } - stream->active = active; - return 0; -} - -static int client_endpoint_create_link(void *object, const struct spa_dict *props) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - struct pw_properties *p; - struct stream *stream; - int res; - - pw_log_debug("create link"); - - if (props == NULL) - return -EINVAL; - - if (spa_list_is_empty(&endpoint->stream_list)) - return -EIO; - - /* FIXME take first stream */ - stream = spa_list_first(&endpoint->stream_list, struct stream, link); - stream_set_active(stream, true); - - p = pw_properties_new_dict(props); - if (p == NULL) - return -errno; - - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - const char *str; - struct sm_object *obj; - - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->id); - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1"); - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT); - if (str == NULL) { - pw_log_warn("%p: no target endpoint given", impl); - res = -EINVAL; - goto exit; - } - obj = sm_media_session_find_object(impl->session, atoi(str)); - if (obj == NULL || !spa_streq(obj->type, PW_TYPE_INTERFACE_Endpoint)) { - pw_log_warn("%p: could not find endpoint %s (%p)", impl, str, obj); - res = -EINVAL; - goto exit; - } - pw_endpoint_create_link((struct pw_endpoint*)obj->proxy, &p->dict); - } else { - pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->node->id); - pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1"); - - sm_media_session_create_links(impl->session, &p->dict); - } - - res = 0; - -exit: - pw_properties_free(p); - - return res; -} - -static const struct pw_client_endpoint_events client_endpoint_events = { - PW_VERSION_CLIENT_ENDPOINT_EVENTS, - .set_session_id = client_endpoint_set_session_id, - .set_param = client_endpoint_set_param, - .stream_set_param = client_endpoint_stream_set_param, - .create_link = client_endpoint_create_link, -}; - -static struct stream *endpoint_add_stream(struct endpoint *endpoint) -{ - struct stream *s; - struct pw_properties *props = endpoint->props; - struct node *node = endpoint->node; - const char *str; - - s = calloc(1, sizeof(*s)); - if (s == NULL) - return NULL; - - s->endpoint = endpoint; - s->props = pw_properties_new(NULL, NULL); - - if ((str = pw_properties_get(props, PW_KEY_MEDIA_CLASS)) != NULL) - pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str); - if (node->direction == PW_DIRECTION_OUTPUT) - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Playback"); - else - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Capture"); - - s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - s->info.id = 0; - s->info.endpoint_id = endpoint->info.id; - s->info.name = (char*)pw_properties_get(s->props, PW_KEY_ENDPOINT_STREAM_NAME); - s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; - s->info.props = &s->props->dict; - spa_list_append(&endpoint->stream_list, &s->link); - - pw_log_debug("stream %d", node->id); - pw_client_endpoint_stream_update(endpoint->client_endpoint, - s->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, - 0, NULL, - &s->info); - return s; -} - -static void destroy_stream(struct stream *stream) -{ - struct endpoint *endpoint = stream->endpoint; - - pw_client_endpoint_stream_update(endpoint->client_endpoint, - stream->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, - 0, NULL, - &stream->info); - - pw_properties_free(stream->props); - spa_list_remove(&stream->link); - free(stream); -} - -static void complete_endpoint(void *data) -{ - struct endpoint *endpoint = data; - struct impl *impl = endpoint->impl; - struct node *node = endpoint->node; - struct sm_param *p; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - spa_list_for_each(p, &node->obj->param_list, link) { - struct spa_audio_info info = { 0, }; - - switch (p->id) { - case SPA_PARAM_EnumFormat: - if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) - continue; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - continue; - - spa_pod_object_fixate((struct spa_pod_object*)p->param); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - if (spa_format_audio_raw_parse(p->param, &info.info.raw) < 0) - continue; - - if (node->format.info.raw.channels < info.info.raw.channels) - node->format = info; - break; - default: - break; - } - } - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_INFO, - 0, NULL, - &endpoint->info); - - endpoint_add_stream(endpoint); -} - -static void update_params(void *data) -{ - uint32_t n_params; - const struct spa_pod **params; - struct endpoint *endpoint = data; - struct impl *impl = endpoint->impl; - struct node *node = endpoint->node; - struct sm_param *p; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - params = alloca(sizeof(struct spa_pod *) * node->obj->n_params); - n_params = 0; - spa_list_for_each(p, &node->obj->param_list, link) { - switch (p->id) { - case SPA_PARAM_Props: - case SPA_PARAM_PropInfo: - params[n_params++] = p->param; - break; - default: - break; - } - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_PARAMS | - PW_CLIENT_ENDPOINT_UPDATE_INFO, - n_params, params, - &endpoint->info); -} - -static void proxy_destroy(void *data) -{ - struct endpoint *endpoint = data; - struct stream *s; - - spa_list_consume(s, &endpoint->stream_list, link) - destroy_stream(s); - - pw_properties_free(endpoint->props); -} - -static void proxy_bound(void *data, uint32_t id) -{ - struct endpoint *endpoint = data; - endpoint->info.id = id; -} - -static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = proxy_destroy, - .bound = proxy_bound, -}; - -static struct endpoint *create_endpoint(struct node *node) -{ - struct impl *impl = node->impl; - struct pw_properties *props; - struct endpoint *endpoint; - struct pw_proxy *proxy; - const char *str, *media_class = NULL, *name = NULL; - uint32_t subscribe[4], n_subscribe = 0; - - props = pw_properties_new(NULL, NULL); - if (props == NULL) - return NULL; - - if (node->obj->info && node->obj->info->props) { - struct spa_dict *dict = node->obj->info->props; - if ((media_class = spa_dict_lookup(dict, PW_KEY_MEDIA_CLASS)) != NULL) - pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class); - if ((name = spa_dict_lookup(dict, PW_KEY_MEDIA_NAME)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name); - if ((str = spa_dict_lookup(dict, PW_KEY_OBJECT_ID)) != NULL) - pw_properties_set(props, PW_KEY_NODE_ID, str); - if ((str = spa_dict_lookup(dict, PW_KEY_CLIENT_ID)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_CLIENT_ID, str); - if ((str = spa_dict_lookup(dict, PW_KEY_NODE_AUTOCONNECT)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_AUTOCONNECT, str); - if ((str = spa_dict_lookup(dict, PW_KEY_NODE_TARGET)) != NULL) - pw_properties_set(props, PW_KEY_NODE_TARGET, str); - if ((str = spa_dict_lookup(dict, PW_KEY_ENDPOINT_TARGET)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_TARGET, str); - } - - proxy = sm_media_session_create_object(impl->session, - "client-endpoint", - PW_TYPE_INTERFACE_ClientEndpoint, - PW_VERSION_CLIENT_ENDPOINT, - &props->dict, sizeof(*endpoint)); - if (proxy == NULL) { - pw_properties_free(props); - return NULL; - } - - endpoint = pw_proxy_get_user_data(proxy); - endpoint->impl = impl; - endpoint->node = node; - endpoint->props = props; - endpoint->client_endpoint = (struct pw_client_endpoint *) proxy; - endpoint->info.version = PW_VERSION_ENDPOINT_INFO; - endpoint->info.name = (char*)pw_properties_get(props, PW_KEY_ENDPOINT_NAME); - endpoint->info.media_class = (char*)pw_properties_get(props, PW_KEY_MEDIA_CLASS); - endpoint->info.session_id = impl->session->session->obj.id; - endpoint->info.direction = node->direction; - endpoint->info.flags = 0; - endpoint->info.change_mask = - PW_ENDPOINT_CHANGE_MASK_STREAMS | - PW_ENDPOINT_CHANGE_MASK_SESSION | - PW_ENDPOINT_CHANGE_MASK_PROPS | - PW_ENDPOINT_CHANGE_MASK_PARAMS; - endpoint->info.n_streams = 1; - endpoint->info.props = &endpoint->props->dict; - endpoint->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - endpoint->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - endpoint->info.params = endpoint->params; - endpoint->info.n_params = 2; - spa_list_init(&endpoint->stream_list); - - pw_proxy_add_listener(proxy, - &endpoint->proxy_listener, - &proxy_events, endpoint); - - pw_client_endpoint_add_listener(endpoint->client_endpoint, - &endpoint->client_endpoint_listener, - &client_endpoint_events, - endpoint); - - subscribe[n_subscribe++] = SPA_PARAM_EnumFormat; - subscribe[n_subscribe++] = SPA_PARAM_Props; - subscribe[n_subscribe++] = SPA_PARAM_PropInfo; - pw_log_debug("%p: node %p proxy %p subscribe %d params", impl, - node->obj, node->obj->obj.proxy, n_subscribe); - pw_node_subscribe_params((struct pw_node*)node->obj->obj.proxy, - subscribe, n_subscribe); - - sm_media_session_sync(impl->session, complete_endpoint, endpoint); - - return endpoint; -} - -static void destroy_endpoint(struct endpoint *endpoint) -{ - pw_proxy_destroy((struct pw_proxy*)endpoint->client_endpoint); -} - -static void object_update(void *data) -{ - struct node *node = data; - struct impl *impl = node->impl; - - pw_log_debug("%p: node %p endpoint %p %08x", impl, node, node->endpoint, node->obj->obj.changed); - - if (node->endpoint == NULL && - node->obj->obj.avail & SM_OBJECT_CHANGE_MASK_PROPERTIES) - node->endpoint = create_endpoint(node); - - if (node->endpoint && - node->obj->obj.changed & SM_NODE_CHANGE_MASK_PARAMS) - update_params(node->endpoint); -} - - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static int -handle_node(struct impl *impl, struct sm_object *obj) -{ - const char *media_class; - enum pw_direction direction; - struct node *node; - - media_class = obj->props ? pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS) : NULL; - - pw_log_debug("%p: node "PW_KEY_MEDIA_CLASS" %s", impl, media_class); - - if (media_class == NULL) - return 0; - - if (!spa_strstartswith(media_class, "Stream/")) - return 0; - - media_class += strlen("Stream/"); - - if (spa_strstartswith(media_class, "Output/")) { - direction = PW_DIRECTION_OUTPUT; - media_class += strlen("Output/"); - } - else if (spa_strstartswith(media_class, "Input/")) { - direction = PW_DIRECTION_INPUT; - media_class += strlen("Input/"); - } - else - return 0; - - node = sm_object_add_data(obj, SESSION_KEY, sizeof(struct node)); - node->obj = (struct sm_node*)obj; - node->impl = impl; - node->id = obj->id; - node->direction = direction; - node->media = strdup(media_class); - pw_log_debug("%p: node %d is stream %d:%s", impl, node->id, - node->direction, node->media); - - sm_object_add_listener(obj, &node->listener, &object_events, node); - - return 1; -} - -static void destroy_node(struct impl *impl, struct node *node) -{ - if (node->endpoint) - destroy_endpoint(node->endpoint); - free(node->media); - spa_hook_remove(&node->listener); - sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) - res = handle_node(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d: %s", impl, - object->id, spa_strerror(res)); - } -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) { - struct node *node; - if ((node = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_node(impl, node); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_stream_endpoint_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - sm_media_session_add_listener(session, &impl->listener, &session_events, impl); - - return 0; -} diff --git a/src/media-session/streams-follow-default.c b/src/media-session/streams-follow-default.c deleted file mode 100644 index f1766eaac..000000000 --- a/src/media-session/streams-follow-default.c +++ /dev/null @@ -1,49 +0,0 @@ -/* PipeWire - * - * Copyright © 2021 Pauli Virtanen - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * Instruct policy-node to move streams when the default sink/sources (either explicitly set - * via metadata, or determined from priority) changes, and the stream does not have an - * explicitly specified target node. - * - * This is done by just setting a session property flag, and policy-node does the rest. - */ - -#include "config.h" - -#include "pipewire/pipewire.h" -#include "pipewire/extensions/metadata.h" - -#include "media-session.h" - -/** \page page_media_session_module_stream_follow_default Media Session Module: Stream Follow Default - */ - -#define KEY_NAME "policy-node.streams-follow-default" - -int sm_streams_follow_default_start(struct sm_media_session *session) -{ - pw_properties_set(session->props, KEY_NAME, "true"); - return 0; -} diff --git a/src/media-session/suspend-node.c b/src/media-session/suspend-node.c deleted file mode 100644 index 34768f16f..000000000 --- a/src/media-session/suspend-node.c +++ /dev/null @@ -1,269 +0,0 @@ -/* PipeWire - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_suspend_node Media Session Module: Suspend Node - */ - -#define NAME "suspend-node" -#define SESSION_KEY "suspend-node" - -#define DEFAULT_IDLE_SECONDS 3 - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct impl { - struct timespec now; - - struct sm_media_session *session; - struct spa_hook listener; - - struct pw_context *context; - - struct spa_list node_list; - int seq; -}; - -struct node { - struct sm_node *obj; - - uint32_t id; - struct impl *impl; - - struct spa_list link; /**< link in impl node_list */ - enum pw_direction direction; - - struct spa_hook listener; - struct spa_source *idle_timeout; -}; - - -static void remove_idle_timeout(struct node *node) -{ - struct impl *impl = node->impl; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - - if (node->idle_timeout) { - pw_loop_destroy_source(main_loop, node->idle_timeout); - node->idle_timeout = NULL; - } -} - -static void idle_timeout(void *data, uint64_t expirations) -{ - struct node *node = data; - struct impl *impl = node->impl; - struct spa_command *cmd = &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); - - pw_log_info("%p: node %d suspend", impl, node->id); - - remove_idle_timeout(node); - - pw_node_send_command((struct pw_node*)node->obj->obj.proxy, cmd); - - sm_object_release(&node->obj->obj); -} - -static void add_idle_timeout(struct node *node) -{ - struct timespec value; - struct impl *impl = node->impl; - struct pw_loop *main_loop = pw_context_get_main_loop(impl->context); - const char *str; - - if (node->obj->info && node->obj->info->props && - (str = spa_dict_lookup(node->obj->info->props, "session.suspend-timeout-seconds")) != NULL) - value.tv_sec = atoi(str); - else - value.tv_sec = DEFAULT_IDLE_SECONDS; - - if (value.tv_sec == 0) - return; - - if (node->idle_timeout == NULL) - node->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, node); - - value.tv_nsec = 0; - pw_loop_update_timer(main_loop, node->idle_timeout, &value, NULL, false); -} - -static int on_node_idle(struct impl *impl, struct node *node) -{ - pw_log_debug("%p: node %d idle", impl, node->id); - add_idle_timeout(node); - return 0; -} - -static int on_node_running(struct impl *impl, struct node *node) -{ - pw_log_debug("%p: node %d running", impl, node->id); - sm_object_acquire(&node->obj->obj); - remove_idle_timeout(node); - return 0; -} - -static void object_update(void *data) -{ - struct node *node = data; - struct impl *impl = node->impl; - - pw_log_debug("%p: node %p %08x", impl, node, node->obj->obj.changed); - - if (node->obj->obj.changed & SM_NODE_CHANGE_MASK_INFO) { - const struct pw_node_info *info = node->obj->info; - - if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) { - switch (info->state) { - case PW_NODE_STATE_ERROR: - case PW_NODE_STATE_IDLE: - on_node_idle(impl, node); - break; - case PW_NODE_STATE_RUNNING: - on_node_running(impl, node); - break; - case PW_NODE_STATE_SUSPENDED: - break; - default: - break; - } - } - } -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static int -handle_node(struct impl *impl, struct sm_object *object) -{ - struct node *node; - const char *media_class; - - media_class = object->props ? pw_properties_get(object->props, PW_KEY_MEDIA_CLASS) : NULL; - if (media_class == NULL) - return 0; - - if (!spa_strstartswith(media_class, "Audio/") && - (!spa_strstartswith(media_class, "Video/"))) - return 0; - - node = sm_object_add_data(object, SESSION_KEY, sizeof(struct node)); - node->obj = (struct sm_node*)object; - node->impl = impl; - node->id = object->id; - spa_list_append(&impl->node_list, &node->link); - - node->obj->obj.mask |= SM_NODE_CHANGE_MASK_INFO; - sm_object_add_listener(&node->obj->obj, &node->listener, &object_events, node); - - return 1; -} - -static void destroy_node(struct impl *impl, struct node *node) -{ - remove_idle_timeout(node); - spa_list_remove(&node->link); - spa_hook_remove(&node->listener); - sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) - res = handle_node(impl, object); - else - res = 0; - - if (res < 0) - pw_log_warn("%p: can't handle global %d", impl, object->id); -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - pw_log_debug("%p: remove global '%d'", impl, object->id); - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Node)) { - struct node *node; - if ((node = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_node(impl, node); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_suspend_node_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - impl->context = session->context; - - spa_list_init(&impl->node_list); - - sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); - - return 0; -} diff --git a/src/media-session/v4l2-endpoint.c b/src/media-session/v4l2-endpoint.c deleted file mode 100644 index 92875415c..000000000 --- a/src/media-session/v4l2-endpoint.c +++ /dev/null @@ -1,651 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" -#include - -#include "media-session.h" - -/** \page page_media_session_module_v4l2_endpoint Media Session Module: V4L2 Endpoint - */ - -#define NAME "v4l2-endpoint" -#define SESSION_KEY "v4l2-endpoint" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct endpoint { - struct spa_list link; - - struct impl *impl; - - struct pw_properties *props; - - struct node *node; - struct spa_hook listener; - - struct pw_client_endpoint *client_endpoint; - struct spa_hook proxy_listener; - struct spa_hook client_endpoint_listener; - struct pw_endpoint_info info; - - struct spa_param_info params[5]; - - struct spa_list stream_list; -}; - -struct stream { - struct spa_list link; - struct endpoint *endpoint; - - struct pw_properties *props; - struct pw_endpoint_stream_info info; - - unsigned int active:1; -}; - -struct node { - struct impl *impl; - struct sm_node *node; - - struct device *device; - - struct endpoint *endpoint; -}; - -struct device { - struct impl *impl; - uint32_t id; - struct sm_device *device; - struct spa_hook listener; - - struct spa_list endpoint_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook listener; -}; - -static int client_endpoint_set_session_id(void *object, uint32_t id) -{ - struct endpoint *endpoint = object; - endpoint->info.session_id = id; - return 0; -} - -static int client_endpoint_set_param(void *object, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - pw_log_debug("%p: endpoint %p set param %d", impl, endpoint, id); - return pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, - id, flags, param); -} - - -static int client_endpoint_stream_set_param(void *object, uint32_t stream_id, - uint32_t id, uint32_t flags, const struct spa_pod *param) -{ - return -ENOTSUP; -} - -static int stream_set_active(struct endpoint *endpoint, struct stream *stream, bool active) -{ - if (stream->active == active) - return 0; - - stream->active = active; - return 0; -} - -static int client_endpoint_create_link(void *object, const struct spa_dict *props) -{ - struct endpoint *endpoint = object; - struct impl *impl = endpoint->impl; - struct pw_properties *p; - int res; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (props == NULL) - return -EINVAL; - - p = pw_properties_new_dict(props); - if (p == NULL) - return -errno; - - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - const char *str; - struct sm_object *obj; - - str = spa_dict_lookup(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT); - if (str == NULL) { - pw_log_warn("%p: no target endpoint given", impl); - res = -EINVAL; - goto exit; - } - obj = sm_media_session_find_object(impl->session, atoi(str)); - if (obj == NULL || !spa_streq(obj->type, PW_TYPE_INTERFACE_Endpoint)) { - pw_log_warn("%p: could not find endpoint %s (%p)", impl, str, obj); - res = -EINVAL; - goto exit; - } - - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1"); - - pw_endpoint_create_link((struct pw_endpoint*)obj->proxy, &p->dict); - } else { - pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->node->node->info->id); - pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1"); - - sm_media_session_create_links(impl->session, &p->dict); - } - - res = 0; -exit: - pw_properties_free(p); - - return res; -} - -static const struct pw_client_endpoint_events client_endpoint_events = { - PW_VERSION_CLIENT_ENDPOINT_EVENTS, - .set_session_id = client_endpoint_set_session_id, - .set_param = client_endpoint_set_param, - .stream_set_param = client_endpoint_stream_set_param, - .create_link = client_endpoint_create_link, -}; - -static struct stream *endpoint_add_stream(struct endpoint *endpoint) -{ - struct stream *s; - const char *str; - - s = calloc(1, sizeof(*s)); - if (s == NULL) - return NULL; - - s->props = pw_properties_new(NULL, NULL); - s->endpoint = endpoint; - - if ((str = pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS)) != NULL) - pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str); - if ((str = pw_properties_get(endpoint->props, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(s->props, PW_KEY_PRIORITY_SESSION, str); - if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Capture"); - } else { - pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Playback"); - } - - s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; - s->info.id = endpoint->info.n_streams; - s->info.endpoint_id = endpoint->info.id; - s->info.name = (char*)pw_properties_get(s->props, PW_KEY_ENDPOINT_STREAM_NAME); - s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; - s->info.props = &s->props->dict; - - pw_log_debug("stream %d", s->info.id); - pw_client_endpoint_stream_update(endpoint->client_endpoint, - s->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, - 0, NULL, - &s->info); - - spa_list_append(&endpoint->stream_list, &s->link); - endpoint->info.n_streams++; - - return s; -} - -static void destroy_stream(struct stream *stream) -{ - struct endpoint *endpoint = stream->endpoint; - - pw_client_endpoint_stream_update(endpoint->client_endpoint, - stream->info.id, - PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, - 0, NULL, - &stream->info); - - spa_list_remove(&stream->link); - endpoint->info.n_streams--; - - pw_properties_free(stream->props); - free(stream); -} - -static void update_params(void *data) -{ - uint32_t n_params; - const struct spa_pod **params; - struct endpoint *endpoint = data; - struct sm_node *node = endpoint->node->node; - struct sm_param *p; - - pw_log_debug("%p: endpoint", endpoint); - - params = alloca(sizeof(struct spa_pod *) * node->n_params); - n_params = 0; - spa_list_for_each(p, &node->param_list, link) { - switch (p->id) { - case SPA_PARAM_Props: - case SPA_PARAM_PropInfo: - params[n_params++] = p->param; - break; - default: - break; - } - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_PARAMS | - PW_CLIENT_ENDPOINT_UPDATE_INFO, - n_params, params, - &endpoint->info); -} - -static struct endpoint *create_endpoint(struct node *node); - -static void object_update(void *data) -{ - struct endpoint *endpoint = data; - struct impl *impl = endpoint->impl; - struct sm_node *node = endpoint->node->node; - - pw_log_debug("%p: endpoint %p", impl, endpoint); - - if (node->obj.changed & SM_NODE_CHANGE_MASK_PARAMS) - update_params(endpoint); -} - -static const struct sm_object_events object_events = { - SM_VERSION_OBJECT_EVENTS, - .update = object_update -}; - -static void complete_endpoint(void *data) -{ - struct endpoint *endpoint = data; - struct stream *stream; - struct sm_param *p; - - pw_log_debug("endpoint %p: complete", endpoint); - - spa_list_for_each(p, &endpoint->node->node->param_list, link) { - struct spa_video_info info = { 0, }; - - if (p->id != SPA_PARAM_EnumFormat) - continue; - - if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) - continue; - - if (info.media_type != SPA_MEDIA_TYPE_video || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - continue; - - spa_pod_object_fixate((struct spa_pod_object*)p->param); - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_pod(2, NULL, p->param); - - if (spa_format_video_raw_parse(p->param, &info.info.raw) < 0) - continue; - } - - pw_client_endpoint_update(endpoint->client_endpoint, - PW_CLIENT_ENDPOINT_UPDATE_INFO, - 0, NULL, - &endpoint->info); - - stream = endpoint_add_stream(endpoint); - stream_set_active(endpoint, stream, true); - - sm_object_add_listener(&endpoint->node->node->obj, &endpoint->listener, &object_events, endpoint); -} - -static void proxy_destroy(void *data) -{ - struct endpoint *endpoint = data; - struct stream *s; - - pw_log_debug("endpoint %p: destroy", endpoint); - - spa_list_consume(s, &endpoint->stream_list, link) - destroy_stream(s); - - pw_properties_free(endpoint->props); - spa_list_remove(&endpoint->link); - spa_hook_remove(&endpoint->proxy_listener); - spa_hook_remove(&endpoint->client_endpoint_listener); - endpoint->client_endpoint = NULL; -} - -static void proxy_bound(void *data, uint32_t id) -{ - struct endpoint *endpoint = data; - endpoint->info.id = id; -} - -static const struct pw_proxy_events proxy_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = proxy_destroy, - .bound = proxy_bound, -}; - -static struct endpoint *create_endpoint(struct node *node) -{ - struct impl *impl = node->impl; - struct device *device = node->device; - struct pw_properties *props; - struct endpoint *endpoint; - struct pw_proxy *proxy; - const char *str, *media_class = NULL, *name = NULL; - uint32_t subscribe[4], n_subscribe = 0; - struct pw_properties *pr = node->node->obj.props; - enum pw_direction direction; - - if (pr == NULL) { - errno = EINVAL; - return NULL; - } - - if ((media_class = pw_properties_get(pr, PW_KEY_MEDIA_CLASS)) == NULL) { - errno = EINVAL; - return NULL; - } - - if (strstr(media_class, "Source") != NULL) { - direction = PW_DIRECTION_OUTPUT; - } else if (strstr(media_class, "Sink") != NULL) { - direction = PW_DIRECTION_INPUT; - } else { - errno = EINVAL; - return NULL; - } - - props = pw_properties_new(NULL, NULL); - if (props == NULL) - return NULL; - - pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class); - - if ((str = pw_properties_get(pr, PW_KEY_PRIORITY_SESSION)) != NULL) - pw_properties_set(props, PW_KEY_PRIORITY_SESSION, str); - if ((name = pw_properties_get(pr, PW_KEY_NODE_DESCRIPTION)) != NULL) { - pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name); - } - if ((str = pw_properties_get(pr, PW_KEY_DEVICE_ICON_NAME)) != NULL) - pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str); - - proxy = sm_media_session_create_object(impl->session, - "client-endpoint", - PW_TYPE_INTERFACE_ClientEndpoint, - PW_VERSION_CLIENT_ENDPOINT, - &props->dict, sizeof(*endpoint)); - if (proxy == NULL) { - pw_properties_free(props); - return NULL; - } - - endpoint = pw_proxy_get_user_data(proxy); - endpoint->impl = impl; - endpoint->node = node; - endpoint->props = props; - endpoint->client_endpoint = (struct pw_client_endpoint *) proxy; - endpoint->info.version = PW_VERSION_ENDPOINT_INFO; - endpoint->info.name = (char*)pw_properties_get(endpoint->props, PW_KEY_ENDPOINT_NAME); - endpoint->info.media_class = (char*)pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS); - endpoint->info.session_id = impl->session->session->obj.id; - endpoint->info.direction = direction; - endpoint->info.flags = 0; - endpoint->info.change_mask = - PW_ENDPOINT_CHANGE_MASK_STREAMS | - PW_ENDPOINT_CHANGE_MASK_SESSION | - PW_ENDPOINT_CHANGE_MASK_PROPS | - PW_ENDPOINT_CHANGE_MASK_PARAMS; - endpoint->info.n_streams = 0; - endpoint->info.props = &endpoint->props->dict; - endpoint->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - endpoint->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - endpoint->info.params = endpoint->params; - endpoint->info.n_params = 2; - spa_list_init(&endpoint->stream_list); - - pw_log_debug("%p: new endpoint %p for v4l2 node %p", impl, endpoint, node); - pw_proxy_add_listener(proxy, - &endpoint->proxy_listener, - &proxy_events, endpoint); - - pw_client_endpoint_add_listener(endpoint->client_endpoint, - &endpoint->client_endpoint_listener, - &client_endpoint_events, - endpoint); - - subscribe[n_subscribe++] = SPA_PARAM_EnumFormat; - subscribe[n_subscribe++] = SPA_PARAM_Props; - subscribe[n_subscribe++] = SPA_PARAM_PropInfo; - pw_log_debug("%p: endpoint %p proxy %p subscribe %d params", impl, - endpoint, node->node->obj.proxy, n_subscribe); - pw_node_subscribe_params((struct pw_node*)node->node->obj.proxy, - subscribe, n_subscribe); - - spa_list_append(&device->endpoint_list, &endpoint->link); - - sm_media_session_sync(impl->session, complete_endpoint, endpoint); - - return endpoint; -} - -static void destroy_endpoint(struct impl *impl, struct endpoint *endpoint) -{ - pw_log_debug("endpoint %p: destroy", endpoint); - if (endpoint->client_endpoint) { - pw_proxy_destroy((struct pw_proxy*)endpoint->client_endpoint); - } -} - -/** fallback, one stream for each node */ -static int setup_v4l2_endpoint(struct device *device) -{ - struct impl *impl = device->impl; - struct sm_node *n; - struct sm_device *d = device->device; - - pw_log_debug("%p: device %p setup", impl, d); - - spa_list_for_each(n, &d->node_list, link) { - struct node *node; - - pw_log_debug("%p: device %p has node %p", impl, d, n); - - node = sm_object_add_data(&n->obj, SESSION_KEY, sizeof(struct node)); - node->device = device; - node->node = n; - node->impl = impl; - node->endpoint = create_endpoint(node); - if (node->endpoint == NULL) - return -errno; - } - return 0; -} - -static int activate_device(struct device *device) -{ - return setup_v4l2_endpoint(device); -} - -static int deactivate_device(struct device *device) -{ - struct impl *impl = device->impl; - struct endpoint *e; - - pw_log_debug("%p: device %p deactivate", impl, device->device); - spa_list_consume(e, &device->endpoint_list, link) - destroy_endpoint(impl, e); - - return 0; -} - -static void device_update(void *data) -{ - struct device *device = data; - struct impl *impl = device->impl; - - pw_log_debug("%p: device %p %08x %08x", impl, device, - device->device->obj.avail, device->device->obj.changed); - - if (!SPA_FLAG_IS_SET(device->device->obj.avail, - SM_DEVICE_CHANGE_MASK_INFO | - SM_DEVICE_CHANGE_MASK_NODES)) - return; - - if (SPA_FLAG_IS_SET(device->device->obj.changed, - SM_DEVICE_CHANGE_MASK_NODES)) { - activate_device(device); - } -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .update = device_update -}; - -static int -handle_device(struct impl *impl, struct sm_object *obj) -{ - const char *media_class, *str; - struct device *device; - - if (obj->props == NULL) - return 0; - - media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS); - str = pw_properties_get(obj->props, PW_KEY_DEVICE_API); - - pw_log_debug("%p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); - - if (!spa_strstartswith(media_class, "Video/")) - return 0; - if (!spa_streq(str, "v4l2")) - return 0; - - device = sm_object_add_data(obj, SESSION_KEY, sizeof(struct device)); - device->impl = impl; - device->id = obj->id; - device->device = (struct sm_device*)obj; - spa_list_init(&device->endpoint_list); - pw_log_debug("%p: found v4l2 device %d media_class %s", impl, obj->id, media_class); - - sm_object_add_listener(obj, &device->listener, &device_events, device); - - return 0; -} - -static void destroy_device(struct impl *impl, struct device *device) -{ - deactivate_device(device); - spa_hook_remove(&device->listener); - sm_object_remove_data((struct sm_object*)device->device, SESSION_KEY); -} - -static void session_create(void *data, struct sm_object *object) -{ - struct impl *impl = data; - int res; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) - res = handle_device(impl, object); - else - res = 0; - - if (res < 0) { - pw_log_warn("%p: can't handle global %d: %s", impl, - object->id, spa_strerror(res)); - } -} - -static void session_remove(void *data, struct sm_object *object) -{ - struct impl *impl = data; - - if (spa_streq(object->type, PW_TYPE_INTERFACE_Device)) { - struct device *device; - if ((device = sm_object_get_data(object, SESSION_KEY)) != NULL) - destroy_device(impl, device); - } -} - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->listener); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .create = session_create, - .remove = session_remove, - .destroy = session_destroy, -}; - -int sm_v4l2_endpoint_start(struct sm_media_session *session) -{ - struct impl *impl; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->session = session; - sm_media_session_add_listener(session, &impl->listener, &session_events, impl); - - return 0; -} diff --git a/src/media-session/v4l2-monitor.c b/src/media-session/v4l2-monitor.c deleted file mode 100644 index a2123879b..000000000 --- a/src/media-session/v4l2-monitor.c +++ /dev/null @@ -1,591 +0,0 @@ -/* PipeWire - * - * Copyright © 2019 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pipewire/pipewire.h" - -#include "media-session.h" - -/** \page page_media_session_module_v4l2_monitor Media Session Module: V4L2 Monitor - */ -#define NAME "v4l2-monitor" -#define SESSION_CONF "v4l2-monitor.conf" - -PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -struct device; - -struct node { - struct impl *impl; - struct device *device; - struct spa_list link; - uint32_t id; - - struct pw_properties *props; - - struct pw_proxy *proxy; - struct spa_node *node; -}; - -struct device { - struct impl *impl; - struct spa_list link; - uint32_t id; - uint32_t device_id; - - int priority; - int profile; - - struct pw_properties *props; - - struct spa_handle *handle; - struct spa_device *device; - struct spa_hook device_listener; - - struct sm_device *sdevice; - struct spa_hook listener; - - unsigned int appeared:1; - struct spa_list node_list; -}; - -struct impl { - struct sm_media_session *session; - struct spa_hook session_listener; - - struct pw_properties *conf; - - struct spa_handle *handle; - struct spa_device *monitor; - struct spa_hook listener; - - struct spa_list device_list; -}; - -static struct node *v4l2_find_node(struct device *dev, uint32_t id, const char *name) -{ - struct node *node; - const char *str; - - spa_list_for_each(node, &dev->node_list, link) { - if (node->id == id) - return node; - if (name != NULL && - (str = pw_properties_get(node->props, PW_KEY_NODE_NAME)) != NULL && - spa_streq(name, str)) - return node; - } - return NULL; -} - -static void v4l2_update_node(struct device *dev, struct node *node, - const struct spa_device_object_info *info) -{ - pw_log_debug("update node %u", node->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(node->props, info->props); -} - -static struct node *v4l2_create_node(struct device *dev, uint32_t id, - const struct spa_device_object_info *info) -{ - struct node *node; - struct impl *impl = dev->impl; - int i, res; - const char *prefix, *str, *d, *rules; - char tmp[1024]; - - pw_log_debug("new node %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) { - errno = EINVAL; - return NULL; - } - node = calloc(1, sizeof(*node)); - if (node == NULL) { - res = -errno; - goto exit; - } - - node->props = pw_properties_new_dict(info->props); - - pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", dev->device_id); - - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NAME); - if (str == NULL) - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NICK); - if (str == NULL) - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_ALIAS); - if (str == NULL) - str = "v4l2-device"; - if (spa_strstartswith(str, "v4l2_device.")) - str += 12; - - if (strstr(info->factory_name, "sink") != NULL) - prefix = "v4l2_output"; - else if (strstr(info->factory_name, "source") != NULL) - prefix = "v4l2_input"; - else - prefix = info->factory_name; - - pw_properties_set(node->props, PW_KEY_NODE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "%s.%s", prefix, str)); - for (i = 2; i <= 99; i++) { - if ((d = pw_properties_get(node->props, PW_KEY_NODE_NAME)) == NULL) - break; - - if (v4l2_find_node(dev, SPA_ID_INVALID, d) == NULL) - break; - - pw_properties_set(node->props, PW_KEY_NODE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "%s.%s.%d", prefix, str, i)); - } - - str = pw_properties_get(dev->props, SPA_KEY_DEVICE_DESCRIPTION); - if (str == NULL) - str = "v4l2-device"; - - pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, - sm_media_session_sanitize_description(tmp, sizeof(tmp), - ' ', "%s", str)); - - pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), node->props); - - node->impl = impl; - node->device = dev; - node->id = id; - node->proxy = sm_media_session_create_object(impl->session, - "spa-node-factory", - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE, - &node->props->dict, - 0); - if (node->proxy == NULL) { - res = -errno; - goto clean_node; - } - - spa_list_append(&dev->node_list, &node->link); - - return node; - -clean_node: - pw_properties_free(node->props); - free(node); -exit: - errno = -res; - return NULL; -} - -static void v4l2_remove_node(struct device *dev, struct node *node) -{ - pw_log_debug("remove node %u", node->id); - spa_list_remove(&node->link); - pw_proxy_destroy(node->proxy); - pw_properties_free(node->props); - free(node); -} - -static void v4l2_device_info(void *data, const struct spa_device_info *info) -{ - struct device *dev = data; - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(dev->props, info->props); -} - -static void v4l2_device_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct device *dev = data; - struct node *node; - - node = v4l2_find_node(dev, id, NULL); - - if (info == NULL) { - if (node == NULL) { - pw_log_warn("device %p: unknown node %u", dev, id); - return; - } - v4l2_remove_node(dev, node); - } else if (node == NULL) { - v4l2_create_node(dev, id, info); - } else { - v4l2_update_node(dev, node, info); - } - sm_media_session_schedule_rescan(dev->impl->session); -} - -static const struct spa_device_events v4l2_device_events = { - SPA_VERSION_DEVICE_EVENTS, - .info = v4l2_device_info, - .object_info = v4l2_device_object_info -}; - -static struct device *v4l2_find_device(struct impl *impl, uint32_t id, const char *name) -{ - struct device *dev; - const char *str; - - spa_list_for_each(dev, &impl->device_list, link) { - if (dev->id == id) - return dev; - if (name != NULL && - (str = pw_properties_get(dev->props, PW_KEY_DEVICE_NAME)) != NULL && - spa_streq(str, name)) - return dev; - } - return NULL; -} - -static void v4l2_update_device(struct impl *impl, struct device *dev, - const struct spa_device_object_info *info) -{ - pw_log_debug("update device %u", dev->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(dev->props, info->props); -} - -static int v4l2_update_device_props(struct device *dev) -{ - struct pw_properties *p = dev->props; - const char *s, *d; - char temp[32], tmp[1024]; - int i; - - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_NAME)) == NULL) { - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_ID)) == NULL) { - if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_PATH)) == NULL) { - snprintf(temp, sizeof(temp), "%d", dev->id); - s = temp; - } - } - } - pw_properties_set(p, PW_KEY_DEVICE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "v4l2_device.%s", s)); - - for (i = 2; i <= 99; i++) { - if ((d = pw_properties_get(p, PW_KEY_DEVICE_NAME)) == NULL) - break; - - if (v4l2_find_device(dev->impl, SPA_ID_INVALID, d) == NULL) - break; - - pw_properties_set(p, PW_KEY_DEVICE_NAME, - sm_media_session_sanitize_name(tmp, sizeof(tmp), - '_', "v4l2_device.%s.%d", s, i)); - } - if (i == 99) - return -EEXIST; - - if (pw_properties_get(p, PW_KEY_DEVICE_DESCRIPTION) == NULL) { - d = pw_properties_get(p, PW_KEY_DEVICE_PRODUCT_NAME); - if (!d) - d = "Unknown device"; - - pw_properties_set(p, PW_KEY_DEVICE_DESCRIPTION, d); - } - return 0; -} - -static void set_profile(struct device *device, int index) -{ - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - pw_log_debug("%p: set profile %d id:%d", device, index, device->device_id); - - device->profile = index; - if (device->device_id != 0) { - spa_device_set_param(device->device, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); - } -} - -static void device_destroy(void *data) -{ - struct device *device = data; - struct node *node; - - pw_log_debug("device %p destroy", device); - spa_list_remove(&device->link); - - spa_list_consume(node, &device->node_list, link) - v4l2_remove_node(device, node); - - if (device->appeared) - spa_hook_remove(&device->device_listener); -} - -static void device_free(void *data) -{ - struct device *device = data; - pw_log_debug("device %p free", device); - spa_hook_remove(&device->listener); - pw_unload_spa_handle(device->handle); - pw_properties_free(device->props); - sm_object_discard(&device->sdevice->obj); - free(device); -} - -static void device_update(void *data) -{ - struct device *device = data; - - pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); - - if (device->appeared) - return; - - device->device_id = device->sdevice->obj.id; - device->appeared = true; - - spa_device_add_listener(device->device, - &device->device_listener, - &v4l2_device_events, device); - - set_profile(device, 1); - sm_object_sync_update(&device->sdevice->obj); -} - -static const struct sm_object_events device_events = { - SM_VERSION_OBJECT_EVENTS, - .destroy = device_destroy, - .free = device_free, - .update = device_update, -}; - -static struct device *v4l2_create_device(struct impl *impl, uint32_t id, - const struct spa_device_object_info *info) -{ - struct pw_context *context = impl->session->context; - struct device *dev; - struct spa_handle *handle; - int res; - void *iface; - const char *rules; - - pw_log_debug("new device %u", id); - - if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { - errno = EINVAL; - return NULL; - } - - dev = calloc(1, sizeof(*dev)); - if (dev == NULL) { - res = -errno; - goto exit; - } - - dev->impl = impl; - dev->id = id; - dev->props = pw_properties_new_dict(info->props); - v4l2_update_device_props(dev); - - if ((rules = pw_properties_get(impl->conf, "rules")) != NULL) - sm_media_session_match_rules(rules, strlen(rules), dev->props); - - handle = pw_context_load_spa_handle(context, - info->factory_name, - &dev->props->dict); - if (handle == NULL) { - res = -errno; - pw_log_error("can't make factory instance: %m"); - goto clean_device; - } - - if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { - pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); - goto unload_handle; - } - - dev->handle = handle; - dev->device = iface; - - dev->sdevice = sm_media_session_export_device(impl->session, - &dev->props->dict, dev->device); - - if (dev->sdevice == NULL) { - res = -errno; - goto clean_device; - } - - pw_log_debug("got object %p", &dev->sdevice->obj); - - sm_object_add_listener(&dev->sdevice->obj, - &dev->listener, - &device_events, dev); - - spa_list_init(&dev->node_list); - spa_list_append(&impl->device_list, &dev->link); - - return dev; - -unload_handle: - pw_unload_spa_handle(handle); -clean_device: - pw_properties_free(dev->props); - free(dev); -exit: - errno = -res; - return NULL; -} - -static void v4l2_remove_device(struct impl *impl, struct device *dev) -{ - pw_log_debug("remove device %u", dev->id); - if (dev->sdevice) - sm_object_destroy(&dev->sdevice->obj); -} - -static void v4l2_udev_object_info(void *data, uint32_t id, - const struct spa_device_object_info *info) -{ - struct impl *impl = data; - struct device *dev; - - dev = v4l2_find_device(impl, id, NULL); - - if (info == NULL) { - if (dev == NULL) - return; - v4l2_remove_device(impl, dev); - } else if (dev == NULL) { - if (v4l2_create_device(impl, id, info) == NULL) - return; - } else { - v4l2_update_device(impl, dev, info); - } -} - -static const struct spa_device_events v4l2_udev_callbacks = -{ - SPA_VERSION_DEVICE_EVENTS, - .object_info = v4l2_udev_object_info, -}; - -static void session_destroy(void *data) -{ - struct impl *impl = data; - spa_hook_remove(&impl->session_listener); - spa_hook_remove(&impl->listener); - pw_unload_spa_handle(impl->handle); - pw_properties_free(impl->conf); - free(impl); -} - -static const struct sm_media_session_events session_events = { - SM_VERSION_MEDIA_SESSION_EVENTS, - .destroy = session_destroy, -}; - -int sm_v4l2_monitor_start(struct sm_media_session *sess) -{ - struct pw_context *context = sess->context; - struct impl *impl; - int res; - void *iface; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - impl->conf = pw_properties_new(NULL, NULL); - if (impl->conf == NULL) { - res = -errno; - goto out_free; - } - impl->session = sess; - - impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_V4L2_ENUM_UDEV, NULL); - if (impl->handle == NULL) { - res = -errno; - pw_log_info("can't load %s: %m", SPA_NAME_API_V4L2_ENUM_UDEV); - goto out_free; - } - - if ((res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { - pw_log_error("can't get MONITOR interface: %d", res); - goto out_unload; - } - - - impl->monitor = iface; - spa_list_init(&impl->device_list); - - if ((res = sm_media_session_load_conf(impl->session, - SESSION_CONF, impl->conf)) < 0) - pw_log_info("can't load "SESSION_CONF" config: %s", spa_strerror(res)); - - spa_device_add_listener(impl->monitor, &impl->listener, - &v4l2_udev_callbacks, impl); - - sm_media_session_add_listener(sess, &impl->session_listener, &session_events, impl); - - return 0; - -out_unload: - pw_unload_spa_handle(impl->handle); -out_free: - free(impl); - return res; -} diff --git a/src/meson.build b/src/meson.build index 1e45830d8..2fb73fd59 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,5 @@ subdir('pipewire') -subdir('media-session') subdir('daemon') subdir('tools') subdir('modules') diff --git a/src/tools/meson.build b/src/tools/meson.build index 904741ece..a9dac3fa4 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -69,6 +69,7 @@ summary({'Build pw-cat tool': build_pw_cat}, bool_yn: true, section: 'pw-cat/pw- if dbus_dep.found() executable('pw-reserve', + 'reserve.h', 'pw-reserve.c', install: true, dependencies : [dbus_dep, pipewire_dep], diff --git a/src/tools/pw-reserve.c b/src/tools/pw-reserve.c index 35e933f4e..0bb803d11 100644 --- a/src/tools/pw-reserve.c +++ b/src/tools/pw-reserve.c @@ -33,7 +33,7 @@ #include "pipewire/pipewire.h" -#include "../media-session/reserve.c" +#include "reserve.c" struct impl { struct pw_main_loop *mainloop; diff --git a/src/media-session/reserve.c b/src/tools/reserve.c similarity index 100% rename from src/media-session/reserve.c rename to src/tools/reserve.c diff --git a/src/media-session/reserve.h b/src/tools/reserve.h similarity index 100% rename from src/media-session/reserve.h rename to src/tools/reserve.h diff --git a/subprojects/media-session.wrap b/subprojects/media-session.wrap new file mode 100644 index 000000000..f6b4e62fb --- /dev/null +++ b/subprojects/media-session.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/pipewire/media-session.git +revision = head +