Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Before publishing a version, add a `## [VERSION]` section to **both** files: the workflow publishes the section matching the tag from each one and fails if either is missing.

## [Unreleased]
### Fixes
- FPS counter: the style saved in Settings now applies as soon as the session starts. Before, the counter only showed up after cycling the "FPS counter" row in the session menu. (#139)
- Back to the PC: the request to close Big Picture now only goes to Steam's own windows. Before, a full-screen game (SDL ones, like Valve's and many indies) could get it and close. (#140)

## [1.6.0-beta.7]
### Heads-up: beta version, not the stable one
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CHANGELOG.en-US.md`. Antes de publicar uma versão, adicione uma seção `## [VERSÃO]` com o changelog daquela versão **nos dois arquivos**. O workflow publica as duas seções correspondentes à tag na mesma release e falha se faltar alguma.

## [Unreleased]
### Correções
- Contador de FPS: o estilo salvo em Ajustes agora vale assim que a sessão começa. Antes, o contador só aparecia depois de alternar a linha "Contador de FPS" no menu da sessão. (#139)
- Voltar ao PC: o pedido de fechar o Big Picture só vai para janelas da própria Steam. Antes, um jogo em tela cheia (os feitos com SDL, como os da Valve e muitos indies) podia receber o pedido e fechar. (#140)

## [1.6.0-beta.7]
### Atenção: versão beta, não é a estável
Expand Down
2 changes: 2 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Pré-condições: lançador = Steam Big Picture; Ajustes → "Mandar a Steam par
- [ ] Sair pelo próprio Big Picture (Sair → Sair do Big Picture): a mesa volta e a Steam permanece na bandeja. Resultado: ______
- [ ] O processo da Steam continua no Gerenciador de Tarefas; clicar no ícone da bandeja reabre o cliente sem login nem reinício. Resultado: ______
- [ ] Com um **jogo da Steam rodando**: a Steam fica aberta e o log mostra `Steam: mantida aberta (jogo em execução, id …)`; o Big Picture ainda é fechado. Resultado: ______
- [ ] Com um **jogo SDL em tela cheia sem bordas** aberto (ex.: CS2, Dota 2, Half-Life: Alyx ou um indie feito com SDL) e não salvo: voltar ao PC **não fecha o jogo**; o Big Picture fecha e, se o jogo passou por Big Picture na detecção, o log mostra `Fechar Big Picture: janela em tela cheia não é da Steam, mantida aberta`. Resultado: ______
- [ ] Com o ajuste **desligado**: o Big Picture fecha, a Steam fica aberta (`Steam: mantida aberta (desligado nos ajustes)`). Resultado: ______
- [ ] Desligar o ajuste, fechar e reabrir o Console Mode e iniciar uma sessão Big Picture: a preferência continua desligada e a Steam fica aberta. Resultado: ______
- [ ] Com o Steam aberto e o estado do jogo indisponível no Registro (valor `RunningAppID` ausente ou ilegível): a Steam fica aberta e o log informa que não foi possível verificar se há jogo em execução. Resultado: ______
Expand Down Expand Up @@ -393,3 +394,4 @@ Pré-requisito: RivaTuner Statistics Server instalado, com "Show On-Screen Displ
- [ ] Com o RTSS fechado, ligar Compacto abre o RTSS e o contador aparece.
- [ ] Voltar ao PC: o nosso some e o do Afterburner volta.
- [ ] A escolha e o layout ficam salvos para a próxima sessão.
- [ ] Com Compacto (ou outro estilo nosso) já salvo em Ajustes, iniciar a sessão: o contador aparece e o do Afterburner some sem precisar mexer na linha do menu.
13 changes: 13 additions & 0 deletions src/ConsoleMode/Models/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ public sealed class AppConfig
/// <summary>Turning the TV on / to the PC's input when console mode starts (issue #75).</summary>
public TvControlConfig Tv { get; set; } = new();

/// <summary>
/// A copy with other monitor fields and every other setting as it is here. A copy, not a field-by-field
/// rebuild: a setting added later is carried along without anyone having to remember it.
/// </summary>
public AppConfig WithMonitors(string focusMonitor, List<string> hideMonitors, Dictionary<string, SavedDisplayMode> monitorModes)
{
var copy = (AppConfig)MemberwiseClone();
copy.FocusMonitor = focusMonitor;
copy.HideMonitors = hideMonitors;
copy.MonitorModes = monitorModes;
return copy;
}

[JsonIgnore]
public string SetupKey =>
$"{FocusMonitor}|{(MonitorModes.TryGetValue(FocusMonitor, out var mode) ? mode.Key : "current")}";
Expand Down
35 changes: 1 addition & 34 deletions src/ConsoleMode/Services/ConsoleEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,40 +407,7 @@ private AppConfig ResolveMonitorNames(AppConfig config)
foreach (var (key, mode) in config.MonitorModes)
modes[Monitors.ResolveName(key)] = mode;

return new AppConfig
{
Version = config.Version,
AppLanguage = config.AppLanguage,
FocusMonitor = focus,
HideMonitors = hide,
HideStrategy = config.HideStrategy,
FullscreenMode = config.FullscreenMode,
PlaynitePath = config.PlaynitePath,
AudioDeviceId = config.AudioDeviceId,
AudioDeviceName = config.AudioDeviceName,
AudioAutoSwitch = config.AudioAutoSwitch,
FpsLimit = config.FpsLimit,
MonitorModes = modes,
HdrEnable = config.HdrEnable,
VrrEnable = config.VrrEnable,
UiMode = config.UiMode,
TourDone = config.TourDone,
ConfirmedSetup = config.ConfirmedSetup,
CheckUpdates = config.CheckUpdates,
BetaUpdates = config.BetaUpdates,
HomeShortcut = config.HomeShortcut,
MenuShortcut = config.MenuShortcut,
ExitShortcut = config.ExitShortcut,
ShortcutsOnboardingDone = config.ShortcutsOnboardingDone,
HomeButtonShortPress = config.HomeButtonShortPress,
CloseSteamOnRestore = config.CloseSteamOnRestore,
AutoStartOnController = config.AutoStartOnController,
InterfaceSounds = config.InterfaceSounds,
ConsoleBackground = config.ConsoleBackground,
ConsoleBackgroundImage = config.ConsoleBackgroundImage,
SkippedUpdateVersion = config.SkippedUpdateVersion,
Tv = config.Tv
};
return config.WithMonitors(focus, hide, modes);
}

private void MoveToFocus(string monitorName, nint[] handles, ScreenRect? rect)
Expand Down
20 changes: 18 additions & 2 deletions src/ConsoleMode/Services/LaunchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ public bool IsPlayniteActive(ConsoleRuntimeState state)
/// <summary>
/// Leaves Big Picture the clean way, through Steam's own URL (back to the desktop client), so it does not
/// stay open on the desk once the screens are back. Halfway through the wait, if it is still there, the
/// window is asked to close (WM_CLOSE: the X button, not a kill). Returns whether it is gone.
/// window is asked to close (WM_CLOSE: the X button, not a kill), and only if it belongs to Steam: a game in
/// full screen can pass for Big Picture in <see cref="GetBigPictureHandles"/>. Returns whether it is gone.
/// </summary>
public bool CloseBigPicture(ConsoleRuntimeState state, int timeoutMs = 6000)
{
Expand All @@ -155,12 +156,27 @@ public bool CloseBigPicture(ConsoleRuntimeState state, int timeoutMs = 6000)
{
nudged = true;
foreach (var handle in handles.Concat(GetBigPictureHandles()).Distinct())
NativeWindows.PostMessage(handle, NativeWindows.WmClose, 0, 0);
{
if (IsSteamWindow(handle)) NativeWindows.PostMessage(handle, NativeWindows.WmClose, 0, 0);
else AppLog.Write("Fechar Big Picture: janela em tela cheia não é da Steam, mantida aberta");
}
}
}
return !IsBigPictureActive(state);
}

private static bool IsSteamWindow(nint handle)
{
NativeWindows.GetWindowThreadProcessId(handle, out var processId);
if (processId == 0) return false;
try
{
using var process = Process.GetProcessById(unchecked((int)processId));
return SteamShutdown.IsSteamProcess(process.ProcessName);
}
catch { return false; /* gone, or not ours to read: not known to be Steam, so left alone */ }
}

public bool IsBigPictureActive(ConsoleRuntimeState state)
{
if (state.CachedBigPictureHandle != 0)
Expand Down
11 changes: 9 additions & 2 deletions src/ConsoleMode/Services/SteamShutdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ namespace ConsoleMode.Services;
/// </summary>
public static class SteamShutdown
{
/// <summary>
/// Steam's own processes. Only their windows may be asked to close: a game is an SDL_app in full
/// screen just like Big Picture, and it must never get the WM_CLOSE meant for Steam.
/// </summary>
public static bool IsSteamProcess(string? processName) =>
string.Equals(processName, "steam", StringComparison.OrdinalIgnoreCase) ||
string.Equals(processName, "steamwebhelper", StringComparison.OrdinalIgnoreCase);

public static bool IsClientWindowCandidate(string processName, string title, string className, bool visible, bool hasOwner) =>
visible && !hasOwner &&
(string.Equals(processName, "steam", StringComparison.OrdinalIgnoreCase) ||
string.Equals(processName, "steamwebhelper", StringComparison.OrdinalIgnoreCase)) &&
IsSteamProcess(processName) &&
string.Equals(title.Trim(), "Steam", StringComparison.OrdinalIgnoreCase) &&
(string.Equals(className, "Chrome_WidgetWin_1", StringComparison.Ordinal) ||
string.Equals(className, "vguiPopupWindow", StringComparison.Ordinal) ||
Expand Down
61 changes: 61 additions & 0 deletions tests/ConsoleMode.Tests/AppConfigTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Reflection;
using ConsoleMode.Models;
using ConsoleMode.Services;

namespace ConsoleMode.Tests;

public class AppConfigTests
{
[Fact]
public void Copy_for_a_session_swaps_only_the_monitor_fields()
{
var config = new AppConfig { FocusMonitor = "id-tv", HideMonitors = ["id-desk"] };
var hide = new List<string> { @"\\.\DISPLAY1" };
var modes = new Dictionary<string, SavedDisplayMode>();

var copy = config.WithMonitors(@"\\.\DISPLAY2", hide, modes);

Assert.Equal(@"\\.\DISPLAY2", copy.FocusMonitor);
Assert.Same(hide, copy.HideMonitors);
Assert.Same(modes, copy.MonitorModes);
Assert.Equal("id-tv", config.FocusMonitor);
Assert.Equal(["id-desk"], config.HideMonitors);
}

// The session used to get a field-by-field copy that forgot the FPS counter: the saved style
// was never applied when console mode started.
[Fact]
public void Copy_for_a_session_keeps_the_fps_counter()
{
var layout = new FpsOverlayLayout { SingleLine = true };
var config = new AppConfig { FpsOverlay = RtssOverlay.Compact, FpsOverlayLayout = layout };

var copy = config.WithMonitors("", [], []);

Assert.Equal(RtssOverlay.Compact, copy.FpsOverlay);
Assert.Same(layout, copy.FpsOverlayLayout);
}

[Fact]
public void Copy_for_a_session_keeps_every_other_setting()
{
string[] swapped = [nameof(AppConfig.FocusMonitor), nameof(AppConfig.HideMonitors), nameof(AppConfig.MonitorModes), nameof(AppConfig.SetupKey)];
var config = new AppConfig();
var properties = typeof(AppConfig).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite && !swapped.Contains(p.Name))
.ToList();
foreach (var property in properties)
{
// A value that differs from the default, so a property left behind shows up.
if (property.PropertyType == typeof(string)) property.SetValue(config, "changed-" + property.Name);
else if (property.PropertyType == typeof(bool)) property.SetValue(config, !(bool)property.GetValue(config)!);
else if (property.PropertyType == typeof(int)) property.SetValue(config, (int)property.GetValue(config)! + 7);
else property.SetValue(config, Activator.CreateInstance(property.PropertyType));
}

var copy = config.WithMonitors("", [], []);

foreach (var property in properties)
Assert.Equal(property.GetValue(config), property.GetValue(copy));
}
}
11 changes: 11 additions & 0 deletions tests/ConsoleMode.Tests/SteamShutdownTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ public class SteamShutdownTests
public void Only_unowned_visible_steam_client_windows_are_close_candidates(string process, string title, string className, bool visible, bool owned, bool expected) =>
Assert.Equal(expected, SteamShutdown.IsClientWindowCandidate(process, title, className, visible, owned));

[Theory]
[InlineData("steam", true)]
[InlineData("steamwebhelper", true)]
[InlineData("SteamWebHelper", true)]
[InlineData("cs2", false)]
[InlineData("steamapp", false)]
[InlineData("", false)]
[InlineData(null, false)]
public void Only_steams_own_processes_may_be_asked_to_close(string? process, bool expected) =>
Assert.Equal(expected, SteamShutdown.IsSteamProcess(process));

[Fact]
public void Steam_window_is_closed_to_tray_when_big_picture_was_the_launcher_and_no_game_is_running()
{
Expand Down
Loading