Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76b48f7c70 | |||
| 4ed70fd858 |
@@ -7,14 +7,21 @@ A production-ready Wayland screen locker inspired by swaylock-effects.
|
||||
**This tool is under active development.** Screen lockers can cause system lockups if they malfunction.
|
||||
|
||||
**If the screen locker gets stuck:**
|
||||
- Type password and press **Enter** to unlock (demo mode - any password works)
|
||||
- Switch to another TTY: Press `Ctrl+Alt+F2`, login, then run `pkill -9 wayrustlock`
|
||||
- From another terminal: `pkill -9 wayrustlock` or `killall wayrustlock`
|
||||
- If screen is black/red: hard restart may be required
|
||||
|
||||
**Always test with a timeout first:**
|
||||
**Debug logging:** Check `~/.wayrustlock.log` to see what's happening
|
||||
|
||||
**Test with timeout first:**
|
||||
```bash
|
||||
timeout 5 ./target/release/wayrustlock --indicator --clock
|
||||
# If stuck, system will auto-unlock after 5 seconds
|
||||
timeout 15 ./target/release/wayrustlock --indicator --clock
|
||||
```
|
||||
|
||||
Then check the log file:
|
||||
```bash
|
||||
cat ~/.wayrustlock.log
|
||||
```
|
||||
|
||||
## Features (Implemented vs Planned)
|
||||
|
||||
@@ -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 pam_client::{Context, ErrorCode, Flag};
|
||||
use secstr::SecVec;
|
||||
use smithay_client_toolkit::reexports::{calloop::channel, calloop::EventLoop};
|
||||
use users::get_current_username;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
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 password: Option<PasswordBuffer>,
|
||||
pub password: Option<Zeroizing<String>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
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 {
|
||||
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 {
|
||||
auth_res_send: channel::Sender<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");
|
||||
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>();
|
||||
|
||||
thread::spawn(move || {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ pub struct Config {
|
||||
#[arg(long)]
|
||||
pub clock: bool,
|
||||
|
||||
#[arg(long)]
|
||||
#[arg(long, default_value = "true")]
|
||||
pub indicator: bool,
|
||||
|
||||
#[arg(long, default_value = "100")]
|
||||
|
||||
+106
-29
@@ -135,6 +135,9 @@ impl LockedSurface {
|
||||
self.renderer.set_background(background.clone());
|
||||
}
|
||||
|
||||
self.renderer
|
||||
.set_password_display(self.input_handler.get_display_password());
|
||||
|
||||
// Render the frame
|
||||
self.renderer.render();
|
||||
|
||||
@@ -171,7 +174,10 @@ impl LockedSurface {
|
||||
}
|
||||
|
||||
/// 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
|
||||
// Note: KeyEvent has fields: time, raw_code, keysym, utf8
|
||||
// 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);
|
||||
|
||||
match action {
|
||||
InputAction::SubmitPassword(_password) => {
|
||||
InputAction::SubmitPassword(password) => {
|
||||
// Show key highlight for visual feedback
|
||||
self.show_key_highlight();
|
||||
|
||||
// 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();
|
||||
Some(InputAction::SubmitPassword(password))
|
||||
}
|
||||
InputAction::Cancel => {
|
||||
// Escape key pressed
|
||||
// TODO: Handle cancel action (maybe show quit confirmation?)
|
||||
}
|
||||
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 => {}
|
||||
InputAction::Cancel => Some(InputAction::Cancel),
|
||||
InputAction::TempScreenshot => Some(InputAction::TempScreenshot),
|
||||
InputAction::PasswordChanged => Some(InputAction::PasswordChanged),
|
||||
InputAction::None => 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
|
||||
pub fn as_image_surface(&self) -> &ImageSurface {
|
||||
self.renderer.as_image_surface()
|
||||
@@ -246,7 +316,7 @@ impl LockedSurface {
|
||||
|
||||
/// Manager for all locked surfaces (multiple outputs)
|
||||
pub struct LockManager {
|
||||
surfaces: Vec<LockedSurface>,
|
||||
pub surfaces: Vec<LockedSurface>,
|
||||
config: Config,
|
||||
locked: bool,
|
||||
}
|
||||
@@ -279,13 +349,20 @@ impl LockManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a key event (distribute to all surfaces or focused surface)
|
||||
pub fn handle_key_event(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent) {
|
||||
// For now, send to all surfaces
|
||||
// In a real implementation, we would determine which surface has focus
|
||||
/// Handle a key event and return any action that needs processing
|
||||
/// Returns the first non-None action from any surface
|
||||
pub fn handle_key_event(
|
||||
&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 {
|
||||
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
|
||||
|
||||
+205
-27
@@ -10,9 +10,40 @@ mod util;
|
||||
use config::Config;
|
||||
use lock::LockManager;
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn setup_file_logging() {
|
||||
let log_path =
|
||||
std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()) + "/.wayrustlock.log";
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&log_path)
|
||||
{
|
||||
let _ = writeln!(file, "=== wayrustlock log ===");
|
||||
let _ = writeln!(file, "Started at: {:?}", std::time::SystemTime::now());
|
||||
}
|
||||
eprintln!("Logging to: {}", log_path);
|
||||
}
|
||||
|
||||
fn log_to_file(msg: &str) {
|
||||
let log_path =
|
||||
std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()) + "/.wayrustlock.log";
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
|
||||
let _ = writeln!(file, "[{:?}] {}", std::time::SystemTime::now(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
setup_file_logging();
|
||||
log_to_file("Program starting");
|
||||
|
||||
let config = Config::load();
|
||||
|
||||
if config.debug {
|
||||
@@ -21,22 +52,26 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
.init();
|
||||
} else {
|
||||
env_logger::Builder::from_default_env()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
}
|
||||
|
||||
log::info!("Starting wayrustlock v{}", env!("CARGO_PKG_VERSION"));
|
||||
log_to_file("Starting wayrustlock");
|
||||
log::info!("Attempting to lock Wayland session...");
|
||||
log_to_file("Attempting to lock Wayland session");
|
||||
log::warn!("WARNING: This is a screen locker. To kill it if stuck, use:");
|
||||
log::warn!(" Method 1: Switch to another TTY (Ctrl+Alt+F2) and kill the process");
|
||||
log::warn!(" Method 2: Use 'pkill -9 wayrustlock' from another terminal");
|
||||
log::warn!(" Method 3: Use 'killall wayrustlock'");
|
||||
log_to_file("Warnings printed");
|
||||
|
||||
let lock_manager = Arc::new(Mutex::new(LockManager::new(config.clone())));
|
||||
let ctrlc_exit = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
|
||||
if let Err(e) = lock_wayland_session(config.clone(), lock_manager.clone(), ctrlc_exit) {
|
||||
log::error!("Failed to lock Wayland session: {}", e);
|
||||
log_to_file(&format!("Failed to lock Wayland session: {}", e));
|
||||
log::warn!("Falling back to demonstration mode");
|
||||
run_demonstration_mode(config, lock_manager)?;
|
||||
}
|
||||
@@ -53,7 +88,7 @@ fn lock_wayland_session(
|
||||
compositor::{CompositorHandler, CompositorState},
|
||||
output::{OutputHandler, OutputState},
|
||||
reexports::{
|
||||
calloop::{EventLoop, LoopHandle},
|
||||
calloop::{channel, EventLoop, LoopHandle},
|
||||
calloop_wayland_source::WaylandSource,
|
||||
},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
@@ -92,16 +127,40 @@ fn lock_wayland_session(
|
||||
config: Config,
|
||||
ctrlc_exit: Arc<std::sync::atomic::AtomicBool>,
|
||||
exit: bool,
|
||||
auth_tx: Option<channel::Sender<Zeroizing<String>>>,
|
||||
unlocking: bool,
|
||||
}
|
||||
|
||||
impl SessionLockHandler for WaylandLock {
|
||||
fn locked(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_session_lock: SessionLock,
|
||||
qh: &QueueHandle<Self>,
|
||||
mut session_lock: SessionLock,
|
||||
) {
|
||||
log::info!("Session locked successfully!");
|
||||
log::info!("===========================================");
|
||||
log::info!("Session LOCKED SUCCESSFULLY!");
|
||||
log::info!("===========================================");
|
||||
log_to_file("Session LOCKED - creating surfaces");
|
||||
eprintln!("SESSION LOCKED - creating lock surfaces");
|
||||
|
||||
// Take ownership of session_lock
|
||||
let lock_ref = &mut session_lock;
|
||||
|
||||
// Create lock surfaces for all outputs
|
||||
for output in self.output_state.outputs() {
|
||||
log_to_file(&format!("Creating lock surface for output"));
|
||||
let surface = self.compositor_state.create_surface(qh);
|
||||
let lock_surface = lock_ref.create_lock_surface(surface, &output, qh);
|
||||
self.lock_surfaces.push(lock_surface);
|
||||
}
|
||||
|
||||
log_to_file(&format!(
|
||||
"Created {} lock surfaces",
|
||||
self.lock_surfaces.len()
|
||||
));
|
||||
|
||||
self.session_lock = Some(session_lock);
|
||||
|
||||
if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||
lock_manager.initialize_lock_surfaces();
|
||||
@@ -114,8 +173,17 @@ fn lock_wayland_session(
|
||||
_qh: &QueueHandle<Self>,
|
||||
_session_lock: SessionLock,
|
||||
) {
|
||||
log::info!("Session unlocked or lock denied");
|
||||
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;
|
||||
log_to_file("Setting exit=true from finished callback");
|
||||
}
|
||||
|
||||
fn configure(
|
||||
@@ -127,7 +195,11 @@ fn lock_wayland_session(
|
||||
_serial: u32,
|
||||
) {
|
||||
let (width, height) = configure.new_size;
|
||||
log::debug!("Configuring lock surface: {}x{}", width, height);
|
||||
log::info!("===========================================");
|
||||
log::info!("CONFIGURE callback: {}x{}", width, height);
|
||||
log::info!("===========================================");
|
||||
log_to_file(&format!("CONFIGURE: {}x{}", width, height));
|
||||
eprintln!("CONFIGURE: {}x{}", width, height);
|
||||
|
||||
let surface_added = if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||
lock_manager.add_surface(width as i32, height as i32)
|
||||
@@ -155,6 +227,9 @@ fn lock_wayland_session(
|
||||
if let Some(locked_surface) = lock_manager.get_surface_mut(surface_count - 1) {
|
||||
locked_surface.set_wayland_surface(session_lock_surface.wl_surface().clone());
|
||||
|
||||
// Render the surface first
|
||||
locked_surface.renderer.render();
|
||||
|
||||
match locked_surface.renderer.get_pixel_data() {
|
||||
Ok(pixel_data) => {
|
||||
let (renderer_width, renderer_height, stride) =
|
||||
@@ -283,10 +358,14 @@ fn lock_wayland_session(
|
||||
fn new_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
qh: &QueueHandle<Self>,
|
||||
output: wl_output::WlOutput,
|
||||
) {
|
||||
log::info!("New output detected");
|
||||
log::info!("New output detected: creating lock surface");
|
||||
if let Some(ref session_lock) = self.session_lock {
|
||||
let surface = self.compositor_state.create_surface(qh);
|
||||
let _lock_surface = session_lock.create_lock_surface(surface, &output, qh);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
@@ -325,7 +404,8 @@ fn lock_wayland_session(
|
||||
_keys: &[u32],
|
||||
_layout_keysyms: &[smithay_client_toolkit::seat::keyboard::Keysym],
|
||||
) {
|
||||
log::debug!("Keyboard entered surface (serial: {})", _serial);
|
||||
log::info!("Keyboard entered surface (serial: {})", _serial);
|
||||
log_to_file(&format!("Keyboard entered surface (serial: {})", _serial));
|
||||
}
|
||||
|
||||
fn leave(
|
||||
@@ -336,7 +416,8 @@ fn lock_wayland_session(
|
||||
_surface: &wl_surface::WlSurface,
|
||||
_serial: u32,
|
||||
) {
|
||||
log::debug!("Keyboard left surface (serial: {})", _serial);
|
||||
log::info!("Keyboard left surface (serial: {})", _serial);
|
||||
log_to_file(&format!("Keyboard left surface (serial: {})", _serial));
|
||||
}
|
||||
|
||||
fn press_key(
|
||||
@@ -347,6 +428,12 @@ fn lock_wayland_session(
|
||||
_serial: u32,
|
||||
event: KeyEvent,
|
||||
) {
|
||||
log::info!(
|
||||
"Key pressed (serial: {}, keysym: {:?})",
|
||||
_serial,
|
||||
event.keysym
|
||||
);
|
||||
log_to_file(&format!("Key pressed: keysym={:?}", event.keysym));
|
||||
self.handle_key_event(event);
|
||||
}
|
||||
|
||||
@@ -380,7 +467,8 @@ fn lock_wayland_session(
|
||||
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
|
||||
_keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>,
|
||||
) {
|
||||
log::debug!("Keymap updated");
|
||||
log::info!("Keymap updated");
|
||||
log_to_file("Keymap updated");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,16 +486,62 @@ fn lock_wayland_session(
|
||||
|
||||
impl WaylandLock {
|
||||
fn handle_key_event(&mut self, event: KeyEvent) {
|
||||
use crate::input::InputAction;
|
||||
use smithay_client_toolkit::seat::keyboard::Keysym;
|
||||
|
||||
log_to_file(&format!(
|
||||
"handle_key_event called: keysym={:?}",
|
||||
event.keysym
|
||||
));
|
||||
log::debug!("Key event: {:?}", event.keysym);
|
||||
|
||||
if event.keysym == Keysym::Return {
|
||||
log::info!("Enter pressed - submitting password");
|
||||
} else if event.keysym == Keysym::BackSpace {
|
||||
log::info!("Backspace pressed");
|
||||
} else if let Some(input) = event.utf8 {
|
||||
log::info!("Key pressed: '{}'", input);
|
||||
log::info!("Enter pressed - submitting password for authentication");
|
||||
log_to_file("ENTER pressed - submitting password");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("Non-character keysym pressed: {:?}", event.keysym);
|
||||
// Distribute to all surfaces (BackSpace, characters, etc.)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,11 +563,37 @@ fn lock_wayland_session(
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_seat: wayland_client::protocol::wl_seat::WlSeat,
|
||||
qh: &QueueHandle<Self>,
|
||||
seat: wayland_client::protocol::wl_seat::WlSeat,
|
||||
capability: smithay_client_toolkit::seat::Capability,
|
||||
) {
|
||||
log::debug!("New capability: {:?}", capability);
|
||||
log::info!("New capability: {:?}", capability);
|
||||
log_to_file(&format!("New capability: {:?}", capability));
|
||||
|
||||
if capability == smithay_client_toolkit::seat::Capability::Keyboard {
|
||||
log::info!("Setting up keyboard");
|
||||
log_to_file("Setting up keyboard - trying to get keyboard");
|
||||
|
||||
match self.seat_state.get_keyboard_with_repeat(
|
||||
qh,
|
||||
&seat,
|
||||
None,
|
||||
self.loop_handle.clone(),
|
||||
Box::new(|state, _wl_kbd, event| {
|
||||
log::info!("Keyboard repeat event: {:?}", event);
|
||||
log_to_file(&format!("Keyboard repeat: {:?}", event));
|
||||
}),
|
||||
) {
|
||||
Ok(_keyboard) => {
|
||||
log::info!("Keyboard created successfully");
|
||||
log_to_file("Keyboard created successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create keyboard: {:?}", e);
|
||||
log_to_file(&format!("Failed to create keyboard: {:?}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
@@ -477,6 +637,9 @@ fn lock_wayland_session(
|
||||
let qh: QueueHandle<WaylandLock> = event_queue.handle();
|
||||
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 {
|
||||
loop_handle: event_loop.handle(),
|
||||
conn: conn.clone(),
|
||||
@@ -498,6 +661,8 @@ fn lock_wayland_session(
|
||||
config,
|
||||
ctrlc_exit: ctrlc_exit.clone(),
|
||||
exit: false,
|
||||
auth_tx: Some(auth_tx),
|
||||
unlocking: false,
|
||||
};
|
||||
|
||||
state.session_lock = Some(
|
||||
@@ -508,19 +673,32 @@ fn lock_wayland_session(
|
||||
);
|
||||
|
||||
log::info!("Session lock requested, waiting for compositor...");
|
||||
log_to_file("Session lock requested");
|
||||
eprintln!("Session lock requested - running event loop");
|
||||
|
||||
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");
|
||||
eprintln!("Starting event loop - press Enter to unlock");
|
||||
|
||||
while !state.exit && !state.ctrlc_exit.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
event_loop.dispatch(Duration::from_millis(16), &mut state)?;
|
||||
}
|
||||
|
||||
if state.ctrlc_exit.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
log::warn!("Exiting due to Ctrl+C");
|
||||
// The session lock will be destroyed when the SessionLock object is dropped
|
||||
}
|
||||
log_to_file("Event loop exited");
|
||||
log::info!("Event loop exited, exit={}", state.exit);
|
||||
|
||||
log::info!("Exiting wayrustlock");
|
||||
log_to_file("Function completed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+47
-10
@@ -17,6 +17,7 @@ pub struct Renderer {
|
||||
wrong_password_start: Option<Instant>,
|
||||
key_highlight_start: Option<Instant>,
|
||||
background: Option<ImageSurface>,
|
||||
password_display: String,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@@ -48,6 +49,7 @@ impl Renderer {
|
||||
wrong_password_start: None,
|
||||
key_highlight_start: None,
|
||||
background: None,
|
||||
password_display: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +85,15 @@ impl Renderer {
|
||||
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
|
||||
pub fn render(&mut self) {
|
||||
// Clear the surface
|
||||
self.context.set_source_rgba(0.0, 0.0, 0.0, 1.0);
|
||||
// Clear the surface - draw a VISIBLE color (dark gray) instead of black
|
||||
self.context.set_source_rgba(0.15, 0.15, 0.15, 1.0);
|
||||
self.context.paint().expect("Failed to clear surface");
|
||||
|
||||
// Draw background if available
|
||||
@@ -98,14 +105,8 @@ impl Renderer {
|
||||
.paint_with_alpha(self.fade_alpha)
|
||||
.expect("Failed to draw background");
|
||||
} else {
|
||||
// Draw solid color background
|
||||
let bg_color = self.tuple_to_color((0.0, 0.0, 0.0, 1.0));
|
||||
self.context.set_source_rgba(
|
||||
bg_color.r as f64 / 255.0,
|
||||
bg_color.g as f64 / 255.0,
|
||||
bg_color.b as f64 / 255.0,
|
||||
bg_color.a as f64 / 255.0 * self.fade_alpha,
|
||||
);
|
||||
// Draw solid color background (dark gray visible color)
|
||||
self.context.set_source_rgba(0.15, 0.15, 0.15, 1.0);
|
||||
self.context
|
||||
.paint()
|
||||
.expect("Failed to draw solid background");
|
||||
@@ -121,6 +122,11 @@ impl Renderer {
|
||||
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
|
||||
if self.wrong_password_shown {
|
||||
self.draw_wrong_password_feedback();
|
||||
@@ -249,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)
|
||||
fn draw_wrong_password_feedback(&self) {
|
||||
let center_x = self.width as f64 / 2.0;
|
||||
|
||||
Reference in New Issue
Block a user