Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ impl App {
self.tempo
.update(modules::tempo::Message::ConfigReloaded(config.tempo));
self.settings
.update(modules::settings::Message::ConfigReloaded(config.settings));
.update(modules::settings::Message::ConfigReloaded(Box::new(
config.settings,
)));
self.media_player
.update(modules::media_player::Message::ConfigReloaded(
config.media_player,
Expand Down
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,10 @@ pub struct SettingsModuleConfig {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub audio_sources_more_cmd: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub audio_sink_post_switch_cmd: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub audio_source_post_switch_cmd: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub wifi_more_cmd: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub vpn_more_cmd: Option<String>,
Expand Down Expand Up @@ -669,6 +673,8 @@ impl Default for SettingsModuleConfig {
brightness_indicator_format: SettingsFormat::Icon,
audio_sinks_more_cmd: Default::default(),
audio_sources_more_cmd: Default::default(),
audio_sink_post_switch_cmd: Default::default(),
audio_source_post_switch_cmd: Default::default(),
wifi_more_cmd: Default::default(),
vpn_more_cmd: Default::default(),
bluetooth_more_cmd: Default::default(),
Expand Down
49 changes: 46 additions & 3 deletions src/modules/settings/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
config::SettingsFormat,
services::{
ReadOnlyService, Service, ServiceEvent,
audio::{AudioCommand, AudioService, ChannelVolumesExt, DevicePortType, Port},
audio::{AudioCommand, AudioEvent, AudioService, ChannelVolumesExt, DevicePortType, Port},
},
t,
theme::use_theme,
Expand Down Expand Up @@ -57,16 +57,21 @@ pub enum Action {
pub struct AudioSettingsConfig {
pub sinks_more_cmd: Option<String>,
pub sources_more_cmd: Option<String>,
pub sink_post_switch_cmd: Option<String>,
pub source_post_switch_cmd: Option<String>,
pub volume_step: u8,
pub max_volume: u8,
pub indicator_format: SettingsFormat,
pub microphone_indicator_format: SettingsFormat,
}

impl AudioSettingsConfig {
#[allow(clippy::too_many_arguments)]
pub fn new(
sinks_more_cmd: Option<String>,
sources_more_cmd: Option<String>,
sink_post_switch_cmd: Option<String>,
source_post_switch_cmd: Option<String>,
volume_step: u8,
max_volume: u8,
indicator_format: SettingsFormat,
Expand All @@ -75,6 +80,8 @@ impl AudioSettingsConfig {
Self {
sinks_more_cmd,
sources_more_cmd,
sink_post_switch_cmd,
source_post_switch_cmd,
volume_step,
max_volume,
indicator_format,
Expand All @@ -86,6 +93,8 @@ impl AudioSettingsConfig {
pub struct AudioSettings {
config: AudioSettingsConfig,
service: Option<AudioService>,
pending_sink_switch: bool,
pending_source_switch: bool,
}

pub struct SubmenuEntry {
Expand All @@ -106,6 +115,8 @@ impl AudioSettings {
Self {
config,
service: None,
pending_sink_switch: false,
pending_source_switch: false,
}
}

Expand Down Expand Up @@ -219,8 +230,32 @@ impl AudioSettings {
}
ServiceEvent::Update(data) => {
if let Some(service) = self.service.as_mut() {
let post_switch = match &data {
AudioEvent::ServerInfo(info) => {
let sink_changed = self.pending_sink_switch
&& info.default_sink != service.server_info.default_sink;
let source_changed = self.pending_source_switch
&& info.default_source != service.server_info.default_source;
self.pending_sink_switch = false;
self.pending_source_switch = false;
(sink_changed, source_changed)
}
_ => (false, false),
};

service.update(data);

if post_switch.0
&& let Some(cmd) = &self.config.sink_post_switch_cmd
{
crate::utils::launcher::execute_command(cmd);
}
if post_switch.1
&& let Some(cmd) = &self.config.source_post_switch_cmd
{
crate::utils::launcher::execute_command(cmd);
}

if !service.has_multiple_sinks() {
return Action::CloseSubMenu;
}
Expand Down Expand Up @@ -255,7 +290,11 @@ impl AudioSettings {
}
Message::DefaultSinkChanged(name, port) => {
if let Some(service) = self.service.as_mut() {
let _ = service.command(AudioCommand::DefaultSink(name, port));
let is_noop = name == service.server_info.default_sink && port.is_none();
if !is_noop {
self.pending_sink_switch = true;
let _ = service.command(AudioCommand::DefaultSink(name, port));
}
}
Action::None
}
Expand All @@ -281,7 +320,11 @@ impl AudioSettings {
}
Message::DefaultSourceChanged(name, port) => {
if let Some(service) = self.service.as_mut() {
let _ = service.command(AudioCommand::DefaultSource(name, port));
let is_noop = name == service.server_info.default_source && port.is_none();
if !is_noop {
self.pending_source_switch = true;
let _ = service.command(AudioCommand::DefaultSource(name, port));
}
}
Action::None
}
Expand Down
6 changes: 5 additions & 1 deletion src/modules/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub enum Message {
CustomButton(String),
CustomButtonsStatus(Vec<(String, Option<bool>)>),
MenuOpened,
ConfigReloaded(SettingsModuleConfig),
ConfigReloaded(Box<SettingsModuleConfig>),
AudioTooltipHover(ButtonUIRef, SurfaceId),
BluetoothTooltipHover(ButtonUIRef, SurfaceId),
WifiTooltipHover(ButtonUIRef, SurfaceId),
Expand Down Expand Up @@ -218,6 +218,8 @@ impl Settings {
audio: AudioSettings::new(AudioSettingsConfig::new(
config.audio_sinks_more_cmd,
config.audio_sources_more_cmd,
config.audio_sink_post_switch_cmd,
config.audio_source_post_switch_cmd,
config.volume_step,
config.max_volume,
config.audio_indicator_format,
Expand Down Expand Up @@ -519,6 +521,8 @@ impl Settings {
.update(audio::Message::ConfigReloaded(AudioSettingsConfig::new(
config.audio_sinks_more_cmd,
config.audio_sources_more_cmd,
config.audio_sink_post_switch_cmd,
config.audio_source_post_switch_cmd,
config.volume_step,
config.max_volume,
config.audio_indicator_format,
Expand Down
2 changes: 2 additions & 0 deletions website/docs/configuration/full_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ lock_cmd = "playerctl --all-players pause; nixGL hyprlock &"
# logout_cmd = "loginctl kill-user $(whoami)" # (default)
audio_sinks_more_cmd = "pavucontrol -t 3"
audio_sources_more_cmd = "pavucontrol -t 4"
# audio_sink_post_switch_cmd = "systemctl --user restart wireplumber" # (default: None)
# audio_source_post_switch_cmd = "..." # (default: None)
wifi_more_cmd = "nm-connection-editor"
vpn_more_cmd = "nm-connection-editor"
bluetooth_more_cmd = "blueberry"
Expand Down
13 changes: 13 additions & 0 deletions website/docs/configuration/modules/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ options you can set commands to open the audio settings
for sinks and sources, if not set the related buttons will not appear.
When configured, right-clicking the speaker or microphone indicators (or their quick settings buttons) launches the respective command immediately.

With the `audio_sink_post_switch_cmd` and `audio_source_post_switch_cmd` options
you can set commands that are automatically executed after switching the audio output or input device.
This is useful for working around PipeWire or WirePlumber issues where audio doesn't
properly route to the new device without a service restart.

The command is executed asynchronously (fire-and-forget) via `bash -c` after every sink or source switch.

```toml
[settings]
audio_sink_post_switch_cmd = "systemctl --user restart wireplumber"
```

With the `wifi_more_cmd`, `vpn_more_cmd` and `bluetooth_more_cmd` options
you can set commands to open the network, VPN and bluetooth settings.
Right-clicking the Wi-Fi, VPN, Bluetooth or airplane-mode quick settings buttons (and the Wi-Fi indicator in the bar) triggers these commands directly when they are set.
Expand Down Expand Up @@ -404,6 +416,7 @@ We also disable the airplane mode button and the idle inhibitor button.
lock_cmd = "hyprlock &"
audio_sinks_more_cmd = "pavucontrol -t 3"
audio_sources_more_cmd = "pavucontrol -t 4"
audio_sink_post_switch_cmd = "systemctl --user restart wireplumber"
wifi_more_cmd = "nm-connection-editor"
vpn_more_cmd = "nm-connection-editor"
bluetooth_more_cmd = "blueman-manager"
Expand Down