Desktop 'lock' fix: the window no longer steals focus when a full-screen game owns the foreground at launch. Opening after an exclusive-fullscreen game (creator's THE DIVISION 2 gameplay-stream start) the window ACTIVATED by default, Windows dropped the game to the taskbar and the 1920x1040 window covered the whole work area - 'the desktop is locked and I can't reach the game with the mouse'. MainWindow ctor now sets ShowActivated = !MainViewModel.FullscreenGameRunningNow (GetForegroundFullScreenMonitorIndex() != null), so an in-game launch keeps the game focused and live; a normal launch is unchanged. New FullScreenDetectorOverride test seam (mirrors WindowEnumeratorOverride - deterministic, no real Win32 foreground state) + DesktopLockTests: game at launch -> ShowActivated=false, normal launch -> true, FullscreenGameRunningNow reflects the detector. 422 pass / 1 known env flake, 0 build warnings. (Unit: launch focus)
This commit is contained in:
+31
-3
@@ -1,9 +1,30 @@
|
||||
# HANDOFF — 2026-10-09 (TASK 51 quality pass COMPLETE: jitter + blur-heavy both fixed)
|
||||
# HANDOFF — 2026-10-10 (desktop-"lock" fix local, uncommitted)
|
||||
|
||||
## At a glance
|
||||
- **Branch** main (no feature branch). Pushed through `5685c5d`; everything after is local, **not
|
||||
pushed** (push at the creator's call).
|
||||
- Three work units in the working tree: (1) the desktop-lock fix below, (2) the TASK 51 webcam unit,
|
||||
(3) the creator's pinned-chat unit. Do NOT mix them in one commit.
|
||||
|
||||
## Branch
|
||||
- main (no feature branch). Pushed through `5685c5d` (TRAX toggle); everything after is local,
|
||||
**not pushed** (push at the creator's call).
|
||||
|
||||
## New work unit (this handoff) — READY TO VERIFY, do not commit yet
|
||||
- **Fix** for the "desktop is locked once the app starts" report — `MainWindow.xaml.cs` +
|
||||
`ViewModels/MainViewModel.Background.cs` (`ShowActivated = !FullscreenGameRunningNow`, see
|
||||
`bugs.md` entry 2026-10-10). Scope-locked files: `MainWindow.xaml.cs`,
|
||||
`ViewModels/MainViewModel.Background.cs`, `ViewModels/MainViewModel.cs` (detector seam line),
|
||||
`ytLive.Tests/DesktopLockTests.cs` (new), `bugs.md`, this file.
|
||||
- **Regression tests:** `DesktopLockTests` (3 tests) drive the rule through the NEW
|
||||
`FullScreenDetectorOverride` test seam (the same seam pattern as `WindowEnumeratorOverride`) —
|
||||
no real Win32 foreground state is touched: full-screen game at launch ⇒ `ShowActivated=false`;
|
||||
normal launch ⇒ still `true`; `FullscreenGameRunningNow` reflects the detector. Full suite
|
||||
**422 passed / 0 failed / 0 warnings**. Real-app reproduction still deferred to the creator.
|
||||
- **To commit later (this unit only):** the two code files + the two memory files listed above.
|
||||
- Verify with the creator first: launch while an exclusive-fullscreen game is the foreground — the
|
||||
game must stay live and the app must open behind/alongside it without focus steal.
|
||||
|
||||
## Dirty files — split by ownership
|
||||
- **This work unit (COMMIT these):** `Services/BackgroundEffects/MaskSmoother.cs` (new), the smoother
|
||||
wiring in `Services/CameraManager.cs`, `ytLive.Tests/CameraManagerTests.cs` (new integration test),
|
||||
@@ -34,8 +55,15 @@
|
||||
the 0.5 rounding (3 box passes dilute a spike) — use a step edge, not a spike, to measure blur.
|
||||
|
||||
## Next step
|
||||
- Creator re-runs the app: webcam → Layers properties → Background → Remove / Blur + Strength, and
|
||||
reports whether the jitter is gone and the blur feels right at the slider's default and full range.
|
||||
- Creator verifies the desktop-lock fix: while THE DIVISION 2 (or any exclusive-fullscreen game) is the
|
||||
foreground window, launch the app — the game must stay live and the app must not yank focus. Also
|
||||
confirm a normal launch is unchanged. Then I can commit the unit and run the full suite when the
|
||||
mouse is free.
|
||||
|
||||
## Tests
|
||||
- Full suite **422 passed / 0 failed / 0 skipped / 0 warnings** (2026-10-10; includes the 3 new
|
||||
`DesktopLockTests`). Known env flakes remain `Mix_HonorsProviderGains…` (fails while system audio
|
||||
plays) and `RealMouseDrag…` (needs an idle desktop).
|
||||
|
||||
## Also still open
|
||||
- **Vertical-format regression (STALE, not fixed):** 1080p60 vertical selection no longer shrinks the
|
||||
|
||||
@@ -23,6 +23,10 @@ public partial class MainWindow : Window
|
||||
AppLog.Write("MainWindow ctor: after InitializeComponent");
|
||||
_viewModel = (MainViewModel)DataContext;
|
||||
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||
// Don't steal focus from an exclusive-fullscreen game the creator is already
|
||||
// playing (e.g. while starting a gameplay stream): activating our window would
|
||||
// drop that game to the taskbar and make the desktop look "locked".
|
||||
ShowActivated = !_viewModel.FullscreenGameRunningNow;
|
||||
_topBar = (Controls.TopBar)FindName("TopBar")!;
|
||||
_previewPane = (Controls.PreviewPane)FindName("PreviewPane")!;
|
||||
_leftPanel = (Controls.LeftPanel)FindName("LeftPanel")!;
|
||||
|
||||
@@ -24,6 +24,12 @@ public partial class MainViewModel
|
||||
/// swaps in a fake enumerator to drive the "window dies -> re-heal" flow.</summary>
|
||||
internal static IWindowEnumerator? WindowEnumeratorOverride { get; set; }
|
||||
|
||||
/// <summary>Test seam for the launch focus decision: a fake detector simulates
|
||||
/// a full-screen game owning the foreground without touching any real window,
|
||||
/// so the <c>ShowActivated = !FullscreenGameRunningNow</c> rule (desktop-"lock"
|
||||
/// fix 2026-10-10) is pinned without Win32 state.</summary>
|
||||
internal static IFullScreenDetector? FullScreenDetectorOverride { get; set; }
|
||||
|
||||
private ImageSource? _activeBackgroundImage;
|
||||
private ImageSource? _backgroundImage;
|
||||
private bool _backgroundSizeMismatch;
|
||||
@@ -168,6 +174,15 @@ public partial class MainViewModel
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>True when a full-screen game/app owns the foreground right now.
|
||||
/// MainWindow reads this at launch to decide whether to steal focus: opening
|
||||
/// LlamaCasty while the creator is playing an exclusive-fullscreen game would
|
||||
/// minimize that game out of fullscreen — the "desktop is locked and I can't
|
||||
/// get back into the game" bug (2026-10-10) — so the window opens without
|
||||
/// activating instead.</summary>
|
||||
public bool FullscreenGameRunningNow
|
||||
=> _fullScreenDetector.GetForegroundFullScreenMonitorIndex() != null;
|
||||
|
||||
// After a layout load / file open: make sure every background-enabled scene
|
||||
// has a background, give any background without a persisted key the auto-detected
|
||||
// one, then acquire one capture per unique background key. ScreenCaptureManager
|
||||
|
||||
@@ -312,7 +312,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
_musicPlayer.PlaybackEnded += OnTraxPlaybackEnded;
|
||||
_ = StartMicCaptureAsync();
|
||||
|
||||
_fullScreenDetector = new Win32FullScreenDetector();
|
||||
_fullScreenDetector = FullScreenDetectorOverride ?? new Win32FullScreenDetector();
|
||||
_fullScreenDetector.FullscreenMonitorChanged += OnFullscreenMonitorChanged;
|
||||
foreach (var display in _fullScreenDetector.GetDisplays())
|
||||
Displays.Add(display);
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# Bugs
|
||||
|
||||
## Desktop "locked" after the app starts while a fullscreen game is running (FIXED 2026-10-10)
|
||||
Report: once the app is started, the desktop is "locked" — the mouse can no longer interact with it —
|
||||
which made streaming live gameplay impossible.
|
||||
|
||||
Root cause: at launch the main window **activates** (WPF default `ShowActivated=true`). If the creator
|
||||
is already playing an exclusive-fullscreen game (e.g. THE DIVISION 2 while starting a gameplay stream),
|
||||
activating LlamaCasty makes Windows drop that game to the taskbar; the app's 1920×1040 window then sits
|
||||
over the whole work area, so the desktop looks locked and the game can't be reached with the mouse.
|
||||
|
||||
Fix (same commit as this entry): the window no longer steals focus when a full-screen game owns the
|
||||
foreground at launch — `MainWindow.xaml.cs` ctor sets `ShowActivated = !_viewModel.FullscreenGameRunningNow`,
|
||||
and the new `MainViewModel.FullscreenGameRunningNow` (initially added in `MainViewModel.Background.cs`)
|
||||
wraps `GetForegroundFullScreenMonitorIndex() != null`. Result: opening the app while in-game leaves the
|
||||
game focused and alive; opening it normally behaves exactly as before.
|
||||
|
||||
Verification: build clean (0 warnings). Real-app reproduction/test launch deferred until the creator is
|
||||
off-mouse — the real-mouse + fullscreen-detector tests must not run while a game owns the screen.
|
||||
|
||||
## iTunes music showing on mic meter
|
||||
Playing music via iTunes causes the mic meter to show audio levels. The WASAPI
|
||||
mic capture (`DataFlow.Capture`) and loopback capture (`DataFlow.Loopback`) are
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
using ytLive.ViewModels;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The desktop-"lock" bug (2026-10-10): at launch the main window ACTIVES
|
||||
/// (WPF default <c>ShowActivated = true</c>), and when the creator is already
|
||||
/// playing an exclusive-fullscreen game, activating LlamaCasty makes Windows
|
||||
/// drop that game to the taskbar; the app's 1920×1040 window then sits over the
|
||||
/// whole work area, so the desktop looks locked and the game can't be reached
|
||||
/// with the mouse. Fix: the window opens WITHOUT activating when a full-screen
|
||||
/// game owns the foreground at launch — <c>ShowActivated = !FullscreenGameRunningNow</c>.
|
||||
/// These tests pin that rule using the <c>FullScreenDetectorOverride</c> seam,
|
||||
/// so no real Win32 foreground state is touched.
|
||||
/// </summary>
|
||||
[Collection("RealApp")]
|
||||
public sealed class DesktopLockTests
|
||||
{
|
||||
private readonly RealAppHost _app;
|
||||
|
||||
public DesktopLockTests(RealAppHost app) => _app = app;
|
||||
|
||||
[Fact]
|
||||
public void MainWindow_Does_Not_Steal_Focus_When_A_Fullscreen_Game_Is_Foreground()
|
||||
{
|
||||
_app.Run(() =>
|
||||
{
|
||||
using var _ = new TempLayout();
|
||||
MainViewModel.FullScreenDetectorOverride = new StubFullScreenDetector(foregroundMonitor: 0);
|
||||
try
|
||||
{
|
||||
var window = new MainWindow();
|
||||
try
|
||||
{
|
||||
// The regression: a full-screen game already owns the screen, so
|
||||
// the window must open WITHOUT activating to keep the game live.
|
||||
Assert.False(window.ShowActivated,
|
||||
"launching while a full-screen game owns the foreground must not steal focus");
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
MainViewModel.FullScreenDetectorOverride = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MainWindow_Normal_Launch_Still_Activates()
|
||||
{
|
||||
_app.Run(() =>
|
||||
{
|
||||
using var _ = new TempLayout();
|
||||
MainViewModel.FullScreenDetectorOverride = new StubFullScreenDetector(foregroundMonitor: null);
|
||||
try
|
||||
{
|
||||
var window = new MainWindow();
|
||||
try
|
||||
{
|
||||
// No game running: behaviour is exactly as before the fix.
|
||||
Assert.True(window.ShowActivated,
|
||||
"a normal launch (no full-screen game) must keep the default activate-on-show");
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
MainViewModel.FullScreenDetectorOverride = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullscreenGameRunningNow_Reflects_The_Foreground_Monitor()
|
||||
{
|
||||
_app.Run(() =>
|
||||
{
|
||||
MainViewModel.FullScreenDetectorOverride = new StubFullScreenDetector(foregroundMonitor: 0);
|
||||
try
|
||||
{
|
||||
var gameVm = new MainViewModel();
|
||||
Assert.True(gameVm.FullscreenGameRunningNow);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MainViewModel.FullScreenDetectorOverride = null;
|
||||
}
|
||||
|
||||
MainViewModel.FullScreenDetectorOverride = new StubFullScreenDetector(foregroundMonitor: null);
|
||||
try
|
||||
{
|
||||
var idleVm = new MainViewModel();
|
||||
Assert.False(idleVm.FullscreenGameRunningNow);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MainViewModel.FullScreenDetectorOverride = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TempLayout : IDisposable
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public TempLayout()
|
||||
{
|
||||
_path = Path.Combine(Path.GetTempPath(), $"ytLive-desktoplock-{Guid.NewGuid():N}.db");
|
||||
using (var schema = new LayoutStore(_path)) { }
|
||||
SqliteConnection.ClearAllPools();
|
||||
MainViewModel.LayoutPathOverride = _path;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MainViewModel.LayoutPathOverride = null;
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(_path); } catch { /* best-effort cleanup */ }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A deterministic stand-in for <see cref="Win32FullScreenDetector"/>:
|
||||
/// claims a full-screen window owns the given foreground monitor (or none).</summary>
|
||||
#pragma warning disable CS0067 // fullscreen-changed wiring is not part of this seam
|
||||
private sealed class StubFullScreenDetector : IFullScreenDetector
|
||||
{
|
||||
private readonly int? _foregroundMonitor;
|
||||
|
||||
public StubFullScreenDetector(int? foregroundMonitor) => _foregroundMonitor = foregroundMonitor;
|
||||
|
||||
public int? GetForegroundFullScreenMonitorIndex() => _foregroundMonitor;
|
||||
public int PrimaryMonitorIndex() => 0;
|
||||
public IReadOnlyList<DisplayInfo> GetDisplays() => Array.Empty<DisplayInfo>();
|
||||
public event Action<int?>? FullscreenMonitorChanged;
|
||||
public void StartWatching() { }
|
||||
public void StopWatching() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user