config: port device-activation module to policy-device.profile.lua

Because all wireplumber policies are in Lua.
This commit is contained in:
Julian Bouzas
2022-01-26 16:46:52 -05:00
parent 0e3505c740
commit b3446efa16
7 changed files with 199 additions and 407 deletions

View File

@@ -25,17 +25,6 @@ shared_library(
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-device-activation',
[
'module-device-activation.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-device-activation"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-default-nodes',
[

View File

@@ -30,8 +30,8 @@ struct _WpDefaultProfileClass
{
WpPluginClass parent_class;
void (*get_profile) (WpDefaultProfile *self, WpPipewireObject *device,
const char **curr_profile);
gchar *(*get_profile) (WpDefaultProfile *self,
WpPipewireObject *device);
};
typedef struct _WpDefaultProfilePrivate WpDefaultProfilePrivate;
@@ -112,24 +112,25 @@ timeout_save_profiles (WpDefaultProfile *self, guint ms)
G_OBJECT (self)));
}
static void
static gchar *
wp_default_profile_get_profile (WpDefaultProfile *self,
WpPipewireObject *device, const gchar **curr_profile)
WpPipewireObject *device)
{
WpDefaultProfilePrivate *priv =
wp_default_profile_get_instance_private (self);
const gchar *dev_name = NULL;
const gchar *profile_name = NULL;
g_return_if_fail (device);
g_return_if_fail (curr_profile);
g_return_if_fail (priv->profiles);
g_return_val_if_fail (device, NULL);
g_return_val_if_fail (priv->profiles, NULL);
/* Get the device name */
dev_name = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
g_return_if_fail (dev_name);
g_return_val_if_fail (dev_name, NULL);
/* Get the profile */
*curr_profile = wp_properties_get (priv->profiles, dev_name);
profile_name = wp_properties_get (priv->profiles, dev_name);
return profile_name ? g_strdup (profile_name) : NULL;
}
static void
@@ -300,7 +301,7 @@ wp_default_profile_class_init (WpDefaultProfileClass * klass)
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (WpDefaultProfileClass, get_profile), NULL, NULL,
NULL, G_TYPE_NONE, 2, WP_TYPE_DEVICE, G_TYPE_POINTER);
NULL, G_TYPE_STRING, 1, WP_TYPE_DEVICE);
}
WP_PLUGIN_EXPORT gboolean

View File

@@ -1,382 +0,0 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/utils/keys.h>
#include <spa/utils/names.h>
G_DEFINE_QUARK (wp-module-device-activation-best-profile, best_profile);
G_DEFINE_QUARK (wp-module-device-activation-active-profile, active_profile);
struct _WpDeviceActivation
{
WpPlugin parent;
GWeakRef default_profile;
WpObjectManager *plugins_om;
WpObjectManager *devices_om;
};
G_DECLARE_FINAL_TYPE (WpDeviceActivation, wp_device_activation, WP,
DEVICE_ACTIVATION, WpPlugin)
G_DEFINE_TYPE (WpDeviceActivation, wp_device_activation, WP_TYPE_PLUGIN)
static void
set_device_profile (WpDeviceActivation *self, WpPipewireObject *device, gint index)
{
const gchar *dn = wp_pipewire_object_get_property (device, PW_KEY_DEVICE_NAME);
gpointer active_ptr = NULL;
g_return_if_fail (device);
/* Make sure the profile we want to set is not active */
active_ptr = g_object_get_qdata (G_OBJECT (device), active_profile_quark ());
if (active_ptr && GPOINTER_TO_INT (active_ptr) - 1 == index) {
wp_info_object (self, "profile %d is already active in %s", index, dn);
return;
}
/* Set profile */
wp_pipewire_object_set_param (device, "Profile", 0,
wp_spa_pod_new_object (
"Spa:Pod:Object:Param:Profile", "Profile",
"index", "i", index,
NULL));
wp_info_object (self, "profile %d set on device %s", index, dn);
}
static gint
find_active_profile (WpPipewireObject *proxy, gboolean *off)
{
g_autoptr (WpIterator) profiles = NULL;
g_auto (GValue) item = G_VALUE_INIT;
gint idx = -1, prio = 0;
guint32 avail = SPA_PARAM_AVAILABILITY_unknown;
const gchar *name;
/* Get current profile */
profiles = wp_pipewire_object_enum_params_sync (proxy, "Profile", NULL);
if (!profiles)
return idx;
for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &idx,
"name", "s", &name,
"priority", "?i", &prio,
"available", "?I", &avail,
NULL))
continue;
g_value_unset (&item);
break;
}
if (off)
*off = idx >= 0 && g_strcmp0 (name, "off") == 0;
return idx;
}
static gint
find_best_profile (WpIterator *profiles)
{
g_auto (GValue) item = G_VALUE_INIT;
gint best_idx = -1, unk_idx = -1, off_idx = -1;
gint best_prio = 0, unk_prio = 0;
wp_iterator_reset (profiles);
for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
gint idx, prio = 0;
guint32 avail = SPA_PARAM_AVAILABILITY_unknown;
const gchar *name;
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &idx,
"name", "s", &name,
"priority", "?i", &prio,
"available", "?I", &avail,
NULL)) {
continue;
}
if (g_strcmp0 (name, "pro-audio") == 0)
continue;
if (g_strcmp0 (name, "off") == 0) {
off_idx = idx;
} else if (avail == SPA_PARAM_AVAILABILITY_yes) {
if (best_idx == -1 || prio > best_prio) {
best_prio = prio;
best_idx = idx;
}
} else if (avail != SPA_PARAM_AVAILABILITY_no) {
if (unk_idx == -1 || prio > unk_prio) {
unk_prio = prio;
unk_idx = idx;
}
}
}
if (best_idx != -1)
return best_idx;
else if (unk_idx != -1)
return unk_idx;
else if (off_idx != -1)
return off_idx;
return -1;
}
static gint
find_default_profile (WpDeviceActivation *self, WpPipewireObject *proxy,
WpIterator *profiles, gboolean *available)
{
g_autoptr (WpPlugin) dp = g_weak_ref_get (&self->default_profile);
g_auto (GValue) item = G_VALUE_INIT;
const gchar *def_name = NULL;
/* Get the default profile name if default-profile module is loaded */
if (dp)
g_signal_emit_by_name (dp, "get-profile", WP_DEVICE (proxy), &def_name);
if (!def_name)
return -1;
/* Find the best profile index */
wp_iterator_reset (profiles);
for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
gint idx = -1, prio = 0;
guint32 avail = SPA_PARAM_AVAILABILITY_unknown;
const gchar *name = NULL;
/* Parse */
if (!wp_spa_pod_get_object (pod, NULL,
"index", "i", &idx,
"name", "s", &name,
"priority", "?i", &prio,
"available", "?I", &avail,
NULL))
continue;
/* Check if the profile name is the default one */
if (g_strcmp0 (def_name, name) == 0) {
if (available)
*available = avail;
g_value_unset (&item);
return idx;
}
}
return -1;
}
static gint
handle_active_profile (WpDeviceActivation *self, WpPipewireObject *proxy,
WpIterator *profiles, gboolean *changed, gboolean *off)
{
const gchar *dn = wp_pipewire_object_get_property (proxy, PW_KEY_DEVICE_NAME);
gpointer active_ptr = NULL;
gint new_active = -1;
gint local_changed = FALSE;
/* Find the new active profile */
new_active = find_active_profile (proxy, off);
if (new_active < 0) {
wp_info_object (self, "cannot find active profile in %s", dn);
return new_active;
}
/* Update active profile if changed */
active_ptr = g_object_get_qdata (G_OBJECT (proxy), active_profile_quark ());
local_changed = !active_ptr || GPOINTER_TO_INT (active_ptr) - 1 != new_active;
if (local_changed) {
wp_info_object (self, "active profile changed to %d in %s", new_active, dn);
g_object_set_qdata (G_OBJECT (proxy), active_profile_quark (),
GINT_TO_POINTER (new_active + 1));
}
if (changed)
*changed = local_changed;
return new_active;
}
static gint
handle_best_profile (WpDeviceActivation *self, WpPipewireObject *proxy,
WpIterator *profiles, gboolean *changed)
{
const gchar *dn = wp_pipewire_object_get_property (proxy, PW_KEY_DEVICE_NAME);
gpointer best_ptr = NULL;
gint new_best = -1;
gboolean local_changed = FALSE;
/* Get the new best profile index */
new_best = find_best_profile (profiles);
if (new_best < 0) {
wp_info_object (self, "cannot find best profile in %s", dn);
return new_best;
}
/* Update best profile if changed */
best_ptr = g_object_get_qdata (G_OBJECT (proxy), best_profile_quark ());
local_changed = !best_ptr || GPOINTER_TO_INT (best_ptr) - 1 != new_best;
if (local_changed) {
wp_info_object (self, "best profile changed to %d in %s", new_best, dn);
g_object_set_qdata (G_OBJECT (proxy), best_profile_quark (),
GINT_TO_POINTER (new_best + 1));
}
if (changed)
*changed = local_changed;
return new_best;
}
static void
handle_enum_profiles (WpDeviceActivation *self, WpPipewireObject *proxy,
WpIterator *profiles)
{
const gchar *dn = wp_pipewire_object_get_property (proxy, PW_KEY_DEVICE_NAME);
gint active_idx = FALSE, best_idx = FALSE;
gboolean active_changed = FALSE, best_changed = FALSE, active_off = FALSE;
/* Set default device if active profile changed to off */
active_idx = handle_active_profile (self, proxy, profiles, &active_changed,
&active_off);
if (active_idx >= 0 && active_changed && active_off) {
gboolean default_avail = FALSE;
gint default_idx = -1;
default_idx = find_default_profile (self, proxy, profiles, &default_avail);
if (default_idx >= 0) {
if (default_avail == SPA_PARAM_AVAILABILITY_no) {
wp_info_object (self, "default profile %d unavailable for %s",
default_idx, dn);
} else {
wp_info_object (self, "found default profile %d for %s", default_idx,
dn);
set_device_profile (self, proxy, default_idx);
return;
}
} else {
wp_info_object (self, "cannot find default profile for %s", dn);
}
}
/* Otherwise just set the best profile if changed */
best_idx = handle_best_profile (self, proxy, profiles, &best_changed);
if (best_idx >= 0 && best_changed)
set_device_profile (self, proxy, best_idx);
else if (best_idx >= 0)
wp_info_object (self, "best profile %d already set in %s", best_idx, dn);
else
wp_info_object (self, "best profile not found in %s", dn);
}
static void
on_device_params_changed (WpPipewireObject * proxy, const gchar *param_name,
WpDeviceActivation *self)
{
if (g_strcmp0 (param_name, "EnumProfile") == 0) {
g_autoptr (WpIterator) profiles = NULL;
profiles = wp_pipewire_object_enum_params_sync (proxy, "EnumProfile", NULL);
if (profiles)
handle_enum_profiles (self, proxy, profiles);
}
}
static void
on_device_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d)
{
WpDeviceActivation *self = WP_DEVICE_ACTIVATION (d);
g_autoptr (WpIterator) profiles = NULL;
g_signal_connect_object (proxy, "params-changed",
G_CALLBACK (on_device_params_changed), self, 0);
on_device_params_changed (proxy, "EnumProfile", self);
}
static void
on_plugin_added (WpObjectManager *om, WpPlugin *plugin, gpointer d)
{
WpDeviceActivation *self = WP_DEVICE_ACTIVATION (d);
g_autoptr (WpPlugin) dp = g_weak_ref_get (&self->default_profile);
const gchar *name = wp_plugin_get_name (plugin);
if (g_strcmp0 (name, "default-profile") == 0) {
if (dp)
wp_warning_object (self, "skipping additional default profile plugin");
else
g_weak_ref_set (&self->default_profile, plugin);
}
}
static void
wp_device_activation_enable (WpPlugin * plugin, WpTransition * transition)
{
WpDeviceActivation *self = WP_DEVICE_ACTIVATION (plugin);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (plugin));
/* Create the plugin object manager */
self->plugins_om = wp_object_manager_new ();
wp_object_manager_add_interest (self->plugins_om, WP_TYPE_PLUGIN,
WP_CONSTRAINT_TYPE_G_PROPERTY, "name", "=s", "default-profile", NULL);
g_signal_connect_object (self->plugins_om, "object-added",
G_CALLBACK (on_plugin_added), self, 0);
wp_core_install_object_manager (core, self->plugins_om);
/* Create the devices object manager */
self->devices_om = wp_object_manager_new ();
wp_object_manager_add_interest (self->devices_om, WP_TYPE_DEVICE, NULL);
wp_object_manager_request_object_features (self->devices_om,
WP_TYPE_DEVICE, WP_PIPEWIRE_OBJECT_FEATURES_ALL);
g_signal_connect_object (self->devices_om, "object-added",
G_CALLBACK (on_device_added), self, 0);
wp_core_install_object_manager (core, self->devices_om);
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}
static void
wp_device_activation_disable (WpPlugin * plugin)
{
WpDeviceActivation *self = WP_DEVICE_ACTIVATION (plugin);
g_clear_object (&self->devices_om);
g_clear_object (&self->plugins_om);
g_weak_ref_clear (&self->default_profile);
}
static void
wp_device_activation_init (WpDeviceActivation * self)
{
}
static void
wp_device_activation_class_init (WpDeviceActivationClass * klass)
{
WpPluginClass *plugin_class = (WpPluginClass *) klass;
plugin_class->enable = wp_device_activation_enable;
plugin_class->disable = wp_device_activation_disable;
}
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_device_activation_get_type (),
"name", "device-activation",
"core", core,
NULL));
return TRUE;
}

View File

@@ -23,6 +23,9 @@ function device_defaults.enable()
-- Selects appropriate default nodes and enables saving and restoring them
load_module("default-nodes", device_defaults.properties)
-- Selects appropriate profile for devices
load_script("policy-device-profile.lua", device_defaults.properties)
-- Selects appropriate default routes ("ports" in pulseaudio terminology)
-- and enables saving and restoring them together with
-- their properties (per-route/port volume levels, channel maps, etc)

View File

@@ -20,6 +20,3 @@ load_script("intended-roles.lua")
-- Automatically suspends idle nodes after 3 seconds
load_script("suspend-node.lua")
-- Automatically sets device profiles to 'On'
load_module("device-activation")

View File

@@ -104,7 +104,7 @@ function createDevice(parent, id, type, factory, properties)
end
properties["device.name"] = name
-- initial profile is to be set by m-device-activation, not spa-bluez5
-- initial profile is to be set by policy-device-profile.lua, not spa-bluez5
properties["bluez5.profile"] = "off"
-- apply properties from config.rules

View File

@@ -0,0 +1,184 @@
-- WirePlumber
--
-- Copyright © 2022 Collabora Ltd.
-- @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT
local self = {}
self.config = ... or {}
self.active_profiles = {}
self.best_profiles = {}
self.default_profile_plugin = Plugin.find("default-profile")
function parseParam(param, id)
local parsed = param:parse()
if parsed.pod_type == "Object" and parsed.object_id == id then
return parsed.properties
else
return nil
end
end
function setDeviceProfile (device, dev_id, dev_name, profile)
if self.active_profiles[dev_id] and
self.active_profiles[dev_id].index == profile.index then
Log.info ("Profile " .. profile.name .. " is already set in " .. dev_name)
return
end
local param = Pod.Object {
"Spa:Pod:Object:Param:Profile", "Profile",
index = profile.index,
}
Log.info ("Setting profile " .. profile.name .. " on " .. dev_name)
device:set_param("Profile", param)
end
function findDefaultProfile (device)
local def_name = nil
if self.default_profile_plugin ~= nil then
def_name = self.default_profile_plugin:call ("get-profile", device)
end
if def_name == nil then
return nil
end
for p in device:iterate_params("EnumProfile") do
local profile = parseParam(p, "EnumProfile")
if profile.name == def_name then
return profile
end
end
return nil
end
function findBestProfile (device)
local off_profile = nil
local best_profile = nil
local unk_profile = nil
for p in device:iterate_params("EnumProfile") do
profile = parseParam(p, "EnumProfile")
if profile and profile.name ~= "pro-audio" then
if profile.name == "off" then
off_profile = profile
elseif profile.available == "yes" then
if best_profile == nil or profile.priority > best_profile.priority then
best_profile = profile
end
elseif profile.available ~= "no" then
if unk_profile == nil or profile.priority > unk_profile.priority then
unk_profile = profile
end
end
end
end
if best_profile ~= nil then
return best_profile
elseif unk_profile ~= nil then
return unk_profile
elseif off_profile ~= nil then
return off_profile
end
return nil
end
function handleActiveProfile (device, dev_id, dev_name)
-- Get active profile
local profile = nil
for p in device:iterate_params("Profile") do
profile = parseParam(p, "Profile")
end
if profile == nil then
Log.info ("Cannot find active profile for device " .. dev_name)
return false
end
-- Update if it has changed
if self.active_profiles[dev_id] == nil or
self.active_profiles[dev_id].index ~= profile.index then
self.active_profiles[dev_id] = profile
Log.info ("Active profile changed to " .. profile.name .. " in " .. dev_name)
return true
end
return false
end
function handleBestProfile (device, dev_id, dev_name)
-- Find best profile
local profile = findBestProfile (device)
if profile == nil then
Log.info ("Cannot find best profile for device " .. dev_name)
return false
end
-- Update if it has changed
if self.best_profiles[dev_id] == nil or
self.best_profiles[dev_id].index ~= profile.index then
self.best_profiles[dev_id] = profile
Log.info ("Best profile changed to " .. profile.name .. " in " .. dev_name)
return true
end
return false
end
function handleProfiles (device)
local dev_id = device["bound-id"]
local dev_name = device.properties["device.name"]
-- Set default device if active profile changed to off
local active_changed = handleActiveProfile (device, dev_id, dev_name)
if active_changed and self.active_profiles[dev_id] ~= nil and
self.active_profiles[dev_id].name == "off" then
local def_profile = findDefaultProfile (device)
if def_profile ~= nil then
if def_profile.available == "no" then
Log.info ("Default profile " .. def_profile.name .. " unavailable for " .. dev_name)
else
Log.info ("Found default profile " .. def_profile.name .. " for " .. dev_name)
setDeviceProfile (device, dev_id, dev_name, def_profile)
return
end
else
Log.info ("Default profile not found for " .. dev_name)
end
end
-- Otherwise just set the best profile if changed
local best_changed = handleBestProfile (device, dev_id, dev_name)
local best_profile = self.best_profiles[dev_id]
if best_changed and best_profile ~= nil then
setDeviceProfile (device, dev_id, dev_name, best_profile)
elseif best_profile ~= nil then
Log.info ("Best profile " .. best_profile.name .. " did not change on " .. dev_name)
else
Log.info ("Best profile not found on " .. dev_name)
end
end
function onDeviceParamsChanged (device, param_name)
if param_name == "EnumProfile" then
handleProfiles (device)
end
end
self.om = ObjectManager {
Interest {
type = "device",
Constraint { "device.name", "is-present", type = "pw-global" },
}
}
self.om:connect("object-added", function (_, device)
device:connect ("params-changed", onDeviceParamsChanged)
handleProfiles (device)
end)
self.om:activate()