From 52a60908e5dc3c3b3354b2fcc0243e89e6ec8d3a Mon Sep 17 00:00:00 2001 From: Filipe Moreira Date: Wed, 30 Sep 2026 15:33:22 -0300 Subject: [PATCH] fix: the session hint toast no longer sticks on screen The toast that shows the menu shortcut a few seconds into a session was created with `_ = new SessionHintWindow(...)` and its close timer was a local variable. Nothing held either of them, so the garbage collector could take the window and the timer before the timer fired, and the toast then stayed on screen and never closed, only sometimes. The window now keeps itself in a static set until it closes and owns its timer; the timer stops itself when it fires and a failed Close goes to the log instead of being swallowed. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 1 + CHANGELOG.md | 1 + docs/TESTING.md | 1 + src/ConsoleMode/SessionHintWindow.cs | 29 +++++++++++++++++++++++----- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 145f695..aa31b4d 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 +- 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) - Audio: if switching the output fails, the session goes on instead of being rolled back after the screens were already switched. (#109) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98ac9a9..20a9bed 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 +- 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) - Áudio: se a troca da saída falhar, a sessão continua em vez de ser desfeita depois de as telas já terem sido trocadas. (#109) diff --git a/docs/TESTING.md b/docs/TESTING.md index 958d8d0..0b7885d 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -41,6 +41,7 @@ Pré-condições: sessão ativa com Big Picture (ou jogo borderless) na tela; co - [ ] Segurar **Select + Y** por ~0,3 s abre o menu centralizado na tela de jogo, por cima do Big Picture, com foco em "Voltar ao jogo". Resultado: ______ - [ ] Select + Y de novo (ou B, ou Esc) fecha o menu e o jogo continua onde estava. Resultado: ______ +- [ ] O aviso "Menu do Console Mode" do início da sessão some sozinho em cerca de 7 s, em várias sessões seguidas, e nunca fica preso na tela. Resultado: ______ - [ ] Cabeçalho mostra o relógio, "Jogando há X min" e o controle detectado. Resultado: ______ - [ ] **Volume:** ◀/▶ na linha muda de 5 em 5 e o Windows reflete; A alterna mudo. O valor inicial é o atual do Windows. Resultado: ______ - [ ] **Resolução:** abre a lista com a atual marcada; escolher outra aplica na TV, o Big Picture continua aberto e a sessão não se encerra sozinha. Ao restaurar no fim, a resolução original volta. Resultado: ______ diff --git a/src/ConsoleMode/SessionHintWindow.cs b/src/ConsoleMode/SessionHintWindow.cs index 9a52742..26c892a 100644 --- a/src/ConsoleMode/SessionHintWindow.cs +++ b/src/ConsoleMode/SessionHintWindow.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using ConsoleMode.Models; using ConsoleMode.Native; +using ConsoleMode.Services; using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; @@ -21,6 +22,12 @@ public sealed class SessionHintWindow : Window private const double HeightDip = 96; private const double MarginDip = 28; + // Nobody holds the window after "new": without this the GC can collect it and its timer + // before they fire, and the toast then stays on screen and never closes. + private static readonly HashSet Live = []; + + private readonly Microsoft.UI.Dispatching.DispatcherQueueTimer _timer; + public SessionHintWindow(string title, string body, ScreenRect? target, TimeSpan duration) { var icon = new FontIcon { Glyph = "", FontSize = 30, VerticalAlignment = VerticalAlignment.Center }; @@ -58,11 +65,23 @@ public SessionHintWindow(string title, string body, ScreenRect? target, TimeSpan WindowPlacement.TopRightOn(appWindow, target, WidthDip, HeightDip, MarginDip); appWindow.Show(activateWindow: false); - var timer = DispatcherQueue.CreateTimer(); - timer.Interval = duration; - timer.IsRepeating = false; - timer.Tick += (_, _) => Close(); - timer.Start(); + Live.Add(this); + Closed += (_, _) => + { + _timer?.Stop(); + Live.Remove(this); + }; + + _timer = DispatcherQueue.CreateTimer(); + _timer.Interval = duration; + _timer.IsRepeating = false; + _timer.Tick += (_, _) => + { + _timer.Stop(); + try { Close(); } + catch (Exception ex) { AppLog.Write($"Menu da sessão: aviso não fechou: {ex.Message}"); } + }; + _timer.Start(); } private const int GwlExStyle = -20;