Merge branch 'state-metadata' into 'master'

Allow clearing the stored device profile at runtime

See merge request pipewire/wireplumber!818
This commit is contained in:
Julian Bouzas 2026-05-01 14:43:59 +00:00
commit fd0146fa4f
8 changed files with 652 additions and 63 deletions

View file

@ -8,9 +8,15 @@ State Storage
digraph inheritance {
rankdir=LR;
GObject -> WpState;
GObject -> WpObject -> WpStateMetadata;
}
.. doxygenstruct:: WpState
.. doxygengroup:: wpstate
:content-only:
.. doxygenstruct:: WpStateMetadata
.. doxygengroup:: wpstatemetadata
:content-only:

View file

@ -440,6 +440,22 @@ wp_properties_update_from_json (WpProperties * self, const WpSpaJson * json)
wp_spa_json_get_size (json));
}
/*!
* \brief Clears all properties from \a self.
*
* \ingroup wpproperties
* \param self a properties object
*/
void
wp_properties_clear (WpProperties *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (!(self->flags & FLAG_IS_DICT));
g_return_if_fail (!(self->flags & FLAG_NO_OWNERSHIP));
pw_properties_clear (self->props);
}
/*!
* \brief Adds new properties in \a self, using the given \a props as a source.
*

View file

@ -85,6 +85,9 @@ gint wp_properties_update_from_dict (WpProperties * self,
WP_API
gint wp_properties_update_from_json (WpProperties * self, const WpSpaJson * json);
WP_API
void wp_properties_clear (WpProperties *self);
/* add */
WP_API

View file

@ -315,6 +315,39 @@ wp_state_clear (WpState *self)
wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno));
}
static gboolean
wp_state_save_internal (const gchar *name, const gchar *location,
WpProperties *props, GError ** error)
{
g_autoptr (GKeyFile) keyfile = g_key_file_new ();
g_autoptr (WpIterator) it = NULL;
g_auto (GValue) item = G_VALUE_INIT;
GError *err = NULL;
g_return_val_if_fail (name, FALSE);
g_return_val_if_fail (location, FALSE);
g_return_val_if_fail (props, FALSE);
/* Set the properties */
for (it = wp_properties_new_iterator (props);
wp_iterator_next (it, &item);
g_value_unset (&item)) {
WpPropertiesItem *pi = g_value_get_boxed (&item);
const gchar *key = wp_properties_item_get_key (pi);
const gchar *val = wp_properties_item_get_value (pi);
g_autofree gchar *escaped_key = escape_string (key);
if (escaped_key)
g_key_file_set_string (keyfile, name, escaped_key, val);
}
if (!g_key_file_save_to_file (keyfile, location, &err)) {
g_propagate_prefixed_error (error, err, "could not save %s: ", name);
return FALSE;
}
return TRUE;
}
/*!
* \brief Saves new properties in the state, overwriting all previous data.
* \ingroup wpstate
@ -326,35 +359,10 @@ wp_state_clear (WpState *self)
gboolean
wp_state_save (WpState *self, WpProperties *props, GError ** error)
{
g_autoptr (GKeyFile) keyfile = g_key_file_new ();
g_autoptr (WpIterator) it = NULL;
g_auto (GValue) item = G_VALUE_INIT;
GError *err = NULL;
g_return_val_if_fail (WP_IS_STATE (self), FALSE);
g_return_val_if_fail (props, FALSE);
wp_state_ensure_location (self);
wp_info_object (self, "saving state into %s", self->location);
/* Set the properties */
for (it = wp_properties_new_iterator (props);
wp_iterator_next (it, &item);
g_value_unset (&item)) {
WpPropertiesItem *pi = g_value_get_boxed (&item);
const gchar *key = wp_properties_item_get_key (pi);
const gchar *val = wp_properties_item_get_value (pi);
g_autofree gchar *escaped_key = escape_string (key);
if (escaped_key)
g_key_file_set_string (keyfile, self->name, escaped_key, val);
}
if (!g_key_file_save_to_file (keyfile, self->location, &err)) {
g_propagate_prefixed_error (error, err, "could not save %s: ", self->name);
return FALSE;
}
return TRUE;
return wp_state_save_internal (self->name, self->location, props, error);
}
static gboolean
@ -408,6 +416,46 @@ wp_state_save_after_timeout (WpState *self, WpCore *core, WpProperties *props)
G_OBJECT (self)));
}
static WpProperties *
wp_state_load_internal (const gchar *name, const gchar *location)
{
g_autoptr (GKeyFile) keyfile = NULL;
g_autoptr (WpProperties) props = NULL;
gchar ** keys = NULL;
g_return_val_if_fail (name, NULL);
g_return_val_if_fail (location, NULL);
props = wp_properties_new_empty ();
/* Open */
keyfile = g_key_file_new ();
if (!g_key_file_load_from_file (keyfile, location,
G_KEY_FILE_NONE, NULL))
return g_steal_pointer (&props);
/* Load all keys */
keys = g_key_file_get_keys (keyfile, name, NULL, NULL);
if (!keys)
return g_steal_pointer (&props);
for (guint i = 0; keys[i]; i++) {
g_autofree gchar *compressed_key = NULL;
const gchar *key = keys[i];
g_autofree gchar *val = NULL;
val = g_key_file_get_string (keyfile, name, key, NULL);
if (!val)
continue;
compressed_key = compress_string (key);
if (compressed_key)
wp_properties_set (props, compressed_key, val);
}
g_strfreev (keys);
return g_steal_pointer (&props);
}
/*!
* \brief Loads the state data from the file system
*
@ -422,36 +470,437 @@ wp_state_save_after_timeout (WpState *self, WpCore *core, WpProperties *props)
WpProperties *
wp_state_load (WpState *self)
{
g_autoptr (GKeyFile) keyfile = g_key_file_new ();
g_autoptr (WpProperties) props = wp_properties_new_empty ();
gchar ** keys = NULL;
g_return_val_if_fail (WP_IS_STATE (self), NULL);
wp_state_ensure_location (self);
return wp_state_load_internal (self->name, self->location);
}
/* Open */
if (!g_key_file_load_from_file (keyfile, self->location,
G_KEY_FILE_NONE, NULL))
return g_steal_pointer (&props);
/* Load all keys */
keys = g_key_file_get_keys (keyfile, self->name, NULL, NULL);
if (!keys)
return g_steal_pointer (&props);
/* WpStateMetadata */
for (guint i = 0; keys[i]; i++) {
g_autofree gchar *compressed_key = NULL;
const gchar *key = keys[i];
g_autofree gchar *val = NULL;
val = g_key_file_get_string (keyfile, self->name, key, NULL);
if (!val)
continue;
compressed_key = compress_string (key);
if (compressed_key)
wp_properties_set (props, compressed_key, val);
/*! \defgroup wpstatemetadata WpStateMetadata */
/*!
* \struct WpStateMetadata
*
* The WpStateMetadata class saves and loads properties from a file and reflects
* the state in a metadata object.
*
* \gproperties
* \gproperty{name, gchar *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
* The file name where the state will be stored.}
*/
enum {
STATE_METADATA_PROP_0,
STATE_METADATA_PROP_NAME,
STATE_METADATA_PROP_TIMEOUT,
};
struct _WpStateMetadata
{
WpObject parent;
/* Props */
gchar *name;
guint timeout;
gchar *location;
WpProperties *metadata_props;
WpImplMetadata *metadata;
GSource *timeout_source;
};
G_DEFINE_TYPE (WpStateMetadata, wp_state_metadata, WP_TYPE_OBJECT)
static void
wp_state_metadata_init (WpStateMetadata * self)
{
self->timeout = DEFAULT_TIMEOUT_MS;
}
static void
wp_state_metadata_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpStateMetadata *self = WP_STATE_METADATA (object);
switch (property_id) {
case STATE_METADATA_PROP_NAME:
g_clear_pointer (&self->name, g_free);
self->name = g_value_dup_string (value);
break;
case STATE_METADATA_PROP_TIMEOUT:
self->timeout = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_state_metadata_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpStateMetadata *self = WP_STATE_METADATA (object);
switch (property_id) {
case STATE_METADATA_PROP_NAME:
g_value_set_string (value, self->name);
break;
case STATE_METADATA_PROP_TIMEOUT:
g_value_set_uint (value, self->timeout);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
enum {
STEP_LOAD = WP_TRANSITION_STEP_CUSTOM_START,
};
static WpObjectFeatures
wp_state_metadata_get_supported_features (WpObject * self)
{
return WP_STATE_METADATA_LOADED;
}
static guint
wp_state_metadata_activate_get_next_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
g_return_val_if_fail (missing == WP_STATE_METADATA_LOADED,
WP_TRANSITION_STEP_ERROR);
return STEP_LOAD;
}
static void
state_metadata_ensure_location (WpStateMetadata *self)
{
if (!self->location)
self->location = get_new_location (self->name);
g_return_if_fail (self->location);
}
static WpProperties *
state_metadata_load (WpStateMetadata *self)
{
state_metadata_ensure_location (self);
return wp_state_load_internal (self->name, self->location);
}
static gboolean
state_metadata_save (WpStateMetadata *self, WpProperties *props,
GError ** error)
{
state_metadata_ensure_location (self);
return wp_state_save_internal (self->name, self->location, props, error);
}
static gboolean
state_metadata_timeout_save_cb (WpStateMetadata *self)
{
g_autoptr (GError) error = NULL;
if (!state_metadata_save (self, self->metadata_props, &error))
wp_warning_object (self, "%s", error->message);
g_clear_pointer (&self->timeout_source, g_source_unref);
wp_info_object (self, "saved changes on state metadata '%s'", self->name);
return G_SOURCE_REMOVE;
}
static void
state_metadata_save_after_timeout (WpStateMetadata *self)
{
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
g_return_if_fail (core);
/* Clear the current timeout callback */
if (self->timeout_source)
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
/* Add the timeout callback */
wp_core_timeout_add_closure (core, &self->timeout_source, self->timeout,
g_cclosure_new_object (G_CALLBACK (state_metadata_timeout_save_cb),
G_OBJECT (self)));
}
static void
state_metadata_clear (WpStateMetadata *self)
{
if (self->metadata_props)
wp_properties_clear (self->metadata_props);
state_metadata_ensure_location (self);
if (remove (self->location) < 0)
wp_warning ("failed to remove %s: %s", self->location, g_strerror (errno));
}
static void
on_state_metadata_changed (WpMetadata *m, guint32 subject,
const gchar *key, const gchar *type, const gchar *value, gpointer d)
{
WpStateMetadata * self = WP_STATE_METADATA (d);
/* Save new key after timeout if valid. Otherwise just clear the state */
if (key) {
wp_properties_set (self->metadata_props, key, value);
if (value)
wp_info_object (self, "key changed on state metadata '%s': %s = %s",
self->name, key, value);
else
wp_info_object (self, "key removed on state metadata '%s': %s",
self->name, key);
state_metadata_save_after_timeout (self);
} else {
state_metadata_clear (self);
wp_info_object (self, "cleared state metadata '%s'", self->name);
}
}
static void
on_metadata_activated (WpObject * proxy, GAsyncResult * res,
WpTransition * transition)
{
WpStateMetadata *self = wp_transition_get_source_object (transition);
g_autoptr (GError) error = NULL;
g_autoptr (WpIterator) it = NULL;
GValue v = G_VALUE_INIT;
/* Make sure there were no errors when activating the metadata */
if (!wp_object_activate_finish (proxy, res, &error)) {
wp_transition_return_error (transition, g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to activate metadata for state %s: %s", self->name,
error->message));
return;
}
g_strfreev (keys);
/* Load the state keys into the metadata */
g_return_if_fail (self->metadata_props == NULL);
self->metadata_props = state_metadata_load (self);
it = wp_properties_new_iterator (self->metadata_props);
while (wp_iterator_next (it, &v)) {
WpPropertiesItem *pi = g_value_get_boxed (&v);
const gchar *key = wp_properties_item_get_key (pi);
const gchar *value = wp_properties_item_get_value (pi);
wp_metadata_set (WP_METADATA (self->metadata), 0, key, NULL, value);
g_value_unset (&v);
}
return g_steal_pointer (&props);
/* Handle metadata changes */
g_signal_connect_object (self->metadata, "changed",
G_CALLBACK (on_state_metadata_changed), self, 0);
wp_object_update_features (WP_OBJECT (self), WP_STATE_METADATA_LOADED, 0);
}
static void
wp_state_metadata_activate_execute_step (WpObject * object,
WpFeatureActivationTransition * transition, guint step,
WpObjectFeatures missing)
{
WpStateMetadata * self = WP_STATE_METADATA (object);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
switch (step) {
case STEP_LOAD: {
g_return_if_fail (self->metadata == NULL);
self->metadata = wp_impl_metadata_new_full (core, self->name,
wp_properties_new ("wireplumber.state", "true", NULL));
wp_object_activate_closure (WP_OBJECT (self->metadata),
WP_OBJECT_FEATURES_ALL, NULL, g_cclosure_new_object (
(GCallback) on_metadata_activated, G_OBJECT (transition)));
break;
}
case WP_TRANSITION_STEP_ERROR:
break;
default:
g_assert_not_reached ();
}
}
static void
wp_state_metadata_deactivate (WpObject * object, WpObjectFeatures features)
{
WpStateMetadata *self = WP_STATE_METADATA (object);
if (self->metadata)
g_signal_handlers_disconnect_by_data (self->metadata, self);
g_clear_pointer (&self->metadata_props, wp_properties_unref);
g_clear_object (&self->metadata);
wp_object_update_features (WP_OBJECT (self), 0, WP_OBJECT_FEATURES_ALL);
}
static void
wp_state_metadata_finalize (GObject * object)
{
WpStateMetadata * self = WP_STATE_METADATA (object);
g_clear_pointer (&self->name, g_free);
g_clear_pointer (&self->location, g_free);
g_clear_pointer (&self->timeout_source, g_source_unref);
G_OBJECT_CLASS (wp_state_metadata_parent_class)->finalize (object);
}
static void
wp_state_metadata_class_init (WpStateMetadataClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpObjectClass * wpobject_class = (WpObjectClass *) klass;
object_class->finalize = wp_state_metadata_finalize;
object_class->set_property = wp_state_metadata_set_property;
object_class->get_property = wp_state_metadata_get_property;
wpobject_class->get_supported_features =
wp_state_metadata_get_supported_features;
wpobject_class->activate_get_next_step =
wp_state_metadata_activate_get_next_step;
wpobject_class->activate_execute_step =
wp_state_metadata_activate_execute_step;
wpobject_class->deactivate = wp_state_metadata_deactivate;
g_object_class_install_property (object_class, STATE_METADATA_PROP_NAME,
g_param_spec_string ("name", "name",
"The file name where the state metadata will be stored", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, STATE_METADATA_PROP_TIMEOUT,
g_param_spec_uint ("timeout", "timeout",
"The timeout in milliseconds to save the state metadata", 0,
G_MAXUINT, DEFAULT_TIMEOUT_MS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
/*!
* \brief Constructs a new state metadata object
* \ingroup wpstatemetadata
* \param core the core associated with the state metadata
* \param name the state metadata name
* \returns (transfer full): the new WpStateMetadata
*/
WpStateMetadata *
wp_state_metadata_new (WpCore *core, const gchar *name)
{
g_return_val_if_fail (core, NULL);
g_return_val_if_fail (name, NULL);
return g_object_new (wp_state_metadata_get_type (),
"core", core,
"name", name,
NULL);
}
/*!
* \brief Gets the name of a state metadata object
* \ingroup wpstatemetadata
* \param self the state metadata
* \returns the name of this state metadata
*/
const gchar *
wp_state_metadata_get_name (WpStateMetadata *self)
{
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
return self->name;
}
/*!
* \brief Gets the location of a state metadata object
* \ingroup wpstatemetadata
* \param self the state metadata
* \returns the location of this state metadata
*/
const gchar *
wp_state_metadata_get_location (WpStateMetadata *self)
{
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
state_metadata_ensure_location (self);
return self->location;
}
/*!
* \brief Clears the state metadata and removes its file
*
* If the state metadata has not been loaded, this won't do anything.
*
* \ingroup wpstatemetadata
* \param self the state metadata
*/
void
wp_state_metadata_clear (WpStateMetadata *self)
{
g_return_if_fail (WP_IS_STATE_METADATA (self));
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
WP_STATE_METADATA_LOADED))
return;
g_return_if_fail (self->metadata);
wp_metadata_clear (WP_METADATA (self->metadata));
}
/*!
* \brief Gets a value from the state metadata
*
* If the state metadata has not been loaded, this won't do anything.
*
* \ingroup wpstatemetadata
* \param self the state metadata
* \param key the key of the value
* \returns the value from the state metadata, or NULL if not found.
*/
const gchar *
wp_state_metadata_get (WpStateMetadata *self, const gchar *key)
{
g_return_val_if_fail (WP_IS_STATE_METADATA (self), NULL);
g_return_val_if_fail (key, NULL);
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
WP_STATE_METADATA_LOADED))
return NULL;
g_return_val_if_fail (self->metadata, NULL);
return wp_metadata_find (WP_METADATA (self->metadata), 0, key, NULL);
}
/*!
* \brief Sets a value into the state metadata
*
* If value is NULL, it will unset the given \a key. Note that this will also
* save the state after the timeout has elapsed.
*
* If the state metadata has not been loaded, this won't do anything.
*
* \ingroup wpstatemetadata
* \param self the metadata object
* \param key: the key to set.
* \param value (nullable): the value to set, or NULL to unset the given \a key
*/
void
wp_state_metadata_set (WpStateMetadata *self, const gchar *key,
const gchar *value)
{
g_return_if_fail (WP_IS_STATE_METADATA (self));
g_return_if_fail (key);
if (!(wp_object_get_active_features (WP_OBJECT (self)) &
WP_STATE_METADATA_LOADED))
return;
g_return_if_fail (self->metadata);
wp_metadata_set (WP_METADATA (self->metadata), 0, key, NULL, value);
}

View file

@ -46,6 +46,46 @@ void wp_state_save_after_timeout (WpState *self, WpCore *core,
WP_API
WpProperties * wp_state_load (WpState *self);
/* WpStateMetadata */
/*!
* \brief Flags to be used as WpObjectFeatures on WpStateMetadata.
* \ingroup wpstate
*/
typedef enum { /*< flags >*/
/*! Loads the state metadata */
WP_STATE_METADATA_LOADED = (1 << 0),
} WpStateMetadataFeatures;
/*!
* \brief The WpStateMetadata GType
* \ingroup wpstatemetadata
*/
#define WP_TYPE_STATE_METADATA (wp_state_metadata_get_type ())
WP_API
G_DECLARE_FINAL_TYPE (WpStateMetadata, wp_state_metadata, WP, STATE_METADATA,
WpObject)
WP_API
WpStateMetadata * wp_state_metadata_new (WpCore *core, const gchar *name);
WP_API
const gchar * wp_state_metadata_get_name (WpStateMetadata *self);
WP_API
const gchar * wp_state_metadata_get_location (WpStateMetadata *self);
WP_API
void wp_state_metadata_clear (WpStateMetadata *self);
WP_API
const gchar * wp_state_metadata_get (WpStateMetadata *self, const gchar *key);
WP_API
void wp_state_metadata_set (WpStateMetadata *self, const gchar *key,
const gchar *value);
G_END_DECLS
#endif

View file

@ -1697,6 +1697,55 @@ static const luaL_Reg state_methods[] = {
{ NULL, NULL }
};
/* WpStateMetadata */
static int
state_metadata_new (lua_State *L)
{
const gchar *name = luaL_checkstring (L, 1);
WpStateMetadata *state_meta = wp_state_metadata_new (get_wp_core (L), name);
wplua_pushobject (L, state_meta);
return 1;
}
static int
state_metadata_clear (lua_State *L)
{
WpStateMetadata *state_meta = wplua_checkobject (L, 1,
WP_TYPE_STATE_METADATA);
wp_state_metadata_clear (state_meta);
return 0;
}
static int
state_metadata_get (lua_State *L)
{
WpStateMetadata *state_meta = wplua_checkobject (L, 1,
WP_TYPE_STATE_METADATA);
const gchar *key = luaL_checkstring (L, 2);
const gchar *v = wp_state_metadata_get (state_meta, key);
lua_pushstring (L, v);
return 1;
}
static int
state_metadata_set (lua_State *L)
{
WpStateMetadata *state_meta = wplua_checkobject (L, 1,
WP_TYPE_STATE_METADATA);
const gchar *key = luaL_checkstring (L, 2);
const gchar *value = luaL_opt (L, luaL_checkstring, 3, NULL);
wp_state_metadata_set (state_meta, key, value);
return 0;
}
static const luaL_Reg state_metadata_methods[] = {
{ "clear", state_metadata_clear },
{ "get", state_metadata_get },
{ "set", state_metadata_set },
{ NULL, NULL }
};
/* ImplModule */
static int
@ -3262,6 +3311,8 @@ wp_lua_scripting_api_init (lua_State *L)
NULL, pipewire_object_methods);
wplua_register_type_methods (L, WP_TYPE_STATE,
state_new, state_methods);
wplua_register_type_methods (L, WP_TYPE_STATE_METADATA,
state_metadata_new, state_metadata_methods);
wplua_register_type_methods (L, WP_TYPE_IMPL_MODULE,
impl_module_new, NULL);
wplua_register_type_methods (L, WP_TYPE_EVENT,

View file

@ -234,6 +234,7 @@ SANDBOX_EXPORT = {
Pod = WpSpaPod,
Json = WpSpaJson,
State = WpState_new,
StateMetadata = WpStateMetadata_new,
LocalModule = WpImplModule_new,
ImplMetadata = WpImplMetadata_new,
Settings = WpSettings,

View file

@ -14,9 +14,8 @@
cutils = require ("common-utils")
log = Log.open_topic ("s-device")
-- the state storage
state = nil
state_table = nil
-- the state meta storage
state_meta = nil
find_stored_profile_hook = SimpleEventHook {
name = "device/find-stored-profile",
@ -43,7 +42,7 @@ find_stored_profile_hook = SimpleEventHook {
return
end
local profile_name = state_table[dev_name]
local profile_name = state_meta:get (dev_name)
if profile_name then
for p in device:iterate_params ("EnumProfile") do
@ -100,7 +99,7 @@ function updateStoredProfile (device, profile)
profile.name, profile.index, dev_name))
-- check if the new profile is the same as the current one
if state_table[dev_name] == profile.name then
if state_meta:get (dev_name) == profile.name then
log:debug (device, " ... profile is already stored")
return
end
@ -121,25 +120,49 @@ function updateStoredProfile (device, profile)
return
end
state_table[dev_name] = profile.name
state:save_after_timeout (state_table)
reevaluate_on_state_changed_hook:remove ()
state_meta:set (dev_name, profile.name)
reevaluate_on_state_changed_hook:register ()
log:info (device, string.format (
"stored profile '%s' (%d) for device '%s'",
profile.name, index, dev_name))
end
reevaluate_on_state_changed_hook = SimpleEventHook {
name = "device/reevaluate-on-state-changed",
interests = {
EventInterest {
Constraint { "event.type", "=", "metadata-changed" },
Constraint { "metadata.name", "=", "default-profile" },
},
},
execute = function (event)
local source = event:get_source ()
local device_om = source:call ("get-object-manager", "device")
for device in device_om:iterate () do
source:call ("push-event", "select-profile", device, nil)
end
end
}
function toggleState (enable)
if enable and not state then
state = State ("default-profile")
state_table = state:load ()
if enable and not state_meta then
state_meta = StateMetadata ("default-profile")
state_meta:activate (Features.ALL, function (_, e)
if e then
log:warning ("failed to activate state metadata: " .. e)
end
end)
find_stored_profile_hook:register ()
store_user_selected_profile_hook:register ()
reevaluate_on_state_changed_hook:register ()
elseif not enable and state then
state = nil
state_table = nil
state_meta:deactivate (Features.ALL)
state_meta = nil
find_stored_profile_hook:remove ()
store_user_selected_profile_hook:remove ()
reevaluate_on_state_changed_hook:remove ()
end
end