Merge branch 'pw-roles' into 'main'
Set media-roles and add example wireplumber config See merge request agx/feedbackd!170
This commit is contained in:
+6
-3
@@ -7,7 +7,8 @@
|
|||||||
{
|
{
|
||||||
"event-name" : "alarm-clock-elapsed",
|
"event-name" : "alarm-clock-elapsed",
|
||||||
"type" : "Sound",
|
"type" : "Sound",
|
||||||
"effect" : "alarm-clock-elapsed"
|
"effect" : "alarm-clock-elapsed",
|
||||||
|
"media-role" : "Alarm"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event-name" : "button-pressed",
|
"event-name" : "button-pressed",
|
||||||
@@ -42,7 +43,8 @@
|
|||||||
{
|
{
|
||||||
"event-name" : "message-new-cellbroadcast",
|
"event-name" : "message-new-cellbroadcast",
|
||||||
"type" : "Sound",
|
"type" : "Sound",
|
||||||
"effect" : "cellbroadcast"
|
"effect" : "cellbroadcast",
|
||||||
|
"media-role" : "Emergency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event-name" : "message-new-email",
|
"event-name" : "message-new-email",
|
||||||
@@ -62,7 +64,8 @@
|
|||||||
{
|
{
|
||||||
"event-name" : "phone-incoming-call",
|
"event-name" : "phone-incoming-call",
|
||||||
"type" : "Sound",
|
"type" : "Sound",
|
||||||
"effect" : "phone-incoming-call"
|
"effect" : "phone-incoming-call",
|
||||||
|
"media-role" : "Ringtone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event-name" : "phone-hangup",
|
"event-name" : "phone-hangup",
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Based on https://gitlab.freedesktop.org/pipewire/wireplumber/-/blob/master/src/config/wireplumber.conf.d.examples/media-role-nodes.conf
|
||||||
|
|
||||||
|
wireplumber.profiles = {
|
||||||
|
main = {
|
||||||
|
policy.linking.role-based.loopbacks = required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wireplumber.settings = {
|
||||||
|
# Default role
|
||||||
|
node.stream.default-media-role = "Multimedia"
|
||||||
|
linking.role-based.duck-level = 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
wireplumber.components.rules = [
|
||||||
|
{
|
||||||
|
matches = [
|
||||||
|
{
|
||||||
|
provides = "~loopback.sink.*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
actions = {
|
||||||
|
merge = {
|
||||||
|
arguments = {
|
||||||
|
capture.props = {
|
||||||
|
# Explicitly mark all these sinks as valid role-based policy
|
||||||
|
# targets, meaning that any links between streams and these sinks
|
||||||
|
# will be managed by the role-based policy
|
||||||
|
policy.role-based.target = true
|
||||||
|
|
||||||
|
audio.position = [ FL, FR ]
|
||||||
|
media.class = Audio/Sink
|
||||||
|
}
|
||||||
|
playback.props = {
|
||||||
|
# This must be set to ensure that the real audio sink is suspended
|
||||||
|
# when there is no active client stream linked
|
||||||
|
node.passive = true
|
||||||
|
# Set this to an unused role to make sure that loopbacks don't
|
||||||
|
# accidentally chain-link on to one another, especially when
|
||||||
|
# node.stream.default-media-role is configured in the settings
|
||||||
|
media.role = "Loopback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requires = [ support.export-core, pw.node-factory.adapter ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
wireplumber.components = [
|
||||||
|
{
|
||||||
|
type = virtual, provides = policy.linking.role-based.loopbacks
|
||||||
|
requires = [ loopback.sink.role.multimedia
|
||||||
|
loopback.sink.role.notification
|
||||||
|
loopback.sink.role.alarm
|
||||||
|
loopback.sink.role.ringtone
|
||||||
|
loopback.sink.role.alert ]
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
node.name = "loopback.sink.role.multimedia"
|
||||||
|
node.description = "Multimedia"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Music", "Movie", "Game", "Multimedia" ]
|
||||||
|
device.icon-name = "applications-multimedia"
|
||||||
|
policy.role-based.priority = 10
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "mix"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.multimedia
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# System events from e.g. notifications
|
||||||
|
node.name = "loopback.sink.role.notification"
|
||||||
|
node.description = "System Events and Notifications"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Notification" ]
|
||||||
|
device.icon-name = "preferences-system-notifications"
|
||||||
|
policy.role-based.priority = 20
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "duck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.notification
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Alarm clocks
|
||||||
|
node.name = "loopback.sink.role.alarm"
|
||||||
|
node.description = "Alarm Clocks"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Alarm" ]
|
||||||
|
device.icon-name = "alarm"
|
||||||
|
policy.role-based.priority = 30
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "duck"
|
||||||
|
policy.role-based.preferred-target = "Speaker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.alarm
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Phone call ring tones
|
||||||
|
node.name = "loopback.sink.role.ringtone"
|
||||||
|
node.description = "Ringtones"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Ringtone" ]
|
||||||
|
device.icon-name = "call-ring"
|
||||||
|
policy.role-based.priority = 40
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "cork"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.ringtone
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Cell Broadcasts, etc
|
||||||
|
node.name = "loopback.sink.role.alert"
|
||||||
|
node.description = "Alerts"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Alert", "Emergency" ]
|
||||||
|
device.icon-name = "network-cellular"
|
||||||
|
policy.role-based.priority = 100
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "cork"
|
||||||
|
policy.role-based.preferred-target = "Speaker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.alert
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -91,4 +91,7 @@ if get_option('daemon')
|
|||||||
install_dir: systemd_user_unit_dir,
|
install_dir: systemd_user_unit_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_option('media-roles')
|
||||||
|
install_data('media-role-nodes.conf', install_dir: wireplumberdir)
|
||||||
|
endif
|
||||||
endif # daemon
|
endif # daemon
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Based on https://gitlab.freedesktop.org/pipewire/wireplumber/-/blob/master/src/config/wireplumber.conf.d.examples/media-role-nodes.conf
|
||||||
|
|
||||||
|
wireplumber.profiles = {
|
||||||
|
main = {
|
||||||
|
policy.linking.role-based.loopbacks = required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wireplumber.settings = {
|
||||||
|
# Default role
|
||||||
|
node.stream.default-media-role = "Multimedia"
|
||||||
|
linking.role-based.duck-level = 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
wireplumber.components.rules = [
|
||||||
|
{
|
||||||
|
matches = [
|
||||||
|
{
|
||||||
|
provides = "~loopback.sink.*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
actions = {
|
||||||
|
merge = {
|
||||||
|
arguments = {
|
||||||
|
capture.props = {
|
||||||
|
# Explicitly mark all these sinks as valid role-based policy
|
||||||
|
# targets, meaning that any links between streams and these sinks
|
||||||
|
# will be managed by the role-based policy
|
||||||
|
policy.role-based.target = true
|
||||||
|
|
||||||
|
audio.position = [ FL, FR ]
|
||||||
|
media.class = Audio/Sink
|
||||||
|
}
|
||||||
|
playback.props = {
|
||||||
|
# This must be set to ensure that the real audio sink is suspended
|
||||||
|
# when there is no active client stream linked
|
||||||
|
node.passive = true
|
||||||
|
# Set this to an unused role to make sure that loopbacks don't
|
||||||
|
# accidentally chain-link on to one another, especially when
|
||||||
|
# node.stream.default-media-role is configured in the settings
|
||||||
|
media.role = "Loopback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requires = [ support.export-core, pw.node-factory.adapter ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
wireplumber.components = [
|
||||||
|
{
|
||||||
|
type = virtual, provides = policy.linking.role-based.loopbacks
|
||||||
|
requires = [ loopback.sink.role.multimedia
|
||||||
|
loopback.sink.role.notification
|
||||||
|
loopback.sink.role.alarm
|
||||||
|
loopback.sink.role.ringtone
|
||||||
|
loopback.sink.role.alert ]
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
node.name = "loopback.sink.role.multimedia"
|
||||||
|
node.description = "Multimedia"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Music", "Movie", "Game", "Multimedia" ]
|
||||||
|
device.icon-name = "applications-multimedia"
|
||||||
|
policy.role-based.priority = 10
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "mix"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.multimedia
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# System events from e.g. notifications
|
||||||
|
node.name = "loopback.sink.role.notification"
|
||||||
|
node.description = "System Events and Notifications"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Notification" ]
|
||||||
|
device.icon-name = "preferences-system-notifications"
|
||||||
|
policy.role-based.priority = 20
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "duck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.notification
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Alarm clocks
|
||||||
|
node.name = "loopback.sink.role.alarm"
|
||||||
|
node.description = "Alarm Clocks"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Alarm" ]
|
||||||
|
device.icon-name = "alarm"
|
||||||
|
policy.role-based.priority = 30
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "duck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.alarm
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Phone call ring tones
|
||||||
|
node.name = "loopback.sink.role.ringtone"
|
||||||
|
node.description = "Ringtones"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Ringtone" ]
|
||||||
|
device.icon-name = "call-ring"
|
||||||
|
policy.role-based.priority = 40
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "cork"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.ringtone
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = libpipewire-module-loopback, type = pw-module
|
||||||
|
arguments = {
|
||||||
|
# Cell Broadcasts, etc
|
||||||
|
node.name = "loopback.sink.role.alert"
|
||||||
|
node.description = "Alerts"
|
||||||
|
capture.props = {
|
||||||
|
device.intended-roles = [ "Alert", "Emergency" ]
|
||||||
|
device.icon-name = "network-cellular"
|
||||||
|
policy.role-based.priority = 100
|
||||||
|
policy.role-based.action.same-priority = "mix"
|
||||||
|
policy.role-based.action.lower-priority = "cork"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
provides = loopback.sink.role.alert
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -42,6 +42,7 @@ if udev.found() and get_option('daemon')
|
|||||||
else
|
else
|
||||||
udevdir = prefix / 'lib' / 'udev' / 'rules.d'
|
udevdir = prefix / 'lib' / 'udev' / 'rules.d'
|
||||||
endif
|
endif
|
||||||
|
wireplumberdir = datadir / 'wireplumber' / 'wireplumber.conf.d'
|
||||||
|
|
||||||
run_data = configuration_data()
|
run_data = configuration_data()
|
||||||
run_data.set('ABS_BUILDDIR', meson.current_build_dir())
|
run_data.set('ABS_BUILDDIR', meson.current_build_dir())
|
||||||
@@ -124,6 +125,7 @@ summary = [
|
|||||||
' Introspection: @0@'.format(introspection),
|
' Introspection: @0@'.format(introspection),
|
||||||
' Vapi: @0@'.format(get_option('vapi')),
|
' Vapi: @0@'.format(get_option('vapi')),
|
||||||
' Tests: @0@'.format(get_option('tests')),
|
' Tests: @0@'.format(get_option('tests')),
|
||||||
|
' Media Roles: @0@'.format(get_option('media-roles')),
|
||||||
'---------------',
|
'---------------',
|
||||||
'',
|
'',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ option('tests',
|
|||||||
option('systemd_user_unit_dir',
|
option('systemd_user_unit_dir',
|
||||||
type: 'string', value: '',
|
type: 'string', value: '',
|
||||||
description: 'Directory for systemd user units')
|
description: 'Directory for systemd user units')
|
||||||
|
option('media-roles',
|
||||||
|
type: 'boolean', value: false,
|
||||||
|
description: 'Use media roles')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ABS_BUILDDIR='@ABS_BUILDDIR@'
|
ABS_BUILDDIR='@ABS_BUILDDIR@'
|
||||||
@@ -14,5 +14,7 @@ if [ "${FBD_GDB}" = 1 ]; then
|
|||||||
WRAPPER="gdb --args"
|
WRAPPER="gdb --args"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
EXEC=${@:-${ABS_BUILDDIR}/src/feedbackd}
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
exec ${WRAPPER} "$@"
|
exec ${WRAPPER} $EXEC
|
||||||
|
|||||||
+9
-2
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2020 Purism SPC
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* 2025 Phosh.mobi e.V.
|
||||||
* SPDX-License-Identifier: GPL-3.0+
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
* Author: Guido Günther <agx@sigxcpu.org>
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
*
|
*
|
||||||
@@ -9,6 +10,8 @@
|
|||||||
|
|
||||||
#define G_LOG_DOMAIN "fbd-dev-sound"
|
#define G_LOG_DOMAIN "fbd-dev-sound"
|
||||||
|
|
||||||
|
#include "fbd-config.h"
|
||||||
|
|
||||||
#include "fbd-dev-sound.h"
|
#include "fbd-dev-sound.h"
|
||||||
#include "fbd-feedback-sound.h"
|
#include "fbd-feedback-sound.h"
|
||||||
|
|
||||||
@@ -199,11 +202,13 @@ on_sound_play_finished_callback (GSoundContext *ctx,
|
|||||||
|
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPlayedCallback callback)
|
fbd_dev_sound_play (FbdDevSound *self,
|
||||||
|
FbdFeedbackSound *feedback,
|
||||||
|
FbdDevSoundPlayedCallback callback)
|
||||||
{
|
{
|
||||||
FbdAsyncData *data;
|
FbdAsyncData *data;
|
||||||
const char *filename;
|
const char *filename;
|
||||||
const char *role;
|
const char *role = NULL;
|
||||||
|
|
||||||
g_return_val_if_fail (FBD_IS_DEV_SOUND (self), FALSE);
|
g_return_val_if_fail (FBD_IS_DEV_SOUND (self), FALSE);
|
||||||
g_return_val_if_fail (GSOUND_IS_CONTEXT (self->ctx), FALSE);
|
g_return_val_if_fail (GSOUND_IS_CONTEXT (self->ctx), FALSE);
|
||||||
@@ -213,7 +218,9 @@ fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPl
|
|||||||
if (!g_hash_table_insert (self->playbacks, feedback, data))
|
if (!g_hash_table_insert (self->playbacks, feedback, data))
|
||||||
g_warning ("Feedback %p already present", feedback);
|
g_warning ("Feedback %p already present", feedback);
|
||||||
|
|
||||||
|
#ifdef FBD_USE_MEDIA_ROLES
|
||||||
role = fbd_feedback_sound_get_media_role (feedback);
|
role = fbd_feedback_sound_get_media_role (feedback);
|
||||||
|
#endif
|
||||||
if (!role)
|
if (!role)
|
||||||
role = "event";
|
role = "event";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
# Feedback daemon
|
# Feedback daemon
|
||||||
if get_option('daemon')
|
if get_option('daemon')
|
||||||
|
|
||||||
|
config_h = configuration_data()
|
||||||
|
config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||||
|
config_h.set('FBD_USE_MEDIA_ROLES', get_option('media-roles'))
|
||||||
|
configure_file(output: 'fbd-config.h', configuration: config_h)
|
||||||
|
|
||||||
fbd_enum_headers = files('fbd-event.h', 'fbd-feedback-led.h', 'fbd-feedback-vibra.h')
|
fbd_enum_headers = files('fbd-event.h', 'fbd-feedback-led.h', 'fbd-feedback-vibra.h')
|
||||||
fbd_enum_sources = gnome.mkenums_simple('fbd-enums', sources: fbd_enum_headers)
|
fbd_enum_sources = gnome.mkenums_simple('fbd-enums', sources: fbd_enum_headers)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user