use windows::Win32::Foundation::WAIT_OBJECT_0;
use windows::Win32::System::Threading::{ResetEvent, WaitForSingleObject};
use windows::Win32::UI::WindowsAndMessaging::{
DispatchMessageW, MSG, MsgWaitForMultipleObjects, PM_REMOVE, PeekMessageW, QS_ALLINPUT,
TranslateMessage,
};
use crate::registry::HookEvent;
use super::types::FlowWM;
const MAX_PENDING_RETRIES: u8 = 5;
const PENDING_RETRY_TIMEOUT_MS: u32 = 100;
impl FlowWM {
pub fn run(&mut self) {
log::info!("flowd: daemon started, event-driven loop on named pipe");
self.server.start_accept();
let hook_handle = self.hook_signal.raw();
let connect_handle = self.server.connected_event_handle();
let wait_handles = [hook_handle, connect_handle];
loop {
self.maybe_resume_float_tracking();
self.reconcile_foreground();
let timeout = self.compute_wait_timeout();
let wait_result = unsafe {
MsgWaitForMultipleObjects(Some(&wait_handles), false, timeout, QS_ALLINPUT)
};
if wait_result.0 == u32::MAX {
log::error!("flowd: MsgWaitForMultipleObjects failed");
break;
}
let signaled = wait_result.0;
self.pump_messages();
unsafe {
let _ = ResetEvent(hook_handle);
}
self.process_hook_events();
let ipc_connected =
signaled == 1 || unsafe { WaitForSingleObject(connect_handle, 0) } == WAIT_OBJECT_0;
if ipc_connected {
loop {
self.pump_messages();
self.process_hook_events();
self.maybe_resume_float_tracking();
match self.server.read_message() {
Ok(msg) => {
let response = self.dispatch(&msg);
let is_stop = self.shutting_down;
if let Err(e) = self.server.write_response(&response) {
log::warn!("flowd: failed to write response: {e}");
break;
}
if is_stop {
log::info!("flowd: shutting down");
return;
}
}
Err(_) => {
break;
}
}
}
if let Err(e) = self.server.disconnect() {
log::warn!("flowd: failed to disconnect client: {e}");
}
self.server.start_accept();
}
}
}
fn pump_messages(&mut self) {
let mut msg = MSG::default();
while unsafe { PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() } {
unsafe {
let _ = TranslateMessage(&msg);
let _ = DispatchMessageW(&msg);
}
}
}
fn compute_wait_timeout(&self) -> u32 {
let next_foreground_sync =
(self.config.focus.foreground_sync_interval_ms != 0).then(|| {
self.last_foreground_sync
+ std::time::Duration::from_millis(
self.config.focus.foreground_sync_interval_ms,
)
});
compute_wait_timeout_inner(
!self.pending_creations.is_empty(),
self.float_resume_deadline,
next_foreground_sync,
std::time::Instant::now(),
)
}
fn process_hook_events(&mut self) {
if !self.pending_creations.is_empty() {
let pending = std::mem::take(&mut self.pending_creations);
let mut still_pending = Vec::new();
for (hwnd, retries) in pending {
if self.on_window_created(hwnd) {
} else if retries < MAX_PENDING_RETRIES {
still_pending.push((hwnd, retries + 1));
}
}
self.pending_creations = still_pending;
}
while let Ok(event) = self.hook_receiver.try_recv() {
log::debug!("hook: received {event:?}");
match event {
HookEvent::Created { hwnd } => {
if !self.on_window_created(hwnd) {
self.pending_creations.push((hwnd, 0));
}
}
HookEvent::Destroyed { hwnd } => {
self.on_window_destroyed(hwnd);
}
HookEvent::Foreground { hwnd } => {
self.on_focus_changed(hwnd);
}
HookEvent::MinimizeStart { hwnd } => {
self.on_window_minimized(hwnd);
}
HookEvent::MinimizeEnd { hwnd } => {
self.on_window_restored(hwnd);
}
HookEvent::Shown { hwnd } => {
self.on_window_shown(hwnd);
}
HookEvent::Hidden { hwnd } => {
self.on_window_hidden(hwnd);
}
HookEvent::StateChange { hwnd } => {
self.on_window_state_change(hwnd);
}
HookEvent::NameChange { hwnd } => {
self.on_window_name_change(hwnd);
}
HookEvent::LocationChange { hwnd } => {
self.on_float_location_changed(hwnd);
}
}
}
}
}
fn compute_wait_timeout_inner(
has_pending_creations: bool,
float_resume_deadline: Option<std::time::Instant>,
next_foreground_sync: Option<std::time::Instant>,
now: std::time::Instant,
) -> u32 {
let mut best: Option<u32> = None;
if has_pending_creations {
best = Some(PENDING_RETRY_TIMEOUT_MS);
}
for deadline in float_resume_deadline
.into_iter()
.chain(next_foreground_sync)
{
let remaining = deadline.saturating_duration_since(now);
let ms = remaining.as_millis().min(u32::MAX as u128) as u32;
let ms = ms.max(1);
best = Some(best.map_or(ms, |b| b.min(ms)));
}
best.unwrap_or(u32::MAX)
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn wait_timeout_infinite_when_idle() {
let now = Instant::now();
assert_eq!(compute_wait_timeout_inner(false, None, None, now), u32::MAX);
}
#[test]
fn wait_timeout_pending_creations_uses_retry_cadence() {
let now = Instant::now();
assert_eq!(
compute_wait_timeout_inner(true, None, None, now),
PENDING_RETRY_TIMEOUT_MS,
);
}
#[test]
fn wait_timeout_float_deadline_uses_remaining_ms() {
let now = Instant::now();
let deadline = now + Duration::from_millis(500);
assert_eq!(
compute_wait_timeout_inner(false, Some(deadline), None, now),
500
);
}
#[test]
fn wait_timeout_picks_sooner_when_deadline_under_pending_cadence() {
let now = Instant::now();
let deadline = now + Duration::from_millis(50);
assert_eq!(
compute_wait_timeout_inner(true, Some(deadline), None, now),
50
);
}
#[test]
fn wait_timeout_pending_cadence_wins_when_deadline_is_farther() {
let now = Instant::now();
let deadline = now + Duration::from_secs(5);
assert_eq!(
compute_wait_timeout_inner(true, Some(deadline), None, now),
PENDING_RETRY_TIMEOUT_MS,
);
}
#[test]
fn wait_timeout_due_deadline_floors_to_one_ms() {
let now = Instant::now();
let deadline = now - Duration::from_millis(10);
assert_eq!(
compute_wait_timeout_inner(false, Some(deadline), None, now),
1
);
}
#[test]
fn wait_timeout_foreground_sync_uses_remaining_ms() {
let now = Instant::now();
let deadline = now + Duration::from_millis(250);
assert_eq!(
compute_wait_timeout_inner(false, None, Some(deadline), now),
250
);
}
#[test]
fn wait_timeout_foreground_sync_wins_when_soonest() {
let now = Instant::now();
let float_deadline = now + Duration::from_secs(5);
let sync_deadline = now + Duration::from_millis(80);
assert_eq!(
compute_wait_timeout_inner(false, Some(float_deadline), Some(sync_deadline), now),
80
);
}
}