diff --git a/default.nix b/default.nix index 8df00a7..afa0150 100644 --- a/default.nix +++ b/default.nix @@ -19,5 +19,6 @@ pkgs.rustPlatform.buildRustPackage { nativeBuildInputs = [ pkgs.pkg-config pkgs.rustPlatform.bindgenHook + pkgs.rustfmt ]; } diff --git a/src/auth.rs b/src/auth.rs index c1b951e..c2bc659 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -35,7 +35,8 @@ impl pam_client::ConversationHandler for LockConversation { } } -pub fn create_and_run_auth_loop() -> Option<(channel::Sender>, channel::Channel)> { +pub fn create_and_run_auth_loop( +) -> Option<(channel::Sender>, channel::Channel)> { let username = get_current_username() .expect("Failed to get username") .to_str() @@ -49,7 +50,10 @@ pub fn create_and_run_auth_loop() -> Option<(channel::Sender>, } Err(err) => { error!("Failed to initialize PAM context: {:?}", err); - error!("Ensure that the PAM service '{}' is correctly configured.", SERVICE_NAME); + error!( + "Ensure that the PAM service '{}' is correctly configured.", + SERVICE_NAME + ); return None; } } @@ -67,17 +71,15 @@ pub fn create_and_run_auth_loop() -> Option<(channel::Sender>, password: Some(password), }; match Context::new(SERVICE_NAME, Some(username.as_str()), conversation) { - Ok(mut context) => { - match context.authenticate(Flag::NONE) { - Ok(()) => { - auth_res_send.send(true).unwrap(); - } - Err(err) => { - error!("Pam authenticate failed with {:?}", err); - auth_res_send.send(false).unwrap(); - } + Ok(mut context) => match context.authenticate(Flag::NONE) { + Ok(()) => { + auth_res_send.send(true).unwrap(); } - } + Err(err) => { + error!("Pam authenticate failed with {:?}", err); + auth_res_send.send(false).unwrap(); + } + }, Err(err) => { error!("Failed to re-initialize PAM context: {:?}", err); auth_res_send.send(false).unwrap(); diff --git a/src/config.rs b/src/config.rs index 5778ee3..06cca53 100644 --- a/src/config.rs +++ b/src/config.rs @@ -135,15 +135,14 @@ pub struct Config { impl Config { pub fn load() -> Self { use clap::CommandFactory; - + let mut config = Config::parse(); let cmd = Config::command(); let matches = cmd.get_matches(); // Helper to check if a value was explicitly set on command line - let is_cli = |key: &str| { - matches.value_source(key) == Some(clap::parser::ValueSource::CommandLine) - }; + let is_cli = + |key: &str| matches.value_source(key) == Some(clap::parser::ValueSource::CommandLine); // 1. Config file layer (overrides defaults and themes) let config_path = config.config.clone().unwrap_or_else(|| { @@ -156,7 +155,7 @@ impl Config { if let Ok(file_content) = std::fs::read_to_string(&config_path) { if let Ok(table) = toml::from_str::(&file_content) { log::debug!("Loaded configuration from {:?}", config_path); - + let merge_bool = |val: &mut bool, key: &str| { if !is_cli(key) { if let Some(toml::Value::Boolean(b)) = table.get(key) { @@ -199,55 +198,55 @@ impl Config { merge_f32(&mut config.grace, "grace"); merge_f32(&mut config.fade_in, "fade_in"); merge_string(&mut config.pam_service, "pam_service"); - merge_bool(&mut config.show_media, "show_media"); - merge_bool(&mut config.show_battery, "show_battery"); - merge_bool(&mut config.show_network, "show_network"); - merge_bool(&mut config.show_bluetooth, "show_bluetooth"); - merge_bool(&mut config.show_album_art, "show_album_art"); - merge_bool(&mut config.show_keyboard_layout, "show_keyboard_layout"); - + merge_bool(&mut config.show_media, "show_media"); + merge_bool(&mut config.show_battery, "show_battery"); + merge_bool(&mut config.show_network, "show_network"); + merge_bool(&mut config.show_bluetooth, "show_bluetooth"); + merge_bool(&mut config.show_album_art, "show_album_art"); + merge_bool(&mut config.show_keyboard_layout, "show_keyboard_layout"); + if !is_cli("image") { if let Some(toml::Value::String(s)) = table.get("image") { config.image = Some(std::path::PathBuf::from(s)); } } - + if !is_cli("wifi_icon") { if let Some(toml::Value::String(s)) = table.get("wifi_icon") { config.wifi_icon = Some(s.clone()); } } - + if !is_cli("bluetooth_icon") { if let Some(toml::Value::String(s)) = table.get("bluetooth_icon") { config.bluetooth_icon = Some(s.clone()); } } - + if !is_cli("battery_icon") { if let Some(toml::Value::String(s)) = table.get("battery_icon") { config.battery_icon = Some(s.clone()); } } - + if !is_cli("media_prev_icon") { if let Some(toml::Value::String(s)) = table.get("media_prev_icon") { config.media_prev_icon = Some(s.clone()); } } - + if !is_cli("media_stop_icon") { if let Some(toml::Value::String(s)) = table.get("media_stop_icon") { config.media_stop_icon = Some(s.clone()); } } - + if !is_cli("media_play_icon") { if let Some(toml::Value::String(s)) = table.get("media_play_icon") { config.media_play_icon = Some(s.clone()); } } - + if !is_cli("media_next_icon") { if let Some(toml::Value::String(s)) = table.get("media_next_icon") { config.media_next_icon = Some(s.clone()); @@ -261,20 +260,40 @@ impl Config { if let Some(theme) = &config.theme { match theme.as_str() { "modern" => { - if config.effect_blur.is_none() && !is_cli("effect_blur") { config.effect_blur = Some((10, 3)); } - if config.effect_vignette.is_none() && !is_cli("effect_vignette") { config.effect_vignette = Some((0.5, 0.5)); } - if !is_cli("indicator_radius") { config.indicator_radius = 120; } - if !is_cli("ring_color") { config.ring_color = (0.2, 0.6, 0.8, 1.0); } + if config.effect_blur.is_none() && !is_cli("effect_blur") { + config.effect_blur = Some((10, 3)); + } + if config.effect_vignette.is_none() && !is_cli("effect_vignette") { + config.effect_vignette = Some((0.5, 0.5)); + } + if !is_cli("indicator_radius") { + config.indicator_radius = 120; + } + if !is_cli("ring_color") { + config.ring_color = (0.2, 0.6, 0.8, 1.0); + } } "pixel" => { - if config.effect_pixelate.is_none() && !is_cli("effect_pixelate") { config.effect_pixelate = Some(10); } - if !is_cli("indicator_radius") { config.indicator_radius = 80; } - if !is_cli("ring_color") { config.ring_color = (0.8, 0.2, 0.2, 1.0); } + if config.effect_pixelate.is_none() && !is_cli("effect_pixelate") { + config.effect_pixelate = Some(10); + } + if !is_cli("indicator_radius") { + config.indicator_radius = 80; + } + if !is_cli("ring_color") { + config.ring_color = (0.8, 0.2, 0.2, 1.0); + } } "glass" => { - if config.effect_blur.is_none() && !is_cli("effect_blur") { config.effect_blur = Some((20, 5)); } - if !is_cli("inside_color") { config.inside_color = (1.0, 1.0, 1.0, 0.1); } - if !is_cli("ring_color") { config.ring_color = (1.0, 1.0, 1.0, 0.5); } + if config.effect_blur.is_none() && !is_cli("effect_blur") { + config.effect_blur = Some((20, 5)); + } + if !is_cli("inside_color") { + config.inside_color = (1.0, 1.0, 1.0, 0.1); + } + if !is_cli("ring_color") { + config.ring_color = (1.0, 1.0, 1.0, 0.5); + } } _ => { log::warn!("Unknown theme: {}", theme); diff --git a/src/input.rs b/src/input.rs index b70762f..8942e5b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -100,8 +100,7 @@ impl InputHandler { } /// Update timers (should be called periodically) - pub fn update(&mut self) { - } + pub fn update(&mut self) {} /// Get the current Caps Lock state pub fn caps_lock(&self) -> bool { diff --git a/src/lock.rs b/src/lock.rs index 7535a3d..993a064 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -165,11 +165,9 @@ impl LockedSurface { event: smithay_client_toolkit::seat::keyboard::KeyEvent, modifiers: smithay_client_toolkit::seat::keyboard::Modifiers, ) -> Option { - let action = self.input_handler.handle_key_event( - event.keysym, - event.utf8, - modifiers, - ); + let action = self + .input_handler + .handle_key_event(event.keysym, event.utf8, modifiers); match action { InputAction::PasswordChanged => { diff --git a/src/main.rs b/src/main.rs index 4a4a288..cd68c22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,12 @@ mod util; use config::Config; use lock::LockManager; use screenshot::{CaptureData, Screenshot, ScreenshotManager}; -use system::SystemManager; use std::error::Error; use std::fs::OpenOptions; use std::io::Write; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; +use system::SystemManager; use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; @@ -489,7 +489,7 @@ impl Dispatch for WaylandLock { stride, } => { let format = format.into_result().unwrap(); - + let mut info = data.info.lock().unwrap(); *info = Some(screenshot::BufferInfo { width, @@ -503,7 +503,8 @@ impl Dispatch for WaylandLock { let size = (stride as usize) * (height as usize); match SlotPool::new(size, &state.shm_state) { Ok(mut pool) => { - match pool.create_buffer(width as i32, height as i32, stride as i32, format) { + match pool.create_buffer(width as i32, height as i32, stride as i32, format) + { Ok((buffer, _canvas)) => { frame.copy(buffer.wl_buffer()); *data.buffer.lock().unwrap() = Some(buffer); @@ -525,8 +526,10 @@ impl Dispatch for WaylandLock { let info = data.info.lock().unwrap().take(); let flags = data.flags.lock().unwrap().take(); let pool = data.pool.lock().unwrap().take(); - - if let (Some(buffer), Some(info), Some(flags), Some(mut pool)) = (buffer, info, flags, pool) { + + if let (Some(buffer), Some(info), Some(flags), Some(mut pool)) = + (buffer, info, flags, pool) + { let handle = screenshot::ScreencopyBufferHandle { buffer, info, @@ -608,7 +611,7 @@ fn main() -> Result<(), Box> { // Initialize state without pool first // We'll create the pool after we know the output dimensions - + // Initialize pool with a minimal size, it will be resized once outputs are detected let pool = SlotPool::new(1, &shm_state)?; @@ -648,7 +651,8 @@ fn main() -> Result<(), Box> { if let Ok(img) = image::open(image_path) { let img = img.to_rgba8(); let (w, h) = img.dimensions(); - let mut surface = cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32).unwrap(); + let mut surface = + cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32).unwrap(); { let mut surface_data = surface.data().unwrap(); for y in 0..h { @@ -662,18 +666,21 @@ fn main() -> Result<(), Box> { } } } - + let mut ss = Screenshot::new(surface); let _ = ss.apply_effects(&state.config); let surface = ss.into_inner(); - + let num_outputs = state.output_state.outputs().count(); state.captured_backgrounds = vec![Some(surface); num_outputs]; - + // Disable screenshots if image was successfully loaded state.config.screenshots = false; } else { - log::error!("Failed to load custom background image from {:?}", image_path); + log::error!( + "Failed to load custom background image from {:?}", + image_path + ); } } @@ -724,7 +731,7 @@ fn main() -> Result<(), Box> { } let mut status = state.system_manager.get_status(); - status.keyboard_layout = Some(state.current_layout.to_string()); + status.keyboard_layout = Some(state.current_layout.to_string()); if let Ok(mut lm) = state.lock_manager.lock() { lm.set_system_status(status); diff --git a/src/render.rs b/src/render.rs index 6c84267..ba9f126 100644 --- a/src/render.rs +++ b/src/render.rs @@ -351,18 +351,18 @@ impl Renderer { self.context.stroke().unwrap(); } - // Use caps lock color when caps lock is on and indicator is enabled, otherwise ring color - let (r, g, b, a) = if self.caps_lock { - self.config.caps_lock_color - } else { - self.config.ring_color - }; - self.context.new_path(); - self.context.set_source_rgba(r, g, b, a * self.fade_alpha); - self.context.set_line_width(thickness); - self.context - .arc(center_x, center_y, radius, 0.0, 2.0 * std::f64::consts::PI); - self.context.stroke().unwrap(); + // Use caps lock color when caps lock is on and indicator is enabled, otherwise ring color + let (r, g, b, a) = if self.caps_lock { + self.config.caps_lock_color + } else { + self.config.ring_color + }; + self.context.new_path(); + self.context.set_source_rgba(r, g, b, a * self.fade_alpha); + self.context.set_line_width(thickness); + self.context + .arc(center_x, center_y, radius, 0.0, 2.0 * std::f64::consts::PI); + self.context.stroke().unwrap(); let (r, g, b, a) = self.config.separator_color; if a > 0.0 { @@ -507,7 +507,8 @@ impl Renderer { if let Ok(img) = image::load_from_memory(data) { let img = img.to_rgba8(); let (w, h) = img.dimensions(); - let mut surface = ImageSurface::create(Format::ARgb32, w as i32, h as i32).unwrap(); + let mut surface = + ImageSurface::create(Format::ARgb32, w as i32, h as i32).unwrap(); { let mut surface_data = surface.data().unwrap(); for y in 0..h { @@ -528,7 +529,11 @@ impl Renderer { } let has_art = self.config.show_album_art && self.media_art_surface.is_some(); - let text_x = if has_art { center_x - spacing / 2.0 } else { center_x }; + let text_x = if has_art { + center_x - spacing / 2.0 + } else { + center_x + }; let art_x = center_x - spacing - art_size / 2.0; if has_art { @@ -544,7 +549,8 @@ impl Renderer { } self.context.new_path(); - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.9); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.9); self.context.set_font_size(16.0); let display_text = if let Some(ref artist) = self.system_status.media_artist { @@ -554,14 +560,19 @@ impl Renderer { }; let te = self.context.text_extents(&display_text).unwrap(); - self.context.move_to(text_x - te.width() / 2.0, start_y + 20.0); + self.context + .move_to(text_x - te.width() / 2.0, start_y + 20.0); self.context.show_text(&display_text).unwrap(); - let status_text = if self.system_status.media_playing { + let status_text = if self.system_status.media_playing { if let Some(ref icon) = self.media_play_icon_surface { // Draw play icon instead of text let play_y = start_y + 40.0; - self.draw_icon_at(center_x - icon.width() as f64 / 2.0, play_y - icon.height() as f64 / 2.0, icon); + self.draw_icon_at( + center_x - icon.width() as f64 / 2.0, + play_y - icon.height() as f64 / 2.0, + icon, + ); "" } else { "▶ Playing" @@ -569,51 +580,57 @@ impl Renderer { } else { "⏸ Paused" }; - + if !status_text.is_empty() { self.context.set_font_size(12.0); let se = self.context.text_extents(status_text).unwrap(); - self.context.move_to(text_x - se.width() / 2.0, start_y + 40.0); + self.context + .move_to(text_x - se.width() / 2.0, start_y + 40.0); self.context.show_text(status_text).unwrap(); } let controls_y = start_y + 65.0; - + // Draw media control icons let icon_size = 20.0; let icon_spacing = 40.0; let controls_center_x = center_x; - + // Previous icon if let Some(ref icon) = self.media_prev_icon_surface { let ix = controls_center_x - icon_spacing; self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon); } else { - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); self.context.set_font_size(16.0); - self.context.move_to(controls_center_x - icon_spacing - 8.0, controls_y); + self.context + .move_to(controls_center_x - icon_spacing - 8.0, controls_y); self.context.show_text("⏮").unwrap(); } - + // Stop icon if let Some(ref icon) = self.media_stop_icon_surface { let ix = controls_center_x; self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon); } else { - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); self.context.set_font_size(16.0); self.context.move_to(controls_center_x - 8.0, controls_y); self.context.show_text("⏹").unwrap(); } - + // Next icon if let Some(ref icon) = self.media_next_icon_surface { let ix = controls_center_x + icon_spacing; self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon); } else { - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7); self.context.set_font_size(16.0); - self.context.move_to(controls_center_x + icon_spacing - 8.0, controls_y); + self.context + .move_to(controls_center_x + icon_spacing - 8.0, controls_y); self.context.show_text("⏭").unwrap(); } } @@ -638,7 +655,15 @@ impl Renderer { self.context.show_text(ssid).unwrap(); } else { let strength = self.system_status.wifi_strength.unwrap_or(0); - let icon = if strength > 75 { "📶" } else if strength > 50 { "📶" } else if strength > 25 { "📶" } else { "📶" }; + let icon = if strength > 75 { + "📶" + } else if strength > 50 { + "📶" + } else if strength > 25 { + "📶" + } else { + "📶" + }; let text = format!("{} {}", icon, ssid); self.context.new_path(); self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha); @@ -649,7 +674,8 @@ impl Renderer { } else { let text = "📵 No WiFi"; self.context.new_path(); - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5); self.context.set_font_size(16.0); self.context.move_to(x, y); self.context.show_text(text).unwrap(); @@ -673,7 +699,14 @@ impl Renderer { self.context.move_to(text_x, y); self.context.show_text(&battery_text).unwrap(); } else { - self.draw_battery_icon_at(x, y - 12.0, icon_width, 15.0, percent, self.system_status.is_charging); + self.draw_battery_icon_at( + x, + y - 12.0, + icon_width, + 15.0, + percent, + self.system_status.is_charging, + ); let battery_text = format!("{:.0}%", percent); self.context.new_path(); self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha); @@ -703,7 +736,10 @@ impl Renderer { self.context.move_to(text_x, y); self.context.show_text(&devices).unwrap(); } else { - let text = format!("🔵 {} device(s)", self.system_status.bluetooth_devices.len()); + let text = format!( + "🔵 {} device(s)", + self.system_status.bluetooth_devices.len() + ); self.context.new_path(); self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha); self.context.set_font_size(14.0); @@ -713,7 +749,8 @@ impl Renderer { } else { let text = "🔴 Bluetooth off"; self.context.new_path(); - self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5); + self.context + .set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5); self.context.set_font_size(14.0); self.context.move_to(x, y); self.context.show_text(text).unwrap(); @@ -750,7 +787,15 @@ impl Renderer { self.context.restore().unwrap(); } - fn draw_battery_icon_at(&self, x: f64, y: f64, width: f64, height: f64, percent: f64, charging: bool) { + fn draw_battery_icon_at( + &self, + x: f64, + y: f64, + width: f64, + height: f64, + percent: f64, + charging: bool, + ) { let alpha = self.fade_alpha; self.context.new_path(); self.context.set_source_rgba(1.0, 1.0, 1.0, alpha * 0.5); @@ -758,7 +803,8 @@ impl Renderer { self.context.rectangle(x, y, width, height); self.context.stroke().unwrap(); self.context.new_path(); - self.context.rectangle(x + width, y + height / 4.0, 3.0, height / 2.0); + self.context + .rectangle(x + width, y + height / 4.0, 3.0, height / 2.0); self.context.fill().unwrap(); let fill_width = (width - 4.0) * (percent / 100.0); self.context.new_path(); @@ -767,7 +813,8 @@ impl Renderer { } else { self.context.set_source_rgba(0.2, 1.0, 0.2, alpha * 0.8); } - self.context.rectangle(x + 2.0, y + 2.0, fill_width, height - 4.0); + self.context + .rectangle(x + 2.0, y + 2.0, fill_width, height - 4.0); self.context.fill().unwrap(); if charging { self.context.new_path(); diff --git a/src/screenshot.rs b/src/screenshot.rs index 4dd7d80..20240d5 100644 --- a/src/screenshot.rs +++ b/src/screenshot.rs @@ -115,10 +115,10 @@ impl Screenshot { for y in 0..height { melt_amount += rng.gen_range(0.0..factor); let src_y = (y as f32 - melt_amount).max(0.0) as i32; - + let src_idx = (src_y as usize * stride) + (x as usize * 4); let dst_idx = (y as usize * stride) + (x as usize * 4); - + // Copy the pixel from above to create a smear let pixel = [ data[src_idx], diff --git a/src/system.rs b/src/system.rs index 4cdc33a..b24c9b5 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,8 +1,8 @@ -use std::sync::{Arc, Mutex}; +use log::{debug, error}; use mpris::PlayerFinder; -use zbus::Connection; -use log::{error, debug}; +use std::sync::{Arc, Mutex}; use tokio::sync::mpsc; +use zbus::Connection; #[derive(Clone, Default)] pub struct SystemStatus { @@ -89,7 +89,7 @@ impl SystemManager { new_status.media_title = metadata.title().map(|s| s.to_string()); new_status.media_artist = metadata.artists().map(|a| a.join(", ")); new_status.media_art_url = metadata.art_url().map(|u| u.to_string()); - + if new_status.media_art_url != last_art_url { last_art_url = new_status.media_art_url.clone(); last_art_data = None; @@ -216,7 +216,7 @@ impl SystemManager { SystemCommand::Reboot => "Reboot", SystemCommand::Suspend => "Suspend", }; - + debug!("Executing system command: {}", method); // Set a timeout for the DBus call to prevent hanging the background thread let result = tokio::time::timeout(