Merge branch 'master' into next
This commit is contained in:
64
NEWS.rst
64
NEWS.rst
@@ -1,6 +1,62 @@
|
||||
WirePlumber 0.4.14
|
||||
WirePlumber 0.4.15
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Additions:
|
||||
|
||||
- A new "DSP policy" module has been added; its purpose is to automatically
|
||||
load a filter-chain when a certain hardware device is present, so that
|
||||
audio always goes through this software DSP before reaching the device.
|
||||
This is mainly to support Apple M1/M2 devices, which require a software
|
||||
DSP to be always present
|
||||
|
||||
- WpImplModule now supports loading module arguments directly from a SPA-JSON
|
||||
config file; this is mainly to support DSP configuration for Apple M1/M2
|
||||
and will likely be reworked for 0.5
|
||||
|
||||
- Added support for automatically combining Bluetooth LE Audio device sets
|
||||
(e.g. pairs of earbuds) (!500)
|
||||
|
||||
- Added command line options in ``wpctl`` to display device/node names and
|
||||
nicknames instead of descriptions
|
||||
|
||||
- Added zsh completions file for ``wpctl``
|
||||
|
||||
- The device profile selection policy now respects the ``device.profile``
|
||||
property if it is set on the device; this is useful to hand-pick a profile
|
||||
based on static configuration rules (alsa_monitor.rules)
|
||||
|
||||
Changes/Fixes:
|
||||
|
||||
- Linking policy now sends an error to the client before destroying the node,
|
||||
if it determines that the node cannot be linked to any target; this fixes
|
||||
error reporting on the client side
|
||||
|
||||
- Fixed a crash in suspend-node that could happen when destroying virtual
|
||||
sinks that were loaded from another process such as pw-loopback (#467)
|
||||
|
||||
- Virtual machine default period size has been bumped to 1024 (#507)
|
||||
|
||||
- Updated bluez5 default configuration, using ``bluez5.roles`` instead of
|
||||
``bluez5.headset-roles`` now (!498)
|
||||
|
||||
- Disabled Bluetooth autoconnect by default (!514)
|
||||
|
||||
- Removed ``RestrictNamespaces`` option from the systemd services in order to
|
||||
allow libcamera to load sandboxed IPA modules (#466)
|
||||
|
||||
- Fixed a JSON encoding bug with empty strings (#471)
|
||||
|
||||
- Lua code can now parse strings without quotes from SPA-JSON
|
||||
|
||||
- Added some missing `\since` annotations and made them show up in the
|
||||
generated gobject-introspection file, to help bindings generators
|
||||
|
||||
Past releases
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
WirePlumber 0.4.14
|
||||
..................
|
||||
|
||||
Additions:
|
||||
|
||||
- Added support for managing Bluetooth-MIDI, complimenting the parts that
|
||||
@@ -24,9 +80,6 @@ Additions:
|
||||
- Added support for disabling libcamera nodes & devices with ``node.disabled``
|
||||
and ``device.disabled``, like it works for ALSA and V4L2 (#418)
|
||||
|
||||
Past releases
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
WirePlumber 0.4.13
|
||||
..................
|
||||
|
||||
@@ -76,9 +129,6 @@ Packaging:
|
||||
|
||||
- Added pkg-config and header information in the gir file
|
||||
|
||||
Past releases
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
WirePlumber 0.4.12
|
||||
..................
|
||||
|
||||
|
@@ -439,10 +439,24 @@ on the ALSA device.
|
||||
|
||||
This can be done in 3 different ways:
|
||||
|
||||
1. Use pavucontrol and toggle the codecs in the output advanced section
|
||||
1. Use pavucontrol and toggle the codecs in the output advanced section.
|
||||
|
||||
2. Modify the ``["iec958.codecs"] = "[ PCM DTS AC3 MPEG MPEG2-AAC EAC3 TrueHD DTS-HD ]"``
|
||||
node property to something.
|
||||
2. Modify the ``["iec958.codecs"]`` node property to contain suported codecs.
|
||||
|
||||
Example ``~/.config/wireplumber/main.lua.d/51-alsa-spdif.lua``:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
table.insert (alsa_monitor.rules, {
|
||||
matches = {
|
||||
{
|
||||
{ "node.name", "matches", "alsa_output.*" },
|
||||
},
|
||||
},
|
||||
apply_properties = {
|
||||
["iec958.codecs"] = "[ PCM DTS AC3 EAC3 TrueHD DTS-HD ]",
|
||||
}
|
||||
})
|
||||
|
||||
3. Use ``pw-cli s <node-id> Props '{ iec958Codecs : [ PCM ] }'`` to modify
|
||||
the codecs at runtime.
|
||||
|
@@ -11,7 +11,7 @@ dropped, the module is unloaded.
|
||||
Constructors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. function:: LocalModule(name, arguments, properties)
|
||||
.. function:: LocalModule(name, arguments, properties, [load_args_from_file])
|
||||
|
||||
Loads the named module with the provided arguments and properties (either of
|
||||
which can be ``nil``).
|
||||
@@ -21,6 +21,7 @@ Constructors
|
||||
module arguments
|
||||
:param table properties: can be ``nil`` or a table that can be
|
||||
:ref:`converted <lua_gobject_lua_to_c>` to :c:struct:`WpProperties`
|
||||
:param load_args_from_file: (since 0.4.15) optional. if true, arguments param is treated as a file path to load args from
|
||||
:returns: a new LocalModule
|
||||
:rtype: LocalModule (:c:struct:`WpImplModule`)
|
||||
:since: 0.4.2
|
||||
|
@@ -10,6 +10,10 @@
|
||||
#include "log.h"
|
||||
|
||||
#include <pipewire/impl.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-module")
|
||||
|
||||
@@ -261,3 +265,63 @@ wp_impl_module_load (WpCore * core, const gchar * name,
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Loads a PipeWire module with arguments from file into the WirePlumber process
|
||||
*
|
||||
* \ingroup wpimplmodule
|
||||
* \since 0.4.15
|
||||
* \param core (transfer none): The WirePlumber core
|
||||
* \param name (transfer none): the name of the module to load
|
||||
* \param filename (transfer none): filename to be used as arguments
|
||||
* \param properties (nullable) (transfer none): additional properties to be
|
||||
* provided to the module
|
||||
* \returns (nullable) (transfer full): the WpImplModule for the module that
|
||||
* was loaded on success, %NULL on failure.
|
||||
*/
|
||||
WpImplModule *
|
||||
wp_impl_module_load_file (WpCore * core, const gchar * name,
|
||||
const gchar * filename, WpProperties * properties)
|
||||
{
|
||||
char *config = "";
|
||||
int fd = open(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
g_warning("Failed to open config file %s: %m", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct stat stats;
|
||||
int err = fstat(fd, &stats);
|
||||
if (err < 0) {
|
||||
g_warning("Failed to stat config file %s: %m", filename);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (config == MAP_FAILED){
|
||||
g_warning("Failed to mmap config file %s: %m", filename);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
WpImplModule *module = WP_IMPL_MODULE (
|
||||
g_object_new (WP_TYPE_IMPL_MODULE,
|
||||
"core", core,
|
||||
"name", name,
|
||||
"arguments", config,
|
||||
"properties", properties,
|
||||
NULL)
|
||||
);
|
||||
|
||||
munmap(config, stats.st_size);
|
||||
|
||||
if (!module->pw_impl_module) {
|
||||
/* Module loading failed, free and return */
|
||||
g_object_unref (module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
@@ -29,6 +29,9 @@ G_DECLARE_FINAL_TYPE (WpImplModule, wp_impl_module, WP, IMPL_MODULE, GObject);
|
||||
WP_API
|
||||
WpImplModule * wp_impl_module_load (WpCore * core, const gchar * name,
|
||||
const gchar * arguments, WpProperties * properties);
|
||||
WP_API
|
||||
WpImplModule * wp_impl_module_load_file (WpCore * core, const gchar * name,
|
||||
const gchar * filename, WpProperties * properties);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -638,8 +638,8 @@ wp_object_manager_is_interested_in_global (WpObjectManager * self,
|
||||
/* and consider the manager interested if the type and the globals match...
|
||||
if pw_properties / g_properties fail, that's ok because they are not
|
||||
known yet (the proxy is likely NULL and properties not yet retrieved) */
|
||||
if (match & (WP_INTEREST_MATCH_GTYPE |
|
||||
WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES)) {
|
||||
if (SPA_FLAG_IS_SET (match, (WP_INTEREST_MATCH_GTYPE |
|
||||
WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES))) {
|
||||
gpointer ft = g_hash_table_lookup (self->features,
|
||||
GSIZE_TO_POINTER (global->type));
|
||||
*wanted_features = (WpObjectFeatures) GPOINTER_TO_UINT (ft);
|
||||
@@ -1049,18 +1049,15 @@ wp_registry_detach (WpRegistry *self)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self)
|
||||
static gboolean
|
||||
expose_tmp_globals (WpCore *core)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
WpRegistry *self = wp_core_get_registry (core);
|
||||
g_autoptr (GPtrArray) tmp_globals = NULL;
|
||||
|
||||
if (!wp_core_sync_finish (core, res, &error))
|
||||
wp_warning_object (core, "core sync error: %s", error->message);
|
||||
|
||||
/* in case the registry was cleared in the meantime... */
|
||||
if (G_UNLIKELY (!self->tmp_globals))
|
||||
return;
|
||||
return G_SOURCE_REMOVE;
|
||||
|
||||
/* steal the tmp_globals list and replace it with an empty one */
|
||||
tmp_globals = self->tmp_globals;
|
||||
@@ -1084,8 +1081,8 @@ expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self)
|
||||
wp_global_rm_flag (old_g, WP_GLOBAL_FLAG_OWNED_BY_PROXY);
|
||||
}
|
||||
|
||||
g_return_if_fail (self->globals->len <= g->id ||
|
||||
g_ptr_array_index (self->globals, g->id) == NULL);
|
||||
g_return_val_if_fail (self->globals->len <= g->id ||
|
||||
g_ptr_array_index (self->globals, g->id) == NULL, G_SOURCE_REMOVE);
|
||||
|
||||
/* set the registry, so that wp_global_rm_flag() can work full-scale */
|
||||
g->registry = self;
|
||||
@@ -1111,6 +1108,8 @@ expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self)
|
||||
}
|
||||
wp_object_manager_maybe_objects_changed (om);
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1161,7 +1160,8 @@ wp_registry_prepare_new_global (WpRegistry * self, guint32 id,
|
||||
|
||||
/* schedule exposing when adding the first global */
|
||||
if (self->tmp_globals->len == 1) {
|
||||
wp_core_sync (core, NULL, (GAsyncReadyCallback) expose_tmp_globals, self);
|
||||
wp_core_idle_add_closure (core, NULL,
|
||||
g_cclosure_new_object (G_CALLBACK (expose_tmp_globals), G_OBJECT (core)));
|
||||
}
|
||||
} else {
|
||||
/* store the most permissive permissions */
|
||||
|
@@ -6,6 +6,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "lua.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include <wp/wp.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
@@ -1536,8 +1537,20 @@ impl_module_new (lua_State *L)
|
||||
properties = wplua_table_to_properties (L, 3);
|
||||
}
|
||||
|
||||
WpImplModule *m = wp_impl_module_load (get_wp_export_core (L),
|
||||
name, args, properties);
|
||||
bool load_file = false; // Load args as file path
|
||||
if (lua_type (L, 4) != LUA_TNONE && lua_type (L, 4) != LUA_TNIL) {
|
||||
luaL_checktype (L, 4, LUA_TBOOLEAN);
|
||||
load_file = lua_toboolean(L, 4);
|
||||
}
|
||||
|
||||
WpImplModule *m = NULL;
|
||||
if (load_file) {
|
||||
m = wp_impl_module_load_file (get_wp_export_core (L),
|
||||
name, args, properties);
|
||||
} else {
|
||||
m = wp_impl_module_load (get_wp_export_core (L),
|
||||
name, args, properties);
|
||||
}
|
||||
|
||||
if (m) {
|
||||
wplua_pushobject (L, m);
|
||||
|
@@ -35,13 +35,17 @@ monitor.alsa.midi.node-properties = {
|
||||
|
||||
## Removes longname/number from MIDI port names
|
||||
api.alsa.disable-longname = true
|
||||
|
||||
## Set priorities so that it can be used as a fallback driver (see pipewire#3562)
|
||||
priority.session = 100
|
||||
priority.driver = 1
|
||||
}
|
||||
|
||||
monitor.alsa.vm.node.defaults = {
|
||||
## These properties override node defaults when running in a virtual machine.
|
||||
## The rules below still override those.
|
||||
|
||||
api.alsa.period-size = 256
|
||||
api.alsa.period-size = 1024
|
||||
api.alsa.headroom = 8192
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,8 @@ SimpleEventHook {
|
||||
local off_profile = nil
|
||||
local best_profile = nil
|
||||
local unk_profile = nil
|
||||
-- Takes absolute priority if available or unknown
|
||||
local profile_prop = device.properties["device.profile"]
|
||||
|
||||
-- skip hook if profile is already selected
|
||||
if selected_profile then
|
||||
@@ -34,7 +36,9 @@ SimpleEventHook {
|
||||
|
||||
for p in device:iterate_params ("EnumProfile") do
|
||||
profile = cutils.parseParam (p, "EnumProfile")
|
||||
if profile and profile.name ~= "pro-audio" then
|
||||
if profile and profile.name == profile_prop and profile.available ~= "no" then
|
||||
selected_profile = profile
|
||||
elseif profile and profile.name ~= "pro-audio" then
|
||||
if profile.name == "off" then
|
||||
off_profile = profile
|
||||
elseif profile.available == "yes" then
|
||||
|
61
src/scripts/policy-dsp.lua
Normal file
61
src/scripts/policy-dsp.lua
Normal file
@@ -0,0 +1,61 @@
|
||||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022-2023 The WirePlumber project contributors
|
||||
-- @author Dmitry Sharshakov <d3dx12.xx@gmail.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
local config = ... or {}
|
||||
config.rules = config.rules or {}
|
||||
|
||||
for _, r in ipairs(config.rules) do
|
||||
r.interests = {}
|
||||
for _, i in ipairs(r.matches) do
|
||||
local interest_desc = { type = "properties" }
|
||||
|
||||
for _, c in ipairs(i) do
|
||||
c.type = "pw"
|
||||
table.insert(interest_desc, Constraint(c))
|
||||
end
|
||||
|
||||
local interest = Interest(interest_desc)
|
||||
table.insert(r.interests, interest)
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: only check for hotplug of nodes with known DSP rules
|
||||
nodes_om = ObjectManager {
|
||||
Interest { type = "node" },
|
||||
}
|
||||
|
||||
filter_chains = {}
|
||||
|
||||
nodes_om:connect("object-added", function (om, node)
|
||||
for _, r in ipairs(config.rules or {}) do
|
||||
for _, interest in ipairs(r.interests) do
|
||||
if interest:matches(node["global-properties"]) then
|
||||
local id = node["global-properties"]["object.id"]
|
||||
|
||||
if r.filter_chain then
|
||||
if filter_chains[id] then
|
||||
Log.warning("Sink " .. id .. " has been plugged now, but has a filter chain loaded. Skipping")
|
||||
else
|
||||
filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
nodes_om:connect("object-removed", function (om, node)
|
||||
local id = node["global-properties"]["object.id"]
|
||||
if filter_chains[id] then
|
||||
Log.debug("Unloading filter chain associated with sink " .. id)
|
||||
filter_chains[id] = nil
|
||||
else
|
||||
Log.debug("Disconnected sink " .. id .. " does not have any filters to be removed")
|
||||
end
|
||||
end)
|
||||
|
||||
nodes_om:activate()
|
@@ -8,6 +8,11 @@ executable('wpctl',
|
||||
dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
install_data('shell-completion/wpctl.zsh',
|
||||
install_dir: get_option('datadir') / 'zsh/site-functions',
|
||||
rename: '_wpctl'
|
||||
)
|
||||
|
||||
executable('wpexec',
|
||||
'wpexec.c',
|
||||
c_args : [
|
||||
|
49
src/tools/shell-completion/wpctl.zsh
Normal file
49
src/tools/shell-completion/wpctl.zsh
Normal file
@@ -0,0 +1,49 @@
|
||||
#compdef wpctl
|
||||
|
||||
(( $+functions[_wpctl_pw_nodes] )) ||
|
||||
_wpctl_pw_nodes() {
|
||||
local -a pw_objects
|
||||
if (( $+commands[pw-dump] )) && (( $+commands[jq] )); then
|
||||
local -a pw_objects=(${(@f)"$(2>/dev/null {
|
||||
command pw-dump |
|
||||
command jq -r '.[] | select(
|
||||
.type == "PipeWire:Interface:Node"
|
||||
) |
|
||||
{id, type, name: (
|
||||
.info.name //
|
||||
(.info.props | (
|
||||
."application.name" //
|
||||
."node.name")
|
||||
) //
|
||||
.type)
|
||||
} |
|
||||
"\(.id):\(.name | gsub(":"; "\\:"))"'
|
||||
})"})
|
||||
fi
|
||||
_wpctl_describe_nodes() {_describe "node id" pw_objects "$@"}
|
||||
_alternative \
|
||||
'pw-defaults:defaults:(@DEFAULT_SINK@ @DEFAULT_SOURCE@)' \
|
||||
'pw-node-id:node id:_wpctl_describe_nodes'
|
||||
}
|
||||
|
||||
local -a node_id=(/$'[^\0]#\0'/ ':pw-node-id:node id:_wpctl_pw_nodes')
|
||||
local -a volume=(/$'[0-9]##(%|)([+-]|)\0'/ ':volume:volume:( )')
|
||||
local -a toggle=(/$'[^\0]#\0'/ ':(0 1 toggle)')
|
||||
local -a set_volume=( "$node_id[@]" "$volume[@]" )
|
||||
local -a set_mute=( "$node_id[@]" "$toggle[@]" )
|
||||
|
||||
_regex_words options 'wpctl options' \
|
||||
{-h,--help}':show help message and exit'
|
||||
local -a options=( "$reply[@]" )
|
||||
|
||||
_regex_words wpctl-commands 'wpctl commands' \
|
||||
'status:show wireplumber status' \
|
||||
'get-volume:get object volume:$node_id' \
|
||||
'set-default:set a default sink:$node_id' \
|
||||
'set-volume:set object volume:$set_volume' \
|
||||
'set-mute:set object mute:$set_mute' \
|
||||
'set-profile:set object profile:$node_id' \
|
||||
'clear-default:unset default sink:$node_id'
|
||||
local -a wpctlcmd=( /$'[^\0]#\0'/ "$options[@]" "#" "$reply[@]")
|
||||
_regex_arguments _wpctl "$wpctlcmd[@]"
|
||||
_wpctl "$@"
|
Reference in New Issue
Block a user