Update Don't Pull Me to Another Desktop to v0.4.0 - #5269
Conversation
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The attribution rework (rooting classification in 1. The new view-supersession hardening is inert for every non-packaged app — i.e. for most apps. This is the headline change in the changelog ("Hardening against apps that briefly activate a pre-existing window before creating a new one"), but it is gated behind
So for a classic Win32 app — the exact case the README note describes ("Some applications can briefly activate an old window while processing a window creation") — the mod falls back to the blanket 50 ms The gate also doesn't seem to buy anything: Suggestion: drop 2. The two settings are internal tuning knobs, and their descriptions don't match what they do. "Rescue stabilization", "view supersession", "stable PFN/AUMID", "rescue candidates", "the worker", "source desktop" are implementation vocabulary — a user has no way to reason about what to set these to, or why. Two concrete inaccuracies in the descriptions as well:
Suggestion: hardcode both constants, or expose at most one plainly-worded option (e.g. 3.
if (IsIconic(request.hwnd)) {
ShowWindow(request.hwnd, SW_RESTORE);
}
SetForegroundWindow(request.hwnd);Both operate on a window owned by another process's thread and do inter-thread message sends; against a hung app they can block for a long time.
4. Size and formatting: the file is 4263 lines, and a large part of that is avoidable. The repo ships a g_startHr.store(
hr,
std::memory_order_relaxed);Reformatting with the repo style would cut the line count substantially without touching a single statement, and would make the diff for the next update readable. Please run Beyond formatting, the mod is now a very large amount of speculative machinery layered on undocumented shell internals, and each layer is another thing that has to be re-verified on the next Windows build. Two concrete reductions, on top of item 1:
Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
Thanks for the detailed review. I've addressed the findings that held up after checking them against the runtime behavior observed while developing the attribution rework. For view supersession, I agree that same-PID identity is the actual correlation criterion and that the previous PFN/AUMID matching was redundant. The superseder is now validated by same PID, a genuinely different shell view/HWND, and ownership by the current/source desktop. Stable package/AUMID identity is queried only once and is now used solely to decide whether to incur the longer 200 ms grace period. I kept that eligibility gate rather than extending every ordinary rescue from ~50 ms to ~200 ms. The two timing settings have been removed entirely. They are now internal policy constants, and the local-foreground replacement validation no longer depends on whether the settling delay is enabled.
I've also run the repository clang-format over the complete source. The file dropped from roughly 4260 lines to roughly 3100 lines without removing behavior. The impossible generation-zero checks, zero-time cancellation-event waits on shell hook paths, redundant second teardown, repeated package API resolution, and normal enable/reload I kept the navigation-focus history rather than replacing it with one slot. During testing we reproduced an older navigation-selected view's ForegroundViewChanged arriving only after a newer DesktopChanged had already completed. Multiple navigation selections can therefore be outstanding; a single slot could be overwritten before the delayed FVP consumes it. I've added a comment explaining that observed race. I also intentionally kept the stale-navigation suppression paths. These aren't unknown callers being treated fail-closed: the operation has been positively attributed to an obsolete navigation generation. We reproduced the A→B→C case where allowing the delayed B foreground-policy operation to continue pulls the user back from C to B. Calling the original in those paths would therefore restore the race the generation tracking was added to prevent. The bounded initialization retry policy remains as-is. Exhausting all attempts is an extremely unusual fail-open startup condition, and I don't think adding permanent retry/rearming state is justified for that case. Finally, I kept the explicit The README now also restores the simple reproduction steps, intentional-navigation guarantees, Virtual Desktop Helper/Disable Virtual Desktop Transition Animation compatibility notes, and explicit "virtual desktop" wording for discoverability. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The lifecycle side of this update looks solid: every thread ( 1. The check accepts any foreground window that (a) isn't Explorer-owned UI is the only exclusion, so this only stays out of the way when foreground routes through the taskbar/tray (the README's Windhawk-tray repro). It bites for the other cases the README advertises — activating from a third-party launcher, a terminal, another app's "show my window" button, or a folder window running in a separate Fix: capture the foreground window when the request is queued (you're already on the hook stack in struct RescueRequest {
...
HWND hwnd = nullptr;
+ HWND foregroundAtQueue = nullptr; // set from GetForegroundWindow() in QueueRescue
...
};
static bool IsForegroundReplacementOnSourceDesktop(WorkerComState* state,
const RescueRequest& request,
HWND* replacementHwnd) {
HWND foreground = GetForegroundWindow();
if (!foreground || foreground == request.hwnd ||
+ foreground == request.foregroundAtQueue ||
!IsRescueCandidate(foreground)) {
return false;
}That keeps the intended "the app resolved the activation locally" case (a genuinely different window took foreground after the request was queued) while dropping the "nothing changed" false positive. It would also let you relax the 2. The v0.4 supersession hardening never applies to non-packaged apps, contradicting both the code comment and the README. In const bool insideSupersessionWindow =
request.extendedSupersessionEligible && observedAt != 0 &&
observedElapsed <= kRescueViewSupersessionMs;
That directly contradicts Fix: drop the eligibility term from the cancel condition and leave it only in the const bool insideSupersessionWindow =
observedAt != 0 && observedElapsed <= kRescueViewSupersessionMs;Better still, consider deleting Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
New commits were pushed, so this pull request left the human review queue and is back to waiting-for-author. Comment |
|
For IsForegroundReplacementOnSourceDesktop, the rescue request now snapshots the Win32 foreground HWND when it is queued. The later settling check only treats a source-desktop foreground as a replacement if it actually differs from that queue-time foreground. I made the guard slightly more conservative as well: if the queue-time foreground is unavailable or already equals the remote activation target, the foreground heuristic abstains because there isn't a trustworthy pre-activation baseline. The existing shell-process exclusion remains as an additional conservative guard. For view supersession, extendedSupersessionEligible now only chooses the listening deadline. A request without stable package/AUMID identity listens for the normal 50 ms settling window; an eligible request listens for up to 200 ms. Any VIEWADD observed inside that request's actual waitWindow is then judged solely by the same-PID / different-view / different-HWND / current-desktop validation. This matches the intended invariant that identity controls latency rather than correlation. I also fixed the README continuation indentation and removed the stale taskbar.dll comment. I kept the timing values internal rather than re-exposing them as settings, since they're implementation policy rather than user-facing behavior. I also kept the CreateWindowExW shell-ownership observer: the important property there is positively identifying the Explorer process that owns Shell_TrayWnd; observing twinui.pcshell.dll loading alone doesn't provide the same ownership signal. The other functionality notes are intentionally unchanged: the bounded settling delay is the tradeoff required for supersession detection, the HookSymbols unload wait is required for safe unload, and the fail-stationary branches are retained for positively identified stale navigation generations. |
|
/ai-review |
|
I've narrowed IsForegroundReplacementOnSourceDesktop further. The queue-time foreground baseline is still retained so the foreground must actually have changed after the rescue was armed, but a later foreground window can now cancel the rescue only if it belongs to request.pid, i.e. the process whose remote view was being activated. That avoids treating normal focus restoration from transient taskbar/tray/launcher UI as evidence that the activation resolved locally, while keeping the fallback useful when the activated application itself genuinely foregrounds a different window on the current desktop. I also applied the three minor cleanups: the package/AUMID eligibility query is skipped when the optional OnViewAddedInternal hook isn't available, the README again explicitly notes that a superseding view is independently verified to belong to the current desktop, and the diagnostic desktop->GetId() call in DesktopChanged_Hook is now only made for the outermost navigation where its log value is actually used. I haven't changed the 50/200 ms timing split. The longer unidentified-app window is an interesting tuning possibility, but we currently have a concrete ~143 ms repro for the extended case and no corresponding evidence that ordinary Win32 rescues need a larger generic delay. I'd prefer to keep the common path at 50 ms until there's a real case demonstrating otherwise. |
|
/ai-review |
|
This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time. Comment |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Narrowing 1.
request.foregroundAtQueue = GetForegroundWindow();and if (!request.foregroundAtQueue ||
request.foregroundAtQueue == request.hwnd) {
return false;
}But That matters beyond dead weight: this function is the only supersession protection when the optional You have the logging to settle this in one session — 2. A navigation-focus record that never gets its FVP stays consumable for 2 s, and can turn a genuine activation into a click that does nothing.
Concrete sequence, all within 2 s:
The FVP for that click consumes the stale generation-N token, The fix is a freshness bound rather than new state. The case the history exists for is a delivery that lags one navigation, so either tighten the token lifetime to something on that order: // A delayed FVP for a navigation-selected view arrives within one
// navigation of its DesktopChanged; anything older is not a delayed
// delivery and must not classify a fresh activation.
static constexpr ULONGLONG kNavigationFocusRecordMaxAgeMs = 300;or drop tokens more than one generation behind the current one when a new outermost Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
…l if it's a replacement window
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Both findings from last round are properly closed: Turning replacement handling on by default for Windows Terminal makes every user pay for a heuristic that you yourself concluded can't be made authoritative. Your commit message says it: "make rollback opt-in only since there's no way to authoritatively tell if it's a replacement window". But the shipped default isn't opt-in — The rollback can undo a summon the user wanted.
The window the user deliberately summoned silently disappears to another desktop, with nothing to indicate why. There's no navigation generation change and no desktop change to catch it, because the user never navigated. The pre-move grace adds unconditional latency. Simplest fix: ship // A replacement window is created as part of the same activation. A window
// that shows up hundreds of ms later is the user opening a window, not the
// app substituting one.
static constexpr ULONGLONG kMaxReplacementLagMs = 400;
const ULONGLONG reference = watch.armedAt;
const ULONGLONG lag = watch.candidateObservedAt > reference
? watch.candidateObservedAt - reference
: 0;
if (lag > kMaxReplacementLagMs) {
return false;
}( Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
Thanks. The theoretical manual-new-window false positive is understood, but I don’t think making the Windows Terminal default empty or adding a short “must appear immediately” cutoff would be an improvement. Windows Terminal isn’t included as a speculative heuristic target. It is the concrete application for which this compatibility path was developed and tested. With Windows Terminal configured as the Default Terminal, stock Windows reproducibly does: activate pre-existing Terminal window on another desktop -> attempt desktop switch -> asynchronously create the actually requested Terminal window on the current desktop. During development I instrumented that sequence directly through the shell hooks: the old Terminal view reaches ForegroundViewChanged first, followed later by OnViewAddedInternal and ForegroundViewChanged for the newly-created same-process Terminal view. Importantly, the replacement timing was not stable. A typical observed run was around 143 ms, but later tests on the same machine exceeded the former 200 ms supersession window. That was the reason the implementation was changed away from treating a fixed timeout as proof that no replacement is coming. Therefore I don’t think narrowing rollback by assuming a genuine replacement must appear “immediately” is sound; it would reintroduce the exact load-dependent failure that this revision removed. The per-app list is intended as an allowlist for behavior whose causality cannot be proven generically, rather than a requirement that every affected application start disabled. Unlisted applications retain the deterministic summon-only path. Windows Terminal is shipped in the list because it is a known, reproduced affected application, not because package/process identity is being used as generic replacement evidence. The two timing values also have deliberately non-causal meanings: The manual-new-window sequence described in the review is theoretically possible for any app placed on the allowlist; that unavoidable ambiguity is why replacement handling is not enabled generically. For Windows Terminal specifically, I think the known real-world handoff and the observed load-dependent delay justify enabling the compatibility policy by default. Users who prefer immediate deterministic summon-only behavior can remove the entry or set both values to zero. |
Changelog
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.