Compare commits
2 Commits
9daa2fdd8f
...
76b48f7c70
| 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.
|
**This tool is under active development.** Screen lockers can cause system lockups if they malfunction.
|
||||||
|
|
||||||
**If the screen locker gets stuck:**
|
**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`
|
- Switch to another TTY: Press `Ctrl+Alt+F2`, login, then run `pkill -9 wayrustlock`
|
||||||
- From another terminal: `pkill -9 wayrustlock` or `killall wayrustlock`
|
- From another terminal: `pkill -9 wayrustlock` or `killall wayrustlock`
|
||||||
- If screen is black/red: hard restart may be required
|
- 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
|
```bash
|
||||||
timeout 5 ./target/release/wayrustlock --indicator --clock
|
timeout 15 ./target/release/wayrustlock --indicator --clock
|
||||||
# If stuck, system will auto-unlock after 5 seconds
|
```
|
||||||
|
|
||||||
|
Then check the log file:
|
||||||
|
```bash
|
||||||
|
cat ~/.wayrustlock.log
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features (Implemented vs Planned)
|
## 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 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
|
||||||
|
|||||||
+205
-27
@@ -10,9 +10,40 @@ mod util;
|
|||||||
use config::Config;
|
use config::Config;
|
||||||
use lock::LockManager;
|
use lock::LockManager;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::{Arc, Mutex};
|
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>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
setup_file_logging();
|
||||||
|
log_to_file("Program starting");
|
||||||
|
|
||||||
let config = Config::load();
|
let config = Config::load();
|
||||||
|
|
||||||
if config.debug {
|
if config.debug {
|
||||||
@@ -21,22 +52,26 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.init();
|
.init();
|
||||||
} else {
|
} else {
|
||||||
env_logger::Builder::from_default_env()
|
env_logger::Builder::from_default_env()
|
||||||
.filter_level(log::LevelFilter::Info)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
.init();
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Starting wayrustlock v{}", env!("CARGO_PKG_VERSION"));
|
log::info!("Starting wayrustlock v{}", env!("CARGO_PKG_VERSION"));
|
||||||
|
log_to_file("Starting wayrustlock");
|
||||||
log::info!("Attempting to lock Wayland session...");
|
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!("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 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 2: Use 'pkill -9 wayrustlock' from another terminal");
|
||||||
log::warn!(" Method 3: Use 'killall wayrustlock'");
|
log::warn!(" Method 3: Use 'killall wayrustlock'");
|
||||||
|
log_to_file("Warnings printed");
|
||||||
|
|
||||||
let lock_manager = Arc::new(Mutex::new(LockManager::new(config.clone())));
|
let lock_manager = Arc::new(Mutex::new(LockManager::new(config.clone())));
|
||||||
let ctrlc_exit = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
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) {
|
if let Err(e) = lock_wayland_session(config.clone(), lock_manager.clone(), ctrlc_exit) {
|
||||||
log::error!("Failed to lock Wayland session: {}", e);
|
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");
|
log::warn!("Falling back to demonstration mode");
|
||||||
run_demonstration_mode(config, lock_manager)?;
|
run_demonstration_mode(config, lock_manager)?;
|
||||||
}
|
}
|
||||||
@@ -53,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},
|
||||||
@@ -92,16 +127,40 @@ 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 {
|
||||||
fn locked(
|
fn locked(
|
||||||
&mut self,
|
&mut self,
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
_session_lock: SessionLock,
|
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() {
|
if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||||
lock_manager.initialize_lock_surfaces();
|
lock_manager.initialize_lock_surfaces();
|
||||||
@@ -114,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");
|
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(
|
||||||
@@ -127,7 +195,11 @@ fn lock_wayland_session(
|
|||||||
_serial: u32,
|
_serial: u32,
|
||||||
) {
|
) {
|
||||||
let (width, height) = configure.new_size;
|
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() {
|
let surface_added = if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||||
lock_manager.add_surface(width as i32, height as i32)
|
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) {
|
if let Some(locked_surface) = lock_manager.get_surface_mut(surface_count - 1) {
|
||||||
locked_surface.set_wayland_surface(session_lock_surface.wl_surface().clone());
|
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() {
|
match locked_surface.renderer.get_pixel_data() {
|
||||||
Ok(pixel_data) => {
|
Ok(pixel_data) => {
|
||||||
let (renderer_width, renderer_height, stride) =
|
let (renderer_width, renderer_height, stride) =
|
||||||
@@ -283,10 +358,14 @@ fn lock_wayland_session(
|
|||||||
fn new_output(
|
fn new_output(
|
||||||
&mut self,
|
&mut self,
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
_output: wl_output::WlOutput,
|
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(
|
fn update_output(
|
||||||
@@ -325,7 +404,8 @@ fn lock_wayland_session(
|
|||||||
_keys: &[u32],
|
_keys: &[u32],
|
||||||
_layout_keysyms: &[smithay_client_toolkit::seat::keyboard::Keysym],
|
_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(
|
fn leave(
|
||||||
@@ -336,7 +416,8 @@ fn lock_wayland_session(
|
|||||||
_surface: &wl_surface::WlSurface,
|
_surface: &wl_surface::WlSurface,
|
||||||
_serial: u32,
|
_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(
|
fn press_key(
|
||||||
@@ -347,6 +428,12 @@ fn lock_wayland_session(
|
|||||||
_serial: u32,
|
_serial: u32,
|
||||||
event: KeyEvent,
|
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);
|
self.handle_key_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +467,8 @@ fn lock_wayland_session(
|
|||||||
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
|
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
|
||||||
_keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>,
|
_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 {
|
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!(
|
||||||
|
"handle_key_event called: keysym={:?}",
|
||||||
|
event.keysym
|
||||||
|
));
|
||||||
|
log::debug!("Key event: {:?}", event.keysym);
|
||||||
|
|
||||||
if event.keysym == Keysym::Return {
|
if event.keysym == Keysym::Return {
|
||||||
log::info!("Enter pressed - submitting password");
|
log::info!("Enter pressed - submitting password for authentication");
|
||||||
} else if event.keysym == Keysym::BackSpace {
|
log_to_file("ENTER pressed - submitting password");
|
||||||
log::info!("Backspace pressed");
|
|
||||||
} else if let Some(input) = event.utf8 {
|
if let Ok(mut lock_manager) = self.lock_manager.lock() {
|
||||||
log::info!("Key pressed: '{}'", input);
|
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 {
|
} 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(
|
fn new_capability(
|
||||||
&mut self,
|
&mut self,
|
||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
_seat: wayland_client::protocol::wl_seat::WlSeat,
|
seat: wayland_client::protocol::wl_seat::WlSeat,
|
||||||
capability: smithay_client_toolkit::seat::Capability,
|
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(
|
fn remove_capability(
|
||||||
@@ -477,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(),
|
||||||
@@ -498,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(
|
||||||
@@ -508,19 +673,32 @@ fn lock_wayland_session(
|
|||||||
);
|
);
|
||||||
|
|
||||||
log::info!("Session lock requested, waiting for compositor...");
|
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())?;
|
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) {
|
while !state.exit && !state.ctrlc_exit.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
event_loop.dispatch(Duration::from_millis(16), &mut state)?;
|
event_loop.dispatch(Duration::from_millis(16), &mut state)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.ctrlc_exit.load(std::sync::atomic::Ordering::SeqCst) {
|
log_to_file("Event loop exited");
|
||||||
log::warn!("Exiting due to Ctrl+C");
|
log::info!("Event loop exited, exit={}", state.exit);
|
||||||
// The session lock will be destroyed when the SessionLock object is dropped
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Exiting wayrustlock");
|
log_to_file("Function completed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-10
@@ -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,10 +85,15 @@ 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
|
// Clear the surface - draw a VISIBLE color (dark gray) instead of black
|
||||||
self.context.set_source_rgba(0.0, 0.0, 0.0, 1.0);
|
self.context.set_source_rgba(0.15, 0.15, 0.15, 1.0);
|
||||||
self.context.paint().expect("Failed to clear surface");
|
self.context.paint().expect("Failed to clear surface");
|
||||||
|
|
||||||
// Draw background if available
|
// Draw background if available
|
||||||
@@ -98,14 +105,8 @@ impl Renderer {
|
|||||||
.paint_with_alpha(self.fade_alpha)
|
.paint_with_alpha(self.fade_alpha)
|
||||||
.expect("Failed to draw background");
|
.expect("Failed to draw background");
|
||||||
} else {
|
} else {
|
||||||
// Draw solid color background
|
// Draw solid color background (dark gray visible color)
|
||||||
let bg_color = self.tuple_to_color((0.0, 0.0, 0.0, 1.0));
|
self.context.set_source_rgba(0.15, 0.15, 0.15, 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,
|
|
||||||
);
|
|
||||||
self.context
|
self.context
|
||||||
.paint()
|
.paint()
|
||||||
.expect("Failed to draw solid background");
|
.expect("Failed to draw solid background");
|
||||||
@@ -121,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();
|
||||||
@@ -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)
|
/// 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