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...
|
# Fedora also ships that, but without the test plugins that we need...
|
||||||
- git clone --depth=1 --branch="$PIPEWIRE_HEAD"
|
- git clone --depth=1 --branch="$PIPEWIRE_HEAD"
|
||||||
https://gitlab.freedesktop.org/pipewire/pipewire.git
|
https://gitlab.freedesktop.org/pipewire/pipewire.git
|
||||||
- meson "$PW_BUILD_DIR" pipewire --prefix="$PREFIX"
|
# Set build options based on PipeWire version
|
||||||
-Dpipewire-alsa=disabled -Dpipewire-jack=disabled
|
- |
|
||||||
-Dalsa=disabled -Dv4l2=disabled -Djack=disabled -Dbluez5=disabled
|
case "$PIPEWIRE_HEAD" in
|
||||||
-Dvulkan=disabled -Dgstreamer=disabled -Dlibsystemd=disabled
|
1.0|1.2|1.4)
|
||||||
-Ddocs=disabled -Dman=disabled -Dexamples=disabled -Dpw-cat=disabled
|
export PIPEWIRE_BUILD_OPTIONS="-Dsystemd=disabled"
|
||||||
-Dsdl2=disabled -Dsndfile=disabled -Dlibpulse=disabled -Davahi=disabled
|
;;
|
||||||
-Decho-cancel-webrtc=disabled -Dsession-managers=[]
|
*)
|
||||||
|
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
|
-Dvideotestsrc=enabled -Daudiotestsrc=enabled -Dtest=enabled
|
||||||
- ninja $NINJA_ARGS -C "$PW_BUILD_DIR" install
|
- ninja $NINJA_ARGS -C "$PW_BUILD_DIR" install
|
||||||
# misc environment only for wireplumber
|
# misc environment only for wireplumber
|
||||||
|
|
@ -236,6 +245,9 @@ build_on_fedora_no_docs:
|
||||||
stage: build
|
stage: build
|
||||||
variables:
|
variables:
|
||||||
BUILD_OPTIONS: -Dintrospection=enabled -Ddoc=disabled -Dsystem-lua=false
|
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:
|
build_on_ubuntu_with_gir:
|
||||||
extends:
|
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,
|
for enabling devices, linking streams, granting permissions to clients,
|
||||||
etc, as appropriate for a desktop system.
|
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
|
that group streams based on their ``media.role`` property, and assigns a
|
||||||
priority to each role. Depending on the priority configuration, lower
|
priority to each role. Depending on the priority configuration, lower
|
||||||
priority roles may be corked or ducked when a higher priority role stream
|
priority roles may be corked or ducked when a higher priority role stream
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Synopsis:
|
||||||
|
|
||||||
$ meson -Dsession-managers="[ 'wireplumber' ]" build
|
$ meson -Dsession-managers="[ 'wireplumber' ]" build
|
||||||
$ ninja -C build
|
$ ninja -C build
|
||||||
$ make run
|
$ make -C build run
|
||||||
|
|
||||||
Run independently or without installing
|
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
|
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:
|
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;
|
g_autoptr (WpProperties) props = NULL;
|
||||||
|
|
||||||
type = spa_debug_type_short_name (info->type);
|
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",
|
wp_debug_object (self, "object info: id:%u type:%s factory:%s",
|
||||||
id, type, info->factory_name);
|
id, type, info->factory_name);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,161 @@
|
||||||
|
|
||||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-event-dispatcher")
|
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;
|
typedef struct _EventData EventData;
|
||||||
struct _EventData
|
struct _EventData
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +204,8 @@ struct _WpEventDispatcher
|
||||||
GObject parent;
|
GObject parent;
|
||||||
|
|
||||||
GWeakRef core;
|
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 */
|
GSource *source; /* the event loop source */
|
||||||
GList *events; /* the events stack */
|
GList *events; /* the events stack */
|
||||||
struct spa_system *system;
|
struct spa_system *system;
|
||||||
|
|
@ -160,7 +316,9 @@ static void
|
||||||
wp_event_dispatcher_init (WpEventDispatcher * self)
|
wp_event_dispatcher_init (WpEventDispatcher * self)
|
||||||
{
|
{
|
||||||
g_weak_ref_init (&self->core, NULL);
|
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));
|
self->source = g_source_new (&source_funcs, sizeof (WpEventSource));
|
||||||
((WpEventSource *) self->source)->dispatcher = self;
|
((WpEventSource *) self->source)->dispatcher = self;
|
||||||
|
|
@ -184,7 +342,8 @@ wp_event_dispatcher_finalize (GObject * object)
|
||||||
|
|
||||||
close (self->eventfd);
|
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_weak_ref_clear (&self->core);
|
||||||
|
|
||||||
G_OBJECT_CLASS (wp_event_dispatcher_parent_class)->finalize (object);
|
G_OBJECT_CLASS (wp_event_dispatcher_parent_class)->finalize (object);
|
||||||
|
|
@ -284,6 +443,10 @@ void
|
||||||
wp_event_dispatcher_register_hook (WpEventDispatcher * self,
|
wp_event_dispatcher_register_hook (WpEventDispatcher * self,
|
||||||
WpEventHook * hook)
|
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_DISPATCHER (self));
|
||||||
g_return_if_fail (WP_IS_EVENT_HOOK (hook));
|
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);
|
g_return_if_fail (already_registered_dispatcher == NULL);
|
||||||
|
|
||||||
wp_event_hook_set_dispatcher (hook, self);
|
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,
|
wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
||||||
WpEventHook * hook)
|
WpEventHook * hook)
|
||||||
{
|
{
|
||||||
|
GHashTableIter iter;
|
||||||
|
gpointer value;
|
||||||
|
|
||||||
g_return_if_fail (WP_IS_EVENT_DISPATCHER (self));
|
g_return_if_fail (WP_IS_EVENT_DISPATCHER (self));
|
||||||
g_return_if_fail (WP_IS_EVENT_HOOK (hook));
|
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);
|
g_return_if_fail (already_registered_dispatcher == self);
|
||||||
|
|
||||||
wp_event_hook_set_dispatcher (hook, NULL);
|
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
|
* \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
|
* \ingroup wpeventdispatcher
|
||||||
*
|
*
|
||||||
* \param self the event dispatcher
|
* \param self the event dispatcher
|
||||||
|
|
@ -327,7 +578,56 @@ wp_event_dispatcher_unregister_hook (WpEventDispatcher * self,
|
||||||
WpIterator *
|
WpIterator *
|
||||||
wp_event_dispatcher_new_hooks_iterator (WpEventDispatcher * self)
|
wp_event_dispatcher_new_hooks_iterator (WpEventDispatcher * self)
|
||||||
{
|
{
|
||||||
GPtrArray *items =
|
GPtrArray *items = g_ptr_array_new_with_free_func (g_object_unref);
|
||||||
g_ptr_array_copy (self->hooks, (GCopyFunc) g_object_ref, NULL);
|
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);
|
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);
|
WpEventHook * hook);
|
||||||
|
|
||||||
WP_API
|
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
|
G_END_DECLS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,24 @@ wp_event_hook_run (WpEventHook * self,
|
||||||
callback_data);
|
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()
|
* \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);
|
wp_interest_event_hook_get_instance_private (self);
|
||||||
g_autoptr (WpProperties) properties = wp_event_get_properties (event);
|
g_autoptr (WpProperties) properties = wp_event_get_properties (event);
|
||||||
g_autoptr (GObject) subject = wp_event_get_subject (event);
|
g_autoptr (GObject) subject = wp_event_get_subject (event);
|
||||||
GType gtype = subject ? G_OBJECT_TYPE (subject) : WP_TYPE_EVENT;
|
|
||||||
guint i;
|
guint i;
|
||||||
WpObjectInterest *interest = NULL;
|
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++) {
|
for (i = 0; i < priv->interests->len; i++) {
|
||||||
interest = g_ptr_array_index (priv->interests, i);
|
interest = g_ptr_array_index (priv->interests, i);
|
||||||
match = wp_object_interest_matches_full (interest,
|
if (wp_object_interest_matches_full (interest,
|
||||||
WP_INTEREST_MATCH_FLAGS_CHECK_ALL,
|
WP_INTEREST_MATCH_FLAGS_NONE,
|
||||||
gtype, subject, properties, properties);
|
WP_TYPE_EVENT, subject, properties, properties) == WP_INTEREST_MATCH_ALL)
|
||||||
|
|
||||||
/* 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)
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return FALSE;
|
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
|
static void
|
||||||
wp_interest_event_hook_class_init (WpInterestEventHookClass * klass)
|
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;
|
object_class->finalize = wp_interest_event_hook_finalize;
|
||||||
hook_class->runs_for_event = wp_interest_event_hook_runs_for_event;
|
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);
|
gboolean (*finish) (WpEventHook * self, GAsyncResult * res, GError ** error);
|
||||||
|
|
||||||
|
GPtrArray * (*get_matching_event_types) (WpEventHook *self);
|
||||||
|
|
||||||
/*< private >*/
|
/*< private >*/
|
||||||
WP_PADDING(5)
|
WP_PADDING(4)
|
||||||
};
|
};
|
||||||
|
|
||||||
WP_API
|
WP_API
|
||||||
|
|
@ -67,6 +69,9 @@ void wp_event_hook_run (WpEventHook * self,
|
||||||
WpEvent * event, GCancellable * cancellable,
|
WpEvent * event, GCancellable * cancellable,
|
||||||
GAsyncReadyCallback callback, gpointer callback_data);
|
GAsyncReadyCallback callback, gpointer callback_data);
|
||||||
|
|
||||||
|
WP_API
|
||||||
|
GPtrArray * wp_event_hook_get_matching_event_types (WpEventHook * self);
|
||||||
|
|
||||||
WP_API
|
WP_API
|
||||||
gboolean wp_event_hook_finish (WpEventHook * self, GAsyncResult * res,
|
gboolean wp_event_hook_finish (WpEventHook * self, GAsyncResult * res,
|
||||||
GError ** error);
|
GError ** error);
|
||||||
|
|
|
||||||
262
lib/wp/event.c
262
lib/wp/event.c
|
|
@ -17,37 +17,11 @@
|
||||||
|
|
||||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-event")
|
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
|
struct _WpEvent
|
||||||
{
|
{
|
||||||
grefcount ref;
|
grefcount ref;
|
||||||
GData *datalist;
|
GData *datalist;
|
||||||
struct spa_list hooks;
|
GPtrArray *hooks;
|
||||||
|
|
||||||
/* immutable fields */
|
/* immutable fields */
|
||||||
gint priority;
|
gint priority;
|
||||||
|
|
@ -96,7 +70,7 @@ wp_event_new (const gchar * type, gint priority, WpProperties * properties,
|
||||||
WpEvent * self = g_new0 (WpEvent, 1);
|
WpEvent * self = g_new0 (WpEvent, 1);
|
||||||
g_ref_count_init (&self->ref);
|
g_ref_count_init (&self->ref);
|
||||||
g_datalist_init (&self->datalist);
|
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->priority = priority;
|
||||||
self->properties = properties ?
|
self->properties = properties ?
|
||||||
|
|
@ -155,11 +129,7 @@ wp_event_get_name(WpEvent *self)
|
||||||
static void
|
static void
|
||||||
wp_event_free (WpEvent * self)
|
wp_event_free (WpEvent * self)
|
||||||
{
|
{
|
||||||
HookData *hook_data;
|
g_clear_pointer (&self->hooks, g_ptr_array_unref);
|
||||||
spa_list_consume (hook_data, &self->hooks, link) {
|
|
||||||
spa_list_remove (&hook_data->link);
|
|
||||||
hook_data_free (hook_data);
|
|
||||||
}
|
|
||||||
g_datalist_clear (&self->datalist);
|
g_datalist_clear (&self->datalist);
|
||||||
g_clear_pointer (&self->properties, wp_properties_unref);
|
g_clear_pointer (&self->properties, wp_properties_unref);
|
||||||
g_clear_object (&self->source);
|
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);
|
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
|
* \brief Collects all the hooks registered in the \a dispatcher that run for
|
||||||
* this \a event
|
* this \a event
|
||||||
|
|
@ -355,199 +298,37 @@ hook_exists_in (const gchar *hook_name, struct spa_list *list)
|
||||||
gboolean
|
gboolean
|
||||||
wp_event_collect_hooks (WpEvent * event, WpEventDispatcher * dispatcher)
|
wp_event_collect_hooks (WpEvent * event, WpEventDispatcher * dispatcher)
|
||||||
{
|
{
|
||||||
struct spa_list collected, result, remaining;
|
|
||||||
g_autoptr (WpIterator) all_hooks = NULL;
|
g_autoptr (WpIterator) all_hooks = NULL;
|
||||||
g_auto (GValue) value = G_VALUE_INIT;
|
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 (event != NULL, FALSE);
|
||||||
g_return_val_if_fail (WP_IS_EVENT_DISPATCHER (dispatcher), FALSE);
|
g_return_val_if_fail (WP_IS_EVENT_DISPATCHER (dispatcher), FALSE);
|
||||||
|
|
||||||
/* hooks already collected */
|
/* Clear all current hooks */
|
||||||
if (!spa_list_is_empty (&event->hooks))
|
g_ptr_array_set_size (event->hooks, 0);
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
spa_list_init (&collected);
|
/* Get the event type */
|
||||||
spa_list_init (&result);
|
event_type = wp_properties_get (event->properties, "event.type");
|
||||||
spa_list_init (&remaining);
|
wp_debug_object (dispatcher, "Collecting hooks for event %s with type %s",
|
||||||
|
event->name, event_type);
|
||||||
|
|
||||||
/* collect hooks that run for this event */
|
/* Collect hooks that run for this event */
|
||||||
all_hooks = wp_event_dispatcher_new_hooks_iterator (dispatcher);
|
all_hooks = wp_event_dispatcher_new_hooks_for_event_type_iterator (dispatcher,
|
||||||
|
event_type);
|
||||||
while (wp_iterator_next (all_hooks, &value)) {
|
while (wp_iterator_next (all_hooks, &value)) {
|
||||||
WpEventHook *hook = g_value_get_object (&value);
|
WpEventHook *hook = g_value_get_object (&value);
|
||||||
|
|
||||||
if (wp_event_hook_runs_for_event (hook, event)) {
|
if (wp_event_hook_runs_for_event (hook, event)) {
|
||||||
HookData *hook_data = hook_data_new (hook);
|
g_ptr_array_add (event->hooks, g_object_ref (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);
|
|
||||||
|
|
||||||
wp_debug_boxed (WP_TYPE_EVENT, event, "added "WP_OBJECT_FORMAT"(%s)",
|
wp_debug_boxed (WP_TYPE_EVENT, event, "added "WP_OBJECT_FORMAT"(%s)",
|
||||||
WP_OBJECT_ARGS (hook), wp_event_hook_get_name (hook));
|
WP_OBJECT_ARGS (hook), wp_event_hook_get_name (hook));
|
||||||
}
|
}
|
||||||
|
|
||||||
g_value_unset (&value);
|
g_value_unset (&value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!spa_list_is_empty (&collected)) {
|
return event->hooks->len > 0;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* \brief Returns an iterator that iterates over all the hooks that were
|
||||||
* collected by wp_event_collect_hooks()
|
* collected by wp_event_collect_hooks()
|
||||||
|
|
@ -558,15 +339,8 @@ static const WpIteratorMethods event_hooks_iterator_methods = {
|
||||||
WpIterator *
|
WpIterator *
|
||||||
wp_event_new_hooks_iterator (WpEvent * event)
|
wp_event_new_hooks_iterator (WpEvent * event)
|
||||||
{
|
{
|
||||||
WpIterator *it = NULL;
|
GPtrArray *hooks;
|
||||||
struct event_hooks_iterator_data *it_data;
|
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;
|
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,
|
WpInterestMatchFlags flags, GType object_type, gpointer object,
|
||||||
WpProperties * pw_props, WpProperties * pw_global_props);
|
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_DEFINE_AUTOPTR_CLEANUP_FUNC (WpObjectInterest, wp_object_interest_unref)
|
||||||
|
|
||||||
G_END_DECLS
|
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_STRUCT_MEMBER (const struct spa_dict *, d->info, iface->props_offset);
|
||||||
|
|
||||||
g_clear_pointer (&d->properties, wp_properties_unref);
|
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");
|
g_object_notify (G_OBJECT (instance), "properties");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <spa/utils/cleanup.h>
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "proc-utils.h"
|
#include "proc-utils.h"
|
||||||
|
|
@ -145,6 +147,21 @@ wp_proc_info_get_cgroup (WpProcInfo * self)
|
||||||
return self->cgroup;
|
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
|
* \brief Gets the process information of a given PID
|
||||||
* \ingroup wpprocutils
|
* \ingroup wpprocutils
|
||||||
|
|
@ -155,51 +172,46 @@ WpProcInfo *
|
||||||
wp_proc_utils_get_proc_info (pid_t pid)
|
wp_proc_utils_get_proc_info (pid_t pid)
|
||||||
{
|
{
|
||||||
WpProcInfo *ret = wp_proc_info_new (pid);
|
WpProcInfo *ret = wp_proc_info_new (pid);
|
||||||
g_autofree gchar *status = NULL;
|
char path [64];
|
||||||
g_autoptr (GError) error = NULL;
|
spa_autoclose int base_fd = -1;
|
||||||
gsize length = 0;
|
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 */
|
/* Get parent PID */
|
||||||
{
|
file = fdopenat (base_fd, "status",
|
||||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/status", pid);
|
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||||
if (g_file_get_contents (path, &status, &length, &error)) {
|
if (file) {
|
||||||
const gchar *loc = strstr (status, "\nPPid:");
|
while (getline (&line, &size, file) > 1)
|
||||||
if (loc) {
|
if (sscanf (line, "PPid:%d\n", &ret->parent) == 1)
|
||||||
const gint res = sscanf (loc, "\nPPid:%d\n", &ret->parent);
|
break;
|
||||||
if (!res || res == EOF)
|
fclose (file);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get cgroup */
|
/* Get cgroup */
|
||||||
{
|
file = fdopenat (base_fd, "cgroup",
|
||||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/cgroup", pid);
|
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||||
if (g_file_get_contents (path, &ret->cgroup, &length, &error)) {
|
if (file) {
|
||||||
if (length > 0)
|
if (getline (&line, &size, file) > 1)
|
||||||
ret->cgroup [length - 1] = '\0'; /* Remove EOF character */
|
ret->cgroup = g_strstrip (g_strdup (line));
|
||||||
} else {
|
fclose (file);
|
||||||
wp_warning ("failed to get cgroup for PID %d: %s", pid, error->message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get args */
|
/* Get args */
|
||||||
{
|
file = fdopenat (base_fd, "cmdline",
|
||||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/cmdline", pid);
|
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY, "r", 0);
|
||||||
FILE *file = fopen (path, "rb");
|
if (file) {
|
||||||
if (file) {
|
while (getdelim (&line, &size, 0, file) > 1 && ret->n_args < MAX_ARGS)
|
||||||
g_autofree gchar *lineptr = NULL;
|
ret->args[ret->n_args++] = g_strdup (line);
|
||||||
size_t size = 0;
|
fclose (file);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
||||||
18
meson.build
18
meson.build
|
|
@ -158,7 +158,25 @@ common_args = [
|
||||||
'-DG_LOG_USE_STRUCTURED',
|
'-DG_LOG_USE_STRUCTURED',
|
||||||
'-DWP_USE_LOCAL_LOG_TOPIC_IN_G_LOG',
|
'-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')
|
add_project_arguments(common_args, language: 'c')
|
||||||
|
summary({'SPA_AUDIO_MAX_CHANNELS': spa_max_channels})
|
||||||
|
|
||||||
i18n_conf = files()
|
i18n_conf = files()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,8 @@ static int
|
||||||
core_get_properties (lua_State *L)
|
core_get_properties (lua_State *L)
|
||||||
{
|
{
|
||||||
WpCore * core = get_wp_core (L);
|
WpCore * core = get_wp_core (L);
|
||||||
g_autoptr (WpProperties) p = wp_core_get_properties (core);
|
WpProperties *p = wp_core_get_properties (core);
|
||||||
wplua_properties_to_table (L, p);
|
wplua_pushboxed (L, WP_TYPE_PROPERTIES, p);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ static int
|
||||||
core_get_info (lua_State *L)
|
core_get_info (lua_State *L)
|
||||||
{
|
{
|
||||||
WpCore * core = get_wp_core (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_newtable (L);
|
||||||
lua_pushinteger (L, wp_core_get_remote_cookie (core));
|
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_setfield (L, -2, "host_name");
|
||||||
lua_pushstring (L, wp_core_get_remote_version (core));
|
lua_pushstring (L, wp_core_get_remote_version (core));
|
||||||
lua_setfield (L, -2, "version");
|
lua_setfield (L, -2, "version");
|
||||||
wplua_properties_to_table (L, p);
|
wplua_pushboxed (L, WP_TYPE_PROPERTIES, p);
|
||||||
lua_setfield (L, -2, "properties");
|
lua_setfield (L, -2, "properties");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +297,13 @@ static int
|
||||||
core_update_properties (lua_State *L)
|
core_update_properties (lua_State *L)
|
||||||
{
|
{
|
||||||
WpCore *core = get_wp_core(L);
|
WpCore *core = get_wp_core(L);
|
||||||
luaL_checktype (L, 1, LUA_TTABLE);
|
WpProperties *props = NULL;
|
||||||
wp_core_update_properties (core, wplua_table_to_properties (L, 1));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,6 +604,28 @@ push_wpiterator (lua_State *L, WpIterator *it)
|
||||||
return 2;
|
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 */
|
/* Settings WpIterator */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
@ -837,7 +864,11 @@ object_interest_matches (lua_State *L)
|
||||||
matches = wp_object_interest_matches (interest, wplua_toobject (L, 2));
|
matches = wp_object_interest_matches (interest, wplua_toobject (L, 2));
|
||||||
}
|
}
|
||||||
else if (lua_istable (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);
|
matches = wp_object_interest_matches (interest, props);
|
||||||
} else
|
} else
|
||||||
luaL_argerror (L, 2, "expected GObject or table");
|
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);
|
const char *name = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpImplMetadata *m = wp_impl_metadata_new_full (get_wp_core (L),
|
||||||
name, properties);
|
name, properties);
|
||||||
|
|
@ -1017,10 +1049,11 @@ device_new (lua_State *L)
|
||||||
const char *factory = luaL_checkstring (L, 1);
|
const char *factory = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpDevice *d = wp_device_new_from_factory (get_wp_export_core (L),
|
||||||
factory, properties);
|
factory, properties);
|
||||||
|
|
@ -1037,10 +1070,11 @@ spa_device_new (lua_State *L)
|
||||||
const char *factory = luaL_checkstring (L, 1);
|
const char *factory = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpSpaDevice *d = wp_spa_device_new_from_spa_factory (get_wp_export_core (L),
|
||||||
factory, properties);
|
factory, properties);
|
||||||
|
|
@ -1105,10 +1139,11 @@ node_new (lua_State *L)
|
||||||
const char *factory = luaL_checkstring (L, 1);
|
const char *factory = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpNode *d = wp_node_new_from_factory (get_wp_export_core (L),
|
||||||
factory, properties);
|
factory, properties);
|
||||||
|
|
@ -1214,10 +1249,11 @@ impl_node_new (lua_State *L)
|
||||||
const char *factory = luaL_checkstring (L, 1);
|
const char *factory = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpImplNode *d = wp_impl_node_new_from_pw_factory (get_wp_export_core (L),
|
||||||
factory, properties);
|
factory, properties);
|
||||||
|
|
@ -1250,10 +1286,11 @@ link_new (lua_State *L)
|
||||||
const char *factory = luaL_checkstring (L, 1);
|
const char *factory = luaL_checkstring (L, 1);
|
||||||
WpProperties *properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL) {
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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);
|
WpLink *l = wp_link_new_from_factory (get_wp_core (L), factory, properties);
|
||||||
if (l)
|
if (l)
|
||||||
|
|
@ -1329,9 +1366,12 @@ static int
|
||||||
client_update_properties (lua_State *L)
|
client_update_properties (lua_State *L)
|
||||||
{
|
{
|
||||||
WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
|
WpClient *client = wplua_checkobject (L, 1, WP_TYPE_CLIENT);
|
||||||
|
WpProperties *properties = NULL;
|
||||||
|
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
if (lua_istable (L, 2))
|
||||||
WpProperties *properties = wplua_table_to_properties (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);
|
wp_client_update_properties (client, properties);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1391,46 +1431,12 @@ static int
|
||||||
session_item_configure (lua_State *L)
|
session_item_configure (lua_State *L)
|
||||||
{
|
{
|
||||||
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
|
WpSessionItem *si = wplua_checkobject (L, 1, WP_TYPE_SESSION_ITEM);
|
||||||
WpProperties *props = wp_properties_new_empty ();
|
WpProperties *props;
|
||||||
|
|
||||||
/* validate arguments */
|
if (lua_istable (L, 2))
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
props = wplua_table_to_properties (L, 2);
|
||||||
|
else
|
||||||
/* build the configuration properties */
|
props = wp_properties_ref (wplua_checkboxed (L, 2, WP_TYPE_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushboolean (L, wp_session_item_configure (si, props));
|
lua_pushboolean (L, wp_session_item_configure (si, props));
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -1452,12 +1458,23 @@ session_item_remove (lua_State *L)
|
||||||
return 0;
|
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[] = {
|
static const luaL_Reg session_item_methods[] = {
|
||||||
{ "get_associated_proxy", session_item_get_associated_proxy },
|
{ "get_associated_proxy", session_item_get_associated_proxy },
|
||||||
{ "reset", session_item_reset },
|
{ "reset", session_item_reset },
|
||||||
{ "configure", session_item_configure },
|
{ "configure", session_item_configure },
|
||||||
{ "register", session_item_register },
|
{ "register", session_item_register },
|
||||||
{ "remove", session_item_remove },
|
{ "remove", session_item_remove },
|
||||||
|
{ "get_property", session_item_get_property },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1527,19 +1544,24 @@ on_enum_params_done (WpPipewireObject * pwobj, GAsyncResult * res,
|
||||||
GClosure * closure)
|
GClosure * closure)
|
||||||
{
|
{
|
||||||
g_autoptr (GError) error = NULL;
|
g_autoptr (GError) error = NULL;
|
||||||
GValue val = G_VALUE_INIT;
|
GValue vals[2] = { G_VALUE_INIT, G_VALUE_INIT };
|
||||||
int n_vals = 0;
|
int n_vals = 1;
|
||||||
WpIterator *it;
|
WpIterator *it;
|
||||||
|
|
||||||
it = wp_pipewire_object_enum_params_finish (pwobj, res, &error);
|
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) {
|
if (!it) {
|
||||||
g_value_init (&val, G_TYPE_STRING);
|
g_value_init (&vals[1], G_TYPE_STRING);
|
||||||
g_value_set_string (&val, error->message);
|
g_value_set_string (&vals[1], error->message);
|
||||||
n_vals = 1;
|
n_vals = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_clear_pointer (&it, wp_iterator_unref);
|
g_clear_pointer (&it, wp_iterator_unref);
|
||||||
g_closure_invoke (closure, NULL, n_vals, &val, NULL);
|
g_closure_invoke (closure, NULL, n_vals, vals, NULL);
|
||||||
g_value_unset (&val);
|
|
||||||
|
g_value_unset (&vals[0]);
|
||||||
|
g_value_unset (&vals[1]);
|
||||||
g_closure_invalidate (closure);
|
g_closure_invalidate (closure);
|
||||||
g_closure_unref (closure);
|
g_closure_unref (closure);
|
||||||
}
|
}
|
||||||
|
|
@ -1575,11 +1597,22 @@ pipewire_object_set_param (lua_State *L)
|
||||||
return 0;
|
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[] = {
|
static const luaL_Reg pipewire_object_methods[] = {
|
||||||
{ "enum_params", pipewire_object_enum_params },
|
{ "enum_params", pipewire_object_enum_params },
|
||||||
{ "iterate_params", pipewire_object_iterate_params },
|
{ "iterate_params", pipewire_object_iterate_params },
|
||||||
{ "set_param" , pipewire_object_set_param },
|
{ "set_param" , pipewire_object_set_param },
|
||||||
{ "set_params" , pipewire_object_set_param }, /* deprecated, compat only */
|
{ "set_params" , pipewire_object_set_param }, /* deprecated, compat only */
|
||||||
|
{ "get_property", pipewire_object_get_property },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1606,9 +1639,14 @@ static int
|
||||||
state_save (lua_State *L)
|
state_save (lua_State *L)
|
||||||
{
|
{
|
||||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
g_autoptr (WpProperties) props = NULL;
|
||||||
g_autoptr (WpProperties) props = wplua_table_to_properties (L, 2);
|
|
||||||
g_autoptr (GError) error = 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);
|
gboolean saved = wp_state_save (state, props, &error);
|
||||||
lua_pushboolean (L, saved);
|
lua_pushboolean (L, saved);
|
||||||
lua_pushstring (L, error ? error->message : "");
|
lua_pushstring (L, error ? error->message : "");
|
||||||
|
|
@ -1619,8 +1657,13 @@ static int
|
||||||
state_save_after_timeout (lua_State *L)
|
state_save_after_timeout (lua_State *L)
|
||||||
{
|
{
|
||||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
g_autoptr (WpProperties) props = NULL;
|
||||||
g_autoptr (WpProperties) props = wplua_table_to_properties (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));
|
||||||
|
|
||||||
wp_state_save_after_timeout (state, get_wp_core (L), props);
|
wp_state_save_after_timeout (state, get_wp_core (L), props);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1629,8 +1672,8 @@ static int
|
||||||
state_load (lua_State *L)
|
state_load (lua_State *L)
|
||||||
{
|
{
|
||||||
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
WpState *state = wplua_checkobject (L, 1, WP_TYPE_STATE);
|
||||||
g_autoptr (WpProperties) props = wp_state_load (state);
|
WpProperties *props = wp_state_load (state);
|
||||||
wplua_properties_to_table (L, props);
|
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||||
return 1;
|
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)
|
if (lua_type (L, 2) != LUA_TNONE && lua_type (L, 2) != LUA_TNIL)
|
||||||
args = luaL_checkstring (L, 2);
|
args = luaL_checkstring (L, 2);
|
||||||
|
|
||||||
if (lua_type (L, 3) != LUA_TNONE && lua_type (L, 3) != LUA_TNIL) {
|
if (lua_istable (L, 3))
|
||||||
luaL_checktype (L, 3, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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),
|
WpImplModule *m = wp_impl_module_load (get_wp_export_core (L),
|
||||||
name, args, properties);
|
name, args, properties);
|
||||||
|
|
@ -1681,9 +1725,10 @@ conf_new (lua_State *L)
|
||||||
WpProperties *p = NULL;
|
WpProperties *p = NULL;
|
||||||
WpConf *conf = NULL;
|
WpConf *conf = NULL;
|
||||||
|
|
||||||
if (lua_istable (L, 2)) {
|
if (lua_istable (L, 2))
|
||||||
p = wplua_table_to_properties (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);
|
conf = wp_conf_new (path, p);
|
||||||
if (conf) {
|
if (conf) {
|
||||||
|
|
@ -1721,7 +1766,7 @@ conf_get_section_as_properties (lua_State *L)
|
||||||
const char *section = NULL;
|
const char *section = NULL;
|
||||||
g_autoptr (WpConf) conf = NULL;
|
g_autoptr (WpConf) conf = NULL;
|
||||||
g_autoptr (WpSpaJson) s = NULL;
|
g_autoptr (WpSpaJson) s = NULL;
|
||||||
g_autoptr (WpProperties) props = NULL;
|
WpProperties *props = NULL;
|
||||||
int argi = 1;
|
int argi = 1;
|
||||||
|
|
||||||
/* check if called as method on object */
|
/* check if called as method on object */
|
||||||
|
|
@ -1736,6 +1781,8 @@ conf_get_section_as_properties (lua_State *L)
|
||||||
|
|
||||||
if (lua_istable (L, argi))
|
if (lua_istable (L, argi))
|
||||||
props = wplua_table_to_properties (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
|
else
|
||||||
props = wp_properties_new_empty ();
|
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))
|
if (s && wp_spa_json_is_object (s))
|
||||||
wp_properties_update_from_json (props, s);
|
wp_properties_update_from_json (props, s);
|
||||||
}
|
}
|
||||||
wplua_properties_to_table (L, props);
|
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1901,10 +1948,12 @@ json_utils_match_rules (lua_State *L)
|
||||||
gboolean res;
|
gboolean res;
|
||||||
|
|
||||||
json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
|
json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
|
||||||
luaL_checktype (L, 2, LUA_TTABLE);
|
|
||||||
luaL_checktype (L, 3, LUA_TFUNCTION);
|
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,
|
res = wp_json_utils_match_rules (json, properties, json_utils_match_rules_cb,
|
||||||
L, &error);
|
L, &error);
|
||||||
|
|
@ -1920,17 +1969,21 @@ json_utils_match_rules (lua_State *L)
|
||||||
static int
|
static int
|
||||||
json_utils_match_rules_update_properties (lua_State *L)
|
json_utils_match_rules_update_properties (lua_State *L)
|
||||||
{
|
{
|
||||||
g_autoptr (WpProperties) properties = NULL;
|
WpProperties *properties = NULL;
|
||||||
WpSpaJson *json;
|
WpSpaJson *json;
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
json = wplua_checkboxed (L, 1, WP_TYPE_SPA_JSON);
|
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);
|
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);
|
lua_pushinteger (L, count);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
@ -2012,6 +2065,108 @@ static const luaL_Reg proc_utils_funcs[] = {
|
||||||
{ NULL, NULL }
|
{ 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 */
|
/* WpSettings */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
@ -2305,8 +2460,8 @@ static int
|
||||||
event_get_properties (lua_State *L)
|
event_get_properties (lua_State *L)
|
||||||
{
|
{
|
||||||
WpEvent *event = wplua_checkboxed (L, 1, WP_TYPE_EVENT);
|
WpEvent *event = wplua_checkboxed (L, 1, WP_TYPE_EVENT);
|
||||||
g_autoptr (WpProperties) props = wp_event_get_properties (event);
|
WpProperties *props = wp_event_get_properties (event);
|
||||||
wplua_properties_to_table (L, props);
|
wplua_pushboxed (L, WP_TYPE_PROPERTIES, props);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2428,10 +2583,11 @@ event_dispatcher_push_event (lua_State *L)
|
||||||
lua_pop (L, 1);
|
lua_pop (L, 1);
|
||||||
|
|
||||||
lua_pushliteral (L, "properties");
|
lua_pushliteral (L, "properties");
|
||||||
if (lua_gettable (L, 1) != LUA_TNIL) {
|
if (lua_istable (L, -1))
|
||||||
luaL_checktype (L, -1, LUA_TTABLE);
|
|
||||||
properties = wplua_table_to_properties (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_pop (L, 1);
|
||||||
|
|
||||||
lua_pushliteral (L, "source");
|
lua_pushliteral (L, "source");
|
||||||
|
|
@ -2984,6 +3140,10 @@ wp_lua_scripting_api_init (lua_State *L)
|
||||||
conf_new, conf_methods);
|
conf_new, conf_methods);
|
||||||
wplua_register_type_methods (L, WP_TYPE_PROC_INFO,
|
wplua_register_type_methods (L, WP_TYPE_PROC_INFO,
|
||||||
NULL, proc_info_funcs);
|
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) ||
|
if (!wplua_load_uri (L, URI_API, &error) ||
|
||||||
!wplua_pcall (L, 0, 0, &error)) {
|
!wplua_pcall (L, 0, 0, &error)) {
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ SANDBOX_EXPORT = {
|
||||||
Conf = WpConf,
|
Conf = WpConf,
|
||||||
JsonUtils = JsonUtils,
|
JsonUtils = JsonUtils,
|
||||||
ProcUtils = ProcUtils,
|
ProcUtils = ProcUtils,
|
||||||
|
Properties = WpProperties_new,
|
||||||
SimpleEventHook = WpSimpleEventHook_new,
|
SimpleEventHook = WpSimpleEventHook_new,
|
||||||
AsyncEventHook = WpAsyncEventHook_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 ();
|
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);
|
lua_pushnil (L);
|
||||||
while (lua_next (L, -2)) {
|
while (lua_next (L, -2)) {
|
||||||
/* We only add table values with string keys */
|
/* We only add table values with string keys */
|
||||||
if (lua_type (L, -2) == LUA_TSTRING) {
|
if (lua_type (L, -2) == LUA_TSTRING) {
|
||||||
wp_spa_json_builder_add_property (builder, lua_tostring (L, -2));
|
wp_spa_json_builder_add_property (builder, lua_tostring (L, -2));
|
||||||
|
|
||||||
switch (lua_type (L, -1)) {
|
switch (lua_type (L, -1)) {
|
||||||
case LUA_TBOOLEAN:
|
case LUA_TBOOLEAN:
|
||||||
wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
|
wp_spa_json_builder_add_boolean (builder, lua_toboolean (L, -1));
|
||||||
break;
|
break;
|
||||||
case LUA_TNUMBER:
|
case LUA_TNUMBER:
|
||||||
if (lua_isinteger (L, -1))
|
if (lua_isinteger (L, -1))
|
||||||
wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
|
wp_spa_json_builder_add_int (builder, lua_tointeger (L, -1));
|
||||||
else
|
else
|
||||||
wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
|
wp_spa_json_builder_add_float (builder, lua_tonumber (L, -1));
|
||||||
break;
|
break;
|
||||||
case LUA_TSTRING:
|
case LUA_TSTRING:
|
||||||
wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
|
wp_spa_json_builder_add_string (builder, lua_tostring (L, -1));
|
||||||
break;
|
break;
|
||||||
case LUA_TUSERDATA: {
|
case LUA_TUSERDATA: {
|
||||||
WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
|
WpSpaJson *json = wplua_checkboxed (L, -1, WP_TYPE_SPA_JSON);
|
||||||
wp_spa_json_builder_add_json (builder, json);
|
wp_spa_json_builder_add_json (builder, json);
|
||||||
break;
|
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));
|
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");
|
GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed");
|
||||||
luaL_argcheck (L, obj_v != NULL, 1,
|
luaL_argcheck (L, obj_v != NULL, 1,
|
||||||
"expected userdata storing GValue<GBoxed>");
|
"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 type = G_VALUE_TYPE (obj_v);
|
||||||
|
GType boxed_type = type;
|
||||||
lua_CFunction func = NULL;
|
lua_CFunction func = NULL;
|
||||||
GHashTable *vtables;
|
GHashTable *vtables;
|
||||||
|
|
||||||
|
|
@ -53,6 +54,104 @@ _wplua_gboxed___index (lua_State *L)
|
||||||
lua_pushcfunction (L, func);
|
lua_pushcfunction (L, func);
|
||||||
return 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +168,8 @@ _wplua_init_gboxed (lua_State *L)
|
||||||
{ "__gc", _wplua_gvalue_userdata___gc },
|
{ "__gc", _wplua_gvalue_userdata___gc },
|
||||||
{ "__eq", _wplua_gboxed___eq },
|
{ "__eq", _wplua_gboxed___eq },
|
||||||
{ "__index", _wplua_gboxed___index },
|
{ "__index", _wplua_gboxed___index },
|
||||||
|
{ "__newindex", _wplua_gboxed___newindex },
|
||||||
|
{ "__pairs", _wplua_gboxed___pairs },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ WpProperties *
|
||||||
wplua_table_to_properties (lua_State *L, int idx)
|
wplua_table_to_properties (lua_State *L, int idx)
|
||||||
{
|
{
|
||||||
WpProperties *p = wp_properties_new_empty ();
|
WpProperties *p = wp_properties_new_empty ();
|
||||||
const gchar *key, *value;
|
|
||||||
int table = lua_absindex (L, idx);
|
int table = lua_absindex (L, idx);
|
||||||
|
|
||||||
if (lua_type (L, table) != LUA_TTABLE) {
|
if (lua_type (L, table) != LUA_TTABLE) {
|
||||||
|
|
@ -24,11 +23,34 @@ wplua_table_to_properties (lua_State *L, int idx)
|
||||||
|
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
while (lua_next (L, table) != 0) {
|
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 */
|
/* copy key & value to convert them to string */
|
||||||
key = luaL_tolstring (L, -2, NULL);
|
luaL_checkany (L, -2);
|
||||||
value = luaL_tolstring (L, -2, NULL);
|
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);
|
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 */
|
/* 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));
|
lua_pushlightuserdata (L, g_value_get_pointer (v));
|
||||||
break;
|
break;
|
||||||
case G_TYPE_BOXED:
|
case G_TYPE_BOXED:
|
||||||
if (G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
|
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
|
||||||
wplua_properties_to_table (L, g_value_get_boxed (v));
|
|
||||||
else
|
|
||||||
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
|
|
||||||
break;
|
break;
|
||||||
case G_TYPE_OBJECT:
|
case G_TYPE_OBJECT:
|
||||||
case G_TYPE_INTERFACE: {
|
case G_TYPE_INTERFACE: {
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,15 @@ static void
|
||||||
bind_call (GObject * obj, GAsyncResult * res, gpointer data)
|
bind_call (GObject * obj, GAsyncResult * res, gpointer data)
|
||||||
{
|
{
|
||||||
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
||||||
GError *err = NULL;
|
g_autoptr (GError) err = NULL;
|
||||||
GDBusProxy *call;
|
GDBusProxy *call;
|
||||||
GVariant *prop;
|
g_autoptr (GVariant) prop = NULL;
|
||||||
gint init_state;
|
gint init_state;
|
||||||
|
|
||||||
call = g_dbus_proxy_new_finish (res, &err);
|
call = g_dbus_proxy_new_finish (res, &err);
|
||||||
if (call == NULL) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,8 +123,6 @@ bind_call (GObject * obj, GAsyncResult * res, gpointer data)
|
||||||
|
|
||||||
if (is_active_state (init_state))
|
if (is_active_state (init_state))
|
||||||
active_calls_inc (wpmm);
|
active_calls_inc (wpmm);
|
||||||
|
|
||||||
g_variant_unref (prop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wpmm->calls = g_list_prepend (wpmm->calls, call);
|
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);
|
g_object_get (wpmm->dbus, "connection", &conn, NULL);
|
||||||
|
|
||||||
if (!g_strcmp0 (signal, "CallAdded")) {
|
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_new (conn,
|
||||||
G_DBUS_PROXY_FLAGS_NONE,
|
G_DBUS_PROXY_FLAGS_NONE,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -175,9 +174,8 @@ on_voice_signal (GDBusProxy * iface,
|
||||||
NULL,
|
NULL,
|
||||||
bind_call,
|
bind_call,
|
||||||
wpmm);
|
wpmm);
|
||||||
g_free (path);
|
|
||||||
} else if (!g_strcmp0 (signal, "CallDeleted")) {
|
} 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.
|
// The user shouldn't have hundreds of calls, so just linear search.
|
||||||
deleted = g_list_find_custom (wpmm->calls, path, match_call_path);
|
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);
|
g_object_unref (deleted->data);
|
||||||
wpmm->calls = g_list_delete_link (wpmm->calls, deleted);
|
wpmm->calls = g_list_delete_link (wpmm->calls, deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free (path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,22 +192,23 @@ list_calls_done (GObject * obj,
|
||||||
gpointer data)
|
gpointer data)
|
||||||
{
|
{
|
||||||
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
WpModemManager *wpmm = WP_MODEM_MANAGER (data);
|
||||||
GVariant *params;
|
g_autoptr (GVariant) params = NULL;
|
||||||
GVariantIter *calls;
|
g_autoptr (GVariantIter) calls = NULL;
|
||||||
gchar *path;
|
gchar *path;
|
||||||
GError *err = NULL;
|
g_autoptr (GError) err = NULL;
|
||||||
g_autoptr (GDBusConnection) conn = NULL;
|
g_autoptr (GDBusConnection) conn = NULL;
|
||||||
|
|
||||||
params = g_dbus_proxy_call_finish (G_DBUS_PROXY (obj), res, &err);
|
params = g_dbus_proxy_call_finish (G_DBUS_PROXY (obj), res, &err);
|
||||||
if (params == NULL) {
|
if (params == NULL) {
|
||||||
g_prefix_error (&err, "Failed to list active calls on startup: ");
|
g_prefix_error (&err, "Failed to list active calls on startup: ");
|
||||||
wp_warning_object (wpmm, "%s", err->message);
|
wp_warning_object (wpmm, "%s", err->message);
|
||||||
g_clear_object (&err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_object_get (wpmm->dbus, "connection", &conn, NULL);
|
||||||
|
|
||||||
g_variant_get (params, "(ao)", &calls);
|
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_new (conn,
|
||||||
G_DBUS_PROXY_FLAGS_NONE,
|
G_DBUS_PROXY_FLAGS_NONE,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -222,9 +219,6 @@ list_calls_done (GObject * obj,
|
||||||
bind_call,
|
bind_call,
|
||||||
wpmm);
|
wpmm);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_variant_iter_free (calls);
|
|
||||||
g_variant_unref (params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -353,7 +347,7 @@ static void
|
||||||
wp_modem_manager_enable (WpPlugin * self, WpTransition * transition)
|
wp_modem_manager_enable (WpPlugin * self, WpTransition * transition)
|
||||||
{
|
{
|
||||||
WpModemManager *wpmm = WP_MODEM_MANAGER (self);
|
WpModemManager *wpmm = WP_MODEM_MANAGER (self);
|
||||||
WpCore *core;
|
g_autoptr (WpCore) core = NULL;
|
||||||
GError *err;
|
GError *err;
|
||||||
g_autoptr (GDBusConnection) conn = NULL;
|
g_autoptr (GDBusConnection) conn = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,11 @@ si_audio_adapter_get_default_clock_rate (WpSiAudioAdapter * self)
|
||||||
static gboolean
|
static gboolean
|
||||||
is_unpositioned (struct spa_audio_info_raw *info)
|
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))
|
if (SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED))
|
||||||
return TRUE;
|
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 &&
|
if (info->position[i] >= SPA_AUDIO_CHANNEL_START_Aux &&
|
||||||
info->position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux)
|
info->position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
@ -197,7 +198,7 @@ si_audio_adapter_find_format (WpSiAudioAdapter * self, WpNode * node,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (position == NULL ||
|
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);
|
SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED);
|
||||||
|
|
||||||
if (mono) {
|
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)) {
|
if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
|
||||||
/* Build the position array spa pod */
|
/* Build the position array spa pod */
|
||||||
g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
|
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]);
|
wp_spa_pod_builder_add_id (position_builder, info->position[i]);
|
||||||
|
|
||||||
/* Add the position property */
|
/* Add the position property */
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,16 @@ on_link_activated (WpObject * proxy, GAsyncResult * res,
|
||||||
{
|
{
|
||||||
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
||||||
guint len = self->node_links ? self->node_links->len : 0;
|
guint len = self->node_links ? self->node_links->len : 0;
|
||||||
|
g_autoptr (GError) error = NULL;
|
||||||
|
|
||||||
/* Count the number of failed and active links */
|
/* 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++;
|
self->n_active_links++;
|
||||||
else
|
} else {
|
||||||
self->n_failed_links++;
|
self->n_failed_links++;
|
||||||
|
wp_info_object (self, "Failed to activate link %p: %s", proxy,
|
||||||
|
error->message);
|
||||||
|
}
|
||||||
|
|
||||||
/* Wait for all links to finish activation */
|
/* Wait for all links to finish activation */
|
||||||
if (self->n_failed_links + self->n_active_links != len)
|
if (self->n_failed_links + self->n_active_links != len)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ typedef enum {
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RESCAN_CONTEXT_LINKING,
|
RESCAN_CONTEXT_LINKING,
|
||||||
RESCAN_CONTEXT_DEFAULT_NODES,
|
RESCAN_CONTEXT_DEFAULT_NODES,
|
||||||
|
RESCAN_CONTEXT_MEDIA_ROLE_VOLUME,
|
||||||
N_RESCAN_CONTEXTS,
|
N_RESCAN_CONTEXTS,
|
||||||
} RescanContext;
|
} RescanContext;
|
||||||
|
|
||||||
|
|
@ -48,6 +49,7 @@ rescan_context_get_type (void)
|
||||||
static const GEnumValue values[] = {
|
static const GEnumValue values[] = {
|
||||||
{ RESCAN_CONTEXT_LINKING, "RESCAN_CONTEXT_LINKING", "linking" },
|
{ RESCAN_CONTEXT_LINKING, "RESCAN_CONTEXT_LINKING", "linking" },
|
||||||
{ RESCAN_CONTEXT_DEFAULT_NODES, "RESCAN_CONTEXT_DEFAULT_NODES", "default-nodes" },
|
{ 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 }
|
{ 0, NULL, NULL }
|
||||||
};
|
};
|
||||||
if (g_once_init_enter (>ype_id)) {
|
if (g_once_init_enter (>ype_id)) {
|
||||||
|
|
@ -161,6 +163,8 @@ get_default_event_priority (const gchar *event_type)
|
||||||
return -490;
|
return -490;
|
||||||
else if (!g_strcmp0 (event_type, "rescan-for-linking"))
|
else if (!g_strcmp0 (event_type, "rescan-for-linking"))
|
||||||
return -500;
|
return -500;
|
||||||
|
else if (!g_strcmp0 (event_type, "rescan-for-media-role-volume"))
|
||||||
|
return -510;
|
||||||
else if (!g_strcmp0 (event_type, "node-state-changed"))
|
else if (!g_strcmp0 (event_type, "node-state-changed"))
|
||||||
return 50;
|
return 50;
|
||||||
else if (!g_strcmp0 (event_type, "metadata-changed"))
|
else if (!g_strcmp0 (event_type, "metadata-changed"))
|
||||||
|
|
|
||||||
12
po/conf.pot
12
po/conf.pot
|
|
@ -123,6 +123,16 @@ msgstr ""
|
||||||
msgid "Ducking level"
|
msgid "Ducking level"
|
||||||
msgstr ""
|
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.settings.schema/monitor.camera-discovery-timeout/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "The camera discovery timeout in milliseconds"
|
msgid "The camera discovery timeout in milliseconds"
|
||||||
|
|
@ -155,7 +165,7 @@ msgstr ""
|
||||||
|
|
||||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "Configure all audio nodes in MONO"
|
msgid "Configure all audio device sink nodes in MONO"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
#. /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
|
# Copyright (C) 2024 WirePlumber's COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the WirePlumber package.
|
# 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 ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: WirePlumber master\n"
|
"Project-Id-Version: WirePlumber master\n"
|
||||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues\n"
|
||||||
"issues\n"
|
"POT-Creation-Date: 2025-12-15 16:28+0000\n"
|
||||||
"POT-Creation-Date: 2025-08-21 03:57+0000\n"
|
"PO-Revision-Date: 2025-12-15 23:31+0100\n"
|
||||||
"PO-Revision-Date: 2025-08-21 15:45+0200\n"
|
|
||||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||||
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
|
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
|
||||||
"Language: sl\n"
|
"Language: sl\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
|
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);\n"
|
||||||
"%100==4 ? 3 : 0);\n"
|
"X-Generator: Poedit 3.8\n"
|
||||||
"X-Generator: Poedit 2.2.1\n"
|
|
||||||
|
|
||||||
#. WirePlumber
|
#. WirePlumber
|
||||||
#.
|
#.
|
||||||
|
|
@ -49,7 +47,7 @@ msgstr "Razdeli %s"
|
||||||
#. also sanitize nick, replace ':' with ' '
|
#. also sanitize nick, replace ':' with ' '
|
||||||
#. ensure the node has a description
|
#. ensure the node has a description
|
||||||
#. also sanitize description, replace ':' with ' '
|
#. 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
|
#. add cpu.vm.name for rule matching purposes
|
||||||
#. apply properties from rules defined in JSON .conf file
|
#. apply properties from rules defined in JSON .conf file
|
||||||
#. handle split HW node
|
#. handle split HW node
|
||||||
|
|
@ -75,6 +73,7 @@ msgstr "Modem"
|
||||||
#. form factor -> icon
|
#. form factor -> icon
|
||||||
#. apply properties from rules defined in JSON .conf file
|
#. apply properties from rules defined in JSON .conf file
|
||||||
#. override the device factory to use ACP
|
#. override the device factory to use ACP
|
||||||
|
#. use HDMI channel detection if enabled in settings
|
||||||
#. use device reservation, if available
|
#. use device reservation, if available
|
||||||
#. unlike pipewire-media-session, this logic here keeps the device
|
#. unlike pipewire-media-session, this logic here keeps the device
|
||||||
#. acquired at all times and destroys it if someone else acquires
|
#. 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"
|
msgid "Default source volume"
|
||||||
msgstr "Privzeta izvorna glasnost"
|
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.settings.schema/linking.allow-moving-streams/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "Streams may be moved by adding PipeWire metadata at runtime"
|
msgid "Streams may be moved by adding PipeWire metadata at runtime"
|
||||||
|
|
@ -247,6 +274,19 @@ msgstr ""
|
||||||
msgid "Ducking level"
|
msgid "Ducking level"
|
||||||
msgstr "Stopnja umikanja"
|
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.settings.schema/monitor.camera-discovery-timeout/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "The camera discovery timeout in milliseconds"
|
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"
|
msgid "Monitor ports"
|
||||||
msgstr "Vrata zvočnih monitorjev"
|
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.settings.schema/node.features.audio.no-dsp/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "Do not convert audio to F32 format"
|
msgid "Do not convert audio to F32 format"
|
||||||
|
|
|
||||||
400
po/tr.po
400
po/tr.po
|
|
@ -1,26 +1,25 @@
|
||||||
# Turkish translation for PipeWire.
|
# Turkish translation for WirePlumber.
|
||||||
# Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER
|
# Copyright (C) 2025 WirePlumber's COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the PipeWire package.
|
# This file is distributed under the same license as the WirePlumber package.
|
||||||
# Necdet Yücel <necdetyucel@gmail.com>, 2014.
|
#
|
||||||
# Kaan Özdinçer <kaanozdincer@gmail.com>, 2014.
|
# Sabri Ünal <yakushabb@gmail.com>, 2025.
|
||||||
# Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017.
|
# Emin Tufan Çetin <etcetin@gmail.com>, 2025
|
||||||
# Oğuz Ersen <oguzersen@protonmail.com>, 2021.
|
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PipeWire master\n"
|
"Project-Id-Version: WirePlumber master\n"
|
||||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/"
|
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
||||||
"issues/new\n"
|
"issues\n"
|
||||||
"POT-Creation-Date: 2022-04-09 15:19+0300\n"
|
"POT-Creation-Date: 2025-11-09 04:07+0000\n"
|
||||||
"PO-Revision-Date: 2021-12-06 21:31+0300\n"
|
"PO-Revision-Date: 2025-11-09 08:00+0300\n"
|
||||||
"Last-Translator: Oğuz Ersen <oguzersen@protonmail.com>\n"
|
"Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n"
|
||||||
"Language-Team: Turkish <tr>\n"
|
"Language-Team: Turkish <takim@gnome.org.tr>\n"
|
||||||
"Language: tr\n"
|
"Language: tr\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Weblate 4.4.2\n"
|
"X-Generator: Poedit 3.8\n"
|
||||||
|
|
||||||
#. WirePlumber
|
#. WirePlumber
|
||||||
#.
|
#.
|
||||||
|
|
@ -28,13 +27,19 @@ msgstr ""
|
||||||
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
#. @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||||
#.
|
#.
|
||||||
#. SPDX-License-Identifier: MIT
|
#. SPDX-License-Identifier: MIT
|
||||||
#. Receive script arguments from config.lua
|
#. unique device/node name tables
|
||||||
#. ensure config.properties is not nil
|
#. SPA ids to node names: name = id_name_table[device_id][node_id]
|
||||||
#. preprocess rules and create Interest objects
|
#. create the underlying hidden ALSA node
|
||||||
#. applies properties from config.rules when asked to
|
#. 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 device id and spa factory name; REQUIRED, do not change
|
||||||
#. set the default pause-on-idle setting
|
#. 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
|
#. set priority
|
||||||
#. ensure the node has a media class
|
#. ensure the node has a media class
|
||||||
#. ensure the node has a name
|
#. ensure the node has a name
|
||||||
|
|
@ -45,15 +50,360 @@ msgstr ""
|
||||||
#. ensure the node has a description
|
#. ensure the node has a description
|
||||||
#. also sanitize description, replace ':' with ' '
|
#. also sanitize description, replace ':' with ' '
|
||||||
#. add api.alsa.card.* properties for rule matching purposes
|
#. 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
|
#. create the node
|
||||||
#. ensure the device has an appropriate name
|
#. ensure the device has an appropriate name
|
||||||
#. deduplicate devices with the same name
|
#. deduplicate devices with the same name
|
||||||
#. ensure the device has a description
|
#. ensure the device has a description
|
||||||
#: src/scripts/monitors/alsa.lua:222
|
#: src/scripts/monitors/alsa.lua:438
|
||||||
msgid "Built-in Audio"
|
msgid "Loopback"
|
||||||
msgstr "Dahili Ses"
|
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"
|
msgid "Modem"
|
||||||
msgstr "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"
|
"Project-Id-Version: pipewire.master-tx\n"
|
||||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/wireplumber/-/"
|
||||||
"issues\n"
|
"issues\n"
|
||||||
"POT-Creation-Date: 2025-10-01 16:13+0000\n"
|
"POT-Creation-Date: 2025-12-15 16:28+0000\n"
|
||||||
"PO-Revision-Date: 2025-10-02 07:57+0800\n"
|
"PO-Revision-Date: 2025-12-16 10:10+0800\n"
|
||||||
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
|
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
|
||||||
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
|
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
|
||||||
"Language: zh_CN\n"
|
"Language: zh_CN\n"
|
||||||
|
|
@ -53,7 +53,7 @@ msgstr "分离 %s"
|
||||||
#. also sanitize nick, replace ':' with ' '
|
#. also sanitize nick, replace ':' with ' '
|
||||||
#. ensure the node has a description
|
#. ensure the node has a description
|
||||||
#. also sanitize description, replace ':' with ' '
|
#. 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
|
#. add cpu.vm.name for rule matching purposes
|
||||||
#. apply properties from rules defined in JSON .conf file
|
#. apply properties from rules defined in JSON .conf file
|
||||||
#. handle split HW node
|
#. handle split HW node
|
||||||
|
|
@ -79,6 +79,7 @@ msgstr "调制解调器"
|
||||||
#. form factor -> icon
|
#. form factor -> icon
|
||||||
#. apply properties from rules defined in JSON .conf file
|
#. apply properties from rules defined in JSON .conf file
|
||||||
#. override the device factory to use ACP
|
#. override the device factory to use ACP
|
||||||
|
#. use HDMI channel detection if enabled in settings
|
||||||
#. use device reservation, if available
|
#. use device reservation, if available
|
||||||
#. unlike pipewire-media-session, this logic here keeps the device
|
#. unlike pipewire-media-session, this logic here keeps the device
|
||||||
#. acquired at all times and destroys it if someone else acquires
|
#. acquired at all times and destroys it if someone else acquires
|
||||||
|
|
@ -273,6 +274,18 @@ msgstr ""
|
||||||
msgid "Ducking level"
|
msgid "Ducking level"
|
||||||
msgstr "回避级别"
|
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.settings.schema/monitor.camera-discovery-timeout/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "The camera discovery timeout in milliseconds"
|
msgid "The camera discovery timeout in milliseconds"
|
||||||
|
|
@ -305,8 +318,8 @@ msgstr "监视器端口"
|
||||||
|
|
||||||
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
#. /wireplumber.settings.schema/node.features.audio.mono/description
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
msgid "Configure all audio nodes in MONO"
|
msgid "Configure all audio device sink nodes in MONO"
|
||||||
msgstr "在单声道中配置所有音频节点"
|
msgstr "在单声道中配置所有音频设备信宿节点"
|
||||||
|
|
||||||
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
#. /wireplumber.settings.schema/node.features.audio.mono/name
|
||||||
#: wireplumber.conf
|
#: wireplumber.conf
|
||||||
|
|
|
||||||
|
|
@ -627,12 +627,17 @@ wireplumber.components = [
|
||||||
name = node/filter-forward-format.lua, type = script/lua
|
name = node/filter-forward-format.lua, type = script/lua
|
||||||
provides = hooks.filter.forward-format
|
provides = hooks.filter.forward-format
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = node/filter-graph.lua, type = script/lua
|
||||||
|
provides = hooks.filter.graph
|
||||||
|
}
|
||||||
{
|
{
|
||||||
type = virtual, provides = policy.node
|
type = virtual, provides = policy.node
|
||||||
requires = [ hooks.node.create-session-item ]
|
requires = [ hooks.node.create-session-item ]
|
||||||
wants = [ hooks.node.suspend
|
wants = [ hooks.node.suspend
|
||||||
hooks.stream.state
|
hooks.stream.state
|
||||||
hooks.filter.forward-format ]
|
hooks.filter.forward-format
|
||||||
|
hooks.filter.graph ]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = node/software-dsp.lua, type = script/lua
|
name = node/software-dsp.lua, type = script/lua
|
||||||
|
|
@ -717,10 +722,21 @@ wireplumber.components = [
|
||||||
provides = hooks.linking.role-based.rescan
|
provides = hooks.linking.role-based.rescan
|
||||||
requires = [ api.mixer ]
|
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
|
type = virtual, provides = policy.linking.role-based
|
||||||
requires = [ policy.linking.standard,
|
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
|
## Standard policy definition
|
||||||
|
|
@ -912,6 +928,12 @@ wireplumber.settings.schema = {
|
||||||
min = 0
|
min = 0
|
||||||
max = 60000
|
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
|
||||||
node.features.audio.no-dsp = {
|
node.features.audio.no-dsp = {
|
||||||
|
|
@ -934,7 +956,7 @@ wireplumber.settings.schema = {
|
||||||
}
|
}
|
||||||
node.features.audio.mono = {
|
node.features.audio.mono = {
|
||||||
name = "Mono"
|
name = "Mono"
|
||||||
description = "Configure all audio nodes in MONO"
|
description = "Configure all audio device sink nodes in MONO"
|
||||||
type = "bool"
|
type = "bool"
|
||||||
default = false
|
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.priority = 100
|
||||||
policy.role-based.action.same-priority = "mix"
|
policy.role-based.action.same-priority = "mix"
|
||||||
policy.role-based.action.lower-priority = "cork"
|
policy.role-based.action.lower-priority = "cork"
|
||||||
|
policy.role-based.preferred-target = "Speaker"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
provides = loopback.sink.role.alert
|
provides = loopback.sink.role.alert
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,25 @@ monitor.alsa.rules = [
|
||||||
api.alsa.headroom = 2048
|
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 om = source:call ("get-object-manager", "metadata")
|
||||||
local metadata = om:lookup { Constraint { "metadata.name", "=", "default" } }
|
local metadata = om:lookup { Constraint { "metadata.name", "=", "default" } }
|
||||||
|
if metadata == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if selected_node then
|
if selected_node then
|
||||||
local key = "default." .. def_node_type
|
local key = "default." .. def_node_type
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
cutils = require ("common-utils")
|
cutils = require ("common-utils")
|
||||||
log = Log.open_topic ("s-automute-alsa-routes")
|
log = Log.open_topic ("s-automute-alsa-routes")
|
||||||
|
hooks_registered = false
|
||||||
|
|
||||||
function setRoute (device, route, mute)
|
function setRoute (device, route, mute)
|
||||||
local param = Pod.Object {
|
local param = Pod.Object {
|
||||||
|
|
@ -194,17 +195,19 @@ evaluate_mute_on_node_removed_hook = SimpleEventHook {
|
||||||
function toggleState ()
|
function toggleState ()
|
||||||
local mute_alsa = Settings.get_boolean ("device.routes.mute-on-alsa-playback-removed")
|
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")
|
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 = {}
|
nodes_info = {}
|
||||||
mute_alsa_devices_hook:register ()
|
mute_alsa_devices_hook:register ()
|
||||||
update_nodes_info_hook:register ()
|
update_nodes_info_hook:register ()
|
||||||
evaluate_mute_on_device_route_changed_hook:register ()
|
evaluate_mute_on_device_route_changed_hook:register ()
|
||||||
evaluate_mute_on_node_removed_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 ()
|
mute_alsa_devices_hook:remove ()
|
||||||
update_nodes_info_hook:remove ()
|
update_nodes_info_hook:remove ()
|
||||||
evaluate_mute_on_device_route_changed_hook:remove ()
|
evaluate_mute_on_device_route_changed_hook:remove ()
|
||||||
evaluate_mute_on_node_removed_hook:remove ()
|
evaluate_mute_on_node_removed_hook:remove ()
|
||||||
|
hooks_registered = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ function handlePersistentSetting (enable)
|
||||||
-- the state storage
|
-- the state storage
|
||||||
state = Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile")
|
state = Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile")
|
||||||
and State ("bluetooth-autoswitch") or nil
|
and State ("bluetooth-autoswitch") or nil
|
||||||
headset_profiles = state and state:load () or {}
|
headset_profiles = state and state:load () or Properties()
|
||||||
else
|
else
|
||||||
state = nil
|
state = nil
|
||||||
headset_profiles = nil
|
headset_profiles = nil
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ SimpleEventHook {
|
||||||
local device = event:get_subject ()
|
local device = event:get_subject ()
|
||||||
local event_properties = event:get_properties ()
|
local event_properties = event:get_properties ()
|
||||||
local active_ids = event_properties ["profile.active-device-ids"]
|
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)
|
local dev_info = devinfo:get_device_info (device)
|
||||||
assert (dev_info)
|
assert (dev_info)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ find_stored_profile_hook = SimpleEventHook {
|
||||||
end
|
end
|
||||||
|
|
||||||
local device = event:get_subject ()
|
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
|
if not dev_name then
|
||||||
log:critical (device, "invalid device.name")
|
log:critical (device, "invalid device.name")
|
||||||
return
|
return
|
||||||
|
|
@ -45,7 +48,8 @@ find_stored_profile_hook = SimpleEventHook {
|
||||||
if profile_name then
|
if profile_name then
|
||||||
for p in device:iterate_params ("EnumProfile") do
|
for p in device:iterate_params ("EnumProfile") do
|
||||||
local profile = cutils.parseParam (p, "EnumProfile")
|
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
|
selected_profile = profile
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ find_stored_routes_hook = SimpleEventHook {
|
||||||
local event_properties = event:get_properties ()
|
local event_properties = event:get_properties ()
|
||||||
local profile_name = event_properties ["profile.name"]
|
local profile_name = event_properties ["profile.name"]
|
||||||
local active_ids = event_properties ["profile.active-device-ids"]
|
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)
|
local dev_info = devinfo:get_device_info (device)
|
||||||
assert (dev_info)
|
assert (dev_info)
|
||||||
|
|
@ -108,13 +108,13 @@ apply_route_props_hook = SimpleEventHook {
|
||||||
},
|
},
|
||||||
execute = function (event)
|
execute = function (event)
|
||||||
local device = event:get_subject ()
|
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 new_selected_routes = {}
|
||||||
|
|
||||||
local dev_info = devinfo:get_device_info (device)
|
local dev_info = devinfo:get_device_info (device)
|
||||||
assert (dev_info)
|
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)
|
log:info (device, "No routes selected to set on " .. dev_info.name)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -159,129 +159,125 @@ store_or_restore_routes_hook = AsyncEventHook {
|
||||||
},
|
},
|
||||||
steps = {
|
steps = {
|
||||||
start = {
|
start = {
|
||||||
next = "evaluate",
|
next = "none",
|
||||||
execute = function (event, transition)
|
execute = function (event, transition)
|
||||||
|
local source = event:get_source ()
|
||||||
|
local device = event:get_subject ()
|
||||||
|
|
||||||
-- Make sure the routes are always updated before evaluating them.
|
-- Make sure the routes are always updated before evaluating them.
|
||||||
-- https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/762
|
-- https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/762
|
||||||
local device = event:get_subject ()
|
device:enum_params ("EnumRoute", function (enum_route_it, e)
|
||||||
device:enum_params ("EnumRoute", function (_, e)
|
local selected_routes = {}
|
||||||
|
local push_select_routes = false
|
||||||
|
|
||||||
|
-- check for error
|
||||||
if e then
|
if e then
|
||||||
transition:return_error ("failed to enum routes: "
|
transition:return_error ("failed to enum routes: "
|
||||||
.. tostring (e));
|
.. tostring (e));
|
||||||
else
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make sure the device is still valid
|
||||||
|
if (device:get_active_features() & Feature.Proxy.BOUND) == 0 then
|
||||||
transition:advance ()
|
transition:advance ()
|
||||||
end
|
return
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- find cached route information
|
local dev_info = devinfo:get_device_info (device)
|
||||||
local route_info = devinfo.find_route_info (dev_info, route, true)
|
if not dev_info then
|
||||||
if not route_info then
|
transition:advance ()
|
||||||
goto skip_enum_route
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update properties
|
local new_route_infos = {}
|
||||||
route_info.prev_active = route_info.active
|
|
||||||
route_info.active = false
|
|
||||||
route_info.save = false
|
|
||||||
|
|
||||||
-- store
|
-- look at all the routes and update/reset cached information
|
||||||
new_route_infos [route.index] = route_info
|
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::
|
-- find cached route information
|
||||||
end
|
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
|
-- update properties
|
||||||
dev_info.route_infos = new_route_infos
|
route_info.prev_active = route_info.active
|
||||||
new_route_infos = nil
|
route_info.active = false
|
||||||
|
route_info.save = false
|
||||||
|
|
||||||
-- check for changes in the active routes
|
-- store
|
||||||
for p in device:iterate_params ("Route") do
|
new_route_infos [route.index] = route_info
|
||||||
local route = cutils.parseParam (p, "Route")
|
|
||||||
if not route then
|
::skip_enum_route::
|
||||||
goto skip_route
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get cached route info and at the same time
|
-- update route_infos with new prev_active, active and save changes
|
||||||
-- ensure that the route is also in EnumRoute
|
dev_info.route_infos = new_route_infos
|
||||||
local route_info = devinfo.find_route_info (dev_info, route, false)
|
new_route_infos = nil
|
||||||
if not route_info then
|
|
||||||
goto skip_route
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update route_info state
|
-- check for changes in the active routes
|
||||||
route_info.active = true
|
for p in device:iterate_params ("Route") do
|
||||||
route_info.save = route.save
|
local route = cutils.parseParam (p, "Route")
|
||||||
|
if not route then
|
||||||
|
goto skip_route
|
||||||
|
end
|
||||||
|
|
||||||
if not route_info.prev_active then
|
-- get cached route info and at the same time
|
||||||
-- a new route is now active, restore the volume and
|
-- ensure that the route is also in EnumRoute
|
||||||
-- make sure we save this as a preferred route
|
local route_info = devinfo.find_route_info (dev_info, route, false)
|
||||||
log:info (device,
|
if not route_info then
|
||||||
string.format ("new active route(%s) found of device(%s)",
|
goto skip_route
|
||||||
route.name, dev_info.name))
|
end
|
||||||
route_info.prev_active = true
|
|
||||||
|
-- update route_info state
|
||||||
route_info.active = true
|
route_info.active = true
|
||||||
|
route_info.save = route.save
|
||||||
|
|
||||||
selected_routes [tostring (route.device)] =
|
if not route_info.prev_active then
|
||||||
Json.Object { index = route_info.index }:to_string ()
|
-- a new route is now active, restore the volume and
|
||||||
push_select_routes = true
|
-- 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
|
selected_routes [tostring (route.device)] =
|
||||||
-- just save route properties
|
Json.Object { index = route_info.index }:to_string ()
|
||||||
log:info (device,
|
push_select_routes = true
|
||||||
string.format ("storing route(%s) props of device(%s)",
|
|
||||||
route.name, dev_info.name))
|
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
::skip_route::
|
-- save selected routes for the active profile
|
||||||
end
|
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
|
-- push a select-routes event to re-apply the routes with new properties
|
||||||
for p in device:iterate_params ("Profile") do
|
if push_select_routes then
|
||||||
local profile = cutils.parseParam (p, "Profile")
|
local e = source:call ("create-event", "select-routes", device, nil)
|
||||||
saveProfileRoutes (dev_info, profile.name)
|
e:set_data ("selected-routes", selected_routes)
|
||||||
end
|
EventDispatcher.push_event (e)
|
||||||
|
end
|
||||||
|
|
||||||
-- push a select-routes event to re-apply the routes with new properties
|
transition:advance ()
|
||||||
if push_select_routes then
|
end)
|
||||||
local e = source:call ("create-event", "select-routes", device, nil)
|
|
||||||
e:set_data ("selected-routes", selected_routes)
|
|
||||||
EventDispatcher.push_event (e)
|
|
||||||
end
|
|
||||||
|
|
||||||
transition:advance ()
|
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Hooks
|
||||||
|
|
||||||
The hooks in this section are organized in 3 sub-categories. The first category
|
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
|
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
|
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
|
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
|
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",
|
name = "linking/find-default-target",
|
||||||
after = { "linking/find-defined-target",
|
after = { "linking/find-defined-target",
|
||||||
"linking/find-filter-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",
|
before = "linking/prepare-link",
|
||||||
interests = {
|
interests = {
|
||||||
EventInterest {
|
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
|
-- always return true if this is not a filter
|
||||||
local node = si:get_associated_proxy ("node")
|
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
|
if link_group == nil then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
@ -43,36 +43,34 @@ function checkFilter (si, om, handle_nonstreams)
|
||||||
end
|
end
|
||||||
|
|
||||||
function checkLinkable (si, om, handle_nonstreams)
|
function checkLinkable (si, om, handle_nonstreams)
|
||||||
local si_props = si.properties
|
|
||||||
|
|
||||||
-- For the rest of them, only handle stream session items
|
-- For the rest of them, only handle stream session items
|
||||||
if not si_props or (si_props ["item.node.type"] ~= "stream"
|
if si:get_property ("item.node.type") ~= "stream" and
|
||||||
and not handle_nonstreams) then
|
not handle_nonstreams then
|
||||||
return false, si_props
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check filters
|
-- check filters
|
||||||
if not checkFilter (si, om, handle_nonstreams) then
|
if not checkFilter (si, om, handle_nonstreams) then
|
||||||
return false, si_props
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, si_props
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function unhandleLinkable (si, om)
|
function unhandleLinkable (si, om)
|
||||||
local si_id = si.id
|
if not checkLinkable (si, om, true) then
|
||||||
local valid, si_props = checkLinkable (si, om, true)
|
|
||||||
if not valid then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local si_id = si.id
|
||||||
log:info (si, string.format ("unhandling item %d", si_id))
|
log:info (si, string.format ("unhandling item %d", si_id))
|
||||||
|
|
||||||
-- iterate over all the links in the graph and
|
-- iterate over all the links in the graph and
|
||||||
-- remove any links associated with this item
|
-- remove any links associated with this item
|
||||||
for silink in om:iterate { type = "SiLink" } do
|
for silink in om:iterate { type = "SiLink" } do
|
||||||
local out_id = tonumber (silink.properties ["out.item.id"])
|
local silink_props = silink.properties
|
||||||
local in_id = tonumber (silink.properties ["in.item.id"])
|
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
|
if out_id == si_id or in_id == si_id then
|
||||||
local in_flags = lutils:get_flags (in_id)
|
local in_flags = lutils:get_flags (in_id)
|
||||||
|
|
@ -84,7 +82,7 @@ function unhandleLinkable (si, om)
|
||||||
out_flags.peer_id = nil
|
out_flags.peer_id = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if cutils.parseBool (silink.properties["is.role.policy.link"]) then
|
if silink_props:get_boolean ("is.role.policy.link") then
|
||||||
lutils.clearPriorityMediaRoleLink(silink)
|
lutils.clearPriorityMediaRoleLink(silink)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -113,17 +111,67 @@ SimpleEventHook {
|
||||||
end
|
end
|
||||||
}:register ()
|
}: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)
|
function handleLinkables (source)
|
||||||
local om = source:call ("get-object-manager", "session-item")
|
local om = source:call ("get-object-manager", "session-item")
|
||||||
|
|
||||||
for si in om:iterate { type = "SiLinkable" } do
|
for si in om:iterate { type = "SiLinkable" } do
|
||||||
local valid, si_props = checkLinkable (si, om)
|
if not checkLinkable (si, om) then
|
||||||
if not valid then
|
|
||||||
goto skip_linkable
|
goto skip_linkable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Get properties
|
||||||
|
local si_props = si.properties
|
||||||
|
|
||||||
-- check if we need to link this node at all
|
-- 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
|
if not autoconnect then
|
||||||
log:debug (si, tostring (si_props ["node.name"]) .. " does not need to be autoconnected")
|
log:debug (si, tostring (si_props ["node.name"]) .. " does not need to be autoconnected")
|
||||||
goto skip_linkable
|
goto skip_linkable
|
||||||
|
|
@ -155,7 +203,7 @@ SimpleEventHook {
|
||||||
Constraint { "node.link-group", "+" },
|
Constraint { "node.link-group", "+" },
|
||||||
} do
|
} do
|
||||||
local node = si:get_associated_proxy ("node")
|
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)
|
local direction = cutils.getTargetDirection (si.properties)
|
||||||
if futils.is_filter_smart (direction, link_group) and
|
if futils.is_filter_smart (direction, link_group) and
|
||||||
futils.is_filter_disabled (direction, link_group) then
|
futils.is_filter_disabled (direction, link_group) then
|
||||||
|
|
@ -235,7 +283,6 @@ SimpleEventHook {
|
||||||
},
|
},
|
||||||
execute = function (event)
|
execute = function (event)
|
||||||
local si = event:get_subject ()
|
local si = event:get_subject ()
|
||||||
local si_props = si.properties
|
|
||||||
local source = event:get_source ()
|
local source = event:get_source ()
|
||||||
|
|
||||||
-- clear timeout source, if any
|
-- clear timeout source, if any
|
||||||
|
|
|
||||||
|
|
@ -297,9 +297,9 @@ function createNode(parent, id, obj_type, factory, properties)
|
||||||
properties["node.description"] = desc:gsub("(:)", " ")
|
properties["node.description"] = desc:gsub("(:)", " ")
|
||||||
end
|
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
|
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
|
properties[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -494,6 +494,11 @@ function prepareDevice(parent, id, obj_type, factory, properties)
|
||||||
factory = "api.alsa.acp.device"
|
factory = "api.alsa.acp.device"
|
||||||
end
|
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
|
-- use device reservation, if available
|
||||||
if rd_plugin and properties["api.alsa.card"] then
|
if rd_plugin and properties["api.alsa.card"] then
|
||||||
local rd_name = "Audio" .. properties["api.alsa.card"]
|
local rd_name = "Audio" .. properties["api.alsa.card"]
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ end
|
||||||
|
|
||||||
function createMonitor()
|
function createMonitor()
|
||||||
local monitor_props = {}
|
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
|
monitor_props[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ SimpleEventHook {
|
||||||
-- Create group loopback module if it does not exist
|
-- Create group loopback module if it does not exist
|
||||||
local m = group_loopback_modules [direction][group]
|
local m = group_loopback_modules [direction][group]
|
||||||
if m == nil then
|
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 ""))
|
(target_object and (" with target object " .. tostring (target_object)) or ""))
|
||||||
m = CreateStreamLoopback (stream_props, group, target_object, direction)
|
m = CreateStreamLoopback (stream_props, group, target_object, direction)
|
||||||
group_loopback_modules [direction][group] = m
|
group_loopback_modules [direction][group] = m
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ items = {}
|
||||||
function configProperties (node)
|
function configProperties (node)
|
||||||
local properties = node.properties
|
local properties = node.properties
|
||||||
local media_class = properties ["media.class"] or ""
|
local media_class = properties ["media.class"] or ""
|
||||||
|
local factory_name = properties ["factory.name"] or ""
|
||||||
|
|
||||||
-- ensure a media.type is set
|
-- ensure a media.type is set
|
||||||
if not properties ["media.type"] then
|
if not properties ["media.type"] then
|
||||||
|
|
@ -40,6 +41,7 @@ function configProperties (node)
|
||||||
properties ["item.features.control-port"] =
|
properties ["item.features.control-port"] =
|
||||||
Settings.get_boolean ("node.features.audio.control-port")
|
Settings.get_boolean ("node.features.audio.control-port")
|
||||||
properties ["item.features.mono"] =
|
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")
|
Settings.get_boolean ("node.features.audio.mono")
|
||||||
properties ["node.id"] = node ["bound-id"]
|
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],
|
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_data('shell-completion/wpctl.zsh',
|
||||||
install_dir: get_option('datadir') / 'zsh/site-functions',
|
install_dir: get_option('datadir') / 'zsh/site-functions',
|
||||||
rename: '_wpctl'
|
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);
|
props = wp_pipewire_object_get_properties (proxy);
|
||||||
g_assert_nonnull (props);
|
g_assert_nonnull (props);
|
||||||
g_assert_true (wp_properties_peek_dict (props) == info->props);
|
|
||||||
id = wp_properties_get (props, PW_KEY_OBJECT_ID);
|
id = wp_properties_get (props, PW_KEY_OBJECT_ID);
|
||||||
g_assert_nonnull (id);
|
g_assert_nonnull (id);
|
||||||
g_assert_cmpint (info->id, ==, atoi(id));
|
g_assert_cmpint (info->id, ==, atoi(id));
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,9 @@ test(
|
||||||
args: ['lua-api-tests', 'event-hooks.lua'],
|
args: ['lua-api-tests', 'event-hooks.lua'],
|
||||||
env: common_env,
|
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 (val.key1 == nil)
|
||||||
assert (json:get_data() == "{}")
|
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
|
-- Raw
|
||||||
json = Json.Raw ("[\"foo\", \"bar\"]")
|
json = Json.Raw ("[\"foo\", \"bar\"]")
|
||||||
assert (json:is_array())
|
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