feat(backdrop): Capture Window… in-app window pin (TASK 38, Good Dog ONE test)
Click-to-pin an open window as the full-bleed Live backdrop, via the existing
window:<hwnd> capture path (same render layer as game/desktop). Session-scoped:
window:/picker: keys heal to auto on reload (HWND-recycle hazard); pin wins
while the window enumerates, then auto-fallbacks (game → desktop → static); a
dead window's capture session heals to auto, no dead ends.
Fix: ScreenCaptureSourceFactory.Resolve hex-parsed "window:0x<Hwnd>" WITHOUT
stripping the 0x prefix — NumberStyles.HexNumber rejects it, so every pin
resolved null ("No capture target for this key") and the heal silently rolled
back. Same flaw in IsAliveWindowPin (pin-wins guard was dead). Both fixed.
Commit also carries an out-of-scope prerequisite: PillRadioTests.cs:105 had a
committed stray token ("...PrimaryStartButtonLabel soil;", CS1003) blocking the
whole test-project compile — token removed.
Plus the previous session's uncommitted pricing docs (one-time $29/$49) that
share TASKS.md/ai.md/HANDOFF.md.
This commit is contained in:
@@ -18,6 +18,11 @@ public partial class MainViewModel
|
||||
private readonly IFullScreenDetector _fullScreenDetector;
|
||||
private readonly ScreenCaptureManager _screenCaptureManager;
|
||||
private readonly ScreenCaptureSourceFactory _screenCaptureFactory;
|
||||
private readonly IWindowEnumerator _windowEnumerator;
|
||||
|
||||
/// <summary>Test seam (mirrors CameraEnumeratorOverride): a real-Win32 test
|
||||
/// swaps in a fake enumerator to drive the "window dies -> re-heal" flow.</summary>
|
||||
internal static IWindowEnumerator? WindowEnumeratorOverride { get; set; }
|
||||
|
||||
private ImageSource? _activeBackgroundImage;
|
||||
private ImageSource? _backgroundImage;
|
||||
@@ -172,6 +177,14 @@ public partial class MainViewModel
|
||||
{
|
||||
HealBackgrounds();
|
||||
|
||||
// Transient keys ("picker:" picks and "window:" pins) are session-scoped
|
||||
// (see ScreenCaptureSourceFactory): a persisted HWND can be recycled to an
|
||||
// unrelated window after restart, so a reload never resurrects them — the
|
||||
// background falls back to auto-detection instead.
|
||||
foreach (var transient in AllBackgrounds()
|
||||
.Where(b => IsTransientCaptureKey(b.CaptureKey)))
|
||||
transient.CaptureKey = null;
|
||||
|
||||
var auto = ResolveAutoCaptureKey();
|
||||
if (auto == null)
|
||||
{
|
||||
@@ -220,17 +233,22 @@ public partial class MainViewModel
|
||||
// game is detected, fall back to the primary monitor (desktop view).
|
||||
private void OnFullscreenMonitorChanged(int? monitor)
|
||||
{
|
||||
// A live window pin outranks the fullscreen-game detector: while the
|
||||
// pinned window still enumerates, keep it as the backdrop (the creator's
|
||||
// explicit choice beats auto-detection). Only when the window is gone
|
||||
// does auto-fallback (game → desktop → static) reclaim the background.
|
||||
var current = AllBackgrounds().FirstOrDefault();
|
||||
if (current != null && IsAliveWindowPin(current.CaptureKey)) return;
|
||||
|
||||
if (monitor != null)
|
||||
{
|
||||
var newKey = $"{MonitorKeyPrefix}{monitor}";
|
||||
var current = AllBackgrounds().FirstOrDefault();
|
||||
if (current?.CaptureKey == newKey) return;
|
||||
_ = RedesignateBackgroundAsync(newKey);
|
||||
}
|
||||
else if (AllBackgrounds().Any(b => b.ShowDesktop))
|
||||
{
|
||||
var desktopKey = $"{MonitorKeyPrefix}{_fullScreenDetector.PrimaryMonitorIndex()}";
|
||||
var current = AllBackgrounds().FirstOrDefault();
|
||||
if (current?.CaptureKey == desktopKey) return;
|
||||
_ = RedesignateBackgroundAsync(desktopKey);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user