feat: sound balance

This commit is contained in:
Ashley Wulber 2025-03-25 15:41:31 -04:00 committed by Ashley Wulber
parent f153bf3797
commit d8edef49f4
6 changed files with 131 additions and 16 deletions

47
Cargo.lock generated
View file

@ -1771,19 +1771,20 @@ dependencies = [
[[package]]
name = "cosmic-settings-subscriptions"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions?branch=eap-peap#ad9c0d0d2df220b8c9336602be8458227e162b74"
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions?branch=sound#9c9b87dca2822060dbd776a4fb714902b6bab5e5"
dependencies = [
"bluez-zbus",
"cosmic-dbus-networkmanager",
"futures",
"iced_futures",
"itertools 0.13.0",
"itertools 0.14.0",
"libpulse-binding",
"log",
"os_pipe",
"pipewire",
"rustix 0.38.44",
"rustix 1.0.3",
"secure-string",
"thiserror 1.0.69",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tracing",
@ -4088,6 +4089,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
@ -4592,6 +4602,12 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
[[package]]
name = "linux-raw-sys"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "litemap"
version = "0.7.4"
@ -5484,6 +5500,16 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os_pipe"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "ouroboros"
version = "0.18.5"
@ -6428,6 +6454,19 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
dependencies = [
"bitflags 2.8.0",
"errno",
"libc",
"linux-raw-sys 0.9.3",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.19"

View file

@ -90,7 +90,7 @@ num-derive = "0.4"
[dependencies.cosmic-settings-subscriptions]
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
branch = "eap-peap"
branch = "sound"
#TODO: only select features as needed
features = ["network_manager", "pipewire", "pulse", "bluetooth"]
optional = true

View file

@ -395,7 +395,7 @@ fn zoom_shortcuts() -> (Vec<Binding>, Vec<Binding>) {
let Some(config) = shortcuts::context().ok() else {
return (Vec::new(), Vec::new());
};
let shortcuts = dbg!(shortcuts::shortcuts(&config));
let shortcuts = shortcuts::shortcuts(&config);
let zoom_in = shortcuts
.iter()

View file

@ -8,19 +8,18 @@ use std::{
use anyhow::Context;
use cosmic::{
Apply, Element, Task,
iced::{Alignment, Length},
iced_core::text::Wrapping,
iced_widget::focus_next,
widget::{self, column, icon},
Apply, Element, Task,
};
use cosmic_settings_page::{self as page, section, Section};
use cosmic_settings_page::{self as page, Section, section};
use cosmic_settings_subscriptions::network_manager::{
self,
self, NetworkManagerState,
available_wifi::{AccessPoint, NetworkType},
current_networks::ActiveConnectionInfo,
hw_address::HwAddress,
NetworkManagerState,
};
use futures::StreamExt;
use secure_string::SecureString;
@ -526,7 +525,7 @@ impl Page {
return cosmic::task::batch(vec![
self.connect(conn.clone()),
connection_settings(conn),
])
]);
}
}

View file

@ -4,12 +4,12 @@
use std::{collections::BTreeMap, time::Duration};
use cosmic::{
iced::{window, Alignment, Length},
Element, Task,
iced::{Alignment, Length, window},
surface,
widget::{self, settings},
Element, Task,
};
use cosmic_settings_page::{self as page, section, Section};
use cosmic_settings_page::{self as page, Section, section};
use cosmic_settings_subscriptions::{pipewire, pulse};
use futures::StreamExt;
use indexmap::IndexMap;
@ -33,8 +33,12 @@ pub enum Message {
SinkProfileSelect(DeviceId),
/// Request to change the default output volume.
SinkVolumeChanged(u32),
/// Request to change the default output balance.
SinkBalanceChanged(u32),
/// Change the output volume.
SinkVolumeApply(NodeId),
/// Change the output balance.
SinkBalanceApply,
/// Toggle the mute status of the output.
SinkMuteToggle,
/// Change the default input output.
@ -89,6 +93,10 @@ pub struct Page {
sink_mute: bool,
sink_volume_debounce: bool,
sink_balance: Option<f32>,
sink_balance_text: Option<String>,
sink_balance_debounce: bool,
sink_channels: Option<pulse::PulseChannels>,
source_volume: u32,
source_volume_text: String,
source_mute: bool,
@ -346,6 +354,26 @@ impl Page {
return command;
}
}
Message::SinkBalanceChanged(balance) => {
self.sink_balance = Some((balance as f32 - 100.) / 100.);
self.sink_balance_text = Some(format!("{balance:.2}"));
if self.sink_balance_debounce {
return Task::none();
}
let mut command = None;
if let Some(&node_id) = self.sink_ids.get(self.active_sink.unwrap_or(0)) {
command = Some(cosmic::task::future(async move {
tokio::time::sleep(Duration::from_millis(64)).await;
crate::pages::Message::Sound(Message::SinkBalanceApply)
}));
}
if let Some(command) = command {
self.sink_balance_debounce = true;
return command;
}
}
Message::Pulse(pulse::Event::SinkVolume(volume)) => {
if self.sink_volume_debounce {
return Task::none();
@ -381,6 +409,13 @@ impl Page {
self.active_profiles
.insert(device_id, card.active_profile.map(|p| p.name));
}
Message::Pulse(pulse::Event::Balance(balance)) => {
self.sink_balance = balance;
self.sink_balance_text = balance.map(|b| format!("{b:.2}"));
}
Message::Pulse(pulse::Event::Channels(channels)) => {
self.sink_channels = Some(channels);
}
Message::Pipewire(pipewire::DeviceEvent::Add(device)) => {
let device_id = match device.variant {
pipewire::DeviceVariant::Alsa { alsa_card, .. } => DeviceId::Alsa(alsa_card),
@ -494,9 +529,19 @@ impl Page {
}
}
}
Message::SinkVolumeApply(node_id) => {
Message::SinkBalanceApply => {
self.sink_balance_debounce = false;
if let Some((balance, channels)) =
self.sink_balance.zip(self.sink_channels.as_mut())
{
channels.set_balance(balance);
}
}
Message::SinkVolumeApply(_) => {
self.sink_volume_debounce = false;
wpctl_set_volume(node_id, self.sink_volume);
if let Some(channels) = self.sink_channels.as_mut() {
channels.set_volume(self.sink_volume as f32 / 100.);
}
}
Message::SourceVolumeApply(node_id) => {
self.source_volume_debounce = false;
@ -656,6 +701,9 @@ fn output() -> Section<crate::pages::Message> {
let device = descriptions.insert(fl!("sound-output", "device"));
let _level = descriptions.insert(fl!("sound-output", "level"));
let profile = descriptions.insert(fl!("profile"));
let balance = descriptions.insert(fl!("sound-output", "balance"));
let left = descriptions.insert(fl!("sound-output", "left"));
let right = descriptions.insert(fl!("sound-output", "right"));
// let balance = descriptions.insert(fl!("sound-output", "balance"));
Section::default()
@ -712,6 +760,33 @@ fn output() -> Section<crate::pages::Message> {
controls = controls.add(settings::item(&*section.descriptions[profile], dropdown));
}
if let Some(sink_balance) = page.sink_balance {
controls = controls.add(settings::item(
&*section.descriptions[balance],
widget::row::with_capacity(4)
.align_y(Alignment::Center)
.push(
widget::text::body(&*section.descriptions[left])
.width(Length::Fixed(22.0))
.align_x(Alignment::Center),
)
.push(widget::horizontal_space().width(8))
.push(
widget::slider(
0..=200,
((sink_balance + 1.).max(0.) * 100.).round() as u32,
Message::SinkBalanceChanged,
)
.breakpoints(&[100]),
)
.push(widget::horizontal_space().width(8))
.push(
widget::text::body(&*section.descriptions[right])
.width(Length::Fixed(22.0))
.align_x(Alignment::Center),
),
));
}
Element::from(controls).map(crate::pages::Message::Sound)
})

View file

@ -449,6 +449,8 @@ sound-output = Output
.level = Output level
.config = Configuration
.balance = Balance
.left = Left
.right = Right
sound-input = Input
.volume = Input volume