diff --git a/src/modules/meson.build b/src/modules/meson.build index 98bc38642..69ce942f8 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -37,6 +37,18 @@ pipewire_module_client_node = shared_library('pipewire-module-client-node', dependencies : [mathlib, dl_lib, pipewire_dep], ) +pipewire_module_client_node = shared_library('pipewire-module-client-device', + [ 'module-client-device.c', + 'module-client-device/resource-device.c', + 'module-client-device/proxy-device.c', + 'module-client-device/protocol-native.c', ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + pipewire_module_link_factory = shared_library('pipewire-module-link-factory', [ 'module-link-factory.c' ], c_args : pipewire_module_c_args, diff --git a/src/modules/module-client-device.c b/src/modules/module-client-device.c new file mode 100644 index 000000000..24b03d8cd --- /dev/null +++ b/src/modules/module-client-device.c @@ -0,0 +1,160 @@ +/* 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 "config.h" + +#include + +#include "module-client-device/client-device.h" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, + { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create and control remote devices" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct pw_proxy *pw_remote_spa_device_export(struct pw_remote *remote, + uint32_t type, struct pw_properties *props, void *object); + +struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_core *core); + +struct factory_data { + struct pw_factory *this; + struct pw_properties *properties; + + struct pw_module *module; + struct spa_hook module_listener; + + struct pw_export_type export_spadevice; +}; + +static void *create_object(void *_data, + struct pw_resource *resource, + uint32_t type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id) +{ + void *result; + struct pw_resource *device_resource; + struct pw_global *parent; + struct pw_client *client = pw_resource_get_client(resource); + + device_resource = pw_resource_new(client, new_id, PW_PERM_RWX, type, version, 0); + if (device_resource == NULL) + goto no_mem; + + parent = pw_client_get_global(client); + + result = pw_client_device_new(device_resource, parent, properties); + if (result == NULL) + goto no_mem; + + return result; + + no_mem: + pw_log_error("can't create device"); + pw_resource_error(resource, -ENOMEM, "can't create device: no memory"); + goto done; + done: + if (properties) + pw_properties_free(properties); + return NULL; +} + +static const struct pw_factory_implementation impl_factory = { + PW_VERSION_FACTORY_IMPLEMENTATION, + .create_object = create_object, +}; + +static void module_destroy(void *data) +{ + struct factory_data *d = data; + + spa_hook_remove(&d->module_listener); + + if (d->properties) + pw_properties_free(d->properties); + + spa_list_remove(&d->export_spadevice.link); + + pw_factory_destroy(d->this); +} + +static const struct pw_module_events module_events = { + PW_VERSION_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static int module_init(struct pw_module *module, struct pw_properties *properties) +{ + struct pw_core *core = pw_module_get_core(module); + struct pw_factory *factory; + struct factory_data *data; + + factory = pw_factory_new(core, + "client-device", + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + NULL, + sizeof(*data)); + if (factory == NULL) + return -ENOMEM; + + data = pw_factory_get_user_data(factory); + data->this = factory; + data->module = module; + data->properties = properties; + + pw_log_debug("module %p: new", module); + + pw_factory_set_implementation(factory, + &impl_factory, + data); + + pw_protocol_native_ext_client_device_init(core); + + pw_factory_register(factory, NULL, pw_module_get_global(module), NULL); + + data->export_spadevice.type = SPA_TYPE_INTERFACE_Device; + data->export_spadevice.func = pw_remote_spa_device_export; + pw_core_register_export_type(core, &data->export_spadevice); + + pw_module_add_listener(module, &data->module_listener, &module_events, data); + + pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; +} + +SPA_EXPORT +int pipewire__module_init(struct pw_module *module, const char *args) +{ + return module_init(module, NULL); +} diff --git a/src/modules/module-client-device/protocol-native.c b/src/modules/module-client-device/protocol-native.c new file mode 100644 index 000000000..1e9db43d3 --- /dev/null +++ b/src/modules/module-client-device/protocol-native.c @@ -0,0 +1,520 @@ +/* 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 + +#if 0 +static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict) +{ + uint32_t i, n_items; + struct spa_pod_frame f; + + n_items = dict ? dict->n_items : 0; + + spa_pod_builder_push_struct(b, &f); + spa_pod_builder_int(b, n_items); + for (i = 0; i < n_items; i++) { + spa_pod_builder_string(b, dict->items[i].key); + spa_pod_builder_string(b, dict->items[i].value); + } + spa_pod_builder_pop(b, &f); +} +#endif + +static int device_marshal_add_listener(void *object, + struct spa_hook *listener, + const struct spa_device_events *events, + void *data) +{ + return -ENOTSUP; +} + +static int device_demarshal_add_listener(void *object, + const struct pw_protocol_native_message *msg) +{ + return -ENOTSUP; +} + +static int device_marshal_enum_params(void *object, int seq, + uint32_t id, uint32_t index, uint32_t max, + const struct spa_pod *filter) +{ + struct pw_protocol_native_message *msg; + struct pw_resource *resource = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + + b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_ENUM_PARAMS, &msg); + + spa_pod_builder_push_struct(b, &f[0]); + spa_pod_builder_add(b, + SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)), + SPA_POD_Id(id), + SPA_POD_Int(index), + SPA_POD_Int(max), + SPA_POD_Pod(filter), NULL); + spa_pod_builder_pop(b, &f[0]); + + return pw_protocol_native_end_resource(resource, b); +} + +static int device_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg) +{ + struct pw_proxy *proxy = object; + struct spa_pod_parser prs; + uint32_t id, index, max; + int seq; + struct spa_pod *filter; + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_get_struct(&prs, + SPA_POD_Int(&seq), + SPA_POD_Id(&id), + SPA_POD_Int(&index), + SPA_POD_Int(&max), + SPA_POD_Pod(&filter)) < 0) + return -EINVAL; + + pw_proxy_notify(proxy, struct spa_device_methods, enum_params, 0, + seq, id, index, max, filter); + return 0; +} + +static int device_marshal_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct pw_resource *resource = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + + b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SET_PARAM, NULL); + + spa_pod_builder_push_struct(b, &f[0]); + spa_pod_builder_add(b, + SPA_POD_Id(id), + SPA_POD_Int(flags), + SPA_POD_Pod(param), NULL); + spa_pod_builder_pop(b, &f[0]); + + return pw_protocol_native_end_resource(resource, b); +} + +static int device_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg) +{ + struct pw_proxy *proxy = object; + struct spa_pod_parser prs; + uint32_t id, flags; + struct spa_pod *param; + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_get_struct(&prs, + SPA_POD_Id(&id), + SPA_POD_Int(&flags), + SPA_POD_Pod(¶m)) < 0) + return -EINVAL; + + pw_proxy_notify(proxy, struct spa_device_methods, set_param, 0, + id, flags, param); + return 0; +} + +static void device_marshal_info(void *object, + const struct spa_device_info *info) +{ + struct pw_proxy *proxy = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + uint32_t i, n_items; + + b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_INFO, NULL); + + spa_pod_builder_push_struct(b, &f[0]); + if (info) { + uint64_t change_mask = info->change_mask; + + change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS | + SPA_DEVICE_CHANGE_MASK_PROPS | + SPA_DEVICE_CHANGE_MASK_PARAMS; + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_push_struct(b, &f[1]); + spa_pod_builder_add(b, + SPA_POD_Long(change_mask), + SPA_POD_Long(info->flags), + SPA_POD_Int(n_items), NULL); + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_String(info->props->items[i].key), + SPA_POD_String(info->props->items[i].value), NULL); + } + spa_pod_builder_add(b, + SPA_POD_Int(info->n_params), NULL); + for (i = 0; i < info->n_params; i++) { + spa_pod_builder_add(b, + SPA_POD_Id(info->params[i].id), + SPA_POD_Int(info->params[i].flags), NULL); + } + spa_pod_builder_pop(b, &f[1]); + } else { + spa_pod_builder_add(b, + SPA_POD_Pod(NULL), NULL); + } + spa_pod_builder_pop(b, &f[0]); + + pw_protocol_native_end_proxy(proxy, b); +} + +static int device_demarshal_info(void *object, + const struct pw_protocol_native_message *msg) +{ + struct pw_resource *resource = object; + struct spa_pod_parser prs; + struct spa_pod *ipod; + struct spa_device_info info = SPA_DEVICE_INFO_INIT(), *infop; + struct spa_dict props; + uint32_t i; + + spa_pod_parser_init(&prs, msg->data, msg->size); + + if (spa_pod_parser_get_struct(&prs, + SPA_POD_PodStruct(&ipod)) < 0) + return -EINVAL; + + if (ipod) { + struct spa_pod_parser p2; + struct spa_pod_frame f2; + infop = &info; + + spa_pod_parser_pod(&p2, ipod); + if (spa_pod_parser_push_struct(&p2, &f2) < 0 || + spa_pod_parser_get(&p2, + SPA_POD_Long(&info.change_mask), + SPA_POD_Long(&info.flags), + SPA_POD_Int(&props.n_items), NULL) < 0) + return -EINVAL; + + info.change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS | + SPA_DEVICE_CHANGE_MASK_PROPS | + SPA_DEVICE_CHANGE_MASK_PARAMS; + + if (props.n_items > 0) { + info.props = &props; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (spa_pod_parser_get(&p2, + SPA_POD_String(&props.items[i].key), + SPA_POD_String(&props.items[i].value), NULL) < 0) + return -EINVAL; + } + } + if (spa_pod_parser_get(&p2, + SPA_POD_Int(&info.n_params), NULL) < 0) + return -EINVAL; + + if (info.n_params > 0) { + info.params = alloca(info.n_params * sizeof(struct spa_param_info)); + for (i = 0; i < info.n_params; i++) { + if (spa_pod_parser_get(&p2, + SPA_POD_Id(&info.params[i].id), + SPA_POD_Int(&info.params[i].flags), NULL) < 0) + return -EINVAL; + } + } + } + else { + infop = NULL; + } + pw_resource_do(resource, struct spa_device_events, info, 0, infop); + return 0; +} + +static void device_marshal_result(void *object, + int seq, int res, uint32_t type, const void *result) +{ + struct pw_proxy *proxy = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + + b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_RESULT, NULL); + spa_pod_builder_push_struct(b, &f[0]); + spa_pod_builder_add(b, + SPA_POD_Int(seq), + SPA_POD_Int(res), + SPA_POD_Id(type), + NULL); + + switch (type) { + case SPA_RESULT_TYPE_DEVICE_PARAMS: + { + const struct spa_result_device_params *r = result; + spa_pod_builder_add(b, + SPA_POD_Id(r->id), + SPA_POD_Int(r->index), + SPA_POD_Int(r->next), + SPA_POD_Pod(r->param), + NULL); + break; + } + default: + break; + } + + spa_pod_builder_pop(b, &f[0]); + + pw_protocol_native_end_proxy(proxy, b); +} + +static int device_demarshal_result(void *object, + const struct pw_protocol_native_message *msg) +{ + struct pw_resource *resource = object; + struct spa_pod_parser prs; + struct spa_pod_frame f[1]; + int seq, res; + uint32_t type; + const void *result; + struct spa_result_device_params params; + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || + spa_pod_parser_get(&prs, + SPA_POD_Int(&seq), + SPA_POD_Int(&res), + SPA_POD_Id(&type), + NULL) < 0) + return -EINVAL; + + switch (type) { + case SPA_RESULT_TYPE_DEVICE_PARAMS: + if (spa_pod_parser_get(&prs, + SPA_POD_Id(¶ms.id), + SPA_POD_Int(¶ms.index), + SPA_POD_Int(¶ms.next), + SPA_POD_PodObject(¶ms.param), + NULL) < 0) + return -EINVAL; + + result = ¶ms; + break; + + default: + result = NULL; + break; + } + + pw_resource_do(resource, struct spa_device_events, result, 0, seq, res, type, result); + return 0; +} + +static void device_marshal_event(void *object, const struct spa_event *event) +{ + struct pw_proxy *proxy = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + + b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_RESULT, NULL); + spa_pod_builder_push_struct(b, &f[0]); + spa_pod_builder_add(b, + SPA_POD_Pod(event), + NULL); + spa_pod_builder_pop(b, &f[0]); + + pw_protocol_native_end_proxy(proxy, b); +} + +static int device_demarshal_event(void *object, + const struct pw_protocol_native_message *msg) +{ + struct pw_resource *resource = object; + struct spa_pod_parser prs; + struct spa_event *event; + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_get_struct(&prs, + SPA_POD_PodObject(&event)) < 0) + return -EINVAL; + + pw_resource_do(resource, struct spa_device_events, event, 0, event); + return 0; +} + +static void device_marshal_object_info(void *object, uint32_t id, + const struct spa_device_object_info *info) +{ + struct pw_proxy *proxy = object; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + uint32_t i, n_items; + + b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_OBJECT_INFO, NULL); + spa_pod_builder_push_struct(b, &f[0]); + spa_pod_builder_add(b, + SPA_POD_Int(id), + NULL); + if (info) { + uint64_t change_mask = info->change_mask; + + change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | + SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_push_struct(b, &f[1]); + spa_pod_builder_add(b, + SPA_POD_Id(info->type), + SPA_POD_Long(change_mask), + SPA_POD_Long(info->flags), + SPA_POD_Int(n_items), NULL); + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_String(info->props->items[i].key), + SPA_POD_String(info->props->items[i].value), NULL); + } + spa_pod_builder_pop(b, &f[1]); + } else { + spa_pod_builder_add(b, + SPA_POD_Pod(NULL), NULL); + } + spa_pod_builder_pop(b, &f[0]); + + pw_protocol_native_end_proxy(proxy, b); +} + +static int device_demarshal_object_info(void *object, + const struct pw_protocol_native_message *msg) +{ + struct pw_resource *resource = object; + struct spa_pod_parser prs; + struct spa_device_object_info info = SPA_DEVICE_OBJECT_INFO_INIT(), *infop; + struct spa_pod *ipod; + struct spa_dict props; + uint32_t i, id; + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_get_struct(&prs, + SPA_POD_Int(&id), + SPA_POD_PodStruct(&ipod)) < 0) + return -EINVAL; + + if (ipod) { + struct spa_pod_parser p2; + struct spa_pod_frame f2; + infop = &info; + + spa_pod_parser_pod(&p2, ipod); + if (spa_pod_parser_push_struct(&p2, &f2) < 0 || + spa_pod_parser_get(&p2, + SPA_POD_Id(&info.type), + SPA_POD_Long(&info.change_mask), + SPA_POD_Long(&info.flags), + SPA_POD_Int(&props.n_items), NULL) < 0) + return -EINVAL; + + info.change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | + SPA_DEVICE_CHANGE_MASK_PROPS; + + if (props.n_items > 0) { + info.props = &props; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (spa_pod_parser_get(&p2, + SPA_POD_String(&props.items[i].key), + SPA_POD_String(&props.items[i].value), NULL) < 0) + return -EINVAL; + } + } + } else { + infop = NULL; + } + + pw_resource_do(resource, struct spa_device_events, object_info, 0, id, infop); + return 0; +} + +static const struct spa_device_methods pw_protocol_native_device_method_marshal = { + SPA_VERSION_DEVICE_METHODS, + .add_listener = &device_marshal_add_listener, + .enum_params = &device_marshal_enum_params, + .set_param = &device_marshal_set_param +}; + +static const struct pw_protocol_native_demarshal +pw_protocol_native_device_method_demarshal[SPA_DEVICE_METHOD_NUM] = +{ + [SPA_DEVICE_METHOD_ADD_LISTENER] = { &device_demarshal_add_listener, 0 }, + [SPA_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0 }, + [SPA_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, 0 }, +}; + +static const struct spa_device_events pw_protocol_native_device_event_marshal = { + SPA_VERSION_DEVICE_EVENTS, + .info = &device_marshal_info, + .result = &device_marshal_result, + .event = &device_marshal_event, + .object_info = &device_marshal_object_info, +}; + +static const struct pw_protocol_native_demarshal +pw_protocol_native_device_event_demarshal[SPA_DEVICE_EVENT_NUM] = +{ + [SPA_DEVICE_EVENT_INFO] = { &device_demarshal_info, 0 }, + [SPA_DEVICE_EVENT_RESULT] = { &device_demarshal_result, 0 }, + [SPA_DEVICE_EVENT_EVENT] = { &device_demarshal_event, 0 }, + [SPA_DEVICE_EVENT_OBJECT_INFO] = { &device_demarshal_object_info, 0 }, +}; + +static const struct pw_protocol_marshal pw_protocol_native_client_device_marshal = { + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + SPA_DEVICE_EVENT_NUM, + SPA_DEVICE_METHOD_NUM, + &pw_protocol_native_device_event_marshal, + pw_protocol_native_device_event_demarshal, + &pw_protocol_native_device_method_marshal, + pw_protocol_native_device_method_demarshal, +}; + +struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_core *core) +{ + struct pw_protocol *protocol; + + protocol = pw_core_find_protocol(core, PW_TYPE_INFO_PROTOCOL_Native); + + if (protocol == NULL) + return NULL; + + pw_protocol_add_marshal(protocol, &pw_protocol_native_client_device_marshal); + + return protocol; +} diff --git a/src/modules/module-client-device/proxy-device.c b/src/modules/module-client-device/proxy-device.c new file mode 100644 index 000000000..98b06a3e7 --- /dev/null +++ b/src/modules/module-client-device/proxy-device.c @@ -0,0 +1,160 @@ +/* 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 "pipewire/pipewire.h" +#include "pipewire/private.h" + +struct device_data { + struct pw_remote *remote; + struct pw_core *core; + + struct spa_device *device; + struct spa_hook device_listener; + struct spa_hook device_methods; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; +}; + +#define pw_device_proxy_event(o,method,version,...) \ +({ \ + int _res = -ENOTSUP; \ + struct spa_interface *_p = o; \ + spa_interface_call_res(&_p->iface, \ + struct pw_core_proxy_methods, _res, \ + method, version, ##__VA_ARGS__); \ + _res; \ +}) + +static void device_event_info(void *_data, const struct spa_device_info *info) +{ + struct device_data *data = _data; + struct spa_interface *iface = (struct spa_interface*)data->proxy; + pw_log_debug("%p", data); + spa_interface_call(iface, struct spa_device_events, info, 0, info); +} + +static void device_event_result(void *_data, int seq, int res, uint32_t type, const void *result) +{ + struct device_data *data = _data; + struct spa_interface *iface = (struct spa_interface*)data->proxy; + pw_log_debug("%p", data); + spa_interface_call(iface, struct spa_device_events, result, 0, seq, res, type, result); +} + +static void device_event_event(void *_data, const struct spa_event *event) +{ + struct device_data *data = _data; + struct spa_interface *iface = (struct spa_interface*)data->proxy; + pw_log_debug("%p", data); + spa_interface_call(iface, struct spa_device_events, event, 0, event); +} + +static void device_event_object_info(void *_data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct device_data *data = _data; + struct spa_interface *iface = (struct spa_interface*)data->proxy; + pw_log_debug("%p", data); + spa_interface_call(iface, struct spa_device_events, object_info, 0, id, info); +} + +static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = device_event_info, + .result = device_event_result, + .event = device_event_event, + .object_info = device_event_object_info, +}; + +static int device_method_enum_params(void *object, int seq, + uint32_t id, uint32_t index, uint32_t max, + const struct spa_pod *filter) +{ + struct device_data *data = object; + pw_log_debug("%p", data); + return spa_device_enum_params(data->device, seq, id, index, max, filter); +} + +static int device_method_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct device_data *data = object; + pw_log_debug("%p", data); + return spa_device_set_param(data->device, id, flags, param); +} + +static const struct spa_device_methods device_methods = { + SPA_VERSION_DEVICE_METHODS, + .enum_params = device_method_enum_params, + .set_param = device_method_set_param, +}; + +static void device_proxy_destroy(void *_data) +{ + struct device_data *data = _data; + spa_hook_remove(&data->device_listener); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = device_proxy_destroy, +}; + +struct pw_proxy *pw_remote_spa_device_export(struct pw_remote *remote, + uint32_t type, struct pw_properties *props, void *object) +{ + struct spa_device *device = object; + struct pw_proxy *proxy; + struct device_data *data; + + proxy = pw_core_proxy_create_object(remote->core_proxy, + "client-device", + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &props->dict, + sizeof(struct device_data)); + if (proxy == NULL) + return NULL; + + data = pw_proxy_get_user_data(proxy); + data->remote = remote; + data->device = device; + data->core = pw_remote_get_core(remote); + data->proxy = proxy; + + pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data); + pw_proxy_add_proxy_listener(proxy, &data->device_methods, &device_methods, data); + spa_device_add_listener(device, &data->device_listener, &device_events, data); + + return proxy; +} diff --git a/src/modules/module-client-device/resource-device.c b/src/modules/module-client-device/resource-device.c new file mode 100644 index 000000000..83ea6afe3 --- /dev/null +++ b/src/modules/module-client-device/resource-device.c @@ -0,0 +1,219 @@ +/* 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "pipewire/private.h" + +struct impl { + struct pw_core *core; + struct pw_device *device; + + struct spa_device impl; + struct spa_hook_list hooks; + + struct pw_resource *resource; + struct spa_hook resource_listener; + + struct pw_global *parent; + unsigned int registered:1; +}; + +#define pw_device_resource(r,m,v,...) \ + pw_resource_notify_res(r,struct spa_device_methods,m,v,__VA_ARGS__) + +#define pw_device_resource_enum_params(r,...) \ + pw_device_resource(r,enum_params,0,__VA_ARGS__) +#define pw_device_resource_set_param(r,...) \ + pw_device_resource(r,set_param,0,__VA_ARGS__) + +static int device_add_listener(void *object, + struct spa_hook *listener, + const struct spa_device_events *events, + void *data) +{ + struct impl *impl = object; + struct spa_hook_list save; + + spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); + pw_log_debug("client-device %p: add listener", impl); + + spa_hook_list_join(&impl->hooks, &save); + + return 0; +} + +static int device_sync(void *object, int seq) +{ + struct impl *impl = object; + + pw_log_debug("client-device %p: sync %d", impl, seq); + + return pw_resource_ping(impl->resource, seq); +} + +static int device_enum_params(void *object, int seq, + uint32_t id, uint32_t index, uint32_t max, + const struct spa_pod *filter) +{ + struct impl *impl = object; + pw_log_debug("client-device %p: enum params", impl); + return pw_device_resource_enum_params(impl->resource, seq, id, index, max, filter); +} + +static int device_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *impl = object; + pw_log_debug("client-device %p: set param", impl); + return pw_device_resource_set_param(impl->resource, id, flags, param); +} + +static const struct spa_device_methods impl_device = { + SPA_VERSION_DEVICE_METHODS, + .add_listener = device_add_listener, + .sync = device_sync, + .enum_params = device_enum_params, + .set_param = device_set_param, +}; + +static void device_info(void *data, const struct spa_device_info *info) +{ + struct impl *impl = data; + spa_device_emit_info(&impl->hooks, info); + + if (!impl->registered) { + pw_device_register(impl->device, impl->resource->client, impl->parent, NULL); + impl->registered = true; + } +} + +static void device_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *impl = data; + pw_log_debug("client-device %p: result %d %d %u", impl, seq, res, type); + spa_device_emit_result(&impl->hooks, seq, res, type, result); +} + +static void device_event(void *data, const struct spa_event *event) +{ + struct impl *impl = data; + spa_device_emit_event(&impl->hooks, event); +} + +static void device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct impl *impl = data; + spa_device_emit_object_info(&impl->hooks, id, info); +} + +static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = device_info, + .result = device_result, + .event = device_event, + .object_info = device_object_info, +}; + +static void device_resource_destroy(void *data) +{ + struct impl *impl = data; + struct pw_device *device = impl->device; + + pw_log_debug("client-device %p: destroy", impl); + + impl->resource = NULL; + spa_hook_remove(&impl->resource_listener); + + pw_device_destroy(device); +} + +static void device_resource_pong(void *data, int seq) +{ + struct impl *impl = data; + spa_device_emit_result(&impl->hooks, seq, 0, 0, NULL); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = device_resource_destroy, + .pong = device_resource_pong, +}; + +struct pw_device *pw_client_device_new(struct pw_resource *resource, + struct pw_global *parent, + struct pw_properties *properties) +{ + struct impl *impl; + struct pw_device *device; + struct pw_client *client = pw_resource_get_client(resource); + struct pw_core *core = pw_client_get_core(client); + const char *name; + + if ((name = pw_properties_get(properties, PW_KEY_DEVICE_NAME)) == NULL) + name = "client-device"; + + device = pw_device_new(core, name, properties, sizeof(struct impl)); + if (device == NULL) + return NULL; + + impl = pw_device_get_user_data(device); + impl->device = device; + impl->core = core; + impl->resource = resource; + impl->parent = parent; + + impl->impl.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, impl); + spa_hook_list_init(&impl->hooks); + pw_device_set_implementation(device, &impl->impl); + + pw_resource_add_listener(impl->resource, + &impl->resource_listener, + &resource_events, + impl); + pw_resource_set_implementation(impl->resource, + &device_events, + impl); + + + return device; +} diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build index f581d52fc..255588d87 100644 --- a/src/modules/spa/meson.build +++ b/src/modules/spa/meson.build @@ -38,3 +38,12 @@ pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-fact install_dir : modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep], ) + +pipewire_module_spa_device_factory = shared_library('pipewire-module-spa-device-factory', + [ 'module-device-factory.c', 'spa-device.c' ], + c_args : pipewire_module_spa_c_args, + include_directories : [configinc, spa_inc], + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) diff --git a/src/modules/spa/module-device-factory.c b/src/modules/spa/module-device-factory.c new file mode 100644 index 000000000..8551746a6 --- /dev/null +++ b/src/modules/spa/module-device-factory.c @@ -0,0 +1,212 @@ +/* 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 +#include +#include +#include + +#include "config.h" + +#include "pipewire/pipewire.h" + +#include "spa-device.h" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, + { PW_KEY_MODULE_DESCRIPTION, "Provide a factory to make SPA devices" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct factory_data { + struct pw_core *core; + struct pw_factory *this; + struct pw_properties *properties; + + struct spa_hook factory_listener; + struct spa_hook module_listener; + + struct spa_list device_list; +}; + +struct device_data { + struct spa_list link; + struct pw_device *device; + struct spa_hook device_listener; +}; + +static void device_destroy(void *data) +{ + struct device_data *nd = data; + spa_list_remove(&nd->link); + spa_hook_remove(&nd->device_listener); +} + +static const struct pw_device_events device_events = { + PW_VERSION_DEVICE_EVENTS, + .destroy = device_destroy, +}; + +static void *create_object(void *_data, + struct pw_resource *resource, + uint32_t type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id) +{ + struct factory_data *data = _data; + struct pw_device *device; + const char *lib, *factory_name, *name; + struct device_data *nd; + + if (properties == NULL) + goto no_properties; + + lib = pw_properties_get(properties, "spa.library.name"); + factory_name = pw_properties_get(properties, "spa.factory.name"); + name = pw_properties_get(properties, "name"); + + if (lib == NULL || factory_name == NULL) + goto no_properties; + + if (name == NULL) + name = "spa-device"; + + device = pw_spa_device_load(data->core, + NULL, + pw_factory_get_global(data->this), + lib, + factory_name, + name, + 0, + properties, + sizeof(struct device_data)); + if (device == NULL) + goto no_mem; + + nd = pw_spa_device_get_user_data(device); + nd->device = device; + spa_list_append(&data->device_list, &nd->link); + + pw_device_add_listener(device, &nd->device_listener, &device_events, nd); + + if (resource) + pw_global_bind(pw_device_get_global(device), + pw_resource_get_client(resource), + PW_PERM_RWX, + version, new_id); + + return device; + + no_properties: + pw_log_error("needed properties: spa.library.name= spa.factory.name="); + if (resource) { + pw_resource_error(resource, -EINVAL, + "needed properties: " + "spa.library.name= " + "spa.factory.name="); + } + return NULL; + no_mem: + pw_log_error("can't create device: no memory"); + if (resource) { + pw_resource_error(resource, -ENOMEM, "no memory"); + } + return NULL; +} + +static const struct pw_factory_implementation factory_impl = { + PW_VERSION_FACTORY_IMPLEMENTATION, + .create_object = create_object, +}; + +static void factory_destroy(void *_data) +{ + struct factory_data *data = _data; + struct device_data *nd; + + spa_hook_remove(&data->module_listener); + + spa_list_consume(nd, &data->device_list, link) + pw_device_destroy(nd->device); + + if (data->properties) + pw_properties_free(data->properties); +} + +static const struct pw_factory_events factory_events = { + PW_VERSION_FACTORY_IMPLEMENTATION, + .destroy = factory_destroy, +}; + +static void module_destroy(void *_data) +{ + struct factory_data *data = _data; + pw_factory_destroy(data->this); +} + +static const struct pw_module_events module_events = { + PW_VERSION_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static int module_init(struct pw_module *module, struct pw_properties *properties) +{ + struct pw_core *core = pw_module_get_core(module); + struct pw_factory *factory; + struct factory_data *data; + + factory = pw_factory_new(core, + "spa-device-factory", + PW_TYPE_INTERFACE_Device, + PW_VERSION_DEVICE_PROXY, + NULL, + sizeof(*data)); + if (factory == NULL) + return -ENOMEM; + + data = pw_factory_get_user_data(factory); + data->this = factory; + data->core = core; + data->properties = properties; + spa_list_init(&data->device_list); + + pw_factory_add_listener(factory, &data->factory_listener, &factory_events, data); + pw_factory_set_implementation(factory, &factory_impl, data); + + pw_log_debug("module %p: new", module); + pw_module_add_listener(module, &data->module_listener, &module_events, data); + + pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + pw_factory_register(factory, NULL, pw_module_get_global(module), NULL); + + return 0; +} + +SPA_EXPORT +int pipewire__module_init(struct pw_module *module, const char *args) +{ + return module_init(module, NULL); +}