Added grace implementation

This commit is contained in:
2026-03-07 22:06:22 +01:00
parent 88f7833d8d
commit 5b3377a15b
8 changed files with 384 additions and 161 deletions
Generated
+26 -26
View File
@@ -1590,6 +1590,32 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "rustlock"
version = "0.1.0"
dependencies = [
"anyhow",
"cairo-rs",
"chrono",
"clap",
"env_logger",
"fastblur",
"futures",
"image",
"log",
"pam-client",
"secstr",
"serde",
"smithay-client-toolkit",
"toml 1.0.3+spec-1.1.0",
"users",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
"xkbcommon",
"zeroize",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -2085,32 +2111,6 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "wayrustlock"
version = "0.1.0"
dependencies = [
"anyhow",
"cairo-rs",
"chrono",
"clap",
"env_logger",
"fastblur",
"futures",
"image",
"log",
"pam-client",
"secstr",
"serde",
"smithay-client-toolkit",
"toml 1.0.3+spec-1.1.0",
"users",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
"xkbcommon",
"zeroize",
]
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.12" version = "0.1.12"
+1 -1
View File
@@ -1,5 +1,5 @@
[package] [package]
name = "wayrustlock" name = "rustlock"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
+51 -39
View File
@@ -1,35 +1,42 @@
# wayrustlock # rustlock
A premium, high-performance Wayland screen locker written in Rust, designed to replicate and exceed the capabilities of `swaylock-effects` while ensuring stability on modern compositors like Niri and Sway. A high-performance Wayland screen locker written in Rust, inspired by `swaylock-effects`.
## Features ## Features
- **Blazing Fast Performance:** Written in 100% safe Rust for maximum efficiency and security. - **Performance**: Written in safe Rust with minimal dependencies
- **Advanced Visual Effects:** - **Visual Effects**:
- **Gaussian Blur:** Highly optimized blur effect for your desktop background. - Gaussian blur (configurable radius and passes)
- **Vignette:** Add a professional vignette effect to darken the edges of your screen. - Vignette effect (configurable base and factor)
- **Fade-in Animation:** Smooth transition from your desktop to the lock screen. - Smooth fade-in animation
- **Iconic swaylock-effects UI:** - **Password Indicator**:
- **Dynamic Rotating Highlights:** Premium visual feedback as you type, with segments that appear at random angles and rotate dynamically. - Circular ring with configurable radius and thickness
- **Internal Information Hub:** A perfectly centered clock, date, and system uptime indicator inside the ring. - Dynamic key highlight segments that rotate with each keystroke
- **Custom Colors:** Full control over ring, inside, separator, and highlight colors. - Customizable colors (ring, inside, separator, highlight)
- **Robust Logic:** - **Information Display**:
- **Niri Compatibility:** Fixed protocol violations that cause red screens on Niri. - Centered clock (HH:MM format)
- **Pre-lock Screenshot:** Captures your desktop *before* locking to ensure visual effects work perfectly even if the compositor hides surfaces immediately. - Full date
- **Grace Period:** Configurable unlock grace period for convenience. - System uptime
- **Safe Exit:** Always waits for the compositor's confirmation before exiting, preventing session corruption. - **Screenshot Support**:
- **Permanent Logging:** Verbose debug logs are always captured in `~/.wayrustlock.log` for immediate troubleshooting. - Captures desktop background before locking
- Applies visual effects to background
- **Authentication**:
- PAM-based authentication
- Configurable grace period (any key press within N seconds unlocks without password)
- **Logging**: Verbose debug logs written to `~/.rustlock.log`
## Usage ## Usage
### Basic Command ### Basic Example
```bash ```bash
wayrustlock --screenshots --effect-blur 7x5 --effect-vignette 0.5:0.5 rustlock --screenshots --effect-blur 7x5 --effect-vignette 0.5:0.5
``` ```
### Full Configuration Example ### Full Configuration
```bash ```bash
wayrustlock \ rustlock \
--screenshots \ --screenshots \
--clock \ --clock \
--indicator \ --indicator \
@@ -46,36 +53,41 @@ wayrustlock \
--fade-in 0.2 --fade-in 0.2
``` ```
## Configuration File ## Configuration
You can also provide settings via a configuration file located at `~/.config/wayrustlock/config.toml`. Note that CLI arguments always take precedence over the config file. Options can be provided via command line or a configuration file at `~/.config/rustlock/config.toml`. CLI arguments take precedence.
## Options ### Options
| Option | Description | | Option | Description |
|--------|-------------| |--------|-------------|
| `--screenshots` | Enable desktop background capture. | | `--screenshots` | Capture desktop background before locking |
| `--clock` | Show the centered clock and uptime. | | `--clock` | Display centered clock and date |
| `--indicator` | Show the password indicator ring. | | `--indicator` | Show password entry ring (default: true) |
| `--indicator-radius` | Radius of the indicator ring (default: 100). | | `--indicator-radius <N>` | Ring radius in pixels (default: 100) |
| `--indicator-thickness` | Thickness of the indicator ring (default: 7). | | `--indicator-thickness <N>` | Ring thickness in pixels (default: 7) |
| `--effect-blur` | Gaussian blur settings (e.g., `7x5` for radius 7, 5 passes). | | `--effect-blur <R>x<P>` | Gaussian blur: radius x passes (e.g., `7x5`) |
| `--effect-vignette` | Vignette settings (e.g., `0.5:0.5` for base:factor). | | `--effect-vignette <B>:<F>` | Vignette: base:factor (e.g., `0.5:0.5`) |
| `--ring-color` | Color of the outer ring (RRGGBB[AA]). | | `--ring-color <RRGGBB[AA]>` | Outer ring color (hex, optional alpha) |
| `--inside-color` | Color of the inner circle (RRGGBB[AA]). | | `--key-hl-color <RRGGBB[AA]>` | Key highlight segment color |
| `--key-hl-color` | Color of the key highlight segments (RRGGBB[AA]). | | `--line-color <RRGGBB[AA]>` | Separator line color |
| `--grace` | Unlock grace period in seconds (default: 2). | | `--inside-color <RRGGBB[AA]>` | Inner circle color |
| `--fade-in` | Fade-in animation duration in seconds (default: 0.2). | | `--separator-color <RRGGBB[AA]>` | Ring separator color |
| `--grace <SECONDS>` | Grace period in seconds (default: 2) |
| `--fade-in <SECONDS>` | Fade-in animation duration (default: 0.2) |
| `--pam-service <NAME>` | PAM service name (default: "rustlock") |
| `--config <PATH>` | Path to config file |
| `--debug` | Enable debug logging |
| `--log-file` | Write logs to `~/.rustlock.log` |
| `--temp-screenshot` | Enable peek feature (press 'p' to temporarily show background) |
## Installation ## Installation
Ensure you have a Rust toolchain and the necessary Wayland development libraries installed, then run:
```bash ```bash
cargo build --release cargo build --release
``` ```
The binary will be available at `target/release/wayrustlock`. The binary will be available at `target/release/rustlock`.
## License ## License
+1 -1
View File
@@ -1,7 +1,7 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
pkgs.rustPlatform.buildRustPackage { pkgs.rustPlatform.buildRustPackage {
pname = "wayrustlock"; pname = "rustlock";
version = "0.1.0"; version = "0.1.0";
src = ./.; src = ./.;
cargoLock = { lockFile = ./Cargo.lock; }; cargoLock = { lockFile = ./Cargo.lock; };
+2 -2
View File
@@ -1,5 +1,5 @@
# PAM configuration for wayrustlock # PAM configuration for rustlock
# Install this file to /etc/pam.d/wayrustlock # Install this file to /etc/pam.d/rustlock
# Use the standard login service authentication # Use the standard login service authentication
auth include login auth include login
+19 -27
View File
@@ -7,7 +7,7 @@ use smithay_client_toolkit::reexports::{calloop::channel, calloop::EventLoop};
use users::get_current_username; use users::get_current_username;
use zeroize::Zeroizing; use zeroize::Zeroizing;
const SERVICE_NAME: &str = "wayrustlock"; const SERVICE_NAME: &str = "rustlock";
pub struct LockConversation { pub struct LockConversation {
pub password: Option<Zeroizing<String>>, pub password: Option<Zeroizing<String>>,
@@ -36,12 +36,6 @@ impl pam_client::ConversationHandler for LockConversation {
} }
pub fn create_and_run_auth_loop() -> (channel::Sender<Zeroizing<String>>, 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,
context: pam_client::Context<LockConversation>,
}
let username = get_current_username() let username = get_current_username()
.expect("Failed to get username") .expect("Failed to get username")
.to_str() .to_str()
@@ -49,7 +43,7 @@ pub fn create_and_run_auth_loop() -> (channel::Sender<Zeroizing<String>>, channe
.to_string(); .to_string();
let conversation = LockConversation { password: None }; let conversation = LockConversation { password: None };
let context = Context::new(SERVICE_NAME, Some(username.as_str()), conversation) let _context = Context::new(SERVICE_NAME, Some(username.as_str()), conversation)
.expect("Failed to initialize PAM context"); .expect("Failed to initialize PAM context");
debug!("Prepared to authenticate user '{}'", username); debug!("Prepared to authenticate user '{}'", username);
@@ -57,35 +51,33 @@ pub fn create_and_run_auth_loop() -> (channel::Sender<Zeroizing<String>>, channe
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 || {
let mut event_loop: EventLoop<AuthLoopState> = EventLoop::try_new().unwrap(); let mut event_loop: EventLoop<()> = EventLoop::try_new().unwrap();
event_loop event_loop
.handle() .handle()
.insert_source(auth_req_recv, |evt, _metadata, state| match evt { .insert_source(auth_req_recv, |evt, _metadata, _state| match evt {
channel::Event::Msg(password) => { channel::Event::Msg(password) => {
state.context.conversation_mut().password = Some(password); let conversation = LockConversation {
let status = match state.context.authenticate(Flag::NONE) { password: Some(password),
Ok(()) => true, };
let mut context =
Context::new(SERVICE_NAME, Some(username.as_str()), conversation)
.expect("Failed to initialize PAM context");
match context.authenticate(Flag::NONE) {
Ok(()) => {
auth_res_send.send(true).unwrap();
}
Err(err) => { Err(err) => {
error!("Pam authenticate failed with {:?}", err); error!("Pam authenticate failed with {:?}", err);
false auth_res_send.send(false).unwrap();
} }
}; }
state.auth_res_send.send(status).unwrap();
} }
channel::Event::Closed => state.main_closed = true, channel::Event::Closed => {}
}) })
.unwrap(); .unwrap();
let mut state = AuthLoopState { loop {
auth_res_send, event_loop.dispatch(None, &mut ()).expect("Failed to run");
main_closed: false,
context,
};
while !state.main_closed {
event_loop
.dispatch(None, &mut state)
.expect("Failed to run");
} }
}); });
+2 -2
View File
@@ -57,7 +57,7 @@ pub struct Config {
#[arg(long)] #[arg(long)]
pub debug: bool, pub debug: bool,
/// Write verbose logs to ~/.wayrustlock.log /// Write verbose logs to ~/.rustlock.log
#[arg(long)] #[arg(long)]
pub log_file: bool, pub log_file: bool,
@@ -73,7 +73,7 @@ impl Config {
// Use path from CLI if provided, otherwise default // Use path from CLI if provided, otherwise default
let config_path = cli_config.config.clone().unwrap_or_else(|| { let config_path = cli_config.config.clone().unwrap_or_else(|| {
let mut path = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_default()); let mut path = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_default());
path.push(".config/wayrustlock/config.toml"); path.push(".config/rustlock/config.toml");
path path
}); });
+276 -57
View File
@@ -9,16 +9,18 @@ mod util;
use config::Config; use config::Config;
use lock::LockManager; use lock::LockManager;
use screenshot::{CaptureData, ScreenshotManager, Screenshot}; use screenshot::{CaptureData, Screenshot, ScreenshotManager};
use std::error::Error; 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 std::time::Duration; use std::time::{Duration, Instant};
use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_output::WlOutput;
use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::{Connection, Dispatch, QueueHandle, Proxy}; use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ZwlrScreencopyFrameV1, Flags}; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{
Flags, ZwlrScreencopyFrameV1,
};
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
use zeroize::Zeroizing; use zeroize::Zeroizing;
@@ -45,9 +47,7 @@ static FILE_LOGGER: std::sync::LazyLock<std::sync::Mutex<Option<std::fs::File>>>
std::sync::LazyLock::new(|| std::sync::Mutex::new(None)); std::sync::LazyLock::new(|| std::sync::Mutex::new(None));
fn setup_file_logging(_config: &Config) { fn setup_file_logging(_config: &Config) {
let log_path = std::env::var("HOME") let log_path = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()) + "/.rustlock.log";
.unwrap_or_else(|_| "/tmp".to_string())
+ "/.wayrustlock.log";
match OpenOptions::new() match OpenOptions::new()
.create(true) .create(true)
@@ -124,10 +124,14 @@ struct WaylandLock {
exit: bool, exit: bool,
unlocking: bool, unlocking: bool,
screenshot_manager: Option<ScreenshotManager>, screenshot_manager: Option<ScreenshotManager>,
grace_until: Option<Instant>,
} }
impl WaylandLock { impl WaylandLock {
fn handle_auth_result(&mut self, success: bool) { fn handle_auth_result(&mut self, success: bool) {
// Clear grace period on any auth result
self.grace_until = None;
if success { if success {
log::info!("✅ Authentication successful - unlocking session"); log::info!("✅ Authentication successful - unlocking session");
if let Some(session_lock) = &self.session_lock { if let Some(session_lock) = &self.session_lock {
@@ -159,6 +163,16 @@ impl WaylandLock {
} }
fn handle_key_event(&mut self, event: KeyEvent) { fn handle_key_event(&mut self, event: KeyEvent) {
// Check if we're in the grace period (any key unlocks without password)
if let Some(grace_until) = self.grace_until {
if Instant::now() < grace_until {
self.handle_auth_result(true);
return;
} else {
self.grace_until = None;
}
}
use crate::input::InputAction; use crate::input::InputAction;
use smithay_client_toolkit::seat::keyboard::Keysym; use smithay_client_toolkit::seat::keyboard::Keysym;
@@ -167,7 +181,9 @@ impl WaylandLock {
if let Ok(mut lock_manager) = self.lock_manager.lock() { if let Ok(mut lock_manager) = self.lock_manager.lock() {
let mut password = Zeroizing::new(String::new()); let mut password = Zeroizing::new(String::new());
for surface in &mut lock_manager.surfaces { for surface in &mut lock_manager.surfaces {
if let Some(InputAction::SubmitPassword(p)) = surface.handle_key_event(event.clone()) { if let Some(InputAction::SubmitPassword(p)) =
surface.handle_key_event(event.clone())
{
password = p; password = p;
} }
} }
@@ -194,32 +210,75 @@ impl WaylandLock {
} }
impl ProvidesRegistryState for WaylandLock { impl ProvidesRegistryState for WaylandLock {
fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState]; registry_handlers![OutputState, SeatState];
} }
impl CompositorHandler for WaylandLock { impl CompositorHandler for WaylandLock {
fn scale_factor_changed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _surface: &WlSurface, _new_factor: i32) {} fn scale_factor_changed(
fn transform_changed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _surface: &WlSurface, _new_transform: wayland_client::protocol::wl_output::Transform) {} &mut self,
fn frame(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _surface: &WlSurface, _time: u32) {} _conn: &Connection,
fn surface_enter(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _surface: &WlSurface, _output: &WlOutput) {} _qh: &QueueHandle<Self>,
fn surface_leave(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _surface: &WlSurface, _output: &WlOutput) {} _surface: &WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_new_transform: wayland_client::protocol::wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_time: u32,
) {
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_output: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_output: &WlOutput,
) {
}
} }
impl OutputHandler for WaylandLock { impl OutputHandler for WaylandLock {
fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {} fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {}
fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {} fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {}
fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {} fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: WlOutput) {
}
} }
impl ShmHandler for WaylandLock { impl ShmHandler for WaylandLock {
fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } fn shm_state(&mut self) -> &mut Shm {
&mut self.shm_state
}
} }
impl SessionLockHandler for WaylandLock { impl SessionLockHandler for WaylandLock {
fn locked(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _session_lock: SessionLock) { fn locked(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _session_lock: SessionLock) {
log::info!("Session LOCKED confirmed by compositor"); log::info!("Session LOCKED confirmed by compositor");
self.grace_until = Some(Instant::now() + Duration::from_secs_f32(self.config.grace));
} }
fn finished(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _lock: SessionLock) { fn finished(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _lock: SessionLock) {
@@ -239,7 +298,9 @@ impl SessionLockHandler for WaylandLock {
log::debug!("CONFIGURE callback: {}x{}", width, height); log::debug!("CONFIGURE callback: {}x{}", width, height);
if let Ok(mut lock_manager) = self.lock_manager.lock() { if let Ok(mut lock_manager) = self.lock_manager.lock() {
if let Some(locked_surface) = lock_manager.find_surface_by_wayland_surface(session_lock_surface.wl_surface()) { if let Some(locked_surface) =
lock_manager.find_surface_by_wayland_surface(session_lock_surface.wl_surface())
{
locked_surface.resize(width as i32, height as i32); locked_surface.resize(width as i32, height as i32);
locked_surface.set_configured(); locked_surface.set_configured();
@@ -249,7 +310,11 @@ impl SessionLockHandler for WaylandLock {
if let Some(idx) = output_idx { if let Some(idx) = output_idx {
if let Some(bg) = self.captured_backgrounds.get(idx).and_then(|b| b.as_ref()) { if let Some(bg) = self.captured_backgrounds.get(idx).and_then(|b| b.as_ref()) {
log::info!("Applying background for output {} (ID: {:?})", idx, output_id); log::info!(
"Applying background for output {} (ID: {:?})",
idx,
output_id
);
locked_surface.set_background(bg.clone()); locked_surface.set_background(bg.clone());
} }
} }
@@ -262,40 +327,143 @@ impl SessionLockHandler for WaylandLock {
} }
impl KeyboardHandler for WaylandLock { impl KeyboardHandler for WaylandLock {
fn enter(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, _surface: &WlSurface, _serial: u32, _raw: &[u32], _keysyms: &[smithay_client_toolkit::seat::keyboard::Keysym]) {} fn enter(
fn leave(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, _surface: &WlSurface, _serial: u32) {} &mut self,
fn press_key(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, _serial: u32, event: KeyEvent) { _conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_surface: &WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[smithay_client_toolkit::seat::keyboard::Keysym],
) {
}
fn leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_surface: &WlSurface,
_serial: u32,
) {
}
fn press_key(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
self.handle_key_event(event); 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) {} fn release_key(
fn update_modifiers(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, _serial: u32, _modifiers: Modifiers, _layout: u32) {} &mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_event: KeyEvent,
) {
}
fn update_modifiers(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_modifiers: Modifiers,
_layout: u32,
) {
}
} }
impl SeatHandler for WaylandLock { impl SeatHandler for WaylandLock {
fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn seat_state(&mut self) -> &mut SeatState {
fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wayland_client::protocol::wl_seat::WlSeat) {} &mut self.seat_state
fn new_capability(&mut self, _conn: &Connection, qh: &QueueHandle<Self>, seat: wayland_client::protocol::wl_seat::WlSeat, capability: smithay_client_toolkit::seat::Capability) { }
fn new_seat(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
) {
}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wayland_client::protocol::wl_seat::WlSeat,
capability: smithay_client_toolkit::seat::Capability,
) {
if capability == smithay_client_toolkit::seat::Capability::Keyboard { if capability == smithay_client_toolkit::seat::Capability::Keyboard {
let _ = self.seat_state.get_keyboard_with_repeat(qh, &seat, None, self.loop_handle.clone(), Box::new(|_state, _kbd, _event| {})); let _ = self.seat_state.get_keyboard_with_repeat(
qh,
&seat,
None,
self.loop_handle.clone(),
Box::new(|_state, _kbd, _event| {}),
);
} }
} }
fn remove_capability(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wayland_client::protocol::wl_seat::WlSeat, _capability: smithay_client_toolkit::seat::Capability) {} fn remove_capability(
fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wayland_client::protocol::wl_seat::WlSeat) {} &mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
_capability: smithay_client_toolkit::seat::Capability,
) {
}
fn remove_seat(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_seat: wayland_client::protocol::wl_seat::WlSeat,
) {
}
} }
impl Dispatch<ZwlrScreencopyManagerV1, ()> for WaylandLock { impl Dispatch<ZwlrScreencopyManagerV1, ()> for WaylandLock {
fn event(_state: &mut Self, _proxy: &ZwlrScreencopyManagerV1, _event: <ZwlrScreencopyManagerV1 as Proxy>::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle<Self>) {} fn event(
_state: &mut Self,
_proxy: &ZwlrScreencopyManagerV1,
_event: <ZwlrScreencopyManagerV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
}
} }
impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock { impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
fn event(state: &mut Self, frame: &ZwlrScreencopyFrameV1, event: <ZwlrScreencopyFrameV1 as Proxy>::Event, data: &CaptureData, _conn: &Connection, _qh: &QueueHandle<Self>) { fn event(
state: &mut Self,
frame: &ZwlrScreencopyFrameV1,
event: <ZwlrScreencopyFrameV1 as Proxy>::Event,
data: &CaptureData,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::Event; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::Event;
match event { match event {
Event::Buffer { format, width, height, stride } => { Event::Buffer {
format,
width,
height,
stride,
} => {
let format = format.into_result().unwrap(); let format = format.into_result().unwrap();
let mut info = data.info.lock().unwrap(); let mut info = data.info.lock().unwrap();
*info = Some(screenshot::BufferInfo { width, height, stride, format }); *info = Some(screenshot::BufferInfo {
match state.pool.create_buffer(width as i32, height as i32, stride as i32, format) { width,
height,
stride,
format,
});
match state
.pool
.create_buffer(width as i32, height as i32, stride as i32, format)
{
Ok((buffer, _canvas)) => { Ok((buffer, _canvas)) => {
frame.copy(buffer.wl_buffer()); frame.copy(buffer.wl_buffer());
*data.buffer.lock().unwrap() = Some(buffer); *data.buffer.lock().unwrap() = Some(buffer);
@@ -303,7 +471,9 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
Err(e) => log::error!("Screencopy: Buffer creation failed: {:?}", e), Err(e) => log::error!("Screencopy: Buffer creation failed: {:?}", e),
} }
} }
Event::Flags { flags } => { *data.flags.lock().unwrap() = Some(flags.into_result().unwrap()); } Event::Flags { flags } => {
*data.flags.lock().unwrap() = Some(flags.into_result().unwrap());
}
Event::Ready { .. } => { Event::Ready { .. } => {
log::info!("Screencopy: Ready for output {}", data.output_idx); log::info!("Screencopy: Ready for output {}", data.output_idx);
if let Some(mgr) = &state.screenshot_manager { if let Some(mgr) = &state.screenshot_manager {
@@ -311,7 +481,11 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
let info = data.info.lock().unwrap().take(); let info = data.info.lock().unwrap().take();
let flags = data.flags.lock().unwrap().take(); let flags = data.flags.lock().unwrap().take();
if let (Some(buffer), Some(info), Some(flags)) = (buffer, info, flags) { if let (Some(buffer), Some(info), Some(flags)) = (buffer, info, flags) {
let handle = screenshot::ScreencopyBufferHandle { buffer, info, y_invert: flags.contains(Flags::YInvert) }; let handle = screenshot::ScreencopyBufferHandle {
buffer,
info,
y_invert: flags.contains(Flags::YInvert),
};
if let Ok(surface) = mgr.buffer_to_surface(handle, &mut state.pool) { if let Ok(surface) = mgr.buffer_to_surface(handle, &mut state.pool) {
let mut ss = Screenshot::new(surface); let mut ss = Screenshot::new(surface);
let _ = ss.apply_effects(&state.config); let _ = ss.apply_effects(&state.config);
@@ -334,8 +508,18 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
} }
} }
impl wayland_client::Dispatch<wayland_client::protocol::wl_registry::WlRegistry, ()> for WaylandLock { impl wayland_client::Dispatch<wayland_client::protocol::wl_registry::WlRegistry, ()>
fn event(_state: &mut Self, _proxy: &wayland_client::protocol::wl_registry::WlRegistry, _event: wayland_client::protocol::wl_registry::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle<Self>) {} for WaylandLock
{
fn event(
_state: &mut Self,
_proxy: &wayland_client::protocol::wl_registry::WlRegistry,
_event: wayland_client::protocol::wl_registry::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
}
} }
smithay_client_toolkit::delegate_compositor!(WaylandLock); smithay_client_toolkit::delegate_compositor!(WaylandLock);
@@ -353,12 +537,13 @@ fn main() -> Result<(), Box<dyn Error>> {
static LOGGER: DualLogger = DualLogger; static LOGGER: DualLogger = DualLogger;
log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Debug))?; log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Debug))?;
log::info!("Starting wayrustlock v{}", env!("CARGO_PKG_VERSION")); log::info!("Starting rustlock v{}", env!("CARGO_PKG_VERSION"));
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));
let conn = Connection::connect_to_env()?; let conn = Connection::connect_to_env()?;
let (globals, mut event_queue) = wayland_client::globals::registry_queue_init::<WaylandLock>(&conn)?; let (globals, mut event_queue) =
wayland_client::globals::registry_queue_init::<WaylandLock>(&conn)?;
let qh: QueueHandle<WaylandLock> = event_queue.handle(); let qh: QueueHandle<WaylandLock> = event_queue.handle();
let shm_state = Shm::bind(&globals, &qh).map_err(|_| "wl_shm not supported")?; let shm_state = Shm::bind(&globals, &qh).map_err(|_| "wl_shm not supported")?;
@@ -389,35 +574,57 @@ fn main() -> Result<(), Box<dyn Error>> {
exit: false, exit: false,
unlocking: false, unlocking: false,
screenshot_manager: ScreenshotManager::new(&globals, &qh).ok(), screenshot_manager: ScreenshotManager::new(&globals, &qh).ok(),
grace_until: None,
}; };
event_queue.blocking_dispatch(&mut state)?; event_queue.blocking_dispatch(&mut state)?;
let _wayland_source = WaylandSource::new(conn.clone(), event_queue).insert(event_loop.handle())?; let _wayland_source =
WaylandSource::new(conn.clone(), event_queue).insert(event_loop.handle())?;
event_loop.handle().insert_source(auth_feedback_rx_actual, |event, _, state| { event_loop
if let calloop::channel::Event::Msg(success) = event { .handle()
state.handle_auth_result(success); .insert_source(auth_feedback_rx_actual, |event, _, state| {
if success { state.lock_surfaces.clear(); } if let calloop::channel::Event::Msg(success) = event {
} state.handle_auth_result(success);
})?; if success {
state.lock_surfaces.clear();
}
}
})?;
let timer = calloop::timer::Timer::from_duration(Duration::from_millis(16)); let timer = calloop::timer::Timer::from_duration(Duration::from_millis(16));
event_loop.handle().insert_source(timer, |_, _, state| { event_loop.handle().insert_source(timer, |_, _, state| {
// Clear expired grace period
if let Some(grace_until) = state.grace_until {
if Instant::now() >= grace_until {
state.grace_until = None;
}
}
if state.unlocking { if state.unlocking {
return calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(100)); return calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(100));
} }
if let Ok(mut lm) = state.lock_manager.lock() { if let Ok(mut lm) = state.lock_manager.lock() {
lm.update(); lm.update();
for surface in &mut lm.surfaces { let _ = surface.commit(&mut state.pool); } for surface in &mut lm.surfaces {
let _ = surface.commit(&mut state.pool);
}
}
if state.exit {
calloop::timer::TimeoutAction::Drop
} else {
calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(16))
} }
if state.exit { calloop::timer::TimeoutAction::Drop } else { calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(16)) }
})?; })?;
if state.config.screenshots { if state.config.screenshots {
state.outputs = state.output_state.outputs().collect(); state.outputs = state.output_state.outputs().collect();
log::info!("Capturing screenshots for {} outputs...", state.outputs.len()); log::info!(
"Capturing screenshots for {} outputs...",
state.outputs.len()
);
state.screenshot_frames = vec![None; state.outputs.len()]; state.screenshot_frames = vec![None; state.outputs.len()];
state.captured_backgrounds = vec![None; state.outputs.len()]; state.captured_backgrounds = vec![None; state.outputs.len()];
state.pending_screenshots = state.outputs.len(); state.pending_screenshots = state.outputs.len();
@@ -427,9 +634,14 @@ fn main() -> Result<(), Box<dyn Error>> {
let data = CaptureData::new(i); let data = CaptureData::new(i);
match mgr.capture_output(output, &qh, data) { match mgr.capture_output(output, &qh, data) {
Ok(frame) => state.screenshot_frames[i] = Some(frame), Ok(frame) => state.screenshot_frames[i] = Some(frame),
Err(e) => { log::error!("Screencopy failed for output {}: {:?}", i, e); state.pending_screenshots -= 1; } Err(e) => {
log::error!("Screencopy failed for output {}: {:?}", i, e);
state.pending_screenshots -= 1;
}
} }
} else { state.pending_screenshots -= 1; } } else {
state.pending_screenshots -= 1;
}
} }
let start = std::time::Instant::now(); let start = std::time::Instant::now();
@@ -440,7 +652,10 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
log::info!("Attempting to lock Wayland session..."); log::info!("Attempting to lock Wayland session...");
let session_lock = state.session_lock_state.lock(&qh).map_err(|e| { log::error!("Lock failed: {}", e); e })?; let session_lock = state.session_lock_state.lock(&qh).map_err(|e| {
log::error!("Lock failed: {}", e);
e
})?;
let outputs_to_lock: Vec<WlOutput> = state.output_state.outputs().collect(); let outputs_to_lock: Vec<WlOutput> = state.output_state.outputs().collect();
for output in outputs_to_lock { for output in outputs_to_lock {
@@ -448,11 +663,15 @@ fn main() -> Result<(), Box<dyn Error>> {
let (width, height) = state.get_output_dimensions(&output); let (width, height) = state.get_output_dimensions(&output);
let lock_surface = session_lock.create_lock_surface(surface.clone(), &output, &qh); let lock_surface = session_lock.create_lock_surface(surface.clone(), &output, &qh);
state.lock_surfaces.push(lock_surface); state.lock_surfaces.push(lock_surface);
if !state.outputs.contains(&output) { state.outputs.push(output.clone()); } if !state.outputs.contains(&output) {
state.outputs.push(output.clone());
}
if let Ok(mut lm) = state.lock_manager.lock() { if let Ok(mut lm) = state.lock_manager.lock() {
lm.add_surface(width, height, output.clone()); lm.add_surface(width, height, output.clone());
let count = lm.surface_count(); let count = lm.surface_count();
if let Some(ls) = lm.get_surface_mut(count - 1) { ls.set_wayland_surface(surface); } if let Some(ls) = lm.get_surface_mut(count - 1) {
ls.set_wayland_surface(surface);
}
} }
} }
state.session_lock = Some(session_lock); state.session_lock = Some(session_lock);
@@ -461,6 +680,6 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.dispatch(Duration::from_millis(16), &mut state)?; event_loop.dispatch(Duration::from_millis(16), &mut state)?;
} }
log::info!("Exiting wayrustlock"); log::info!("Exiting rustlock");
Ok(()) Ok(())
} }