[ MIRROR ] Customisable, minimalist screen locker for Wayland git.ngill.net/nlock.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189// SPDX-License-Identifier: GPL-3.0-or-later// Copyright (C) 2026, Nathan Gill
use std::{ os::fd::RawFd, time::{Duration, Instant},};
use anyhow::{Result, bail};use nix::{errno::Errno, libc, poll::PollTimeout};use tracing::trace;
pub type EventTag = usize;
pub type PollResult<T> = std::result::Result<T, Errno>;
#[derive(Debug)]struct TaggedFd { fd: RawFd, tag: EventTag,}
#[derive(Debug)]struct Timer { interval: Duration, last: Instant, tag: EventTag,}
pub enum Event { Readable { fd: RawFd, tag: EventTag }, Timeout { tag: EventTag },}
pub enum Expiration { Interval(Duration), IntervalDelayed(Duration, Duration),}
pub enum EventSource { Fd(RawFd), Timer(Expiration),}
pub struct NLockEventLoop { source_fds: Vec<TaggedFd>, timers: Vec<Timer>,}
impl NLockEventLoop { pub fn new() -> Self { Self { source_fds: Vec::new(), timers: Vec::new(), } }
fn next_expiry(&self) -> Option<Duration> { if self.timers.is_empty() { return None; }
let mut dur = Duration::MAX; for t in &self.timers { let now = Instant::now(); let elapsed = now.saturating_duration_since(t.last);
if elapsed >= t.interval { // already expired return Some(Duration::ZERO); }
// this is weird but required to handle delayed timers let left = if t.last > now { t.last.duration_since(now) } else { t.interval - elapsed };
if left < dur { dur = left; } }
Some(dur) }
pub fn poll(&mut self) -> PollResult<Vec<Event>> { let mut events = Vec::new();
let mut poll_fds: Vec<libc::pollfd> = self .source_fds .iter() .map(|t| libc::pollfd { fd: t.fd, events: libc::POLLIN, revents: 0, }) .collect();
let timeout = self .next_expiry() .map(|d| libc::c_int::try_from(d.as_millis()).unwrap_or(libc::c_int::MAX)) .unwrap_or(PollTimeout::NONE.into());
let res = unsafe { libc::poll( poll_fds.as_mut_ptr(), poll_fds.len() as libc::nfds_t, timeout, ) }; if res == -1 { return Err(Errno::last()); }
for pf in &poll_fds { if pf.revents & libc::POLLIN != 0 && let Some(tfd) = self.source_fds.iter().find(|t| t.fd == pf.fd) { let ev = Event::Readable { fd: tfd.fd, tag: tfd.tag, }; events.push(ev); } }
for t in &mut self.timers { let now = Instant::now(); let elapsed = now - t.last; if elapsed >= t.interval { let ev = Event::Timeout { tag: t.tag }; t.last = now; events.push(ev); } }
Ok(events) }
pub fn add(&mut self, source: EventSource, tag: EventTag) -> Result<()> { match source { EventSource::Fd(fd) => { let tfd = TaggedFd { fd, tag }; trace!("added event source: {:?}", tfd); self.source_fds.push(tfd); } EventSource::Timer(expiration) => { let (delay, interval) = match expiration { Expiration::Interval(interval) => (None, interval), Expiration::IntervalDelayed(delay, interval) => (Some(delay), interval), };
if interval.is_zero() { bail!("timer interval cannot be set to zero!"); }
let timer = Timer { interval, last: if let Some(delay) = delay { // offset next interval by delay Instant::now() + delay - interval } else { Instant::now() }, tag, }; trace!("added timer: {:?}", timer); self.timers.push(timer); } }
Ok(()) }
pub fn remove(&mut self, tag: EventTag) { self.source_fds.retain(|t| t.tag != tag); self.timers.retain(|t| t.tag != tag); trace!("removed tag: {:?}", tag); }}
impl Default for NLockEventLoop { fn default() -> Self { Self::new() }}