feat: sound balance
This commit is contained in:
parent
f153bf3797
commit
d8edef49f4
6 changed files with 131 additions and 16 deletions
47
Cargo.lock
generated
47
Cargo.lock
generated
|
|
@ -1771,19 +1771,20 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-settings-subscriptions"
|
name = "cosmic-settings-subscriptions"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"bluez-zbus",
|
"bluez-zbus",
|
||||||
"cosmic-dbus-networkmanager",
|
"cosmic-dbus-networkmanager",
|
||||||
"futures",
|
"futures",
|
||||||
"iced_futures",
|
"iced_futures",
|
||||||
"itertools 0.13.0",
|
"itertools 0.14.0",
|
||||||
"libpulse-binding",
|
"libpulse-binding",
|
||||||
"log",
|
"log",
|
||||||
|
"os_pipe",
|
||||||
"pipewire",
|
"pipewire",
|
||||||
"rustix 0.38.44",
|
"rustix 1.0.3",
|
||||||
"secure-string",
|
"secure-string",
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -4088,6 +4089,15 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
|
@ -4592,6 +4602,12 @@ version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
|
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
|
@ -5484,6 +5500,16 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"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]]
|
[[package]]
|
||||||
name = "ouroboros"
|
name = "ouroboros"
|
||||||
version = "0.18.5"
|
version = "0.18.5"
|
||||||
|
|
@ -6428,6 +6454,19 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ num-derive = "0.4"
|
||||||
|
|
||||||
[dependencies.cosmic-settings-subscriptions]
|
[dependencies.cosmic-settings-subscriptions]
|
||||||
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
||||||
branch = "eap-peap"
|
branch = "sound"
|
||||||
#TODO: only select features as needed
|
#TODO: only select features as needed
|
||||||
features = ["network_manager", "pipewire", "pulse", "bluetooth"]
|
features = ["network_manager", "pipewire", "pulse", "bluetooth"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ fn zoom_shortcuts() -> (Vec<Binding>, Vec<Binding>) {
|
||||||
let Some(config) = shortcuts::context().ok() else {
|
let Some(config) = shortcuts::context().ok() else {
|
||||||
return (Vec::new(), Vec::new());
|
return (Vec::new(), Vec::new());
|
||||||
};
|
};
|
||||||
let shortcuts = dbg!(shortcuts::shortcuts(&config));
|
let shortcuts = shortcuts::shortcuts(&config);
|
||||||
|
|
||||||
let zoom_in = shortcuts
|
let zoom_in = shortcuts
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,18 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
|
Apply, Element, Task,
|
||||||
iced::{Alignment, Length},
|
iced::{Alignment, Length},
|
||||||
iced_core::text::Wrapping,
|
iced_core::text::Wrapping,
|
||||||
iced_widget::focus_next,
|
iced_widget::focus_next,
|
||||||
widget::{self, column, icon},
|
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::{
|
use cosmic_settings_subscriptions::network_manager::{
|
||||||
self,
|
self, NetworkManagerState,
|
||||||
available_wifi::{AccessPoint, NetworkType},
|
available_wifi::{AccessPoint, NetworkType},
|
||||||
current_networks::ActiveConnectionInfo,
|
current_networks::ActiveConnectionInfo,
|
||||||
hw_address::HwAddress,
|
hw_address::HwAddress,
|
||||||
NetworkManagerState,
|
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use secure_string::SecureString;
|
use secure_string::SecureString;
|
||||||
|
|
@ -526,7 +525,7 @@ impl Page {
|
||||||
return cosmic::task::batch(vec![
|
return cosmic::task::batch(vec![
|
||||||
self.connect(conn.clone()),
|
self.connect(conn.clone()),
|
||||||
connection_settings(conn),
|
connection_settings(conn),
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
use std::{collections::BTreeMap, time::Duration};
|
use std::{collections::BTreeMap, time::Duration};
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{window, Alignment, Length},
|
Element, Task,
|
||||||
|
iced::{Alignment, Length, window},
|
||||||
surface,
|
surface,
|
||||||
widget::{self, settings},
|
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 cosmic_settings_subscriptions::{pipewire, pulse};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
@ -33,8 +33,12 @@ pub enum Message {
|
||||||
SinkProfileSelect(DeviceId),
|
SinkProfileSelect(DeviceId),
|
||||||
/// Request to change the default output volume.
|
/// Request to change the default output volume.
|
||||||
SinkVolumeChanged(u32),
|
SinkVolumeChanged(u32),
|
||||||
|
/// Request to change the default output balance.
|
||||||
|
SinkBalanceChanged(u32),
|
||||||
/// Change the output volume.
|
/// Change the output volume.
|
||||||
SinkVolumeApply(NodeId),
|
SinkVolumeApply(NodeId),
|
||||||
|
/// Change the output balance.
|
||||||
|
SinkBalanceApply,
|
||||||
/// Toggle the mute status of the output.
|
/// Toggle the mute status of the output.
|
||||||
SinkMuteToggle,
|
SinkMuteToggle,
|
||||||
/// Change the default input output.
|
/// Change the default input output.
|
||||||
|
|
@ -89,6 +93,10 @@ pub struct Page {
|
||||||
sink_mute: bool,
|
sink_mute: bool,
|
||||||
sink_volume_debounce: 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: u32,
|
||||||
source_volume_text: String,
|
source_volume_text: String,
|
||||||
source_mute: bool,
|
source_mute: bool,
|
||||||
|
|
@ -346,6 +354,26 @@ impl Page {
|
||||||
return command;
|
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)) => {
|
Message::Pulse(pulse::Event::SinkVolume(volume)) => {
|
||||||
if self.sink_volume_debounce {
|
if self.sink_volume_debounce {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -381,6 +409,13 @@ impl Page {
|
||||||
self.active_profiles
|
self.active_profiles
|
||||||
.insert(device_id, card.active_profile.map(|p| p.name));
|
.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)) => {
|
Message::Pipewire(pipewire::DeviceEvent::Add(device)) => {
|
||||||
let device_id = match device.variant {
|
let device_id = match device.variant {
|
||||||
pipewire::DeviceVariant::Alsa { alsa_card, .. } => DeviceId::Alsa(alsa_card),
|
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;
|
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) => {
|
Message::SourceVolumeApply(node_id) => {
|
||||||
self.source_volume_debounce = false;
|
self.source_volume_debounce = false;
|
||||||
|
|
@ -656,6 +701,9 @@ fn output() -> Section<crate::pages::Message> {
|
||||||
let device = descriptions.insert(fl!("sound-output", "device"));
|
let device = descriptions.insert(fl!("sound-output", "device"));
|
||||||
let _level = descriptions.insert(fl!("sound-output", "level"));
|
let _level = descriptions.insert(fl!("sound-output", "level"));
|
||||||
let profile = descriptions.insert(fl!("profile"));
|
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"));
|
// let balance = descriptions.insert(fl!("sound-output", "balance"));
|
||||||
|
|
||||||
Section::default()
|
Section::default()
|
||||||
|
|
@ -712,6 +760,33 @@ fn output() -> Section<crate::pages::Message> {
|
||||||
|
|
||||||
controls = controls.add(settings::item(&*section.descriptions[profile], dropdown));
|
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)
|
Element::from(controls).map(crate::pages::Message::Sound)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -449,6 +449,8 @@ sound-output = Output
|
||||||
.level = Output level
|
.level = Output level
|
||||||
.config = Configuration
|
.config = Configuration
|
||||||
.balance = Balance
|
.balance = Balance
|
||||||
|
.left = Left
|
||||||
|
.right = Right
|
||||||
|
|
||||||
sound-input = Input
|
sound-input = Input
|
||||||
.volume = Input volume
|
.volume = Input volume
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue