use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use windows::Win32::Foundation::{
CloseHandle, HANDLE, HWND, LPARAM, LRESULT, WAIT_OBJECT_0, WPARAM,
};
use windows::Win32::System::RemoteDesktop::{
NOTIFY_FOR_THIS_SESSION, WTSRegisterSessionNotification, WTSUnRegisterSessionNotification,
};
use windows::Win32::System::Threading::{
CancelWaitableTimer, CreateEventW, CreateWaitableTimerExW, INFINITE, ResetEvent, SetEvent,
SetWaitableTimer, TIMER_ALL_ACCESS, WaitForSingleObject,
};
use windows::Win32::UI::WindowsAndMessaging::{
DefWindowProcW, IDC_ARROW, MWMO_INPUTAVAILABLE, MsgWaitForMultipleObjectsEx, QS_ALLINPUT,
WM_WTSSESSION_CHANGE, WTS_SESSION_UNLOCK,
};
use windows::core::{PCWSTR, w};
use crate::Ui;
use crate::error::{Call, Result};
use crate::native::{self, NativeWindow};
use crate::ui::Notice;
const CLASS_NAME: PCWSTR = w!("GlasskitEvents");
const UNIX_EPOCH_AS_FILETIME: i64 = 116_444_736_000_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PopupId(pub(crate) u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DialogId(pub(crate) u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowId(pub(crate) u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Event<C> {
Command(C),
Popup(PopupId),
Dialog(DialogId),
Window(WindowId),
Deadline,
SessionUnlocked,
Wake,
Quit,
}
pub(crate) trait Source<C> {
fn take_command(&self) -> Option<C>;
}
enum Ready {
Popup(PopupId),
Dialog(DialogId),
Window(WindowId),
SessionUnlocked,
Wake,
Deadline,
}
impl<C> From<Ready> for Event<C> {
fn from(ready: Ready) -> Self {
match ready {
Ready::Popup(id) => Event::Popup(id),
Ready::Dialog(id) => Event::Dialog(id),
Ready::Window(id) => Event::Window(id),
Ready::SessionUnlocked => Event::SessionUnlocked,
Ready::Wake => Event::Wake,
Ready::Deadline => Event::Deadline,
}
}
}
pub struct EventLoop<C> {
waiter: Waiter,
sources: RefCell<Vec<Weak<dyn Source<C>>>>,
}
impl<C> EventLoop<C> {
pub fn new(ui: &Ui) -> Result<Self> {
Ok(Self {
waiter: Waiter::new(ui)?,
sources: RefCell::new(Vec::new()),
})
}
pub fn ui(&self) -> &Ui {
&self.waiter.ui
}
pub fn waker(&self) -> Waker {
Waker(Arc::clone(&self.waiter.signal))
}
pub(crate) fn add_source(&self, source: Weak<dyn Source<C>>) {
self.sources.borrow_mut().push(source);
}
pub fn next(&mut self, until: Option<SystemTime>) -> Result<Event<C>> {
if until.is_none() {
self.waiter.timer.cancel();
}
loop {
if self.waiter.ui.pump_pending() {
return Ok(Event::Quit);
}
if let Some(command) = self.take_command() {
return Ok(Event::Command(command));
}
if let Some(ready) = self.waiter.poll(until)? {
return Ok(ready.into());
}
}
}
fn take_command(&mut self) -> Option<C> {
let sources = self.sources.get_mut();
sources.retain(|s| s.strong_count() > 0);
sources
.iter()
.find_map(|s| s.upgrade().and_then(|s| s.take_command()))
}
}
struct Waiter {
ui: Ui,
timer: DeadlineTimer,
signal: Arc<Signal>,
window: NativeWindow<Cell<bool>>,
}
impl Waiter {
fn new(ui: &Ui) -> Result<Self> {
let timer = DeadlineTimer::new()?;
let signal = Arc::new(Signal::new()?);
let hinstance = ui.hinstance();
native::register_class(CLASS_NAME, Some(wndproc), hinstance, IDC_ARROW)?;
let hwnd = native::create_hidden(CLASS_NAME, hinstance)?;
let window = NativeWindow::attach(hwnd, Rc::new(Cell::new(false)));
unsafe {
let _ = WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION);
}
Ok(Self {
ui: ui.clone(),
timer,
signal,
window,
})
}
fn poll(&mut self, until: Option<SystemTime>) -> Result<Option<Ready>> {
match self.ui.take_notice() {
Some(Notice::Popup(id)) => return Ok(Some(Ready::Popup(PopupId(id)))),
Some(Notice::Dialog(id)) => return Ok(Some(Ready::Dialog(DialogId(id)))),
Some(Notice::Window(id)) => return Ok(Some(Ready::Window(WindowId(id)))),
None => {}
}
if self.window.state().replace(false) {
return Ok(Some(Ready::SessionUnlocked));
}
if self.signal.take() {
return Ok(Some(Ready::Wake));
}
if let Some(t) = until {
if SystemTime::now() >= t {
return Ok(Some(Ready::Deadline));
}
self.timer.set(t)?;
}
unsafe {
MsgWaitForMultipleObjectsEx(
Some(&[self.timer.0, self.signal.0]),
INFINITE,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE,
);
}
Ok(None)
}
}
impl Drop for Waiter {
fn drop(&mut self) {
unsafe {
let _ = WTSUnRegisterSessionNotification(self.window.hwnd());
}
}
}
extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
unsafe {
if let (WM_WTSSESSION_CHANGE, Some(unlocked)) = (msg, native::state_of::<Cell<bool>>(hwnd))
{
if wparam.0 == WTS_SESSION_UNLOCK as usize {
unlocked.set(true);
}
return LRESULT(0);
}
DefWindowProcW(hwnd, msg, wparam, lparam)
}
}
#[derive(Clone)]
pub struct Waker(Arc<Signal>);
impl Waker {
pub fn wake(&self) {
self.0.set();
}
}
impl std::fmt::Debug for Waker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Waker").finish_non_exhaustive()
}
}
struct Signal(HANDLE);
unsafe impl Send for Signal {}
unsafe impl Sync for Signal {}
impl Signal {
fn new() -> Result<Self> {
let handle =
unsafe { CreateEventW(None, true, false, PCWSTR::null()).call("CreateEventW")? };
Ok(Self(handle))
}
fn set(&self) {
unsafe {
let _ = SetEvent(self.0);
}
}
fn take(&self) -> bool {
unsafe {
let set = WaitForSingleObject(self.0, 0) == WAIT_OBJECT_0;
if set {
let _ = ResetEvent(self.0);
}
set
}
}
}
impl Drop for Signal {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
fn absolute_due_time(at: SystemTime) -> i64 {
let since_unix = at.duration_since(UNIX_EPOCH).unwrap_or_default();
i64::try_from(since_unix.as_nanos().div_ceil(100))
.unwrap_or(i64::MAX)
.saturating_add(UNIX_EPOCH_AS_FILETIME)
}
struct DeadlineTimer(HANDLE);
impl DeadlineTimer {
fn new() -> Result<Self> {
let handle = unsafe {
CreateWaitableTimerExW(None, PCWSTR::null(), 0, TIMER_ALL_ACCESS.0)
.call("CreateWaitableTimerExW")?
};
Ok(Self(handle))
}
fn set(&self, at: SystemTime) -> Result<()> {
let due = absolute_due_time(at);
unsafe { SetWaitableTimer(self.0, &due, 0, None, None, false).call("SetWaitableTimer") }
}
fn cancel(&self) {
unsafe {
let _ = CancelWaitableTimer(self.0);
}
}
}
impl Drop for DeadlineTimer {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn due_time_counts_from_1601() {
assert_eq!(absolute_due_time(UNIX_EPOCH), UNIX_EPOCH_AS_FILETIME);
let one_second = UNIX_EPOCH + Duration::from_secs(1);
assert_eq!(
absolute_due_time(one_second),
UNIX_EPOCH_AS_FILETIME + 10_000_000
);
}
#[test]
fn due_time_stays_positive_far_in_the_future() {
const YEAR: u64 = 365 * 24 * 60 * 60;
let far = UNIX_EPOCH
.checked_add(Duration::from_secs(20_000 * YEAR))
.expect("SystemTime holds 20000 years");
assert!(absolute_due_time(far) > UNIX_EPOCH_AS_FILETIME);
}
#[test]
fn due_time_before_1970_is_1970() {
let before = UNIX_EPOCH - Duration::from_secs(60);
assert_eq!(absolute_due_time(before), UNIX_EPOCH_AS_FILETIME);
}
#[test]
fn a_deadline_in_the_past_returns_at_once() {
let ui = Ui::new().unwrap();
let mut events: EventLoop<()> = EventLoop::new(&ui).unwrap();
let past = SystemTime::now() - Duration::from_secs(1);
assert_eq!(events.next(Some(past)).unwrap(), Event::Deadline);
}
#[test]
fn a_wake_from_another_thread_gives_one_event() {
let ui = Ui::new().unwrap();
let mut events: EventLoop<()> = EventLoop::new(&ui).unwrap();
let waker = events.waker();
std::thread::spawn(move || {
waker.wake();
waker.wake();
})
.join()
.unwrap();
assert_eq!(events.next(None).unwrap(), Event::Wake);
let soon = SystemTime::now() + Duration::from_millis(20);
assert_eq!(events.next(Some(soon)).unwrap(), Event::Deadline);
}
#[test]
fn a_wake_ends_a_wait() {
let ui = Ui::new().unwrap();
let mut events: EventLoop<()> = EventLoop::new(&ui).unwrap();
let waker = events.waker();
let thread = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
waker.wake();
});
assert_eq!(events.next(None).unwrap(), Event::Wake);
thread.join().unwrap();
}
#[test]
fn a_wake_after_the_loop_does_nothing() {
let ui = Ui::new().unwrap();
let events: EventLoop<()> = EventLoop::new(&ui).unwrap();
let waker = events.waker();
drop(events);
waker.wake();
}
#[test]
fn a_notice_wakes_the_loop() {
let ui = Ui::new().unwrap();
let mut events: EventLoop<()> = EventLoop::new(&ui).unwrap();
crate::ui::notify(Notice::Popup(7));
assert_eq!(events.next(None).unwrap(), Event::Popup(PopupId(7)));
}
}