diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md
index 0a44b2e..d58d5ef 100644
--- a/CHANGELOG.en-US.md
+++ b/CHANGELOG.en-US.md
@@ -7,6 +7,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor
- Settings → "Receive test versions (alpha and beta)": anyone can join the tests of upcoming versions from the app. Off by default; turning it on shows a warning that test versions can have bugs.
### Fixes
+- Steam: when you go back to the PC (button, menu, shortcut, `stop` link or leaving Big Picture) Big Picture is always closed and Steam is asked to quit the normal way, its own Exit, without killing the process. Before, it stayed open and was often left glitchy. It does not close Steam if a game is running, and you can turn it off in Settings → "Close Steam when going back to the PC". (#121)
- Session menu: the notice that shows the shortcut when a session starts no longer gets stuck on screen; it goes away by itself after a few seconds. (#114)
- Displays: a monitor turned to portrait (rotated) keeps its rotation in the layout backup, so restoring the desk puts it back in portrait. Before, the rotation was never saved or restored. (#108)
- Playnite: a slow start no longer sends you back to the PC after a few seconds. When Playnite swaps its loading window for the main one, Console Mode now follows the new window and only restores the desk once Playnite actually closes. (#83)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13d4367..103bf90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH
- Ajustes → "Receber versões de teste (alpha e beta)": qualquer pessoa pode entrar nos testes das próximas versões pelo próprio app. Desligado por padrão; ao ligar, o app avisa que a versão pode ter bugs.
### Correções
+- Steam: ao voltar ao PC (botão, menu, atalho, link `stop` ou saindo do Big Picture) o Big Picture é sempre fechado e a Steam é chamada para fechar do jeito normal, o "Sair" dela, sem encerrar o processo à força. Antes ela ficava aberta e costumava ficar bugada. Não fecha a Steam se houver um jogo rodando, e dá para desligar em Ajustes → "Fechar a Steam ao voltar ao PC". (#121)
- Menu da sessão: o aviso que mostra o atalho no início da sessão não fica mais preso na tela; ele some sozinho depois de alguns segundos. (#114)
- Telas: um monitor girado (em retrato) mantém a rotação no backup do layout, então restaurar a mesa devolve o monitor em retrato. Antes a rotação nunca era salva nem restaurada. (#108)
- Playnite: quando ele demora para abrir, o app não volta mais para o PC depois de alguns segundos. Se o Playnite troca a janela de carregamento pela principal, o Console Mode passa a acompanhar a nova e só restaura a mesa quando o Playnite fecha de verdade. (#83)
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 4995db3..57793ba 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -106,6 +106,21 @@ Pré-condições: Ajustes → Interface = **Automático** (padrão).
- [ ] "Copiar diagnóstico" cola dispositivos + amostra; o `consolemode.log` tem a linha `Controles: …` de abertura com o DualSense listado. Resultado: ______
- [ ] Desconectar e reconectar durante o teste: a lista atualiza e a leitura continua. Resultado: ______
+## 3c. Fechar a Steam ao voltar ao PC
+
+Pré-condições: lançador = Steam Big Picture; Ajustes → "Fechar a Steam ao voltar ao PC" **ligado** (padrão); nenhum jogo aberto.
+
+- [ ] Voltar ao PC por cada caminho (botão "Voltar ao PC", menu da sessão, atalho do controle, `consolemode://stop`, tray → Restaurar): o **Big Picture fecha** e some da mesa, e a **Steam fecha sozinha** em alguns segundos (o ícone sai da bandeja, sem janela de erro). Resultado: ______
+- [ ] Sair pelo próprio Big Picture (Sair → Sair do Big Picture): a mesa volta e a Steam também fecha. Resultado: ______
+- [ ] A Steam **não é encerrada à força**: no Gerenciador de Tarefas ela some sem o aviso "o programa não está respondendo", e o log mostra `Steam: pedindo para fechar (sair normal)`. 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 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: ______
+- [ ] Steam **já fechada** antes de voltar: nada é aberto nem pedido (`Steam: não estava aberta`); a Steam não é iniciada de novo por engano. Resultado: ______
+- [ ] Lançador Playnite ou Modo Xbox: a Steam não é tocada. Resultado: ______
+- [ ] Se o Big Picture demorar a fechar (travado), a restauração da mesa continua e termina normalmente (o log mostra `Big Picture ainda aberto`). Resultado: ______
+
## 4. Regressões
- [ ] Interface Desktop: mapa de telas, `Segmented`, chips, tour de 3 passos e Ajustes continuam como antes. Resultado: ______
diff --git a/src/ConsoleMode/Models/Models.cs b/src/ConsoleMode/Models/Models.cs
index c1e25e4..c6895f6 100644
--- a/src/ConsoleMode/Models/Models.cs
+++ b/src/ConsoleMode/Models/Models.cs
@@ -47,6 +47,12 @@ public sealed class AppConfig
/// A short press of the Guide button is enough (Game Bar's own shortcut is turned off).
public bool HomeButtonShortPress { get; set; }
+ ///
+ /// Going back to the PC also quits Steam (its own Exit, never a kill), unless a game is running.
+ /// Big Picture itself is always closed.
+ ///
+ public bool CloseSteamOnRestore { get; set; } = true;
+
/// Enter console mode when a controller connects while the app is in the tray.
public bool AutoStartOnController { get; set; }
@@ -248,6 +254,7 @@ public sealed class ConsoleRuntimeState
public List HideMonitors { get; set; } = [];
public string HideStrategy { get; set; } = "disconnect";
public string FullscreenMode { get; set; } = "bigPicture";
+ public bool CloseSteamOnRestore { get; set; } = true;
public string? AudioDeviceId { get; set; }
public bool AudioAutoSwitch { get; set; }
public string? AudioDeviceHint { get; set; }
diff --git a/src/ConsoleMode/Native/NativeWindows.cs b/src/ConsoleMode/Native/NativeWindows.cs
index 472b14c..05218c8 100644
--- a/src/ConsoleMode/Native/NativeWindows.cs
+++ b/src/ConsoleMode/Native/NativeWindows.cs
@@ -72,6 +72,11 @@ public static bool ForceForeground(nint hwnd)
[DllImport("user32.dll")]
public static extern bool IsWindow(nint hWnd);
+ [DllImport("user32.dll")]
+ public static extern bool PostMessage(nint hWnd, uint msg, nint wParam, nint lParam);
+
+ public const uint WmClose = 0x0010;
+
[DllImport("user32.dll")]
public static extern nint SetWinEventHook(uint eventMin, uint eventMax, nint hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
diff --git a/src/ConsoleMode/Resources/Strings.en-US.json b/src/ConsoleMode/Resources/Strings.en-US.json
index bd8e198..ca00509 100644
--- a/src/ConsoleMode/Resources/Strings.en-US.json
+++ b/src/ConsoleMode/Resources/Strings.en-US.json
@@ -77,6 +77,8 @@
"ChooseRoleFor": "What does the {0} screen do?",
"HomeButtonCard": "Open with the controller Home button",
"HomeButtonDescription": "With the app in the tray, hold the Xbox button for 1 second to enter console mode. During a session, hold Start + Select for 1 second to go back to the PC. Xbox (XInput) controllers only.",
+ "CloseSteamCard": "Close Steam when going back to the PC",
+ "CloseSteamDescription": "When you leave Big Picture or go back to the PC, Steam is asked to quit the normal way (its own Exit, never forced). Big Picture is always closed. Steam stays open if a game is running.",
"AutoStartControllerCard": "Enter when a controller connects",
"AutoStartControllerDescription": "With the app in the tray, turning on or plugging in a controller enters console mode. Ignores the first 15 s after opening and 30 s after restoring.",
"HomeButtonShortPressCard": "Short press of the Xbox button",
diff --git a/src/ConsoleMode/Resources/Strings.es-ES.json b/src/ConsoleMode/Resources/Strings.es-ES.json
index fd97117..5cc9299 100644
--- a/src/ConsoleMode/Resources/Strings.es-ES.json
+++ b/src/ConsoleMode/Resources/Strings.es-ES.json
@@ -77,6 +77,8 @@
"ChooseRoleFor": "¿Qué hace la pantalla {0}?",
"HomeButtonCard": "Abrir con el botón Home del mando",
"HomeButtonDescription": "Con la aplicación en la bandeja, mantén el botón Xbox 1 segundo para entrar en modo consola. Durante la sesión, mantén Start + Select 1 segundo para volver al PC. Solo mandos Xbox (XInput).",
+ "CloseSteamCard": "Cerrar Steam al volver al PC",
+ "CloseSteamDescription": "Al salir de Big Picture o volver al PC, se le pide a Steam que se cierre de la forma normal (su propio «Salir», nunca a la fuerza). Big Picture siempre se cierra. Steam sigue abierta si hay un juego en ejecución.",
"AutoStartControllerCard": "Entrar al conectar un mando",
"AutoStartControllerDescription": "Con la aplicación en la bandeja, encender o conectar un mando entra en modo consola. Ignora los primeros 15 s tras abrir y 30 s tras restaurar.",
"HomeButtonShortPressCard": "Pulsación corta del botón Xbox",
diff --git a/src/ConsoleMode/Resources/Strings.pt-BR.json b/src/ConsoleMode/Resources/Strings.pt-BR.json
index dcabf5d..f494586 100644
--- a/src/ConsoleMode/Resources/Strings.pt-BR.json
+++ b/src/ConsoleMode/Resources/Strings.pt-BR.json
@@ -77,6 +77,8 @@
"ChooseRoleFor": "O que a tela {0} faz?",
"HomeButtonCard": "Abrir com o botão Home do controle",
"HomeButtonDescription": "Com o app na bandeja, segure o botão Xbox por 1 segundo para entrar no modo console. Durante a sessão, segure Start + Select por 1 segundo para voltar ao PC. Só controles Xbox (XInput).",
+ "CloseSteamCard": "Fechar a Steam ao voltar ao PC",
+ "CloseSteamDescription": "Ao sair do Big Picture ou voltar ao PC, a Steam é chamada para fechar do jeito normal (o \"Sair\" dela, nunca à força). O Big Picture sempre é fechado. A Steam fica aberta se houver um jogo rodando.",
"AutoStartControllerCard": "Entrar ao conectar um controle",
"AutoStartControllerDescription": "Com o app na bandeja, ligar ou conectar um controle entra no modo console. Ignora os primeiros 15 s após abrir e 30 s após restaurar.",
"HomeButtonShortPressCard": "Toque curto no botão Xbox",
diff --git a/src/ConsoleMode/Services/ConsoleEngine.cs b/src/ConsoleMode/Services/ConsoleEngine.cs
index c360d9d..db89699 100644
--- a/src/ConsoleMode/Services/ConsoleEngine.cs
+++ b/src/ConsoleMode/Services/ConsoleEngine.cs
@@ -42,6 +42,7 @@ public void Start(AppConfig config, MonitorInfo? focusInfo, Func
+ /// Going back to the PC: Big Picture is closed (it would otherwise be left on the desk monitor when the
+ /// screens come back), and Steam is asked to quit its own way, because after Big Picture it is often left
+ /// half-working. Never a kill, never when a game is running, and never a reason to fail the restore.
+ ///
+ private void CloseSteamForRestore()
+ {
+ if (State.FullscreenMode != "bigPicture") return;
+ try
+ {
+ var closed = Launch.CloseBigPicture(State);
+ AppLog.Write($"Steam: Big Picture {(closed ? "fechado" : "ainda aberto")}");
+ var appId = SteamSession.RunningAppId();
+ var decision = SteamShutdown.Decide(State.FullscreenMode, State.CloseSteamOnRestore, SteamSession.IsRunning(), appId);
+ AppLog.Write(SteamShutdown.Describe(decision, appId));
+ if (decision == SteamShutdown.Decision.Shutdown) SteamSession.RequestShutdown();
+ }
+ catch (Exception ex)
+ {
+ AppLog.Write($"Steam: fechar ao voltar: {ex.Message}");
+ }
+ }
+
public void RequestExit() => State.ShouldExit = true;
private void ShowCurtains(IReadOnlyList names)
@@ -372,10 +397,12 @@ private AppConfig ResolveMonitorNames(AppConfig config)
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,
@@ -383,10 +410,15 @@ private AppConfig ResolveMonitorNames(AppConfig config)
MonitorModes = modes,
HdrEnable = config.HdrEnable,
VrrEnable = config.VrrEnable,
+ UiMode = config.UiMode,
TourDone = config.TourDone,
ConfirmedSetup = config.ConfirmedSetup,
CheckUpdates = config.CheckUpdates,
BetaUpdates = config.BetaUpdates,
+ HomeButtonLaunch = config.HomeButtonLaunch,
+ HomeButtonShortPress = config.HomeButtonShortPress,
+ CloseSteamOnRestore = config.CloseSteamOnRestore,
+ AutoStartOnController = config.AutoStartOnController,
SkippedUpdateVersion = config.SkippedUpdateVersion
};
}
diff --git a/src/ConsoleMode/Services/LaunchService.cs b/src/ConsoleMode/Services/LaunchService.cs
index 3eeca58..32f46d7 100644
--- a/src/ConsoleMode/Services/LaunchService.cs
+++ b/src/ConsoleMode/Services/LaunchService.cs
@@ -133,6 +133,34 @@ public bool IsPlayniteActive(ConsoleRuntimeState state)
return false;
}
+ ///
+ /// 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.
+ ///
+ public bool CloseBigPicture(ConsoleRuntimeState state, int timeoutMs = 6000)
+ {
+ if (!IsBigPictureActive(state)) return true;
+ var handles = GetBigPictureHandles();
+ try { ProcessRunner.StartDetached("steam://close/bigpicture"); }
+ catch (Exception ex) { AppLog.Write($"Fechar Big Picture: {ex.Message}"); }
+
+ var deadline = Environment.TickCount64 + timeoutMs;
+ var nudged = false;
+ while (Environment.TickCount64 < deadline)
+ {
+ Thread.Sleep(400);
+ if (!IsBigPictureActive(state)) return true;
+ if (!nudged && Environment.TickCount64 > deadline - timeoutMs / 2)
+ {
+ nudged = true;
+ foreach (var handle in handles.Concat(GetBigPictureHandles()).Distinct())
+ NativeWindows.PostMessage(handle, NativeWindows.WmClose, 0, 0);
+ }
+ }
+ return !IsBigPictureActive(state);
+ }
+
public bool IsBigPictureActive(ConsoleRuntimeState state)
{
if (state.CachedBigPictureHandle != 0)
diff --git a/src/ConsoleMode/Services/LocalizationService.cs b/src/ConsoleMode/Services/LocalizationService.cs
index a4162c0..1bc3df6 100644
--- a/src/ConsoleMode/Services/LocalizationService.cs
+++ b/src/ConsoleMode/Services/LocalizationService.cs
@@ -187,6 +187,8 @@ internal void NotifyAll()
public string HomeButtonCard => LocalizationService.Get(nameof(HomeButtonCard));
public string HomeButtonDescription => LocalizationService.Get(nameof(HomeButtonDescription));
public string HomeButtonShortPressCard => LocalizationService.Get(nameof(HomeButtonShortPressCard));
+ public string CloseSteamCard => LocalizationService.Get(nameof(CloseSteamCard));
+ public string CloseSteamDescription => LocalizationService.Get(nameof(CloseSteamDescription));
public string AutoStartControllerCard => LocalizationService.Get(nameof(AutoStartControllerCard));
public string AutoStartControllerDescription => LocalizationService.Get(nameof(AutoStartControllerDescription));
public string NotifyUpdatesCard => LocalizationService.Get(nameof(NotifyUpdatesCard));
diff --git a/src/ConsoleMode/Services/SteamSession.cs b/src/ConsoleMode/Services/SteamSession.cs
new file mode 100644
index 0000000..88a40cd
--- /dev/null
+++ b/src/ConsoleMode/Services/SteamSession.cs
@@ -0,0 +1,78 @@
+using System.Diagnostics;
+using Microsoft.Win32;
+
+namespace ConsoleMode.Services;
+
+///
+/// Talks to the Steam client about the end of a session: is it running, is a game running, and the
+/// request to quit. Quitting is Steam's own "-shutdown" (what its Exit menu does): it is asked to close,
+/// never killed, so it can save and leave cleanly.
+///
+public static class SteamSession
+{
+ private const string SteamKey = @"Software\Valve\Steam";
+
+ public static bool IsRunning()
+ {
+ var processes = Process.GetProcessesByName("steam");
+ try { return processes.Length > 0; }
+ finally { foreach (var p in processes) p.Dispose(); }
+ }
+
+ /// The id of the game Steam is running, 0 if none, or null if the state is unknown.
+ public static int? RunningAppId()
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(SteamKey);
+ return SteamShutdown.ParseRunningAppId(key?.GetValue("RunningAppID"));
+ }
+ catch (Exception ex)
+ {
+ AppLog.Write($"Steam: RunningAppID: {ex.Message}");
+ return null;
+ }
+ }
+
+ public static string? FindSteamExe()
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(SteamKey);
+ var exe = key?.GetValue("SteamExe") as string;
+ if (!string.IsNullOrWhiteSpace(exe) && File.Exists(exe)) return Path.GetFullPath(exe);
+ var folder = key?.GetValue("SteamPath") as string;
+ if (!string.IsNullOrWhiteSpace(folder))
+ {
+ var candidate = Path.Combine(folder, "steam.exe");
+ if (File.Exists(candidate)) return Path.GetFullPath(candidate);
+ }
+ }
+ catch (Exception ex)
+ {
+ AppLog.Write($"Steam: caminho: {ex.Message}");
+ }
+ return null;
+ }
+
+ /// Asks Steam to exit the normal way. Returns whether the request was sent; it does not wait.
+ public static bool RequestShutdown()
+ {
+ var exe = FindSteamExe();
+ if (exe is null)
+ {
+ AppLog.Write("Steam: não achei o steam.exe para fechar");
+ return false;
+ }
+ try
+ {
+ Process.Start(new ProcessStartInfo { FileName = exe, Arguments = "-shutdown", UseShellExecute = false, CreateNoWindow = true })?.Dispose();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ AppLog.Write($"Steam: pedido de fechar falhou: {ex.Message}");
+ return false;
+ }
+ }
+}
diff --git a/src/ConsoleMode/Services/SteamShutdown.cs b/src/ConsoleMode/Services/SteamShutdown.cs
new file mode 100644
index 0000000..1c6d461
--- /dev/null
+++ b/src/ConsoleMode/Services/SteamShutdown.cs
@@ -0,0 +1,60 @@
+namespace ConsoleMode.Services;
+
+///
+/// The rule for closing Steam when the user goes back to the PC, kept pure so it is tested: Steam is
+/// asked to quit (its own "Exit", never a kill) only when Big Picture was the launcher, the setting is
+/// on, Steam is actually running and no game is running. A running game means the user may be in the
+/// middle of it, so Steam stays.
+///
+public static class SteamShutdown
+{
+ public enum Decision
+ {
+ /// Ask Steam to exit.
+ Shutdown,
+ /// The launcher is not Big Picture: Steam was never ours to close.
+ NotBigPicture,
+ /// Turned off in Settings.
+ SettingOff,
+ /// Steam is not running: nothing to close (and asking would start it).
+ NotRunning,
+ /// A game launched through Steam is running (RunningAppID is not 0).
+ GameRunning,
+ /// The game state could not be read, so Steam must be left open.
+ GameStateUnknown
+ }
+
+ public static Decision Decide(string launcherMode, bool closeSteamSetting, bool steamRunning, int? runningAppId)
+ {
+ if (!string.Equals(launcherMode, "bigPicture", StringComparison.Ordinal)) return Decision.NotBigPicture;
+ if (!closeSteamSetting) return Decision.SettingOff;
+ if (!steamRunning) return Decision.NotRunning;
+ if (runningAppId is null) return Decision.GameStateUnknown;
+ if (runningAppId > 0) return Decision.GameRunning;
+ return Decision.Shutdown;
+ }
+
+ /// The line for the log.
+ public static string Describe(Decision decision, int? runningAppId) => decision switch
+ {
+ Decision.Shutdown => "Steam: pedindo para fechar (sair normal)",
+ Decision.SettingOff => "Steam: mantida aberta (desligado nos ajustes)",
+ Decision.NotRunning => "Steam: não estava aberta",
+ Decision.GameRunning => $"Steam: mantida aberta (jogo em execução, id {runningAppId})",
+ Decision.GameStateUnknown => "Steam: mantida aberta (não foi possível verificar se há jogo em execução)",
+ _ => "Steam: não é o lançador desta sessão"
+ };
+
+ ///
+ /// Steam keeps the running game's id in the registry (HKCU\Software\Valve\Steam\RunningAppID, a DWORD).
+ /// Zero means the registry was read successfully and no game is running; null means the value is
+ /// missing, malformed, out of range, or could not be read, so the state is unknown.
+ ///
+ public static int? ParseRunningAppId(object? registryValue) => registryValue switch
+ {
+ int id when id >= 0 => id,
+ uint id when id <= int.MaxValue => (int)id,
+ string text when int.TryParse(text, out var id) && id >= 0 => id,
+ _ => null
+ };
+}
diff --git a/src/ConsoleMode/ViewModels/MainViewModel.Updates.cs b/src/ConsoleMode/ViewModels/MainViewModel.Updates.cs
index 2eb80dc..2cee39d 100644
--- a/src/ConsoleMode/ViewModels/MainViewModel.Updates.cs
+++ b/src/ConsoleMode/ViewModels/MainViewModel.Updates.cs
@@ -20,6 +20,7 @@ private enum UpdateStatusKind { InstallKind, Searching, Latest, Available, Faile
[ObservableProperty] private bool _homeButtonLaunch = true;
[ObservableProperty] private bool _homeButtonShortPress;
[ObservableProperty] private bool _autoStartOnController;
+ [ObservableProperty] private bool _closeSteamOnRestore = true;
[ObservableProperty] private string _homeButtonHint = "";
[ObservableProperty] private bool _startWithWindows;
[ObservableProperty] private bool _isUpdateOpen;
@@ -92,6 +93,8 @@ partial void OnBetaUpdatesChanged(bool value)
partial void OnAutoStartOnControllerChanged(bool value) => SaveQuietly();
+ partial void OnCloseSteamOnRestoreChanged(bool value) => SaveQuietly();
+
partial void OnHomeButtonShortPressChanged(bool value)
{
if (_applying) return;
diff --git a/src/ConsoleMode/ViewModels/MainViewModel.cs b/src/ConsoleMode/ViewModels/MainViewModel.cs
index b2919d1..6fdbdb0 100644
--- a/src/ConsoleMode/ViewModels/MainViewModel.cs
+++ b/src/ConsoleMode/ViewModels/MainViewModel.cs
@@ -305,6 +305,7 @@ private void Apply(LoadResult data)
HomeButtonLaunch = config.HomeButtonLaunch;
HomeButtonShortPress = config.HomeButtonShortPress;
AutoStartOnController = config.AutoStartOnController;
+ CloseSteamOnRestore = config.CloseSteamOnRestore;
RefreshHomeButtonHint();
SelectedUiMode = UiModeOptions.FirstOrDefault(o => o.Value == config.UiMode) ?? UiModeOptions.FirstOrDefault();
@@ -892,6 +893,7 @@ private AppConfig BuildConfig()
HomeButtonLaunch = HomeButtonLaunch,
HomeButtonShortPress = HomeButtonShortPress,
AutoStartOnController = AutoStartOnController,
+ CloseSteamOnRestore = CloseSteamOnRestore,
UiMode = SelectedUiMode?.Value ?? _loadedConfig.UiMode,
SkippedUpdateVersion = _loadedConfig.SkippedUpdateVersion
};
diff --git a/src/ConsoleMode/Views/SettingsView.xaml b/src/ConsoleMode/Views/SettingsView.xaml
index 97f1a8f..9818fcc 100644
--- a/src/ConsoleMode/Views/SettingsView.xaml
+++ b/src/ConsoleMode/Views/SettingsView.xaml
@@ -88,6 +88,13 @@
+
+
+
+
+
+
diff --git a/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj b/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj
index 5448440..011bcb9 100644
--- a/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj
+++ b/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj
@@ -33,6 +33,7 @@
+
diff --git a/tests/ConsoleMode.Tests/SteamShutdownTests.cs b/tests/ConsoleMode.Tests/SteamShutdownTests.cs
new file mode 100644
index 0000000..a9df60f
--- /dev/null
+++ b/tests/ConsoleMode.Tests/SteamShutdownTests.cs
@@ -0,0 +1,83 @@
+using ConsoleMode.Services;
+using static ConsoleMode.Services.SteamShutdown;
+
+namespace ConsoleMode.Tests;
+
+public class SteamShutdownTests
+{
+ [Fact]
+ public void Steam_is_asked_to_quit_when_big_picture_was_the_launcher_nothing_blocks_it()
+ {
+ Assert.Equal(Decision.Shutdown, Decide("bigPicture", closeSteamSetting: true, steamRunning: true, runningAppId: 0));
+ }
+
+ [Theory]
+ [InlineData("playnite")]
+ [InlineData("xboxMode")]
+ [InlineData("")]
+ [InlineData("BigPicture")] // the mode names are exact
+ public void Steam_is_left_alone_when_it_was_not_the_launcher(string mode) =>
+ Assert.Equal(Decision.NotBigPicture, Decide(mode, true, true, 0));
+
+ [Fact]
+ public void The_setting_turns_it_off()
+ {
+ Assert.Equal(Decision.SettingOff, Decide("bigPicture", closeSteamSetting: false, steamRunning: true, runningAppId: 0));
+ }
+
+ [Fact]
+ public void A_steam_that_is_not_running_is_not_asked_to_quit()
+ {
+ // Asking would start it.
+ Assert.Equal(Decision.NotRunning, Decide("bigPicture", true, steamRunning: false, runningAppId: 0));
+ }
+
+ [Theory]
+ [InlineData(1245620)]
+ [InlineData(570)]
+ [InlineData(1)]
+ public void A_running_game_keeps_steam_open(int appId) =>
+ Assert.Equal(Decision.GameRunning, Decide("bigPicture", true, true, appId));
+
+ [Fact]
+ public void Unknown_game_state_keeps_steam_open() =>
+ Assert.Equal(Decision.GameStateUnknown, Decide("bigPicture", true, true, runningAppId: null));
+
+ [Fact]
+ public void Missing_running_app_id_remains_unknown_through_the_shutdown_decision_and_log()
+ {
+ var appId = ParseRunningAppId(null);
+ var decision = Decide("bigPicture", closeSteamSetting: true, steamRunning: true, appId);
+
+ Assert.Equal(Decision.GameStateUnknown, decision);
+ Assert.Contains("mantida aberta", Describe(decision, appId));
+ Assert.Contains("não foi possível verificar", Describe(decision, appId));
+ }
+
+ [Fact]
+ public void The_setting_wins_over_everything_else()
+ {
+ Assert.Equal(Decision.SettingOff, Decide("bigPicture", false, false, 570));
+ }
+
+ [Theory]
+ [InlineData(1245620, 1245620)]
+ [InlineData(0, 0)]
+ [InlineData(-5, null)]
+ [InlineData(570u, 570)]
+ [InlineData(4000000000u, null)] // does not fit an int: state unknown
+ [InlineData("730", 730)]
+ [InlineData("abc", null)]
+ [InlineData("", null)]
+ [InlineData(null, null)]
+ public void The_running_game_id_is_read_from_whatever_the_registry_holds(object? value, int? expected) =>
+ Assert.Equal(expected, ParseRunningAppId(value));
+
+ [Fact]
+ public void Every_decision_has_a_log_line_and_the_game_one_names_the_game()
+ {
+ foreach (var decision in Enum.GetValues())
+ Assert.False(string.IsNullOrWhiteSpace(Describe(decision, 570)));
+ Assert.Contains("570", Describe(Decision.GameRunning, 570));
+ }
+}