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:
@@ -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