First propper working version with actual pam
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# PAM configuration for wayrustlock
|
||||||
|
# Install this file to /etc/pam.d/wayrustlock
|
||||||
|
|
||||||
|
# Use the standard login service authentication
|
||||||
|
auth include login
|
||||||
+5
-54
@@ -3,63 +3,14 @@ use std::thread;
|
|||||||
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use pam_client::{Context, ErrorCode, Flag};
|
use pam_client::{Context, ErrorCode, Flag};
|
||||||
use secstr::SecVec;
|
|
||||||
use smithay_client_toolkit::reexports::{calloop::channel, calloop::EventLoop};
|
use smithay_client_toolkit::reexports::{calloop::channel, calloop::EventLoop};
|
||||||
use users::get_current_username;
|
use users::get_current_username;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
const SERVICE_NAME: &str = "wayrustlock";
|
const SERVICE_NAME: &str = "wayrustlock";
|
||||||
|
|
||||||
pub struct PasswordBuffer(SecVec<u8>);
|
|
||||||
|
|
||||||
impl PasswordBuffer {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(SecVec::new(Vec::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zeroize_string(mut data: String) {
|
|
||||||
use std::sync::atomic;
|
|
||||||
|
|
||||||
let default = u8::default();
|
|
||||||
|
|
||||||
for c in unsafe { data.as_bytes_mut() } {
|
|
||||||
unsafe { std::ptr::write_volatile(c, default) };
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic::fence(atomic::Ordering::SeqCst);
|
|
||||||
atomic::compiler_fence(atomic::Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append(&mut self, data: String) {
|
|
||||||
let bytes = data.as_bytes();
|
|
||||||
let mut og_len = self.0.unsecure().len();
|
|
||||||
self.0.resize(og_len + bytes.len(), 0);
|
|
||||||
for b in bytes {
|
|
||||||
self.0.unsecure_mut()[og_len] = *b;
|
|
||||||
og_len += 1;
|
|
||||||
}
|
|
||||||
Self::zeroize_string(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backspace(&mut self) {
|
|
||||||
let og_len = self.0.unsecure().len();
|
|
||||||
if og_len != 0 {
|
|
||||||
self.0.resize(og_len - 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unsecure(&self) -> &str {
|
|
||||||
unsafe { std::str::from_utf8_unchecked(self.0.unsecure()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take(&mut self) -> Self {
|
|
||||||
let mut new_buffer = SecVec::new(Vec::new());
|
|
||||||
std::mem::swap(&mut self.0, &mut new_buffer);
|
|
||||||
Self(new_buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LockConversation {
|
pub struct LockConversation {
|
||||||
pub password: Option<PasswordBuffer>,
|
pub password: Option<Zeroizing<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pam_client::ConversationHandler for LockConversation {
|
impl pam_client::ConversationHandler for LockConversation {
|
||||||
@@ -71,7 +22,7 @@ impl pam_client::ConversationHandler for LockConversation {
|
|||||||
|
|
||||||
fn prompt_echo_off(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
|
fn prompt_echo_off(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
|
||||||
if let Some(password) = self.password.take() {
|
if let Some(password) = self.password.take() {
|
||||||
CString::new(password.unsecure()).map_err(|_| ErrorCode::ABORT)
|
CString::new(password.as_str()).map_err(|_| ErrorCode::ABORT)
|
||||||
} else {
|
} else {
|
||||||
Err(ErrorCode::ABORT)
|
Err(ErrorCode::ABORT)
|
||||||
}
|
}
|
||||||
@@ -84,7 +35,7 @@ impl pam_client::ConversationHandler for LockConversation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_and_run_auth_loop() -> (channel::Sender<PasswordBuffer>, channel::Channel<bool>) {
|
pub fn create_and_run_auth_loop() -> (channel::Sender<Zeroizing<String>>, channel::Channel<bool>) {
|
||||||
struct AuthLoopState {
|
struct AuthLoopState {
|
||||||
auth_res_send: channel::Sender<bool>,
|
auth_res_send: channel::Sender<bool>,
|
||||||
main_closed: bool,
|
main_closed: bool,
|
||||||
@@ -102,7 +53,7 @@ pub fn create_and_run_auth_loop() -> (channel::Sender<PasswordBuffer>, channel::
|
|||||||
.expect("Failed to initialize PAM context");
|
.expect("Failed to initialize PAM context");
|
||||||
debug!("Prepared to authenticate user '{}'", username);
|
debug!("Prepared to authenticate user '{}'", username);
|
||||||
|
|
||||||
let (auth_req_send, auth_req_recv) = channel::channel::<PasswordBuffer>();
|
let (auth_req_send, auth_req_recv) = channel::channel::<Zeroizing<String>>();
|
||||||
let (auth_res_send, auth_res_recv) = channel::channel::<bool>();
|
let (auth_res_send, auth_res_recv) = channel::channel::<bool>();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ pub struct Config {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub clock: bool,
|
pub clock: bool,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long, default_value = "true")]
|
||||||
pub indicator: bool,
|
pub indicator: bool,
|
||||||
|
|
||||||
#[arg(long, default_value = "100")]
|
#[arg(long, default_value = "100")]
|
||||||
|
|||||||
+106
-29
@@ -135,6 +135,9 @@ impl LockedSurface {
|
|||||||
self.renderer.set_background(background.clone());
|
self.renderer.set_background(background.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.renderer
|
||||||
|
.set_password_display(self.input_handler.get_display_password());
|
||||||
|
|
||||||
// Render the frame
|
// Render the frame
|
||||||
self.renderer.render();
|
self.renderer.render();
|
||||||
|
|
||||||
@@ -171,7 +174,10 @@ impl LockedSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a key event from Wayland
|
/// Handle a key event from Wayland
|
||||||
pub fn handle_key_event(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent) {
|
pub fn handle_key_event(
|
||||||
|
&mut self,
|
||||||
|
event: smithay_client_toolkit::seat::keyboard::KeyEvent,
|
||||||
|
) -> Option<InputAction> {
|
||||||
// Convert to our input handler format
|
// Convert to our input handler format
|
||||||
// Note: KeyEvent has fields: time, raw_code, keysym, utf8
|
// Note: KeyEvent has fields: time, raw_code, keysym, utf8
|
||||||
// We need to determine state and modifiers from context (not available in this demo)
|
// We need to determine state and modifiers from context (not available in this demo)
|
||||||
@@ -185,34 +191,98 @@ impl LockedSurface {
|
|||||||
.handle_key_event(keysym, state, modifiers);
|
.handle_key_event(keysym, state, modifiers);
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
InputAction::SubmitPassword(_password) => {
|
InputAction::SubmitPassword(password) => {
|
||||||
// Show key highlight for visual feedback
|
// Show key highlight for visual feedback
|
||||||
self.show_key_highlight();
|
self.show_key_highlight();
|
||||||
|
Some(InputAction::SubmitPassword(password))
|
||||||
// TODO: Authenticate with PAM
|
|
||||||
// For now, just clear the password
|
|
||||||
self.input_handler.clear_password();
|
|
||||||
|
|
||||||
// TODO: If authentication succeeds, unlock the session
|
|
||||||
// If authentication fails, show wrong password feedback
|
|
||||||
self.show_wrong_password();
|
|
||||||
}
|
}
|
||||||
InputAction::Cancel => {
|
InputAction::Cancel => Some(InputAction::Cancel),
|
||||||
// Escape key pressed
|
InputAction::TempScreenshot => Some(InputAction::TempScreenshot),
|
||||||
// TODO: Handle cancel action (maybe show quit confirmation?)
|
InputAction::PasswordChanged => Some(InputAction::PasswordChanged),
|
||||||
}
|
InputAction::None => None,
|
||||||
InputAction::TempScreenshot => {
|
|
||||||
// Temp screenshot (peek) activated
|
|
||||||
// Already handled in update() method via timer
|
|
||||||
}
|
|
||||||
InputAction::PasswordChanged => {
|
|
||||||
// Password changed, update display
|
|
||||||
// TODO: Update password display in renderer
|
|
||||||
}
|
|
||||||
InputAction::None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authenticate a password using PAM
|
||||||
|
pub fn authenticate_password(&self, password: zeroize::Zeroizing<String>) -> bool {
|
||||||
|
// Create a simple PAM conversation that provides the password
|
||||||
|
struct SimpleConversation {
|
||||||
|
password: Option<zeroize::Zeroizing<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl pam_client::ConversationHandler for SimpleConversation {
|
||||||
|
fn init(&mut self, _default_user: Option<impl AsRef<str>>) {}
|
||||||
|
|
||||||
|
fn prompt_echo_on(
|
||||||
|
&mut self,
|
||||||
|
_msg: &std::ffi::CStr,
|
||||||
|
) -> Result<std::ffi::CString, pam_client::ErrorCode> {
|
||||||
|
Err(pam_client::ErrorCode::ABORT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_echo_off(
|
||||||
|
&mut self,
|
||||||
|
_msg: &std::ffi::CStr,
|
||||||
|
) -> Result<std::ffi::CString, pam_client::ErrorCode> {
|
||||||
|
if let Some(pwd) = self.password.take() {
|
||||||
|
std::ffi::CString::new(pwd.as_str()).map_err(|_| pam_client::ErrorCode::ABORT)
|
||||||
|
} else {
|
||||||
|
Err(pam_client::ErrorCode::ABORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_info(&mut self, _msg: &std::ffi::CStr) {}
|
||||||
|
fn error_msg(&mut self, _msg: &std::ffi::CStr) {}
|
||||||
|
fn radio_prompt(
|
||||||
|
&mut self,
|
||||||
|
_msg: &std::ffi::CStr,
|
||||||
|
) -> Result<bool, pam_client::ErrorCode> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username
|
||||||
|
let username = match users::get_current_username() {
|
||||||
|
Some(name) => name.to_string_lossy().into_owned(),
|
||||||
|
None => {
|
||||||
|
log::error!("Failed to get current username");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create PAM context
|
||||||
|
let service_name = &self.config.pam_service;
|
||||||
|
let conversation = SimpleConversation {
|
||||||
|
password: Some(password),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut context =
|
||||||
|
match pam_client::Context::new(service_name, Some(username.as_str()), conversation) {
|
||||||
|
Ok(ctx) => ctx,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to initialize PAM context: {:?}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
match context.authenticate(pam_client::Flag::NONE) {
|
||||||
|
Ok(()) => {
|
||||||
|
log::info!("PAM authentication successful for user {}", username);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("PAM authentication failed: {:?}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the input handler for this locked surface
|
||||||
|
pub fn input_handler(&self) -> &InputHandler {
|
||||||
|
&self.input_handler
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the rendered image surface for this locked surface
|
/// Get the rendered image surface for this locked surface
|
||||||
pub fn as_image_surface(&self) -> &ImageSurface {
|
pub fn as_image_surface(&self) -> &ImageSurface {
|
||||||
self.renderer.as_image_surface()
|
self.renderer.as_image_surface()
|
||||||
@@ -246,7 +316,7 @@ impl LockedSurface {
|
|||||||
|
|
||||||
/// Manager for all locked surfaces (multiple outputs)
|
/// Manager for all locked surfaces (multiple outputs)
|
||||||
pub struct LockManager {
|
pub struct LockManager {
|
||||||
surfaces: Vec<LockedSurface>,
|
pub surfaces: Vec<LockedSurface>,
|
||||||
config: Config,
|
config: Config,
|
||||||
locked: bool,
|
locked: bool,
|
||||||
}
|
}
|
||||||
@@ -279,13 +349,20 @@ impl LockManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a key event (distribute to all surfaces or focused surface)
|
/// Handle a key event and return any action that needs processing
|
||||||
pub fn handle_key_event(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent) {
|
/// Returns the first non-None action from any surface
|
||||||
// For now, send to all surfaces
|
pub fn handle_key_event(
|
||||||
// In a real implementation, we would determine which surface has focus
|
&mut self,
|
||||||
|
event: smithay_client_toolkit::seat::keyboard::KeyEvent,
|
||||||
|
) -> Option<InputAction> {
|
||||||
|
// Distribute key event to all surfaces and collect first action
|
||||||
|
let mut action = None;
|
||||||
for surface in &mut self.surfaces {
|
for surface in &mut self.surfaces {
|
||||||
surface.handle_key_event(event.clone());
|
if let Some(a) = surface.handle_key_event(event.clone()) {
|
||||||
|
action = Some(a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if session is locked
|
/// Check if session is locked
|
||||||
|
|||||||
+75
-16
@@ -13,6 +13,7 @@ use std::error::Error;
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
fn setup_file_logging() {
|
fn setup_file_logging() {
|
||||||
let log_path =
|
let log_path =
|
||||||
@@ -87,7 +88,7 @@ fn lock_wayland_session(
|
|||||||
compositor::{CompositorHandler, CompositorState},
|
compositor::{CompositorHandler, CompositorState},
|
||||||
output::{OutputHandler, OutputState},
|
output::{OutputHandler, OutputState},
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{EventLoop, LoopHandle},
|
calloop::{channel, EventLoop, LoopHandle},
|
||||||
calloop_wayland_source::WaylandSource,
|
calloop_wayland_source::WaylandSource,
|
||||||
},
|
},
|
||||||
registry::{ProvidesRegistryState, RegistryState},
|
registry::{ProvidesRegistryState, RegistryState},
|
||||||
@@ -126,6 +127,8 @@ fn lock_wayland_session(
|
|||||||
config: Config,
|
config: Config,
|
||||||
ctrlc_exit: Arc<std::sync::atomic::AtomicBool>,
|
ctrlc_exit: Arc<std::sync::atomic::AtomicBool>,
|
||||||
exit: bool,
|
exit: bool,
|
||||||
|
auth_tx: Option<channel::Sender<Zeroizing<String>>>,
|
||||||
|
unlocking: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionLockHandler for WaylandLock {
|
impl SessionLockHandler for WaylandLock {
|
||||||
@@ -170,8 +173,17 @@ fn lock_wayland_session(
|
|||||||
_qh: &QueueHandle<Self>,
|
_qh: &QueueHandle<Self>,
|
||||||
_session_lock: SessionLock,
|
_session_lock: SessionLock,
|
||||||
) {
|
) {
|
||||||
log::info!("Session unlocked or lock denied - exiting");
|
if self.unlocking {
|
||||||
|
log::info!("✅ Session successfully unlocked - compositor confirmed");
|
||||||
|
log_to_file("✅ Session successfully unlocked - compositor confirmed");
|
||||||
|
self.unlocking = false;
|
||||||
|
} else {
|
||||||
|
log::warn!("Session finished without unlock request - lock denied or cancelled");
|
||||||
|
log_to_file("Session finished without unlock request");
|
||||||
|
}
|
||||||
|
self.session_lock = None;
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
|
log_to_file("Setting exit=true from finished callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure(
|
fn configure(
|
||||||
@@ -474,30 +486,62 @@ fn lock_wayland_session(
|
|||||||
|
|
||||||
impl WaylandLock {
|
impl WaylandLock {
|
||||||
fn handle_key_event(&mut self, event: KeyEvent) {
|
fn handle_key_event(&mut self, event: KeyEvent) {
|
||||||
|
use crate::input::InputAction;
|
||||||
use smithay_client_toolkit::seat::keyboard::Keysym;
|
use smithay_client_toolkit::seat::keyboard::Keysym;
|
||||||
|
|
||||||
log_to_file(&format!(
|
log_to_file(&format!(
|
||||||
"handle_key_event called: keysym={:?}",
|
"handle_key_event called: keysym={:?}",
|
||||||
event.keysym
|
event.keysym
|
||||||
));
|
));
|
||||||
|
log::debug!("Key event: {:?}", event.keysym);
|
||||||
|
|
||||||
if event.keysym == Keysym::Return {
|
if event.keysym == Keysym::Return {
|
||||||
log::info!("Enter pressed - unlocking session (demo mode)");
|
log::info!("Enter pressed - submitting password for authentication");
|
||||||
log_to_file("ENTER pressed - unlocking!");
|
log_to_file("ENTER pressed - submitting password");
|
||||||
if let Some(ref session_lock) = self.session_lock.take() {
|
|
||||||
session_lock.unlock();
|
if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||||
|
if let Some(InputAction::SubmitPassword(password)) =
|
||||||
|
lock_manager.handle_key_event(event.clone())
|
||||||
|
{
|
||||||
|
// Send password to auth thread for processing
|
||||||
|
if let Some(tx) = &self.auth_tx {
|
||||||
|
let _ = tx.send(password);
|
||||||
|
}
|
||||||
|
log::debug!("Password submitted for authentication");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.exit = true;
|
|
||||||
log_to_file("Set exit=true");
|
|
||||||
} else if event.keysym == Keysym::BackSpace {
|
|
||||||
log::info!("Backspace pressed");
|
|
||||||
log_to_file("Backspace pressed");
|
|
||||||
} else if let Some(input) = event.utf8 {
|
|
||||||
log::info!("Key pressed: '{}'", input);
|
|
||||||
log_to_file(&format!("Key pressed: '{}'", input));
|
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Non-character keysym pressed: {:?}", event.keysym);
|
// Distribute to all surfaces (BackSpace, characters, etc.)
|
||||||
log_to_file(&format!("Non-char keysym: {:?}", event.keysym));
|
let _ = self
|
||||||
|
.lock_manager
|
||||||
|
.lock()
|
||||||
|
.map(|mut lm| lm.handle_key_event(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle authentication result from the auth thread
|
||||||
|
fn handle_auth_result(&mut self, success: bool) {
|
||||||
|
if success {
|
||||||
|
log::info!("✅ Authentication successful - unlocking session");
|
||||||
|
log_to_file("✅ Authentication successful - unlocking session");
|
||||||
|
|
||||||
|
// Call unlock on the session_lock, but keep it to receive finished event
|
||||||
|
if let Some(session_lock) = &self.session_lock {
|
||||||
|
session_lock.unlock();
|
||||||
|
self.unlocking = true;
|
||||||
|
log_to_file("Unlock requested - waiting for finished event");
|
||||||
|
} else {
|
||||||
|
log::error!("No session_lock available to unlock!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("❌ Authentication failed - wrong password");
|
||||||
|
log_to_file("❌ Authentication failed - wrong password");
|
||||||
|
// Show wrong password feedback on all surfaces
|
||||||
|
if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||||
|
for surface in &mut lock_manager.surfaces {
|
||||||
|
surface.show_wrong_password();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,6 +637,9 @@ fn lock_wayland_session(
|
|||||||
let qh: QueueHandle<WaylandLock> = event_queue.handle();
|
let qh: QueueHandle<WaylandLock> = event_queue.handle();
|
||||||
let mut event_loop: EventLoop<WaylandLock> = EventLoop::try_new()?;
|
let mut event_loop: EventLoop<WaylandLock> = EventLoop::try_new()?;
|
||||||
|
|
||||||
|
// Create the authentication loop in a separate thread
|
||||||
|
let (auth_tx, auth_rx) = auth::create_and_run_auth_loop();
|
||||||
|
|
||||||
let mut state = WaylandLock {
|
let mut state = WaylandLock {
|
||||||
loop_handle: event_loop.handle(),
|
loop_handle: event_loop.handle(),
|
||||||
conn: conn.clone(),
|
conn: conn.clone(),
|
||||||
@@ -614,6 +661,8 @@ fn lock_wayland_session(
|
|||||||
config,
|
config,
|
||||||
ctrlc_exit: ctrlc_exit.clone(),
|
ctrlc_exit: ctrlc_exit.clone(),
|
||||||
exit: false,
|
exit: false,
|
||||||
|
auth_tx: Some(auth_tx),
|
||||||
|
unlocking: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.session_lock = Some(
|
state.session_lock = Some(
|
||||||
@@ -629,6 +678,16 @@ fn lock_wayland_session(
|
|||||||
|
|
||||||
WaylandSource::new(conn, event_queue).insert(event_loop.handle())?;
|
WaylandSource::new(conn, event_queue).insert(event_loop.handle())?;
|
||||||
|
|
||||||
|
// Insert the authentication channel into the event loop
|
||||||
|
event_loop.handle().insert_source(
|
||||||
|
auth_rx,
|
||||||
|
|event: channel::Event<bool>, _metadata, wayland_lock| {
|
||||||
|
if let channel::Event::Msg(result) = event {
|
||||||
|
wayland_lock.handle_auth_result(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
log_to_file("Starting event loop");
|
log_to_file("Starting event loop");
|
||||||
eprintln!("Starting event loop - press Enter to unlock");
|
eprintln!("Starting event loop - press Enter to unlock");
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct Renderer {
|
|||||||
wrong_password_start: Option<Instant>,
|
wrong_password_start: Option<Instant>,
|
||||||
key_highlight_start: Option<Instant>,
|
key_highlight_start: Option<Instant>,
|
||||||
background: Option<ImageSurface>,
|
background: Option<ImageSurface>,
|
||||||
|
password_display: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
@@ -48,6 +49,7 @@ impl Renderer {
|
|||||||
wrong_password_start: None,
|
wrong_password_start: None,
|
||||||
key_highlight_start: None,
|
key_highlight_start: None,
|
||||||
background: None,
|
background: None,
|
||||||
|
password_display: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +85,11 @@ impl Renderer {
|
|||||||
self.key_highlight_start = Some(Instant::now());
|
self.key_highlight_start = Some(Instant::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the password display string (masked)
|
||||||
|
pub fn set_password_display(&mut self, password: String) {
|
||||||
|
self.password_display = password;
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the current frame
|
/// Render the current frame
|
||||||
pub fn render(&mut self) {
|
pub fn render(&mut self) {
|
||||||
// Clear the surface - draw a VISIBLE color (dark gray) instead of black
|
// Clear the surface - draw a VISIBLE color (dark gray) instead of black
|
||||||
@@ -115,6 +122,11 @@ impl Renderer {
|
|||||||
self.draw_indicator();
|
self.draw_indicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw password display (if not empty)
|
||||||
|
if !self.password_display.is_empty() {
|
||||||
|
self.draw_password_display();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw wrong password feedback if active
|
// Draw wrong password feedback if active
|
||||||
if self.wrong_password_shown {
|
if self.wrong_password_shown {
|
||||||
self.draw_wrong_password_feedback();
|
self.draw_wrong_password_feedback();
|
||||||
@@ -243,6 +255,37 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the password display (masked characters)
|
||||||
|
fn draw_password_display(&self) {
|
||||||
|
if self.password_display.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position: below the indicator ring (or centered if no indicator)
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Place password text below the ring
|
||||||
|
let text_y = center_y + radius + thickness + 40.0; // 40px below ring
|
||||||
|
|
||||||
|
self.context.set_font_size(36.0);
|
||||||
|
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha);
|
||||||
|
|
||||||
|
// Center the text
|
||||||
|
let extents = self
|
||||||
|
.context
|
||||||
|
.text_extents(&self.password_display)
|
||||||
|
.expect("Failed to get password text extents");
|
||||||
|
let text_x = center_x - extents.width() / 2.0;
|
||||||
|
|
||||||
|
self.context.move_to(text_x, text_y);
|
||||||
|
self.context
|
||||||
|
.show_text(&self.password_display)
|
||||||
|
.expect("Failed to draw password");
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw wrong password feedback (red flash)
|
/// Draw wrong password feedback (red flash)
|
||||||
fn draw_wrong_password_feedback(&self) {
|
fn draw_wrong_password_feedback(&self) {
|
||||||
let center_x = self.width as f64 / 2.0;
|
let center_x = self.width as f64 / 2.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user