diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..2cc7b98d --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,15 @@ +{ + "format_on_save": "on", + "lsp": { + "rust-analyzer": { + "initialization_options": { + "check": { + "command": "clippy", + }, + "rustfmt": { + "extraArgs": ["+nightly"], + }, + }, + }, + }, +} diff --git a/audio-client/src/lib.rs b/audio-client/src/lib.rs index fe8e3e1d..353f78ff 100644 --- a/audio-client/src/lib.rs +++ b/audio-client/src/lib.rs @@ -8,7 +8,8 @@ pub use zlink; use zlink::Connection; pub use cosmic_settings_audio_core::*; -use std::{os::fd::OwnedFd, path::PathBuf}; +use std::os::fd::OwnedFd; +use std::path::PathBuf; pub async fn connect() -> zlink::Result { zlink::unix::connect(socket_path()) diff --git a/audio-server/src/backend.rs b/audio-server/src/backend.rs index 2c3dfc30..020dfa86 100644 --- a/audio-server/src/backend.rs +++ b/audio-server/src/backend.rs @@ -8,9 +8,9 @@ use cosmic_settings_daemon_config::{CosmicSettingsDaemonConfig, CosmicSettingsDa use futures_util::{SinkExt, StreamExt}; use intmap::IntMap; use pipewire::Availability; -use std::{ - process::Stdio, sync::{Arc, OnceLock}, time::Instant -}; +use std::process::Stdio; +use std::sync::{Arc, OnceLock}; +use std::time::Instant; use tokio::net::unix::pipe; use tokio_util::codec::FramedWrite; diff --git a/audio-server/src/config.rs b/audio-server/src/config.rs index 0cbd1d3a..efe158ab 100644 --- a/audio-server/src/config.rs +++ b/audio-server/src/config.rs @@ -2,6 +2,7 @@ use cosmic_config::{Config as CosmicConfig, ConfigGet}; const AUDIO_CONFIG: &str = "com.system76.CosmicAudio"; const AMPLIFICATION_SINK: &str = "amplification_sink"; +const VOLUME_STEP: &str = "volume_step"; pub async fn amplification_sink() -> bool { match CosmicConfig::new(AUDIO_CONFIG, 1) { @@ -12,3 +13,16 @@ pub async fn amplification_sink() -> bool { } } } + +pub async fn volume_step() -> u32 { + match CosmicConfig::new(AUDIO_CONFIG, 1) { + Ok(config) => config + .get::(VOLUME_STEP) + .map(|v| v.max(1)) + .unwrap_or(5), + Err(e) => { + tracing::debug!("Failed to read volume step config: {}", e); + 5 + } + } +} diff --git a/audio-server/src/context.rs b/audio-server/src/context.rs index 730a2339..ffc5c7f0 100644 --- a/audio-server/src/context.rs +++ b/audio-server/src/context.rs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::backend::*; -use std::{ - sync::{Arc, Mutex}, time::Duration -}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; use tokio::sync::mpsc; #[derive(Clone)] diff --git a/audio-server/src/server.rs b/audio-server/src/server.rs index 93cb98ed..25bfba40 100644 --- a/audio-server/src/server.rs +++ b/audio-server/src/server.rs @@ -4,7 +4,8 @@ //! Interfaces for implementing the varlink methods for `com.system76.CosmicSettings.Audio`. use cosmic_settings_audio_core::{Error, Mute, Node, Volume}; -use std::{os::fd::OwnedFd, sync::Arc}; +use std::os::fd::OwnedFd; +use std::sync::Arc; use crate::{config, context}; @@ -124,7 +125,8 @@ impl Server { set_node_mute(&mut model, node_id, mute) } - pub async fn source_volume_lower(&mut self, step: u32) -> Result { + pub async fn source_volume_lower(&mut self) -> Result { + let step = config::volume_step().await; let mut model = self.backend.model.lock().await; let Some(id) = model.active_source_node else { return Err(Error::NoActiveSource); @@ -142,7 +144,8 @@ impl Server { set_node_volume(&mut model, id, volume, None) } - pub async fn source_volume_raise(&mut self, step: u32) -> Result { + pub async fn source_volume_raise(&mut self) -> Result { + let step = config::volume_step().await; let mut model = self.backend.model.lock().await; let Some(id) = model.active_source_node else { return Err(Error::NoActiveSource); @@ -170,7 +173,8 @@ impl Server { set_node_mute(&mut model, node_id, mute) } - pub async fn sink_volume_lower(&mut self, step: u32) -> Result { + pub async fn sink_volume_lower(&mut self) -> Result { + let step = config::volume_step().await; let mut model = self.backend.model.lock().await; let Some(id) = model.active_sink_node else { return Err(Error::NoActiveSink); @@ -189,12 +193,13 @@ impl Server { set_node_volume(&mut model, id, volume, balance) } - pub async fn sink_volume_raise(&mut self, step: u32) -> Result { + pub async fn sink_volume_raise(&mut self) -> Result { let max_volume = if config::amplification_sink().await { 150 } else { 100 }; + let step = config::volume_step().await; let mut model = self.backend.model.lock().await; let Some(id) = model.active_sink_node else { return Err(Error::NoActiveSink); diff --git a/config/src/shortcuts/binding.rs b/config/src/shortcuts/binding.rs index fc43d20b..a7ce8a2e 100644 --- a/config/src/shortcuts/binding.rs +++ b/config/src/shortcuts/binding.rs @@ -46,7 +46,7 @@ impl Binding { Binding { description: None, modifiers: modifiers.into(), - keycode: None, + keycode: key.map(|key| key.raw()), key: None, } } @@ -75,10 +75,10 @@ impl Binding { }; // Try case-sensitive lookup first in case of two symbols that only differ in case. - match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) { + match xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) { x if x.raw() == super::sym::NO_SYMBOL => { // Fallback to case insensitive lookup. - match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) { + match xkb::keysym_from_name(name, xkb::KEYSYM_CASE_INSENSITIVE) { x_insensitive if x_insensitive.raw() == super::sym::NO_SYMBOL => { return Err(format!("'{name}' is not a valid key symbol")); } @@ -109,11 +109,12 @@ impl Binding { /// Check if the binding has been set pub fn is_set(&self) -> bool { - (self.has_modifier() && self.key.is_some()) - || self.is_super() + (self.has_modifier() && (self.key.is_some() || self.keycode.is_some())) + || self.is_modifier_only() || self .key - .map_or(false, |key| !is_forbidden_unmodified_keysym(key)) + .is_some_and(|key| !is_forbidden_unmodified_keysym(key)) + || self.keycode.is_some() } /// Check if the key binding is binding directly to Super @@ -125,6 +126,16 @@ impl Binding { && !self.modifiers.ctrl } + /// Check if the binding contains either Super alone or multiple modifiers. + pub fn is_modifier_only(&self) -> bool { + let modifier_count = self.modifiers.logo as u8 + + self.modifiers.shift as u8 + + self.modifiers.alt as u8 + + self.modifiers.ctrl as u8; + + self.key.is_none() && self.keycode.is_none() && (self.is_super() || modifier_count >= 2) + } + /// Get the inferred direction of a xkb key pub fn inferred_direction(&self) -> Option { match self.key? { @@ -169,12 +180,13 @@ impl Binding { && (self.modifiers.shift & other.modifiers.shift == self.modifiers.shift) && (self.modifiers.logo & other.modifiers.logo == self.modifiers.logo) && (self.key.is_none() || self.key == other.key) + && (self.keycode.is_none() || self.keycode == other.keycode) } } impl PartialEq for Binding { fn eq(&self, other: &Self) -> bool { - self.modifiers == other.modifiers && self.key == other.key + self.modifiers == other.modifiers && self.key == other.key && self.keycode == other.keycode } } @@ -189,6 +201,7 @@ impl ToString for Binding { impl Hash for Binding { fn hash(&self, state: &mut H) { self.key.hash(state); + self.keycode.hash(state); self.modifiers.hash(state); } } @@ -198,8 +211,8 @@ impl FromStr for Binding { fn from_str(value: &str) -> Result { let binding = Binding::from_str_partial(value)?; - if binding.key.is_none() && !binding.modifiers.logo { - return Err(format!("no key was defined for this binding")); + if binding.key.is_none() && !binding.is_modifier_only() { + return Err("no key was defined for this binding".to_string()); } Ok(binding) @@ -271,14 +284,21 @@ mod tests { )) ); - // Must have a non-modifier key. - assert!(matches!(Binding::from_str("Super+Shift"), Err(_))); + assert_eq!( + Binding::from_str("Alt+Shift"), + Ok(Binding::new(Modifiers::new().alt().shift(), None)) + ); + + assert!(Binding::from_str("Caps_Lock").unwrap().is_set()); + + // A single modifier other than Super is not a complete binding. + assert!(Binding::from_str("Shift").is_err()); // Can't have multiple non-modifier keys. - assert!(matches!(Binding::from_str("Super+Up+Down"), Err(_))); + assert!(Binding::from_str("Super+Up+Down").is_err()); // At least one key is required. - assert!(matches!(Binding::from_str(" "), Err(_))); + assert!(Binding::from_str(" ").is_err()); } #[test] @@ -290,10 +310,21 @@ mod tests { ); // Can't have multiple non-modifier keys. - assert!(matches!(Binding::from_str("Super+Up+Down"), Err(_))); + assert!(Binding::from_str("Super+Up+Down").is_err()); // At least one key is required. - assert!(matches!(Binding::from_str(" "), Err(_))); + assert!(Binding::from_str(" ").is_err()); + } + + #[test] + fn keycode_is_part_of_binding_identity() { + let caps_lock = + Binding::new_keycode(Modifiers::new(), Some(xkbcommon::xkb::Keycode::new(66))); + let num_lock = + Binding::new_keycode(Modifiers::new(), Some(xkbcommon::xkb::Keycode::new(77))); + + assert_eq!(caps_lock.keycode, Some(66)); + assert_ne!(caps_lock, num_lock); } } @@ -309,21 +340,6 @@ pub fn is_forbidden_unmodified_keysym(key: xkb::Keysym) -> bool { 0xff89 | // KP_Tab 0xff0d | // Return 0xff8d | // KP_Enter - 0xff7e | // Mode_switch - 0xff14 | // Scroll_Lock - 0xff15 | // Sys_Req - 0xff20 | // Multi_key (Compose) - 0xff7f | // Num_Lock - 0xffe5 | // Caps_Lock - 0xfe01 | // ISO_Lock - 0xfe08 | // ISO_Next_Group - 0xfe0a | // ISO_Prev_Group - 0xfe0c | // ISO_First_Group - 0xfe0e | // ISO_Last_Group - 0xfed0..=0xfed2 | // First/Prev/Next_Virtual_Screen - 0xfed4 | // Last_Virtual_Screen - 0xfed5 | // Terminate_Server - 0xfe7a | // AudibleBell_Enable 0x04a1..=0x04df | // Kana (Japanese) 0x05ac..=0x05f2 | // Arabic 0x06a1..=0x06ff | // Cyrillic diff --git a/config/src/shortcuts/mod.rs b/config/src/shortcuts/mod.rs index 4cd9f9f3..8a088df4 100644 --- a/config/src/shortcuts/mod.rs +++ b/config/src/shortcuts/mod.rs @@ -15,8 +15,7 @@ pub mod sym; use cosmic_config::cosmic_config_derive::CosmicConfigEntry; use cosmic_config::{ConfigGet, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use xkbcommon::xkb; pub const ID: &str = "com.system76.CosmicSettings.Shortcuts"; @@ -170,9 +169,7 @@ impl Shortcuts { keycode: None, key: Some(key), }; - if !self.0.contains_key(&pattern) { - self.0.insert(pattern, action.clone()); - } + self.0.entry(pattern).or_insert(action.clone()); } for key in keycodes { let pattern = Binding { @@ -181,9 +178,7 @@ impl Shortcuts { keycode: Some(key.raw()), key: None, }; - if !self.0.contains_key(&pattern) { - self.0.insert(pattern, action.clone()); - } + self.0.entry(pattern).or_insert(action.clone()); } } } diff --git a/config/src/shortcuts/modifier.rs b/config/src/shortcuts/modifier.rs index 82c0e442..91de5f2e 100644 --- a/config/src/shortcuts/modifier.rs +++ b/config/src/shortcuts/modifier.rs @@ -70,15 +70,15 @@ impl std::ops::BitOr for Modifier { } } -impl Into for Modifier { - fn into(self) -> Modifiers { +impl From for Modifiers { + fn from(src: Modifier) -> Self { let mut modifiers = Modifiers { ctrl: false, alt: false, shift: false, logo: false, }; - modifiers += self; + modifiers += src; modifiers } } diff --git a/config/src/window_rules/mod.rs b/config/src/window_rules/mod.rs index 30cd9085..496e09e4 100644 --- a/config/src/window_rules/mod.rs +++ b/config/src/window_rules/mod.rs @@ -77,13 +77,12 @@ pub fn tiling_exceptions(context: &cosmic_config::Config) -> Vec>("tiling_exception_custom") .unwrap_or_else(|why| { - if why.is_err() { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() != std::io::ErrorKind::NotFound { - tracing::error!("tiling exceptions custom config error: {why}"); - return Vec::new(); - } - } + if why.is_err() + && let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() != std::io::ErrorKind::NotFound + { + tracing::error!("tiling exceptions custom config error: {why}"); + return Vec::new(); } tracing::debug!("tiling exceptions custom config not present: {why}"); Vec::new() diff --git a/cosmic-pipewire/Cargo.toml b/cosmic-pipewire/Cargo.toml index 00df0eb0..cc2b2655 100644 --- a/cosmic-pipewire/Cargo.toml +++ b/cosmic-pipewire/Cargo.toml @@ -15,5 +15,3 @@ pipewire = "0.10.0" serde = { version = "1.0.228", features = ["derive"]} serde_json = "1.0.149" tracing = "0.1.44" - -[features] diff --git a/cosmic-pipewire/src/lib.rs b/cosmic-pipewire/src/lib.rs index 7c779bc3..c0ec15a5 100644 --- a/cosmic-pipewire/src/lib.rs +++ b/cosmic-pipewire/src/lib.rs @@ -14,27 +14,26 @@ mod profile; pub use profile::{Profile, ProfileClass}; mod route; -pub use route::PortType; -pub use route::{Route, RouteProps}; +pub use route::{PortType, Route, RouteProps}; mod spa_utils; pub use spa_utils::Channel; -use libspa::{ - param::{ParamType, format::FormatProperties}, - pod::{self, Pod, serialize::PodSerializer}, - utils::SpaTypes, -}; -use pipewire::{ - device::{DeviceChangeMask, DeviceListener}, - main_loop::MainLoopWeak, - metadata::MetadataListener, - node::NodeListener, - proxy::{ProxyListener, ProxyT}, - types::ObjectType, -}; +use libspa::param::ParamType; +use libspa::param::format::FormatProperties; +use libspa::pod::serialize::PodSerializer; +use libspa::pod::{self, Pod}; +use libspa::utils::SpaTypes; +use pipewire::device::{DeviceChangeMask, DeviceListener}; +use pipewire::main_loop::MainLoopWeak; +use pipewire::metadata::MetadataListener; +use pipewire::node::NodeListener; +use pipewire::proxy::{ProxyListener, ProxyT}; +use pipewire::registry::{GlobalObject, Registry}; +use pipewire::types::ObjectType; +use std::cell::RefCell; +use std::rc::Rc; use std::time::Duration; -use std::{cell::RefCell, rc::Rc}; pub type NodeId = u32; pub type RouteId = u32; @@ -173,323 +172,305 @@ fn run_service( }; match obj.type_ { - ObjectType::Device => { - let Ok(device) = registry.bind::(obj) else { - return; - }; + ObjectType::Device => bind_device(®istry, obj, state.clone()), + ObjectType::Node => bind_node(®istry, obj, state.clone()), + ObjectType::Metadata => bind_metadata(®istry, obj, state.clone()), + _ => {} + } + }) + .register(); - device.subscribe_params(&[ - ParamType::EnumProfile, - ParamType::Profile, - ParamType::EnumRoute, - ParamType::Route, - ]); - - let pw_id = device.upcast_ref().id(); - - let listener = device - .add_listener_local() - .info({ - let state = Rc::downgrade(&state); - move |info| { - let change_mask = info.change_mask(); - if change_mask == DeviceChangeMask::PARAMS { - if let Some(state) = state.upgrade() { - let state = state.borrow(); - let Some((_device_id, device, ..)) = - state.proxies.devices.get(pw_id) - else { - return; - }; - - device.enum_params( - 1, - Some(ParamType::EnumRoute), - 0, - u32::MAX, - ); - device.enum_params(1, Some(ParamType::Route), 0, u32::MAX); - device.enum_params( - 1, - Some(ParamType::EnumProfile), - 0, - u32::MAX, - ); - device.enum_params( - 1, - Some(ParamType::Profile), - 0, - u32::MAX, - ); - } - - return; - } - - if let Some(device) = Device::from_device(info) { - if let Some(state) = state.upgrade() { - state.borrow_mut().add_device(pw_id, device); - } - } - } - }) - .param({ - let state = Rc::downgrade(&state); - move |_seq, param_type, index, _next, param| { - let Some(pod) = param else { - return; - }; - - let Some(state) = state.upgrade() else { - return; - }; - - let Some(&(device_id, ..)) = - state.borrow().proxies.devices.get(pw_id) - else { - return; - }; - - match param_type { - ParamType::EnumProfile => { - if let Some(profile) = Profile::from_pod(pod) { - state - .borrow_mut() - .add_profile(device_id, index, profile); - } - } - - ParamType::EnumRoute => { - if let Some(route) = Route::from_pod(pod) { - state.borrow_mut().add_route(device_id, index, route); - } - } - - ParamType::Profile => { - if let Some(profile) = Profile::from_pod(pod) { - state.borrow_mut().active_profile(device_id, profile); - } - } - - ParamType::Route => { - if let Some(route) = Route::from_pod(pod) { - state - .borrow_mut() - .active_route(device_id, index, route); - } - } - - _ => (), - } - } - }) - .register(); - - let proxy = device.upcast_ref(); - - let remove_listener = proxy - .add_listener_local() - .removed({ - let state = Rc::downgrade(&state); - move || { - if let Some(state) = state.upgrade() { - state.borrow_mut().remove_device(pw_id); - } - } - }) - .register(); - - state - .borrow_mut() - .proxies - .devices - .insert(pw_id, (0, device, listener, remove_listener)); + main_loop.run(); + Ok(()) +} + +fn bind_device

(registry: &Registry, obj: &GlobalObject

, state: Rc>) +where + P: AsRef, +{ + let Ok(device) = registry.bind::(obj) else { + return; + }; + + device.subscribe_params(&[ + ParamType::EnumProfile, + ParamType::Profile, + ParamType::EnumRoute, + ParamType::Route, + ]); + + let pw_id = device.upcast_ref().id(); + + let listener = device + .add_listener_local() + .info({ + let state = Rc::downgrade(&state); + move |info| { + let change_mask = info.change_mask(); + if change_mask == DeviceChangeMask::PARAMS { + if let Some(state) = state.upgrade() { + let state = state.borrow(); + let Some((_device_id, device, ..)) = state.proxies.devices.get(pw_id) + else { + return; + }; + + device.enum_params(1, Some(ParamType::EnumRoute), 0, u32::MAX); + device.enum_params(1, Some(ParamType::Route), 0, u32::MAX); + device.enum_params(1, Some(ParamType::EnumProfile), 0, u32::MAX); + device.enum_params(1, Some(ParamType::Profile), 0, u32::MAX); + } + + return; } - ObjectType::Node => { - let Ok(node) = registry.bind::(obj) else { - return; - }; + if let Some(device) = Device::from_device(info) + && let Some(state) = state.upgrade() + { + state.borrow_mut().add_device(pw_id, device); + } + } + }) + .param({ + let state = Rc::downgrade(&state); + move |_seq, param_type, index, _next, param| { + let Some(pod) = param else { + return; + }; + + let Some(state) = state.upgrade() else { + return; + }; + + let Some(&(device_id, ..)) = state.borrow().proxies.devices.get(pw_id) else { + return; + }; + + match param_type { + ParamType::EnumProfile => { + if let Some(profile) = Profile::from_pod(pod) { + state.borrow_mut().add_profile(device_id, index, profile); + } + } - node.subscribe_params(&[ParamType::Props]); + ParamType::EnumRoute => { + if let Some(route) = Route::from_pod(pod) { + state.borrow_mut().add_route(device_id, index, route); + } + } - let id = node.upcast_ref().id(); + ParamType::Profile => { + if let Some(profile) = Profile::from_pod(pod) { + state.borrow_mut().active_profile(device_id, profile); + } + } - let listener = node - .add_listener_local() - .info({ - let state = Rc::downgrade(&state); - move |info| { - if let Some(node) = Node::from_node(info) - && let Some(state) = state.upgrade() - { - state.borrow_mut().add_node(id, node); - } - } - }) - .param({ - let state = Rc::downgrade(&state); - move |_seq, param_type, _index, _next, param| { - let Some(pod) = param else { - return; - }; - - let Some(state) = state.upgrade() else { - return; - }; - - let Some(&(node_id, ..)) = state.borrow().proxies.nodes.get(id) - else { - return; - }; - - match param_type { - ParamType::Props => { - if let Some(props) = NodeProps::from_pod(pod) { - state.borrow_mut().set_node_props(node_id, props); - } - } - - _ => (), - } - } - }) - .register(); - - let remove_listener = node - .upcast_ref() - .add_listener_local() - .removed({ - let state = Rc::downgrade(&state); - move || { - if let Some(state) = state.upgrade() { - state.borrow_mut().remove_node(id); - } - } - }) - .register(); - - state - .borrow_mut() - .proxies - .nodes - .insert(id, (0, node, listener, remove_listener)); + ParamType::Route => { + if let Some(route) = Route::from_pod(pod) { + state.borrow_mut().active_route(device_id, index, route); + } + } + + _ => (), } + } + }) + .register(); - ObjectType::Metadata => { - let Some(props) = obj.props else { - return; - }; + let proxy = device.upcast_ref(); - let Some(name) = props.get("metadata.name").map(String::from) else { - return; - }; + let remove_listener = proxy + .add_listener_local() + .removed({ + let state = Rc::downgrade(&state); + move || { + if let Some(state) = state.upgrade() { + state.borrow_mut().remove_device(pw_id); + } + } + }) + .register(); + + state + .borrow_mut() + .proxies + .devices + .insert(pw_id, (0, device, listener, remove_listener)); +} - let Ok(metadata) = registry.bind::(obj) else { - return; +fn bind_node

(registry: &Registry, obj: &GlobalObject

, state: Rc>) +where + P: AsRef, +{ + let Ok(node) = registry.bind::(obj) else { + return; + }; + + node.subscribe_params(&[ParamType::Props]); + + let id = node.upcast_ref().id(); + + let listener = node + .add_listener_local() + .info({ + let state = Rc::downgrade(&state); + move |info| { + if let Some(node) = Node::from_node(info) + && let Some(state) = state.upgrade() + { + state.borrow_mut().add_node(id, node); + } + } + }) + .param({ + let state = Rc::downgrade(&state); + move |_seq, param_type, _index, _next, param| { + let Some(pod) = param else { + return; + }; + + let Some(state) = state.upgrade() else { + return; + }; + + let Some(&(node_id, ..)) = state.borrow().proxies.nodes.get(id) else { + return; + }; + + match param_type { + ParamType::Props => { + if let Some(props) = NodeProps::from_pod(pod) { + state.borrow_mut().set_node_props(node_id, props); + } + } + + _ => (), + } + } + }) + .register(); + + let remove_listener = node + .upcast_ref() + .add_listener_local() + .removed({ + let state = Rc::downgrade(&state); + move || { + if let Some(state) = state.upgrade() { + state.borrow_mut().remove_node(id); + } + } + }) + .register(); + + state + .borrow_mut() + .proxies + .nodes + .insert(id, (0, node, listener, remove_listener)); +} + +fn bind_metadata

(registry: &Registry, obj: &GlobalObject

, state: Rc>) +where + P: AsRef, +{ + let Some(props) = &obj.props else { + return; + }; + + let Some(name) = props.as_ref().get("metadata.name").map(String::from) else { + return; + }; + + let Ok(metadata) = registry.bind::(obj) else { + return; + }; + + let id = metadata.upcast_ref().id(); + + let listener = metadata.add_listener_local(); + let listener = match name.as_str() { + "default" => listener + .property({ + let state = Rc::downgrade(&state); + move |_subject, key, _type, value| { + let Some((key, value)) = key.zip(value) else { + return 0; }; - let id = metadata.upcast_ref().id(); - - let listener = metadata.add_listener_local(); - let listener = match name.as_str() { - "default" => listener - .property({ - let state = Rc::downgrade(&state); - move |_subject, key, _type, value| { - let Some((key, value)) = key.zip(value) else { - return 0; - }; - - match key { - "default.audio.sink" => { - tracing::info!(target:"audio-backend", value, "default.audio.sink"); - if let Ok(value) = - serde_json::de::from_str::(value) - && let Some(state) = state.upgrade() - { - state - .borrow_mut() - .default_sink(value.name.to_owned()) - } - } - - "default.audio.source" => { - tracing::info!(target:"audio-backend", value, "default.audio.source"); - if let Ok(value) = - serde_json::de::from_str::(value) - && let Some(state) = state.upgrade() - { - state - .borrow_mut() - .default_source(value.name.to_owned()) - } - } - - _ => (), - } - - 0 - } - }) - .register(), - - "sm-settings" => listener - .property({ - let state = Rc::downgrade(&state); - move |_subject, key, _type, value| { - let Some((key, value)) = key.zip(value) else { - return 0; - }; - - match key { - "node.features.audio.mono" => { - if let Ok(value) = - serde_json::de::from_str::(value) - { - if let Some(state) = state.upgrade() { - state.borrow_mut().mono_audio(value.value); - } - } - } - - _ => (), - } - - 0 - } - }) - .register(), - - _ => listener.register(), + match key { + "default.audio.sink" => { + tracing::info!(target:"audio-backend", value, "default.audio.sink"); + if let Ok(value) = serde_json::de::from_str::(value) + && let Some(state) = state.upgrade() + { + state.borrow_mut().default_sink(value.name.to_owned()) + } + } + + "default.audio.source" => { + tracing::info!(target:"audio-backend", value, "default.audio.source"); + if let Ok(value) = serde_json::de::from_str::(value) + && let Some(state) = state.upgrade() + { + state.borrow_mut().default_source(value.name.to_owned()) + } + } + + _ => (), + } + + 0 + } + }) + .register(), + + "sm-settings" => listener + .property({ + let state = Rc::downgrade(&state); + move |_subject, key, _type, value| { + let Some((key, value)) = key.zip(value) else { + return 0; }; - let remove_listener = metadata - .upcast_ref() - .add_listener_local() - .removed({ - let state = Rc::downgrade(&state); - move || { - if let Some(state) = state.upgrade() { - state.borrow_mut().remove_metadata(id); - } + match key { + "node.features.audio.mono" => { + if let Ok(value) = serde_json::de::from_str::(value) + && let Some(state) = state.upgrade() + { + state.borrow_mut().mono_audio(value.value); } - }) - .register(); - - state - .borrow_mut() - .proxies - .metadata - .insert(id, (name, metadata, listener, remove_listener)); + } + + _ => (), + } + + 0 } - _ => {} - }; + }) + .register(), + + _ => listener.register(), + }; + + let remove_listener = metadata + .upcast_ref() + .add_listener_local() + .removed({ + let state = Rc::downgrade(&state); + move || { + if let Some(state) = state.upgrade() { + state.borrow_mut().remove_metadata(id); + } + } }) .register(); - main_loop.run(); - Ok(()) + state + .borrow_mut() + .proxies + .metadata + .insert(id, (name, metadata, listener, remove_listener)); } /// Response from pipewire diff --git a/cosmic-pipewire/src/node.rs b/cosmic-pipewire/src/node.rs index 6e6db962..0b4b2eaf 100644 --- a/cosmic-pipewire/src/node.rs +++ b/cosmic-pipewire/src/node.rs @@ -1,9 +1,11 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 -use crate::{Channel, spa_utils::array_from_pod}; +use crate::Channel; +use crate::spa_utils::array_from_pod; use float_cmp::{ApproxEq, F32Margin}; -use libspa::{pod::Pod, utils::Id}; +use libspa::pod::Pod; +use libspa::utils::Id; use pipewire::node::{NodeInfoRef, NodeState}; use std::ffi::c_float; diff --git a/cosmic-pipewire/src/profile.rs b/cosmic-pipewire/src/profile.rs index 69c5125e..4d3daf0f 100644 --- a/cosmic-pipewire/src/profile.rs +++ b/cosmic-pipewire/src/profile.rs @@ -3,9 +3,8 @@ use std::ffi::c_int; -use crate::{ - Availability, spa_utils::{array_from_pod, string_from_pod} -}; +use crate::Availability; +use crate::spa_utils::{array_from_pod, string_from_pod}; use libspa::pod::Pod; #[derive(Clone, Debug, Default)] @@ -66,19 +65,18 @@ impl Profile { fields.next(); while let Some((key, value)) = fields.next().zip(fields.next()) { - if let Some("card.profile.devices") = string_from_pod(key).as_deref() { - if let Some(card_profile_devices) = + if let Some("card.profile.devices") = string_from_pod(key).as_deref() + && let Some(card_profile_devices) = unsafe { array_from_pod::(value) } - { - classes.push(match class_name.as_str() { - "Audio/Sink" => ProfileClass::AudioSink { - card_profile_devices, - }, - _ => ProfileClass::AudioSource { - card_profile_devices, - }, - }); - } + { + classes.push(match class_name.as_str() { + "Audio/Sink" => ProfileClass::AudioSink { + card_profile_devices, + }, + _ => ProfileClass::AudioSource { + card_profile_devices, + }, + }); } } } diff --git a/cosmic-pipewire/src/route.rs b/cosmic-pipewire/src/route.rs index 48af93e5..787c3888 100644 --- a/cosmic-pipewire/src/route.rs +++ b/cosmic-pipewire/src/route.rs @@ -3,10 +3,10 @@ use std::ffi::{c_float, c_int}; -use crate::{ - Availability, Channel, Direction, spa_utils::{array_from_pod, string_from_pod} -}; -use libspa::{pod::Pod, utils::Id}; +use crate::spa_utils::{array_from_pod, string_from_pod}; +use crate::{Availability, Channel, Direction}; +use libspa::pod::Pod; +use libspa::utils::Id; #[derive(Clone, Debug, Default)] pub struct Route { @@ -102,10 +102,10 @@ impl Route { } } Some("card.profile.port") => { - if let Some(value) = string_from_pod(value) { - if let Ok(value) = value.parse::() { - this.card_profile_port = value; - } + if let Some(value) = string_from_pod(value) + && let Ok(value) = value.parse::() + { + this.card_profile_port = value; } } Some("device.icon-name") => { diff --git a/cosmic-pipewire/src/spa_utils.rs b/cosmic-pipewire/src/spa_utils.rs index 7e3e859d..0593dfe1 100644 --- a/cosmic-pipewire/src/spa_utils.rs +++ b/cosmic-pipewire/src/spa_utils.rs @@ -14,10 +14,8 @@ pub fn string_from_pod(pod: &Pod) -> Option { unsafe { // SAFETY: Pod is checked to be a string beforehand - if libspa_sys::spa_pod_get_string(pod.as_raw_ptr(), &mut cstr) == 0 { - if !cstr.is_null() { - return Some(String::from_utf8_lossy(CStr::from_ptr(cstr).to_bytes()).into_owned()); - } + if libspa_sys::spa_pod_get_string(pod.as_raw_ptr(), &mut cstr) == 0 && !cstr.is_null() { + return Some(String::from_utf8_lossy(CStr::from_ptr(cstr).to_bytes()).into_owned()); } } diff --git a/cosmic-settings-daemon-config/src/greeter.rs b/cosmic-settings-daemon-config/src/greeter.rs index 9dcaa422..3f13ab3a 100644 --- a/cosmic-settings-daemon-config/src/greeter.rs +++ b/cosmic-settings-daemon-config/src/greeter.rs @@ -1,7 +1,9 @@ -use cosmic_config::{Config, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}; +use cosmic_config::cosmic_config_derive::CosmicConfigEntry; +use cosmic_config::{Config, CosmicConfigEntry}; use cosmic_theme::{CosmicPalette, Theme, ThemeBuilder}; use serde::{Deserialize, Serialize}; -use std::{option_env, path::PathBuf}; +use std::option_env; +use std::path::PathBuf; pub const GREETER_STATE: Option<&'static str> = option_env!("GREETER_STATE"); diff --git a/cosmic-settings-daemon-config/src/lib.rs b/cosmic-settings-daemon-config/src/lib.rs index 70af852a..4519a02e 100644 --- a/cosmic-settings-daemon-config/src/lib.rs +++ b/cosmic-settings-daemon-config/src/lib.rs @@ -1,4 +1,5 @@ -use cosmic_config::{Config, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}; +use cosmic_config::cosmic_config_derive::CosmicConfigEntry; +use cosmic_config::{Config, CosmicConfigEntry}; use serde::{Deserialize, Serialize}; #[cfg(feature = "greeter")] diff --git a/debian/control b/debian/control index 16f83260..c2b829c8 100644 --- a/debian/control +++ b/debian/control @@ -22,8 +22,6 @@ Architecture: amd64 arm64 Depends: acpid, adw-gtk3, - qt5ct, - qt6ct, pop-sound-theme, pulseaudio-utils, ${misc:Depends}, @@ -31,4 +29,6 @@ Depends: Recommends: breeze-icon-theme, playerctl, + qt5ct, + qt6ct, Description: Cosmic settings daemon diff --git a/geonames/src/main.rs b/geonames/src/main.rs index 880f8aa2..b870e98b 100644 --- a/geonames/src/main.rs +++ b/geonames/src/main.rs @@ -1,7 +1,7 @@ use geonames::GeoPosition; -use std::{ - collections::BTreeMap, fs, io::{self, BufRead} -}; +use std::collections::BTreeMap; +use std::fs; +use std::io::{self, BufRead}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -29,11 +29,11 @@ async fn main() -> Result<(), Box> { let line = line_res?; let mut parts = line.split('\t'); let Some(_id) = parts.next() else { continue }; - let Some(name) = parts.next() else { continue }; + let Some(_name) = parts.next() else { continue }; let Some(_ascii_name) = parts.next() else { continue; }; - let Some(alternate_names) = parts.next() else { + let Some(_alternate_names) = parts.next() else { continue; }; let Some(latitude) = parts.next() else { @@ -95,9 +95,7 @@ async fn main() -> Result<(), Box> { let mut timezone_positions = BTreeMap::new(); for (_, timezone, geoposition) in sorted_data { - if !timezone_positions.contains_key(&timezone) { - timezone_positions.insert(timezone, geoposition); - } + timezone_positions.entry(timezone).or_insert(geoposition); } for (timezone, geoposition) in &timezone_positions { diff --git a/rustfmt.toml b/rustfmt.toml index 7ad5ba74..c1578aaf 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1 @@ -imports_layout = "Horizontal" +imports_granularity = "Module" diff --git a/src/battery.rs b/src/battery.rs index d53560ee..54230dff 100644 --- a/src/battery.rs +++ b/src/battery.rs @@ -1,9 +1,9 @@ use acpid_plug::AcPlugEvents; use notify_rust::Notification; -use std::time::Instant; -use std::{path::Path, time::Duration}; -use tokio::sync::mpsc::Sender; -use tokio::sync::mpsc::{Receiver, error::TryRecvError}; +use std::path::Path; +use std::time::{Duration, Instant}; +use tokio::sync::mpsc::error::TryRecvError; +use tokio::sync::mpsc::{Receiver, Sender}; use tokio_stream::StreamExt; use upower_dbus::BatteryLevel; use zbus::Connection; diff --git a/src/brightness_device.rs b/src/brightness_device.rs index 41873346..16830fbd 100644 --- a/src/brightness_device.rs +++ b/src/brightness_device.rs @@ -1,9 +1,10 @@ use ddc_hi::{Ddc, Display}; +use std::error::Error; +use std::io; +use std::str::FromStr; use std::sync::{Arc, Mutex}; -use std::time::Instant; -use std::{error::Error, io, str::FromStr, time::Duration}; -use tokio::fs; -use tokio::time; +use std::time::Duration; +use tokio::{fs, time}; use crate::LogindSessionProxy; @@ -34,33 +35,47 @@ impl BrightnessDevice { let v: Arc<(Mutex>, std::sync::Condvar)> = Arc::new((Mutex::new(Some(100u16)), std::sync::Condvar::new())); let brightness_dcc = v.clone(); - let mut displays = Display::enumerate(); - let displays_empty = displays.is_empty(); + let displays_empty = Display::enumerate().is_empty(); std::thread::spawn(move || { + // How long cached DDC display handles are kept after the last + // brightness change. Each handle is an open fd on a /dev/i2c-* + // adapter; holding them indefinitely blocks the kernel's DP-MST + // teardown on monitor unplug, permanently wedging drm_dp_mst_wq + // (https://github.com/pop-os/cosmic-settings-daemon/issues/165). + // The cache still avoids re-enumeration during a burst of + // brightness key repeats. + const IDLE_TIMEOUT: Duration = Duration::from_secs(2); + + let mut displays: Vec = Vec::new(); let mut cur = 100; - let mut last_change = Instant::now(); - loop { - let v = v.clone(); - let mut guard = v.0.lock().unwrap(); - while guard.is_some_and(|v| v == cur) { - guard = v.1.wait(guard).unwrap(); - } - - let Some(brightness) = *guard else { - break; + 'outer: loop { + let brightness = { + let mut guard = v.0.lock().unwrap(); + loop { + match *guard { + None => break 'outer, + Some(b) if b != cur => break b, + Some(_) => {} + } + if displays.is_empty() { + guard = v.1.wait(guard).unwrap(); + } else { + let (g, res) = v.1.wait_timeout(guard, IDLE_TIMEOUT).unwrap(); + guard = g; + if res.timed_out() { + // Idle: release the i2c fds so unplugged + // displays can be torn down by the kernel. + displays.clear(); + } + } + } }; - drop(guard); cur = brightness; - let now = Instant::now(); - if now.checked_duration_since(last_change).unwrap_or_default() - > Duration::from_secs(10) - { - // pull in latest case anything has changed... + if displays.is_empty() { displays = Display::enumerate(); } - last_change = now; for display in &mut displays { if display.update_capabilities().is_err() { continue; @@ -82,7 +97,7 @@ impl BrightnessDevice { } pub async fn new(subsystem: &'static str, sysname: String) -> io::Result { - let path = format!("/sys/class/{}/{}/max_brightness", subsystem, &sysname); + let path = format!("/sys/class/{}/{}/max_brightness", subsystem, sysname); let value = fs::read_to_string(&path).await?; let max_brightness = u32::from_str(value.trim()).map_err(invalid_data)?; let mut external = Self::external(); @@ -105,10 +120,10 @@ impl BrightnessDevice { if d.update_capabilities().is_err() { continue; } - if let Some(feature) = d.info.mccs_database.get(BRIGHTNESS) { - if let Ok(value) = d.handle.get_vcp_feature(feature.code) { - return Ok(value.value() as u32); - } + if let Some(feature) = d.info.mccs_database.get(BRIGHTNESS) + && let Ok(value) = d.handle.get_vcp_feature(feature.code) + { + return Ok(value.value() as u32); } } } @@ -216,10 +231,7 @@ impl BrightnessDevice { ) -> zbus::Result<()> { // Never set 0 on LCD backlights unless the device is clearly coarse (<=20 levels). // Keyboard LEDs and other subsystems can still use 0. - let clamped = value.clamp( - self.min_brightness(), - self.max_brightness.unwrap_or(100) as u32, - ); + let clamped = value.clamp(self.min_brightness(), self.max_brightness.unwrap_or(100)); let b_dcc = (clamped * 100 / self.max_brightness.unwrap_or(100)) as u16; { diff --git a/src/greeter.rs b/src/greeter.rs index 547dedba..b03c26e4 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -17,10 +17,10 @@ pub fn sync_with_greeter() -> anyhow::Result<()> { } }; - if let Some(hc) = state.high_contrast { - if let Err(err) = greeter::apply_hc_theme(hc) { - log::error!("Failed to apply high contrast changes from the greeter: {err:?}"); - } + if let Some(hc) = state.high_contrast + && let Err(err) = greeter::apply_hc_theme(hc) + { + log::error!("Failed to apply high contrast changes from the greeter: {err:?}"); } if let Some(screen_reader) = state.screen_reader { @@ -34,11 +34,11 @@ pub fn sync_with_greeter() -> anyhow::Result<()> { continue; } }; - if let Ok(proxy) = StatusProxy::new(&conn).await { - if let Err(err) = proxy.set_screen_reader_enabled(screen_reader).await { - log::error!("Failed to apply screen reader status. {err:?}"); - continue; - } + if let Ok(proxy) = StatusProxy::new(&conn).await + && let Err(err) = proxy.set_screen_reader_enabled(screen_reader).await + { + log::error!("Failed to apply screen reader status. {err:?}"); + continue; } break; } diff --git a/src/locale.rs b/src/locale.rs index 70b3f386..6893599b 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -6,8 +6,8 @@ use cosmic_config::{ConfigGet, ConfigSet}; use tokio::sync::mpsc::Receiver; use tokio_stream::StreamExt; -pub const COSMIC_COMP_ID: &'static str = "com.system76.CosmicComp"; -pub const COSMIC_COMP_XDG_KEY: &'static str = "xkb_config"; +pub const COSMIC_COMP_ID: &str = "com.system76.CosmicComp"; +pub const COSMIC_COMP_XDG_KEY: &str = "xkb_config"; pub async fn sync_locale1(mut rx: Receiver<()>) -> anyhow::Result<()> { let conn = zbus::Connection::system().await?; diff --git a/src/location.rs b/src/location.rs index 06a70af4..01054431 100644 --- a/src/location.rs +++ b/src/location.rs @@ -1,17 +1,21 @@ -use std::{collections::BTreeMap, io, path::Path, rc::Rc, time::Duration}; +use std::collections::BTreeMap; +use std::io; +use std::path::Path; +use std::rc::Rc; +use std::time::Duration; use futures::{Stream, StreamExt}; pub use geonames::GeoPosition; use notify::{PollWatcher, RecursiveMode, Watcher}; -static GEODATA: &'static [u8] = include_bytes!("../data/timezone-geodata.bitcode-v0-6"); +static GEODATA: &[u8] = include_bytes!("../data/timezone-geodata.bitcode-v0-6"); /// Decodes the embedded geodata containing the largest cities nearest each timezone. pub fn decode_geodata() -> BTreeMap { match geonames::bitcode::decode(GEODATA) { Ok(ok) => ok, Err(err) => { - log::error!("failed to decode timezone geodata: {}", err.to_string()); + log::error!("failed to decode timezone geodata: {}", err); BTreeMap::new() } } diff --git a/src/main.rs b/src/main.rs index 397cf227..0f5a168a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,33 +5,29 @@ use brightness_device::BrightnessDevice; use cosmic_config::ConfigGet; use futures::lock::Mutex; use logind_session::LogindSessionProxy; -use notify::{EventKind, Watcher, event::ModifyKind}; +use notify::event::ModifyKind; +use notify::{EventKind, Watcher}; +use std::collections::{HashMap, HashSet}; +use std::io; use std::os::unix::process::CommandExt; +use std::path::PathBuf; use std::process::ExitCode; -use std::sync::atomic::{AtomicBool, AtomicU64}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::time::Duration; -use std::{ - collections::{HashMap, HashSet}, - io, - path::PathBuf, - sync::{Arc, atomic::Ordering}, -}; use theme::watch_theme; +use tokio::io::Interest; +use tokio::io::unix::AsyncFd; use tokio::signal::unix::SignalKind; -use tokio::{ - io::{Interest, unix::AsyncFd}, - sync::RwLock, - task, -}; +use tokio::sync::RwLock; +use tokio::task; use tokio_stream::StreamExt; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; -use zbus::{ - Connection, MatchRule, MessageStream, - names::{MemberName, UniqueName, WellKnownName}, - object_server::SignalEmitter, - zvariant::ObjectPath, -}; +use zbus::names::{MemberName, UniqueName, WellKnownName}; +use zbus::object_server::SignalEmitter; +use zbus::zvariant::ObjectPath; +use zbus::{Connection, MatchRule, MessageStream}; mod battery; mod brightness_device; mod greeter; @@ -192,14 +188,14 @@ impl SettingsDaemon { #[zbus(property)] async fn max_display_brightness(&self) -> i32 { - self.display_brightness_device.max_brightness() as i32 + self.display_brightness_device.max_brightness() } #[zbus(property)] async fn set_display_brightness(&self, value: i32) { if let Some(logind_session) = self.logind_session.as_ref() { // Align with slider behavior and device clamp: floor at 1 for backlight - let max = self.display_brightness_device.max_brightness() as i32; + let max = self.display_brightness_device.max_brightness(); let min = self.display_brightness_device.min_brightness() as i32; let clamped = value.clamp(min, max); @@ -262,7 +258,6 @@ impl SettingsDaemon { log::error!( "Failed to toggle screen reader. Could not apply current state. {err:?}" ); - return; } } else { log::error!("Failed to toggle screen reader.") @@ -275,7 +270,7 @@ impl SettingsDaemon { .lock() .await .audio_server - .sink_volume_raise(5) + .sink_volume_raise() .await { log::error!("Failed to raise volume: {}", why); @@ -288,7 +283,7 @@ impl SettingsDaemon { .lock() .await .audio_server - .sink_volume_lower(5) + .sink_volume_lower() .await { log::error!("Failed to lower volume: {}", why); @@ -374,7 +369,7 @@ async fn choose_best_backlight(udev_devices: &HashMap) -> } } - best_backlight.unwrap_or_else(|| BrightnessDevice::external()) + best_backlight.unwrap_or_else(BrightnessDevice::external) } async fn backlight_monitor_task( @@ -560,13 +555,11 @@ async fn main() -> ExitCode { .and_then(|prefix| path.strip_prefix(prefix).ok()) { (path, false) - } else if let Some(path) = xdg_state_clone - .as_ref() - .and_then(|prefix| path.strip_prefix(prefix).ok()) - { - (path, true) } else { - return None; + let path = xdg_state_clone + .as_ref() + .and_then(|prefix| path.strip_prefix(prefix).ok())?; + (path, true) }; // really only care about keys if path.starts_with(".atomicwrite") { @@ -601,15 +594,15 @@ async fn main() -> ExitCode { }) .expect("Failed to create notify watcher"); - if let Some(xdg_config) = xdg_config { - if let Err(err) = watcher.watch(&xdg_config, notify::RecursiveMode::Recursive) { - log::error!("Failed to watch xdg config dir: {}", err); - } + if let Some(xdg_config) = xdg_config + && let Err(err) = watcher.watch(&xdg_config, notify::RecursiveMode::Recursive) + { + log::error!("Failed to watch xdg config dir: {}", err); } - if let Some(xdg_state) = xdg_state { - if let Err(err) = watcher.watch(&xdg_state, notify::RecursiveMode::Recursive) { - log::error!("Failed to watch xdg state dir: {}", err); - } + if let Some(xdg_state) = xdg_state + && let Err(err) = watcher.watch(&xdg_state, notify::RecursiveMode::Recursive) + { + log::error!("Failed to watch xdg state dir: {}", err); } let watched_configs = Arc::new(RwLock::new(HashMap::new())); let watched_states = Arc::new(RwLock::new(HashMap::new())); diff --git a/src/pipewire.rs b/src/pipewire.rs index 4e68e8be..7f5e6355 100644 --- a/src/pipewire.rs +++ b/src/pipewire.rs @@ -26,7 +26,7 @@ pub fn play_sound(theme: &'static str, sound: &'static str) { #[memoize::memoize] fn sound_path(theme: &'static str, sound: &'static str) -> Option { - let entries = WalkDir::new(&["/usr/share/sounds/", theme].concat()) + let entries = WalkDir::new(["/usr/share/sounds/", theme].concat()) .follow_links(true) .into_iter() .filter_map(Result::ok); diff --git a/src/theme.rs b/src/theme.rs index d11b2d0d..534739f7 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -8,7 +8,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::bail; use chrono::{DateTime, Days, Local}; -use cosmic::{config::CosmicTk, theme::CosmicTheme}; +use cosmic::config::CosmicTk; +use cosmic::theme::CosmicTheme; use cosmic_config::CosmicConfigEntry; use cosmic_theme::{Theme, ThemeMode}; @@ -143,11 +144,11 @@ pub async fn watch_theme( Ok(t) => t, Err((errs, t)) => { for why in errs { - if let cosmic_config::Error::GetKey(_, err) = &why { - if err.kind() == std::io::ErrorKind::NotFound { - // No system default config installed; don't error - continue; - } + if let cosmic_config::Error::GetKey(_, err) = &why + && err.kind() == std::io::ErrorKind::NotFound + { + // No system default config installed; don't error + continue; } log::error!("{why}"); } @@ -193,10 +194,8 @@ pub async fn watch_theme( } set_gnome_desktop_interface(theme_mode.is_dark); - } else { - if let Err(err) = Theme::reset_exports() { - log::error!("Failed to reset the cosmic theme exports. {err:?}"); - } + } else if let Err(err) = Theme::reset_exports() { + log::error!("Failed to reset the cosmic theme exports. {err:?}"); } // TODO allow preference for config file instead? @@ -213,7 +212,7 @@ pub async fn watch_theme( let mut sunrise_sunset: Option = None; loop { let sunset_deadline = - if let Some(Some(s)) = theme_mode.auto_switch.then(|| sunrise_sunset.as_mut()) { + if let Some(Some(s)) = theme_mode.auto_switch.then_some(sunrise_sunset.as_mut()) { Some(s.update_next()?) } else { None @@ -249,11 +248,7 @@ pub async fn watch_theme( log::error!("Error updating the theme mode {err:?}"); } - if sunrise_sunset.as_ref().is_some_and(|s| s.is_dark().is_ok_and(|s_is_dark| s_is_dark != theme_mode.is_dark)) { - override_until_next = true; - } else { - override_until_next = false; - } + override_until_next = sunrise_sunset.as_ref().is_some_and(|s| s.is_dark().is_ok_and(|s_is_dark| s_is_dark != theme_mode.is_dark)); if theme_mode.auto_switch && !auto_switch_prev { let Some(is_dark) = sunrise_sunset.as_ref().and_then(|s| s.is_dark().ok()) else { @@ -336,10 +331,8 @@ pub async fn watch_theme( } set_gnome_desktop_interface(theme_mode.is_dark); - } else { - if let Err(err) = Theme::reset_exports() { - log::error!("Failed to reset the cosmic theme exports. {err:?}"); - } + } else if let Err(err) = Theme::reset_exports() { + log::error!("Failed to reset the cosmic theme exports. {err:?}"); } }, ThemeMsg::Theme(is_dark) => { @@ -369,11 +362,10 @@ pub async fn watch_theme( t }, }; - if theme_mode.is_dark == is_dark { - if let Err(err) = t.apply_exports() { + if theme_mode.is_dark == is_dark + && let Err(err) = t.apply_exports() { log::error!("Failed to apply COSMIC theme exports. {err:?}"); } - } set_gnome_desktop_interface(theme_mode.is_dark); } @@ -562,7 +554,7 @@ fn set_gnome_button_layout(show_maximize: bool, show_minimize: bool) { }; let _res = tokio::process::Command::new("gsettings") - .args(&[ + .args([ "set", "org.gnome.desktop.wm.preferences", "button-layout", @@ -586,7 +578,7 @@ fn set_gnome_desktop_interface(is_dark: bool) { tokio::spawn(async { let _res = tokio::process::Command::new("gsettings") - .args(&[ + .args([ "set", "org.gnome.desktop.interface", "color-scheme", @@ -599,7 +591,7 @@ fn set_gnome_desktop_interface(is_dark: bool) { if Path::new(adw_theme_path).exists() { tokio::spawn(async { let _res = tokio::process::Command::new("gsettings") - .args(&["set", "org.gnome.desktop.interface", "gtk-theme", adw_theme]) + .args(["set", "org.gnome.desktop.interface", "gtk-theme", adw_theme]) .status() .await; }); @@ -609,7 +601,7 @@ fn set_gnome_desktop_interface(is_dark: bool) { fn set_gnome_icon_theme(theme: String) { tokio::spawn(async move { let _res = tokio::process::Command::new("gsettings") - .args(&[ + .args([ "set", "org.gnome.desktop.interface", "icon-theme", diff --git a/src/wayland.rs b/src/wayland.rs index 23281801..710ac909 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -2,20 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use calloop_wayland_source::WaylandSource; -use cctk::{ - cosmic_protocols::keyboard_layout::v1::client::zcosmic_keyboard_layout_v1::ZcosmicKeyboardLayoutV1, - keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState}, - sctk::{ - self, - registry::{ProvidesRegistryState, RegistryState}, - seat::{Capability, SeatHandler, SeatState}, - }, - wayland_client::{ - Connection, QueueHandle, delegate_noop, - globals::registry_queue_init, - protocol::{wl_keyboard, wl_seat}, - }, -}; +use cctk::cosmic_protocols::keyboard_layout::v1::client::zcosmic_keyboard_layout_v1::ZcosmicKeyboardLayoutV1; +use cctk::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState}; +use cctk::sctk::registry::{ProvidesRegistryState, RegistryState}; +use cctk::sctk::seat::{Capability, SeatHandler, SeatState}; +use cctk::sctk::{self}; +use cctk::wayland_client::globals::registry_queue_init; +use cctk::wayland_client::protocol::{wl_keyboard, wl_seat}; +use cctk::wayland_client::{Connection, QueueHandle, delegate_noop}; use cosmic_comp_config::XkbConfig; use cosmic_config::ConfigGet; use std::thread; @@ -74,14 +68,14 @@ struct AppData { impl AppData { fn input_source_switch(&mut self) { - if let Some(keyboard) = &self.keyboard { - if let Some(xkb) = xkb_config() { - let count = xkb.layout.split_terminator(',').count(); - - let group = (self.current_layout + 1) % count as u32; - keyboard.keyboard_layout.set_group(group); - self.current_layout = group; - } + if let Some(keyboard) = &self.keyboard + && let Some(xkb) = xkb_config() + { + let count = xkb.layout.split_terminator(',').count(); + + let group = (self.current_layout + 1) % count as u32; + keyboard.keyboard_layout.set_group(group); + self.current_layout = group; } } } diff --git a/varlink-server/src/lib.rs b/varlink-server/src/lib.rs index ec6656fe..504e98c8 100644 --- a/varlink-server/src/lib.rs +++ b/varlink-server/src/lib.rs @@ -10,7 +10,9 @@ use cosmic_settings_audio_core as audio; use cosmic_settings_audio_server as audio_server; -use std::{os::fd::OwnedFd, path::PathBuf, sync::Arc}; +use std::os::fd::OwnedFd; +use std::path::PathBuf; +use std::sync::Arc; use tokio::sync::Mutex; pub async fn init() -> (Daemon, impl Future + 'static + Send) { @@ -140,32 +142,16 @@ where interface = "com.system76.CosmicSettings.Audio", rename = "SinkVolumeLower" )] - pub async fn audio_sink_volume_lower( - &mut self, - step: u32, - ) -> Result { - self.0 - .lock() - .await - .audio_server - .sink_volume_lower(step) - .await + pub async fn audio_sink_volume_lower(&mut self) -> Result { + self.0.lock().await.audio_server.sink_volume_lower().await } #[zlink( interface = "com.system76.CosmicSettings.Audio", rename = "SinkVolumeRaise" )] - pub async fn audio_sink_volume_raise( - &mut self, - step: u32, - ) -> Result { - self.0 - .lock() - .await - .audio_server - .sink_volume_raise(step) - .await + pub async fn audio_sink_volume_raise(&mut self) -> Result { + self.0.lock().await.audio_server.sink_volume_raise().await } #[zlink( @@ -180,32 +166,16 @@ where interface = "com.system76.CosmicSettings.Audio", rename = "SourceVolumeLower" )] - pub async fn audio_source_volume_lower( - &mut self, - step: u32, - ) -> Result { - self.0 - .lock() - .await - .audio_server - .source_volume_lower(step) - .await + pub async fn audio_source_volume_lower(&mut self) -> Result { + self.0.lock().await.audio_server.source_volume_lower().await } #[zlink( interface = "com.system76.CosmicSettings.Audio", rename = "SourceVolumeRaise" )] - pub async fn audio_source_volume_raise( - &mut self, - step: u32, - ) -> Result { - self.0 - .lock() - .await - .audio_server - .source_volume_raise(step) - .await + pub async fn audio_source_volume_raise(&mut self) -> Result { + self.0.lock().await.audio_server.source_volume_raise().await } #[zlink(interface = "com.system76.CosmicSettings.Audio", rename = "SetDefault")]