use std::time::Instant;
use winit::event::ElementState;
use winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
#[cfg(all(unix, not(target_os = "macos")))]
mod atspi;
mod keysym;
#[cfg(test)]
mod tests;
#[cfg(all(unix, not(target_os = "macos")))]
mod worker;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DeviceEventKind {
Pressed = 0,
Released = 1,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DeviceEvent {
pub kind: DeviceEventKind,
pub keysym: u32,
pub hw_code: u32,
pub modifiers: u32,
pub timestamp: u32,
pub event_string: String,
pub is_text: bool,
}
impl DeviceEvent {
pub(crate) fn for_key(
key: &KeyInput<'_>,
keyboard: KeyboardState,
num_lock: bool,
withhold: bool,
timestamp: u32,
) -> Self {
let modifiers = keyboard.modifiers;
let withheld = withhold && keysym::types_text(key.logical_key);
let event_string = if withheld {
String::new()
} else {
keysym::event_string(key.logical_key)
};
let chorded = modifiers.control_key() || modifiers.alt_key() || modifiers.super_key();
Self {
kind: match key.state {
ElementState::Pressed => DeviceEventKind::Pressed,
ElementState::Released => DeviceEventKind::Released,
},
keysym: if withheld {
keysym::VOID_SYMBOL
} else {
keysym::keysym(key.logical_key, key.location, modifiers.shift_key())
},
hw_code: keysym::hardware_keycode(key.physical_key),
modifiers: keysym::modifier_mask(keyboard, num_lock),
timestamp,
is_text: !event_string.is_empty() && !chorded,
event_string,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct KeyInput<'a> {
pub physical_key: PhysicalKey,
pub logical_key: &'a Key,
pub location: KeyLocation,
pub state: ElementState,
pub repeat: bool,
pub synthetic: bool,
}
impl<'a> KeyInput<'a> {
pub fn from_winit(event: &'a winit::event::KeyEvent, is_synthetic: bool) -> Self {
Self {
physical_key: event.physical_key,
logical_key: &event.logical_key,
location: event.location,
state: event.state,
repeat: event.repeat,
synthetic: is_synthetic,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct KeyboardState {
pub modifiers: ModifiersState,
pub caps_lock: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct KeyTarget {
pub reader_attached: bool,
pub secure_field: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(all(unix, not(target_os = "macos"))), allow(dead_code))]
pub(crate) enum ReportOutcome {
Consumed,
NotConsumed,
Unanswered,
}
pub(crate) trait KeyEventReporter {
fn report_press(&mut self, event: &DeviceEvent) -> ReportOutcome;
fn report_release(&mut self, event: &DeviceEvent);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyDisposition {
Deliver,
Drop,
}
pub struct KeyReportGate {
reporter: Option<Box<dyn KeyEventReporter>>,
start: Option<fn() -> Box<dyn KeyEventReporter>>,
held: Vec<Hold>,
num_lock: Option<bool>,
epoch: Instant,
}
#[derive(Debug, Clone)]
struct Hold {
key: PhysicalKey,
delivered: bool,
reported: bool,
withheld: bool,
named: Option<(u32, String, bool)>,
}
impl std::fmt::Debug for KeyReportGate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeyReportGate")
.field("started", &self.reporter.is_some())
.finish_non_exhaustive()
}
}
impl Default for KeyReportGate {
fn default() -> Self {
Self::for_platform()
}
}
impl KeyReportGate {
pub fn for_platform() -> Self {
#[cfg(all(unix, not(target_os = "macos")))]
let start: Option<fn() -> Box<dyn KeyEventReporter>> = Some(atspi::start_reporter);
#[cfg(not(all(unix, not(target_os = "macos"))))]
let start: Option<fn() -> Box<dyn KeyEventReporter>> = None;
Self {
reporter: None,
start,
held: Vec::new(),
num_lock: None,
epoch: Instant::now(),
}
}
#[cfg(test)]
pub(crate) fn with_reporter(reporter: impl KeyEventReporter + 'static) -> Self {
Self {
reporter: Some(Box::new(reporter)),
start: None,
held: Vec::new(),
num_lock: None,
epoch: Instant::now(),
}
}
pub fn warm_up(&mut self) {
self.reporter();
}
fn reporter(&mut self) -> Option<&mut (dyn KeyEventReporter + 'static)> {
if self.reporter.is_none()
&& let Some(start) = self.start.take()
{
self.reporter = Some(start());
}
self.reporter.as_deref_mut()
}
pub fn filter(
&mut self,
key: &KeyInput<'_>,
keyboard: KeyboardState,
target: KeyTarget,
) -> KeyDisposition {
let at_active = target.reader_attached;
if key.synthetic {
return KeyDisposition::Deliver;
}
let pressed = key.state == ElementState::Pressed;
if pressed && let Some(on) = keysym::num_lock_shown(key.logical_key, key.location) {
self.num_lock = Some(on);
}
let num_lock = self.num_lock == Some(true);
let epoch = self.epoch;
let event = |key: &KeyInput<'_>, withhold: bool| {
let timestamp = epoch.elapsed().as_millis() as u32;
DeviceEvent::for_key(key, keyboard, num_lock, withhold, timestamp)
};
let secure = target.secure_field;
match key.state {
ElementState::Pressed => {
let reporter = if at_active { self.reporter() } else { None };
let reported = reporter.is_some();
let mut named = None;
let outcome = reporter.map_or(ReportOutcome::Unanswered, |reporter| {
let press = event(key, secure);
let outcome = reporter.report_press(&press);
named = Some((press.keysym, press.event_string, press.is_text));
outcome
});
if *key.logical_key == Key::Named(NamedKey::NumLock) && !key.repeat {
self.num_lock = self.num_lock.map(|on| !on);
}
let delivered = outcome != ReportOutcome::Consumed;
match self
.held
.iter_mut()
.find(|hold| hold.key == key.physical_key)
{
Some(hold) if key.repeat => {
hold.delivered |= delivered;
hold.reported |= reported;
hold.withheld |= secure;
hold.named = named.or(hold.named.take());
}
Some(hold) => {
hold.delivered = delivered;
hold.reported = reported;
hold.withheld = secure;
hold.named = named;
}
None => self.held.push(Hold {
key: key.physical_key,
delivered,
reported,
withheld: secure,
named,
}),
}
if delivered {
KeyDisposition::Deliver
} else {
KeyDisposition::Drop
}
}
ElementState::Released => {
let hold = self
.held
.iter()
.position(|hold| hold.key == key.physical_key)
.map(|index| self.held.swap_remove(index));
if (at_active || hold.as_ref().is_some_and(|hold| hold.reported))
&& let Some(reporter) = self.reporter()
{
let withhold = secure || hold.as_ref().is_some_and(|hold| hold.withheld);
let mut release = event(key, withhold);
if !withhold
&& let Some((keysym, text, is_text)) =
hold.as_ref().and_then(|hold| hold.named.clone())
{
release.keysym = keysym;
release.event_string = text;
release.is_text = is_text;
}
reporter.report_release(&release);
}
if hold.is_none_or(|hold| hold.delivered) {
KeyDisposition::Deliver
} else {
KeyDisposition::Drop
}
}
}
}
}