Skip to content

Title: Fix 35 upstream bugs: volume flyout, mixer, media flyout, visualizer, taskbar widget, startup - #1108

Open
frans15-2006 wants to merge 38 commits into
unchihugo:masterfrom
frans15-2006:upstream-pr
Open

Title: Fix 35 upstream bugs: volume flyout, mixer, media flyout, visualizer, taskbar widget, startup#1108
frans15-2006 wants to merge 38 commits into
unchihugo:masterfrom
frans15-2006:upstream-pr

Conversation

@frans15-2006

Copy link
Copy Markdown
Contributor

Summary

Batch fix for 35 open issues across volume flyout/mixer, media flyout, taskbar widget, visualizer, and startup paths. Each commit is scoped to one issue and references its number. No new dependencies were added.

Motivation

Closes #1086, #1075, #1078, #1087, #1084, #1015, #984, #1096, #1085, #1019, #1094, #1071, #1107, #1029, #1083, #973, #961, #1065, #1052, #966, #983, #987, #1039, #1076, #920, #746, #153, #659, #608, #529, #843, #817, #849, #802

Type of Change

  • Bug fix

What Changed

Volume flyout / mixer

Media flyout / widget

Taskbar widget / visualizer

System integration

Additional Information

Developed via AI-assisted tooling on a Linux sandbox without the .NET Windows workload. Built and manually verified on Windows (net10.0-windows) with dotnet build succeeding at 0 errors. Manually tested: startup behavior, volume flyout, media flyout, mixer names/icons, Alt+Tab, sleep/resume, audio device switching, seekbar, and native OSD suppression — all passing.

Checklist

  • Code changes are manually tested and working.
  • Formatting and naming are consistent with the project.
  • Self-review of changes is done.
  • AI tools were used (if yes, I reviewed and fully understand the changes myself).

frans15-2006 and others added 30 commits September 5, 2026 21:52
SongImage ImageBrush used Stretch=UniformToFill inside the fixed 78x78
frame, center-cropping rectangular covers. Use Uniform so the full artwork
is always visible (letterboxed).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Any duplicate launch signals the running instance to open its settings
window, so duplicate autostart entries/boot storms surface a window on
every login. Gate the signal: when the first instance started <30s ago,
the duplicate exits silently. Interactive re-launches keep opening settings.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…exclusive mode)

pauseOtherMediaSessionsIfNeeded ran on every media-property change, so
thumbnail refreshes/progress updates re-paused the other app even when
playback focus never moved. Gate the metadata path on genuine
new-track/status boundaries (state transitions still pause via
CurrentSession_OnPlaybackStateChanged). Harden PauseOtherSessions fan-out
(null session guard + per-session catch; it is fire-and-forget).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…tion change

- Handle WM_DISPLAYCHANGE (was ignored): immediately drop cached tray/XAML
  handles, poisoned automation lookups and monitor id, then re-resolve the
  taskbar/position instead of running up to 1.5s stale. WindowProc is now an
  instance method to reach instance state.
- Invalid DPI no longer keeps the stale hit-test region: reset to empty
  (invisible + click-through) instead of leaving a ghost that eats clicks.
- Sanitize region rects (NaN/infinity/overflow/non-positive) to Empty so a
  garbage rect from transient geometry can never swallow taskbar input.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Process.MainModule throws Access Denied for admin-level processes seen
from a non-elevated caller, so sessions fell back to 'Unknown' with no
icon. Resolve exe paths via OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)
+ QueryFullProcessImageName (granted cross-integrity), then read
FileVersionInfo/icons from the file itself: new MediaPlayerData.TryGetProcessPath
shared by the mixer icon lookup and the session display-name fallback chain
(FileDescription -> exe name -> window title/ProcessName). Process handles
now disposed.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…it audio

OnDataAvailable assumed 16-bit int or 32-bit float and treated interleaved
channels as mono samples: 24-bit streams (typical of spatial/Atmos
pipelines) decoded as permanent silence, and 6/8-channel streams fed the
FFT at multiples of the real rate. Decode per WaveFormat (8/16/24/32-int,
32-float via Encoding) and downmix each frame to mono, with a non-finite
guard. Stereo behavior is equivalent (L/R average vs alternating).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Explorer recreates the OSD XAML island (new HWND), so the stored hidden
handle is dead and ShowFlyout's Zero-gate never re-hides. On
TaskbarCreated recovery (post-stabilization), reset the handle and
re-hide with retries, gated on VolumeControlEnabled.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…D hunt)

- HideVolumeOsd enumerated XAML islands with NULL child-after, which always
  returns the FIRST match: infinite 100%-CPU spin + hiding the wrong
  island whenever it missed criteria. Chain properly through islands and
  their content bridges.
- Both flyout auto-hide loops (async void) caught only TaskCanceledException:
  any other throw was an abnormal exit with no trace. Catch Exception, log,
  emergency-hide. Per-session poll guarded so one dead session can't break
  the mixer refresh.
- AudioSessionModel: suppress device write-back while syncing from device
  (same echo guard as master volume).
- Breadcrumb logs on volume flyout show/hide for the next crash report.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
… silent guards)

- OpenAnimation DIP scaling can race the window's DPI flip on mixed-DPI
  multi-monitor setups, stranding the flyout off-screen; queue a raw-pixel
  resting-position correction after the flight (no-op when already exact).
  Shared by media + volume flyouts.
- Normalize out-of-range Position setting to bottom-center instead of
  flying with a stale storyboard target.
- Log ShowMediaFlyout suppressions (null session/disabled/fullscreen) and
  every show with monitor + raw coords - these paths were silent, leaving
  empty logs for flyout-never-appears reports.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Alt+Tab/keys dying over time (regression vs v2.10, worse at boot):
HookCallback ran ShowMediaFlyout synchronously on the low-level keyboard
hook - blocking Dispatcher.Invoke, media COM fetches and animation setup
on every key press. Slow hooks stall all keystrokes system-wide and
Windows silently drops them. Debounce + queue via Dispatcher.BeginInvoke
so the hook returns immediately (volume path already deferred).

Dismiss jump-up-then-down:
CloseAnimation derived From from GetWindowPlacement (zeros for transient
handles) scaled by the selected monitor's DPI (wrong when the flyout
lives on another monitor). Now From comes from the window's actual
rendered position (PointToScreen + host monitor DPI, placement fallback),
To is always recomputed as From +/-20dip (pinned to From when animations
are off so stale storyboard values can't teleport the window).

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ng change

UpdateUi set the new single-copy text but left the previous song's
forever-loop TranslateTransform running until the async position pass
rebuilt marquees. Frames in between rendered the new text at the old
offset (empty widget), then snapped back (blink) - worst with loop
mode, which doubles the text.

- ResetMarquee helper stops the animation, zeroes X and restores
  single-copy text synchronously on song change and on hide
- Refresh cached StringWidth measurements + UpdateMarquees immediately
  for text changes only (play/pause updates skip it so scrolling never
  restarts); extracted shared RefreshCachedTextWidths for CalculateSize

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Manifest:
- Add desktop/com to IgnorableNamespaces (strict parsers reject unknown unmarked extension namespaces -> 'error in parsing the app package')
- Fix Dependencies versions (bogus MaxVersionTested 10.0.0.0, stale 14393 min) to match wapproj 19041/26100

Installer:
- Check for .NET 10 Desktop Runtime (x64) in FluentFlyout_Installer.bat; GitHub builds are framework-dependent so a missing runtime installs fine but the app silently never starts (no window, no Task Manager entry) - offer the download instead of failing silently
- Document the prerequisite in README.txt and README.md msixbundle section
- Drop stale dev-machine AppInstallerUri from wapproj

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…resume

Visualizer:
- Stop WASAPI cleanly on Suspend and clear cached device ID; on Resume wait 2.5s for audio stack then restart with backoff
- Extend restart retries 5x500ms -> 10x800ms plus one delayed 15s follow-up so slow resume without further system events still recovers
- Re-arm single-shot silence watchdog while silence is short (was firing once then going blind) and null-guard DataAvailable timer access
- Restart on abnormal RecordingStopped (device invalidated underneath us)

Volume mixer (same S3 root cause: stale MMDevice, no DefaultDeviceChanged on desktop):
- Subscribe to PowerModeChanged.Resume + SessionUnlock/Logon and re-resolve default render device after 2s settle, fixing frozen volume flyout and single-session mixer after sleep

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
The layout pass only updated SongImageBorder.Height when switching between
compact (36) and normal (78) layouts, leaving the 78px width from XAML in
place. In compact layout the cover box became 78x36, so the album art was
rendered stretched/cropped instead of square. Set Width alongside Height and
use UniformToFill so non-square source art fills the square box consistently
with the taskbar widget and next-up flyout.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…ihugo#973)

The mixer enumerated every WASAPI session and added a row for each, so apps
that open multiple sessions or multiple processes of the same executable
(e.g. Flow Launcher) appeared several times. Deduplicate by process id and
by resolved display name while enumerating, matching the native Windows
mixer's per-app grouping. 'System sounds' (pid 0) stays a single entry.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…nchihugo#1087)

Some apps (notably packaged and elevated ones) report an AudioSessionControl
DisplayName that is an unexpanded indirect string resource such as
"@%SystemRoot%\System32\foo.dll,-101" or "@{Package?ms-resource:...}".
That raw string was used as the mixer row label, showing garbage or an empty
name. Treat any '@'-prefixed DisplayName as unresolved so the existing
executable-based fallback (FileDescription, then file name) supplies a real
app name.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…chihugo#1086, unchihugo#1076)

The view model attached the default render endpoint once at construction. If
that failed (audio stack not ready at startup) or the cached MMDevice was
later invalidated (driver reset, endpoint reconfiguration, DefaultDeviceChanged
reporting an id that no longer resolves), every subsequent read threw and was
swallowed, so the flyout kept displaying the last known value while the real
system volume changed - the frozen number users report.

- add EnsureDeviceAttached() to lazily re-resolve the default endpoint
- drop the cached device and reattach when a master-volume read fails
- fall back to the default endpoint when GetDeviceById returns null
- ensure a device before adjusting master volume from the taskbar widget

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…o#1039)

With "Automatically hide the taskbar" enabled the taskbar is excluded from the
monitor work area, so flyouts anchored 16px from the screen edge sat exactly
where the taskbar slides out and were overlapped by it. The native volume OSD
reserves that space; we now do the same.

Add SHAppBarMessage/APPBARDATA P/Invokes and a GetAutoHideTaskbarInset() helper
that returns the auto-hidden taskbar's thickness for the relevant screen edge
(0 when auto-hide is off or the taskbar is on another edge), and apply it to
every bottom- and top-anchored flyout placement, including GetFinalPosition
used to stack the volume flyout above the media flyout.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
pauseOtherMediaSessionsIfNeeded ran for whichever session raised a playback or
media-property event. Any background player that reported itself as Playing
therefore paused the session the user had just started, which in turn raised an
event and paused the first one back. Users experienced this as an undocumented
"exclusive audio mode" that ping-pongs between e.g. Spotify and a browser
video, with no setting that appeared to control it.

Restrict the fan-out to the currently focused/active session and null-guard the
playback info lookup, so the feature does what its setting describes: the
session you are controlling pauses the rest.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…nchihugo#961)

When a song restarts (loop/repeat in YouTube Music) the player briefly
republishes empty media properties. That blank snapshot was pushed straight to
the taskbar widget, which then displayed just the album art with no title or
artist, and it was also stored in the dedupe cache so the following real update
could be swallowed. Skip blank metadata while the session is still alive.

The next-up flyout additionally compared only against the last shown title, so
a looped track - whose title is unchanged - never triggered it again. Treat a
republished title as a new song when it is not a thumbnail-only update.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…go#1065, unchihugo#1052)

The widget window is reparented into Shell_TrayWnd as a WS_CHILD. When Explorer
restarts, that parent HWND is destroyed and recreated; our child window survives
with its hit-test region intact - which is why users report the widget and
visualizer still respond to clicks while being completely invisible - but it is
never composited into the new taskbar again. Merely calling SetParent on the
next position tick does not restore rendering.

Rebuild the taskbar window once Explorer has stabilized (we already wait for the
new Shell_TrayWnd to have geometry there), alongside the existing tray-icon and
native-OSD recovery.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…hihugo#1071)

The capture watchdog was created in Start() but only ever armed from
OnDataAvailable. If StartRecording() succeeded while the underlying stream was
dead - the usual outcome of a desktop S3 sleep/wake cycle, where WASAPI accepts
the AudioClient but never delivers a buffer - no callback ever arrived, so the
watchdog was never started, the "no audio callbacks" recovery path never ran,
and the visualizer stayed frozen until the app was restarted manually.

Arm the watchdog right after the capture starts, and clean up a partially
constructed capture when Start() throws so the next restart attempt can bind
the endpoint.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…hugo#983)

Locking the PC and letting the monitors sleep tears down and recreates taskbar
child windows without necessarily producing a WM_DISPLAYCHANGE for us, so the
widget was simply gone after unlocking and only came back by restarting the
app. The existing display-environment refresh (which recreates the taskbar
window and re-syncs DPI/placement) already handles this case - it just was
never triggered.

Subscribe to SystemEvents.SessionSwitch (unlock/logon/remote or console
connect) and PowerModeChanged (Resume) and route them through the existing
debounced ScheduleDisplayEnvironmentRefresh, unsubscribing during cleanup.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Turning off a secondary display or changing the projection mode makes
EnumDisplayMonitors return an empty list for a short window. Both
MonitorUtil.GetSelectedMonitor and TaskbarWindow.GetSelectedTaskbarHandle then
evaluated Math.Clamp(index, 0, -1), which throws ArgumentException because min
is greater than max.

That exception escaped from flyout placement and from every taskbar position
tick, so the widget was left with a stale hit-test region: invisible but still
swallowing taskbar clicks, matching the reported "taskbar stops responding
until the widget is disabled".

Return a default MonitorInfo / the primary Shell_TrayWnd instead; the next tick
picks up the real topology once Windows has settled.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…nchihugo#1078)

Hiding the Windows volume flyout was attempted lazily, from the first
ShowFlyout call, and only once. Two consequences: the native OSD still appeared
for the first volume change after login, and if the XAML island wasn't
enumerable at that moment (common right after login or an Explorer restart) the
handle stayed zero and the native flyout kept showing for the rest of the
session with no further attempt.

Kick off the existing retrying hide routine once the mixer window is created
when the volume flyout feature is enabled.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
VolumeMixerWindow.ShowFlyout is async void, and only its auto-hide loop was
wrapped in a try/catch. Everything before it - blur setup, mixer refresh,
placement/OpenAnimation, Show, and the CancellationTokenSource swap - ran
unguarded, so any exception there (window torn down mid-show by a display
refresh, DWM/blur call failing, CTS already disposed) escaped an async void
method and terminated the process. Users saw the app vanish right after
pressing a volume key, with logs ending mid-session.

Wrap the show path, tolerate a disposed CTS in both ShowFlyout and OnClosed,
and guard the deferred expand dispatch.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
frans15-2006 and others added 7 commits September 6, 2026 10:27
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…inates

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
…anges

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@github-actions github-actions Bot added MainWindow / Media Flyout Changes to MainWindow including the Media Flyout TaskbarWindow Changes to TaskbarWindow; the container for taskbar widgets Volume Flyout Changes to the Volume Flyout or Volume Mixer Taskbar Widget Changes to the Taskbar Media Widget labels Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MainWindow / Media Flyout Changes to MainWindow including the Media Flyout Taskbar Widget Changes to the Taskbar Media Widget TaskbarWindow Changes to TaskbarWindow; the container for taskbar widgets Volume Flyout Changes to the Volume Flyout or Volume Mixer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Volume Flyout does not correctly update current volume level

1 participant