From fbd7aee9d21bfa9c665ab994c6da3652e643d339 Mon Sep 17 00:00:00 2001 From: Jory Severijnse Date: Fri, 10 Apr 2026 12:59:39 +0200 Subject: [PATCH] feat: add cli option and ctrl+u clears password - Add `--hide-password` CLI flag - Show temporary "CLEARED" feedback on password clear --- Cargo.toml | 10 +++---- src/config.rs | 24 ++++++++++------- src/input.rs | 13 +++++++++ src/lock.rs | 3 +++ src/render.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3eee050..7662cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,8 +43,8 @@ default = ["networking"] networking = ["dep:reqwest", "tokio/net"] [profile.release] -opt-level = "z" # Optimize for size -lto = true # Enable Link Time Optimization -codegen-units = 1 # Reduce parallel code gen to allow better size optimization -panic = "abort" # Remove heavy stack unwinding code -strip = true # Automatically strip symbols and debug info +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" +strip = true diff --git a/src/config.rs b/src/config.rs index 06cca53..04c33d4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,13 +6,13 @@ use std::path::PathBuf; #[derive(Parser, Debug, Clone, Serialize, Deserialize)] #[command(author, version, about, long_about = None)] pub struct Config { - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "false", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue)] pub screenshots: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "false", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue)] pub clock: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub indicator: bool, #[arg(long, default_value = "100")] @@ -54,7 +54,7 @@ pub struct Config { #[arg(long, default_value = "E5A445", value_parser = util::parse_hex_color)] pub caps_lock_text_color: (f64, f64, f64, f64), - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_caps_lock_text: bool, #[arg(long, default_value = "00000000", value_parser = util::parse_hex_color)] @@ -85,22 +85,25 @@ pub struct Config { #[arg(long)] pub log_file: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_media: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_battery: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_network: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_bluetooth: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue, default_value_t = true)] pub show_album_art: bool, - #[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "false", default_missing_value = "true")] + #[arg(long, action = clap::ArgAction::SetTrue)] + pub hide_password: bool, + + #[arg(long, action = clap::ArgAction::SetTrue)] pub show_keyboard_layout: bool, #[arg(long)] @@ -203,6 +206,7 @@ impl Config { 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.hide_password, "hide_password"); merge_bool(&mut config.show_keyboard_layout, "show_keyboard_layout"); if !is_cli("image") { diff --git a/src/input.rs b/src/input.rs index 8942e5b..5231a02 100644 --- a/src/input.rs +++ b/src/input.rs @@ -30,6 +30,15 @@ impl InputHandler { // Update Caps Lock state self.caps_lock = modifiers.caps_lock; + if modifiers.ctrl && keysym == Keysym::u { + if !self.password_buffer.is_empty() { + self.password_buffer.clear(); + self.cursor_position = 0; + return InputAction::PasswordCleared; + } + return InputAction::None; + } + // Handle special keys first using keysym use smithay_client_toolkit::seat::keyboard::Keysym; match keysym { @@ -37,6 +46,9 @@ impl InputHandler { if !self.password_buffer.is_empty() && self.cursor_position > 0 { self.cursor_position -= 1; self.password_buffer.remove(self.cursor_position); + if self.password_buffer.is_empty() { + return InputAction::PasswordCleared; + } } return InputAction::PasswordChanged; } @@ -113,6 +125,7 @@ impl InputHandler { pub enum InputAction { None, PasswordChanged, + PasswordCleared, SubmitPassword(Zeroizing), Cancel, } diff --git a/src/lock.rs b/src/lock.rs index 087228b..66dbfd3 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -174,6 +174,9 @@ impl LockedSurface { self.input_handler.set_key_highlight(); self.key_highlight_shown = false; } + InputAction::PasswordCleared => { + self.renderer.show_cleared_feedback(); + } InputAction::SubmitPassword(_) => { self.input_handler.set_key_highlight(); self.key_highlight_shown = false; diff --git a/src/render.rs b/src/render.rs index 04c6a6a..c03450b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -14,8 +14,10 @@ pub struct Renderer { fade_alpha: f64, wrong_password_shown: bool, key_highlight_shown: bool, + cleared_feedback_shown: bool, wrong_password_start: Option, key_highlight_start: Option, + cleared_feedback_start: Option, key_highlight_angle: f64, background: Option, password_display: String, @@ -51,8 +53,10 @@ impl Renderer { fade_alpha: 0.0, wrong_password_shown: false, key_highlight_shown: false, + cleared_feedback_shown: false, wrong_password_start: None, key_highlight_start: None, + cleared_feedback_start: None, key_highlight_angle: 0.0, background: None, password_display: String::new(), @@ -183,6 +187,12 @@ impl Renderer { self.key_highlight_angle = ((random_val % 360) as f64).to_radians(); } + /// Show cleared feedback + pub fn show_cleared_feedback(&mut self) { + self.cleared_feedback_shown = true; + self.cleared_feedback_start = Some(Instant::now()); + } + /// Set the password display string (masked) pub fn set_password_display(&mut self, password: String) { self.password_display = password; @@ -250,6 +260,10 @@ impl Renderer { self.draw_key_highlight_feedback(); } + if self.cleared_feedback_shown { + self.draw_cleared_feedback(); + } + self.update_feedback_timers(); } @@ -376,6 +390,9 @@ impl Renderer { } fn draw_password_display(&self) { + if self.config.hide_password { + return; + } let center_x = self.width as f64 / 2.0; let center_y = self.height as f64 / 2.0; let radius = self.config.indicator_radius as f64; @@ -476,6 +493,56 @@ impl Renderer { } } + fn draw_cleared_feedback(&self) { + let center_x = self.width as f64 / 2.0; + let center_y = self.height as f64 / 2.0; + let radius = self.config.indicator_radius as f64; + let thickness = self.config.indicator_thickness as f64; + let intensity = if let Some(start) = self.cleared_feedback_start { + let elapsed = start.elapsed(); + let duration = std::time::Duration::from_millis(500); + if elapsed < duration { + 1.0 - (elapsed.as_secs_f64() / duration.as_secs_f64()) + } else { + 0.0 + } + } else { + 0.0 + }; + + if intensity > 0.0 { + self.context.new_path(); + self.context + .set_source_rgba(1.0, 0.0, 0.0, intensity * self.fade_alpha * 0.5); + self.context.arc( + center_x, + center_y, + radius - thickness / 2.0, + 0.0, + 2.0 * std::f64::consts::PI, + ); + self.context.fill().unwrap(); + + self.context.new_path(); + self.context + .set_source_rgba(1.0, 0.0, 0.0, intensity * self.fade_alpha); + self.context.set_line_width(thickness + 4.0); + self.context + .arc(center_x, center_y, radius, 0.0, 2.0 * std::f64::consts::PI); + self.context.stroke().unwrap(); + + self.context.new_path(); + self.context.set_font_size(24.0); + self.context + .set_source_rgba(1.0, 1.0, 1.0, intensity * self.fade_alpha); + let text = "CLEARED"; + let te = self.context.text_extents(text).unwrap(); + self.context + .move_to(center_x - te.width() / 2.0, center_y + te.height() / 2.0); + self.context.show_text(text).unwrap(); + } + } + fn update_feedback_timers(&mut self) { self.update_uptime(); if let Some(start) = self.wrong_password_start { @@ -490,6 +557,12 @@ impl Renderer { self.key_highlight_start = None; } } + if let Some(start) = self.cleared_feedback_start { + if start.elapsed() > std::time::Duration::from_millis(500) { + self.cleared_feedback_shown = false; + self.cleared_feedback_start = None; + } + } } fn draw_media(&mut self) {