diff --git a/Cargo.lock b/Cargo.lock index 10b7f76..5867d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index a4b8473..0b5a5ad 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -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 diff --git a/cosmic-settings/src/pages/accessibility/magnifier.rs b/cosmic-settings/src/pages/accessibility/magnifier.rs index b92f192..38a36ae 100644 --- a/cosmic-settings/src/pages/accessibility/magnifier.rs +++ b/cosmic-settings/src/pages/accessibility/magnifier.rs @@ -395,7 +395,7 @@ fn zoom_shortcuts() -> (Vec, Vec) { 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() diff --git a/cosmic-settings/src/pages/networking/wifi.rs b/cosmic-settings/src/pages/networking/wifi.rs index 8e53fb8..66f5af7 100644 --- a/cosmic-settings/src/pages/networking/wifi.rs +++ b/cosmic-settings/src/pages/networking/wifi.rs @@ -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), - ]) + ]); } } diff --git a/cosmic-settings/src/pages/sound.rs b/cosmic-settings/src/pages/sound.rs index a3dbcd4..3e4546e 100644 --- a/cosmic-settings/src/pages/sound.rs +++ b/cosmic-settings/src/pages/sound.rs @@ -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, + sink_balance_text: Option, + sink_balance_debounce: bool, + sink_channels: Option, 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 { 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 { 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) }) diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index b306ddd..04994fb 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -449,6 +449,8 @@ sound-output = Output .level = Output level .config = Configuration .balance = Balance + .left = Left + .right = Right sound-input = Input .volume = Input volume