Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ac686d4
feat(sound): redesign with separate device profiles page
mmstick Oct 20, 2025
c8c3e64
fix(sound): support nodes without a device ID association
mmstick Oct 20, 2025
fdb02ad
chore: update libcosmic and other dependencies
mmstick Oct 20, 2025
4df6fae
fix(sound): node with no device ID being set on page load
mmstick Oct 20, 2025
513a807
fix(sound): devices disappear when setting profile to Off
mmstick Oct 21, 2025
403181a
fix(sound): use popup dropdown widget
mmstick Oct 21, 2025
aadc130
fix(sound): wrong profile position when profiles are filtered
mmstick Oct 21, 2025
f3025a3
chore: remove log
mmstick Oct 21, 2025
9d06963
feat(sound): show profile description in sink/source names
mmstick Oct 22, 2025
33db46e
fix(sound): add routes to device select and improve reliability
mmstick Oct 27, 2025
5fa8c56
fix(sound): volume slider jumps when adjusting volume
mmstick Oct 27, 2025
509a3fc
fix(sound): try re-inserting node associations even if it is already set
mmstick Oct 28, 2025
85c2443
fix(sound): try sending AddDevice event before enumerating its profil…
mmstick Oct 28, 2025
80fd2ea
fix(sound): try allowing nodes to be re-added even if they were inserted
mmstick Oct 28, 2025
97d8721
fix(sound): track card.profile.device and find the available route
mmstick Oct 29, 2025
55d6e9d
fix(sound) re-set default node after profile change once we see the node
mmstick Oct 31, 2025
3a78834
fix(sound): fall back to pw-cli and pactl when setting sinks/sources …
mmstick Oct 31, 2025
eda271c
fix(sound): on page load, add missing Input/Audio line to stream-prop…
mmstick Nov 3, 2025
7301e9a
fix(sound): restart wireplumber when updating state config
mmstick Nov 4, 2025
26f18c7
fix(resources): change desktop entry Types to Application
mmstick Nov 5, 2025
1ca1587
fix(sound): set profile and device defaults on startup
mmstick Nov 5, 2025
4da6b98
chore: update dependencies
mmstick Nov 5, 2025
6b860ad
chore(sound): remove unnecessary workaround
mmstick Nov 6, 2025
61558ae
fix(sound): use pw-cli for setting profiles that persist
mmstick Nov 6, 2025
788ba55
chore(sound): node name and is_sink not needed anymore
mmstick Nov 6, 2025
dab3549
chore(sound): ignore stdout/stderr on wpctl/pw-cli commands
mmstick Nov 6, 2025
a6ba937
feat: update libcosmic with context drawer title changes
mmstick Nov 6, 2025
15d29d1
fix(sound): update device name when active route changes
mmstick Nov 6, 2025
7b42058
fix(sound): use libpulse to monitor active port changes
mmstick Nov 7, 2025
f5dfaf6
fix(sound): pro-audio devices not having their routes added to the name
mmstick Nov 7, 2025
ff15c0d
fix(sound): only set device route for routes that are available
mmstick Nov 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 140 additions & 112 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions cosmic-settings/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,13 @@ impl cosmic::Application for SettingsApp {
}
}

#[cfg(feature = "page-sound")]
crate::pages::Message::SoundDeviceProfiles(message) => {
if let Some(page) = self.pages.page_mut::<sound::device_profiles::Page>() {
return page.update(message).map(Into::into);
}
}

crate::pages::Message::StartupApps(message) => {
if let Some(page) = self.pages.page_mut::<applications::startup_apps::Page>() {
return page.update(message).map(Into::into);
Expand Down
2 changes: 2 additions & 0 deletions cosmic-settings/src/pages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum Message {
Region(time::region::Message),
#[cfg(feature = "page-sound")]
Sound(sound::Message),
#[cfg(feature = "page-sound")]
SoundDeviceProfiles(sound::device_profiles::Message),
StartupApps(applications::startup_apps::Message),
#[cfg(feature = "page-users")]
User(system::users::Message),
Expand Down
125 changes: 125 additions & 0 deletions cosmic-settings/src/pages/sound/device_profiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2025 System76 <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use cosmic::{Apply, widget};
use cosmic_settings_page::{self as page, Section, section};
use cosmic_settings_sound_subscription::{self as subscription, pipewire};
use itertools::Itertools;
use slotmap::SlotMap;

#[derive(Clone, Debug)]
pub enum Message {}

impl From<Message> for crate::pages::Message {
fn from(message: Message) -> Self {
crate::pages::Message::SoundDeviceProfiles(message)
}
}

impl From<Message> for crate::Message {
fn from(message: Message) -> Self {
crate::Message::PageMessage(message.into())
}
}

#[derive(Default)]
pub struct Page {
entity: page::Entity,
}

impl page::AutoBind<crate::pages::Message> for Page {}

impl page::Page<crate::pages::Message> for Page {
fn info(&self) -> page::Info {
page::Info::new("sound-device-profiles", "preferences-sound-symbolic")
.title(fl!("sound-device-profiles"))
}

fn content(
&self,
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
) -> Option<page::Content> {
Some(vec![sections.insert(view())])
}

fn on_leave(&mut self) -> cosmic::Task<crate::pages::Message> {
cosmic::Task::done(crate::pages::Message::Sound(super::Message::Reload))
}

fn set_id(&mut self, entity: cosmic_settings_page::Entity) {
self.entity = entity;
}

fn subscription(
&self,
_core: &cosmic::Core,
) -> cosmic::iced::Subscription<crate::pages::Message> {
cosmic::iced::Subscription::run(subscription::watch)
.map(|message| super::Message::Subscription(message).into())
}
}

impl Page {
pub fn update(&mut self, _message: Message) -> cosmic::Task<crate::app::Message> {
cosmic::Task::none()
}
}

pub fn view() -> Section<crate::pages::Message> {
Section::default().view::<Page>(move |binder, _page, _section| {
let sound_page_id = binder.find_page_by_id("sound").unwrap().0;
let sound_page = binder.page[sound_page_id]
.downcast_ref::<super::Page>()
.unwrap();

let devices = sound_page
.model
.device_profiles
.iter()
.filter_map(|(device_id, profiles)| {
let name = sound_page.model.device_names.get(device_id)?.as_str();

// TODO: cache
let active_profile =
sound_page
.model
.active_profiles
.get(device_id)
.and_then(|profile| {
profiles
.iter()
.filter(|p| !matches!(p.available, pipewire::Availability::No))
.enumerate()
.find(|(_, p)| p.index == profile.index)
.map(|(pos, _)| pos)
});

// TODO: cache
let (indexes, profiles): (Vec<_>, Vec<_>) = profiles
.iter()
.filter(|p| !matches!(p.available, pipewire::Availability::No))
.map(|p| (p.index as u32, p.description.clone()))
.collect();

let dropdown = widget::dropdown::popup_dropdown(
Vec::from_iter(profiles),
active_profile,
move |id| super::Message::SetProfile(device_id, indexes[id]),
cosmic::iced::window::Id::RESERVED,
super::Message::Surface,
crate::Message::from,
)
.apply(cosmic::Element::from)
.map(crate::pages::Message::from);

Some((
name,
widget::settings::item::builder(name).control(dropdown),
))
})
.sorted_by(|a, b| a.0.cmp(b.0))
.map(|(_, element)| element);

widget::settings::section().extend(devices).into()
})
}
Loading