mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2025-12-20 05:20:05 +01:00
Compare commits
54 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80478e7548 | ||
|
|
3fb5b775ee | ||
|
|
a5538f4167 | ||
|
|
bec20fc054 | ||
|
|
beded0214d | ||
|
|
2286152c07 | ||
|
|
94fe1cbfbd | ||
|
|
6ebf81453c | ||
|
|
ceed5dca7c | ||
|
|
84e4752f1a | ||
|
|
9e390f1121 | ||
|
|
133b82e61a | ||
|
|
9045d2439a | ||
|
|
278541f637 | ||
|
|
27b6027649 | ||
|
|
6398bf1bce | ||
|
|
f196d10e87 | ||
|
|
5071a85997 | ||
|
|
15d98f59e5 | ||
|
|
be6f2b2926 | ||
|
|
01eb206460 | ||
|
|
5a4ecceee6 | ||
|
|
a35e40c1d2 | ||
|
|
2712cbb5a9 | ||
|
|
c68eb59017 | ||
|
|
c0e047c241 | ||
|
|
238fd3c067 | ||
|
|
b80a0975c7 | ||
|
|
7ca21699a9 | ||
|
|
551353482a | ||
|
|
f0b224b210 | ||
|
|
0dad52f774 | ||
|
|
27f97f6c45 | ||
|
|
285230af67 | ||
|
|
c095ae5254 | ||
|
|
d2a49e8bc5 | ||
|
|
b2c4993ab5 | ||
|
|
ae30b4f022 | ||
|
|
962be34a2b | ||
|
|
6f5ca5a79d | ||
|
|
5ecfe9f555 | ||
|
|
93377a8b4f | ||
|
|
ee72196500 | ||
|
|
e30c2a7cd9 | ||
|
|
4239055454 | ||
|
|
f188ddfb34 | ||
|
|
fb1738932b | ||
|
|
f8be5a76e6 | ||
|
|
5c6a72e3cf | ||
|
|
7b78078ed2 | ||
|
|
6cfaf3f70d | ||
|
|
2942903d0e | ||
|
|
38a21ea191 | ||
|
|
41b310c2d5 |
55 changed files with 2166 additions and 657 deletions
|
|
@ -154,13 +154,22 @@ include:
|
|||
# Fedora also ships that, but without the test plugins that we need...
|
||||
- git clone --depth=1 --branch="$PIPEWIRE_HEAD"
|
||||
https://gitlab.freedesktop.org/pipewire/pipewire.git
|
||||
- meson "$PW_BUILD_DIR" pipewire --prefix="$PREFIX"
|
||||
-Dpipewire-alsa=disabled -Dpipewire-jack=disabled
|
||||
-Dalsa=disabled -Dv4l2=disabled -Djack=disabled -Dbluez5=disabled
|
||||
-Dvulkan=disabled -Dgstreamer=disabled -Dlibsystemd=disabled
|
||||
-Ddocs=disabled -Dman=disabled -Dexamples=disabled -Dpw-cat=disabled
|
||||
-Dsdl2=disabled -Dsndfile=disabled -Dlibpulse=disabled -Davahi=disabled
|
||||
-Decho-cancel-webrtc=disabled -Dsession-managers=[]
|
||||
# Set build options based on PipeWire version
|
||||
- |
|
||||
case "$PIPEWIRE_HEAD" in
|
||||
1.0|1.2|1.4)
|
||||
export PIPEWIRE_BUILD_OPTIONS="-Dsystemd=disabled"
|
||||
;;
|
||||
*)
|
||||
export PIPEWIRE_BUILD_OPTIONS="-Dlibsystemd=disabled"
|
||||
;;
|
||||
esac
|
||||
- meson "$PW_BUILD_DIR" pipewire --prefix="$PREFIX" $PIPEWIRE_BUILD_OPTIONS
|
||||
-Dpipewire-alsa=disabled -Dpipewire-jack=disabled -Dalsa=disabled
|
||||
-Dv4l2=disabled -Djack=disabled -Dbluez5=disabled -Dvulkan=disabled
|
||||
-Dgstreamer=disabled -Ddocs=disabled -Dman=disabled -Dexamples=disabled
|
||||
-Dpw-cat=disabled -Dsdl2=disabled -Dsndfile=disabled -Dlibpulse=disabled
|
||||
-Davahi=disabled -Decho-cancel-webrtc=disabled -Dsession-managers=[]
|
||||
-Dvideotestsrc=enabled -Daudiotestsrc=enabled -Dtest=enabled
|
||||
- ninja $NINJA_ARGS -C "$PW_BUILD_DIR" install
|
||||
# misc environment only for wireplumber
|
||||
|
|
@ -236,6 +245,9 @@ build_on_fedora_no_docs:
|
|||
stage: build
|
||||
variables:
|
||||
BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=disabled -Dsystem-lua=false
|
||||
parallel:
|
||||
matrix:
|
||||
- PIPEWIRE_HEAD: ['master', '1.4', '1.2', '1.0']
|
||||
|
||||
build_on_ubuntu_with_gir:
|
||||
extends:
|
||||
|
|
|
|||
37
AGENTS.md
Normal file
37
AGENTS.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
## Building and Testing
|
||||
|
||||
- To compile the project: `meson compile -C build` (compiles everything, no target needed)
|
||||
- To run tests: `meson test -C build`
|
||||
- The build artifacts always live in a directory called `build` or `builddir`.
|
||||
If `build` doesn't exist, use `-C builddir` in the meson commands.
|
||||
|
||||
## Git Workflow
|
||||
|
||||
- Main branch: `master`
|
||||
- Always create feature branches for new work
|
||||
- Use descriptive commit messages following project conventions
|
||||
- Reference GitLab MR/issue numbers in commits where applicable
|
||||
- Never commit build artifacts or temporary files
|
||||
- Use `glab` CLI tool for GitLab interactions (MRs, issues, etc.)
|
||||
|
||||
## Making a release
|
||||
|
||||
- Each release always consists of an entry in NEWS.rst, at the top of the file, which describes
|
||||
the changes between the previous release and the current one. In addition, each release is given
|
||||
a unique version number, which is present:
|
||||
1. on the section header of that NEWS.rst entry
|
||||
2. in the project() command in meson.build
|
||||
3. on the commit message of the commit that introduces the above 2 changes
|
||||
4. on the git tag that marks the above commit
|
||||
- In order to make a release:
|
||||
- Begin by analyzing the git history and the merged MRs from GitLab between the previous release
|
||||
and today. GitLab MRs that are relevant always have the new release's version number set as a
|
||||
"milestone"
|
||||
- Create a new entry in NEWS.rst describing the changes, in a similar style and format as the
|
||||
previous entries. Consolidate the changes to larger work items and also reference the relevant
|
||||
gitlab MR that corresponds to each change and/or the gitlab issues that were addressed by each
|
||||
change.
|
||||
- Make sure to move the "Past releases" section header up, so that the only 2 top-level sections
|
||||
are the new release section and the "Past releases" section.
|
||||
- Edit meson.build to change the project version to the new release number
|
||||
- Do not commit anything to git. Let the user review the changes and commit manually.
|
||||
|
|
@ -140,9 +140,9 @@ Policies
|
|||
for enabling devices, linking streams, granting permissions to clients,
|
||||
etc, as appropriate for a desktop system.
|
||||
|
||||
.. describe:: policy.role-priority-system
|
||||
.. describe:: policy.role-based
|
||||
|
||||
Enables the role priority system policy. This system creates virtual sinks
|
||||
Enables the role based priority system policy. This system creates virtual sinks
|
||||
that group streams based on their ``media.role`` property, and assigns a
|
||||
priority to each role. Depending on the priority configuration, lower
|
||||
priority roles may be corked or ducked when a higher priority role stream
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Synopsis:
|
|||
|
||||
$ meson -Dsession-managers="[ 'wireplumber' ]" build
|
||||
$ ninja -C build
|
||||
$ make run
|
||||
$ make -C build run
|
||||
|
||||
Run independently or without installing
|
||||
---------------------------------------
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ assignments:
|
|||
}
|
||||
|
||||
|
||||
After that, once the media class of a device node has been select for a
|
||||
After that, once the media class of a device node has been selected for a
|
||||
particular stream node, and there are more than 1 device node matching such
|
||||
media class, WirePlumber will select one based on a set of priorities:
|
||||
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ spa_device_event_object_info (void *data, uint32_t id,
|
|||
g_autoptr (WpProperties) props = NULL;
|
||||
|
||||
type = spa_debug_type_short_name (info->type);
|
||||
props = wp_properties_new_wrap_dict (info->props);
|
||||
props = wp_properties_new_copy_dict (info->props);
|
||||
|
||||
wp_debug_object (self, "object info: id:%u type:%s factory:%s",
|
||||
id, type, info->factory_name);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,161 @@
|
|||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-event-dispatcher")
|
||||
|
||||
typedef struct _HookData HookData;
|
||||
struct _HookData
|
||||
{
|
||||
struct spa_list link;
|
||||
WpEventHook *hook;
|
||||
GPtrArray *dependencies;
|
||||
};
|
||||
|
||||
static inline HookData *
|
||||
hook_data_new (WpEventHook * hook)
|
||||
{
|
||||
HookData *hook_data = g_new0 (HookData, 1);
|
||||
spa_list_init (&hook_data->link);
|
||||
hook_data->hook = g_object_ref (hook);
|
||||
hook_data->dependencies = g_ptr_array_new ();
|
||||
return hook_data;
|
||||
}
|
||||
|
||||
static void
|
||||
hook_data_free (HookData *self)
|
||||
{
|
||||
g_clear_object (&self->hook);
|
||||
g_clear_pointer (&self->dependencies, g_ptr_array_unref);
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
static inline void
|
||||
record_dependency (struct spa_list *list, const gchar *target,
|
||||
const gchar *dependency)
|
||||
{
|
||||
HookData *hook_data;
|
||||
spa_list_for_each (hook_data, list, link) {
|
||||
if (g_pattern_match_simple (target, wp_event_hook_get_name (hook_data->hook))) {
|
||||
g_ptr_array_insert (hook_data->dependencies, -1, (gchar *) dependency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
hook_exists_in (const gchar *hook_name, struct spa_list *list)
|
||||
{
|
||||
HookData *hook_data;
|
||||
if (!spa_list_is_empty (list)) {
|
||||
spa_list_for_each (hook_data, list, link) {
|
||||
if (g_pattern_match_simple (hook_name, wp_event_hook_get_name (hook_data->hook))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sort_hooks (GPtrArray *hooks)
|
||||
{
|
||||
struct spa_list collected, result, remaining;
|
||||
HookData *sorted_hook_data = NULL;
|
||||
|
||||
spa_list_init (&collected);
|
||||
spa_list_init (&result);
|
||||
spa_list_init (&remaining);
|
||||
|
||||
for (guint i = 0; i < hooks->len; i++) {
|
||||
WpEventHook *hook = g_ptr_array_index (hooks, i);
|
||||
HookData *hook_data = hook_data_new (hook);
|
||||
|
||||
/* record "after" dependencies directly */
|
||||
const gchar * const * strv =
|
||||
wp_event_hook_get_runs_after_hooks (hook_data->hook);
|
||||
while (strv && *strv) {
|
||||
g_ptr_array_insert (hook_data->dependencies, -1, (gchar *) *strv);
|
||||
strv++;
|
||||
}
|
||||
|
||||
spa_list_append (&collected, &hook_data->link);
|
||||
}
|
||||
|
||||
if (!spa_list_is_empty (&collected)) {
|
||||
HookData *hook_data;
|
||||
|
||||
/* convert "before" dependencies into "after" dependencies */
|
||||
spa_list_for_each (hook_data, &collected, link) {
|
||||
const gchar * const * strv =
|
||||
wp_event_hook_get_runs_before_hooks (hook_data->hook);
|
||||
while (strv && *strv) {
|
||||
/* record hook_data->hook as a dependency of the *strv hook */
|
||||
record_dependency (&collected, *strv,
|
||||
wp_event_hook_get_name (hook_data->hook));
|
||||
strv++;
|
||||
}
|
||||
}
|
||||
|
||||
/* sort */
|
||||
while (!spa_list_is_empty (&collected)) {
|
||||
gboolean made_progress = FALSE;
|
||||
|
||||
/* examine each hook to see if its dependencies are satisfied in the
|
||||
result list; if yes, then append it to the result too */
|
||||
spa_list_consume (hook_data, &collected, link) {
|
||||
guint deps_satisfied = 0;
|
||||
|
||||
spa_list_remove (&hook_data->link);
|
||||
|
||||
for (guint i = 0; i < hook_data->dependencies->len; i++) {
|
||||
const gchar *dep = g_ptr_array_index (hook_data->dependencies, i);
|
||||
/* if the dependency is already in the sorted result list or if
|
||||
it doesn't exist at all, we consider it satisfied */
|
||||
if (hook_exists_in (dep, &result) ||
|
||||
!(hook_exists_in (dep, &collected) ||
|
||||
hook_exists_in (dep, &remaining))) {
|
||||
deps_satisfied++;
|
||||
}
|
||||
}
|
||||
|
||||
if (deps_satisfied == hook_data->dependencies->len) {
|
||||
spa_list_append (&result, &hook_data->link);
|
||||
made_progress = TRUE;
|
||||
} else {
|
||||
spa_list_append (&remaining, &hook_data->link);
|
||||
}
|
||||
}
|
||||
|
||||
if (made_progress) {
|
||||
/* run again with the remaining hooks */
|
||||
spa_list_insert_list (&collected, &remaining);
|
||||
spa_list_init (&remaining);
|
||||
}
|
||||
else if (!spa_list_is_empty (&remaining)) {
|
||||
/* if we did not make any progress towards growing the result list,
|
||||
it means the dependencies cannot be satisfied because of circles */
|
||||
spa_list_consume (hook_data, &result, link) {
|
||||
spa_list_remove (&hook_data->link);
|
||||
hook_data_free (hook_data);
|
||||
}
|
||||
spa_list_consume (hook_data, &remaining, link) {
|
||||
spa_list_remove (&hook_data->link);
|
||||
hook_data_free (hook_data);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* clear hooks and add the sorted ones */
|
||||
g_ptr_array_set_size (hooks, 0);
|
||||
spa_list_consume (sorted_hook_data, &result, link) {
|
||||
spa_list_remove (&sorted_hook_data->link);
|
||||
g_ptr_array_add (hooks, g_object_ref (sorted_hook_data->hook));
|
||||
hook_data_free (sorted_hook_data);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
typedef struct _EventData EventData;
|
||||
struct _EventData
|
||||
{
|
||||
|
|
@ -49,7 +204,8 @@ struct _WpEventDispatcher
|
|||
GObject parent;
|
||||
|
||||
GWeakRef core;
|
||||
GPtrArray *hooks; /* registered hooks */
|
||||
GHashTable *defined_hooks; /* registered hooks for defined events */
|
||||
GPtrArray *undefined_hooks; /* registered hooks for undefined events */
|
||||
GSource *source; /* the event loop source */
|
||||
GList *events; /* the events stack */
|
||||
struct spa_system *system;
|
||||
|
|
@ -160,7 +316,9 @@ static void
|
|||
wp_event_dispatcher_init (WpEventDispatcher * self)
|
||||
{
|
||||
g_weak_ref_init (&self->core, NULL);
|
||||
self->hooks = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
self->defined_hooks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
|
||||
(GDestroyNotify)g_ptr_array_unref);
|
||||
self->undefined_hooks = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
self->source = g_source_new (&source_funcs, sizeof (WpEventSource));
|
||||
((WpEventSource *) self->source)->dispatcher = self;
|
||||
|
|
@ -184,7 +342,8 @@ wp_event_dispatcher_finalize (GObject * object)
|
|||
|
||||
close (self->eventfd);
|
||||
|
||||
g_clear_pointer (&self->hooks, g_ptr_array_unref);
|
||||
g_clear_pointer (&self->defined_hooks, g_hash_table_unref);
|
||||
g_clear_pointer (&self->undefined_hooks, g_ptr_array_unref);
|
||||
g_weak_ref_clear (&self->core);
|
||||
|
||||
G_OBJECT_CLASS (wp_event_dispatcher_parent_class)->finalize (object);
|
||||
|
|
@ -284,6 +443,10 @@ void
|
|||
wp_event_dispatcher_register_hook (WpEventDispatcher * self,
|
||||
WpEventHook * hook)
|
||||
{
|
||||
g_autoptr (GPtrArray) event_types = NULL;
|
||||
gboolean is_defined = FALSE;
|
||||
const gchar *hook_name;
|
||||
|
||||
g_return_if_fail (WP_IS_EVENT_DISPATCHER (self));
|
||||
g_return_if_fail (WP_IS_EVENT_HOOK (hook));
|
||||
|
||||
|
|
@ -292,7 +455,74 @@ wp_event_dispatcher_register_hook (WpEventDispatcher * self,
|
|||
g_return_if_fail (already_registered_dispatcher == NULL);
|
||||
|
||||
wp_event_hook_set_dispatcher (hook, self);
|
||||
g_ptr_array_add (self->hooks, g_object_ref (hook));
|
||||
|
||||
/* Register the event hook in the defined hooks table if it is defined */
|
||||
hook_name = wp_event_hook_get_name (hook);
|
||||
event_types = wp_event_hook_get_matching_event_types (hook);
|
||||
if (event_types) {
|
||||
for (guint i = 0; i < event_types->len; i++) {
|
||||
const gchar *event_type = g_ptr_array_index (event_types, i);
|
||||
GPtrArray *hooks;
|
||||
|
||||
wp_debug_object (self, "Registering hook %s for defined event type %s",
|
||||
hook_name, event_type);
|
||||
|
||||
/* Check if the event type was registered in the hash table */
|
||||
hooks = g_hash_table_lookup (self->defined_hooks, event_type);
|
||||
if (hooks) {
|
||||
g_ptr_array_add (hooks, g_object_ref (hook));
|
||||
if (!sort_hooks (hooks))
|
||||
goto sort_error;
|
||||
} else {
|
||||
GPtrArray *new_hooks = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
/* Add undefined hooks */
|
||||
for (guint i = 0; i < self->undefined_hooks->len; i++) {
|
||||
WpEventHook *uh = g_ptr_array_index (self->undefined_hooks, i);
|
||||
g_ptr_array_add (new_hooks, g_object_ref (uh));
|
||||
}
|
||||
/* Add current hook */
|
||||
g_ptr_array_add (new_hooks, g_object_ref (hook));
|
||||
g_hash_table_insert (self->defined_hooks, g_strdup (event_type),
|
||||
new_hooks);
|
||||
if (!sort_hooks (new_hooks))
|
||||
goto sort_error;
|
||||
}
|
||||
|
||||
is_defined = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise just register it as undefined hook */
|
||||
if (!is_defined) {
|
||||
GHashTableIter iter;
|
||||
gpointer value;
|
||||
|
||||
wp_debug_object (self, "Registering hook %s for undefined event types",
|
||||
hook_name);
|
||||
|
||||
/* Add it to the defined hooks table */
|
||||
g_hash_table_iter_init (&iter, self->defined_hooks);
|
||||
while (g_hash_table_iter_next (&iter, NULL, &value)) {
|
||||
GPtrArray *defined_hooks = value;
|
||||
g_ptr_array_add (defined_hooks, g_object_ref (hook));
|
||||
if (!sort_hooks (defined_hooks))
|
||||
goto sort_error;
|
||||
}
|
||||
|
||||
/* Add it to the undefined hooks */
|
||||
g_ptr_array_add (self->undefined_hooks, g_object_ref (hook));
|
||||
if (!sort_hooks (self->undefined_hooks))
|
||||
goto sort_error;
|
||||
}
|
||||
|
||||
wp_info_object (self, "Registered hook %s successfully", hook_name);
|
||||
return;
|
||||
|
||||
sort_error:
|
||||
/* Unregister hook */
|
||||
wp_event_dispatcher_unregister_hook (self, hook);
|
||||
wp_warning_object (self,
|
||||
"Could not register hook %s because of circular dependencies", hook_name);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
@ -306,6 +536,9 @@ void
|
|||
wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
||||
WpEventHook * hook)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer value;
|
||||
|
||||
g_return_if_fail (WP_IS_EVENT_DISPATCHER (self));
|
||||
g_return_if_fail (WP_IS_EVENT_HOOK (hook));
|
||||
|
||||
|
|
@ -314,11 +547,29 @@ wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
|||
g_return_if_fail (already_registered_dispatcher == self);
|
||||
|
||||
wp_event_hook_set_dispatcher (hook, NULL);
|
||||
g_ptr_array_remove_fast (self->hooks, hook);
|
||||
|
||||
/* Remove hook from defined table and undefined list */
|
||||
g_hash_table_iter_init (&iter, self->defined_hooks);
|
||||
while (g_hash_table_iter_next (&iter, NULL, &value)) {
|
||||
GPtrArray *defined_hooks = value;
|
||||
g_ptr_array_remove (defined_hooks, hook);
|
||||
}
|
||||
g_ptr_array_remove (self->undefined_hooks, hook);
|
||||
}
|
||||
|
||||
static void
|
||||
add_unique (GPtrArray *array, WpEventHook * hook)
|
||||
{
|
||||
for (guint i = 0; i < array->len; i++)
|
||||
if (g_ptr_array_index (array, i) == hook)
|
||||
return;
|
||||
g_ptr_array_add (array, g_object_ref (hook));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns an iterator to iterate over all the registered hooks
|
||||
* \deprecated Use \ref wp_event_dispatcher_new_hooks_for_event_type_iterator
|
||||
* instead.
|
||||
* \ingroup wpeventdispatcher
|
||||
*
|
||||
* \param self the event dispatcher
|
||||
|
|
@ -327,7 +578,56 @@ wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
|||
WpIterator *
|
||||
wp_event_dispatcher_new_hooks_iterator (WpEventDispatcher * self)
|
||||
{
|
||||
GPtrArray *items =
|
||||
g_ptr_array_copy (self->hooks, (GCopyFunc) g_object_ref, NULL);
|
||||
GPtrArray *items = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
GHashTableIter iter;
|
||||
gpointer value;
|
||||
|
||||
/* Add all defined hooks */
|
||||
g_hash_table_iter_init (&iter, self->defined_hooks);
|
||||
while (g_hash_table_iter_next (&iter, NULL, &value)) {
|
||||
GPtrArray *hooks = value;
|
||||
for (guint i = 0; i < hooks->len; i++) {
|
||||
WpEventHook *hook = g_ptr_array_index (hooks, i);
|
||||
add_unique (items, hook);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add all undefined hooks */
|
||||
for (guint i = 0; i < self->undefined_hooks->len; i++) {
|
||||
WpEventHook *hook = g_ptr_array_index (self->undefined_hooks, i);
|
||||
add_unique (items, hook);
|
||||
}
|
||||
|
||||
return wp_iterator_new_ptr_array (items, WP_TYPE_EVENT_HOOK);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns an iterator to iterate over the registered hooks for a
|
||||
* particular event type.
|
||||
* \ingroup wpeventdispatcher
|
||||
*
|
||||
* \param self the event dispatcher
|
||||
* \param event_type the event type
|
||||
* \return (transfer full): a new iterator
|
||||
* \since 0.5.13
|
||||
*/
|
||||
WpIterator *
|
||||
wp_event_dispatcher_new_hooks_for_event_type_iterator (
|
||||
WpEventDispatcher * self, const gchar *event_type)
|
||||
{
|
||||
GPtrArray *items;
|
||||
GPtrArray *hooks;
|
||||
|
||||
hooks = g_hash_table_lookup (self->defined_hooks, event_type);
|
||||
if (hooks) {
|
||||
wp_debug_object (self, "Using %d defined hooks for event type %s",
|
||||
hooks->len, event_type);
|
||||
} else {
|
||||
hooks = self->undefined_hooks;
|
||||
wp_debug_object (self, "Using %d undefined hooks for event type %s",
|
||||
hooks->len, event_type);
|
||||
}
|
||||
|
||||
items = g_ptr_array_copy (hooks, (GCopyFunc) g_object_ref, NULL);
|
||||
return wp_iterator_new_ptr_array (items, WP_TYPE_EVENT_HOOK);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ void wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
|||
WpEventHook * hook);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_event_dispatcher_new_hooks_iterator (WpEventDispatcher * self);
|
||||
WpIterator * wp_event_dispatcher_new_hooks_iterator (WpEventDispatcher * self)
|
||||
G_GNUC_DEPRECATED_FOR (wp_event_dispatcher_new_hooks_for_event_type_iterator);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_event_dispatcher_new_hooks_for_event_type_iterator (
|
||||
WpEventDispatcher * self, const gchar *event_type);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,24 @@ wp_event_hook_run (WpEventHook * self,
|
|||
callback_data);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Gets all the matching event types for this hook if any.
|
||||
*
|
||||
* \ingroup wpeventhook
|
||||
* \param self the event hook
|
||||
* \returns (element-type gchar*) (transfer full) (nullable): the matching
|
||||
* event types for this hook if any.
|
||||
* \since 0.5.13
|
||||
*/
|
||||
GPtrArray *
|
||||
wp_event_hook_get_matching_event_types (WpEventHook * self)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_EVENT_HOOK (self), NULL);
|
||||
g_return_val_if_fail (
|
||||
WP_EVENT_HOOK_GET_CLASS (self)->get_matching_event_types, NULL);
|
||||
return WP_EVENT_HOOK_GET_CLASS (self)->get_matching_event_types (self);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Finishes the async operation that was started by wp_event_hook_run()
|
||||
*
|
||||
|
|
@ -321,36 +339,63 @@ wp_interest_event_hook_runs_for_event (WpEventHook * hook, WpEvent * event)
|
|||
wp_interest_event_hook_get_instance_private (self);
|
||||
g_autoptr (WpProperties) properties = wp_event_get_properties (event);
|
||||
g_autoptr (GObject) subject = wp_event_get_subject (event);
|
||||
GType gtype = subject ? G_OBJECT_TYPE (subject) : WP_TYPE_EVENT;
|
||||
guint i;
|
||||
WpObjectInterest *interest = NULL;
|
||||
WpInterestMatch match;
|
||||
|
||||
const unsigned int MATCH_ALL_PROPS = (WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES |
|
||||
WP_INTEREST_MATCH_PW_PROPERTIES |
|
||||
WP_INTEREST_MATCH_G_PROPERTIES);
|
||||
|
||||
for (i = 0; i < priv->interests->len; i++) {
|
||||
interest = g_ptr_array_index (priv->interests, i);
|
||||
match = wp_object_interest_matches_full (interest,
|
||||
WP_INTEREST_MATCH_FLAGS_CHECK_ALL,
|
||||
gtype, subject, properties, properties);
|
||||
|
||||
/* the interest may have a GType that matches the GType of the subject
|
||||
or it may have WP_TYPE_EVENT as its GType, in which case it will
|
||||
match any type of subject */
|
||||
if (match == WP_INTEREST_MATCH_ALL)
|
||||
return TRUE;
|
||||
else if (subject && (match & MATCH_ALL_PROPS) == MATCH_ALL_PROPS) {
|
||||
match = wp_object_interest_matches_full (interest, 0,
|
||||
WP_TYPE_EVENT, NULL, NULL, NULL);
|
||||
if (match & WP_INTEREST_MATCH_GTYPE)
|
||||
if (wp_object_interest_matches_full (interest,
|
||||
WP_INTEREST_MATCH_FLAGS_NONE,
|
||||
WP_TYPE_EVENT, subject, properties, properties) == WP_INTEREST_MATCH_ALL)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
add_unique (GPtrArray *array, const gchar * lookup)
|
||||
{
|
||||
for (guint i = 0; i < array->len; i++)
|
||||
if (g_str_equal (g_ptr_array_index (array, i), lookup))
|
||||
return;
|
||||
g_ptr_array_add (array, g_strdup (lookup));
|
||||
}
|
||||
|
||||
static GPtrArray *
|
||||
wp_interest_event_hook_get_matching_event_types (WpEventHook * hook)
|
||||
{
|
||||
WpInterestEventHook *self = WP_INTEREST_EVENT_HOOK (hook);
|
||||
WpInterestEventHookPrivate *priv =
|
||||
wp_interest_event_hook_get_instance_private (self);
|
||||
GPtrArray *res = g_ptr_array_new_with_free_func (g_free);
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < priv->interests->len; i++) {
|
||||
WpObjectInterest *interest = g_ptr_array_index (priv->interests, i);
|
||||
if (wp_object_interest_matches_full (interest, WP_INTEREST_MATCH_FLAGS_NONE,
|
||||
WP_TYPE_EVENT, NULL, NULL, NULL) & WP_INTEREST_MATCH_GTYPE) {
|
||||
g_autoptr (GPtrArray) values =
|
||||
wp_object_interest_find_defined_constraint_values (interest,
|
||||
WP_CONSTRAINT_TYPE_NONE, "event.type");
|
||||
if (!values || values->len == 0) {
|
||||
/* We always consider the hook undefined if it has at least one interest
|
||||
* without a defined 'event.type' constraint */
|
||||
return NULL;
|
||||
} else {
|
||||
for (guint j = 0; j < values->len; j++) {
|
||||
GVariant *v = g_ptr_array_index (values, j);
|
||||
if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) {
|
||||
const gchar *v_str = g_variant_get_string (v, NULL);
|
||||
add_unique (res, v_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_interest_event_hook_class_init (WpInterestEventHookClass * klass)
|
||||
{
|
||||
|
|
@ -359,6 +404,8 @@ wp_interest_event_hook_class_init (WpInterestEventHookClass * klass)
|
|||
|
||||
object_class->finalize = wp_interest_event_hook_finalize;
|
||||
hook_class->runs_for_event = wp_interest_event_hook_runs_for_event;
|
||||
hook_class->get_matching_event_types =
|
||||
wp_interest_event_hook_get_matching_event_types;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ struct _WpEventHookClass
|
|||
|
||||
gboolean (*finish) (WpEventHook * self, GAsyncResult * res, GError ** error);
|
||||
|
||||
GPtrArray * (*get_matching_event_types) (WpEventHook *self);
|
||||
|
||||
/*< private >*/
|
||||
WP_PADDING(5)
|
||||
WP_PADDING(4)
|
||||
};
|
||||
|
||||
WP_API
|
||||
|
|
@ -67,6 +69,9 @@ void wp_event_hook_run (WpEventHook * self,
|
|||
WpEvent * event, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer callback_data);
|
||||
|
||||
WP_API
|
||||
GPtrArray * wp_event_hook_get_matching_event_types (WpEventHook * self);
|
||||
|
||||
WP_API
|
||||
gboolean wp_event_hook_finish (WpEventHook * self, GAsyncResult * res,
|
||||
GError ** error);
|
||||
|
|
|
|||
262
lib/wp/event.c
262
lib/wp/event.c
|
|
@ -17,37 +17,11 @@
|
|||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-event")
|
||||
|
||||
typedef struct _HookData HookData;
|
||||
struct _HookData
|
||||
{
|
||||
struct spa_list link;
|
||||
WpEventHook *hook;
|
||||
GPtrArray *dependencies;
|
||||
};
|
||||
|
||||
static inline HookData *
|
||||
hook_data_new (WpEventHook * hook)
|
||||
{
|
||||
HookData *hook_data = g_new0 (HookData, 1);
|
||||
spa_list_init (&hook_data->link);
|
||||
hook_data->hook = g_object_ref (hook);
|
||||
hook_data->dependencies = g_ptr_array_new ();
|
||||
return hook_data;
|
||||
}
|
||||
|
||||
static void
|
||||
hook_data_free (HookData *self)
|
||||
{
|
||||
g_clear_object (&self->hook);
|
||||
g_clear_pointer (&self->dependencies, g_ptr_array_unref);
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
struct _WpEvent
|
||||
{
|
||||
grefcount ref;
|
||||
GData *datalist;
|
||||
struct spa_list hooks;
|
||||
GPtrArray *hooks;
|
||||
|
||||
/* immutable fields */
|
||||
gint priority;
|
||||
|
|
@ -96,7 +70,7 @@ wp_event_new (const gchar * type, gint priority, WpProperties * properties,
|
|||
WpEvent * self = g_new0 (WpEvent, 1);
|
||||
g_ref_count_init (&self->ref);
|
||||
g_datalist_init (&self->datalist);
|
||||
spa_list_init (&self->hooks);
|
||||
self->hooks = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
self->priority = priority;
|
||||
self->properties = properties ?
|
||||
|
|
@ -155,11 +129,7 @@ wp_event_get_name(WpEvent *self)
|
|||
static void
|
||||
wp_event_free (WpEvent * self)
|
||||
{
|
||||
HookData *hook_data;
|
||||
spa_list_consume (hook_data, &self->hooks, link) {
|
||||
spa_list_remove (&hook_data->link);
|
||||
hook_data_free (hook_data);
|
||||
}
|
||||
g_clear_pointer (&self->hooks, g_ptr_array_unref);
|
||||
g_datalist_clear (&self->datalist);
|
||||
g_clear_pointer (&self->properties, wp_properties_unref);
|
||||
g_clear_object (&self->source);
|
||||
|
|
@ -316,33 +286,6 @@ wp_event_get_data (WpEvent * self, const gchar * key)
|
|||
return g_datalist_get_data (&self->datalist, key);
|
||||
}
|
||||
|
||||
static inline void
|
||||
record_dependency (struct spa_list *list, const gchar *target,
|
||||
const gchar *dependency)
|
||||
{
|
||||
HookData *hook_data;
|
||||
spa_list_for_each (hook_data, list, link) {
|
||||
if (g_pattern_match_simple (target, wp_event_hook_get_name (hook_data->hook))) {
|
||||
g_ptr_array_insert (hook_data->dependencies, -1, (gchar *) dependency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
hook_exists_in (const gchar *hook_name, struct spa_list *list)
|
||||
{
|
||||
HookData *hook_data;
|
||||
if (!spa_list_is_empty (list)) {
|
||||
spa_list_for_each (hook_data, list, link) {
|
||||
if (g_pattern_match_simple (hook_name, wp_event_hook_get_name (hook_data->hook))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Collects all the hooks registered in the \a dispatcher that run for
|
||||
* this \a event
|
||||
|
|
@ -355,199 +298,37 @@ hook_exists_in (const gchar *hook_name, struct spa_list *list)
|
|||
gboolean
|
||||
wp_event_collect_hooks (WpEvent * event, WpEventDispatcher * dispatcher)
|
||||
{
|
||||
struct spa_list collected, result, remaining;
|
||||
g_autoptr (WpIterator) all_hooks = NULL;
|
||||
g_auto (GValue) value = G_VALUE_INIT;
|
||||
const gchar *event_type = NULL;
|
||||
|
||||
g_return_val_if_fail (event != NULL, FALSE);
|
||||
g_return_val_if_fail (WP_IS_EVENT_DISPATCHER (dispatcher), FALSE);
|
||||
|
||||
/* hooks already collected */
|
||||
if (!spa_list_is_empty (&event->hooks))
|
||||
return TRUE;
|
||||
/* Clear all current hooks */
|
||||
g_ptr_array_set_size (event->hooks, 0);
|
||||
|
||||
spa_list_init (&collected);
|
||||
spa_list_init (&result);
|
||||
spa_list_init (&remaining);
|
||||
/* Get the event type */
|
||||
event_type = wp_properties_get (event->properties, "event.type");
|
||||
wp_debug_object (dispatcher, "Collecting hooks for event %s with type %s",
|
||||
event->name, event_type);
|
||||
|
||||
/* collect hooks that run for this event */
|
||||
all_hooks = wp_event_dispatcher_new_hooks_iterator (dispatcher);
|
||||
/* Collect hooks that run for this event */
|
||||
all_hooks = wp_event_dispatcher_new_hooks_for_event_type_iterator (dispatcher,
|
||||
event_type);
|
||||
while (wp_iterator_next (all_hooks, &value)) {
|
||||
WpEventHook *hook = g_value_get_object (&value);
|
||||
|
||||
if (wp_event_hook_runs_for_event (hook, event)) {
|
||||
HookData *hook_data = hook_data_new (hook);
|
||||
|
||||
/* record "after" dependencies directly */
|
||||
const gchar * const * strv =
|
||||
wp_event_hook_get_runs_after_hooks (hook_data->hook);
|
||||
while (strv && *strv) {
|
||||
g_ptr_array_insert (hook_data->dependencies, -1, (gchar *) *strv);
|
||||
strv++;
|
||||
}
|
||||
|
||||
spa_list_append (&collected, &hook_data->link);
|
||||
|
||||
g_ptr_array_add (event->hooks, g_object_ref (hook));
|
||||
wp_debug_boxed (WP_TYPE_EVENT, event, "added "WP_OBJECT_FORMAT"(%s)",
|
||||
WP_OBJECT_ARGS (hook), wp_event_hook_get_name (hook));
|
||||
}
|
||||
|
||||
g_value_unset (&value);
|
||||
}
|
||||
|
||||
if (!spa_list_is_empty (&collected)) {
|
||||
HookData *hook_data;
|
||||
|
||||
/* convert "before" dependencies into "after" dependencies */
|
||||
spa_list_for_each (hook_data, &collected, link) {
|
||||
const gchar * const * strv =
|
||||
wp_event_hook_get_runs_before_hooks (hook_data->hook);
|
||||
while (strv && *strv) {
|
||||
/* record hook_data->hook as a dependency of the *strv hook */
|
||||
record_dependency (&collected, *strv,
|
||||
wp_event_hook_get_name (hook_data->hook));
|
||||
strv++;
|
||||
}
|
||||
}
|
||||
|
||||
/* sort */
|
||||
while (!spa_list_is_empty (&collected)) {
|
||||
gboolean made_progress = FALSE;
|
||||
|
||||
/* examine each hook to see if its dependencies are satisfied in the
|
||||
result list; if yes, then append it to the result too */
|
||||
spa_list_consume (hook_data, &collected, link) {
|
||||
guint deps_satisfied = 0;
|
||||
|
||||
spa_list_remove (&hook_data->link);
|
||||
|
||||
wp_trace_boxed (WP_TYPE_EVENT, event,
|
||||
"examining: %s", wp_event_hook_get_name (hook_data->hook));
|
||||
|
||||
for (guint i = 0; i < hook_data->dependencies->len; i++) {
|
||||
const gchar *dep = g_ptr_array_index (hook_data->dependencies, i);
|
||||
/* if the dependency is already in the sorted result list or if
|
||||
it doesn't exist at all, we consider it satisfied */
|
||||
if (hook_exists_in (dep, &result) ||
|
||||
!(hook_exists_in (dep, &collected) ||
|
||||
hook_exists_in (dep, &remaining))) {
|
||||
deps_satisfied++;
|
||||
}
|
||||
|
||||
wp_trace_boxed (WP_TYPE_EVENT, event, "depends: %s, satisfied: %u/%u",
|
||||
dep, deps_satisfied, hook_data->dependencies->len);
|
||||
}
|
||||
|
||||
if (deps_satisfied == hook_data->dependencies->len) {
|
||||
wp_trace_boxed (WP_TYPE_EVENT, event,
|
||||
"sorted: "WP_OBJECT_FORMAT"(%s)",
|
||||
WP_OBJECT_ARGS (hook_data->hook),
|
||||
wp_event_hook_get_name (hook_data->hook));
|
||||
|
||||
spa_list_append (&result, &hook_data->link);
|
||||
made_progress = TRUE;
|
||||
} else {
|
||||
spa_list_append (&remaining, &hook_data->link);
|
||||
}
|
||||
}
|
||||
|
||||
if (made_progress) {
|
||||
/* run again with the remaining hooks */
|
||||
spa_list_insert_list (&collected, &remaining);
|
||||
spa_list_init (&remaining);
|
||||
}
|
||||
else if (!spa_list_is_empty (&remaining)) {
|
||||
/* if we did not make any progress towards growing the result list,
|
||||
it means the dependencies cannot be satisfied because of circles */
|
||||
wp_critical_boxed (WP_TYPE_EVENT, event, "detected circular "
|
||||
"dependencies in the collected hooks!");
|
||||
|
||||
/* clean up */
|
||||
spa_list_consume (hook_data, &result, link) {
|
||||
spa_list_remove (&hook_data->link);
|
||||
hook_data_free (hook_data);
|
||||
}
|
||||
spa_list_consume (hook_data, &remaining, link) {
|
||||
spa_list_remove (&hook_data->link);
|
||||
hook_data_free (hook_data);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spa_list_insert_list (&event->hooks, &result);
|
||||
return !spa_list_is_empty (&event->hooks);
|
||||
return event->hooks->len > 0;
|
||||
}
|
||||
|
||||
struct event_hooks_iterator_data
|
||||
{
|
||||
WpEvent *event;
|
||||
HookData *cur;
|
||||
};
|
||||
|
||||
static void
|
||||
event_hooks_iterator_reset (WpIterator *it)
|
||||
{
|
||||
struct event_hooks_iterator_data *it_data = wp_iterator_get_user_data (it);
|
||||
struct spa_list *list = &it_data->event->hooks;
|
||||
|
||||
if (!spa_list_is_empty (list))
|
||||
it_data->cur = spa_list_first (&it_data->event->hooks, HookData, link);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
event_hooks_iterator_next (WpIterator *it, GValue *item)
|
||||
{
|
||||
struct event_hooks_iterator_data *it_data = wp_iterator_get_user_data (it);
|
||||
struct spa_list *list = &it_data->event->hooks;
|
||||
|
||||
if (!spa_list_is_empty (list) &&
|
||||
!spa_list_is_end (it_data->cur, list, link)) {
|
||||
g_value_init (item, WP_TYPE_EVENT_HOOK);
|
||||
g_value_set_object (item, it_data->cur->hook);
|
||||
it_data->cur = spa_list_next (it_data->cur, link);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
event_hooks_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret,
|
||||
gpointer data)
|
||||
{
|
||||
struct event_hooks_iterator_data *it_data = wp_iterator_get_user_data (it);
|
||||
struct spa_list *list = &it_data->event->hooks;
|
||||
HookData *hook_data;
|
||||
|
||||
if (!spa_list_is_empty (list)) {
|
||||
spa_list_for_each (hook_data, list, link) {
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
g_value_init (&item, WP_TYPE_EVENT_HOOK);
|
||||
g_value_set_object (&item, hook_data->hook);
|
||||
if (!func (&item, ret, data))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
event_hooks_iterator_finalize (WpIterator *it)
|
||||
{
|
||||
struct event_hooks_iterator_data *it_data = wp_iterator_get_user_data (it);
|
||||
wp_event_unref (it_data->event);
|
||||
}
|
||||
|
||||
static const WpIteratorMethods event_hooks_iterator_methods = {
|
||||
.version = WP_ITERATOR_METHODS_VERSION,
|
||||
.reset = event_hooks_iterator_reset,
|
||||
.next = event_hooks_iterator_next,
|
||||
.fold = event_hooks_iterator_fold,
|
||||
.finalize = event_hooks_iterator_finalize,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Returns an iterator that iterates over all the hooks that were
|
||||
* collected by wp_event_collect_hooks()
|
||||
|
|
@ -558,15 +339,8 @@ static const WpIteratorMethods event_hooks_iterator_methods = {
|
|||
WpIterator *
|
||||
wp_event_new_hooks_iterator (WpEvent * event)
|
||||
{
|
||||
WpIterator *it = NULL;
|
||||
struct event_hooks_iterator_data *it_data;
|
||||
GPtrArray *hooks;
|
||||
hooks = g_ptr_array_copy (event->hooks, (GCopyFunc) g_object_ref, NULL);
|
||||
return wp_iterator_new_ptr_array (hooks, WP_TYPE_EVENT_HOOK);
|
||||
|
||||
g_return_val_if_fail (event != NULL, NULL);
|
||||
|
||||
it = wp_iterator_new (&event_hooks_iterator_methods,
|
||||
sizeof (struct event_hooks_iterator_data));
|
||||
it_data = wp_iterator_get_user_data (it);
|
||||
it_data->event = wp_event_ref (event);
|
||||
event_hooks_iterator_reset (it);
|
||||
return it;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -881,3 +881,51 @@ wp_object_interest_matches_full (WpObjectInterest * self,
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Finds all the defined constraint values for a subject in \a self.
|
||||
*
|
||||
* A defined constraint value is the value of a constraint with the 'equal' or
|
||||
* 'in-list' verb, because the full value must be defined with those verbs. This
|
||||
* can be useful for cases where we want to enumerate interests that are
|
||||
* interested in specific subjects.
|
||||
*
|
||||
* \ingroup wpobjectinterest
|
||||
* \param self the object interest
|
||||
* \param type the constraint type
|
||||
* \param subject the subject that the constraint applies to
|
||||
* \returns (element-type GVariant) (transfer full) (nullable): the defined
|
||||
* constraint values for this object interest.
|
||||
* \since 0.5.13
|
||||
*/
|
||||
GPtrArray *
|
||||
wp_object_interest_find_defined_constraint_values (WpObjectInterest * self,
|
||||
WpConstraintType type, const gchar * subject)
|
||||
{
|
||||
GPtrArray *res = g_ptr_array_new_with_free_func (
|
||||
(GDestroyNotify)g_variant_unref);
|
||||
struct constraint *c;
|
||||
|
||||
pw_array_for_each (c, &self->constraints) {
|
||||
if ((c->type == type || WP_CONSTRAINT_TYPE_NONE == type) &&
|
||||
g_str_equal (c->subject, subject)) {
|
||||
switch (c->verb) {
|
||||
case WP_CONSTRAINT_VERB_EQUALS:
|
||||
g_ptr_array_add (res, g_variant_ref (c->value));
|
||||
break;
|
||||
case WP_CONSTRAINT_VERB_IN_LIST: {
|
||||
GVariantIter iter;
|
||||
GVariant *child;
|
||||
g_variant_iter_init (&iter, c->value);
|
||||
while ((child = g_variant_iter_next_value (&iter)))
|
||||
g_ptr_array_add (res, child);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ WpInterestMatch wp_object_interest_matches_full (WpObjectInterest * self,
|
|||
WpInterestMatchFlags flags, GType object_type, gpointer object,
|
||||
WpProperties * pw_props, WpProperties * pw_global_props);
|
||||
|
||||
WP_API
|
||||
GPtrArray * wp_object_interest_find_defined_constraint_values (
|
||||
WpObjectInterest * self, WpConstraintType type, const gchar * subject);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpObjectInterest, wp_object_interest_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
|||
|
|
@ -783,7 +783,7 @@ wp_pw_object_mixin_handle_event_info (gpointer instance, gconstpointer update)
|
|||
G_STRUCT_MEMBER (const struct spa_dict *, d->info, iface->props_offset);
|
||||
|
||||
g_clear_pointer (&d->properties, wp_properties_unref);
|
||||
d->properties = wp_properties_new_wrap_dict (props);
|
||||
d->properties = wp_properties_new_copy_dict (props);
|
||||
|
||||
g_object_notify (G_OBJECT (instance), "properties");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <spa/utils/cleanup.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "proc-utils.h"
|
||||
|
|
@ -145,6 +147,21 @@ wp_proc_info_get_cgroup (WpProcInfo * self)
|
|||
return self->cgroup;
|
||||
}
|
||||
|
||||
static FILE *
|
||||
fdopenat (int dirfd, const char *path, int flags, const char *mode, mode_t perm)
|
||||
{
|
||||
int fd = openat (dirfd, path, flags, perm);
|
||||
if (fd >= 0) {
|
||||
FILE *f = fdopen (fd, mode);
|
||||
if (f)
|
||||
return f;
|
||||
close (fd);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Gets the process information of a given PID
|
||||
* \ingroup wpprocutils
|
||||
|
|
@ -155,51 +172,46 @@ WpProcInfo *
|
|||
wp_proc_utils_get_proc_info (pid_t pid)
|
||||
{
|
||||
WpProcInfo *ret = wp_proc_info_new (pid);
|
||||
g_autofree gchar *status = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
gsize length = 0;
|
||||
char path [64];
|
||||
spa_autoclose int base_fd = -1;
|
||||
FILE *file;
|
||||
g_autofree gchar *line = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
snprintf (path, sizeof(path), "/proc/%d", pid);
|
||||
base_fd = open (path,
|
||||
O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY, 0);
|
||||
if (base_fd < 0) {
|
||||
wp_info ("Could not open process info directory %s, skipping", path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get parent PID */
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/status", pid);
|
||||
if (g_file_get_contents (path, &status, &length, &error)) {
|
||||
const gchar *loc = strstr (status, "\nPPid:");
|
||||
if (loc) {
|
||||
const gint res = sscanf (loc, "\nPPid:%d\n", &ret->parent);
|
||||
if (!res || res == EOF)
|
||||
wp_warning ("failed to parse status PPID for PID %d", pid);
|
||||
} else {
|
||||
wp_warning ("failed to find status parent PID for PID %d", pid);
|
||||
}
|
||||
} else {
|
||||
wp_warning ("failed to get status for PID %d: %s", pid, error->message);
|
||||
}
|
||||
file = fdopenat (base_fd, "status",
|
||||
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||
if (file) {
|
||||
while (getline (&line, &size, file) > 1)
|
||||
if (sscanf (line, "PPid:%d\n", &ret->parent) == 1)
|
||||
break;
|
||||
fclose (file);
|
||||
}
|
||||
|
||||
/* Get cgroup */
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/cgroup", pid);
|
||||
if (g_file_get_contents (path, &ret->cgroup, &length, &error)) {
|
||||
if (length > 0)
|
||||
ret->cgroup [length - 1] = '\0'; /* Remove EOF character */
|
||||
} else {
|
||||
wp_warning ("failed to get cgroup for PID %d: %s", pid, error->message);
|
||||
}
|
||||
file = fdopenat (base_fd, "cgroup",
|
||||
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||
if (file) {
|
||||
if (getline (&line, &size, file) > 1)
|
||||
ret->cgroup = g_strstrip (g_strdup (line));
|
||||
fclose (file);
|
||||
}
|
||||
|
||||
/* Get args */
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/cmdline", pid);
|
||||
FILE *file = fopen (path, "rb");
|
||||
if (file) {
|
||||
g_autofree gchar *lineptr = NULL;
|
||||
size_t size = 0;
|
||||
while (getdelim (&lineptr, &size, 0, file) > 1 && ret->n_args < MAX_ARGS)
|
||||
ret->args[ret->n_args++] = g_strdup (lineptr);
|
||||
fclose (file);
|
||||
} else {
|
||||
wp_warning ("failed to get cmdline for PID %d: %m", pid);
|
||||
}
|
||||
file = fdopenat (base_fd, "cmdline",
|
||||
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||
if (file) {
|
||||
while (getdelim (&line, &size, 0, file) > 1 && ret->n_args < MAX_ARGS)
|
||||
ret->args[ret->n_args++] = g_strdup (line);
|
||||
fclose (file);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
18
meson.build
18
meson.build
|
|
@ -158,7 +158,25 @@ common_args = [
|
|||
'-DG_LOG_USE_STRUCTURED',
|
||||
'-DWP_USE_LOCAL_LOG_TOPIC_IN_G_LOG',
|
||||
]
|
||||
|
||||
# Check if SPA_AUDIO_MAX_CHANNELS can be overridden
|
||||
# (newer headers have #ifndef guards, older ones don't)
|
||||
check_spa_max_channels_override = '''
|
||||
#define SPA_AUDIO_MAX_CHANNELS 128u
|
||||
#include <spa/param/audio/raw.h>
|
||||
void main() { int x = SPA_AUDIO_MAX_CHANNELS; }
|
||||
'''
|
||||
spa_max_channels = 64
|
||||
if cc.compiles(check_spa_max_channels_override,
|
||||
dependencies: spa_dep,
|
||||
args: ['-Werror'],
|
||||
name: 'SPA_AUDIO_MAX_CHANNELS override')
|
||||
common_args += ['-DSPA_AUDIO_MAX_CHANNELS=128u']
|
||||
spa_max_channels = 128
|
||||
endif
|
||||
|
||||
add_project_arguments(common_args, language: 'c')
|
||||
summary({'SPA_AUDIO_MAX_CHANNELS': spa_max_channels})
|
||||
|
||||
i18n_conf = files()
|
||||
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ static int
|
|||
core_get_properties (lua_State *L)
|
||||
{
|
||||
WpCore * core = get_wp_core (L);
|
||||
g_autoptr (WpProperties) p = wp_core_get_properties (core);
|
||||
wplua_properties_to_table (L, p);
|
||||
WpProperties *p = wp_core_get_properties (core);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ static int
|
|||
core_get_info (lua_State *L)
|
||||
{
|
||||
WpCore * core = get_wp_core (L);
|
||||
g_autoptr (WpProperties) p = wp_core_get_remote_properties (core);
|
||||
WpProperties *p = wp_core_get_remote_properties (core);
|
||||
|
||||
lua_newtable (L);
|
||||
lua_pushinteger (L, wp_core_get_remote_cookie (core));
|
||||
|
|
@ -168,7 +168,7 @@ core_get_info (lua_State *L)
|
|||
lua_setfield (L, -2, "host_name");
|
||||
lua_pushstring (L, wp_core_get_remote_version (core));
|
||||
lua_setfield (L, -2, "version");
|
||||
wplua_properties_to_table (L, p);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, p);
|
||||
lua_setfield (L, -2, "properties");
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -297,8 +297,13 @@ static int
|
|||
core_update_properties (lua_State *L)
|
||||
{
|
||||
WpCore *core = get_wp_core(L);
|
||||
luaL_checktype (L, 1, LUA_TTABLE);
|
||||
wp_core_update_properties (core, wplua_table_to_properties (L, 1));
|
||||
WpProperties *props = NULL;
|
||||
if (lua_istable (L, 1))
|
||||
props = wplua_table_to_properties (L, 1);
|
||||
else
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES));
|
||||
|
||||
wp_core_update_properties (core, props);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -599,6 +604,28 @@ push_wpiterator (lua_State *L, WpIterator *it)
|
|||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
iterator_reset (lua_State *L)
|
||||
{
|
||||
WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
|
||||
wp_iterator_reset (it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
iterator_iterate (lua_State *L)
|
||||
{
|
||||
WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
|
||||
return push_wpiterator (L, wp_iterator_ref (it));
|
||||
}
|
||||
|
||||
static const luaL_Reg iterator_funcs[] = {
|
||||
{ "next", iterator_next },
|
||||
{ "reset", iterator_reset },
|
||||
{ "iterate", iterator_iterate },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* Settings WpIterator */
|
||||
|
||||
static int
|
||||
|
|
@ -837,7 +864,11 @@ object_interest_matches (lua_State *L)
|
|||
matches = wp_object_interest_matches (interest, wplua_toobject (L, 2));
|
||||
}
|
||||
else if (lua_istable (L, 2)) {
|
||||
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
if (lua_istable (L, 2))
|
||||
props = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
matches = wp_object_interest_matches (interest, props);
|
||||
} else
|
||||
luaL_argerror (L, 2, "expected GObject or table");
|
||||
|
|
@ -997,10 +1028,11 @@ impl_metadata_new (lua_State *L)
|
|||
const char *name = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpImplMetadata *m = wp_impl_metadata_new_full (get_wp_core (L),
|
||||
name, properties);
|
||||
|
|
@ -1017,10 +1049,11 @@ device_new (lua_State *L)
|
|||
const char *factory = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpDevice *d = wp_device_new_from_factory (get_wp_export_core (L),
|
||||
factory, properties);
|
||||
|
|
@ -1037,10 +1070,11 @@ spa_device_new (lua_State *L)
|
|||
const char *factory = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpSpaDevice *d = wp_spa_device_new_from_spa_factory (get_wp_export_core (L),
|
||||
factory, properties);
|
||||
|
|
@ -1105,10 +1139,11 @@ node_new (lua_State *L)
|
|||
const char *factory = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (
|
||||
wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
WpNode *d = wp_node_new_from_factory (get_wp_export_core (L),
|
||||
factory, properties);
|
||||
|
|
@ -1214,10 +1249,11 @@ impl_node_new (lua_State *L)
|
|||
const char *factory = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpImplNode *d = wp_impl_node_new_from_pw_factory (get_wp_export_core (L),
|
||||
factory, properties);
|
||||
|
|
@ -1250,10 +1286,11 @@ link_new (lua_State *L)
|
|||
const char *factory = luaL_checkstring (L, 1);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpLink *l = wp_link_new_from_factory (get_wp_core (L), factory, properties);
|
||||
if (l)
|
||||
|
|
@ -1329,9 +1366,12 @@ static int
|
|||
client_update_properties (lua_State *L)
|
||||
{
|
||||
WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
|
||||
WpProperties *properties = NULL;
|
||||
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
WpProperties *properties = wplua_table_to_properties (L, 2);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
wp_client_update_properties (client, properties);
|
||||
return 0;
|
||||
|
|
@ -1391,46 +1431,12 @@ static int
|
|||
session_item_configure (lua_State *L)
|
||||
{
|
||||
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
|
||||
WpProperties *props = wp_properties_new_empty ();
|
||||
WpProperties *props;
|
||||
|
||||
/* validate arguments */
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
|
||||
/* build the configuration properties */
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, 2)) {
|
||||
const gchar *key = NULL;
|
||||
g_autofree gchar *var = NULL;
|
||||
|
||||
switch (lua_type (L, -1)) {
|
||||
case LUA_TBOOLEAN:
|
||||
var = g_strdup_printf ("%u", lua_toboolean (L, -1));
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
if (lua_isinteger (L, -1))
|
||||
var = g_strdup_printf ("%lld", lua_tointeger (L, -1));
|
||||
else
|
||||
var = g_strdup_printf ("%f", lua_tonumber (L, -1));
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
var = g_strdup (lua_tostring (L, -1));
|
||||
break;
|
||||
case LUA_TUSERDATA: {
|
||||
GValue *v = lua_touserdata (L, -1);
|
||||
gpointer p = g_value_peek_pointer (v);
|
||||
var = g_strdup_printf ("%p", p);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
luaL_error (L, "configure does not support lua type ",
|
||||
lua_typename(L, lua_type(L, -1)));
|
||||
break;
|
||||
}
|
||||
|
||||
key = luaL_tolstring (L, -2, NULL);
|
||||
wp_properties_set (props, key, var);
|
||||
lua_pop (L, 2);
|
||||
}
|
||||
if (lua_istable (L, 2))
|
||||
props = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
lua_pushboolean (L, wp_session_item_configure (si, props));
|
||||
return 1;
|
||||
|
|
@ -1452,12 +1458,23 @@ session_item_remove (lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
session_item_get_property (lua_State *L)
|
||||
{
|
||||
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
|
||||
const char *key = luaL_checkstring (L, 2);
|
||||
const char *val = wp_session_item_get_property (si, key);
|
||||
lua_pushstring (L, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg session_item_methods[] = {
|
||||
{ "get_associated_proxy", session_item_get_associated_proxy },
|
||||
{ "reset", session_item_reset },
|
||||
{ "configure", session_item_configure },
|
||||
{ "register", session_item_register },
|
||||
{ "remove", session_item_remove },
|
||||
{ "get_property", session_item_get_property },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
@ -1527,19 +1544,24 @@ on_enum_params_done (WpPipewireObject * pwobj, GAsyncResult * res,
|
|||
GClosure * closure)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
GValue val = G_VALUE_INIT;
|
||||
int n_vals = 0;
|
||||
GValue vals[2] = { G_VALUE_INIT, G_VALUE_INIT };
|
||||
int n_vals = 1;
|
||||
WpIterator *it;
|
||||
|
||||
it = wp_pipewire_object_enum_params_finish (pwobj, res, &error);
|
||||
g_value_init (&vals[0], WP_TYPE_ITERATOR);
|
||||
g_value_set_boxed (&vals[0], it);
|
||||
if (!it) {
|
||||
g_value_init (&val, G_TYPE_STRING);
|
||||
g_value_set_string (&val, error->message);
|
||||
n_vals = 1;
|
||||
g_value_init (&vals[1], G_TYPE_STRING);
|
||||
g_value_set_string (&vals[1], error->message);
|
||||
n_vals = 2;
|
||||
}
|
||||
|
||||
g_clear_pointer (&it, wp_iterator_unref);
|
||||
g_closure_invoke (closure, NULL, n_vals, &val, NULL);
|
||||
g_value_unset (&val);
|
||||
g_closure_invoke (closure, NULL, n_vals, vals, NULL);
|
||||
|
||||
g_value_unset (&vals[0]);
|
||||
g_value_unset (&vals[1]);
|
||||
g_closure_invalidate (closure);
|
||||
g_closure_unref (closure);
|
||||
}
|
||||
|
|
@ -1575,11 +1597,22 @@ pipewire_object_set_param (lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pipewire_object_get_property (lua_State *L)
|
||||
{
|
||||
WpPipewireObject *pwobj = wplua_checkobject (L, 1, WP_TYPE_PIPEWIRE_OBJECT);
|
||||
const char *key = luaL_checkstring (L, 2);
|
||||
const char *val = wp_pipewire_object_get_property (pwobj, key);
|
||||
lua_pushstring (L, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg pipewire_object_methods[] = {
|
||||
{ "enum_params", pipewire_object_enum_params },
|
||||
{ "iterate_params", pipewire_object_iterate_params },
|
||||
{ "set_param" , pipewire_object_set_param },
|
||||
{ "set_params" , pipewire_object_set_param }, /* deprecated, compat only */
|
||||
{ "get_property", pipewire_object_get_property },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
@ -1606,9 +1639,14 @@ static int
|
|||
state_save (lua_State *L)
|
||||
{
|
||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (lua_istable (L, 2))
|
||||
props = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
gboolean saved = wp_state_save (state, props, &error);
|
||||
lua_pushboolean (L, saved);
|
||||
lua_pushstring (L, error ? error->message : "");
|
||||
|
|
@ -1619,8 +1657,13 @@ static int
|
|||
state_save_after_timeout (lua_State *L)
|
||||
{
|
||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
|
||||
if (lua_istable (L, 2))
|
||||
props = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
wp_state_save_after_timeout (state, get_wp_core (L), props);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1629,8 +1672,8 @@ static int
|
|||
state_load (lua_State *L)
|
||||
{
|
||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
||||
wplua_properties_to_table (L, props);
|
||||
WpProperties *props = wp_state_load (state);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -1655,10 +1698,11 @@ impl_module_new (lua_State *L)
|
|||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL)
|
||||
args = luaL_checkstring (L, 2);
|
||||
|
||||
if (lua_type (L, 3) != LUA_TNONE && lua_type (L, 3) != LUA_TNIL) {
|
||||
luaL_checktype (L, 3, LUA_TTABLE);
|
||||
if (lua_istable (L, 3))
|
||||
properties = wplua_table_to_properties (L, 3);
|
||||
}
|
||||
else if (!lua_isnone (L, 3) && !lua_isnil (L, 3))
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 3,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
WpImplModule *m = wp_impl_module_load (get_wp_export_core (L),
|
||||
name, args, properties);
|
||||
|
|
@ -1681,9 +1725,10 @@ conf_new (lua_State *L)
|
|||
WpProperties *p = NULL;
|
||||
WpConf *conf = NULL;
|
||||
|
||||
if (lua_istable (L, 2)) {
|
||||
if (lua_istable (L, 2))
|
||||
p = wplua_table_to_properties (L, 2);
|
||||
}
|
||||
else if (!lua_isnone (L, 2) && !lua_isnil (L, 2))
|
||||
p = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
conf = wp_conf_new (path, p);
|
||||
if (conf) {
|
||||
|
|
@ -1721,7 +1766,7 @@ conf_get_section_as_properties (lua_State *L)
|
|||
const char *section = NULL;
|
||||
g_autoptr (WpConf) conf = NULL;
|
||||
g_autoptr (WpSpaJson) s = NULL;
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
WpProperties *props = NULL;
|
||||
int argi = 1;
|
||||
|
||||
/* check if called as method on object */
|
||||
|
|
@ -1736,6 +1781,8 @@ conf_get_section_as_properties (lua_State *L)
|
|||
|
||||
if (lua_istable (L, argi))
|
||||
props = wplua_table_to_properties (L, argi);
|
||||
else if (!lua_isnone (L, argi) && !lua_isnil (L, argi))
|
||||
props = wp_properties_ref (wplua_checkboxed (L, argi, WP_TYPE_PROPERTIES));
|
||||
else
|
||||
props = wp_properties_new_empty ();
|
||||
|
||||
|
|
@ -1744,7 +1791,7 @@ conf_get_section_as_properties (lua_State *L)
|
|||
if (s && wp_spa_json_is_object (s))
|
||||
wp_properties_update_from_json (props, s);
|
||||
}
|
||||
wplua_properties_to_table (L, props);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -1901,10 +1948,12 @@ json_utils_match_rules (lua_State *L)
|
|||
gboolean res;
|
||||
|
||||
json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
luaL_checktype (L, 3, LUA_TFUNCTION);
|
||||
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_PROPERTIES));
|
||||
|
||||
res = wp_json_utils_match_rules (json, properties, json_utils_match_rules_cb,
|
||||
L, &error);
|
||||
|
|
@ -1920,17 +1969,21 @@ json_utils_match_rules (lua_State *L)
|
|||
static int
|
||||
json_utils_match_rules_update_properties (lua_State *L)
|
||||
{
|
||||
g_autoptr (WpProperties) properties = NULL;
|
||||
WpProperties *properties = NULL;
|
||||
WpSpaJson *json;
|
||||
int count;
|
||||
|
||||
json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
|
||||
luaL_checktype (L, 2, LUA_TTABLE);
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
|
||||
if (lua_istable (L, 2))
|
||||
properties = wplua_table_to_properties (L, 2);
|
||||
else
|
||||
properties = wp_properties_ref (wplua_checkboxed (L, 2,
|
||||
WP_TYPE_PROPERTIES));
|
||||
|
||||
count = wp_json_utils_match_rules_update_properties (json, properties);
|
||||
|
||||
wplua_properties_to_table (L, properties);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, properties);
|
||||
lua_pushinteger (L, count);
|
||||
return 2;
|
||||
}
|
||||
|
|
@ -2012,6 +2065,108 @@ static const luaL_Reg proc_utils_funcs[] = {
|
|||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* Properties */
|
||||
|
||||
static int
|
||||
properties_new (lua_State *L)
|
||||
{
|
||||
WpProperties *props;
|
||||
|
||||
if (lua_istable (L, 1))
|
||||
props = wplua_table_to_properties (L, 1);
|
||||
else if (!lua_isnone (L, 1) && !lua_isnil (L, 1))
|
||||
props = wp_properties_ref (wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES));
|
||||
else
|
||||
props = wp_properties_new_empty ();
|
||||
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_get_boolean (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
const char *key = luaL_checkstring (L, 2);
|
||||
const char *val = wp_properties_get (props, key);
|
||||
if (val)
|
||||
lua_pushboolean (L, spa_atob (val));
|
||||
else
|
||||
lua_pushnil (L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_get_int (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
const char *key = luaL_checkstring (L, 2);
|
||||
const char *val = wp_properties_get (props, key);
|
||||
if (val) {
|
||||
gint64 int_val = 0;
|
||||
if (spa_atoi64 (val, &int_val, 10))
|
||||
lua_pushinteger (L, int_val);
|
||||
else
|
||||
lua_pushnil (L);
|
||||
} else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_get_float (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
const char *key = luaL_checkstring (L, 2);
|
||||
const char *val = wp_properties_get (props, key);
|
||||
if (val) {
|
||||
double d_val = 0;
|
||||
if (spa_atod (val, &d_val))
|
||||
lua_pushnumber (L, d_val);
|
||||
else
|
||||
lua_pushnil (L);
|
||||
} else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_get_count (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
lua_pushinteger (L, wp_properties_get_count (props));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_copy (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
WpProperties *copy = wp_properties_copy (props);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, copy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_parse (lua_State *L)
|
||||
{
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
wplua_properties_to_table (L, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg properties_funcs[] = {
|
||||
{ "get_boolean", properties_get_boolean },
|
||||
{ "get_int", properties_get_int },
|
||||
{ "get_float", properties_get_float },
|
||||
{ "get_count", properties_get_count },
|
||||
{ "copy", properties_copy },
|
||||
{ "parse", properties_parse },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* WpSettings */
|
||||
|
||||
static int
|
||||
|
|
@ -2305,8 +2460,8 @@ static int
|
|||
event_get_properties (lua_State *L)
|
||||
{
|
||||
WpEvent *event = wplua_checkboxed (L, 1, WP_TYPE_EVENT);
|
||||
g_autoptr (WpProperties) props = wp_event_get_properties (event);
|
||||
wplua_properties_to_table (L, props);
|
||||
WpProperties *props = wp_event_get_properties (event);
|
||||
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -2428,10 +2583,11 @@ event_dispatcher_push_event (lua_State *L)
|
|||
lua_pop (L, 1);
|
||||
|
||||
lua_pushliteral (L, "properties");
|
||||
if (lua_gettable (L, 1) != LUA_TNIL) {
|
||||
luaL_checktype (L, -1, LUA_TTABLE);
|
||||
if (lua_istable (L, -1))
|
||||
properties = wplua_table_to_properties (L, -1);
|
||||
}
|
||||
else if (!lua_isnil (L, -1) && !lua_isnone (L, -1) && !lua_isstring (L, -1))
|
||||
properties = wp_properties_ref (
|
||||
wplua_checkboxed (L, -1, WP_TYPE_PROPERTIES));
|
||||
lua_pop (L, 1);
|
||||
|
||||
lua_pushliteral (L, "source");
|
||||
|
|
@ -2984,6 +3140,10 @@ wp_lua_scripting_api_init (lua_State *L)
|
|||
conf_new, conf_methods);
|
||||
wplua_register_type_methods (L, WP_TYPE_PROC_INFO,
|
||||
NULL, proc_info_funcs);
|
||||
wplua_register_type_methods (L, WP_TYPE_ITERATOR,
|
||||
NULL, iterator_funcs);
|
||||
wplua_register_type_methods (L, WP_TYPE_PROPERTIES,
|
||||
properties_new, properties_funcs);
|
||||
|
||||
if (!wplua_load_uri (L, URI_API, &error) ||
|
||||
!wplua_pcall (L, 0, 0, &error)) {
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ SANDBOX_EXPORT = {
|
|||
Conf = WpConf,
|
||||
JsonUtils = JsonUtils,
|
||||
ProcUtils = ProcUtils,
|
||||
Properties = WpProperties_new,
|
||||
SimpleEventHook = WpSimpleEventHook_new,
|
||||
AsyncEventHook = WpAsyncEventHook_new,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,40 +300,54 @@ spa_json_object_new (lua_State *L)
|
|||
{
|
||||
g_autoptr (WpSpaJsonBuilder) builder = wp_spa_json_builder_new_object ();
|
||||
|
||||
luaL_checktype (L, 1, LUA_TTABLE);
|
||||
if (lua_istable (L, 1)) {
|
||||
luaL_checktype (L, 1, LUA_TTABLE);
|
||||
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, -2)) {
|
||||
/* We only add table values with string keys */
|
||||
if (lua_type (L, -2) == LUA_TSTRING) {
|
||||
wp_spa_json_builder_add_property (builder, lua_tostring (L, -2));
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, -2)) {
|
||||
/* We only add table values with string keys */
|
||||
if (lua_type (L, -2) == LUA_TSTRING) {
|
||||
wp_spa_json_builder_add_property (builder, lua_tostring (L, -2));
|
||||
|
||||
switch (lua_type (L, -1)) {
|
||||
case LUA_TBOOLEAN:
|
||||
wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
if (lua_isinteger (L, -1))
|
||||
wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
|
||||
else
|
||||
wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
|
||||
break;
|
||||
case LUA_TUSERDATA: {
|
||||
WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
|
||||
wp_spa_json_builder_add_json (builder, json);
|
||||
break;
|
||||
switch (lua_type (L, -1)) {
|
||||
case LUA_TBOOLEAN:
|
||||
wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
if (lua_isinteger (L, -1))
|
||||
wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
|
||||
else
|
||||
wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
|
||||
break;
|
||||
case LUA_TUSERDATA: {
|
||||
WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
|
||||
wp_spa_json_builder_add_json (builder, json);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
luaL_error (L, "Json does not support lua type %s",
|
||||
lua_typename(L, lua_type(L, -1)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
luaL_error (L, "Json does not support lua type %s",
|
||||
lua_typename(L, lua_type(L, -1)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lua_pop (L, 1);
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
} else {
|
||||
WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES);
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
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 *value = wp_properties_item_get_value (pi);
|
||||
wp_spa_json_builder_add_property (builder, key);
|
||||
wp_spa_json_builder_add_string (builder, value);
|
||||
}
|
||||
}
|
||||
|
||||
wplua_pushboxed (L, WP_TYPE_SPA_JSON, wp_spa_json_builder_end (builder));
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ _wplua_gboxed___index (lua_State *L)
|
|||
GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed");
|
||||
luaL_argcheck (L, obj_v != NULL, 1,
|
||||
"expected userdata storing GValue<GBoxed>");
|
||||
const gchar *key = luaL_checkstring (L, 2);
|
||||
const gchar *key = luaL_tolstring (L, 2, NULL);
|
||||
GType type = G_VALUE_TYPE (obj_v);
|
||||
GType boxed_type = type;
|
||||
lua_CFunction func = NULL;
|
||||
GHashTable *vtables;
|
||||
|
||||
|
|
@ -53,6 +54,104 @@ _wplua_gboxed___index (lua_State *L)
|
|||
lua_pushcfunction (L, func);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If WpProperties type, just return the property value for that key */
|
||||
if (boxed_type == WP_TYPE_PROPERTIES) {
|
||||
WpProperties * props = g_value_get_boxed (obj_v);
|
||||
const gchar *val = wp_properties_get (props, key);
|
||||
lua_pushstring (L, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gboxed___newindex (lua_State *L)
|
||||
{
|
||||
GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed");
|
||||
luaL_argcheck (L, obj_v != NULL, 1,
|
||||
"expected userdata storing GValue<GBoxed>");
|
||||
const gchar *key = luaL_tolstring (L, 2, NULL);
|
||||
GType type = G_VALUE_TYPE (obj_v);
|
||||
|
||||
/* Set property value */
|
||||
if (type == WP_TYPE_PROPERTIES) {
|
||||
WpProperties * props = g_value_dup_boxed (obj_v);
|
||||
g_autofree gchar *val = NULL;
|
||||
luaL_checkany (L, 3);
|
||||
|
||||
switch (lua_type (L, 3)) {
|
||||
case LUA_TNIL:
|
||||
break;
|
||||
case LUA_TUSERDATA: {
|
||||
if (wplua_gvalue_userdata_type (L, 3) != G_TYPE_INVALID) {
|
||||
GValue *v = lua_touserdata (L, 3);
|
||||
gpointer p = g_value_peek_pointer (v);
|
||||
val = g_strdup_printf ("%p", p);
|
||||
break;
|
||||
} else {
|
||||
val = g_strdup (luaL_tolstring (L, 3, NULL));
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
val = g_strdup (luaL_tolstring (L, 3, NULL));
|
||||
break;
|
||||
}
|
||||
|
||||
props = wp_properties_ensure_unique_owner (props);
|
||||
wp_properties_set (props, key, val);
|
||||
g_value_take_boxed (obj_v, props);
|
||||
} else {
|
||||
luaL_error (L, "cannot assign property '%s' to boxed type %s",
|
||||
key, g_type_name (type));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
properties_iterator_next (lua_State *L)
|
||||
{
|
||||
WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR);
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
if (wp_iterator_next (it, &item)) {
|
||||
WpPropertiesItem *si = g_value_get_boxed (&item);
|
||||
const gchar *k = wp_properties_item_get_key (si);
|
||||
const gchar *v = wp_properties_item_get_value (si);
|
||||
lua_pushstring (L, k);
|
||||
lua_pushstring (L, v);
|
||||
return 2;
|
||||
} else {
|
||||
lua_pushnil (L);
|
||||
lua_pushnil (L);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
push_properties_wpiterator (lua_State *L, WpIterator *it)
|
||||
{
|
||||
lua_pushcfunction (L, properties_iterator_next);
|
||||
wplua_pushboxed (L, WP_TYPE_ITERATOR, it);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
_wplua_gboxed___pairs (lua_State *L)
|
||||
{
|
||||
GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed");
|
||||
luaL_argcheck (L, obj_v != NULL, 1,
|
||||
"expected userdata storing GValue<GBoxed>");
|
||||
GType type = G_VALUE_TYPE (obj_v);
|
||||
|
||||
if (type == WP_TYPE_PROPERTIES) {
|
||||
WpProperties * props = g_value_get_boxed (obj_v);
|
||||
WpIterator *it = wp_properties_new_iterator (props);
|
||||
return push_properties_wpiterator (L, it);
|
||||
} else {
|
||||
luaL_error (L, "cannot do pairs of boxed type %s", g_type_name (type));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +168,8 @@ _wplua_init_gboxed (lua_State *L)
|
|||
{ "__gc", _wplua_gvalue_userdata___gc },
|
||||
{ "__eq", _wplua_gboxed___eq },
|
||||
{ "__index", _wplua_gboxed___index },
|
||||
{ "__newindex", _wplua_gboxed___newindex },
|
||||
{ "__pairs", _wplua_gboxed___pairs },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ WpProperties *
|
|||
wplua_table_to_properties (lua_State *L, int idx)
|
||||
{
|
||||
WpProperties *p = wp_properties_new_empty ();
|
||||
const gchar *key, *value;
|
||||
int table = lua_absindex (L, idx);
|
||||
|
||||
if (lua_type (L, table) != LUA_TTABLE) {
|
||||
|
|
@ -24,11 +23,34 @@ wplua_table_to_properties (lua_State *L, int idx)
|
|||
|
||||
lua_pushnil(L);
|
||||
while (lua_next (L, table) != 0) {
|
||||
const gchar *key = luaL_tolstring (L, -2, NULL);
|
||||
g_autofree gchar *value = NULL;
|
||||
|
||||
/* copy key & value to convert them to string */
|
||||
key = luaL_tolstring (L, -2, NULL);
|
||||
value = luaL_tolstring (L, -2, NULL);
|
||||
luaL_checkany (L, -2);
|
||||
switch (lua_type (L, -2)) {
|
||||
case LUA_TNIL:
|
||||
lua_pop (L, 2);
|
||||
break;
|
||||
case LUA_TUSERDATA: {
|
||||
if (wplua_gvalue_userdata_type(L, -2) != G_TYPE_INVALID) {
|
||||
GValue *v = lua_touserdata (L, -2);
|
||||
gpointer p = g_value_peek_pointer (v);
|
||||
value = g_strdup_printf ("%p", p);
|
||||
lua_pop (L, 2);
|
||||
} else {
|
||||
value = g_strdup (luaL_tolstring (L, -2, NULL));
|
||||
lua_pop (L, 3);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
value = g_strdup (luaL_tolstring (L, -2, NULL));
|
||||
lua_pop (L, 3);
|
||||
break;
|
||||
}
|
||||
|
||||
wp_properties_set (p, key, value);
|
||||
lua_pop (L, 3);
|
||||
}
|
||||
|
||||
/* sort, because the lua table has a random order and it's too messy to read */
|
||||
|
|
@ -313,10 +335,7 @@ wplua_gvalue_to_lua (lua_State *L, const GValue *v)
|
|||
lua_pushlightuserdata (L, g_value_get_pointer (v));
|
||||
break;
|
||||
case G_TYPE_BOXED:
|
||||
if (G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
|
||||
wplua_properties_to_table (L, g_value_get_boxed (v));
|
||||
else
|
||||
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
|
||||
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
|
||||
break;
|
||||
case G_TYPE_OBJECT:
|
||||
case G_TYPE_INTERFACE: {
|
||||
|
|
|
|||
|
|
@ -103,14 +103,15 @@ static void
|
|||
bind_call (GObject * obj, GAsyncResult * res, gpointer data)
|
||||
{
|
||||
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
||||
GError *err = NULL;
|
||||
g_autoptr (GError) err = NULL;
|
||||
GDBusProxy *call;
|
||||
GVariant *prop;
|
||||
g_autoptr (GVariant) prop = NULL;
|
||||
gint init_state;
|
||||
|
||||
call = g_dbus_proxy_new_finish (res, &err);
|
||||
if (call == NULL) {
|
||||
wp_warning_object (wpmm, "Failed to get call");
|
||||
g_prefix_error (&err, "Failed to get call: ");
|
||||
wp_warning_object (wpmm, "%s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +123,6 @@ bind_call (GObject * obj, GAsyncResult * res, gpointer data)
|
|||
|
||||
if (is_active_state (init_state))
|
||||
active_calls_inc (wpmm);
|
||||
|
||||
g_variant_unref (prop);
|
||||
}
|
||||
|
||||
wpmm->calls = g_list_prepend (wpmm->calls, call);
|
||||
|
|
@ -165,7 +164,7 @@ on_voice_signal (GDBusProxy * iface,
|
|||
g_object_get (wpmm->dbus, "connection", &conn, NULL);
|
||||
|
||||
if (!g_strcmp0 (signal, "CallAdded")) {
|
||||
g_variant_get (params, "(o)", &path);
|
||||
g_variant_get (params, "(&o)", &path);
|
||||
g_dbus_proxy_new (conn,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
|
|
@ -175,9 +174,8 @@ on_voice_signal (GDBusProxy * iface,
|
|||
NULL,
|
||||
bind_call,
|
||||
wpmm);
|
||||
g_free (path);
|
||||
} else if (!g_strcmp0 (signal, "CallDeleted")) {
|
||||
g_variant_get (params, "(o)", &path);
|
||||
g_variant_get (params, "(&o)", &path);
|
||||
|
||||
// The user shouldn't have hundreds of calls, so just linear search.
|
||||
deleted = g_list_find_custom (wpmm->calls, path, match_call_path);
|
||||
|
|
@ -185,8 +183,6 @@ on_voice_signal (GDBusProxy * iface,
|
|||
g_object_unref (deleted->data);
|
||||
wpmm->calls = g_list_delete_link (wpmm->calls, deleted);
|
||||
}
|
||||
|
||||
g_free (path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,22 +192,23 @@ list_calls_done (GObject * obj,
|
|||
gpointer data)
|
||||
{
|
||||
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
||||
GVariant *params;
|
||||
GVariantIter *calls;
|
||||
g_autoptr (GVariant) params = NULL;
|
||||
g_autoptr (GVariantIter) calls = NULL;
|
||||
gchar *path;
|
||||
GError *err = NULL;
|
||||
g_autoptr (GError) err = NULL;
|
||||
g_autoptr (GDBusConnection) conn = NULL;
|
||||
|
||||
params = g_dbus_proxy_call_finish (G_DBUS_PROXY (obj), res, &err);
|
||||
if (params == NULL) {
|
||||
g_prefix_error (&err, "Failed to list active calls on startup: ");
|
||||
wp_warning_object (wpmm, "%s", err->message);
|
||||
g_clear_object (&err);
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_get (wpmm->dbus, "connection", &conn, NULL);
|
||||
|
||||
g_variant_get (params, "(ao)", &calls);
|
||||
while (g_variant_iter_loop (calls, "o", &path)) {
|
||||
while (g_variant_iter_loop (calls, "&o", &path)) {
|
||||
g_dbus_proxy_new (conn,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
|
|
@ -222,9 +219,6 @@ list_calls_done (GObject * obj,
|
|||
bind_call,
|
||||
wpmm);
|
||||
}
|
||||
|
||||
g_variant_iter_free (calls);
|
||||
g_variant_unref (params);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -353,7 +347,7 @@ static void
|
|||
wp_modem_manager_enable (WpPlugin * self, WpTransition * transition)
|
||||
{
|
||||
WpModemManager *wpmm = WP_MODEM_MANAGER (self);
|
||||
WpCore *core;
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
GError *err;
|
||||
g_autoptr (GDBusConnection) conn = NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -137,10 +137,11 @@ si_audio_adapter_get_default_clock_rate (WpSiAudioAdapter * self)
|
|||
static gboolean
|
||||
is_unpositioned (struct spa_audio_info_raw *info)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t i, n_pos;
|
||||
if (SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED))
|
||||
return TRUE;
|
||||
for (i = 0; i < info->channels; i++)
|
||||
n_pos = SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position));
|
||||
for (i = 0; i < n_pos; i++)
|
||||
if (info->position[i] >= SPA_AUDIO_CHANNEL_START_Aux &&
|
||||
info->position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux)
|
||||
return TRUE;
|
||||
|
|
@ -197,7 +198,7 @@ si_audio_adapter_find_format (WpSiAudioAdapter * self, WpNode * node,
|
|||
continue;
|
||||
|
||||
if (position == NULL ||
|
||||
!spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_AUDIO_MAX_CHANNELS))
|
||||
!spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_N_ELEMENTS(raw_format.position)))
|
||||
SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED);
|
||||
|
||||
if (mono) {
|
||||
|
|
@ -349,7 +350,8 @@ format_audio_raw_build (const struct spa_audio_info_raw *info)
|
|||
if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
|
||||
/* Build the position array spa pod */
|
||||
g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
|
||||
for (guint i = 0; i < info->channels; i++)
|
||||
guint n_pos = SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position));
|
||||
for (guint i = 0; i < n_pos; i++)
|
||||
wp_spa_pod_builder_add_id (position_builder, info->position[i]);
|
||||
|
||||
/* Add the position property */
|
||||
|
|
|
|||
|
|
@ -195,12 +195,16 @@ on_link_activated (WpObject * proxy, GAsyncResult * res,
|
|||
{
|
||||
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
||||
guint len = self->node_links ? self->node_links->len : 0;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
/* Count the number of failed and active links */
|
||||
if (wp_object_activate_finish (proxy, res, NULL))
|
||||
if (wp_object_activate_finish (proxy, res, &error)) {
|
||||
self->n_active_links++;
|
||||
else
|
||||
} else {
|
||||
self->n_failed_links++;
|
||||
wp_info_object (self, "Failed to activate link %p: %s", proxy,
|
||||
error->message);
|
||||
}
|
||||
|
||||
/* Wait for all links to finish activation */
|
||||
if (self->n_failed_links + self->n_active_links != len)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ typedef enum {
|
|||
typedef enum {
|
||||
RESCAN_CONTEXT_LINKING,
|
||||
RESCAN_CONTEXT_DEFAULT_NODES,
|
||||
RESCAN_CONTEXT_MEDIA_ROLE_VOLUME,
|
||||
N_RESCAN_CONTEXTS,
|
||||
} RescanContext;
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ rescan_context_get_type (void)
|
|||
static const GEnumValue values[] = {
|
||||
{ RESCAN_CONTEXT_LINKING, "RESCAN_CONTEXT_LINKING", "linking" },
|
||||
{ RESCAN_CONTEXT_DEFAULT_NODES, "RESCAN_CONTEXT_DEFAULT_NODES", "default-nodes" },
|
||||
{ RESCAN_CONTEXT_MEDIA_ROLE_VOLUME, "RESCAN_CONTEXT_MEDIA_ROLE_VOLUME", "media-role-volume" },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
if (g_once_init_enter (>ype_id)) {
|
||||
|
|
@ -161,6 +163,8 @@ get_default_event_priority (const gchar *event_type)
|
|||
return -490;
|
||||
else if (!g_strcmp0 (event_type, "rescan-for-linking"))
|
||||
return -500;
|
||||
else if (!g_strcmp0 (event_type, "rescan-for-media-role-volume"))
|
||||
return -510;
|
||||
else if (!g_strcmp0 (event_type, "node-state-changed"))
|
||||
return 50;
|
||||
else if (!g_strcmp0 (event_type, "metadata-changed"))
|
||||
|
|
|
|||
12
po/conf.pot
12
po/conf.pot
|
|
@ -123,6 +123,16 @@ msgstr ""
|
|||
msgid "Ducking level"
|
||||
msgstr ""
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/description
|
||||
#: wireplumber.conf
|
||||
msgid "Automatically detect channel count and positions for HDMI devices (experimental)"
|
||||
msgstr ""
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/name
|
||||
#: wireplumber.conf
|
||||
msgid "Automatically detect HDMI channels (experimental)"
|
||||
msgstr ""
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.camera-discovery-timeout/description
|
||||
#: wireplumber.conf
|
||||
msgid "The camera discovery timeout in milliseconds"
|
||||
|
|
@ -155,7 +165,7 @@ msgstr ""
|
|||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||
#: wireplumber.conf
|
||||
msgid "Configure all audio nodes in MONO"
|
||||
msgid "Configure all audio device sink nodes in MONO"
|
||||
msgstr ""
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
||||
|
|
|
|||
68
po/sl.po
68
po/sl.po
|
|
@ -2,24 +2,22 @@
|
|||
# Copyright (C) 2024 WirePlumber's COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the WirePlumber package.
|
||||
#
|
||||
# Martin <miles@filmsi.net>, 2024, 2025.
|
||||
# Martin <miles@filmsi.net>, 2024, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WirePlumber master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2025-08-21 03:57+0000\n"
|
||||
"PO-Revision-Date: 2025-08-21 15:45+0200\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues\n"
|
||||
"POT-Creation-Date: 2025-12-15 16:28+0000\n"
|
||||
"PO-Revision-Date: 2025-12-15 23:31+0100\n"
|
||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
|
||||
"%100==4 ? 3 : 0);\n"
|
||||
"X-Generator: Poedit 2.2.1\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);\n"
|
||||
"X-Generator: Poedit 3.8\n"
|
||||
|
||||
#. WirePlumber
|
||||
#.
|
||||
|
|
@ -49,7 +47,7 @@ msgstr "Razdeli %s"
|
|||
#. also sanitize nick, replace ':' with ' '
|
||||
#. ensure the node has a description
|
||||
#. also sanitize description, replace ':' with ' '
|
||||
#. add api.alsa.card.* properties for rule matching purposes
|
||||
#. add api.alsa.card.* and alsa.* properties for rule matching purposes
|
||||
#. add cpu.vm.name for rule matching purposes
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. handle split HW node
|
||||
|
|
@ -75,6 +73,7 @@ msgstr "Modem"
|
|||
#. form factor -> icon
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. override the device factory to use ACP
|
||||
#. use HDMI channel detection if enabled in settings
|
||||
#. use device reservation, if available
|
||||
#. unlike pipewire-media-session, this logic here keeps the device
|
||||
#. acquired at all times and destroys it if someone else acquires
|
||||
|
|
@ -201,6 +200,34 @@ msgstr "Privzeta glasnost za zvočne vire"
|
|||
msgid "Default source volume"
|
||||
msgstr "Privzeta izvorna glasnost"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-alsa-playback-removed/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically mute all audio devices when active wired headphones/speakers "
|
||||
"are disconnected to prevent unintended sound output"
|
||||
msgstr ""
|
||||
"Samodejno utišaj vse zvočne naprave, ko so aktivne žične slušalke/zvočniki "
|
||||
"odklopljeni, za preprečitev nenamernega zvočnega izhoda"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-alsa-playback-removed/name
|
||||
#: wireplumber.conf
|
||||
msgid "Auto-mute on wired audio disconnect"
|
||||
msgstr "Samodejna utišaj zvok pri prekinitvi žične zvokovne povezave"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-bluetooth-playback-removed/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically mute all audio devices when active Bluetooth headphones/"
|
||||
"speakers are disconnected to prevent unintended sound output"
|
||||
msgstr ""
|
||||
"Samodejno utišaj vse zvočne naprave, ko so aktivne slušalke/zvočniki "
|
||||
"Bluetooth odklopljeni, da preprečite nenamerni izhod zvoka"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-bluetooth-playback-removed/name
|
||||
#: wireplumber.conf
|
||||
msgid "Auto-mute on Bluetooth audio disconnect"
|
||||
msgstr "Samodejni utišaj pri prekinitvi zvokovne povezave Bluetooth"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.allow-moving-streams/description
|
||||
#: wireplumber.conf
|
||||
msgid "Streams may be moved by adding PipeWire metadata at runtime"
|
||||
|
|
@ -247,6 +274,19 @@ msgstr ""
|
|||
msgid "Ducking level"
|
||||
msgstr "Stopnja umikanja"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically detect channel count and positions for HDMI devices "
|
||||
"(experimental)"
|
||||
msgstr ""
|
||||
"Samodejno zaznaj število kanalov in položaje za naprave HDMI (poskusno)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/name
|
||||
#: wireplumber.conf
|
||||
msgid "Automatically detect HDMI channels (experimental)"
|
||||
msgstr "Samodejno zaznaj kanale HDMI (poskusno)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.camera-discovery-timeout/description
|
||||
#: wireplumber.conf
|
||||
msgid "The camera discovery timeout in milliseconds"
|
||||
|
|
@ -277,6 +317,16 @@ msgstr "Omogoči vrata nadzornih zvočnikov na zvočnih vozliščih"
|
|||
msgid "Monitor ports"
|
||||
msgstr "Vrata zvočnih monitorjev"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||
#: wireplumber.conf
|
||||
msgid "Configure all audio device sink nodes in MONO"
|
||||
msgstr "Prilagodi vsa vozlišča zvokovnih ponorov v MONO"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
||||
#: wireplumber.conf
|
||||
msgid "Mono"
|
||||
msgstr "Mono"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.no-dsp/description
|
||||
#: wireplumber.conf
|
||||
msgid "Do not convert audio to F32 format"
|
||||
|
|
|
|||
400
po/tr.po
400
po/tr.po
|
|
@ -1,26 +1,25 @@
|
|||
# Turkish translation for PipeWire.
|
||||
# Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PipeWire package.
|
||||
# Necdet Yücel <necdetyucel@gmail.com>, 2014.
|
||||
# Kaan Özdinçer <kaanozdincer@gmail.com>, 2014.
|
||||
# Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017.
|
||||
# Oğuz Ersen <oguzersen@protonmail.com>, 2021.
|
||||
# Turkish translation for WirePlumber.
|
||||
# Copyright (C) 2025 WirePlumber's COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the WirePlumber package.
|
||||
#
|
||||
# Sabri Ünal <yakushabb@gmail.com>, 2025.
|
||||
# Emin Tufan Çetin <etcetin@gmail.com>, 2025
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PipeWire master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
|
||||
"PO-Revision-Date: 2021-12-06 21:31+0300\n"
|
||||
"Last-Translator: Oğuz Ersen <oguzersen@protonmail.com>\n"
|
||||
"Language-Team: Turkish <tr>\n"
|
||||
"Project-Id-Version: WirePlumber master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2025-11-09 04:07+0000\n"
|
||||
"PO-Revision-Date: 2025-11-09 08:00+0300\n"
|
||||
"Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n"
|
||||
"Language-Team: Turkish <takim@gnome.org.tr>\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 4.4.2\n"
|
||||
"X-Generator: Poedit 3.8\n"
|
||||
|
||||
#. WirePlumber
|
||||
#.
|
||||
|
|
@ -28,13 +27,19 @@ msgstr ""
|
|||
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||
#.
|
||||
#. SPDX-License-Identifier: MIT
|
||||
#. Receive script arguments from config.lua
|
||||
#. ensure config.properties is not nil
|
||||
#. preprocess rules and create Interest objects
|
||||
#. applies properties from config.rules when asked to
|
||||
#. unique device/node name tables
|
||||
#. SPA ids to node names: name = id_name_table[device_id][node_id]
|
||||
#. create the underlying hidden ALSA node
|
||||
#. not suitable for loopback
|
||||
#: src/scripts/monitors/alsa.lua:106
|
||||
#, lua-format
|
||||
msgid "Split %s"
|
||||
msgstr "Bölük %s"
|
||||
|
||||
#. Connect ObjectConfig events to the right node
|
||||
#. set the device id and spa factory name; REQUIRED, do not change
|
||||
#. set the default pause-on-idle setting
|
||||
#. try to negotiate the max ammount of channels
|
||||
#. try to negotiate the max amount of channels
|
||||
#. set priority
|
||||
#. ensure the node has a media class
|
||||
#. ensure the node has a name
|
||||
|
|
@ -45,15 +50,360 @@ msgstr ""
|
|||
#. ensure the node has a description
|
||||
#. also sanitize description, replace ':' with ' '
|
||||
#. add api.alsa.card.* properties for rule matching purposes
|
||||
#. apply properties from config.rules
|
||||
#. add cpu.vm.name for rule matching purposes
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. handle split HW node
|
||||
#. create split PCM node
|
||||
#. create the node
|
||||
#. ensure the device has an appropriate name
|
||||
#. deduplicate devices with the same name
|
||||
#. ensure the device has a description
|
||||
#: src/scripts/monitors/alsa.lua:222
|
||||
msgid "Built-in Audio"
|
||||
msgstr "Dahili Ses"
|
||||
#: src/scripts/monitors/alsa.lua:438
|
||||
msgid "Loopback"
|
||||
msgstr "Geri Döngü"
|
||||
|
||||
#: src/scripts/monitors/alsa.lua:224
|
||||
#: src/scripts/monitors/alsa.lua:440
|
||||
msgid "Built-in Audio"
|
||||
msgstr "Yerleşik Ses"
|
||||
|
||||
#: src/scripts/monitors/alsa.lua:442
|
||||
msgid "Modem"
|
||||
msgstr "Modem"
|
||||
|
||||
#. ensure the device has a nick
|
||||
#. set the icon name
|
||||
#. form factor -> icon
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. override the device factory to use ACP
|
||||
#. use HDMI channel detection if enabled in settings
|
||||
#. use device reservation, if available
|
||||
#. unlike pipewire-media-session, this logic here keeps the device
|
||||
#. acquired at all times and destroys it if someone else acquires
|
||||
#. create the device
|
||||
#. attempt to acquire again
|
||||
#. destroy the device
|
||||
#. create the device
|
||||
#. handle create-object to prepare device
|
||||
#. handle object-removed to destroy device reservations and recycle device name
|
||||
#. reset the name tables to make sure names are recycled
|
||||
#. activate monitor
|
||||
#. if the reserve-device plugin is enabled, at the point of script execution
|
||||
#. it is expected to be connected. if it is not, assume the d-bus connection
|
||||
#. has failed and continue without it
|
||||
#. handle rd_plugin state changes to destroy and re-create the ALSA monitor in
|
||||
#. case D-Bus service is restarted
|
||||
#. create the monitor
|
||||
#. WirePlumber
|
||||
#.
|
||||
#. Copyright © 2022 Pauli Virtanen
|
||||
#. @author Pauli Virtanen
|
||||
#.
|
||||
#. SPDX-License-Identifier: MIT
|
||||
#. unique device/node name tables
|
||||
#. set the node description
|
||||
#. sanitize description, replace ':' with ' '
|
||||
#. set the node name
|
||||
#. sanitize name
|
||||
#. deduplicate nodes with the same name
|
||||
#. apply properties from the rules in the configuration file
|
||||
#. create the node
|
||||
#. it doesn't necessarily need to be a local node,
|
||||
#. the other Bluetooth parts run in the local process,
|
||||
#. so it's consistent to have also this here
|
||||
#. reset the name tables to make sure names are recycled
|
||||
#: src/scripts/monitors/bluez-midi.lua:114
|
||||
#, lua-format
|
||||
msgid "BLE MIDI %d"
|
||||
msgstr "BLE MIDI %d"
|
||||
|
||||
#. if logind support is enabled, activate
|
||||
#. the monitor only when the seat is active
|
||||
#. WirePlumber
|
||||
#.
|
||||
#. Copyright © 2023 Collabora Ltd.
|
||||
#. @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
|
||||
#.
|
||||
#. SPDX-License-Identifier: MIT
|
||||
#. set the device id and spa factory name; REQUIRED, do not change
|
||||
#. set the default pause-on-idle setting
|
||||
#. set the node name
|
||||
#. sanitize name
|
||||
#. deduplicate nodes with the same name
|
||||
#. set the node description
|
||||
#: src/scripts/monitors/libcamera/name-node.lua:61
|
||||
msgid "Built-in Front Camera"
|
||||
msgstr "Yerleşik Ön Kamera"
|
||||
|
||||
#: src/scripts/monitors/libcamera/name-node.lua:63
|
||||
msgid "Built-in Back Camera"
|
||||
msgstr "Yerleşik Arka Kamera"
|
||||
|
||||
#. /wireplumber.settings.schema/bluetooth.autoswitch-to-headset-profile/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Always show microphone for Bluetooth headsets, and switch to headset mode "
|
||||
"when recording"
|
||||
msgstr ""
|
||||
"Bluetooth kulaklıklar için her zaman mikrofonu göster ve kayıt sırasında "
|
||||
"kulaklık kipine geç"
|
||||
|
||||
#. /wireplumber.settings.schema/bluetooth.autoswitch-to-headset-profile/name
|
||||
#: wireplumber.conf
|
||||
msgid "Auto-switch to headset profile"
|
||||
msgstr "Kulaklık profiline kendiliğinden geç"
|
||||
|
||||
#. /wireplumber.settings.schema/bluetooth.use-persistent-storage/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore Bluetooth headset mode status"
|
||||
msgstr "Bluetooth kulaklık kipi durumunu anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/bluetooth.use-persistent-storage/name
|
||||
#: wireplumber.conf
|
||||
msgid "Persistent storage"
|
||||
msgstr "Kalıcı depolama"
|
||||
|
||||
#. /wireplumber.settings.schema/device.restore-profile/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore device profiles"
|
||||
msgstr "Aygıt profillerini anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/device.restore-profile/name
|
||||
#: wireplumber.conf
|
||||
msgid "Restore profile"
|
||||
msgstr "Profili geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/device.restore-routes/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore device routes"
|
||||
msgstr "Aygıt rotalarını anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/device.restore-routes/name
|
||||
#: wireplumber.conf
|
||||
msgid "Restore routes"
|
||||
msgstr "Rotaları geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.default-sink-volume/description
|
||||
#: wireplumber.conf
|
||||
msgid "The default volume for audio sinks"
|
||||
msgstr "Ses alıcıları için öntanımlı ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.default-sink-volume/name
|
||||
#: wireplumber.conf
|
||||
msgid "Default sink volume"
|
||||
msgstr "Öntanımlı alıcı ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.default-source-volume/description
|
||||
#: wireplumber.conf
|
||||
msgid "The default volume for audio sources"
|
||||
msgstr "Ses kaynakları için öntanımlı ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.default-source-volume/name
|
||||
#: wireplumber.conf
|
||||
msgid "Default source volume"
|
||||
msgstr "Öntanımlı kaynak ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-alsa-playback-removed/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically mute all audio devices when active wired headphones/speakers "
|
||||
"are disconnected to prevent unintended sound output"
|
||||
msgstr ""
|
||||
"İstenmeyen ses çıktısını önlemek için etkin kablolu kulaklık/hoparlör "
|
||||
"bağlantısı kesildiğinde tüm ses aygıtlarını kendiliğinden sustur"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-alsa-playback-removed/name
|
||||
#: wireplumber.conf
|
||||
msgid "Auto-mute on wired audio disconnect"
|
||||
msgstr "Kablolu ses bağlantısı kesildiğinde kendiliğinden sustur"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-bluetooth-playback-removed/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically mute all audio devices when active Bluetooth headphones/"
|
||||
"speakers are disconnected to prevent unintended sound output"
|
||||
msgstr ""
|
||||
"İstenmeyen ses çıktısını önlemek için etkin Bluetooth kulaklık/hoparlör "
|
||||
"bağlantısı kesildiğinde tüm ses aygıtlarını kendiliğinden sustur"
|
||||
|
||||
#. /wireplumber.settings.schema/device.routes.mute-on-bluetooth-playback-removed/name
|
||||
#: wireplumber.conf
|
||||
msgid "Auto-mute on Bluetooth audio disconnect"
|
||||
msgstr "Bluetooth ses bağlantısı kesildiğinde kendiliğinden sustur"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.allow-moving-streams/description
|
||||
#: wireplumber.conf
|
||||
msgid "Streams may be moved by adding PipeWire metadata at runtime"
|
||||
msgstr "Akışlar, çalışma zamanında PipeWire üst verileri eklenerek taşınabilir"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.allow-moving-streams/name
|
||||
#: wireplumber.conf
|
||||
msgid "Allow moving streams"
|
||||
msgstr "Akışları taşımaya izin ver"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.follow-default-target/description
|
||||
#: wireplumber.conf
|
||||
msgid "Streams connected to the default device follow when default changes"
|
||||
msgstr "Öntanımlı aygıta bağlı akışlar öntanımlı değiştiğinde izler"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.follow-default-target/name
|
||||
#: wireplumber.conf
|
||||
msgid "Follow default target"
|
||||
msgstr "Öntanımlı hedefi izle"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.pause-playback/description
|
||||
#: wireplumber.conf
|
||||
msgid "Pause media players if their target sink is removed"
|
||||
msgstr "Hedef alıcıları kaldırılırsa ortam oynatıcıları duraklat"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.pause-playback/name
|
||||
#: wireplumber.conf
|
||||
msgid "Pause playback if output removed"
|
||||
msgstr "Çıktı kaldırılırsa oynatmayı duraklat"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.role-based.duck-level/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"The volume level to apply when ducking (= reducing volume for a higher "
|
||||
"priority stream to be audible) in the role-based linking policy"
|
||||
msgstr ""
|
||||
"Rol tabanlı bağlantı ilkesinde eğilirken (= daha öncelikli akışın "
|
||||
"duyulabilmesi için ses düzeyinin azaltılması) uygulanacak ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/linking.role-based.duck-level/name
|
||||
#: wireplumber.conf
|
||||
msgid "Ducking level"
|
||||
msgstr "Eğilme düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically detect channel count and positions for HDMI devices "
|
||||
"(experimental)"
|
||||
msgstr ""
|
||||
"HDMI aygıtları için kanal sayısını ve konumlarını kendiliğinden algıla "
|
||||
"(deneysel)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/name
|
||||
#: wireplumber.conf
|
||||
msgid "Automatically detect HDMI channels (experimental)"
|
||||
msgstr "HDMI kanallarını kendiliğinden algıla (deneysel)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.camera-discovery-timeout/description
|
||||
#: wireplumber.conf
|
||||
msgid "The camera discovery timeout in milliseconds"
|
||||
msgstr "Kamera keşif zaman aşımı, saniye türünden"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.camera-discovery-timeout/name
|
||||
#: wireplumber.conf
|
||||
msgid "Discovery timeout"
|
||||
msgstr "Keşif zaman aşımı"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.control-port/description
|
||||
#: wireplumber.conf
|
||||
msgid "Enable control ports on audio nodes"
|
||||
msgstr "Ses düğümlerinde denetim bağlantı noktalarını etkinleştir"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.control-port/name
|
||||
#: wireplumber.conf
|
||||
msgid "Control ports"
|
||||
msgstr "Denetim bağlantı noktaları"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.monitor-ports/description
|
||||
#: wireplumber.conf
|
||||
msgid "Enable monitor ports on audio nodes"
|
||||
msgstr "Ses düğümlerinde izleme bağlantı noktalarını etkinleştir"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.monitor-ports/name
|
||||
#: wireplumber.conf
|
||||
msgid "Monitor ports"
|
||||
msgstr "İzleme bağlantı noktaları"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||
#: wireplumber.conf
|
||||
msgid "Configure all audio nodes in MONO"
|
||||
msgstr "Tüm ses düğümlerini MONO olarak yapılandır"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
||||
#: wireplumber.conf
|
||||
msgid "Mono"
|
||||
msgstr "Mono"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.no-dsp/description
|
||||
#: wireplumber.conf
|
||||
msgid "Do not convert audio to F32 format"
|
||||
msgstr "Sesi F32 biçimine dönüştürme"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.no-dsp/name
|
||||
#: wireplumber.conf
|
||||
msgid "No DSP"
|
||||
msgstr "DSP yok"
|
||||
|
||||
#. /wireplumber.settings.schema/node.filter.forward-format/description
|
||||
#: wireplumber.conf
|
||||
msgid "Forward format on filter nodes or not"
|
||||
msgstr "Süzgeç düğümlerinde biçimi ilet ya da iletme"
|
||||
|
||||
#. /wireplumber.settings.schema/node.filter.forward-format/name
|
||||
#: wireplumber.conf
|
||||
msgid "Forward format"
|
||||
msgstr "Biçimi ilet"
|
||||
|
||||
#. /wireplumber.settings.schema/node.restore-default-targets/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore default audio/video input/output devices"
|
||||
msgstr "Öntanımlı ses/video girdi/çıktı aygıtlarını anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/node.restore-default-targets/name
|
||||
#: wireplumber.conf
|
||||
msgid "Restore default target"
|
||||
msgstr "Öntanımlı hedefi geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-capture-volume/description
|
||||
#: wireplumber.conf
|
||||
msgid "The default volume for capture nodes"
|
||||
msgstr "Yakalama düğümleri için öntanımlı ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-capture-volume/name
|
||||
#: wireplumber.conf
|
||||
msgid "Default capture volume"
|
||||
msgstr "Öntanımlı yakalama ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-media-role/description
|
||||
#: wireplumber.conf
|
||||
msgid "Default media.role to assign on streams that do not specify it"
|
||||
msgstr "Belirtilmeyen akışlarda atanacak öntanımlı ortam rolü"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-media-role/name
|
||||
#: wireplumber.conf
|
||||
msgid "Default media role"
|
||||
msgstr "Öntanımlı ortam rolü"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-playback-volume/description
|
||||
#: wireplumber.conf
|
||||
msgid "The default volume for playback nodes"
|
||||
msgstr "Oynatma düğümleri için öntanımlı ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.default-playback-volume/name
|
||||
#: wireplumber.conf
|
||||
msgid "Default playback volume"
|
||||
msgstr "Öntanımlı oynatma ses düzeyi"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.restore-props/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore properties of streams"
|
||||
msgstr "Akışların özelliklerini anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.restore-props/name
|
||||
#: wireplumber.conf
|
||||
msgid "Restore properties"
|
||||
msgstr "Özellikleri geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.restore-target/description
|
||||
#: wireplumber.conf
|
||||
msgid "Remember and restore stream targets"
|
||||
msgstr "Akış hedeflerini anımsa ve geri yükle"
|
||||
|
||||
#. /wireplumber.settings.schema/node.stream.restore-target/name
|
||||
#: wireplumber.conf
|
||||
msgid "Restore target"
|
||||
msgstr "Hedefi geri yükle"
|
||||
|
|
|
|||
23
po/zh_CN.po
23
po/zh_CN.po
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"Project-Id-Version: pipewire.master-tx\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
||||
"issues\n"
|
||||
"POT-Creation-Date: 2025-10-01 16:13+0000\n"
|
||||
"PO-Revision-Date: 2025-10-02 07:57+0800\n"
|
||||
"POT-Creation-Date: 2025-12-15 16:28+0000\n"
|
||||
"PO-Revision-Date: 2025-12-16 10:10+0800\n"
|
||||
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
|
||||
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
|
|
@ -53,7 +53,7 @@ msgstr "分离 %s"
|
|||
#. also sanitize nick, replace ':' with ' '
|
||||
#. ensure the node has a description
|
||||
#. also sanitize description, replace ':' with ' '
|
||||
#. add api.alsa.card.* properties for rule matching purposes
|
||||
#. add api.alsa.card.* and alsa.* properties for rule matching purposes
|
||||
#. add cpu.vm.name for rule matching purposes
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. handle split HW node
|
||||
|
|
@ -79,6 +79,7 @@ msgstr "调制解调器"
|
|||
#. form factor -> icon
|
||||
#. apply properties from rules defined in JSON .conf file
|
||||
#. override the device factory to use ACP
|
||||
#. use HDMI channel detection if enabled in settings
|
||||
#. use device reservation, if available
|
||||
#. unlike pipewire-media-session, this logic here keeps the device
|
||||
#. acquired at all times and destroys it if someone else acquires
|
||||
|
|
@ -273,6 +274,18 @@ msgstr ""
|
|||
msgid "Ducking level"
|
||||
msgstr "回避级别"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/description
|
||||
#: wireplumber.conf
|
||||
msgid ""
|
||||
"Automatically detect channel count and positions for HDMI devices "
|
||||
"(experimental)"
|
||||
msgstr "自动检测 HDMI 设备的声道数量和位置(实验性)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.alsa.autodetect-hdmi-channels/name
|
||||
#: wireplumber.conf
|
||||
msgid "Automatically detect HDMI channels (experimental)"
|
||||
msgstr "自动检测 HDMI 声道(实验性)"
|
||||
|
||||
#. /wireplumber.settings.schema/monitor.camera-discovery-timeout/description
|
||||
#: wireplumber.conf
|
||||
msgid "The camera discovery timeout in milliseconds"
|
||||
|
|
@ -305,8 +318,8 @@ msgstr "监视器端口"
|
|||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||
#: wireplumber.conf
|
||||
msgid "Configure all audio nodes in MONO"
|
||||
msgstr "在单声道中配置所有音频节点"
|
||||
msgid "Configure all audio device sink nodes in MONO"
|
||||
msgstr "在单声道中配置所有音频设备信宿节点"
|
||||
|
||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
||||
#: wireplumber.conf
|
||||
|
|
|
|||
|
|
@ -627,12 +627,17 @@ wireplumber.components = [
|
|||
name = node/filter-forward-format.lua, type = script/lua
|
||||
provides = hooks.filter.forward-format
|
||||
}
|
||||
{
|
||||
name = node/filter-graph.lua, type = script/lua
|
||||
provides = hooks.filter.graph
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.node
|
||||
requires = [ hooks.node.create-session-item ]
|
||||
wants = [ hooks.node.suspend
|
||||
hooks.stream.state
|
||||
hooks.filter.forward-format ]
|
||||
hooks.filter.forward-format
|
||||
hooks.filter.graph ]
|
||||
}
|
||||
{
|
||||
name = node/software-dsp.lua, type = script/lua
|
||||
|
|
@ -717,10 +722,21 @@ wireplumber.components = [
|
|||
provides = hooks.linking.role-based.rescan
|
||||
requires = [ api.mixer ]
|
||||
}
|
||||
{
|
||||
name = node/find-media-role-default-volume.lua, type = script/lua
|
||||
provides = hooks.node.role-based.default-volume
|
||||
requires = [ hooks.linking.role-based.rescan ]
|
||||
}
|
||||
{
|
||||
name = linking/find-media-role-sink-target.lua, type = script/lua
|
||||
provides = hooks.linking.target.find-media-role-sink
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.linking.role-based
|
||||
requires = [ policy.linking.standard,
|
||||
hooks.linking.role-based.rescan ]
|
||||
hooks.linking.role-based.rescan,
|
||||
hooks.node.role-based.default-volume,
|
||||
hooks.linking.target.find-media-role-sink ]
|
||||
}
|
||||
|
||||
## Standard policy definition
|
||||
|
|
@ -912,6 +928,12 @@ wireplumber.settings.schema = {
|
|||
min = 0
|
||||
max = 60000
|
||||
}
|
||||
monitor.alsa.autodetect-hdmi-channels = {
|
||||
name = "Automatically detect HDMI channels (experimental)"
|
||||
description = "Automatically detect channel count and positions for HDMI devices (experimental)"
|
||||
type = "bool"
|
||||
default = false
|
||||
}
|
||||
|
||||
## Node
|
||||
node.features.audio.no-dsp = {
|
||||
|
|
@ -934,7 +956,7 @@ wireplumber.settings.schema = {
|
|||
}
|
||||
node.features.audio.mono = {
|
||||
name = "Mono"
|
||||
description = "Configure all audio nodes in MONO"
|
||||
description = "Configure all audio device sink nodes in MONO"
|
||||
type = "bool"
|
||||
default = false
|
||||
}
|
||||
|
|
|
|||
43
src/config/wireplumber.conf.d.examples/filter-graph.conf
Normal file
43
src/config/wireplumber.conf.d.examples/filter-graph.conf
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
node.filter-graph.rules = [
|
||||
## The list of filter graph rules
|
||||
|
||||
## This rule example creates two filter graphs for each audio source node
|
||||
# {
|
||||
# matches = [
|
||||
# {
|
||||
# ## This matches all audio source nodes
|
||||
# media.class = "Audio/Source"
|
||||
# }
|
||||
# ]
|
||||
# actions = {
|
||||
# create-filter-graph = [
|
||||
# ## Multiple filter graphs can be defined here.
|
||||
# ## The syntax is the same as the pipewire filter-chain conf files.
|
||||
#
|
||||
# ## This is an example of a bultin passthrough filter
|
||||
# {
|
||||
# nodes = [
|
||||
# {
|
||||
# type = builtin
|
||||
# label = copy
|
||||
# name = passthrough
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# ## This is an example of a LADSPA rnnoise filter
|
||||
# {
|
||||
# nodes = [
|
||||
# {
|
||||
# type = ladspa
|
||||
# name = rnnoise
|
||||
# plugin = librnnoise_ladspa
|
||||
# label = noise_suppressor_stereo
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
]
|
||||
|
||||
|
|
@ -150,6 +150,7 @@ wireplumber.components = [
|
|||
policy.role-based.priority = 100
|
||||
policy.role-based.action.same-priority = "mix"
|
||||
policy.role-based.action.lower-priority = "cork"
|
||||
policy.role-based.preferred-target = "Speaker"
|
||||
}
|
||||
}
|
||||
provides = loopback.sink.role.alert
|
||||
|
|
|
|||
|
|
@ -19,5 +19,25 @@ monitor.alsa.rules = [
|
|||
api.alsa.headroom = 2048
|
||||
}
|
||||
}
|
||||
},
|
||||
# VMware & VirtualBox on Windows hosts require more headroom to
|
||||
# avoid stuttering.
|
||||
{
|
||||
matches = [
|
||||
{
|
||||
node.name = "~alsa_input.pci.*"
|
||||
cpu.vm.name = "~^(vmware)|(oracle)$"
|
||||
}
|
||||
{
|
||||
node.name = "~alsa_output.pci.*"
|
||||
cpu.vm.name = "~^(vmware)|(oracle)$"
|
||||
}
|
||||
]
|
||||
actions = {
|
||||
update-props = {
|
||||
api.alsa.period-size = 1024
|
||||
api.alsa.headroom = 8192
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ SimpleEventHook {
|
|||
|
||||
local om = source:call ("get-object-manager", "metadata")
|
||||
local metadata = om:lookup { Constraint { "metadata.name", "=", "default" } }
|
||||
if metadata == nil then
|
||||
return
|
||||
end
|
||||
|
||||
if selected_node then
|
||||
local key = "default." .. def_node_type
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
cutils = require ("common-utils")
|
||||
log = Log.open_topic ("s-automute-alsa-routes")
|
||||
hooks_registered = false
|
||||
|
||||
function setRoute (device, route, mute)
|
||||
local param = Pod.Object {
|
||||
|
|
@ -194,17 +195,19 @@ evaluate_mute_on_node_removed_hook = SimpleEventHook {
|
|||
function toggleState ()
|
||||
local mute_alsa = Settings.get_boolean ("device.routes.mute-on-alsa-playback-removed")
|
||||
local mute_bluez = Settings.get_boolean ("device.routes.mute-on-bluetooth-playback-removed")
|
||||
if mute_alsa or mute_bluez then
|
||||
if (mute_alsa or mute_bluez) and not hooks_registered then
|
||||
nodes_info = {}
|
||||
mute_alsa_devices_hook:register ()
|
||||
update_nodes_info_hook:register ()
|
||||
evaluate_mute_on_device_route_changed_hook:register ()
|
||||
evaluate_mute_on_node_removed_hook:register ()
|
||||
else
|
||||
hooks_registered = true
|
||||
elseif not mute_alsa and not mute_bluez and hooks_registered then
|
||||
mute_alsa_devices_hook:remove ()
|
||||
update_nodes_info_hook:remove ()
|
||||
evaluate_mute_on_device_route_changed_hook:remove ()
|
||||
evaluate_mute_on_node_removed_hook:remove ()
|
||||
hooks_registered = false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ function handlePersistentSetting (enable)
|
|||
-- the state storage
|
||||
state = Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile")
|
||||
and State ("bluetooth-autoswitch") or nil
|
||||
headset_profiles = state and state:load () or {}
|
||||
headset_profiles = state and state:load () or Properties()
|
||||
else
|
||||
state = nil
|
||||
headset_profiles = nil
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ SimpleEventHook {
|
|||
local device = event:get_subject ()
|
||||
local event_properties = event:get_properties ()
|
||||
local active_ids = event_properties ["profile.active-device-ids"]
|
||||
local selected_routes = event:get_data ("selected-routes") or {}
|
||||
local selected_routes = event:get_data ("selected-routes") or Properties()
|
||||
|
||||
local dev_info = devinfo:get_device_info (device)
|
||||
assert (dev_info)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,10 @@ find_stored_profile_hook = SimpleEventHook {
|
|||
end
|
||||
|
||||
local device = event:get_subject ()
|
||||
local dev_name = device.properties["device.name"]
|
||||
local device_props = device.properties
|
||||
local dev_name = device_props["device.name"]
|
||||
local dont_restore_off_profile = cutils.parseBool (
|
||||
device_props["session.dont-restore-off-profile"])
|
||||
if not dev_name then
|
||||
log:critical (device, "invalid device.name")
|
||||
return
|
||||
|
|
@ -45,7 +48,8 @@ find_stored_profile_hook = SimpleEventHook {
|
|||
if profile_name then
|
||||
for p in device:iterate_params ("EnumProfile") do
|
||||
local profile = cutils.parseParam (p, "EnumProfile")
|
||||
if profile.name == profile_name and profile.available ~= "no" then
|
||||
if profile.name == profile_name and profile.available ~= "no" and
|
||||
(not dont_restore_off_profile or profile.index ~= 0) then
|
||||
selected_profile = profile
|
||||
break
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ find_stored_routes_hook = SimpleEventHook {
|
|||
local event_properties = event:get_properties ()
|
||||
local profile_name = event_properties ["profile.name"]
|
||||
local active_ids = event_properties ["profile.active-device-ids"]
|
||||
local selected_routes = event:get_data ("selected-routes") or {}
|
||||
local selected_routes = event:get_data ("selected-routes") or Properties()
|
||||
|
||||
local dev_info = devinfo:get_device_info (device)
|
||||
assert (dev_info)
|
||||
|
|
@ -108,13 +108,13 @@ apply_route_props_hook = SimpleEventHook {
|
|||
},
|
||||
execute = function (event)
|
||||
local device = event:get_subject ()
|
||||
local selected_routes = event:get_data ("selected-routes") or {}
|
||||
local selected_routes = event:get_data ("selected-routes") or Properties()
|
||||
local new_selected_routes = {}
|
||||
|
||||
local dev_info = devinfo:get_device_info (device)
|
||||
assert (dev_info)
|
||||
|
||||
if next (selected_routes) == nil then
|
||||
if selected_routes:get_count () == 0 then
|
||||
log:info (device, "No routes selected to set on " .. dev_info.name)
|
||||
return
|
||||
end
|
||||
|
|
@ -159,129 +159,125 @@ store_or_restore_routes_hook = AsyncEventHook {
|
|||
},
|
||||
steps = {
|
||||
start = {
|
||||
next = "evaluate",
|
||||
next = "none",
|
||||
execute = function (event, transition)
|
||||
local source = event:get_source ()
|
||||
local device = event:get_subject ()
|
||||
|
||||
-- Make sure the routes are always updated before evaluating them.
|
||||
-- https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/762
|
||||
local device = event:get_subject ()
|
||||
device:enum_params ("EnumRoute", function (_, e)
|
||||
device:enum_params ("EnumRoute", function (enum_route_it, e)
|
||||
local selected_routes = {}
|
||||
local push_select_routes = false
|
||||
|
||||
-- check for error
|
||||
if e then
|
||||
transition:return_error ("failed to enum routes: "
|
||||
.. tostring (e));
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
-- Make sure the device is still valid
|
||||
if (device:get_active_features() & Feature.Proxy.BOUND) == 0 then
|
||||
transition:advance ()
|
||||
end
|
||||
end)
|
||||
end
|
||||
},
|
||||
evaluate = {
|
||||
next = "none",
|
||||
execute = function (event, transition)
|
||||
local device = event:get_subject ()
|
||||
local source = event:get_source ()
|
||||
local selected_routes = {}
|
||||
local push_select_routes = false
|
||||
|
||||
-- Make sure the device is still valid
|
||||
if (device:get_active_features() & Feature.Proxy.BOUND) == 0 then
|
||||
transition:advance ()
|
||||
return
|
||||
end
|
||||
|
||||
local dev_info = devinfo:get_device_info (device)
|
||||
if not dev_info then
|
||||
transition:advance ()
|
||||
return
|
||||
end
|
||||
|
||||
local new_route_infos = {}
|
||||
|
||||
-- look at all the routes and update/reset cached information
|
||||
for p in device:iterate_params ("EnumRoute") do
|
||||
-- parse pod
|
||||
local route = cutils.parseParam (p, "EnumRoute")
|
||||
if not route then
|
||||
goto skip_enum_route
|
||||
return
|
||||
end
|
||||
|
||||
-- find cached route information
|
||||
local route_info = devinfo.find_route_info (dev_info, route, true)
|
||||
if not route_info then
|
||||
goto skip_enum_route
|
||||
local dev_info = devinfo:get_device_info (device)
|
||||
if not dev_info then
|
||||
transition:advance ()
|
||||
return
|
||||
end
|
||||
|
||||
-- update properties
|
||||
route_info.prev_active = route_info.active
|
||||
route_info.active = false
|
||||
route_info.save = false
|
||||
local new_route_infos = {}
|
||||
|
||||
-- store
|
||||
new_route_infos [route.index] = route_info
|
||||
-- look at all the routes and update/reset cached information
|
||||
for p in enum_route_it:iterate() do
|
||||
-- parse pod
|
||||
local route = cutils.parseParam (p, "EnumRoute")
|
||||
if not route then
|
||||
goto skip_enum_route
|
||||
end
|
||||
|
||||
::skip_enum_route::
|
||||
end
|
||||
-- find cached route information
|
||||
local route_info = devinfo.find_route_info (dev_info, route, true)
|
||||
if not route_info then
|
||||
goto skip_enum_route
|
||||
end
|
||||
|
||||
-- update route_infos with new prev_active, active and save changes
|
||||
dev_info.route_infos = new_route_infos
|
||||
new_route_infos = nil
|
||||
-- update properties
|
||||
route_info.prev_active = route_info.active
|
||||
route_info.active = false
|
||||
route_info.save = false
|
||||
|
||||
-- check for changes in the active routes
|
||||
for p in device:iterate_params ("Route") do
|
||||
local route = cutils.parseParam (p, "Route")
|
||||
if not route then
|
||||
goto skip_route
|
||||
-- store
|
||||
new_route_infos [route.index] = route_info
|
||||
|
||||
::skip_enum_route::
|
||||
end
|
||||
|
||||
-- get cached route info and at the same time
|
||||
-- ensure that the route is also in EnumRoute
|
||||
local route_info = devinfo.find_route_info (dev_info, route, false)
|
||||
if not route_info then
|
||||
goto skip_route
|
||||
end
|
||||
-- update route_infos with new prev_active, active and save changes
|
||||
dev_info.route_infos = new_route_infos
|
||||
new_route_infos = nil
|
||||
|
||||
-- update route_info state
|
||||
route_info.active = true
|
||||
route_info.save = route.save
|
||||
-- check for changes in the active routes
|
||||
for p in device:iterate_params ("Route") do
|
||||
local route = cutils.parseParam (p, "Route")
|
||||
if not route then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
if not route_info.prev_active then
|
||||
-- a new route is now active, restore the volume and
|
||||
-- make sure we save this as a preferred route
|
||||
log:info (device,
|
||||
string.format ("new active route(%s) found of device(%s)",
|
||||
route.name, dev_info.name))
|
||||
route_info.prev_active = true
|
||||
-- get cached route info and at the same time
|
||||
-- ensure that the route is also in EnumRoute
|
||||
local route_info = devinfo.find_route_info (dev_info, route, false)
|
||||
if not route_info then
|
||||
goto skip_route
|
||||
end
|
||||
|
||||
-- update route_info state
|
||||
route_info.active = true
|
||||
route_info.save = route.save
|
||||
|
||||
selected_routes [tostring (route.device)] =
|
||||
Json.Object { index = route_info.index }:to_string ()
|
||||
push_select_routes = true
|
||||
if not route_info.prev_active then
|
||||
-- a new route is now active, restore the volume and
|
||||
-- make sure we save this as a preferred route
|
||||
log:info (device,
|
||||
string.format ("new active route(%s) found of device(%s)",
|
||||
route.name, dev_info.name))
|
||||
route_info.prev_active = true
|
||||
route_info.active = true
|
||||
|
||||
elseif route.available ~= "no" and route.save and route.props then
|
||||
-- just save route properties
|
||||
log:info (device,
|
||||
string.format ("storing route(%s) props of device(%s)",
|
||||
route.name, dev_info.name))
|
||||
selected_routes [tostring (route.device)] =
|
||||
Json.Object { index = route_info.index }:to_string ()
|
||||
push_select_routes = true
|
||||
|
||||
saveRouteProps (dev_info, route)
|
||||
elseif route.available ~= "no" and route.save and route.props then
|
||||
-- just save route properties
|
||||
log:info (device,
|
||||
string.format ("storing route(%s) props of device(%s)",
|
||||
route.name, dev_info.name))
|
||||
|
||||
saveRouteProps (dev_info, route)
|
||||
end
|
||||
|
||||
::skip_route::
|
||||
end
|
||||
|
||||
::skip_route::
|
||||
end
|
||||
-- save selected routes for the active profile
|
||||
for p in device:iterate_params ("Profile") do
|
||||
local profile = cutils.parseParam (p, "Profile")
|
||||
saveProfileRoutes (dev_info, profile.name)
|
||||
end
|
||||
|
||||
-- save selected routes for the active profile
|
||||
for p in device:iterate_params ("Profile") do
|
||||
local profile = cutils.parseParam (p, "Profile")
|
||||
saveProfileRoutes (dev_info, profile.name)
|
||||
end
|
||||
-- push a select-routes event to re-apply the routes with new properties
|
||||
if push_select_routes then
|
||||
local e = source:call ("create-event", "select-routes", device, nil)
|
||||
e:set_data ("selected-routes", selected_routes)
|
||||
EventDispatcher.push_event (e)
|
||||
end
|
||||
|
||||
-- push a select-routes event to re-apply the routes with new properties
|
||||
if push_select_routes then
|
||||
local e = source:call ("create-event", "select-routes", device, nil)
|
||||
e:set_data ("selected-routes", selected_routes)
|
||||
EventDispatcher.push_event (e)
|
||||
end
|
||||
|
||||
transition:advance ()
|
||||
transition:advance ()
|
||||
end)
|
||||
end
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Hooks
|
|||
|
||||
The hooks in this section are organized in 3 sub-categories. The first category
|
||||
includes hooks that are triggered by changes in the graph. Some of them are tasked
|
||||
to schedule a "rescan-for-linking" event, which is the lowest priority event and
|
||||
to schedule a "rescan-for-linking" event, which is the lowest priority linking event and
|
||||
its purpose is to scan through all the linkable session items and link them
|
||||
to a particular target. The "rescan-for-linking" event is always scheduled to run
|
||||
once for all the graph changes in a cycle. This is achieved by flagging the event
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ SimpleEventHook {
|
|||
name = "linking/find-default-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-filter-target",
|
||||
"linking/find-media-role-target" },
|
||||
"linking/find-media-role-target",
|
||||
"linking/find-media-role-sink-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
|
|
|
|||
81
src/scripts/linking/find-media-role-sink-target.lua
Normal file
81
src/scripts/linking/find-media-role-sink-target.lua
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2025 Phosh.mobi e.V.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Pick up a preferred target node for the output stream of role-based loopbacks
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
cutils = require ("common-utils")
|
||||
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-media-role-sink-target",
|
||||
after = { "linking/find-defined-target",
|
||||
"linking/find-media-role-target" },
|
||||
before = "linking/prepare-link",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local _, om, si, si_props, _, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
|
||||
local node_name = si_props["node.name"]
|
||||
local target_direction = cutils.getTargetDirection (si_props)
|
||||
local media_class = si_props["media.class"]
|
||||
local link_group = si_props["node.link-group"]
|
||||
local is_virtual = si_props["node.virtual"]
|
||||
|
||||
log:info (si, string.format ("Lookup for '%s' (%s) / '%s' / '%s'",
|
||||
node_name, tostring (si_props ["node.id"]), media_class, link_group))
|
||||
|
||||
--- bypass the hook if the target is already set or there's no link group
|
||||
if target or media_class ~= "Stream/Output/Audio" or not is_virtual or link_group == nil then
|
||||
return
|
||||
end
|
||||
|
||||
--- We link the output node but the relevant properties are on the input node
|
||||
--- of the link group
|
||||
local input_node = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "media.class", "=", "Audio/Sink" },
|
||||
Constraint { "node.link-group", "=", link_group },
|
||||
}
|
||||
|
||||
if input_node == nil then
|
||||
log:warning (si, string.format("No input node for %s found", link_group))
|
||||
return
|
||||
end
|
||||
|
||||
local target_name = input_node.properties["policy.role-based.preferred-target"]
|
||||
--- no preferred target
|
||||
if target_name == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local si_target = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "node.name", "=", target_name },
|
||||
}
|
||||
if si_target == nil then
|
||||
si_target = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "c", "si-audio-adapter", "si-node" },
|
||||
Constraint { "node.nick", "=", target_name },
|
||||
}
|
||||
end
|
||||
if si_target then
|
||||
log:info (si,
|
||||
string.format ("... role based sink target picked: %s (%s)",
|
||||
tostring (si_target.properties ["node.name"]),
|
||||
tostring (si_target.properties ["node.id"])))
|
||||
event:set_data ("target", si_target)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
|
|
@ -26,7 +26,7 @@ function checkFilter (si, om, handle_nonstreams)
|
|||
|
||||
-- always return true if this is not a filter
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties["node.link-group"]
|
||||
local link_group = node:get_property ("node.link-group")
|
||||
if link_group == nil then
|
||||
return true
|
||||
end
|
||||
|
|
@ -43,36 +43,34 @@ function checkFilter (si, om, handle_nonstreams)
|
|||
end
|
||||
|
||||
function checkLinkable (si, om, handle_nonstreams)
|
||||
local si_props = si.properties
|
||||
|
||||
-- For the rest of them, only handle stream session items
|
||||
if not si_props or (si_props ["item.node.type"] ~= "stream"
|
||||
and not handle_nonstreams) then
|
||||
return false, si_props
|
||||
if si:get_property ("item.node.type") ~= "stream" and
|
||||
not handle_nonstreams then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check filters
|
||||
if not checkFilter (si, om, handle_nonstreams) then
|
||||
return false, si_props
|
||||
return false
|
||||
end
|
||||
|
||||
return true, si_props
|
||||
return true
|
||||
end
|
||||
|
||||
function unhandleLinkable (si, om)
|
||||
local si_id = si.id
|
||||
local valid, si_props = checkLinkable (si, om, true)
|
||||
if not valid then
|
||||
if not checkLinkable (si, om, true) then
|
||||
return
|
||||
end
|
||||
|
||||
local si_id = si.id
|
||||
log:info (si, string.format ("unhandling item %d", si_id))
|
||||
|
||||
-- iterate over all the links in the graph and
|
||||
-- remove any links associated with this item
|
||||
for silink in om:iterate { type = "SiLink" } do
|
||||
local out_id = tonumber (silink.properties ["out.item.id"])
|
||||
local in_id = tonumber (silink.properties ["in.item.id"])
|
||||
local silink_props = silink.properties
|
||||
local out_id = silink_props:get_int ("out.item.id")
|
||||
local in_id = silink_props:get_int ("in.item.id")
|
||||
|
||||
if out_id == si_id or in_id == si_id then
|
||||
local in_flags = lutils:get_flags (in_id)
|
||||
|
|
@ -84,7 +82,7 @@ function unhandleLinkable (si, om)
|
|||
out_flags.peer_id = nil
|
||||
end
|
||||
|
||||
if cutils.parseBool (silink.properties["is.role.policy.link"]) then
|
||||
if silink_props:get_boolean ("is.role.policy.link") then
|
||||
lutils.clearPriorityMediaRoleLink(silink)
|
||||
end
|
||||
|
||||
|
|
@ -113,17 +111,67 @@ SimpleEventHook {
|
|||
end
|
||||
}:register ()
|
||||
|
||||
-- Handle newly added linkable immediately without waiting for full rescan
|
||||
-- Only for simple cases where we know it won't affect other parts of the graph
|
||||
SimpleEventHook {
|
||||
name = "linking/linkable-added-immediate",
|
||||
before = "linking/rescan-trigger",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "session-item-added" },
|
||||
Constraint { "event.session-item.interface", "=", "linkable" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local si = event:get_subject ()
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
|
||||
if not checkLinkable (si, om, false) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't handle immediately if this is a smart filter that could affect other nodes
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node:get_property ("node.link-group")
|
||||
if link_group then
|
||||
local direction = cutils.getTargetDirection (si.properties)
|
||||
if futils.is_filter_smart (direction, link_group) then
|
||||
-- Smart filters need full rescan to handle cascading effects
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Only handle if autoconnect is enabled
|
||||
local autoconnect = si:get_property ("node.autoconnect")
|
||||
if autoconnect ~= "true" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if this is a simple stream (most common case)
|
||||
-- Don't handle device nodes or special nodes that might become default targets
|
||||
if si:get_property ("item.node.type") ~= "stream" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Push select-target event immediately for simple stream case
|
||||
source:call ("push-event", "select-target", si, nil)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
function handleLinkables (source)
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
|
||||
for si in om:iterate { type = "SiLinkable" } do
|
||||
local valid, si_props = checkLinkable (si, om)
|
||||
if not valid then
|
||||
if not checkLinkable (si, om) then
|
||||
goto skip_linkable
|
||||
end
|
||||
|
||||
-- Get properties
|
||||
local si_props = si.properties
|
||||
|
||||
-- check if we need to link this node at all
|
||||
local autoconnect = cutils.parseBool (si_props ["node.autoconnect"])
|
||||
local autoconnect = si_props:get_boolean ("node.autoconnect")
|
||||
if not autoconnect then
|
||||
log:debug (si, tostring (si_props ["node.name"]) .. " does not need to be autoconnected")
|
||||
goto skip_linkable
|
||||
|
|
@ -155,7 +203,7 @@ SimpleEventHook {
|
|||
Constraint { "node.link-group", "+" },
|
||||
} do
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local link_group = node.properties["node.link-group"]
|
||||
local link_group = node:get_property ("node.link-group")
|
||||
local direction = cutils.getTargetDirection (si.properties)
|
||||
if futils.is_filter_smart (direction, link_group) and
|
||||
futils.is_filter_disabled (direction, link_group) then
|
||||
|
|
@ -235,7 +283,6 @@ SimpleEventHook {
|
|||
},
|
||||
execute = function (event)
|
||||
local si = event:get_subject ()
|
||||
local si_props = si.properties
|
||||
local source = event:get_source ()
|
||||
|
||||
-- clear timeout source, if any
|
||||
|
|
|
|||
|
|
@ -297,9 +297,9 @@ function createNode(parent, id, obj_type, factory, properties)
|
|||
properties["node.description"] = desc:gsub("(:)", " ")
|
||||
end
|
||||
|
||||
-- add api.alsa.card.* properties for rule matching purposes
|
||||
-- add api.alsa.card.* and alsa.* properties for rule matching purposes
|
||||
for k, v in pairs(dev_props) do
|
||||
if k:find("^api%.alsa%.card%..*") then
|
||||
if k:find("^api%.alsa%.card%..*") or k:find("^alsa%..*") then
|
||||
properties[k] = v
|
||||
end
|
||||
end
|
||||
|
|
@ -494,6 +494,11 @@ function prepareDevice(parent, id, obj_type, factory, properties)
|
|||
factory = "api.alsa.acp.device"
|
||||
end
|
||||
|
||||
-- use HDMI channel detection if enabled in settings
|
||||
if Settings.get_boolean ("monitor.alsa.autodetect-hdmi-channels") then
|
||||
properties["api.acp.use-eld-channels"] = true
|
||||
end
|
||||
|
||||
-- use device reservation, if available
|
||||
if rd_plugin and properties["api.alsa.card"] then
|
||||
local rd_name = "Audio" .. properties["api.alsa.card"]
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ end
|
|||
|
||||
function createMonitor()
|
||||
local monitor_props = {}
|
||||
for k, v in pairs(config.properties or {}) do
|
||||
for k, v in pairs(config.properties or Properties()) do
|
||||
monitor_props[k] = v
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ SimpleEventHook {
|
|||
-- Create group loopback module if it does not exist
|
||||
local m = group_loopback_modules [direction][group]
|
||||
if m == nil then
|
||||
Log.warning ("Creating " .. direction .. " loopback for audio group " .. group ..
|
||||
Log.info ("Creating " .. direction .. " loopback for audio group " .. group ..
|
||||
(target_object and (" with target object " .. tostring (target_object)) or ""))
|
||||
m = CreateStreamLoopback (stream_props, group, target_object, direction)
|
||||
group_loopback_modules [direction][group] = m
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ items = {}
|
|||
function configProperties (node)
|
||||
local properties = node.properties
|
||||
local media_class = properties ["media.class"] or ""
|
||||
local factory_name = properties ["factory.name"] or ""
|
||||
|
||||
-- ensure a media.type is set
|
||||
if not properties ["media.type"] then
|
||||
|
|
@ -40,6 +41,7 @@ function configProperties (node)
|
|||
properties ["item.features.control-port"] =
|
||||
Settings.get_boolean ("node.features.audio.control-port")
|
||||
properties ["item.features.mono"] =
|
||||
(factory_name == "api.alsa.pcm.sink" or factory_name == "api.bluez5.a2dp.sink") and
|
||||
Settings.get_boolean ("node.features.audio.mono")
|
||||
properties ["node.id"] = node ["bound-id"]
|
||||
|
||||
|
|
|
|||
53
src/scripts/node/filter-graph.lua
Normal file
53
src/scripts/node/filter-graph.lua
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2025 The WirePlumber project contributors
|
||||
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
log = Log.open_topic("s-node")
|
||||
|
||||
config = {}
|
||||
config.rules = Conf.get_section_as_json ("node.filter-graph.rules", Json.Array{})
|
||||
|
||||
function setNodeFilterGraphParams (node, graph_params)
|
||||
local pod = Pod.Object {
|
||||
"Spa:Pod:Object:Param:Props", "Props",
|
||||
params = Pod.Struct (graph_params)
|
||||
}
|
||||
node:set_params("Props", pod)
|
||||
end
|
||||
|
||||
|
||||
SimpleEventHook {
|
||||
name = "node/create-filter-graph",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "node-added" },
|
||||
Constraint { "library.name", "=", "audioconvert/libspa-audioconvert", type = "pw" },
|
||||
},
|
||||
},
|
||||
execute = function(event)
|
||||
local node = event:get_subject()
|
||||
|
||||
JsonUtils.match_rules (config.rules, node.properties, function (action, value)
|
||||
|
||||
if action == "create-filter-graph" then
|
||||
local graphs = value:parse (1)
|
||||
|
||||
local graph_params = {}
|
||||
for idx, val in ipairs (graphs) do
|
||||
local index = tonumber(idx) - 1
|
||||
local key = "audioconvert.filter-graph." .. tostring (index)
|
||||
|
||||
log:info (node, "setting node filter graph param '" .. key .. "' to: " .. val)
|
||||
|
||||
table.insert(graph_params, key)
|
||||
table.insert(graph_params, val)
|
||||
end
|
||||
|
||||
setNodeFilterGraphParams (node, graph_params)
|
||||
end
|
||||
end)
|
||||
end
|
||||
}:register()
|
||||
94
src/scripts/node/find-media-role-default-volume.lua
Normal file
94
src/scripts/node/find-media-role-default-volume.lua
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2025 Phosh.mobi e.V.
|
||||
-- @author Guido Günther <agx@sigxcpu.org>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Select the media role default volume
|
||||
|
||||
log = Log.open_topic("s-node")
|
||||
|
||||
local cutils = require ("common-utils")
|
||||
|
||||
function findHighestPriorityRoleNode (node_om)
|
||||
local best_role = nil
|
||||
local best_prio = 0
|
||||
|
||||
local default_role = Settings.get ("node.stream.default-media-role")
|
||||
if default_role then
|
||||
default_role = default_role:parse()
|
||||
end
|
||||
|
||||
for ni in node_om:iterate {
|
||||
type = "node",
|
||||
Constraint { "media.class", "=", "Audio/Sink" },
|
||||
Constraint { "node.name", "#", "input.loopback.sink.role.*" },
|
||||
} do
|
||||
local ni_props = ni.properties
|
||||
local roles = ni_props["device.intended-roles"]
|
||||
local node_name = ni_props ["node.name"]
|
||||
local prio = tonumber(ni_props ["policy.role-based.priority"])
|
||||
|
||||
-- Use the node that handles the default_role as fallback
|
||||
-- when no node is in running state
|
||||
if best_role == nil and roles and default_role then
|
||||
local roles_table = Json.Raw(roles):parse()
|
||||
for i, v in ipairs (roles_table) do
|
||||
if default_role == v then
|
||||
best_role = node_name
|
||||
best_prio = prio
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ni.state == "running" then
|
||||
if prio > best_prio then
|
||||
best_role = node_name
|
||||
best_prio = prio
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
log:info (string.format ("Volume control is on : '%s', prio %d", best_role, best_prio))
|
||||
local metadata = cutils.get_default_metadata_object ()
|
||||
metadata:set (0, "current.role-based.volume.control", "Spa:String:JSON",
|
||||
Json.Object { ["name"] = best_role }:to_string ())
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "node/rescan-for-media-role-volume",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "rescan-for-media-role-volume" }
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local node_om = source:call ("get-object-manager", "node")
|
||||
findHighestPriorityRoleNode (node_om)
|
||||
end
|
||||
}:register ()
|
||||
|
||||
-- Track best volume control for media role based priorities
|
||||
SimpleEventHook {
|
||||
name = "node/find-media-role-default-volume",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "node-added" },
|
||||
Constraint { "media.class", "=", "Audio/Sink" },
|
||||
Constraint { "node.name", "#", "input.loopback.sink.role.*" }
|
||||
},
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "node-state-changed" },
|
||||
Constraint { "media.class", "=", "Audio/Sink" },
|
||||
Constraint { "node.name", "#", "input.loopback.sink.role.*" }
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local node_om = source:call ("get-object-manager", "node")
|
||||
source:call ("schedule-rescan", "media-role-volume")
|
||||
end
|
||||
}:register ()
|
||||
|
|
@ -4,6 +4,11 @@ executable('wpctl',
|
|||
dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep, libintl_dep],
|
||||
)
|
||||
|
||||
install_data('shell-completion/wpctl.bash',
|
||||
install_dir: get_option('datadir') / 'bash-completion/completions',
|
||||
rename: 'wpctl'
|
||||
)
|
||||
|
||||
install_data('shell-completion/wpctl.zsh',
|
||||
install_dir: get_option('datadir') / 'zsh/site-functions',
|
||||
rename: '_wpctl'
|
||||
|
|
|
|||
30
src/tools/shell-completion/wpctl.bash
Normal file
30
src/tools/shell-completion/wpctl.bash
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
_wpctl_pw_defaults() {
|
||||
local defaults="@DEFAULT_SINK@ @DEFAULT_AUDIO_SINK@ @DEFAULT_SOURCE@
|
||||
@DEFAULT_AUDIO_SOURCE@ @DEFAULT_VIDEO_SOURCE@"
|
||||
COMPREPLY+=($(compgen -W "$defaults" -- "$cur"))
|
||||
}
|
||||
|
||||
_wpctl() {
|
||||
local cur prev words cword
|
||||
local commands="status get-volume inspect set-default set-volume set-mute
|
||||
set-profile set-route clear-default settings set-log-level"
|
||||
|
||||
_init_completion -n = || return
|
||||
|
||||
if [[ ${#COMP_WORDS[@]} -eq 2 ]]; then
|
||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
||||
return
|
||||
fi
|
||||
|
||||
case $prev in
|
||||
get-volume | inspect | set-volume | set-mute | set-profile | set-route)
|
||||
_wpctl_pw_defaults
|
||||
;;
|
||||
|
||||
clear-default)
|
||||
COMPREPLY+=($(compgen -W "0 1 2" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _wpctl wpctl
|
||||
|
|
@ -174,7 +174,6 @@ test_node (TestFixture *f, gconstpointer data)
|
|||
|
||||
props = wp_pipewire_object_get_properties (proxy);
|
||||
g_assert_nonnull (props);
|
||||
g_assert_true (wp_properties_peek_dict (props) == info->props);
|
||||
id = wp_properties_get (props, PW_KEY_OBJECT_ID);
|
||||
g_assert_nonnull (id);
|
||||
g_assert_cmpint (info->id, ==, atoi(id));
|
||||
|
|
|
|||
|
|
@ -64,3 +64,9 @@ test(
|
|||
args: ['lua-api-tests', 'event-hooks.lua'],
|
||||
env: common_env,
|
||||
)
|
||||
test(
|
||||
'test-lua-properties',
|
||||
script_tester,
|
||||
args: ['lua-api-tests', 'properties.lua'],
|
||||
env: common_env,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -177,6 +177,23 @@ assert (#val == 0)
|
|||
assert (val.key1 == nil)
|
||||
assert (json:get_data() == "{}")
|
||||
|
||||
json = Json.Object (
|
||||
Properties {
|
||||
["key0"] = nil,
|
||||
["key1"] = false,
|
||||
["key2"] = 64,
|
||||
["key3"] = 2.71,
|
||||
["key4"] = "string",
|
||||
}
|
||||
)
|
||||
assert (json:is_object())
|
||||
val = json:parse ()
|
||||
assert (val.key0 == nil)
|
||||
assert (val.key1 == "false")
|
||||
assert (val.key2 == "64")
|
||||
assert (tonumber (val.key3) > 2.70 and tonumber (val.key3) < 2.72)
|
||||
assert (val.key4 == "string")
|
||||
|
||||
-- Raw
|
||||
json = Json.Raw ("[\"foo\", \"bar\"]")
|
||||
assert (json:is_array())
|
||||
|
|
|
|||
93
tests/wplua/scripts/properties.lua
Normal file
93
tests/wplua/scripts/properties.lua
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
-- create empty properties
|
||||
props = Properties ()
|
||||
|
||||
-- set nil
|
||||
props["key-nil"] = nil
|
||||
assert (props["key-nil"] == nil)
|
||||
|
||||
-- set bool
|
||||
props["key-bool"] = false
|
||||
assert (props["key-bool"] == "false")
|
||||
assert (props:get_boolean ("key-bool") == false)
|
||||
props["key-bool"] = true
|
||||
assert (props["key-bool"] == "true")
|
||||
assert (props:get_boolean ("key-bool") == true)
|
||||
|
||||
-- set int
|
||||
props["key-int"] = 4
|
||||
assert (props["key-int"] == "4")
|
||||
assert (props:get_int ("key-int") == 4)
|
||||
|
||||
-- set float
|
||||
props["key-float"] = 3.14
|
||||
val = props:get_float ("key-float")
|
||||
assert (val > 3.13 and val < 3.15)
|
||||
|
||||
-- set string
|
||||
props["key-string"] = "value"
|
||||
assert (props["key-string"] == "value")
|
||||
assert (props:get_boolean ("key-string") == false)
|
||||
assert (props:get_int ("key-string") == nil)
|
||||
assert (props:get_float ("key-string") == nil)
|
||||
|
||||
-- copy
|
||||
copy = props:copy ()
|
||||
assert (copy["key-nil"] == nil)
|
||||
assert (copy:get_boolean ("key-bool") == true)
|
||||
assert (copy:get_int ("key-int") == 4)
|
||||
val = copy:get_float ("key-float")
|
||||
assert (val > 3.13 and val < 3.15)
|
||||
assert (copy["key-string"] == "value")
|
||||
|
||||
-- remove int property
|
||||
props["key-int"] = nil
|
||||
assert (props["key-int"] == nil)
|
||||
assert (copy:get_int ("key-int") == 4)
|
||||
|
||||
-- create properties from table
|
||||
props = Properties {
|
||||
["key0"] = nil,
|
||||
["key1"] = false,
|
||||
["key2"] = 64,
|
||||
["key3"] = 2.71,
|
||||
["key4"] = "string",
|
||||
}
|
||||
assert (props["key0"] == nil)
|
||||
assert (props:get_boolean ("key1") == false)
|
||||
assert (props:get_int ("key2") == 64)
|
||||
val = props:get_float ("key3")
|
||||
assert (val > 2.70 and val < 2.72)
|
||||
assert (props["key4"] == "string")
|
||||
|
||||
-- count
|
||||
assert (props:get_count () == 4)
|
||||
|
||||
-- parse
|
||||
parsed = props:parse ()
|
||||
assert (parsed["key0"] == nil)
|
||||
assert (parsed["key1"] == "false")
|
||||
assert (tonumber (parsed["key2"]) == 64)
|
||||
val = tonumber (parsed["key3"])
|
||||
assert (val > 2.70 and val < 2.72)
|
||||
assert (parsed["key4"] == "string")
|
||||
|
||||
-- pairs
|
||||
values = {}
|
||||
for k, v in pairs (props) do
|
||||
values [k] = v
|
||||
end
|
||||
assert (values["key0"] == nil)
|
||||
assert (values["key1"] == "false")
|
||||
assert (tonumber (values["key2"]) == 64)
|
||||
val = tonumber (values["key3"])
|
||||
assert (val > 2.70 and val < 2.72)
|
||||
assert (values["key4"] == "string")
|
||||
|
||||
-- Make sure the reference changes are also updated
|
||||
local properties = Properties ()
|
||||
properties["key"] = "value"
|
||||
assert (properties["key"] == "value")
|
||||
local properties2 = properties
|
||||
properties2["key"] = "another-value"
|
||||
assert (properties2["key"] == "another-value")
|
||||
assert (properties["key"] == "another-value")
|
||||
Loading…
Add table
Reference in a new issue