diff --git a/Cargo.toml b/Cargo.toml index 19988c9..fe67104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ cosmic-randr = { git = "https://github.com/pop-os/cosmic-randr" } tokio = { version = "1.47.0", features = ["macros"] } [workspace.dependencies.libcosmic] -features = ["multi-window", "winit", "tokio"] +features = ["dbus-config", "multi-window", "winit", "tokio"] git = "https://github.com/pop-os/libcosmic" [workspace.dependencies.cosmic-config] @@ -33,7 +33,8 @@ git = "https://github.com/pop-os/cosmic-panel" git = "https://github.com/pop-os/cosmic-randr" [workspace.dependencies.cosmic-settings-subscriptions] -git = "https://github.com/pop-os/cosmic-settings-subscriptions" +# git = "https://github.com/pop-os/cosmic-settings-subscriptions" +path = "../../pop/cosmic-settings-subscriptions" [workspace.dependencies.sctk] git = "https://github.com/smithay/client-toolkit/" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index 1fdfe84..e4b2463 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -162,10 +162,7 @@ page-networking = [ ] page-power = ["dep:upower_dbus", "dep:zbus"] page-region = ["gettext", "dep:locales-rs", "dep:locale1", "dep:zbus"] -page-sound = [ - "cosmic-settings-subscriptions/pipewire", - "cosmic-settings-subscriptions/pulse", -] +page-sound = ["cosmic-settings-subscriptions/sound"] page-users = ["xdg-portal", "dep:accounts-zbus", "dep:zbus", "dep:zbus_polkit"] page-window-management = ["dep:cosmic-settings-config"] page-workspaces = ["dep:cosmic-comp-config"] diff --git a/cosmic-settings/src/pages/sound.rs b/cosmic-settings/src/pages/sound.rs index 2f6a224..aa2518d 100644 --- a/cosmic-settings/src/pages/sound.rs +++ b/cosmic-settings/src/pages/sound.rs @@ -1,8 +1,6 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use std::{collections::BTreeMap, sync::Arc, time::Duration}; - use cosmic::{ Apply, Element, Task, iced::{Alignment, Length, window}, @@ -10,66 +8,36 @@ use cosmic::{ widget::{self, settings}, }; use cosmic_settings_page::{self as page, Section, section}; -use cosmic_settings_subscriptions::{pipewire, pulse}; -use futures::StreamExt; -use indexmap::IndexMap; use slab::Slab; use slotmap::SlotMap; -pub type NodeId = u32; -pub type ProfileId = u32; +use cosmic_settings_subscriptions::sound as subscription; #[derive(Clone, Debug)] pub enum Message { - /// Get default sinks/sources and their volumes/mute status. - Pulse(pulse::Event), - /// Get ALSA cards and their profiles. - Pipewire(pipewire::DeviceEvent), + SinkBalanceChanged(u32), /// Change the default output. SinkChanged(usize), - /// Change the active profile for an output. - SinkProfileChanged(usize), - /// Select a device from the given card after a profile change. - 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 active profile for an output. + SinkProfileChanged(usize), + /// Request to change the default output volume. + SinkVolumeChanged(u32), /// Change the default input output. SourceChanged(usize), - /// Change the active profile for an output. - SourceProfileChanged(usize), - /// Select a device from the given card after a profile change. - SourceProfileSelect(DeviceId), - /// Request to change the input volume. - SourceVolumeChanged(u32), - /// Change the input volume. - 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), + /// Change the active profile for an output. + SourceProfileChanged(usize), + /// Request to change the input volume. + SourceVolumeChanged(u32), + /// + Subscription(subscription::sound::Message), /// 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) @@ -82,91 +50,16 @@ impl From for crate::Message { } } -#[derive(Debug)] -struct Card { - devices: IndexMap, -} - -#[derive(Debug)] -struct Device { - class: pipewire::MediaClass, - identifier: String, - description: String, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum DeviceId { - Alsa(u32), - Bluez5(String), - Unknown(), +impl Into for subscription::sound::Message { + fn into(self) -> Message { + Message::Subscription(self) + } } #[derive(Default)] pub struct Page { entity: page::Entity, - subscription_handle: Option, - sink_channels: Option, - - devices: BTreeMap, - card_names: IndexMap, - card_profiles: IndexMap>, - active_profiles: IndexMap>, - - /** Sink devices */ - - /// Product names for source sink devices. - sinks: Vec, - /// Pipewire object IDs for sink devices. - sink_pw_ids: Vec, - /// Profile IDs for the actively-selected sink device. - sink_profiles: Vec, - /// Names of profiles for the actively-selected sink device. - sink_profile_names: Vec, - /// Device ID of active sink device. - active_sink_device: Option, - /// Index of active sink device. - active_sink: Option, - /// Card profile index of active sink device. - active_sink_profile: Option, - - /** Source devices */ - - /// Product names for source devices. - sources: Vec, - /// Pipewire object IDs for source devices. - source_pw_ids: Vec, - /// Profile IDs for the actively-selected source device. - source_profiles: Vec, - /// Names of profiles for the actively-selected source device. - source_profile_names: Vec, - /// Device ID of active source device. - active_source_device: Option, - /// Index of active source device. - active_source: Option, - /// Card profile index of active source device. - active_source_profile: Option, - - /// Device identifier of the default sink. - default_sink: String, - /// Device identifier of the default source. - default_source: String, - - sink_volume_text: String, - source_volume_text: String, - - sink_balance_text: Option, - sink_balance: Option, - - sink_volume: u32, - source_volume: u32, - - sink_mute: bool, - sink_volume_debounce: bool, - sink_balance_debounce: bool, - source_mute: bool, - source_volume_debounce: bool, - changing_sink_profile: bool, - changing_source_profile: bool, + model: subscription::sound::Model, } impl page::Page for Page { @@ -187,71 +80,12 @@ impl page::Page for Page { &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::<()>(); - - let (tx, mut pulse_rx) = futures::channel::mpsc::channel(1); - let _pulse_handle = std::thread::spawn(move || { - pulse::thread(tx); - }); - - 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; - } - - event = pw_rx.next() => { - let Some(event) = event else { - break; - }; - - emitter - .emit(crate::pages::Message::from(Message::Pipewire(event))) - .await; - } - - _ = cancel_rx => break, - } - } - - drop(pulse_rx); - drop(pw_rx); - - futures::future::pending::().await; - }) - }) + cosmic::iced::Subscription::run(|| subscription::watch()) + .map(|message| Message::Subscription(message).into()) } fn on_leave(&mut self) -> Task { - if let Some(handle) = self.subscription_handle.take() { - _ = handle.cancel_tx.send(()); - _ = handle.pipewire.send(()); - } - - if let Some(channel) = self.sink_channels.take() { - channel.quit(); - } + self.model.clear(); *self = Page { entity: self.entity, @@ -265,421 +99,70 @@ impl page::Page for Page { impl page::AutoBind for Page {} impl Page { - fn device_profiles(&self, device_id: &DeviceId) -> (Vec, Vec, Option) { - let (profiles, profile_descriptions): (Vec, Vec) = self - .card_profiles - .get(device_id) - .map_or((Vec::new(), Vec::new()), |profiles| { - profiles - .iter() - .filter(|p| p.available && p.name != "off") - .map(|p| (p.name.clone(), p.description.clone())) - .collect() - }); - - let active_profile = self.active_profiles.get(device_id).and_then(|profile| { - profile - .as_ref() - .and_then(|profile| profiles.iter().position(|p| p == profile)) - }); - - (profiles, profile_descriptions, active_profile) - } - - fn set_sink_profiles(&mut self, device_id: &DeviceId) { - ( - self.sink_profile_names, - self.sink_profiles, - self.active_sink_profile, - ) = self.device_profiles(device_id); - } - - fn set_source_profiles(&mut self, device_id: &DeviceId) { - ( - self.source_profile_names, - self.source_profiles, - self.active_source_profile, - ) = self.device_profiles(device_id); - } - - fn set_default_sink(&mut self, sink: String) { - if self.default_sink == sink { - return; - } - - self.default_sink = sink; - self.active_sink_profile = None; - self.sink_profiles.clear(); - self.sink_profile_names.clear(); - - for (device_id, card) in &self.devices { - for (&node_id, device) in &card.devices { - if let pipewire::MediaClass::Sink = device.class { - if device.identifier == self.default_sink { - self.active_sink = self.sink_pw_ids.iter().position(|&id| id == node_id); - let device_id = device_id.clone(); - self.set_sink_profiles(&device_id); - self.active_sink_device = Some(device_id); - return; - } - } - } - } - } - - fn set_default_source(&mut self, source: String) { - if self.default_source == source { - return; - } - - self.default_source = source; - self.active_source_profile = None; - self.source_profiles.clear(); - self.source_profile_names.clear(); - - for (device_id, card) in &self.devices { - for (&node_id, device) in &card.devices { - if let pipewire::MediaClass::Source = device.class { - if device.identifier == self.default_source { - self.active_source = - self.source_pw_ids.iter().position(|&id| id == node_id); - let device_id = device_id.clone(); - self.set_source_profiles(&device_id); - self.active_source_device = Some(device_id); - return; - } - } - } - } - } - pub fn update(&mut self, message: Message) -> Task { match message { - Message::SourceVolumeChanged(volume) => { - self.source_volume = volume; - self.source_volume_text = volume.to_string(); - if self.source_volume_debounce { - return Task::none(); - } - - 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 { - tokio::time::sleep(Duration::from_millis(64)).await; - Message::SourceVolumeApply(node_id).into() - })); - } - - if let Some(command) = command { - self.source_volume_debounce = true; - return command; - } - } - Message::Pulse(pulse::Event::SourceVolume(volume)) => { - if self.sink_volume_debounce { - return Task::none(); - } - - self.source_volume = volume; - self.source_volume_text = volume.to_string(); - } - Message::SinkVolumeChanged(volume) => { - self.sink_volume = volume; - self.sink_volume_text = volume.to_string(); - if self.sink_volume_debounce { - return Task::none(); - } - - 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 { - tokio::time::sleep(Duration::from_millis(64)).await; - Message::SinkVolumeApply(node_id).into() - })); - } - - if let Some(command) = command { - self.sink_volume_debounce = true; - 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 !self - .sink_pw_ids - .get(self.active_sink.unwrap_or(0)) - .is_none() - { - command = Some(cosmic::Task::future(async move { - tokio::time::sleep(Duration::from_millis(64)).await; - Message::SinkBalanceApply.into() - })); - } - - 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(); - } - - self.sink_volume = volume; - self.sink_volume_text = volume.to_string(); - } - Message::Pulse(pulse::Event::DefaultSink(sink)) => { - if !self.changing_sink_profile { - self.set_default_sink(sink); - } - } - Message::Pulse(pulse::Event::DefaultSource(source)) => { - if !self.changing_source_profile { - self.set_default_source(source); - } - } - Message::Pulse(pulse::Event::SinkMute(mute)) => { - self.sink_mute = mute; - } - Message::Pulse(pulse::Event::SourceMute(mute)) => { - self.source_mute = mute; - } - Message::Pulse(pulse::Event::CardInfo(card)) => { - let device_id = match card.variant { - pulse::DeviceVariant::Alsa { alsa_card, .. } => DeviceId::Alsa(alsa_card), - pulse::DeviceVariant::Bluez5 { address, .. } => DeviceId::Bluez5(address), - }; - - self.card_names.insert(device_id.clone(), card.name); - self.card_profiles.insert(device_id.clone(), card.profiles); - 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), - pipewire::DeviceVariant::Bluez5 { address, .. } => DeviceId::Bluez5(address), - pipewire::DeviceVariant::Unknown {} => DeviceId::Unknown {}, - }; - - match device.media_class { - pipewire::MediaClass::Sink => { - self.sinks.push(device.product_name.clone()); - self.sink_pw_ids.push(device.object_id); - - sort_pulse_devices(&mut self.sinks, &mut self.sink_pw_ids); - - if self.default_sink == device.node_name { - self.active_sink = - self.sinks.iter().position(|s| *s == device.product_name); - self.active_sink_device = Some(device_id.clone()); - self.set_sink_profiles(&device_id); - } - } - - pipewire::MediaClass::Source => { - self.sources.push(device.product_name.clone()); - self.source_pw_ids.push(device.object_id); - - sort_pulse_devices(&mut self.sources, &mut self.source_pw_ids); - - if self.default_source == device.node_name { - self.active_source = - self.sources.iter().position(|s| *s == device.product_name); - self.active_source_device = Some(device_id.clone()); - self.set_source_profiles(&device_id); - } - } - } - - let card = self.devices.entry(device_id).or_insert_with(|| Card { - devices: IndexMap::new(), - }); - - card.devices.insert( - device.object_id, - Device { - class: device.media_class, - identifier: device.node_name, - description: device.product_name, - }, - ); - - card.devices - .sort_unstable_by(|_, av, _, bv| av.description.cmp(&bv.description)); - } - Message::Pipewire(pipewire::DeviceEvent::Remove(node_id)) => { - let mut remove = None; - for (card_id, card) in &mut self.devices { - if card.devices.shift_remove(&node_id).is_some() { - if card.devices.is_empty() { - remove = Some(card_id.clone()); - } - break; - } - } - - if let Some(card_id) = remove { - _ = self.devices.remove(&card_id); - } - - if let Some(pos) = self.sink_pw_ids.iter().position(|&id| id == node_id) { - _ = self.sink_pw_ids.remove(pos); - _ = self.sinks.remove(pos); - if self.active_sink == Some(pos) { - self.active_sink = None; - self.active_sink_device = None; - self.active_sink_profile = None; - } - } else if let Some(pos) = self.source_pw_ids.iter().position(|&id| id == node_id) { - _ = self.source_pw_ids.remove(pos); - _ = self.sources.remove(pos); - if self.active_source == Some(pos) { - self.active_source = None; - self.active_source_device = None; - self.active_source_profile = None; - } - } + return self + .model + .sink_balance_changed(balance) + .map(|message| Message::Subscription(message).into()); } Message::SinkChanged(pos) => { - if let Some(node_id) = self.sink_pw_ids.get(pos) { - for card in self.devices.values() { - for (nid, device) in &card.devices { - if node_id == nid { - self.active_sink = Some(pos); - pactl_set_default_sink(device.identifier.clone()); - self.set_default_sink(device.identifier.clone()); - return Task::none(); - } - } - } - } - } - Message::SourceChanged(pos) => { - if let Some(node_id) = self.source_pw_ids.get(pos) { - for card in self.devices.values() { - for (nid, device) in &card.devices { - if node_id == nid { - self.active_source = Some(pos); - pactl_set_default_source(device.identifier.clone()); - self.set_default_source(device.identifier.clone()); - return Task::none(); - } - } - } - } - } - 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; - 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; - wpctl_set_volume(node_id, self.source_volume); - } - Message::SinkMuteToggle => { - self.sink_mute = !self.sink_mute; - if let Some(&node_id) = self.sink_pw_ids.get(self.active_sink.unwrap_or(0)) { - wpctl_set_mute(node_id, self.sink_mute); - } - } - Message::SourceMuteToggle => { - self.source_mute = !self.source_mute; - if let Some(&node_id) = self.source_pw_ids.get(self.active_source.unwrap_or(0)) { - wpctl_set_mute(node_id, self.source_mute); - } + return self + .model + .sink_changed(pos) + .map(|message| Message::Subscription(message).into()); } + + Message::SinkMuteToggle => self.model.sink_mute_toggle(), + Message::SinkProfileChanged(profile) => { - self.active_sink_profile = Some(profile); - - if let Some(profile) = self.sink_profile_names.get(profile).cloned() { - if let Some(device_id) = self.active_sink_device.clone() { - if let Some(name) = self.card_names.get(&device_id).cloned() { - self.active_profiles - .insert(device_id.clone(), Some(profile.clone())); - - self.changing_sink_profile = true; - return cosmic::Task::future(async move { - pactl_set_card_profile(name, profile).await; - Message::SinkProfileSelect(device_id).into() - }); - } - } - } + return self + .model + .sink_profile_changed(profile) + .map(|message| Message::Subscription(message).into()); } - Message::SinkProfileSelect(device_id) => { - self.changing_sink_profile = false; - let sink_pos = self.active_sink.unwrap_or(0); - if let Some(card) = self.devices.get(&device_id) { - if let Some((_, device)) = card.devices.get_index(sink_pos) { - pactl_set_default_sink(device.identifier.clone()); - self.set_default_sink(device.identifier.clone()); - } - } + Message::SinkVolumeChanged(volume) => { + return self + .model + .sink_volume_changed(volume) + .map(|message| Message::Subscription(message).into()); } + + Message::SourceChanged(pos) => { + return self + .model + .source_changed(pos) + .map(|message| Message::Subscription(message).into()); + } + + Message::SourceMuteToggle => self.model.source_mute_toggle(), + Message::SourceProfileChanged(profile) => { - self.active_source_profile = Some(profile); - if let Some(profile) = self.source_profile_names.get(profile).cloned() { - if let Some(device_id) = self.active_source_device.clone() { - if let Some(name) = self.card_names.get(&device_id).cloned() { - self.active_profiles - .insert(device_id.clone(), Some(profile.clone())); - - self.changing_source_profile = true; - return cosmic::Task::future(async move { - pactl_set_card_profile(name, profile).await; - Message::SourceProfileSelect(device_id).into() - }); - } - } - } - } - Message::SourceProfileSelect(device_id) => { - self.changing_source_profile = false; - let source_pos = self.active_source.unwrap_or(0); - - if let Some(card) = self.devices.get(&device_id) { - if let Some((_, device)) = card.devices.get_index(source_pos) { - pactl_set_default_source(device.identifier.clone()); - self.set_default_source(device.identifier.clone()); - } - } - } - Message::Surface(a) => { - return cosmic::task::message(crate::app::Message::Surface(a)); + return self + .model + .source_profile_changed(profile) + .map(|message| Message::Subscription(message).into()); } - Message::SubHandle(handle) => { - if let Some(handle) = Arc::into_inner(handle) { - self.subscription_handle = Some(handle); - } + Message::SourceVolumeChanged(volume) => { + return self + .model + .source_volume_changed(volume) + .map(|message| Message::Subscription(message).into()); } + + Message::Subscription(message) => { + return self + .model + .update(message) + .map(|message| Message::Subscription(message).into()); + } + + Message::Surface(a) => return cosmic::task::message(crate::app::Message::Surface(a)), } + Task::none() } } @@ -696,14 +179,14 @@ fn input() -> Section { .title(fl!("sound-input")) .descriptions(descriptions) .view::(move |_binder, page, section| { - if page.sources.is_empty() { + if page.model.sources().is_empty() { return widget::row().into(); } let volume_control = widget::row::with_capacity(4) .align_y(Alignment::Center) .push( - widget::button::icon(widget::icon::from_name(if page.source_mute { + widget::button::icon(widget::icon::from_name(if page.model.source_mute { "microphone-sensitivity-muted-symbolic" } else { "audio-input-microphone-symbolic" @@ -711,20 +194,20 @@ fn input() -> Section { .on_press(Message::SourceMuteToggle.into()), ) .push( - widget::text::body(&page.source_volume_text) + widget::text::body(&page.model.source_volume_text) .width(Length::Fixed(22.0)) .align_x(Alignment::Center), ) .push(widget::horizontal_space().width(8)) .push( - widget::slider(0..=150, page.source_volume, |change| { + widget::slider(0..=150, page.model.source_volume, |change| { Message::SourceVolumeChanged(change).into() }) .breakpoints(&[100]), ); let devices = widget::dropdown::popup_dropdown( - &page.sources, - Some(page.active_source.unwrap_or(0)), + page.model.sources(), + Some(page.model.active_source().unwrap_or(0)), Message::SourceChanged, window::Id::RESERVED, Message::Surface, @@ -741,10 +224,10 @@ fn input() -> Section { )) .add(settings::item(&*section.descriptions[device], devices)); - if !page.source_profiles.is_empty() { + if !page.model.source_profiles().is_empty() { let dropdown = widget::dropdown::popup_dropdown( - &page.source_profiles, - page.active_source_profile, + page.model.source_profiles(), + page.model.active_source_profile(), Message::SourceProfileChanged, window::Id::RESERVED, Message::Surface, @@ -779,7 +262,7 @@ fn output() -> Section { let volume_control = widget::row::with_capacity(4) .align_y(Alignment::Center) .push( - widget::button::icon(if page.sink_mute { + widget::button::icon(if page.model.sink_mute { widget::icon::from_name("audio-volume-muted-symbolic") } else { widget::icon::from_name("audio-volume-high-symbolic") @@ -787,21 +270,21 @@ fn output() -> Section { .on_press(Message::SinkMuteToggle.into()), ) .push( - widget::text::body(&page.sink_volume_text) + widget::text::body(&page.model.sink_volume_text) .width(Length::Fixed(22.0)) .align_x(Alignment::Center), ) .push(widget::horizontal_space().width(8)) .push( - widget::slider(0..=150, page.sink_volume, |change| { + widget::slider(0..=150, page.model.sink_volume, |change| { Message::SinkVolumeChanged(change).into() }) .breakpoints(&[100]), ); let devices = widget::dropdown::popup_dropdown( - &page.sinks, - Some(page.active_sink.unwrap_or(0)), + page.model.sinks(), + Some(page.model.active_sink().unwrap_or(0)), Message::SinkChanged, window::Id::RESERVED, Message::Surface, @@ -818,10 +301,10 @@ fn output() -> Section { )) .add(settings::item(&*section.descriptions[device], devices)); - if !page.sink_profiles.is_empty() { + if !page.model.sink_profiles().is_empty() { let dropdown = widget::dropdown::popup_dropdown( - &page.sink_profiles, - page.active_sink_profile, + page.model.sink_profiles(), + page.model.active_sink_profile(), Message::SinkProfileChanged, window::Id::RESERVED, Message::Surface, @@ -832,7 +315,7 @@ fn output() -> Section { controls = controls.add(settings::item(&*section.descriptions[profile], dropdown)); } - if let Some(sink_balance) = page.sink_balance { + if let Some(sink_balance) = page.model.sink_balance { controls = controls.add(settings::item( &*section.descriptions[balance], widget::row::with_capacity(4) @@ -897,60 +380,3 @@ fn output() -> Section { // .into() // }) // } - -fn sort_pulse_devices(descriptions: &mut Vec, node_ids: &mut Vec) { - let mut tmp: Vec<(String, NodeId)> = std::mem::take(descriptions) - .into_iter() - .zip(std::mem::take(node_ids)) - .collect(); - - tmp.sort_unstable_by(|(ak, _), (bk, _)| ak.cmp(bk)); - - (*descriptions, *node_ids) = tmp.into_iter().collect(); -} - -async fn pactl_set_card_profile(id: String, profile: String) { - _ = tokio::process::Command::new("pactl") - .args(["set-card-profile", id.as_str(), profile.as_str()]) - .status() - .await -} - -fn pactl_set_default_sink(id: String) { - tokio::task::spawn(async move { - _ = tokio::process::Command::new("pactl") - .args(["set-default-sink", id.as_str()]) - .status() - .await; - }); -} - -fn pactl_set_default_source(id: String) { - tokio::task::spawn(async move { - _ = tokio::process::Command::new("pactl") - .args(["set-default-source", id.as_str()]) - .status() - .await; - }); -} - -fn wpctl_set_mute(id: u32, mute: bool) { - tokio::task::spawn(async move { - let default = id.to_string(); - _ = tokio::process::Command::new("wpctl") - .args(["set-mute", default.as_str(), if mute { "1" } else { "0" }]) - .status() - .await; - }); -} - -fn wpctl_set_volume(id: u32, volume: u32) { - tokio::task::spawn(async move { - let id = id.to_string(); - let volume = format!("{}.{:02}", volume / 100, volume % 100); - _ = tokio::process::Command::new("wpctl") - .args(["set-volume", id.as_str(), volume.as_str()]) - .status() - .await; - }); -} diff --git a/cosmic-settings/src/pages/system/about.rs b/cosmic-settings/src/pages/system/about.rs index 6ce23de..16010b7 100644 --- a/cosmic-settings/src/pages/system/about.rs +++ b/cosmic-settings/src/pages/system/about.rs @@ -1,6 +1,7 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use cosmic::iced_core::text::Wrapping; use cosmic_settings_page::{self as page, Section, section}; use cosmic::widget::{editable_input, list_column, settings, text};