Skip to content

Update mod: Taskbar Blob Shape (1.1.1) - #5428

Draft
Deen-0x wants to merge 1 commit into
ramensoftware:mainfrom
Deen-0x:taskbar-blob-shape-patch
Draft

Update mod: Taskbar Blob Shape (1.1.1)#5428
Deen-0x wants to merge 1 commit into
ramensoftware:mainfrom
Deen-0x:taskbar-blob-shape-patch

Conversation

@Deen-0x

@Deen-0x Deen-0x commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Taskbar Blob Shape 1.1.1 — compatibility with newer Windows 11 builds

A recent Windows 11 update changed the taskbar's visual tree, which broke the search blob and all system tray blobs (date/time, control center, language, chevron) on updated systems. This release adapts to the new tree while keeping the previous one working — every fix is a union over both layouts, with no behavior changes on older builds.

Root causes and fixes

  • All tray blobs dead: SystemTrayFrameGrid is now a StackPanel (previously a Grid), so the try_as<Grid> cast returned null and the tray was never swept — no tray entry ever existed. All host-typed surfaces (entry/host records, root resolution, insertion, expression binding, sweeps) are generalized from Grid to Panel, which covers both. Hosting in a StackPanel requires nothing further by construction: the blob's negative margins zero its layout size (it cannot displace stacked siblings), and the position expression's - self.Offset terms cancel whatever slot the panel assigns.
  • Search blob dead: the inner SearchUx.SearchUI.SearchIconButton's name gained a generated suffix on newer builds (e.g. SearchIcon_2605), so the name lookup found nothing. The sweep now matches it by class, which is stable across builds.
  • Wrapper Grid inside button panels: newer builds wrap the contents of Taskbar.TaskListButtonPanel and SearchUx.SearchUI.SearchButtonRootGrid in an extra Grid, moving the visual state groups off the button's first child. Checked-state detection and the event-attach fallback now walk first children (bounded to 3 hops) until a node actually carries state groups — on older builds this resolves to the first child immediately, as before.
  • Taskbar.TaskListLabeledButtonPanel#IconPanel becoming Grid#IconPanel required no change: IconPanel was always resolved by name, never by class.

Tested

  • Windows 11 25H2 (OS Build 26200.9445): search open/close, date/time, control center, language and chevron blobs all activate/deactivate with their flyouts; tray icon add/remove re-discovery; task-button regression pass; enable/disable cycle restores all native visuals.
  • Older builds: compatibility by construction (all changes are supersets of the previous matching — Panel covers Grid, class matching covers the previously-named element, the group walk resolves to the first child on the old tree)

Changelog

  • Compatibility with newer Windows 11 builds: the tray host is matched as a Panel (SystemTrayFrameGrid became a StackPanel), the search icon button is matched by class (its name gained a generated suffix), and visual-state detection tolerates the new wrapper Grid inside the button panels. No behavior changes on older builds.

A recent Windows 11 update changed the taskbar's visual tree, breaking
the search blob and all system tray blobs (including the clock). All
fixes are unions over the old and new trees, so older builds keep
working unchanged:

- SystemTrayFrameGrid is now a StackPanel (was a Grid), so the
  Grid cast failed and the tray was never swept. All host types are
  generalized from Grid to Panel; hosting in a StackPanel needs no
  further changes, since the blob's negative margins zero its layout
  size and the position expression cancels self.Offset.
- SearchUx.SearchUI.SearchIconButton's name gained a generated suffix
  (e.g. SearchIcon_2605), so the name match found nothing. The sweep
  now matches the inner search button by class.
- Button panel contents are wrapped in an extra Grid, moving the
  visual state groups off the button's first child. State detection
  and the event-attach fallback now walk first children (bounded)
  until a node actually carries groups.
- TaskListLabeledButtonPanel#IconPanel becoming Grid#IconPanel needed
  no change: IconPanel was always resolved by name, not class.
@windhawk-reviewer windhawk-reviewer Bot added the waiting-for-author The author's turn: request an AI review, or respond to one that was posted. label Sep 9, 2026
@windhawk-reviewer

Copy link
Copy Markdown

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 /ai-review. Once you're happy with the result, comment /ready-for-reviewer to hand it over to a human reviewer.

See the pull request review process for the full details.

@Deen-0x

Deen-0x commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Sep 9, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

Note: 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.


No blocking issues — this looks good to merge.

Each of the three changes is a strict superset of the previous matching (Panel covers Grid, the class match covers the element the old name match found, and the bounded first-child walk resolves at hop 0 when the first child already carries the groups), so older builds keep the previous behavior. The SystemTrayFrameGrid type change is independently corroborated in this repo: neiz-supersmile-audio-visualizer already treats it as "Grid or StackPanel", and windows-11-taskbar-styler carries dual selectors for the new wrapper Grid inside the button panels. The rest of the mod is unchanged and still checks out: settings block and code reads are in sync, HookSymbols is still one call per module, LoadLibraryExW is still hooked through kernelbase, and the global-object/teardown handling (the [[clang::no_destroy]] std::optional<> entry list, the two unload barriers) is untouched.

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • Naming drift after the GridPanel change. The types moved but the names didn't: GetHostRootGrid, SweptHost::grid / SweptHost::trayGrid, BlobEntry::grid, parentGrid, resweepGrids, and InsertBlobBelowRepeater(Panel const& grid, ...). Renaming at least the function (GetHostRootPanel) and that parameter would stop the next reader from assuming a Grid is guaranteed — the doc comment above the function already says "hosting panel" while the name still says grid.

  • GetStateGroupHost can descend into a nested control's own state groups. The walk stops at the first node that carries any groups, and IsSystemButtonChecked then treats any state named Checked* on that node as "flyout open". At hop 0 that node is the button's template root, which is what you want; at hops 1–2 it could be an inner Button/ToggleButton inside the wrapper, whose Checked state has nothing to do with the flyout. A template wrapper is a plain Grid/Border, so a cheap guard is to refuse to descend into anything that is itself a Control:

    for (int i = 0; i < 3 && node; i++) {
        try {
            if (VisualStateManager::GetVisualStateGroups(node).Size() > 0) return node;
        } catch (...) {}
        // Only plain layout wrappers are walked through: a Control owns its own
        // state groups and is never our button's template root.
        if (node.try_as<Control>()) return nullptr;
        node = VisualTreeHelper::GetChildrenCount(node) > 0
            ? VisualTreeHelper::GetChild(node, 0).try_as<FrameworkElement>()
            : nullptr;
    }
  • The search-button lookup now runs a recursive class walk on every sweep. FindDescendantByClass(child, L"SearchUx.SearchUI.SearchIconButton", 6) calls winrt::get_class_name per node (each one an HSTRING round-trip), where the old FindChildByName compared Name(). TaskbarSweepBody runs on every RootGrid SizeChanged, so this is the same cost pattern you already avoided for the tray by caching trayGrid on the host record. A winrt::weak_ref<FrameworkElement> searchButton on SweptHost, re-resolved only when it expires, would keep sweeps as cheap as before. Small either way.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Hosting the blob inside a StackPanel tray host — two things worth double-checking on 26200. The negative-margin trick does clamp the Path's DesiredSize to 0 (XAML clamps a negative desired size at zero), so it can't push stacked siblings — but a StackPanel isn't entirely indifferent to an extra child:

    • StackPanel.Spacing is applied per gap between children, not per pixel of child size, so if the new SystemTrayFrameGrid has a non-zero Spacing, each inserted blob adds one more gap. With date/time, control center, language and chevron blobs all enabled that's up to four extra gaps of tray shift, and it would appear only once the user enables the tray toggles (which default to off), so an enable-once test wouldn't necessarily show it. Worth confirming Spacing is 0 there — a one-line Wh_Log of trayGrid.try_as<StackPanel>().Spacing() on first resolve would settle it.
    • yBase is measured from the host panel's top (Translation.Y = yBase - self.Offset.Y), so tray blobs are anchored to the top of SystemTrayFrameGrid, not to the top of the taskbar. On the old Grid those coincided; if the new StackPanel is vertically centered or shorter than the taskbar, tray blobs will sit a few px lower than task-button blobs. Worth an eyeball check with a task-button blob and a tray blob active at the same time.
  • Which element actually lost its state groups. windows-11-taskbar-styler's dual selectors for the new tree keep the group annotation on the panel in both variants — Taskbar.TaskListButtonPanel@CommonStates > Grid > Border#BackgroundElement (L725) and SearchUx.SearchUI.SearchButtonRootGrid@CommonStates > Grid > Border#BackgroundElement (L1518) — i.e. the new Grid wraps the panel's contents, below the group host, rather than sitting above it. If that's right, GetStateGroupHost still resolves at hop 0 for those two, and the extra hops were needed for some other element (a tray SystemTray.OmniButton / SystemTray.IconView?). No code change either way — the walk is harmless — but it's worth pinning down which element it was, so the comment above the function describes the real tree and the next build change is diagnosed against the right node.

  • Search modes other than "icon only". The class match will now also find a SearchUx.SearchUI.SearchIconButton if one exists (even collapsed) in the other search modes, where the old Name() == L"SearchIcon" match found nothing. As far as I can tell that's benign — a collapsed/zero-width element yields ActualWidth() == 0, so entry->bound stays false and show = isActive && entry->bound never suppresses the native visuals — but if you still have a machine in "Search box" / "Search icon and label" mode, a quick pass there would confirm nothing appears where it shouldn't.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Sep 9, 2026
@Deen-0x

Deen-0x commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

/ready-for-reviewer

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-reviewer Ready for a human reviewer, and in the queue for one. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Sep 9, 2026
@Deen-0x
Deen-0x marked this pull request as draft September 10, 2026 09:53
@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-reviewer Ready for a human reviewer, and in the queue for one. labels Sep 10, 2026
@windhawk-reviewer

Copy link
Copy Markdown

This pull request was converted to a draft, so it left the human review queue and is back to waiting-for-author.

Mark it as ready for review and comment /ready-for-reviewer to hand it over to a human reviewer again. See the pull request review process for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-author The author's turn: request an AI review, or respond to one that was posted.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant