use std::{
ffi::{c_long, c_ulong, c_void},
fmt::{self, Debug},
ptr::NonNull,
time::{Duration, Instant, SystemTime},
};
use num_enum::{FromPrimitive, IntoPrimitive};
use windows_sys::Win32::{
Foundation::HWND,
System::SystemInformation::GetTickCount,
UI::{Accessibility::HWINEVENTHOOK, WindowsAndMessaging},
};
use crate::raw_event;
pub type RawHookHandle = HWINEVENTHOOK;
pub type HookHandle = NonNull<c_void>;
pub type RawWindowHandle = HWND;
pub type WindowHandle = NonNull<c_void>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RawWindowEvent {
pub hook_handle: HWINEVENTHOOK,
pub event_id: c_ulong,
pub window_handle: HWND,
pub object_id: c_long,
pub child_id: c_long,
pub thread_id: c_ulong,
pub timestamp: c_ulong,
}
unsafe impl Send for RawWindowEvent {}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowEvent {
pub raw: RawWindowEvent,
}
impl Debug for WindowEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct(stringify!(WindowEvent));
let _ = builder.field("hook_handle", &self.hook_handle());
let _ = builder.field("event_type", &self.event_type());
let _ = builder.field("window_handle", &self.window_handle());
let _ = builder.field("object_type", &self.object_type());
let _ = builder.field("child_id", &self.child_id());
let _ = builder.field("thread_id", &self.thread_id());
let _ = builder.field("timestamp", &self.timestamp());
builder.finish()
}
}
impl WindowEvent {
#[must_use]
pub fn from_raw(raw: RawWindowEvent) -> Self {
Self { raw }
}
#[must_use]
pub fn hook_handle(&self) -> HookHandle {
unsafe { HookHandle::new_unchecked(self.raw.hook_handle) }
}
#[must_use]
pub fn event_type(&self) -> WindowEventType {
(self.raw.event_id as i32).into()
}
#[must_use]
pub fn window_handle(&self) -> Option<WindowHandle> {
NonNull::new(self.raw.window_handle.cast())
}
#[must_use]
pub fn object_type(&self) -> AccessibleObjectId {
self.raw.object_id.into()
}
#[must_use]
pub fn child_id(&self) -> Option<i32> {
const CHILDID_SELF: i32 = WindowsAndMessaging::CHILDID_SELF.cast_signed();
match self.raw.child_id {
CHILDID_SELF => None,
n => Some(n),
}
}
#[must_use]
pub fn thread_id(&self) -> u32 {
self.raw.thread_id
}
#[must_use]
pub fn timestamp(&self) -> Instant {
let time_since_system_start = Duration::from_millis(u64::from(unsafe { GetTickCount() }));
let event_time_relative_to_system_start =
Duration::from_millis(u64::from(self.raw.timestamp));
let now = Instant::now();
let system_start_instant = now.checked_sub(time_since_system_start).unwrap_or_else(|| {
let time_since_unix_epoch = SystemTime::UNIX_EPOCH.elapsed().unwrap();
now.checked_sub(time_since_unix_epoch).unwrap()
});
system_start_instant + event_time_relative_to_system_start
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, FromPrimitive,
)]
#[repr(i32)]
pub enum AccessibleObjectId {
Alert = -10,
Caret = -8,
Client = -4,
Cursor = -9,
HorizontalScroll = -6,
NativeObjectModel = -16,
Menu = -3,
QueryClassNameIdx = -12,
SizeGrip = -7,
Sound = -11,
SystemMenu = -1,
TitleBar = -2,
VerticalScroll = -5,
Window = 0,
#[num_enum(catch_all)]
Other(i32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum WindowEventType {
System(SystemWindowEvent),
OemDefined(i32),
Console(ConsoleWindowEvent),
UiaEvent(i32),
UiaPropertyChange(i32),
Object(ObjectWindowEvent),
Atom(i32),
Aia(i32),
Other(i32),
}
impl From<i32> for WindowEventType {
fn from(number: i32) -> Self {
if raw_event::all_system().contains(&number) {
Self::System(SystemWindowEvent::from(number))
} else if raw_event::all_console().contains(&number) {
Self::Console(ConsoleWindowEvent::from(number))
} else if raw_event::all_object().contains(&number) {
Self::Object(ObjectWindowEvent::from(number))
} else if raw_event::all_atom().contains(&number) {
Self::Atom(number)
} else if raw_event::all_aia().contains(&number) {
Self::Aia(number)
} else if raw_event::all_oem_defined().contains(&number) {
Self::OemDefined(number)
} else if raw_event::all_uia_event().contains(&number) {
Self::UiaEvent(number)
} else if raw_event::all_uia_property_change().contains(&number) {
Self::UiaPropertyChange(number)
} else {
Self::Other(number)
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, FromPrimitive,
)]
#[repr(i32)]
pub enum SystemWindowEvent {
Sound = raw_event::SYSTEM_SOUND,
Alert = raw_event::SYSTEM_ALERT,
Foreground = raw_event::SYSTEM_FOREGROUND,
MenuStart = raw_event::SYSTEM_MENUSTART,
MenuEnd = raw_event::SYSTEM_MENUEND,
MenuPopupStart = raw_event::SYSTEM_MENUPOPUPSTART,
MenuPopupEnd = raw_event::SYSTEM_MENUPOPUPEND,
CaptureStart = raw_event::SYSTEM_CAPTURESTART,
CaptureEnd = raw_event::SYSTEM_CAPTUREEND,
MoveSizeStart = raw_event::SYSTEM_MOVESIZESTART,
MoveSizeEnd = raw_event::SYSTEM_MOVESIZEEND,
ContextHelpStart = raw_event::SYSTEM_CONTEXTHELPSTART,
ContextHelpEnd = raw_event::SYSTEM_CONTEXTHELPEND,
DragDropStart = raw_event::SYSTEM_DRAGDROPSTART,
DragDropEnd = raw_event::SYSTEM_DRAGDROPEND,
DialogStart = raw_event::SYSTEM_DIALOGSTART,
DialogEnd = raw_event::SYSTEM_DIALOGEND,
ScrollingStart = raw_event::SYSTEM_SCROLLINGSTART,
ScrollingEnd = raw_event::SYSTEM_SCROLLINGEND,
SwitchStart = raw_event::SYSTEM_SWITCHSTART,
SwitchEnd = raw_event::SYSTEM_SWITCHEND,
MinimizeStart = raw_event::SYSTEM_MINIMIZESTART,
MinimizeEnd = raw_event::SYSTEM_MINIMIZEEND,
DesktopSwitch = raw_event::SYSTEM_DESKTOPSWITCH,
#[num_enum(catch_all)]
Other(i32),
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, FromPrimitive,
)]
#[repr(i32)]
#[allow(missing_docs)]
pub enum ConsoleWindowEvent {
Caret = raw_event::CONSOLE_CARET,
UpdateRegion = raw_event::CONSOLE_UPDATE_REGION,
UpdateSimple = raw_event::CONSOLE_UPDATE_SIMPLE,
UpdateScroll = raw_event::CONSOLE_UPDATE_SCROLL,
Layout = raw_event::CONSOLE_LAYOUT,
StartApplication = raw_event::CONSOLE_START_APPLICATION,
EndApplication = raw_event::CONSOLE_END_APPLICATION,
#[num_enum(catch_all)]
Other(i32),
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, FromPrimitive,
)]
#[repr(i32)]
pub enum ObjectWindowEvent {
Create = raw_event::OBJECT_CREATE,
Destroy = raw_event::OBJECT_DESTROY,
Show = raw_event::OBJECT_SHOW,
Hide = raw_event::OBJECT_HIDE,
Reorder = raw_event::OBJECT_REORDER,
Focus = raw_event::OBJECT_FOCUS,
Selection = raw_event::OBJECT_SELECTION,
SelectionAdd = raw_event::OBJECT_SELECTIONADD,
SelectionRemove = raw_event::OBJECT_SELECTIONREMOVE,
SelectionWithin = raw_event::OBJECT_SELECTIONWITHIN,
StateChange = raw_event::OBJECT_STATECHANGE,
LocationChange = raw_event::OBJECT_LOCATIONCHANGE,
NameChange = raw_event::OBJECT_NAMECHANGE,
DescriptionChange = raw_event::OBJECT_DESCRIPTIONCHANGE,
ValueChange = raw_event::OBJECT_VALUECHANGE,
ParentChange = raw_event::OBJECT_PARENTCHANGE,
HelpChange = raw_event::OBJECT_HELPCHANGE,
DefaultActionChange = raw_event::OBJECT_DEFACTIONCHANGE,
AcceloratorChange = raw_event::OBJECT_ACCELERATORCHANGE,
Invoked = raw_event::OBJECT_INVOKED,
TextSelectionChanged = raw_event::OBJECT_TEXTSELECTIONCHANGED,
ContentScrolled = raw_event::OBJECT_CONTENTSCROLLED,
ArrangementPreview = raw_event::SYSTEM_ARRANGMENTPREVIEW,
Cloaked = raw_event::OBJECT_CLOAKED,
Uncloaked = raw_event::OBJECT_UNCLOAKED,
LiveRegionChanged = raw_event::OBJECT_LIVEREGIONCHANGED,
HostedObjectsInvalidated = raw_event::OBJECT_HOSTEDOBJECTSINVALIDATED,
DragStart = raw_event::OBJECT_DRAGSTART,
DragCancel = raw_event::OBJECT_DRAGCANCEL,
DragComplete = raw_event::OBJECT_DRAG_COMPLETE,
DragEnter = raw_event::OBJECT_DRAGENTER,
DragLeave = raw_event::OBJECT_DRAGLEAVE,
DragDropped = raw_event::OBJECT_DRAGDROPPED,
ImeShow = raw_event::OBJECT_IME_SHOW,
ImeHide = raw_event::OBJECT_IME_HIDE,
ImeChange = raw_event::OBJECT_IME_CHANGE,
TextEditConversionTargetChanged = raw_event::OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED,
#[num_enum(catch_all)]
Other(i32),
}