implement route-settings metadata and notification volume
Make a module that creates a route-settings metadata and provides some helper functions to parse the json fields. Load the route-settings module in the policy. Move the Notification settings to the metadata. Listen for metadata updates and update our state table. Fixes #51
This commit is contained in:

committed by
George Kiagiadakis

parent
8c82265752
commit
a13308bac7
@@ -58,6 +58,17 @@ shared_library(
|
||||
dependencies : [wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
shared_library(
|
||||
'wireplumber-module-route-settings-api',
|
||||
[
|
||||
'module-route-settings-api.c',
|
||||
],
|
||||
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-route-settings-api"'],
|
||||
install : true,
|
||||
install_dir : wireplumber_module_dir,
|
||||
dependencies : [wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
subdir('module-reserve-device')
|
||||
shared_library(
|
||||
'wireplumber-module-reserve-device',
|
||||
|
138
modules/module-route-settings-api.c
Normal file
138
modules/module-route-settings-api.c
Normal file
@@ -0,0 +1,138 @@
|
||||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2021 Collabora Ltd.
|
||||
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <pipewire/keys.h>
|
||||
#include <spa/utils/json.h>
|
||||
|
||||
struct _WpRouteSettingsApi
|
||||
{
|
||||
WpPlugin parent;
|
||||
|
||||
WpImplMetadata *metadata;
|
||||
};
|
||||
|
||||
enum {
|
||||
ACTION_CONVERT,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals[N_SIGNALS] = {0};
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpRouteSettingsApi, wp_route_settings_api,
|
||||
WP, ROUTE_SETTINGS_API, WpPlugin)
|
||||
G_DEFINE_TYPE (WpRouteSettingsApi, wp_route_settings_api, WP_TYPE_PLUGIN)
|
||||
|
||||
static void
|
||||
wp_route_settings_api_init (WpRouteSettingsApi * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
on_metadata_activated (GObject * obj, GAsyncResult * res, gpointer user_data)
|
||||
{
|
||||
WpTransition * transition = WP_TRANSITION (user_data);
|
||||
WpRouteSettingsApi * self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!wp_object_activate_finish (WP_OBJECT (obj), res, &error)) {
|
||||
g_clear_object (&self->metadata);
|
||||
g_prefix_error (&error, "Failed to activate WpImplMetadata: ");
|
||||
wp_transition_return_error (transition, g_steal_pointer (&error));
|
||||
return;
|
||||
}
|
||||
|
||||
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_route_settings_api_enable (WpPlugin * plugin, WpTransition * transition)
|
||||
{
|
||||
WpRouteSettingsApi * self = WP_ROUTE_SETTINGS_API (plugin);
|
||||
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
|
||||
g_return_if_fail (core);
|
||||
|
||||
self->metadata = wp_impl_metadata_new_full (core, "route-settings", NULL);
|
||||
wp_object_activate (WP_OBJECT (self->metadata),
|
||||
WP_OBJECT_FEATURES_ALL, NULL, on_metadata_activated, transition);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_route_settings_api_disable (WpPlugin * plugin)
|
||||
{
|
||||
WpRouteSettingsApi * self = WP_ROUTE_SETTINGS_API (plugin);
|
||||
g_clear_object (&self->metadata);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
wp_route_settings_api_convert (WpRouteSettingsApi * self,
|
||||
const gchar * json, const gchar *field)
|
||||
{
|
||||
struct spa_json it[3];
|
||||
char k[128];
|
||||
|
||||
spa_json_init(&it[0], json, strlen(json));
|
||||
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
|
||||
return NULL;
|
||||
|
||||
while (spa_json_get_string(&it[1], k, sizeof(k)-1) > 0) {
|
||||
int len;
|
||||
const char *value;
|
||||
|
||||
if (strcmp(k, field) != 0)
|
||||
continue;
|
||||
|
||||
if ((len = spa_json_next(&it[1], &value)) <= 0)
|
||||
break;
|
||||
|
||||
if (spa_json_is_null(value, len))
|
||||
return NULL;
|
||||
else if (spa_json_is_array(value, len)) {
|
||||
GString *str;
|
||||
spa_json_enter(&it[1], &it[2]);
|
||||
str = g_string_new("");
|
||||
while ((len = spa_json_next(&it[2], &value)) > 0) {
|
||||
char v[1024];
|
||||
if (len > 1023)
|
||||
continue;
|
||||
spa_json_parse_string(value, len, v);
|
||||
g_string_append_printf(str, "%s;", v);
|
||||
}
|
||||
return g_string_free(str, false);
|
||||
}
|
||||
else
|
||||
return g_strndup(value, len);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_route_settings_api_class_init (WpRouteSettingsApiClass * klass)
|
||||
{
|
||||
WpPluginClass *plugin_class = (WpPluginClass *) klass;
|
||||
|
||||
plugin_class->enable = wp_route_settings_api_enable;
|
||||
plugin_class->disable = wp_route_settings_api_disable;
|
||||
|
||||
signals[ACTION_CONVERT] = g_signal_new_class_handler (
|
||||
"convert", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||
(GCallback) wp_route_settings_api_convert,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_STRING, 2, G_TYPE_STRING, G_TYPE_STRING);
|
||||
}
|
||||
|
||||
WP_PLUGIN_EXPORT gboolean
|
||||
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
|
||||
{
|
||||
wp_plugin_register (g_object_new (wp_route_settings_api_get_type (),
|
||||
"name", "route-settings-api",
|
||||
"core", core,
|
||||
NULL));
|
||||
return TRUE;
|
||||
}
|
@@ -22,6 +22,9 @@ function default_policy.enable()
|
||||
-- API to access default nodes from scripts
|
||||
load_module("default-nodes-api")
|
||||
|
||||
-- API to access volume of streams from scripts
|
||||
load_module("route-settings-api")
|
||||
|
||||
-- API to access mixer controls, needed for volume ducking
|
||||
load_module("mixer-api")
|
||||
|
||||
|
@@ -12,6 +12,8 @@
|
||||
state = State("restore-stream")
|
||||
state_table = state:load()
|
||||
|
||||
route_settings = Plugin.find("route-settings-api")
|
||||
|
||||
-- simple serializer {"foo", "bar"} -> "foo;bar;"
|
||||
function serializeArray(a)
|
||||
local str = ""
|
||||
@@ -22,7 +24,7 @@ function serializeArray(a)
|
||||
end
|
||||
|
||||
-- simple deserializer "foo;bar;" -> {"foo", "bar"}
|
||||
function parseArray(str, convert_value)
|
||||
function parseArray(str, convert_value, with_type)
|
||||
local array = {}
|
||||
local pos = 1
|
||||
while true do
|
||||
@@ -36,6 +38,9 @@ function parseArray(str, convert_value)
|
||||
break
|
||||
end
|
||||
end
|
||||
if with_type then
|
||||
array["pod_type"] = "Array"
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
@@ -139,6 +144,76 @@ function restoreTarget(node, target_name)
|
||||
end
|
||||
end
|
||||
|
||||
function jsonTable(val, name)
|
||||
local tmp = ""
|
||||
local count = 0
|
||||
|
||||
if name then tmp = tmp .. string.format("%q", name) .. ": " end
|
||||
|
||||
if type(val) == "table" then
|
||||
if val["pod_type"] == "Array" then
|
||||
tmp = tmp .. "["
|
||||
for _, v in ipairs(val) do
|
||||
if count > 0 then tmp = tmp .. "," end
|
||||
tmp = tmp .. jsonTable(v)
|
||||
count = count + 1
|
||||
end
|
||||
tmp = tmp .. "]"
|
||||
else
|
||||
tmp = tmp .. "{"
|
||||
for k, v in pairs(val) do
|
||||
if count > 0 then tmp = tmp .. "," end
|
||||
tmp = tmp .. jsonTable(v, k)
|
||||
count = count + 1
|
||||
end
|
||||
tmp = tmp .. "}"
|
||||
end
|
||||
elseif type(val) == "number" then
|
||||
tmp = tmp .. tostring(val)
|
||||
elseif type(val) == "string" then
|
||||
tmp = tmp .. string.format("%q", val)
|
||||
elseif type(val) == "boolean" then
|
||||
tmp = tmp .. (val and "true" or "false")
|
||||
else
|
||||
tmp = tmp .. "\"[type:" .. type(val) .. "]\""
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
|
||||
function moveToMetadata(key_base, metadata)
|
||||
local route_table = { }
|
||||
local count = 0
|
||||
|
||||
key = "restore.stream." .. key_base
|
||||
key = string.gsub(key, ":", ".", 1);
|
||||
|
||||
local str = state_table[key_base .. ":volume"]
|
||||
if str then
|
||||
route_table["volume"] = tonumber(str)
|
||||
count = count + 1;
|
||||
end
|
||||
local str = state_table[key_base .. ":mute"]
|
||||
if str then
|
||||
route_table["mute"] = str == "true"
|
||||
count = count + 1;
|
||||
end
|
||||
local str = state_table[key_base .. ":channelVolumes"]
|
||||
if str then
|
||||
route_table["volumes"] = parseArray(str, tonumber, true)
|
||||
count = count + 1;
|
||||
end
|
||||
local str = state_table[key_base .. ":channelMap"]
|
||||
if str then
|
||||
route_table["channels"] = parseArray(str, nil, true)
|
||||
count = count + 1;
|
||||
end
|
||||
|
||||
if count > 0 then
|
||||
metadata:set(0, key, "Spa:String:JSON", jsonTable(route_table));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function saveStream(node)
|
||||
local key_base = findSuitableKey(node)
|
||||
if not key_base then
|
||||
@@ -240,6 +315,55 @@ metadata_om:connect("object-added", function (om, metadata)
|
||||
end)
|
||||
metadata_om:activate()
|
||||
|
||||
function handleRouteSettings(subject, key, type, value)
|
||||
if type ~= "Spa:String:JSON" then
|
||||
return
|
||||
end
|
||||
if string.find(key, "^restore.stream.") == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local key_base = string.sub(key, string.len("restore.stream.") + 1)
|
||||
local str;
|
||||
|
||||
key_base = string.gsub(key_base, "%.", ":", 1);
|
||||
|
||||
str = route_settings:call("convert", value, "volume");
|
||||
if str then
|
||||
state_table[key_base .. ":volume"] = str
|
||||
end
|
||||
str = route_settings:call("convert", value, "mute");
|
||||
if str then
|
||||
state_table[key_base .. ":mute"] = str
|
||||
end
|
||||
str = route_settings:call("convert", value, "channels");
|
||||
if str then
|
||||
state_table[key_base .. ":channelMap"] = str
|
||||
end
|
||||
str = route_settings:call("convert", value, "volumes");
|
||||
if str then
|
||||
state_table[key_base .. ":channelVolumes"] = str
|
||||
end
|
||||
|
||||
storeAfterTimeout()
|
||||
end
|
||||
|
||||
rs_metadata_om = ObjectManager {
|
||||
Interest {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "route-settings" },
|
||||
}
|
||||
}
|
||||
rs_metadata_om:connect("object-added", function (om, metadata)
|
||||
-- copy state into the metadata
|
||||
moveToMetadata("Output/Audio:media.role:Notification", metadata)
|
||||
-- watch for changes
|
||||
metadata:connect("changed", function (m, subject, key, type, value)
|
||||
handleRouteSettings(subject, key, type, value)
|
||||
end)
|
||||
end)
|
||||
rs_metadata_om:activate()
|
||||
|
||||
allnodes_om = ObjectManager { Interest { type = "node" } }
|
||||
allnodes_om:activate()
|
||||
|
||||
|
Reference in New Issue
Block a user