Skip to content

fix(monitor): skip modal/owned windows and their parents#6

Merged
frankieg33 merged 7 commits into
mainfrom
claude/cherry-pick-modal-and-hardening
May 14, 2026
Merged

fix(monitor): skip modal/owned windows and their parents#6
frankieg33 merged 7 commits into
mainfrom
claude/cherry-pick-modal-and-hardening

Conversation

@frankieg33

Copy link
Copy Markdown
Owner

The action loop reached close/minimize for any non-foreground window past
its idle timeout, ignoring is_owned on WindowInfo. A parent window could
be closed while an owned modal (Save As, auth prompt, etc.) was active.

Now skip entries where is_owned is true, and skip any window whose PID
has a visible owned window in the current snapshot.

claude added 2 commits May 13, 2026 18:30
The action loop reached close/minimize for any non-foreground window past
its idle timeout, ignoring is_owned on WindowInfo. A parent window could
be closed while an owned modal (Save As, auth prompt, etc.) was active.

Now skip entries where is_owned is true, and skip any window whose PID
has a visible owned window in the current snapshot.
filter:
- add is_cloaked + is_on_current_desktop to WindowInfo; short-circuit in
  is_system_window so DWM-cloaked surfaces (UWP background, virtual-desktop
  residue, shell-hidden) never reach the action loop.

winapi:
- add WindowApi::get_foreground_hwnd for HWND-level checks; default for the
  trait + null for non-Windows; real impl uses GetForegroundWindow().
- populate is_cloaked via DwmGetWindowAttribute(DWMWA_CLOAKED). The cloaked
  flag also covers windows on other virtual desktops (DWM_CLOAKED_SHELL),
  so explicit IVirtualDesktopManager COM is not required for this fix.
- broaden is_fullscreen with a near-full coverage path (>=99% of monitor
  rect) so DXGI/borderless-windowed-fullscreen titles are protected.
- cache PID -> process name across a single enumeration cycle.
- split unsafe regions in enum_window_callback_v2 and tag each with a brief
  invariant note.

monitor:
- HWND-level foreground guard runs immediately before action dispatch, in
  addition to the existing process-name check.
- detect monotonic gaps between polls (>= 5x interval or >= 60s) and rebase
  every foreground timestamp to now, so resume-from-sleep / lock /
  hibernation does not fire a wave of closes the second the user unlocks.
- record rule source (AppRule vs Bucket) and timeout_mins on every action
  log entry; surface both in the runtime info log line.
- redact window title from the info-level action log (titles may contain
  sensitive data); preserve them at debug level + in the GUI action log.

config:
- ResolvedRule now reports source (AppRule | Bucket).
- save() fsyncs the temp file before rename and best-effort fsyncs the
  parent directory after rename so an atomic rename is also durable
  across power loss.

main:
- startup GUI hydration no longer unwraps the config / search-state RwLocks
  (poison would have panicked the tray); fall back to defaults instead.

tests:
- foreground HWND guard, cloaked-window filter, off-desktop filter.
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ecd7a12699

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/monitor.rs Outdated
let pids_with_owned: HashSet<u32> = self
.current_windows
.iter()
.filter(|e| e.info.is_owned && !filter::is_system_window(&e.info))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include all owned windows when shielding parent processes

The PID guard only tracks owned windows that also satisfy !filter::is_system_window, which means owned dialogs filtered as system (for example empty-title or tool-style owned windows) are excluded from pids_with_owned. In that case, the parent window for the same PID is still eligible for close/minimize, so this patch can still interrupt modal/owned flows in the exact scenario it aims to prevent. Build the owned-PID set from all owned visible windows (or apply system filtering after parent shielding) so any owned dialog suppresses parent actions.

Useful? React with 👍 / 👎.

…tered

Addresses Codex P2 feedback on PR #6: pre-filtering pids_with_owned with
is_system_window let empty-title or tool-style owned dialogs slip past
the guard, leaving their parent eligible for action — exactly the modal
flow this check exists to protect.
@frankieg33

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f73c961c85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/monitor.rs Outdated
Comment on lines +293 to +297
.current_windows
.iter()
.filter(|e| e.info.is_owned)
.map(|e| e.pid)
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict owned-window skip to modal dialogs

This marks a process as non-actionable whenever any owned window exists, but is_owned/GW_OWNER does not imply modality; many apps keep modeless owned helpers (for example find/replace or floating tool windows) open during normal use. In that case the parent PID is skipped forever at pids_with_owned.contains(&entry.pid), so configured minimize/close actions never fire for that app. The guard should distinguish modal/interactive blocking dialogs from generic owned windows before suppressing the whole process.

Useful? React with 👍 / 👎.

Previously any owned window on a process caused fade to skip every
window owned by that process. This swept up floating helpers (find /
replace, color pickers, tool palettes) that leave the parent fully
interactive, so e.g. Notepad with Find open would never be minimized.

Windows distinguishes true app-modal dialogs by disabling the owner
(IsWindowEnabled returns FALSE on the parent — the grey-out you see
behind a Save As). Track that explicitly via disables_owner on
WindowInfo and gate the parent-process shield on it. Owned windows
themselves are still never direct action targets.

Copy link
Copy Markdown
Owner Author

@chatgpt-codex-connector please review.

Addresses the P2 comment on src/monitor.rs:297 ("Restrict owned-window skip to modal dialogs"): added a disables_owner flag on WindowInfo, set from IsWindowEnabled(owner) in the Win32 enumerator. The parent-process shield now keys off disables_owner instead of is_owned, so modeless helpers (find/replace, color pickers, tool palettes) no longer suppress idle actions on their parent. Owned windows themselves are still never direct action targets. New test test_parent_acted_on_with_floating_helper covers the modeless case; the existing modal test was updated to set disables_owner = true.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

@codex review

Latest commits address the outstanding P2 ("Restrict owned-window skip to modal dialogs") by gating the parent-process shield on disables_owner (set from IsWindowEnabled(owner) in the Win32 enumerator) rather than is_owned, so modeless helpers like find/replace no longer suppress idle actions on their parent.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ea2e26b6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/monitor.rs Outdated
.current_windows
.iter()
.filter(|e| e.info.disables_owner)
.map(|e| e.pid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Shield modal owners by owner PID, not dialog PID

The modal-protection set is built from each owned dialog's own PID (.map(|e| e.pid)), and the action loop skips windows only when their PID is in that set. This misses valid Win32 cases where an owned modal is hosted out-of-process (owner and owned windows have different PIDs), so the true owner window is not shielded and can still be minimized/closed while the modal is active. Please derive the shield set from the owner window's PID (or include both owner and dialog PIDs).

Useful? React with 👍 / 👎.

When a modal dialog is hosted in a different process from its owner
(shell-hosted picker dialogs, security prompts, UWP modals over a Win32
app), the dialog's PID alone is not enough to protect the parent. Look
up the owner window's PID via GetWindowThreadProcessId on the owner
HWND and include both PIDs in the shield set.
@frankieg33

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@frankieg33 frankieg33 merged commit f1afb27 into main May 14, 2026
1 check passed
@frankieg33 frankieg33 deleted the claude/cherry-pick-modal-and-hardening branch May 14, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants