use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, TryRecvError};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
use crate::error::{Error, Result};
use crate::types::KeyEvent;
pub use crate::platform::state::BlockingHotkeys;
pub struct KeyboardListener {
event_receiver: Receiver<KeyEvent>,
_thread_handle: Option<JoinHandle<()>>,
running: Arc<AtomicBool>,
blocking_hotkeys: Option<BlockingHotkeys>,
}
impl KeyboardListener {
pub fn new() -> Result<Self> {
Self::new_internal(None)
}
pub fn new_with_blocking(blocking_hotkeys: BlockingHotkeys) -> Result<Self> {
Self::new_internal(Some(blocking_hotkeys))
}
fn new_internal(blocking_hotkeys: Option<BlockingHotkeys>) -> Result<Self> {
#[cfg(target_os = "macos")]
{
use crate::platform::macos::listener;
let state = listener::spawn(blocking_hotkeys)?;
Ok(KeyboardListener {
event_receiver: state.event_receiver,
_thread_handle: state.thread_handle,
running: state.running,
blocking_hotkeys: state.blocking_hotkeys,
})
}
#[cfg(target_os = "windows")]
{
use crate::platform::windows::listener;
let state = listener::spawn(blocking_hotkeys)?;
Ok(KeyboardListener {
event_receiver: state.event_receiver,
_thread_handle: state.thread_handle,
running: state.running,
blocking_hotkeys: state.blocking_hotkeys,
})
}
#[cfg(target_os = "linux")]
{
use crate::platform::linux::listener;
let state = listener::spawn(blocking_hotkeys)?;
Ok(KeyboardListener {
event_receiver: state.event_receiver,
_thread_handle: state.thread_handle,
running: state.running,
blocking_hotkeys: state.blocking_hotkeys,
})
}
}
pub fn blocking_hotkeys(&self) -> Option<&BlockingHotkeys> {
self.blocking_hotkeys.as_ref()
}
pub fn recv(&self) -> Result<KeyEvent> {
self.event_receiver
.recv()
.map_err(|_| Error::EventLoopNotRunning)
}
pub fn recv_timeout(&self, timeout: Duration) -> Result<KeyEvent> {
self.event_receiver
.recv_timeout(timeout)
.map_err(|e| match e {
std::sync::mpsc::RecvTimeoutError::Timeout => Error::Timeout,
std::sync::mpsc::RecvTimeoutError::Disconnected => Error::EventLoopNotRunning,
})
}
pub fn try_recv(&self) -> Option<KeyEvent> {
match self.event_receiver.try_recv() {
Ok(event) => Some(event),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => None,
}
}
}
impl Drop for KeyboardListener {
fn drop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(handle) = self._thread_handle.take() {
let _ = handle.join();
}
}
}