From 7952fe98a235efd6ead0882062425e673ce29318 Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 15:33:57 -0300 Subject: [PATCH 1/5] chore(release): 1.6.0-alpha.3 (#118) Turns [Unreleased] into [1.6.0-alpha.3] in both changelogs, adds the alpha heads-up, points the README pair at the new alpha and bumps the version in ConsoleMode.csproj. The tag is not created here: it is pushed by the owner after this and the PRs it sits on are merged. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 5 ++++- CHANGELOG.md | 5 ++++- README.md | 2 +- README.pt-BR.md | 2 +- src/ConsoleMode/ConsoleMode.csproj | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index c2d1d24..b46eda8 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -2,7 +2,7 @@ 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] +## [1.6.0-alpha.3] ### What's new - Session menu (the controller shortcut, Select + Y in the suggestion) redesigned like Steam's overlay (Shift + Tab): it covers the whole screen, the game dims and blurs behind it, a side panel gathers the options (volume with a gauge, resolution, audio, FPS, HDR) and the **open windows** are always visible in the middle, an in-menu Alt + Tab: the D-pad picks, A brings the window to the front, X closes it and B goes back to the game (switching windows does not end the session). The frosted glass only shows when Windows' transparency effects are on (otherwise the panel is dark and solid, and it changes live if you flip the setting), and in high contrast it uses the system colours. It has a focus that grows, sounds and animations (the panel slides in, the rows follow one after another and closing fades it out). In this alpha it also opens **outside a session**, as a preview with a badge on top, so it can be tried without turning on console mode (in it the FPS limit is hidden and "Back to the PC" becomes "Close menu"). (#120) - Redesigned Console interface, made to feel like a console: a top bar with three tabs (Home, Session and System) you switch with LB/RB (L1/R1 on PlayStation) with a slide animation, screen tiles that show what will happen to each monitor, quick settings with icons and a focus that grows as you move over it. Home brings the Play now banner and a summary of the current setup (game screen, audio, launcher and HDR/VRR), each opening its choices. (#117) @@ -26,6 +26,9 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor - The package embeds only rtss-cli; leftover MultiMonitorTool/SoundVolumeView files no longer end up in the executable. (#110) - The code no longer mentions MultiMonitorTool or SoundVolumeView, and the cleanup of their old files in the `tools` folder is gone: anyone updating straight from 1.5 keeps those two files until they delete them by hand. (#119) +### Heads-up: alpha release +- Not verified on many monitor, TV and controller setups yet. If something fails, go back to 1.5.1 and tell us with the feedback button (attach `consolemode.log`). People on the stable release don't get this version automatically. + ## [1.6.0-alpha.2] ### Changes - 1.6 is all the project's own code: the option to go back to MultiMonitorTool / SoundVolumeView is gone too, and their old files in the `tools` folder are deleted when the app opens. (#91) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03072bb..01b7a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ 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] +## [1.6.0-alpha.3] ### Novidades - Menu da sessão (o atalho do controle, Select + Y na sugestão) redesenhado como o overlay do Steam (Shift+Tab): cobre a tela inteira, o jogo escurece e desfoca por trás, um painel lateral reúne as opções (volume com medidor, resolução, áudio, FPS, HDR) e no meio ficam sempre visíveis as **janelas abertas**, um ALT+TAB interno: o direcional escolhe, A traz a janela para a frente, X a fecha e B volta ao jogo (trocar de janela não encerra a sessão). O vidro fosco só aparece se os efeitos de transparência do Windows estiverem ligados (senão o painel fica escuro e sólido, e muda ao vivo se você trocar a configuração), e em alto contraste usa as cores do sistema. Tem foco que cresce, sons e animações (o painel entra deslizando, as linhas vêm em cascata e ao fechar ele some suavemente). Nesta alpha ele abre também **fora da sessão**, como prévia com um selo no topo, para testar sem ligar o modo console (nela o limite de FPS não aparece e "Voltar ao PC" vira "Fechar menu"). (#120) - Interface Console redesenhada, com cara de console: barra no topo com três abas (Início, Sessão e Sistema) que você troca com LB/RB (L1/R1 no PlayStation) com uma animação de deslize, blocos das telas que mostram o que vai acontecer com cada monitor, ajustes rápidos com ícones e foco que cresce ao passar. Início traz o banner com Jogar agora e um resumo do setup atual (tela de jogo, áudio, iniciador e HDR/VRR), que abre cada escolha. (#117) @@ -26,6 +26,9 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH - O pacote embute só o rtss-cli; sobras de MultiMonitorTool/SoundVolumeView não entram mais no executável. (#110) - O código não cita mais o MultiMonitorTool nem o SoundVolumeView, e a limpeza dos arquivos antigos deles na pasta `tools` saiu: quem atualiza direto da 1.5 mantém esses dois arquivos até apagá-los à mão. (#119) +### Atenção: versão alpha +- Ainda não validada em muitos setups de monitores, TVs e controles. Se algo falhar, volte para a 1.5.1 e conte pelo botão de feedback (anexe o `consolemode.log`). Quem usa a versão estável não recebe esta versão automaticamente. + ## [1.6.0-alpha.2] ### Mudanças - A 1.6 é só código do projeto: saiu também a opção de voltar ao MultiMonitorTool / SoundVolumeView, e os arquivos antigos deles na pasta `tools` são apagados ao abrir o app. (#91) diff --git a/README.md b/README.md index adc3967..1ab7131 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Get **[1.5.1](https://github.com/lippdev/consolemode/releases/tag/v1.5.1)**, the - **`ConsoleMode-Setup-x64.exe`** (recommended): per-user install, no admin, updates itself. - **`ConsoleMode-Portable-x64.exe`**: a single exe that keeps its data next to it. -Want to help test what comes next? **[1.6.0-alpha.2](https://github.com/lippdev/consolemode/releases/tag/v1.6.0-alpha.2)** is an early alpha of 1.6 that no longer bundles any third-party tool for displays and audio. It is not verified on many setups yet: keep 1.5.1 if you need it to just work, or turn on Settings → "Receive test versions" in 1.5.1 to get it from the app. +Want to help test what comes next? **[1.6.0-alpha.3](https://github.com/lippdev/consolemode/releases/tag/v1.6.0-alpha.3)** is an early alpha of 1.6 that no longer bundles any third-party tool for displays and audio. It is not verified on many setups yet: keep 1.5.1 if you need it to just work, or turn on Settings → "Receive test versions" in 1.5.1 to get it from the app. On 1.4.0? The app offers the update. If it fails with a timeout, download the installer once; your settings are kept. diff --git a/README.pt-BR.md b/README.pt-BR.md index 52bc716..49c3f9e 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -17,7 +17,7 @@ Baixe a **[1.5.1](https://github.com/lippdev/consolemode/releases/tag/v1.5.1)**, - **`ConsoleMode-Setup-x64.exe`** (recomendado): instala por usuário, sem admin, e se atualiza sozinho. - **`ConsoleMode-Portable-x64.exe`**: um único exe que guarda os dados ao lado dele. -Quer ajudar a testar o que vem aí? A **[1.6.0-alpha.2](https://github.com/lippdev/consolemode/releases/tag/v1.6.0-alpha.2)** é uma alpha da 1.6 que não embute mais nenhuma ferramenta de terceiros para telas e áudio. Ela ainda não foi validada em muitos setups: fique na 1.5.1 se precisa que funcione sem surpresas, ou ligue Ajustes → "Receber versões de teste" na 1.5.1 para recebê-la pelo app. +Quer ajudar a testar o que vem aí? A **[1.6.0-alpha.3](https://github.com/lippdev/consolemode/releases/tag/v1.6.0-alpha.3)** é uma alpha da 1.6 que não embute mais nenhuma ferramenta de terceiros para telas e áudio. Ela ainda não foi validada em muitos setups: fique na 1.5.1 se precisa que funcione sem surpresas, ou ligue Ajustes → "Receber versões de teste" na 1.5.1 para recebê-la pelo app. Está na 1.4.0? O app oferece a atualização. Se ela falhar por tempo esgotado, baixe o instalador uma vez; as configurações são mantidas. diff --git a/src/ConsoleMode/ConsoleMode.csproj b/src/ConsoleMode/ConsoleMode.csproj index c6ce683..70daee4 100644 --- a/src/ConsoleMode/ConsoleMode.csproj +++ b/src/ConsoleMode/ConsoleMode.csproj @@ -17,10 +17,10 @@ true latest x64 - 1.6.0-alpha.2 + 1.6.0-alpha.3 1.6.0.0 1.6.0.0 - 1.6.0-alpha.2 + 1.6.0-alpha.3 Console Mode Console Mode lippdev From 9635aee9366fa1a3e9ae7f9d4efd612d9a67847b Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 17:41:58 -0300 Subject: [PATCH 2/5] feat: pinned ControlFS file explorer panel in the session menu Finds the installed ControlFS (registry, LocalAppData, Program Files), opens it with its own --start flag (never kills it) and falls back to its release page when it is not installed. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 1 + CHANGELOG.md | 1 + docs/TESTING.md | 13 +++ src/ConsoleMode/Resources/Strings.en-US.json | 4 + src/ConsoleMode/Resources/Strings.es-ES.json | 4 + src/ConsoleMode/Resources/Strings.pt-BR.json | 4 + src/ConsoleMode/Services/ControlFsLocator.cs | 38 +++++++++ src/ConsoleMode/Services/ControlFsService.cs | 81 +++++++++++++++++++ .../Services/LocalizationService.cs | 3 + src/ConsoleMode/SessionMenuWindow.xaml | 26 +++++- .../ViewModels/MainViewModel.SessionMenu.cs | 22 +++++ .../ConsoleMode.Tests.csproj | 1 + .../ControlFsLocatorTests.cs | 32 ++++++++ 13 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 src/ConsoleMode/Services/ControlFsLocator.cs create mode 100644 src/ConsoleMode/Services/ControlFsService.cs create mode 100644 tests/ConsoleMode.Tests/ControlFsLocatorTests.cs diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 4f0e2dd..0baf24d 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,6 +4,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] ### What's new +- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. - Session menu (the controller shortcut, Select + Y in the suggestion) redesigned like Steam's overlay (Shift + Tab): it covers the whole screen, the game dims and blurs behind it, a side panel gathers the options (volume with a gauge, resolution, audio, FPS, HDR) and the **open windows** are always visible in the middle, an in-menu Alt + Tab: the D-pad picks, A brings the window to the front, X closes it and B goes back to the game (switching windows does not end the session). The frosted glass only shows when Windows' transparency effects are on (otherwise the panel is dark and solid, and it changes live if you flip the setting), and in high contrast it uses the system colours. It has a focus that grows, sounds and animations (the panel slides in, the rows follow one after another and closing fades it out). In this alpha it also opens **outside a session**, as a preview with a badge on top, so it can be tried without turning on console mode (in it the FPS limit is hidden and "Back to the PC" becomes "Close menu"). (#120) - Redesigned Console interface, made to feel like a console: a top bar with three tabs (Home, Session and System) you switch with LB/RB (L1/R1 on PlayStation) with a slide animation, screen tiles that show what will happen to each monitor, quick settings with icons and a focus that grows as you move over it. Home brings the Play now banner and a summary of the current setup (game screen, audio, launcher and HDR/VRR), each opening its choices. (#117) - Console background: by default, a collage of the covers of your installed Steam games under a dark gradient (the app only reads images Steam already downloaded; nothing is bundled). You can switch to just the gradient or to a picture of your own, in System → Background. (#117) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e64c9..cd16673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] ### Novidades +- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. - Menu da sessão (o atalho do controle, Select + Y na sugestão) redesenhado como o overlay do Steam (Shift+Tab): cobre a tela inteira, o jogo escurece e desfoca por trás, um painel lateral reúne as opções (volume com medidor, resolução, áudio, FPS, HDR) e no meio ficam sempre visíveis as **janelas abertas**, um ALT+TAB interno: o direcional escolhe, A traz a janela para a frente, X a fecha e B volta ao jogo (trocar de janela não encerra a sessão). O vidro fosco só aparece se os efeitos de transparência do Windows estiverem ligados (senão o painel fica escuro e sólido, e muda ao vivo se você trocar a configuração), e em alto contraste usa as cores do sistema. Tem foco que cresce, sons e animações (o painel entra deslizando, as linhas vêm em cascata e ao fechar ele some suavemente). Nesta alpha ele abre também **fora da sessão**, como prévia com um selo no topo, para testar sem ligar o modo console (nela o limite de FPS não aparece e "Voltar ao PC" vira "Fechar menu"). (#120) - Interface Console redesenhada, com cara de console: barra no topo com três abas (Início, Sessão e Sistema) que você troca com LB/RB (L1/R1 no PlayStation) com uma animação de deslize, blocos das telas que mostram o que vai acontecer com cada monitor, ajustes rápidos com ícones e foco que cresce ao passar. Início traz o banner com Jogar agora e um resumo do setup atual (tela de jogo, áudio, iniciador e HDR/VRR), que abre cada escolha. (#117) - Plano de fundo do modo Console: por padrão, uma colagem com as capas dos seus jogos instalados na Steam sob um gradiente escuro (o app só lê as imagens que a Steam já baixou; nada é embutido). Dá para trocar por só o gradiente ou por uma imagem sua, em Sistema → Plano de fundo. (#117) diff --git a/docs/TESTING.md b/docs/TESTING.md index d020c11..a734b89 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -149,6 +149,19 @@ Pré-condições: sessão ativa com Big Picture (ou jogo borderless) na tela; co - [ ] Jogo em **tela cheia exclusiva**: o menu não aparece por cima (limitação documentada); Select + Y não quebra nada. Resultado: ______ - [ ] `start consolemode://menu` no cmd abre o menu; fora da sessão, não faz nada. Resultado: ______ +## 1d. Explorador de arquivos (ControlFS) no menu da sessão + +Pré-condição: ControlFS instalado (https://github.com/nextestudios/ControlFS). + +- [ ] Select + Y: no topo da lateral esquerda, fora da lista que rola, há o painel "Explorador de arquivos (ControlFS)"; o restante das opções rola por baixo dele. Resultado: ______ +- [ ] Direcional para cima a partir de "Voltar ao jogo" chega nele; A abre o ControlFS em tela cheia na TV, por cima do jogo/Big Picture, e o menu fecha. Resultado: ______ +- [ ] O ControlFS responde ao controle logo de cara (o foco do controle fica com ele, sem o Console Mode reagir aos botões). Resultado: ______ +- [ ] Com o ControlFS já aberto: A só o traz para a frente (não abre uma segunda instância). Resultado: ______ +- [ ] A sessão **não** termina nem restaura a mesa enquanto o ControlFS está na frente; ao sair dele (Menu do ControlFS → Sair) volta-se ao jogo. Resultado: ______ +- [ ] Select + Y com o ControlFS na frente abre o menu por cima dele (saída de emergência). Resultado: ______ +- [ ] Sem o ControlFS instalado: a linha diz "Não instalado · aperte para baixar" e A abre a página de releases no navegador. Resultado: ______ +- [ ] Fora da sessão (prévia do menu) o painel funciona igual. Resultado: ______ + ## 2. Links `consolemode://` - [ ] `start consolemode://start` no `cmd` com o app **fechado**: abre e entra no modo console. Resultado: ______ diff --git a/src/ConsoleMode/Resources/Strings.en-US.json b/src/ConsoleMode/Resources/Strings.en-US.json index 860fdaa..2c00b67 100644 --- a/src/ConsoleMode/Resources/Strings.en-US.json +++ b/src/ConsoleMode/Resources/Strings.en-US.json @@ -87,6 +87,10 @@ "ConsoleSettingsHint": "Press A on a row to change its value. Everything is saved right away.", "BackToPc": "Back to the PC", "CloseMenu": "Close menu", + "ControlFsCard": "File explorer (ControlFS)", + "ControlFsOpen": "Browse your files with the controller", + "ControlFsMissing": "Not installed · press to get it", + "ControlFsMissingNote": "ControlFS is not installed. Opening its download page in the browser.", "SessionWindowsCard": "Windows", "SessionWindowsCount": "{0} open", "SwitcherEmpty": "No open windows", diff --git a/src/ConsoleMode/Resources/Strings.es-ES.json b/src/ConsoleMode/Resources/Strings.es-ES.json index 507d41f..a9a14c6 100644 --- a/src/ConsoleMode/Resources/Strings.es-ES.json +++ b/src/ConsoleMode/Resources/Strings.es-ES.json @@ -87,6 +87,10 @@ "ConsoleSettingsHint": "Pulsa A en una fila para cambiar su valor. Todo se guarda al momento.", "BackToPc": "Volver al PC", "CloseMenu": "Cerrar menú", + "ControlFsCard": "Explorador de archivos (ControlFS)", + "ControlFsOpen": "Explora tus archivos con el mando", + "ControlFsMissing": "No instalado · pulsa para descargarlo", + "ControlFsMissingNote": "ControlFS no está instalado. Abriendo su página de descarga en el navegador.", "SessionWindowsCard": "Ventanas", "SessionWindowsCount": "{0} abiertas", "SwitcherEmpty": "No hay ventanas abiertas", diff --git a/src/ConsoleMode/Resources/Strings.pt-BR.json b/src/ConsoleMode/Resources/Strings.pt-BR.json index c7fe898..ff3bd57 100644 --- a/src/ConsoleMode/Resources/Strings.pt-BR.json +++ b/src/ConsoleMode/Resources/Strings.pt-BR.json @@ -87,6 +87,10 @@ "ConsoleSettingsHint": "Aperte A numa linha para trocar o valor. Tudo é salvo na hora.", "BackToPc": "Voltar ao PC", "CloseMenu": "Fechar menu", + "ControlFsCard": "Explorador de arquivos (ControlFS)", + "ControlFsOpen": "Navegue pelos seus arquivos com o controle", + "ControlFsMissing": "Não instalado · aperte para baixar", + "ControlFsMissingNote": "O ControlFS não está instalado. Abrindo a página de download no navegador.", "SessionWindowsCard": "Janelas", "SessionWindowsCount": "{0} abertas", "SwitcherEmpty": "Nenhuma janela aberta", diff --git a/src/ConsoleMode/Services/ControlFsLocator.cs b/src/ConsoleMode/Services/ControlFsLocator.cs new file mode 100644 index 0000000..cf7c781 --- /dev/null +++ b/src/ConsoleMode/Services/ControlFsLocator.cs @@ -0,0 +1,38 @@ +namespace ConsoleMode.Services; + +/// +/// Where ControlFS (the controller-first file explorer from the same team) is installed. Pure: the registry and +/// the disk are passed in, so the order of preference is testable. +/// +public static class ControlFsLocator +{ + public const string ExeName = "ControlFS.exe"; + public const string ReleasesUrl = "https://github.com/nextestudios/ControlFS/releases/latest"; + + /// Folders to look in, most reliable first, without blanks or repeats. + public static IReadOnlyList Folders(string? registryInstallLocation, string? localAppData, string? programFiles) + { + var folders = new List(); + void Add(string? path) + { + if (string.IsNullOrWhiteSpace(path)) return; + var clean = path.Trim().Trim('"').TrimEnd('\\', '/'); + if (clean.Length > 0 && !folders.Contains(clean, StringComparer.OrdinalIgnoreCase)) folders.Add(clean); + } + Add(registryInstallLocation); + if (!string.IsNullOrWhiteSpace(localAppData)) Add(Path.Combine(localAppData, "Programs", "ControlFS")); + if (!string.IsNullOrWhiteSpace(programFiles)) Add(Path.Combine(programFiles, "ControlFS")); + return folders; + } + + /// The first folder that really has ControlFS.exe, or null (not installed). + public static string? Resolve(IEnumerable folders, Func fileExists) + { + foreach (var folder in folders) + { + var exe = Path.Combine(folder, ExeName); + if (fileExists(exe)) return exe; + } + return null; + } +} diff --git a/src/ConsoleMode/Services/ControlFsService.cs b/src/ConsoleMode/Services/ControlFsService.cs new file mode 100644 index 0000000..63caa7e --- /dev/null +++ b/src/ConsoleMode/Services/ControlFsService.cs @@ -0,0 +1,81 @@ +using System.Diagnostics; +using Microsoft.Win32; + +namespace ConsoleMode.Services; + +/// +/// Opens ControlFS from the session menu. ControlFS is a separate app (installed on its own): it is found the way +/// Windows lists it, started with its own --start flag (opens it, or brings the open one to the front; it +/// runs one instance per user) and never killed from here. +/// +public static class ControlFsService +{ + public static string? FindExe() + { + try + { + var folders = ControlFsLocator.Folders( + ReadInstallLocation(), + Environment.GetEnvironmentVariable("LOCALAPPDATA"), + Environment.GetEnvironmentVariable("ProgramFiles")); + return ControlFsLocator.Resolve(folders, File.Exists); + } + catch (Exception ex) + { + AppLog.Write($"ControlFS: procurar: {ex.Message}"); + return null; + } + } + + /// Starts (or brings to the front) ControlFS. False when it isn't installed or didn't start. + public static bool Open() + { + var exe = FindExe(); + if (exe is null) return false; + try + { + var psi = new ProcessStartInfo { FileName = exe, UseShellExecute = false, WorkingDirectory = Path.GetDirectoryName(exe)! }; + psi.ArgumentList.Add("--start"); + Process.Start(psi)?.Dispose(); + AppLog.Write("ControlFS: aberto"); + return true; + } + catch (Exception ex) + { + AppLog.Write($"ControlFS: abrir: {ex.Message}"); + return false; + } + } + + /// Not installed: the release page opens in the browser. + public static void OpenDownloadPage() + { + try { Process.Start(new ProcessStartInfo(ControlFsLocator.ReleasesUrl) { UseShellExecute = true })?.Dispose(); } + catch (Exception ex) { AppLog.Write($"ControlFS: página de download: {ex.Message}"); } + } + + private static string? ReadInstallLocation() + { + const string uninstall = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + foreach (var (hive, view) in new[] + { (RegistryHive.CurrentUser, RegistryView.Default), (RegistryHive.LocalMachine, RegistryView.Registry64), (RegistryHive.LocalMachine, RegistryView.Registry32) }) + { + try + { + using var root = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(uninstall); + if (root is null) continue; + foreach (var name in root.GetSubKeyNames()) + { + using var key = root.OpenSubKey(name); + if (key?.GetValue("DisplayName") is string display + && display.Equals("ControlFS", StringComparison.OrdinalIgnoreCase) + && key.GetValue("InstallLocation") is string location + && !string.IsNullOrWhiteSpace(location)) + return location; + } + } + catch { /* this hive/view isn't readable: try the next */ } + } + return null; + } +} diff --git a/src/ConsoleMode/Services/LocalizationService.cs b/src/ConsoleMode/Services/LocalizationService.cs index fd82083..ad2cded 100644 --- a/src/ConsoleMode/Services/LocalizationService.cs +++ b/src/ConsoleMode/Services/LocalizationService.cs @@ -189,6 +189,9 @@ internal void NotifyAll() public string SoundsCard => LocalizationService.Get(nameof(SoundsCard)); public string SessionPreviewBadge => LocalizationService.Get(nameof(SessionPreviewBadge)); public string CloseMenu => LocalizationService.Get(nameof(CloseMenu)); + public string ControlFsCard => LocalizationService.Get(nameof(ControlFsCard)); + public string ControlFsOpen => LocalizationService.Get(nameof(ControlFsOpen)); + public string ControlFsMissing => LocalizationService.Get(nameof(ControlFsMissing)); public string SessionWindowsCard => LocalizationService.Get(nameof(SessionWindowsCard)); public string SwitcherEmpty => LocalizationService.Get(nameof(SwitcherEmpty)); public string SwitcherOpen => LocalizationService.Get(nameof(SwitcherOpen)); diff --git a/src/ConsoleMode/SessionMenuWindow.xaml b/src/ConsoleMode/SessionMenuWindow.xaml index 7c296a3..2029421 100644 --- a/src/ConsoleMode/SessionMenuWindow.xaml +++ b/src/ConsoleMode/SessionMenuWindow.xaml @@ -127,7 +127,30 @@ - + + + + + + + + diff --git a/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs b/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs index 072fa96..2d2bcf4 100644 --- a/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs +++ b/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs @@ -84,6 +84,7 @@ public void ToggleSessionMenu() _sessionMenu.Closed += (_, _) => { _sessionMenu = null; IsSessionMenuOpen = false; StopSessionClock(); }; IsSessionMenuOpen = true; IsSessionPickerOpen = false; + ControlFsInstalled = ControlFsService.FindExe() is not null; RefreshSessionHeader(); StartSessionClock(); _sessionMenu.Activate(); @@ -310,6 +311,27 @@ private async Task RunSessionActionAsync(Action action) finally { _busy = false; IsSessionMenuBusy = false; } } + /// Whether ControlFS is installed; decides what the pinned row says. Read each time the menu opens. + [ObservableProperty] private bool _controlFsInstalled; + + public string ControlFsRowText => ControlFsInstalled ? Texts.ControlFsOpen : Texts.ControlFsMissing; + partial void OnControlFsInstalledChanged(bool value) => OnPropertyChanged(nameof(ControlFsRowText)); + + /// + /// The pinned "File explorer (ControlFS)" row: the menu closes and ControlFS (a separate app, controller-first) + /// opens or comes to the front. Without it installed, its download page opens instead. Outside a session it + /// works the same, so it can be tried in the preview. + /// + [RelayCommand] + private void OpenControlFs() + { + var installed = ControlFsService.FindExe() is not null; + CloseSessionMenu(); + if (installed && ControlFsService.Open()) return; + AppLog.Write("ControlFS: não instalado, abrindo a página de download"); + ControlFsService.OpenDownloadPage(); + } + [RelayCommand] private async Task RestoreFromMenuAsync() { diff --git a/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj b/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj index 3008210..7cd58d0 100644 --- a/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj +++ b/tests/ConsoleMode.Tests/ConsoleMode.Tests.csproj @@ -37,6 +37,7 @@ + diff --git a/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs new file mode 100644 index 0000000..0873b61 --- /dev/null +++ b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs @@ -0,0 +1,32 @@ +using ConsoleMode.Services; + +namespace ConsoleMode.Tests; + +public class ControlFsLocatorTests +{ + [Fact] + public void The_registry_folder_comes_first_then_the_usual_ones_without_repeats() + { + var folders = ControlFsLocator.Folders(@"C:\Users\ana\AppData\Local\Programs\ControlFS\", @"C:\Users\ana\AppData\Local", @"C:\Program Files"); + Assert.Equal( + [@"C:\Users\ana\AppData\Local\Programs\ControlFS", Path.Combine(@"C:\Program Files", "ControlFS")], + folders); + } + + [Fact] + public void Blank_and_quoted_inputs_are_cleaned_or_dropped() + { + var folders = ControlFsLocator.Folders(" ", null, null); + Assert.Empty(folders); + Assert.Equal([@"D:\Apps\ControlFS"], ControlFsLocator.Folders("\"D:\\Apps\\ControlFS\\\"", null, null)); + } + + [Fact] + public void Resolve_returns_the_first_folder_that_has_the_exe_and_null_when_none_does() + { + string[] folders = [@"C:\a", @"C:\b", @"C:\c"]; + var hit = ControlFsLocator.Resolve(folders, p => p == Path.Combine(@"C:\b", "ControlFS.exe") || p == Path.Combine(@"C:\c", "ControlFS.exe")); + Assert.Equal(Path.Combine(@"C:\b", "ControlFS.exe"), hit); + Assert.Null(ControlFsLocator.Resolve(folders, _ => false)); + } +} From 6bf455fad81d4f3b8b712f18324a76ddc1ba7e60 Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 17:46:09 -0300 Subject: [PATCH 3/5] feat: ControlFS always opens full screen on the session screen It has no start-up flag for it, so the window is found, moved to the session screen and F11 is sent when it does not cover the screen exactly (maximized as fallback). An open window is only brought forward: starting it again left a second copy running. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 2 +- CHANGELOG.md | 2 +- docs/TESTING.md | 2 + src/ConsoleMode/Native/NativeWindows.cs | 15 ++++ src/ConsoleMode/Services/ControlFsLocator.cs | 9 +++ src/ConsoleMode/Services/ControlFsService.cs | 73 +++++++++++++++++++ .../ViewModels/MainViewModel.SessionMenu.cs | 7 +- .../ControlFsLocatorTests.cs | 10 +++ 8 files changed, 117 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 0baf24d..7de9708 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,7 +4,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] ### What's new -- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. +- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top, always full screen on the session screen (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. - Session menu (the controller shortcut, Select + Y in the suggestion) redesigned like Steam's overlay (Shift + Tab): it covers the whole screen, the game dims and blurs behind it, a side panel gathers the options (volume with a gauge, resolution, audio, FPS, HDR) and the **open windows** are always visible in the middle, an in-menu Alt + Tab: the D-pad picks, A brings the window to the front, X closes it and B goes back to the game (switching windows does not end the session). The frosted glass only shows when Windows' transparency effects are on (otherwise the panel is dark and solid, and it changes live if you flip the setting), and in high contrast it uses the system colours. It has a focus that grows, sounds and animations (the panel slides in, the rows follow one after another and closing fades it out). In this alpha it also opens **outside a session**, as a preview with a badge on top, so it can be tried without turning on console mode (in it the FPS limit is hidden and "Back to the PC" becomes "Close menu"). (#120) - Redesigned Console interface, made to feel like a console: a top bar with three tabs (Home, Session and System) you switch with LB/RB (L1/R1 on PlayStation) with a slide animation, screen tiles that show what will happen to each monitor, quick settings with icons and a focus that grows as you move over it. Home brings the Play now banner and a summary of the current setup (game screen, audio, launcher and HDR/VRR), each opening its choices. (#117) - Console background: by default, a collage of the covers of your installed Steam games under a dark gradient (the app only reads images Steam already downloaded; nothing is bundled). You can switch to just the gradient or to a picture of your own, in System → Background. (#117) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd16673..b9dbce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] ### Novidades -- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. +- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima, sempre em tela cheia na tela da sessão (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. - Menu da sessão (o atalho do controle, Select + Y na sugestão) redesenhado como o overlay do Steam (Shift+Tab): cobre a tela inteira, o jogo escurece e desfoca por trás, um painel lateral reúne as opções (volume com medidor, resolução, áudio, FPS, HDR) e no meio ficam sempre visíveis as **janelas abertas**, um ALT+TAB interno: o direcional escolhe, A traz a janela para a frente, X a fecha e B volta ao jogo (trocar de janela não encerra a sessão). O vidro fosco só aparece se os efeitos de transparência do Windows estiverem ligados (senão o painel fica escuro e sólido, e muda ao vivo se você trocar a configuração), e em alto contraste usa as cores do sistema. Tem foco que cresce, sons e animações (o painel entra deslizando, as linhas vêm em cascata e ao fechar ele some suavemente). Nesta alpha ele abre também **fora da sessão**, como prévia com um selo no topo, para testar sem ligar o modo console (nela o limite de FPS não aparece e "Voltar ao PC" vira "Fechar menu"). (#120) - Interface Console redesenhada, com cara de console: barra no topo com três abas (Início, Sessão e Sistema) que você troca com LB/RB (L1/R1 no PlayStation) com uma animação de deslize, blocos das telas que mostram o que vai acontecer com cada monitor, ajustes rápidos com ícones e foco que cresce ao passar. Início traz o banner com Jogar agora e um resumo do setup atual (tela de jogo, áudio, iniciador e HDR/VRR), que abre cada escolha. (#117) - Plano de fundo do modo Console: por padrão, uma colagem com as capas dos seus jogos instalados na Steam sob um gradiente escuro (o app só lê as imagens que a Steam já baixou; nada é embutido). Dá para trocar por só o gradiente ou por uma imagem sua, em Sistema → Plano de fundo. (#117) diff --git a/docs/TESTING.md b/docs/TESTING.md index a734b89..eb14123 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -155,6 +155,8 @@ Pré-condição: ControlFS instalado (https://github.com/nextestudios/ControlFS) - [ ] Select + Y: no topo da lateral esquerda, fora da lista que rola, há o painel "Explorador de arquivos (ControlFS)"; o restante das opções rola por baixo dele. Resultado: ______ - [ ] Direcional para cima a partir de "Voltar ao jogo" chega nele; A abre o ControlFS em tela cheia na TV, por cima do jogo/Big Picture, e o menu fecha. Resultado: ______ +- [ ] O ControlFS abre **sempre em tela cheia** na tela da sessão (a TV), cobrindo-a por inteiro, mesmo que ele estivesse em janela ou maximizado; se já estava em tela cheia, nada muda. Resultado: ______ +- [ ] Abrir de novo com o ControlFS já aberto só o traz para a frente (continua um processo só no Gerenciador de Tarefas). Resultado: ______ - [ ] O ControlFS responde ao controle logo de cara (o foco do controle fica com ele, sem o Console Mode reagir aos botões). Resultado: ______ - [ ] Com o ControlFS já aberto: A só o traz para a frente (não abre uma segunda instância). Resultado: ______ - [ ] A sessão **não** termina nem restaura a mesa enquanto o ControlFS está na frente; ao sair dele (Menu do ControlFS → Sair) volta-se ao jogo. Resultado: ______ diff --git a/src/ConsoleMode/Native/NativeWindows.cs b/src/ConsoleMode/Native/NativeWindows.cs index 472b14c..47c04bd 100644 --- a/src/ConsoleMode/Native/NativeWindows.cs +++ b/src/ConsoleMode/Native/NativeWindows.cs @@ -231,6 +231,21 @@ public static WindowMatch[] GetAllVisibleWindows() return [.. list]; } + /// A plain F11 to the window in front (ControlFS uses it for full screen). + public static void SendF11() + { + keybd_event(VK_F11, 0, 0, UIntPtr.Zero); + keybd_event(VK_F11, 0, KEYEVENTF_KEYUP, UIntPtr.Zero); + } + + public static bool TryGetWindowRect(nint hWnd, out int left, out int top, out int width, out int height) + { + left = top = width = height = 0; + if (hWnd == 0 || !GetWindowRect(hWnd, out var r)) return false; + left = r.Left; top = r.Top; width = r.Right - r.Left; height = r.Bottom - r.Top; + return true; + } + public static void SendWinF11() { keybd_event(VK_LWIN, 0, 0, UIntPtr.Zero); diff --git a/src/ConsoleMode/Services/ControlFsLocator.cs b/src/ConsoleMode/Services/ControlFsLocator.cs index cf7c781..2512bb4 100644 --- a/src/ConsoleMode/Services/ControlFsLocator.cs +++ b/src/ConsoleMode/Services/ControlFsLocator.cs @@ -25,6 +25,15 @@ void Add(string? path) return folders; } + /// + /// Full screen means the window is exactly the screen. A maximized window is about 16 px bigger (its frame hangs + /// over the edges), so it is not counted; a pixel of tolerance covers rounding. + /// + public static bool IsFullScreen(int left, int top, int width, int height, int screenX, int screenY, int screenWidth, int screenHeight) => + screenWidth > 0 && screenHeight > 0 + && Math.Abs(left - screenX) <= 1 && Math.Abs(top - screenY) <= 1 + && Math.Abs(width - screenWidth) <= 1 && Math.Abs(height - screenHeight) <= 1; + /// The first folder that really has ControlFS.exe, or null (not installed). public static string? Resolve(IEnumerable folders, Func fileExists) { diff --git a/src/ConsoleMode/Services/ControlFsService.cs b/src/ConsoleMode/Services/ControlFsService.cs index 63caa7e..0d39014 100644 --- a/src/ConsoleMode/Services/ControlFsService.cs +++ b/src/ConsoleMode/Services/ControlFsService.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using ConsoleMode.Models; +using ConsoleMode.Native; using Microsoft.Win32; namespace ConsoleMode.Services; @@ -34,6 +36,17 @@ public static bool Open() if (exe is null) return false; try { + // Already open: bring that window forward. Launching it again can leave a second copy running + // (seen with the installed build), so the flag is only for when there is no window yet. + var open = FindOpenWindow(); + if (open != 0) + { + NativeWindows.ShowWindow(open, 9 /* SW_RESTORE */); + NativeWindows.ForceForeground(open); + AppLog.Write("ControlFS: já estava aberto, trazido para a frente"); + return true; + } + var psi = new ProcessStartInfo { FileName = exe, UseShellExecute = false, WorkingDirectory = Path.GetDirectoryName(exe)! }; psi.ArgumentList.Add("--start"); Process.Start(psi)?.Dispose(); @@ -47,6 +60,66 @@ public static bool Open() } } + /// + /// ControlFS has no start-up flag for full screen (it keeps the choice in its own settings), so once its window is + /// up it is moved to the session screen and, if it isn't exactly covering it, F11 (its full-screen key) is sent. + /// Maximized is the fallback if F11 didn't take. Runs off the UI thread; never throws. + /// + public static void EnsureFullScreen(ScreenRect? screen) + { + if (screen is null || screen.Width <= 0 || screen.Height <= 0) return; + try + { + var hwnd = WaitForWindow(TimeSpan.FromSeconds(12)); + if (hwnd == 0) { AppLog.Write("ControlFS: janela não apareceu para colocar em tela cheia"); return; } + if (IsCovering(hwnd, screen)) return; + + // A restored or maximized window first goes to the session screen, so F11 makes it cover that one. + NativeWindows.ShowWindow(hwnd, 9 /* SW_RESTORE */); + NativeWindows.MoveWindowToRect(hwnd, screen.X + 80, screen.Y + 80, Math.Max(400, screen.Width - 160), Math.Max(300, screen.Height - 160)); + Thread.Sleep(300); + if (NativeWindows.GetForegroundWindow() != hwnd) NativeWindows.ForceForeground(hwnd); + Thread.Sleep(200); + NativeWindows.SendF11(); + Thread.Sleep(700); + if (IsCovering(hwnd, screen)) { AppLog.Write("ControlFS: em tela cheia"); return; } + + NativeWindows.ShowWindow(hwnd, 3 /* SW_MAXIMIZE */); + AppLog.Write("ControlFS: F11 não surtiu efeito, janela maximizada"); + } + catch (Exception ex) { AppLog.Write($"ControlFS: tela cheia: {ex.Message}"); } + } + + private static bool IsCovering(nint hwnd, ScreenRect screen) => + NativeWindows.TryGetWindowRect(hwnd, out var l, out var t, out var w, out var h) + && ControlFsLocator.IsFullScreen(l, t, w, h, screen.X, screen.Y, screen.Width, screen.Height); + + private static nint FindOpenWindow() + { + foreach (var process in Process.GetProcessesByName("ControlFS")) + { + using (process) + { + process.Refresh(); + var hwnd = process.MainWindowHandle; + if (hwnd != 0 && NativeWindows.IsWindowStillVisible(hwnd)) return hwnd; + } + } + return 0; + } + + private static nint WaitForWindow(TimeSpan timeout) + { + var deadline = Environment.TickCount64 + (long)timeout.TotalMilliseconds; + while (Environment.TickCount64 < deadline) + { + var hwnd = FindOpenWindow(); + if (hwnd != 0) { Thread.Sleep(500); return hwnd; } + Thread.Sleep(250); + } + return 0; + } + /// Not installed: the release page opens in the browser. public static void OpenDownloadPage() { diff --git a/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs b/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs index 2d2bcf4..f18c31a 100644 --- a/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs +++ b/src/ConsoleMode/ViewModels/MainViewModel.SessionMenu.cs @@ -326,8 +326,13 @@ private async Task RunSessionActionAsync(Action action) private void OpenControlFs() { var installed = ControlFsService.FindExe() is not null; + var screen = IsConsoleActive ? Engine.State.FocusMonitorRect : Engine.Monitors.GetMonitorRect(SessionMonitorName, Engine.State); CloseSessionMenu(); - if (installed && ControlFsService.Open()) return; + if (installed && ControlFsService.Open()) + { + _ = Task.Run(() => ControlFsService.EnsureFullScreen(screen)); + return; + } AppLog.Write("ControlFS: não instalado, abrindo a página de download"); ControlFsService.OpenDownloadPage(); } diff --git a/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs index 0873b61..fa8259b 100644 --- a/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs +++ b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs @@ -21,6 +21,16 @@ public void Blank_and_quoted_inputs_are_cleaned_or_dropped() Assert.Equal([@"D:\Apps\ControlFS"], ControlFsLocator.Folders("\"D:\\Apps\\ControlFS\\\"", null, null)); } + [Fact] + public void Full_screen_is_the_exact_screen_not_a_maximized_window() + { + Assert.True(ControlFsLocator.IsFullScreen(0, 0, 1920, 1080, 0, 0, 1920, 1080)); + Assert.True(ControlFsLocator.IsFullScreen(1920, 0, 3840, 2160, 1920, 0, 3840, 2160)); // a second screen + Assert.False(ControlFsLocator.IsFullScreen(-8, -8, 1936, 1048, 0, 0, 1920, 1080)); // maximized: the frame overhangs + Assert.False(ControlFsLocator.IsFullScreen(0, 0, 1920, 1080, 0, 0, 3840, 2160)); // on the wrong screen + Assert.False(ControlFsLocator.IsFullScreen(0, 0, 100, 100, 0, 0, 0, 0)); // no screen known + } + [Fact] public void Resolve_returns_the_first_folder_that_has_the_exe_and_null_when_none_does() { From 0170f3beed01c07296408027b3dae5b94911f092 Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 17:49:14 -0300 Subject: [PATCH 4/5] docs: add PR number to the ControlFS changelog item Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 2 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 7de9708..dead997 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,7 +4,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] ### What's new -- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top, always full screen on the session screen (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. +- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top, always full screen on the session screen (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. (#122) - Session menu (the controller shortcut, Select + Y in the suggestion) redesigned like Steam's overlay (Shift + Tab): it covers the whole screen, the game dims and blurs behind it, a side panel gathers the options (volume with a gauge, resolution, audio, FPS, HDR) and the **open windows** are always visible in the middle, an in-menu Alt + Tab: the D-pad picks, A brings the window to the front, X closes it and B goes back to the game (switching windows does not end the session). The frosted glass only shows when Windows' transparency effects are on (otherwise the panel is dark and solid, and it changes live if you flip the setting), and in high contrast it uses the system colours. It has a focus that grows, sounds and animations (the panel slides in, the rows follow one after another and closing fades it out). In this alpha it also opens **outside a session**, as a preview with a badge on top, so it can be tried without turning on console mode (in it the FPS limit is hidden and "Back to the PC" becomes "Close menu"). (#120) - Redesigned Console interface, made to feel like a console: a top bar with three tabs (Home, Session and System) you switch with LB/RB (L1/R1 on PlayStation) with a slide animation, screen tiles that show what will happen to each monitor, quick settings with icons and a focus that grows as you move over it. Home brings the Play now banner and a summary of the current setup (game screen, audio, launcher and HDR/VRR), each opening its choices. (#117) - Console background: by default, a collage of the covers of your installed Steam games under a dark gradient (the app only reads images Steam already downloaded; nothing is bundled). You can switch to just the gradient or to a picture of your own, in System → Background. (#117) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9dbce7..acbad86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] ### Novidades -- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima, sempre em tela cheia na tela da sessão (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. +- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima, sempre em tela cheia na tela da sessão (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. (#122) - Menu da sessão (o atalho do controle, Select + Y na sugestão) redesenhado como o overlay do Steam (Shift+Tab): cobre a tela inteira, o jogo escurece e desfoca por trás, um painel lateral reúne as opções (volume com medidor, resolução, áudio, FPS, HDR) e no meio ficam sempre visíveis as **janelas abertas**, um ALT+TAB interno: o direcional escolhe, A traz a janela para a frente, X a fecha e B volta ao jogo (trocar de janela não encerra a sessão). O vidro fosco só aparece se os efeitos de transparência do Windows estiverem ligados (senão o painel fica escuro e sólido, e muda ao vivo se você trocar a configuração), e em alto contraste usa as cores do sistema. Tem foco que cresce, sons e animações (o painel entra deslizando, as linhas vêm em cascata e ao fechar ele some suavemente). Nesta alpha ele abre também **fora da sessão**, como prévia com um selo no topo, para testar sem ligar o modo console (nela o limite de FPS não aparece e "Voltar ao PC" vira "Fechar menu"). (#120) - Interface Console redesenhada, com cara de console: barra no topo com três abas (Início, Sessão e Sistema) que você troca com LB/RB (L1/R1 no PlayStation) com uma animação de deslize, blocos das telas que mostram o que vai acontecer com cada monitor, ajustes rápidos com ícones e foco que cresce ao passar. Início traz o banner com Jogar agora e um resumo do setup atual (tela de jogo, áudio, iniciador e HDR/VRR), que abre cada escolha. (#117) - Plano de fundo do modo Console: por padrão, uma colagem com as capas dos seus jogos instalados na Steam sob um gradiente escuro (o app só lê as imagens que a Steam já baixou; nada é embutido). Dá para trocar por só o gradiente ou por uma imagem sua, em Sistema → Plano de fundo. (#117) From 9abce09de5604615274861276cd5e495689d04bb Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 18:34:51 -0300 Subject: [PATCH 5/5] fix: protect foreground apps when opening ControlFS --- CHANGELOG.en-US.md | 2 +- CHANGELOG.md | 2 +- docs/TESTING.md | 1 + src/ConsoleMode/Services/ControlFsService.cs | 11 +++++++++-- .../ControlFsLocatorTests.cs | 19 +++++++++++++------ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index dead997..4fdbde6 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,7 +4,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] ### What's new -- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top, always full screen on the session screen (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag and never closes it. (#122) +- Session menu: a pinned panel at the top of the side column with the **File explorer (ControlFS)**, the team's open-source, controller-first file manager. One press opens ControlFS on top, always full screen on the session screen (or brings it to the front if already open); if it isn't installed, its download page opens. Console Mode only calls it through its own `--start` flag, never closes it, and never sends F11 to another window if Windows denies focus. (#122) - Session menu (the controller shortcut, Select + Y in the suggestion) redesigned like Steam's overlay (Shift + Tab): it covers the whole screen, the game dims and blurs behind it, a side panel gathers the options (volume with a gauge, resolution, audio, FPS, HDR) and the **open windows** are always visible in the middle, an in-menu Alt + Tab: the D-pad picks, A brings the window to the front, X closes it and B goes back to the game (switching windows does not end the session). The frosted glass only shows when Windows' transparency effects are on (otherwise the panel is dark and solid, and it changes live if you flip the setting), and in high contrast it uses the system colours. It has a focus that grows, sounds and animations (the panel slides in, the rows follow one after another and closing fades it out). In this alpha it also opens **outside a session**, as a preview with a badge on top, so it can be tried without turning on console mode (in it the FPS limit is hidden and "Back to the PC" becomes "Close menu"). (#120) - Redesigned Console interface, made to feel like a console: a top bar with three tabs (Home, Session and System) you switch with LB/RB (L1/R1 on PlayStation) with a slide animation, screen tiles that show what will happen to each monitor, quick settings with icons and a focus that grows as you move over it. Home brings the Play now banner and a summary of the current setup (game screen, audio, launcher and HDR/VRR), each opening its choices. (#117) - Console background: by default, a collage of the covers of your installed Steam games under a dark gradient (the app only reads images Steam already downloaded; nothing is bundled). You can switch to just the gradient or to a picture of your own, in System → Background. (#117) diff --git a/CHANGELOG.md b/CHANGELOG.md index acbad86..500d17c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] ### Novidades -- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima, sempre em tela cheia na tela da sessão (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra. (#122) +- Menu da sessão: painel fixo no topo da lateral com o **Explorador de arquivos (ControlFS)**, o gerenciador de arquivos de código aberto da equipe, feito para o controle. Um toque abre o ControlFS por cima, sempre em tela cheia na tela da sessão (ou o traz para a frente se já estiver aberto); se não estiver instalado, abre a página de download. O Console Mode só o chama pelo `--start` dele, não o encerra nem envia F11 a outra janela se o Windows negar o foco. (#122) - Menu da sessão (o atalho do controle, Select + Y na sugestão) redesenhado como o overlay do Steam (Shift+Tab): cobre a tela inteira, o jogo escurece e desfoca por trás, um painel lateral reúne as opções (volume com medidor, resolução, áudio, FPS, HDR) e no meio ficam sempre visíveis as **janelas abertas**, um ALT+TAB interno: o direcional escolhe, A traz a janela para a frente, X a fecha e B volta ao jogo (trocar de janela não encerra a sessão). O vidro fosco só aparece se os efeitos de transparência do Windows estiverem ligados (senão o painel fica escuro e sólido, e muda ao vivo se você trocar a configuração), e em alto contraste usa as cores do sistema. Tem foco que cresce, sons e animações (o painel entra deslizando, as linhas vêm em cascata e ao fechar ele some suavemente). Nesta alpha ele abre também **fora da sessão**, como prévia com um selo no topo, para testar sem ligar o modo console (nela o limite de FPS não aparece e "Voltar ao PC" vira "Fechar menu"). (#120) - Interface Console redesenhada, com cara de console: barra no topo com três abas (Início, Sessão e Sistema) que você troca com LB/RB (L1/R1 no PlayStation) com uma animação de deslize, blocos das telas que mostram o que vai acontecer com cada monitor, ajustes rápidos com ícones e foco que cresce ao passar. Início traz o banner com Jogar agora e um resumo do setup atual (tela de jogo, áudio, iniciador e HDR/VRR), que abre cada escolha. (#117) - Plano de fundo do modo Console: por padrão, uma colagem com as capas dos seus jogos instalados na Steam sob um gradiente escuro (o app só lê as imagens que a Steam já baixou; nada é embutido). Dá para trocar por só o gradiente ou por uma imagem sua, em Sistema → Plano de fundo. (#117) diff --git a/docs/TESTING.md b/docs/TESTING.md index eb14123..65cbca3 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -156,6 +156,7 @@ Pré-condição: ControlFS instalado (https://github.com/nextestudios/ControlFS) - [ ] Select + Y: no topo da lateral esquerda, fora da lista que rola, há o painel "Explorador de arquivos (ControlFS)"; o restante das opções rola por baixo dele. Resultado: ______ - [ ] Direcional para cima a partir de "Voltar ao jogo" chega nele; A abre o ControlFS em tela cheia na TV, por cima do jogo/Big Picture, e o menu fecha. Resultado: ______ - [ ] O ControlFS abre **sempre em tela cheia** na tela da sessão (a TV), cobrindo-a por inteiro, mesmo que ele estivesse em janela ou maximizado; se já estava em tela cheia, nada muda. Resultado: ______ +- [ ] Se outra aplicação permanecer em primeiro plano porque o Windows negou o foco ao ControlFS, o F11 não é enviado a ela; a janela de outra aplicação não muda de estado. Resultado: ______ - [ ] Abrir de novo com o ControlFS já aberto só o traz para a frente (continua um processo só no Gerenciador de Tarefas). Resultado: ______ - [ ] O ControlFS responde ao controle logo de cara (o foco do controle fica com ele, sem o Console Mode reagir aos botões). Resultado: ______ - [ ] Com o ControlFS já aberto: A só o traz para a frente (não abre uma segunda instância). Resultado: ______ diff --git a/src/ConsoleMode/Services/ControlFsService.cs b/src/ConsoleMode/Services/ControlFsService.cs index 0d39014..8b4f90e 100644 --- a/src/ConsoleMode/Services/ControlFsService.cs +++ b/src/ConsoleMode/Services/ControlFsService.cs @@ -80,8 +80,15 @@ public static void EnsureFullScreen(ScreenRect? screen) Thread.Sleep(300); if (NativeWindows.GetForegroundWindow() != hwnd) NativeWindows.ForceForeground(hwnd); Thread.Sleep(200); - NativeWindows.SendF11(); - Thread.Sleep(700); + if (NativeWindows.GetForegroundWindow() == hwnd) + { + NativeWindows.SendF11(); + Thread.Sleep(700); + } + else + { + AppLog.Write("ControlFS: F11 n\u00e3o enviado porque outra janela est\u00e1 em primeiro plano"); + } if (IsCovering(hwnd, screen)) { AppLog.Write("ControlFS: em tela cheia"); return; } NativeWindows.ShowWindow(hwnd, 3 /* SW_MAXIMIZE */); diff --git a/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs index fa8259b..d78b728 100644 --- a/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs +++ b/tests/ConsoleMode.Tests/ControlFsLocatorTests.cs @@ -7,9 +7,12 @@ public class ControlFsLocatorTests [Fact] public void The_registry_folder_comes_first_then_the_usual_ones_without_repeats() { - var folders = ControlFsLocator.Folders(@"C:\Users\ana\AppData\Local\Programs\ControlFS\", @"C:\Users\ana\AppData\Local", @"C:\Program Files"); + var localAppData = Path.Combine(Path.GetTempPath(), "ana", "AppData", "Local"); + var registryLocation = Path.Combine(localAppData, "Programs", "ControlFS"); + var programFiles = Path.Combine(Path.GetTempPath(), "Program Files"); + var folders = ControlFsLocator.Folders(registryLocation + Path.DirectorySeparatorChar, localAppData, programFiles); Assert.Equal( - [@"C:\Users\ana\AppData\Local\Programs\ControlFS", Path.Combine(@"C:\Program Files", "ControlFS")], + [registryLocation, Path.Combine(programFiles, "ControlFS")], folders); } @@ -18,7 +21,9 @@ public void Blank_and_quoted_inputs_are_cleaned_or_dropped() { var folders = ControlFsLocator.Folders(" ", null, null); Assert.Empty(folders); - Assert.Equal([@"D:\Apps\ControlFS"], ControlFsLocator.Folders("\"D:\\Apps\\ControlFS\\\"", null, null)); + var quotedPath = Path.Combine(Path.GetTempPath(), "Apps", "ControlFS"); + var quoted = $"\"{quotedPath}{Path.DirectorySeparatorChar}\""; + Assert.Equal([quotedPath], ControlFsLocator.Folders(quoted, null, null)); } [Fact] @@ -34,9 +39,11 @@ public void Full_screen_is_the_exact_screen_not_a_maximized_window() [Fact] public void Resolve_returns_the_first_folder_that_has_the_exe_and_null_when_none_does() { - string[] folders = [@"C:\a", @"C:\b", @"C:\c"]; - var hit = ControlFsLocator.Resolve(folders, p => p == Path.Combine(@"C:\b", "ControlFS.exe") || p == Path.Combine(@"C:\c", "ControlFS.exe")); - Assert.Equal(Path.Combine(@"C:\b", "ControlFS.exe"), hit); + string[] folders = [Path.Combine(Path.GetTempPath(), "a"), Path.Combine(Path.GetTempPath(), "b"), Path.Combine(Path.GetTempPath(), "c")]; + var secondExe = Path.Combine(folders[1], "ControlFS.exe"); + var thirdExe = Path.Combine(folders[2], "ControlFS.exe"); + var hit = ControlFsLocator.Resolve(folders, p => p == secondExe || p == thirdExe); + Assert.Equal(secondExe, hit); Assert.Null(ControlFsLocator.Resolve(folders, _ => false)); } }