Release v4.3: speak status line (Tolk) + Logging tab with housekeeping

- New screen-reader hotkey "Speak the RemSound status information" (issue #13):
  reads the status line aloud through the active screen reader via Tolk, fires from
  anywhere (system-wide), unset by default. Built behind an IScreenReaderOutput seam
  so a future build can swap Tolk for Prism on Windows 10+ without touching callers.
  Tolk DLLs vendored under tolk/ and shipped next to the exe.
- New Logging tab in Preferences: Enable logs + Write logs now moved there, plus
  opt-in startup "warn if logs folder exceeds N MB" and "delete logs older than N days",
  and a "Delete all logs" button (Yes/No confirm). New LogMaintenance helper + AppConfig
  settings drive it.
- Manual (readme.html + regenerated MANUAL.md), About changelog and RELEASE_NOTES
  updated in plain English; csproj <Version> bumped to 4.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-19 13:09:06 +01:00
co-authored by Claude Opus 4.8
parent 034a7de632
commit fd5c31740a
20 changed files with 783 additions and 26 deletions
+70 -1
View File
@@ -716,7 +716,10 @@ public sealed class MainForm : Form
() => SendRemoteControl(RemoteControlKind.SystemVolumeUp, 0),
() => SendRemoteControl(RemoteControlKind.SystemVolumeDown, 0),
() => SendRemoteControl(RemoteControlKind.SystemMuteToggle, 0),
ShowQuickProfileSwitch);
ShowQuickProfileSwitch,
// Speak the status line aloud through the active screen reader (issue #13). Screen-reader
// specific; the global hotkey is unset by default (the user binds it in Keyboard shortcuts).
SpeakStatusLine);
// Pipe hotkey controller diagnostics into the main log so we can see, e.g.,
// "capture send-system-volume-down: OK = Ctrl+Shift+Alt+J" and
// "register send-system-volume-down: FAILED = Ctrl+Shift+Alt+J (Win32 error 1409:
@@ -1235,6 +1238,7 @@ public sealed class MainForm : Form
}
hotkeyController.Dispose();
ScreenReader.Shutdown();
trayController.Dispose();
logFile.Dispose();
};
@@ -1379,11 +1383,54 @@ public sealed class MainForm : Form
if (IsDisposed) return;
MaybeShowWhatsNewAfterUpdate();
if (IsDisposed) return;
MaybeRunLogHousekeeping();
if (IsDisposed) return;
MaybeWarnAboutRealtekAsio();
if (IsDisposed) return;
MaybeWarnMicBlockedOnStartup();
}
/// <summary>Startup log-folder housekeeping driven by the Logging-tab preferences. Both steps are
/// opt-in (off by default): first prune logs older than the configured age, then warn if the
/// folder still exceeds the configured size. Best-effort — failures never block launch.</summary>
private void MaybeRunLogHousekeeping()
{
if (IsDisposed) return;
AppConfig cfg;
try { cfg = AppConfig.Load(); }
catch { return; }
if (cfg.PruneOldLogs)
{
var removed = LogMaintenance.PruneLogsOlderThan(cfg.PruneOldLogsDays, logFile.Path);
if (removed > 0)
logFile.Event($"log housekeeping: pruned {removed} log(s) older than {cfg.PruneOldLogsDays} day(s)");
}
if (cfg.WarnIfLogsFolderExceeds)
{
var bytes = LogMaintenance.LogsFolderSizeBytes();
var limitBytes = (long)cfg.LogsFolderWarnThresholdMb * 1024 * 1024;
if (bytes > limitBytes)
{
var mb = bytes / (1024.0 * 1024.0);
logFile.Event($"log housekeeping: logs folder {mb:0.#} MB exceeds {cfg.LogsFolderWarnThresholdMb} MB threshold — warning user");
var page = new TaskDialogPage
{
Caption = "RemSound",
Heading = "Logs folder is getting large",
Text = $"The RemSound logs folder is using about {mb:0} MB, which is over your {cfg.LogsFolderWarnThresholdMb} MB warning size.\n\n"
+ "You can clear old logs from Preferences, on the Logging tab.",
Icon = TaskDialogIcon.Warning,
Buttons = { TaskDialogButton.OK },
AllowCancel = true,
};
try { ForegroundDialog.Show(owner => TaskDialog.ShowDialog(owner, page)); }
catch (Exception ex) { logFile.Event($"log housekeeping: warn dialog failed: {ex.GetType().Name}: {ex.Message}"); }
}
}
}
/// <summary>If the user opted in (<see cref="AppConfig.ShowWhatsNewAfterUpdate"/>) and the
/// running version changed since the last launch we recorded, open the About box once so
/// they see what changed in the update just installed. Always records the current version
@@ -2242,6 +2289,13 @@ public sealed class MainForm : Form
UpdateDiagnosticsGate();
},
writeLogsNow: () => logFile.Event("user requested write logs now"),
deleteAllLogs: () =>
{
// Spare the log we're currently writing (it's held open and can't be removed anyway).
var removed = LogMaintenance.DeleteAllLogs(logFile.Path);
logFile.Event($"user deleted all logs from Preferences ({removed} file(s) removed)");
return removed;
},
checkForUpdatesNow: () => CheckForUpdatesManually(),
onUpdateFrequencyChanged: ApplyUpdateCheckTimer,
applyUpnpEnabled: enabled =>
@@ -3130,6 +3184,21 @@ public sealed class MainForm : Form
statusReadout.Text = text;
}
/// <summary>Speak the current connection status line aloud through the active screen reader, via
/// Tolk (<see cref="ScreenReader"/>). Answers issue #13: NVDA sometimes can't see the status
/// readout ("no status line found"), so this reads it on demand. Triggered only by a user-set
/// global hotkey (screen-reader only, unset by default) — being a system-wide hotkey it works from
/// anywhere, whether or not RemSound is focused. Newlines become ". " so the multi-line readout
/// speaks as a sentence rather than running together.</summary>
private void SpeakStatusLine()
{
var text = statusReadout.Text;
text = string.IsNullOrWhiteSpace(text)
? "No status information available."
: text.Replace("\r\n", ". ").Replace("\n", ". ");
ScreenReader.Speak(text);
}
private string ComputeStatusText()
{
// Compute byte-rates from delta since last sample. First call has no baseline so