use std::ops::{BitOr, BitOrAssign};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::Instant;
use windows::Win32::UI::Controls::WM_MOUSELEAVE;
use windows::Win32::UI::WindowsAndMessaging::{
WM_ACTIVATE, WM_ACTIVATEAPP, WM_CANCELMODE, WM_CAPTURECHANGED, WM_CLOSE, WM_DESTROY,
WM_DISPLAYCHANGE, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_LBUTTONDBLCLK,
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDBLCLK, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEACTIVATE,
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCACTIVATE, WM_NCDESTROY,
WM_NCHITTEST, WM_NCLBUTTONDBLCLK, WM_NCLBUTTONDOWN, WM_NCLBUTTONUP, WM_NCMBUTTONDBLCLK,
WM_NCMBUTTONDOWN, WM_NCMBUTTONUP, WM_NCMOUSELEAVE, WM_NCMOUSEMOVE, WM_NCRBUTTONDBLCLK,
WM_NCRBUTTONDOWN, WM_NCRBUTTONUP, WM_NCXBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP,
WM_RBUTTONDBLCLK, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETTINGCHANGE, WM_SHOWWINDOW,
WM_SIZE, WM_STYLECHANGED, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDBLCLK,
WM_XBUTTONDOWN, WM_XBUTTONUP,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct WakeBits(pub u32);
impl WakeBits {
pub const NONE: WakeBits = WakeBits(0);
pub const fn bits(self) -> u32 {
self.0
}
pub const fn contains(self, other: WakeBits) -> bool {
self.0 & other.0 == other.0
}
pub const fn union(self, other: WakeBits) -> WakeBits {
WakeBits(self.0 | other.0)
}
}
impl BitOr for WakeBits {
type Output = WakeBits;
fn bitor(self, rhs: WakeBits) -> WakeBits {
self.union(rhs)
}
}
impl BitOrAssign for WakeBits {
fn bitor_assign(&mut self, rhs: WakeBits) {
self.0 |= rhs.0;
}
}
pub const POINTER: WakeBits = WakeBits(1 << 0);
pub const DRAG: WakeBits = WakeBits(1 << 1);
pub const WINDOW_CMD: WakeBits = WakeBits(1 << 2);
pub const ZORDER: WakeBits = WakeBits(1 << 3);
pub const WM_GEOMETRY: WakeBits = WakeBits(1 << 4);
pub const PRESENT: WakeBits = WakeBits(1 << 5);
pub const ANIM: WakeBits = WakeBits(1 << 6);
pub const REARM: WakeBits = WakeBits(1 << 7);
pub const GRAPHICS: WakeBits = WakeBits(1 << 8);
pub const FORCE: WakeBits = WakeBits(1 << 9);
pub const ALL: [(&str, WakeBits); 10] = [
("POINTER", POINTER),
("DRAG", DRAG),
("WINDOW_CMD", WINDOW_CMD),
("ZORDER", ZORDER),
("WM_GEOMETRY", WM_GEOMETRY),
("PRESENT", PRESENT),
("ANIM", ANIM),
("REARM", REARM),
("GRAPHICS", GRAPHICS),
("FORCE", FORCE),
];
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct WakeSnapshot {
pub bits: u32,
pub deadline_due: bool,
}
impl WakeSnapshot {
pub fn is_empty(&self) -> bool {
self.bits == 0 && !self.deadline_due
}
}
const NO_DEADLINE: u64 = u64::MAX;
struct Wake {
bits: AtomicU32,
deadline_nanos: AtomicU64,
epoch: OnceLock<Instant>,
}
impl Wake {
const fn new() -> Wake {
Wake {
bits: AtomicU32::new(0),
deadline_nanos: AtomicU64::new(NO_DEADLINE),
epoch: OnceLock::new(),
}
}
fn epoch(&self) -> Instant {
*self.epoch.get_or_init(Instant::now)
}
fn to_nanos(&self, at: Instant) -> u64 {
let elapsed = at.saturating_duration_since(self.epoch()).as_nanos();
let capped = elapsed.min(u128::from(NO_DEADLINE - 1));
capped as u64
}
fn mark(&self, bits: WakeBits) {
if bits == WakeBits::NONE {
return;
}
self.bits.fetch_or(bits.bits(), Ordering::AcqRel);
}
fn arm_deadline(&self, at: Instant) {
let nanos = self.to_nanos(at);
self.deadline_nanos.fetch_min(nanos, Ordering::AcqRel);
}
fn take(&self, now: Instant) -> WakeSnapshot {
let bits = self.bits.swap(0, Ordering::AcqRel);
let now_nanos = self.to_nanos(now);
let deadline = self.deadline_nanos.load(Ordering::Acquire);
let deadline_due = deadline != NO_DEADLINE && deadline <= now_nanos;
if deadline_due {
let _ = self.deadline_nanos.compare_exchange(
deadline,
NO_DEADLINE,
Ordering::AcqRel,
Ordering::Acquire,
);
}
WakeSnapshot { bits, deadline_due }
}
}
static GLOBAL: Wake = Wake::new();
pub fn mark(bits: WakeBits) {
GLOBAL.mark(bits);
}
pub fn arm_deadline(at: Instant) {
GLOBAL.arm_deadline(at);
}
pub fn take(now: Instant) -> WakeSnapshot {
GLOBAL.take(now)
}
pub const KNOWN_MESSAGE_TABLE: &[(u32, WakeBits)] = &[
(WM_WINDOWPOSCHANGING, WM_GEOMETRY),
(WM_WINDOWPOSCHANGED, WM_GEOMETRY),
(WM_SIZE, WM_GEOMETRY),
(WM_MOVE, WM_GEOMETRY),
(WM_DPICHANGED, WM_GEOMETRY),
(WM_DISPLAYCHANGE, WM_GEOMETRY),
(WM_SETTINGCHANGE, WM_GEOMETRY),
(WM_ACTIVATE, WM_GEOMETRY),
(WM_ACTIVATEAPP, WM_GEOMETRY),
(WM_NCACTIVATE, WM_GEOMETRY),
(WM_SHOWWINDOW, WM_GEOMETRY),
(WM_DESTROY, WM_GEOMETRY),
(WM_NCDESTROY, WM_GEOMETRY),
(WM_CLOSE, WM_GEOMETRY),
(WM_STYLECHANGED, WM_GEOMETRY),
(WM_ENTERSIZEMOVE, WM_GEOMETRY),
(WM_EXITSIZEMOVE, WM_GEOMETRY),
(WM_MOUSEMOVE, POINTER),
(WM_MOUSELEAVE, POINTER),
(WM_SETCURSOR, POINTER),
(WM_NCHITTEST, POINTER),
(WM_MOUSEACTIVATE, POINTER),
(WM_LBUTTONDOWN, POINTER),
(WM_LBUTTONUP, POINTER),
(WM_LBUTTONDBLCLK, POINTER),
(WM_RBUTTONDOWN, POINTER),
(WM_RBUTTONUP, POINTER),
(WM_RBUTTONDBLCLK, POINTER),
(WM_MBUTTONDOWN, POINTER),
(WM_MBUTTONUP, POINTER),
(WM_MBUTTONDBLCLK, POINTER),
(WM_XBUTTONDOWN, POINTER),
(WM_XBUTTONUP, POINTER),
(WM_XBUTTONDBLCLK, POINTER),
(WM_MOUSEWHEEL, POINTER),
(WM_MOUSEHWHEEL, POINTER),
(WM_CAPTURECHANGED, POINTER),
(WM_CANCELMODE, POINTER),
(WM_NCMOUSEMOVE, POINTER),
(WM_NCMOUSELEAVE, POINTER),
(WM_NCLBUTTONDOWN, POINTER),
(WM_NCLBUTTONUP, POINTER),
(WM_NCLBUTTONDBLCLK, POINTER),
(WM_NCRBUTTONDOWN, POINTER),
(WM_NCRBUTTONUP, POINTER),
(WM_NCRBUTTONDBLCLK, POINTER),
(WM_NCMBUTTONDOWN, POINTER),
(WM_NCMBUTTONUP, POINTER),
(WM_NCMBUTTONDBLCLK, POINTER),
(WM_NCXBUTTONDOWN, POINTER),
(WM_NCXBUTTONUP, POINTER),
(WM_NCXBUTTONDBLCLK, POINTER),
];
pub fn wake_bits_for_message(msg: u32) -> WakeBits {
match msg {
WM_WINDOWPOSCHANGING | WM_WINDOWPOSCHANGED | WM_SIZE | WM_MOVE | WM_DPICHANGED
| WM_DISPLAYCHANGE | WM_SETTINGCHANGE | WM_ACTIVATE | WM_ACTIVATEAPP | WM_NCACTIVATE
| WM_SHOWWINDOW | WM_DESTROY | WM_NCDESTROY | WM_CLOSE | WM_STYLECHANGED
| WM_ENTERSIZEMOVE | WM_EXITSIZEMOVE => WM_GEOMETRY,
WM_MOUSEMOVE | WM_MOUSELEAVE | WM_SETCURSOR | WM_NCHITTEST | WM_MOUSEACTIVATE
| WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP
| WM_RBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK | WM_XBUTTONDOWN
| WM_XBUTTONUP | WM_XBUTTONDBLCLK | WM_MOUSEWHEEL | WM_MOUSEHWHEEL | WM_CAPTURECHANGED
| WM_CANCELMODE | WM_NCMOUSEMOVE | WM_NCMOUSELEAVE | WM_NCLBUTTONDOWN | WM_NCLBUTTONUP
| WM_NCLBUTTONDBLCLK | WM_NCRBUTTONDOWN | WM_NCRBUTTONUP | WM_NCRBUTTONDBLCLK
| WM_NCMBUTTONDOWN | WM_NCMBUTTONUP | WM_NCMBUTTONDBLCLK | WM_NCXBUTTONDOWN
| WM_NCXBUTTONUP | WM_NCXBUTTONDBLCLK => POINTER,
_ => FORCE,
}
}
#[cfg(test)]
#[path = "tick_wake_tests.rs"]
mod tests;