diff --git a/Cargo.lock b/Cargo.lock index f53f515..31c2e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index a63ceed..6aff4d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index 447bebf..d6c4a36 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -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"] diff --git a/cosmic-settings/src/pages/sound.rs b/cosmic-settings/src/pages/sound.rs index 1ae7ccc..69f2a64 100644 --- a/cosmic-settings/src/pages/sound.rs +++ b/cosmic-settings/src/pages/sound.rs @@ -1,10 +1,10 @@ // Copyright 2023 System76 // 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), /// 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 for crate::pages::Message { + fn from(message: Message) -> Self { + crate::pages::Message::Sound(message) + } +} + +impl From for crate::Message { + fn from(message: Message) -> Self { + crate::Message::PageMessage(message.into()) + } +} + #[derive(Debug)] struct Card { devices: IndexMap, @@ -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>, + subscription_handle: Option, sink_channels: Option, devices: BTreeMap, @@ -146,10 +170,6 @@ pub struct Page { } impl page::Page for Page { - fn set_id(&mut self, entity: page::Entity) { - self.entity = entity; - } - fn content( &self, sections: &mut SlotMap>, @@ -163,74 +183,80 @@ impl page::Page for Page { .description(fl!("sound", "desc")) } - fn on_enter(&mut self) -> Task { - 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 { + 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::().await; + }) + }) } fn on_leave(&mut self) -> Task { - 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 { } 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 { ) .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 { 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 { 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 { } 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 { ) .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 { 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 { 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 { 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 { )); } - Element::from(controls).map(crate::pages::Message::Sound) + Element::from(controls) }) } diff --git a/pages/system/Cargo.toml b/pages/system/Cargo.toml index bdff947..52a564c 100644 --- a/pages/system/Cargo.toml +++ b/pages/system/Cargo.toml @@ -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 diff --git a/pages/wallpapers/Cargo.toml b/pages/wallpapers/Cargo.toml index 3f6cd48..f1ad1bb 100644 --- a/pages/wallpapers/Cargo.toml +++ b/pages/wallpapers/Cargo.toml @@ -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