#![warn(missing_debug_implementations)]
mod error;
#[cfg(test)]
mod tests;
pub use crate::error::Error;
pub use crate::error::ErrorKind;
use crate::error::Repr;
pub use crate::error::Result;
use std::os::windows::io::AsHandle;
use std::os::windows::io::AsRawHandle;
use std::os::windows::io::BorrowedHandle;
use std::os::windows::io::FromRawHandle;
use std::os::windows::io::OwnedHandle;
use std::os::windows::io::RawHandle;
use std::ptr;
use std::time::Duration;
use windows_registry::CURRENT_USER;
use windows_registry::Key;
use windows_result::WIN32_ERROR;
use windows_sys::Win32::Foundation::WAIT_FAILED;
use windows_sys::Win32::Foundation::WAIT_OBJECT_0;
use windows_sys::Win32::Foundation::WAIT_TIMEOUT;
use windows_sys::Win32::System::Registry::KEY_NOTIFY;
use windows_sys::Win32::System::Registry::REG_NOTIFY_CHANGE_LAST_SET;
use windows_sys::Win32::System::Registry::REG_NOTIFY_THREAD_AGNOSTIC;
use windows_sys::Win32::System::Registry::RegNotifyChangeKeyValue;
use windows_sys::Win32::System::Threading::CreateEventW;
use windows_sys::Win32::System::Threading::INFINITE;
use windows_sys::Win32::System::Threading::WaitForSingleObject;
const MAX_TIMEOUT_MILLIS: u32 = INFINITE - 1;
#[derive(Debug)]
pub struct Watcher {
key: Key,
event: OwnedHandle,
}
impl Watcher {
#[expect(unsafe_code, reason = "create and take ownership of a Win32 event")]
pub fn new(path: &str) -> Result<Self> {
if path.is_empty() || path.contains('\0') {
return Err(Repr::InvalidPath.into());
}
let key = CURRENT_USER
.options()
.access(KEY_NOTIFY)
.open(path)
.map_err(|source| Repr::Open { source })?;
let event = unsafe { CreateEventW(ptr::null(), 0, 0, ptr::null()) };
if event.is_null() {
return Err(Repr::CreateEvent {
source: windows_result::Error::from_thread(),
}
.into());
}
let event = unsafe { OwnedHandle::from_raw_handle(event) };
let watcher = Self { key, event };
watcher.rearm()?;
Ok(watcher)
}
#[expect(unsafe_code, reason = "register an owned key with its owned event")]
pub fn rearm(&self) -> Result<()> {
let status = unsafe {
RegNotifyChangeKeyValue(
self.key.as_raw(),
0,
REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_THREAD_AGNOSTIC,
self.event.as_raw_handle(),
1,
)
};
WIN32_ERROR(status)
.ok()
.map_err(|source| Repr::Register { source }.into())
}
#[expect(unsafe_code, reason = "wait on the event owned by this watcher")]
pub fn wait_timeout(&self, duration: Duration) -> Result<bool> {
let milliseconds = timeout_millis(duration)?;
let status = unsafe { WaitForSingleObject(self.event.as_raw_handle(), milliseconds) };
match status {
WAIT_OBJECT_0 => Ok(true),
WAIT_TIMEOUT => Ok(false),
WAIT_FAILED => Err(Repr::Wait {
source: windows_result::Error::from_thread(),
}
.into()),
other => Err(Repr::WaitStatus(other).into()),
}
}
}
impl AsHandle for Watcher {
fn as_handle(&self) -> BorrowedHandle<'_> {
self.event.as_handle()
}
}
impl AsRawHandle for Watcher {
fn as_raw_handle(&self) -> RawHandle {
self.event.as_raw_handle()
}
}
fn timeout_millis(duration: Duration) -> Result<u32> {
let milliseconds =
u32::try_from(duration.as_nanos().div_ceil(1_000_000)).map_err(|_error| Repr::Timeout)?;
if milliseconds > MAX_TIMEOUT_MILLIS {
return Err(Repr::Timeout.into());
}
Ok(milliseconds)
}