Files
rustlock/src/main.rs
T
2026-03-05 12:40:53 +01:00

685 lines
24 KiB
Rust

mod auth;
mod config;
mod input;
mod lock;
mod render;
mod screenshot;
mod timer;
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};
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 {
env_logger::Builder::from_default_env()
.filter_level(log::LevelFilter::Debug)
.init();
} else {
env_logger::Builder::from_default_env()
.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)?;
}
Ok(())
}
fn lock_wayland_session(
config: Config,
lock_manager: Arc<Mutex<LockManager>>,
ctrlc_exit: Arc<std::sync::atomic::AtomicBool>,
) -> Result<(), Box<dyn Error>> {
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
output::{OutputHandler, OutputState},
reexports::{
calloop::{EventLoop, LoopHandle},
calloop_wayland_source::WaylandSource,
},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{
keyboard::{KeyEvent, KeyboardData, KeyboardHandler, Modifiers, RepeatInfo},
pointer::PointerHandler,
SeatHandler, SeatState,
},
session_lock::{
SessionLock, SessionLockHandler, SessionLockState, SessionLockSurface,
SessionLockSurfaceConfigure,
},
shm::{slot::SlotPool, Shm, ShmHandler},
};
use std::time::Duration;
use wayland_client::{
globals::registry_queue_init,
protocol::{wl_output, wl_surface},
Connection, QueueHandle,
};
struct WaylandLock {
loop_handle: LoopHandle<'static, Self>,
conn: Connection,
compositor_state: CompositorState,
output_state: OutputState,
registry_state: RegistryState,
session_lock_state: SessionLockState,
seat_state: SeatState,
shm_state: Shm,
pool: SlotPool,
session_lock: Option<SessionLock>,
lock_surfaces: Vec<SessionLockSurface>,
lock_manager: Arc<Mutex<LockManager>>,
config: Config,
ctrlc_exit: Arc<std::sync::atomic::AtomicBool>,
exit: bool,
}
impl SessionLockHandler for WaylandLock {
fn locked(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
mut session_lock: SessionLock,
) {
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();
}
}
fn finished(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_session_lock: SessionLock,
) {
log::info!("Session unlocked or lock denied - exiting");
self.exit = true;
}
fn configure(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
session_lock_surface: SessionLockSurface,
configure: SessionLockSurfaceConfigure,
_serial: u32,
) {
let (width, height) = configure.new_size;
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)
} else {
false
};
if !surface_added {
log::error!("Failed to add surface to lock manager");
session_lock_surface.wl_surface().commit();
return;
}
log::debug!("Surface added to lock manager");
self.lock_surfaces.push(session_lock_surface.clone());
if let Ok(mut lock_manager) = self.lock_manager.lock() {
let surface_count = lock_manager.surface_count();
if surface_count == 0 {
log::error!("No surfaces in lock manager");
session_lock_surface.wl_surface().commit();
return;
}
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) =
locked_surface.renderer.surface_info();
let actual_width = width as i32;
let actual_height = height as i32;
let actual_stride = stride;
// Create a buffer from the pool
match self.pool.create_buffer(
actual_width,
actual_height,
actual_stride,
wayland_client::protocol::wl_shm::Format::Argb8888,
) {
Ok((buffer, canvas)) => {
// Copy pixel data to the buffer
let data_len = canvas.len();
let copy_len = pixel_data.len().min(data_len);
canvas[..copy_len].copy_from_slice(&pixel_data[..copy_len]);
// Damage the entire surface
session_lock_surface.wl_surface().damage_buffer(
0,
0,
actual_width,
actual_height,
);
// Attach buffer and commit
if let Err(e) =
buffer.attach_to(session_lock_surface.wl_surface())
{
log::error!("Failed to attach buffer: {:?}", e);
} else {
session_lock_surface.wl_surface().commit();
log::debug!(
"Surface committed with buffer {}x{}",
actual_width,
actual_height
);
}
}
Err(e) => {
log::error!("Failed to create buffer from pool: {:?}", e);
session_lock_surface.wl_surface().commit();
}
}
}
Err(e) => {
log::error!("Failed to get pixel data from Cairo surface: {:?}", e);
session_lock_surface.wl_surface().commit();
}
}
}
}
}
}
impl CompositorHandler for WaylandLock {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_factor: i32,
) {
log::debug!("Scale factor changed");
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_transform: wl_output::Transform,
) {
log::debug!("Transform changed");
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
surface: &wl_surface::WlSurface,
_time: u32,
) {
if let Ok(mut lock_manager) = self.lock_manager.lock() {
lock_manager.update();
if let Some(locked_surface) = lock_manager.find_surface_by_wayland_surface(surface)
{
// For now, just log that we would update the surface
// TODO: Implement proper buffer creation for animation
log::debug!("Frame update for surface");
}
}
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
log::debug!("Surface entered");
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
log::debug!("Surface left");
}
}
impl OutputHandler for WaylandLock {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
output: wl_output::WlOutput,
) {
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(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
log::debug!("Output updated");
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
log::info!("Output destroyed");
}
}
impl ShmHandler for WaylandLock {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm_state
}
}
impl KeyboardHandler for WaylandLock {
fn enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_surface: &wl_surface::WlSurface,
_serial: u32,
_keys: &[u32],
_layout_keysyms: &[smithay_client_toolkit::seat::keyboard::Keysym],
) {
log::info!("Keyboard entered surface (serial: {})", _serial);
log_to_file(&format!("Keyboard entered surface (serial: {})", _serial));
}
fn leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_surface: &wl_surface::WlSurface,
_serial: u32,
) {
log::info!("Keyboard left surface (serial: {})", _serial);
log_to_file(&format!("Keyboard left surface (serial: {})", _serial));
}
fn press_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_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);
}
fn release_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_event: KeyEvent,
) {
log::debug!("Key released (serial: {})", _serial);
}
fn update_modifiers(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_modifiers: Modifiers,
_layout: u32,
) {
log::debug!("Modifiers updated (serial: {})", _serial);
}
fn update_keymap(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>,
) {
log::info!("Keymap updated");
log_to_file("Keymap updated");
}
}
impl PointerHandler for WaylandLock {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &wayland_client::protocol::wl_pointer::WlPointer,
_events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
) {
// Screen locker doesn't need pointer events
}
}
impl WaylandLock {
fn handle_key_event(&mut self, event: KeyEvent) {
use smithay_client_toolkit::seat::keyboard::Keysym;
log_to_file(&format!(
"handle_key_event called: keysym={:?}",
event.keysym
));
if event.keysym == Keysym::Return {
log::info!("Enter pressed - unlocking session (demo mode)");
log_to_file("ENTER pressed - unlocking!");
if let Some(ref session_lock) = self.session_lock.take() {
session_lock.unlock();
}
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 {
log::debug!("Non-character keysym pressed: {:?}", event.keysym);
log_to_file(&format!("Non-char keysym: {:?}", event.keysym));
}
}
}
impl SeatHandler for WaylandLock {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
) {
log::debug!("New seat detected");
}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wayland_client::protocol::wl_seat::WlSeat,
capability: smithay_client_toolkit::seat::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(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
capability: smithay_client_toolkit::seat::Capability,
) {
log::debug!("Capability removed: {:?}", capability);
}
fn remove_seat(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
) {
log::debug!("Seat removed");
}
}
impl ProvidesRegistryState for WaylandLock {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState,];
}
smithay_client_toolkit::delegate_compositor!(WaylandLock);
smithay_client_toolkit::delegate_output!(WaylandLock);
smithay_client_toolkit::delegate_session_lock!(WaylandLock);
smithay_client_toolkit::delegate_registry!(WaylandLock);
smithay_client_toolkit::delegate_shm!(WaylandLock);
smithay_client_toolkit::delegate_seat!(WaylandLock);
smithay_client_toolkit::delegate_keyboard!(WaylandLock);
smithay_client_toolkit::delegate_pointer!(WaylandLock);
let conn = Connection::connect_to_env()?;
let (globals, event_queue) = registry_queue_init(&conn)?;
let qh: QueueHandle<WaylandLock> = event_queue.handle();
let mut event_loop: EventLoop<WaylandLock> = EventLoop::try_new()?;
let mut state = WaylandLock {
loop_handle: event_loop.handle(),
conn: conn.clone(),
compositor_state: CompositorState::bind(&globals, &qh)?,
output_state: OutputState::new(&globals, &qh),
registry_state: RegistryState::new(&globals),
session_lock_state: SessionLockState::new(&globals, &qh),
seat_state: SeatState::new(&globals, &qh),
shm_state: Shm::bind(&globals, &qh).map_err(|_| "wl_shm protocol not supported")?,
pool: SlotPool::new(
1920 * 1080 * 4,
&Shm::bind(&globals, &qh).map_err(|_| "wl_shm protocol not supported")?,
)
.map_err(|e| format!("Failed to create slot pool: {:?}", e))?,
session_lock: None,
lock_surfaces: Vec::new(),
lock_manager,
config,
ctrlc_exit: ctrlc_exit.clone(),
exit: false,
};
state.session_lock = Some(
state
.session_lock_state
.lock(&qh)
.map_err(|_| "ext-session-lock-v1 protocol not supported by 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())?;
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)?;
}
log_to_file("Event loop exited");
log::info!("Event loop exited, exit={}", state.exit);
log_to_file("Function completed");
Ok(())
}
fn run_demonstration_mode(
_config: Config,
lock_manager: Arc<Mutex<LockManager>>,
) -> Result<(), Box<dyn Error>> {
use std::thread;
use std::time::Duration;
println!("wayrustlock - Wayland Screen Locker (Demonstration Mode)");
println!("=========================================================");
println!("Note: Running in demonstration mode because:");
println!("1. Not running on Wayland compositor, OR");
println!("2. ext-session-lock-v1 protocol not available, OR");
println!("3. Wayland connection failed");
println!();
println!("To actually lock your screen, ensure:");
println!("1. You're running on sway, niri, or another Wayland compositor");
println!("2. The compositor supports ext-session-lock-v1 protocol");
println!("3. You have the required Wayland libraries installed");
println!();
{
let mut lock_manager = lock_manager.lock().unwrap();
lock_manager.initialize_lock_surfaces();
log::info!(
"Initialized {} lock surface(s)",
lock_manager.surface_count()
);
}
log::info!("Demonstration mode: Press Ctrl+C to exit");
// For demonstration mode, just run for a short time then exit
let running = Arc::new(std::sync::atomic::AtomicBool::new(true));
std::thread::sleep(Duration::from_secs(30));
log::info!("Exiting demonstration mode");
Ok(())
}