pub(crate) trait SecureInputOs {
fn enable(&mut self);
fn disable(&mut self);
}
#[derive(Default)]
pub(crate) struct Platform;
#[cfg(target_os = "macos")]
#[link(name = "Carbon", kind = "framework")]
unsafe extern "C" {
fn EnableSecureEventInput() -> i32;
fn DisableSecureEventInput() -> i32;
}
impl SecureInputOs for Platform {
fn enable(&mut self) {
#[cfg(target_os = "macos")]
unsafe {
EnableSecureEventInput();
}
}
fn disable(&mut self) {
#[cfg(target_os = "macos")]
unsafe {
DisableSecureEventInput();
}
}
}
pub(crate) struct SecureInput<O: SecureInputOs = Platform> {
os: O,
held: bool,
}
impl Default for SecureInput {
fn default() -> Self {
Self::new(Platform)
}
}
impl<O: SecureInputOs> SecureInput<O> {
pub(crate) fn new(os: O) -> Self {
Self { os, held: false }
}
pub(crate) fn set(&mut self, want: bool) {
match (self.held, want) {
(false, true) => self.os.enable(),
(true, false) => self.os.disable(),
_ => return,
}
self.held = want;
}
#[cfg(test)]
pub(crate) fn held(&self) -> bool {
self.held
}
}
impl<O: SecureInputOs> Drop for SecureInput<O> {
fn drop(&mut self) {
self.set(false);
}
}
pub(crate) fn wanted(windows: impl IntoIterator<Item = (bool, bool)>) -> bool {
windows.into_iter().any(|(asked, focused)| asked && focused)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[derive(Clone, Default)]
struct Counting {
enables: Rc<Cell<u32>>,
disables: Rc<Cell<u32>>,
}
impl SecureInputOs for Counting {
fn enable(&mut self) {
self.enables.set(self.enables.get() + 1);
}
fn disable(&mut self) {
self.disables.set(self.disables.get() + 1);
assert!(
self.disables.get() <= self.enables.get(),
"a disable with nothing to balance"
);
}
}
fn step(guard: &mut SecureInput<Counting>, windows: &[(bool, bool)]) {
guard.set(wanted(windows.iter().copied()));
}
#[test]
fn secure_input_follows_the_asking_window_s_focus_and_stays_balanced() {
let os = Counting::default();
let (en, dis) = (os.enables.clone(), os.disables.clone());
let calls = || (en.get(), dis.get());
let mut guard = SecureInput::new(os);
step(&mut guard, &[(false, true)]);
step(&mut guard, &[(false, false)]);
assert_eq!(calls(), (0, 0));
step(&mut guard, &[(true, false)]);
assert_eq!(calls(), (0, 0));
step(&mut guard, &[(true, true)]);
step(&mut guard, &[(true, true)]);
step(&mut guard, &[(true, true)]);
assert_eq!(calls(), (1, 0));
assert!(guard.held());
step(&mut guard, &[(true, false)]);
assert_eq!(calls(), (1, 1));
step(&mut guard, &[(true, true)]);
assert_eq!(calls(), (2, 1));
step(&mut guard, &[(false, true)]);
assert_eq!(calls(), (2, 2));
assert!(!guard.held());
step(&mut guard, &[(true, true), (false, false)]);
assert_eq!(calls(), (3, 2));
step(&mut guard, &[(true, false), (false, true)]);
assert_eq!(calls(), (3, 3));
step(&mut guard, &[(true, true), (true, false)]);
step(&mut guard, &[(true, false), (true, true)]);
assert_eq!(calls(), (4, 3));
step(&mut guard, &[(false, false)]);
assert_eq!(calls(), (4, 4));
step(&mut guard, &[(true, true)]);
assert_eq!(calls(), (5, 4));
drop(guard);
assert_eq!(calls(), (5, 5), "never left on at exit");
}
#[test]
fn a_guard_that_never_held_calls_nothing_on_drop() {
let os = Counting::default();
let (en, dis) = (os.enables.clone(), os.disables.clone());
let mut guard = SecureInput::new(os);
guard.set(false);
drop(guard);
assert_eq!((en.get(), dis.get()), (0, 0));
}
}