From 0ac2947aed039236bb2409336d7e87d0ef09756a Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 17 Oct 2023 18:12:18 +0300 Subject: [PATCH] scripts: add new sm-objects script This allows loading objects on demand by adding entries on the "sm-objects" metadata object. It is useful to dynamically load pipewire modules such as loopbacks or network modules without having to start a new pipewire process with a hardcoded config file. It is also useful to load new metadata objects in order to implement the singleton metatada concept as discussed in pipewire!1742 This may be expanded in the future to be able to load other types of objects. The key name, combined with the subject, is considered a unique id for this instance of the object. The value should be a json object with a 'type' specifying the type of object, together with a 'name' and 'args' --- src/config/main.lua.d/90-enable-all.lua | 3 + src/scripts/sm-objects.lua | 103 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/scripts/sm-objects.lua diff --git a/src/config/main.lua.d/90-enable-all.lua b/src/config/main.lua.d/90-enable-all.lua index 1aa194df..37d790ea 100644 --- a/src/config/main.lua.d/90-enable-all.lua +++ b/src/config/main.lua.d/90-enable-all.lua @@ -21,3 +21,6 @@ load_script("intended-roles.lua") -- Automatically suspends idle nodes after 3 seconds load_script("suspend-node.lua") + +-- Allows loading objects on demand via metadata +load_script("sm-objects.lua") diff --git a/src/scripts/sm-objects.lua b/src/scripts/sm-objects.lua new file mode 100644 index 00000000..c22b29d7 --- /dev/null +++ b/src/scripts/sm-objects.lua @@ -0,0 +1,103 @@ +-- WirePlumber +-- +-- Copyright © 2023 Collabora Ltd. +-- @author George Kiagiadakis +-- +-- SPDX-License-Identifier: MIT +-- +-- The script exposes a metadata object named "sm-objects" that clients can +-- use to load objects into the WirePlumber daemon process. The objects are +-- loaded as soon as the metadata is set and are destroyed when the metadata +-- is cleared. +-- +-- To load an object, a client needs to set a metadata entry with: +-- +-- * subject: +-- The ID of the owner of the object; you can use 0 here, but the +-- idea is to be able to restrict which clients can change and/or +-- delete these objects by using IDs of other objects appropriately +-- +-- * key: "" +-- This is the name that will be used to identify the object. +-- If an object with the same name already exists, it will be destroyed. +-- Note that the keys are unique per subject, so you can have multiple +-- objects with the same name as long as they are owned by different subjects. +-- +-- * type: "Spa:String:JSON" +-- +-- * value: "{ type = , +-- name = , +-- args = { ...object arguments... } }" +-- The object type can be one of the following: +-- - "pw-module": loads a pipewire module: `name` and `args` are interpreted +-- just like a module entry in pipewire.conf +-- - "metadata": loads a metadata object with `metadata.name` = `name` +-- and any additional properties provided in `args` +-- + +on_demand_objects = {} + +object_constructors = { + ["pw-module"] = LocalModule, + ["metadata"] = function (name, args) + local m = ImplMetadata (name, args) + m:activate (Features.ALL, function (m, e) + if e then + Log.warning ("failed to activate on-demand metadata `" .. name .. "`: " .. tostring (e)) + end + end) + return m + end +} + +function handle_metadata_changed (m, subject, key, type, value) + -- destroy all objects when metadata is cleared + if not key then + on_demand_objects = {} + return + end + + local object_id = key .. "@" .. tostring(subject) + + -- destroy existing object instance, if needed + if on_demand_objects[object_id] then + Log.debug("destroy on-demand object: " .. object_id) + on_demand_objects[object_id] = nil + end + + if value then + local json = Json.Raw(value) + if not json:is_object() then + Log.warning("loading '".. object_id .. "' failed: expected JSON object, got: '" .. value .. "'") + return + end + + local obj = json:parse(1) + if not obj.type then + Log.warning("loading '".. object_id .. "' failed: no object type specified") + return + end + if not obj.name then + Log.warning("loading '".. object_id .. "' failed: no object name specified") + return + end + + local constructor = object_constructors[obj.type] + if not constructor then + Log.warning("loading '".. object_id .. "' failed: unknown object type: " .. obj.type) + return + end + + Log.info("load on-demand object: " .. object_id .. " -> " .. obj.name) + on_demand_objects[object_id] = constructor(obj.name, obj.args) + end +end + +objects_metadata = ImplMetadata ("sm-objects") +objects_metadata:activate (Features.ALL, function (m, e) + if e then + Log.warning ("failed to activate the sm-objects metadata: " .. tostring (e)) + else + m:connect("changed", handle_metadata_changed) + end +end)