Skip to content

fix(start): intercept Start clicks on secondary-monitor taskbars#71

Merged
Alpaq92 merged 1 commit into
mainfrom
claude/quizzical-pike-c05505
Jul 9, 2026
Merged

fix(start): intercept Start clicks on secondary-monitor taskbars#71
Alpaq92 merged 1 commit into
mainfrom
claude/quizzical-pike-c05505

Conversation

@Alpaq92

@Alpaq92 Alpaq92 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Follow-up to #67 (originally stacked on it; rebased onto main after it merged in v0.8.13).

Problem

The WH_MOUSE_LL hook only tested clicks against the primary taskbar's Start rect (Shell_TrayWnd). With "show my taskbar on all displays" enabled, each secondary monitor gets its own taskbar (Shell_SecondaryTrayWnd) with its own Start button — clicking it always opened the native Windows Start menu instead of MenYou.

Change

  • StartButtonLocator.Get()GetAll(List<RECT>): the primary rect (the fix(start): DPI-scale the Start-button rect and default to centered alignment #67 logic, split into a per-tray GetStartRect helper) plus one rect per Shell_SecondaryTrayWnd found via a FindWindowExW loop. Each rect scales by its own tray's GetDpiForWindow — mixed-DPI monitors are the main real-world case for secondary taskbars.
  • Secondary taskbars are treated as left-aligned regardless of TaskbarAl (Win 11 doesn't center the icon group on secondary taskbars, so Start is their leftmost element).
  • A tray hwnd that dies mid-query yields an empty rect and is skipped (same convention as the other failure paths).
  • StartClickHook stays allocation-free per event: two double-buffered rect lists (fill scratch, swap references), refreshed under the existing 250 ms throttle. Swallow logic and the SystemStartMenu.IsOpen pass-through unchanged.

Verification

  • Builds clean (0 warnings) on top of current main.
  • Single-monitor regression on a live machine (96 DPI, TaskbarAl=0): GetAll returns exactly one rect, (0,907)-(55,955), byte-identical to the Start button bounding rect reported by UI Automation — single-monitor behavior is unchanged.
  • The (x*dpi+95)/96 ceiling matches true ceil() at 96/120/144/150/192/240 DPI.

⚠️ Needs multi-monitor testing before release

The dev machine has a single display, so two claims are code-reviewed but not live-verified:

  1. Secondary-taskbar Start buttons are always leftmost (never centered), even with TaskbarAl=1.
  2. Per-tray DPI rects line up on mixed-DPI setups.

Quickest check: on a multi-monitor machine with taskbar on all displays, click Start on each monitor — MenYou should open every time, with no native menu flash.

🤖 Generated with Claude Code

The WH_MOUSE_LL hook only tested clicks against the primary taskbar's
Start rect (Shell_TrayWnd), so with "show my taskbar on all displays"
on, clicking Start on a secondary-monitor taskbar
(Shell_SecondaryTrayWnd) always opened the native Windows Start menu
instead of MenYou.

- StartButtonLocator.Get() becomes GetAll(List<RECT>): the primary
  rect (existing logic, split into a per-tray GetStartRect helper)
  plus one rect per Shell_SecondaryTrayWnd found via a FindWindowExW
  loop. Each rect scales by ITS OWN tray's GetDpiForWindow — mixed-DPI
  monitors are the main real-world case for secondary taskbars.
- Secondary taskbars are treated as left-aligned regardless of
  TaskbarAl: Win 11 doesn't center the icon group on secondary
  taskbars, so Start is their leftmost element there. (Needs a live
  multi-monitor confirmation — dev machine has a single display.)
- A tray hwnd that dies mid-query yields an empty rect and is
  skipped, matching the existing failure convention.
- StartClickHook keeps its per-event path allocation-free: two
  double-buffered rect lists (fill scratch, swap references),
  refreshed under the existing 250 ms throttle. The swallow logic and
  the SystemStartMenu.IsOpen pass-through are unchanged.

Single-monitor behavior verified unchanged: GetAll returns exactly one
rect, byte-identical to the UIA-reported Start button bounding rect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Alpaq92, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22d5614b-3bbb-4cad-b223-a661fea2d02d

📥 Commits

Reviewing files that changed from the base of the PR and between 2559332 and 8a82b5e.

📒 Files selected for processing (2)
  • src/MenYou/Platform/Windows/StartButtonLocator.cs
  • src/MenYou/Platform/Windows/StartClickHook.cs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/quizzical-pike-c05505

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Alpaq92 Alpaq92 enabled auto-merge (squash) July 9, 2026 17:17
/// first; leaves it empty if no taskbar is found (Explorer not up). A tray
/// window that dies mid-query is skipped, not treated as fatal. Takes the
/// caller's list so the click hook can refresh without allocating.
public static void GetAll(List<RECT> results)
// Secondary taskbars don't center their icon group on Win 11: Start is
// the leftmost element there even when TaskbarAl says centered.
var secondary = IntPtr.Zero;
while ((secondary = FindWindowExW(IntPtr.Zero, secondary, "Shell_SecondaryTrayWnd", null)) != IntPtr.Zero)
private static extern IntPtr FindWindowW(string? lpClassName, string? lpWindowName);

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, string? lpszClass, string? lpszWindow);
if (_startRect.IsEmpty) return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
EnsureRectsFresh();
var rects = _startRects;
if (rects.Count == 0) return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
@Alpaq92 Alpaq92 disabled auto-merge July 9, 2026 20:17
@Alpaq92 Alpaq92 merged commit 05b41e0 into main Jul 9, 2026
11 checks passed
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