improv(sound): reduce codegen, use subscription, and fix threads not exiting on page close
This commit is contained in:
parent
d41cdc5dc2
commit
6a29294e90
6 changed files with 141 additions and 99 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1789,7 +1789,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-settings-subscriptions"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions#da6ba802b1117f739fb3170aeb654c46fd2f08c3"
|
||||
source = "git+https://github.com/pop-os/cosmic-settings-subscriptions#d9e9639062df73623d04396d7b4473511d5812a4"
|
||||
dependencies = [
|
||||
"bluez-zbus",
|
||||
"cosmic-dbus-a11y",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ default-members = ["cosmic-settings"]
|
|||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.80.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
cosmic-randr = { git = "https://github.com/pop-os/cosmic-randr" }
|
||||
|
|
@ -32,6 +32,9 @@ git = "https://github.com/pop-os/cosmic-panel"
|
|||
[workspace.dependencies.cosmic-randr-shell]
|
||||
git = "https://github.com/pop-os/cosmic-randr"
|
||||
|
||||
[workspace.dependencies.cosmic-settings-subscriptions]
|
||||
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
||||
|
||||
[workspace.dependencies.sctk]
|
||||
git = "https://github.com/smithay/client-toolkit/"
|
||||
package = "smithay-client-toolkit"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ cosmic-randr-shell.workspace = true
|
|||
cosmic-randr = { workspace = true, optional = true }
|
||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||
cosmic-settings-page = { path = "../page" }
|
||||
cosmic-settings-subscriptions = { workspace = true, optional = true }
|
||||
cosmic-settings-system = { path = "../pages/system", optional = true }
|
||||
cosmic-settings-wallpaper = { path = "../pages/wallpapers" }
|
||||
cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||
|
|
@ -90,10 +91,6 @@ num-traits = "0.2"
|
|||
num-derive = "0.4"
|
||||
pwhash = "1"
|
||||
|
||||
[dependencies.cosmic-settings-subscriptions]
|
||||
git = "https://github.com/pop-os/cosmic-settings-subscriptions"
|
||||
optional = true
|
||||
|
||||
[dependencies.icu]
|
||||
version = "1.5.0"
|
||||
features = ["experimental", "compiled_data", "icu_datetime_experimental"]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||
|
||||
use cosmic::{
|
||||
Element, Task,
|
||||
Apply, Element, Task,
|
||||
iced::{Alignment, Length, window},
|
||||
surface,
|
||||
widget::{self, settings},
|
||||
|
|
@ -53,10 +53,35 @@ pub enum Message {
|
|||
SourceVolumeApply(NodeId),
|
||||
/// Toggle the mute status of the input output.
|
||||
SourceMuteToggle,
|
||||
/// On init of the subscription, channels for closing background threads are given to the app.
|
||||
SubHandle(Arc<SubscriptionHandle>),
|
||||
/// Surface Action
|
||||
Surface(surface::Action),
|
||||
}
|
||||
|
||||
pub struct SubscriptionHandle {
|
||||
cancel_tx: futures::channel::oneshot::Sender<()>,
|
||||
pipewire: pipewire::Sender<()>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SubscriptionHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("SubscriptionHandle")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for crate::pages::Message {
|
||||
fn from(message: Message) -> Self {
|
||||
crate::pages::Message::Sound(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for crate::Message {
|
||||
fn from(message: Message) -> Self {
|
||||
crate::Message::PageMessage(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Card {
|
||||
devices: IndexMap<NodeId, Device>,
|
||||
|
|
@ -79,8 +104,7 @@ pub enum DeviceId {
|
|||
#[derive(Default)]
|
||||
pub struct Page {
|
||||
entity: page::Entity,
|
||||
pipewire_thread: Option<(tokio::sync::oneshot::Sender<()>, pipewire::Sender<()>)>,
|
||||
pulse_thread: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
subscription_handle: Option<SubscriptionHandle>,
|
||||
sink_channels: Option<pulse::PulseChannels>,
|
||||
|
||||
devices: BTreeMap<DeviceId, Card>,
|
||||
|
|
@ -146,10 +170,6 @@ pub struct Page {
|
|||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn set_id(&mut self, entity: page::Entity) {
|
||||
self.entity = entity;
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
|
|
@ -163,74 +183,80 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.description(fl!("sound", "desc"))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
let mut tasks = Vec::with_capacity(2);
|
||||
if self.pulse_thread.is_none() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
fn subscription(
|
||||
&self,
|
||||
_core: &cosmic::Core,
|
||||
) -> cosmic::iced::Subscription<crate::pages::Message> {
|
||||
cosmic::iced::Subscription::run(|| {
|
||||
async_fn_stream::fn_stream(|emitter| async move {
|
||||
let (cancel_tx, mut cancel_rx) = futures::channel::oneshot::channel::<()>();
|
||||
|
||||
// Listen to events from the pulse thread until the tx channel is closed.
|
||||
_ = std::thread::spawn(move || {
|
||||
pulse::thread(tx);
|
||||
});
|
||||
let (tx, mut pulse_rx) = futures::channel::mpsc::channel(1);
|
||||
let _pulse_handle = std::thread::spawn(move || {
|
||||
pulse::thread(tx);
|
||||
});
|
||||
|
||||
// Forward events from the pulse thread to the application until
|
||||
// the application requests to stop listening to the pulse thread.
|
||||
tasks.push(Task::stream(async_fn_stream::fn_stream(
|
||||
|emitter| async move {
|
||||
let forwarder = std::pin::pin!(async move {
|
||||
while let Some(event) = rx.next().await {
|
||||
let event = crate::pages::Message::Sound(Message::Pulse(event));
|
||||
emitter.emit(event).await;
|
||||
let (tx, mut pw_rx) = futures::channel::mpsc::channel(1);
|
||||
let (_pipewire_handle, pipewire_terminate) = pipewire::thread(tx);
|
||||
|
||||
emitter
|
||||
.emit(
|
||||
Message::SubHandle(Arc::new(SubscriptionHandle {
|
||||
cancel_tx,
|
||||
pipewire: pipewire_terminate,
|
||||
}))
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
loop {
|
||||
futures::select! {
|
||||
event = pulse_rx.next() => {
|
||||
let Some(event) = event else {
|
||||
break;
|
||||
};
|
||||
|
||||
emitter
|
||||
.emit(crate::pages::Message::from(Message::Pulse(event)))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
futures::future::select(std::pin::pin!(cancel_rx), forwarder).await;
|
||||
},
|
||||
)));
|
||||
event = pw_rx.next() => {
|
||||
let Some(event) = event else {
|
||||
break;
|
||||
};
|
||||
|
||||
self.pulse_thread = Some(cancel_tx);
|
||||
}
|
||||
|
||||
if self.pipewire_thread.is_none() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
|
||||
// Listen to events from the pipewire thread until the tx channel is closed.
|
||||
let (_handle, terminate) = pipewire::thread(tx);
|
||||
|
||||
// Forward events from the pipewire thread to the application until
|
||||
// the application requests to stop listening to the pulse thread.
|
||||
tasks.push(Task::stream(async_fn_stream::fn_stream(
|
||||
|emitter| async move {
|
||||
let forwarder = std::pin::pin!(async move {
|
||||
while let Some(event) = rx.next().await {
|
||||
let event = crate::pages::Message::Sound(Message::Pipewire(event));
|
||||
emitter.emit(event).await;
|
||||
emitter
|
||||
.emit(crate::pages::Message::from(Message::Pipewire(event)))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
futures::future::select(std::pin::pin!(cancel_rx), forwarder).await;
|
||||
},
|
||||
)));
|
||||
_ = cancel_rx => break,
|
||||
}
|
||||
}
|
||||
|
||||
self.pipewire_thread = Some((cancel_tx, terminate));
|
||||
}
|
||||
drop(pulse_rx);
|
||||
drop(pw_rx);
|
||||
|
||||
cosmic::task::batch(tasks)
|
||||
futures::future::pending::<crate::pages::Message>().await;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Task<crate::pages::Message> {
|
||||
if let Some(cancellation) = self.pulse_thread.take() {
|
||||
_ = cancellation.send(());
|
||||
if let Some(handle) = self.subscription_handle.take() {
|
||||
_ = handle.cancel_tx.send(());
|
||||
_ = handle.pipewire.send(());
|
||||
}
|
||||
|
||||
if let Some((cancellation, terminate)) = self.pipewire_thread.take() {
|
||||
_ = cancellation.send(());
|
||||
_ = terminate.send(());
|
||||
if let Some(channel) = self.sink_channels.take() {
|
||||
channel.quit();
|
||||
}
|
||||
|
||||
*self = Page::default();
|
||||
*self = Page {
|
||||
entity: self.entity,
|
||||
..Page::default()
|
||||
};
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -338,9 +364,9 @@ impl Page {
|
|||
|
||||
let mut command = None;
|
||||
if let Some(&node_id) = self.source_pw_ids.get(self.active_source.unwrap_or(0)) {
|
||||
command = Some(cosmic::task::future(async move {
|
||||
command = Some(cosmic::Task::future(async move {
|
||||
tokio::time::sleep(Duration::from_millis(64)).await;
|
||||
crate::pages::Message::Sound(Message::SourceVolumeApply(node_id))
|
||||
Message::SourceVolumeApply(node_id).into()
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -366,9 +392,9 @@ impl Page {
|
|||
|
||||
let mut command = None;
|
||||
if let Some(&node_id) = self.sink_pw_ids.get(self.active_sink.unwrap_or(0)) {
|
||||
command = Some(cosmic::task::future(async move {
|
||||
command = Some(cosmic::Task::future(async move {
|
||||
tokio::time::sleep(Duration::from_millis(64)).await;
|
||||
crate::pages::Message::Sound(Message::SinkVolumeApply(node_id))
|
||||
Message::SinkVolumeApply(node_id).into()
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -390,9 +416,9 @@ impl Page {
|
|||
.get(self.active_sink.unwrap_or(0))
|
||||
.is_none()
|
||||
{
|
||||
command = Some(cosmic::task::future(async move {
|
||||
command = Some(cosmic::Task::future(async move {
|
||||
tokio::time::sleep(Duration::from_millis(64)).await;
|
||||
crate::pages::Message::Sound(Message::SinkBalanceApply)
|
||||
Message::SinkBalanceApply.into()
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -597,12 +623,10 @@ impl Page {
|
|||
.insert(device_id.clone(), Some(profile.clone()));
|
||||
|
||||
self.changing_sink_profile = true;
|
||||
return cosmic::task::future(async move {
|
||||
return cosmic::Task::future(async move {
|
||||
pactl_set_card_profile(name, profile).await;
|
||||
Message::SinkProfileSelect(device_id)
|
||||
})
|
||||
.map(crate::pages::Message::Sound)
|
||||
.map(crate::app::Message::PageMessage);
|
||||
Message::SinkProfileSelect(device_id).into()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -627,12 +651,10 @@ impl Page {
|
|||
.insert(device_id.clone(), Some(profile.clone()));
|
||||
|
||||
self.changing_source_profile = true;
|
||||
return cosmic::task::future(async move {
|
||||
return cosmic::Task::future(async move {
|
||||
pactl_set_card_profile(name, profile).await;
|
||||
Message::SourceProfileSelect(device_id)
|
||||
})
|
||||
.map(crate::pages::Message::Sound)
|
||||
.map(crate::app::Message::PageMessage);
|
||||
Message::SourceProfileSelect(device_id).into()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -651,6 +673,12 @@ impl Page {
|
|||
Message::Surface(a) => {
|
||||
return cosmic::task::message(crate::app::Message::Surface(a));
|
||||
}
|
||||
|
||||
Message::SubHandle(handle) => {
|
||||
if let Some(handle) = Arc::into_inner(handle) {
|
||||
self.subscription_handle = Some(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -676,7 +704,7 @@ fn input() -> Section<crate::pages::Message> {
|
|||
} else {
|
||||
"audio-input-microphone-symbolic"
|
||||
}))
|
||||
.on_press(Message::SourceMuteToggle),
|
||||
.on_press(Message::SourceMuteToggle.into()),
|
||||
)
|
||||
.push(
|
||||
widget::text::body(&page.source_volume_text)
|
||||
|
|
@ -685,8 +713,10 @@ fn input() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(
|
||||
widget::slider(0..=150, page.source_volume, Message::SourceVolumeChanged)
|
||||
.breakpoints(&[100]),
|
||||
widget::slider(0..=150, page.source_volume, |change| {
|
||||
Message::SourceVolumeChanged(change).into()
|
||||
})
|
||||
.breakpoints(&[100]),
|
||||
);
|
||||
let devices = widget::dropdown::popup_dropdown(
|
||||
&page.sources,
|
||||
|
|
@ -694,8 +724,10 @@ fn input() -> Section<crate::pages::Message> {
|
|||
Message::SourceChanged,
|
||||
window::Id::RESERVED,
|
||||
Message::Surface,
|
||||
|a| crate::app::Message::PageMessage(crate::pages::Message::Sound(a)),
|
||||
);
|
||||
|a| crate::Message::from(a),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::from);
|
||||
|
||||
let mut controls = settings::section()
|
||||
.title(§ion.title)
|
||||
|
|
@ -712,13 +744,15 @@ fn input() -> Section<crate::pages::Message> {
|
|||
Message::SourceProfileChanged,
|
||||
window::Id::RESERVED,
|
||||
Message::Surface,
|
||||
|a| crate::app::Message::PageMessage(crate::pages::Message::Sound(a)),
|
||||
);
|
||||
|a| crate::Message::from(a),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::from);
|
||||
|
||||
controls = controls.add(settings::item(&*section.descriptions[profile], dropdown));
|
||||
}
|
||||
|
||||
Element::from(controls).map(crate::pages::Message::Sound)
|
||||
Element::from(controls)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -746,7 +780,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
} else {
|
||||
widget::icon::from_name("audio-volume-high-symbolic")
|
||||
})
|
||||
.on_press(Message::SinkMuteToggle),
|
||||
.on_press(Message::SinkMuteToggle.into()),
|
||||
)
|
||||
.push(
|
||||
widget::text::body(&page.sink_volume_text)
|
||||
|
|
@ -755,8 +789,10 @@ fn output() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.push(widget::horizontal_space().width(8))
|
||||
.push(
|
||||
widget::slider(0..=150, page.sink_volume, Message::SinkVolumeChanged)
|
||||
.breakpoints(&[100]),
|
||||
widget::slider(0..=150, page.sink_volume, |change| {
|
||||
Message::SinkVolumeChanged(change).into()
|
||||
})
|
||||
.breakpoints(&[100]),
|
||||
);
|
||||
|
||||
let devices = widget::dropdown::popup_dropdown(
|
||||
|
|
@ -765,8 +801,10 @@ fn output() -> Section<crate::pages::Message> {
|
|||
Message::SinkChanged,
|
||||
window::Id::RESERVED,
|
||||
Message::Surface,
|
||||
|a| crate::app::Message::PageMessage(crate::pages::Message::Sound(a)),
|
||||
);
|
||||
|a| crate::Message::from(a),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::from);
|
||||
|
||||
let mut controls = settings::section()
|
||||
.title(§ion.title)
|
||||
|
|
@ -783,8 +821,10 @@ fn output() -> Section<crate::pages::Message> {
|
|||
Message::SinkProfileChanged,
|
||||
window::Id::RESERVED,
|
||||
Message::Surface,
|
||||
|a| crate::app::Message::PageMessage(crate::pages::Message::Sound(a)),
|
||||
);
|
||||
|a| crate::Message::from(a),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::from);
|
||||
|
||||
controls = controls.add(settings::item(&*section.descriptions[profile], dropdown));
|
||||
}
|
||||
|
|
@ -803,7 +843,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
widget::slider(
|
||||
0..=200,
|
||||
((sink_balance + 1.).max(0.) * 100.).round() as u32,
|
||||
Message::SinkBalanceChanged,
|
||||
|change| Message::SinkBalanceChanged(change).into(),
|
||||
)
|
||||
.breakpoints(&[100]),
|
||||
)
|
||||
|
|
@ -816,7 +856,7 @@ fn output() -> Section<crate::pages::Message> {
|
|||
));
|
||||
}
|
||||
|
||||
Element::from(controls).map(crate::pages::Message::Sound)
|
||||
Element::from(controls)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ name = "cosmic-settings-system"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
name = "cosmic-settings-wallpaper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue