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
+19
View File
@@ -128,6 +128,25 @@ public sealed class AppConfig
/// ticks <em>Enable logs</em> in the Preferences dialog.</summary>
public bool LoggingEnabled { get; set; }
/// <summary>If true, RemSound checks the total size of the <c>logs\</c> folder at startup and,
/// when it exceeds <see cref="LogsFolderWarnThresholdMb"/> megabytes, shows a one-time warning so
/// the user can prune or clear it. Off by default (opt-in, like the other behaviour toggles).
/// Machine-local — a "watch my disk on this box" decision, not a per-profile audio setting.</summary>
public bool WarnIfLogsFolderExceeds { get; set; }
/// <summary>The size threshold in megabytes for <see cref="WarnIfLogsFolderExceeds"/>. Default
/// 100 MB. Only consulted when that flag is on.</summary>
public int LogsFolderWarnThresholdMb { get; set; } = 100;
/// <summary>If true, RemSound deletes log files older than <see cref="PruneOldLogsDays"/> days
/// from the <c>logs\</c> folder at startup. Off by default (opt-in). The currently-open log file
/// is never a candidate (it's today's). Machine-local.</summary>
public bool PruneOldLogs { get; set; }
/// <summary>Age in days for <see cref="PruneOldLogs"/>: log files last written more than this many
/// days ago are deleted at startup. Range 1–30, default 14. Only consulted when that flag is on.</summary>
public int PruneOldLogsDays { get; set; } = 14;
/// <summary>If non-null and a profile with this title exists, RemSound skips the
/// startup profile picker and loads this profile directly. Combine with
/// <see cref="StartMinimised"/> + the Windows auto-start registry entry
+3
View File
@@ -212,6 +212,9 @@ public sealed class Profile
/// <summary>Global hotkey that opens the Quick profile switch popup — a list of all profiles you
/// can arrow through and press Enter to switch to, from anywhere in Windows. Unset by default.</summary>
public HotkeyRecord? QuickProfileSwitchHotkey { get; set; }
/// <summary>Optional global hotkey that speaks the connection status line aloud through the active
/// screen reader (issue #13). Unset by default. Screen-reader specific.</summary>
public HotkeyRecord? SpeakStatusLineHotkey { get; set; }
/// <summary>When true, this machine honours incoming Control packets from connected
/// peers — adjusts the local volume slider or toggles mute. Default false: receiving
/// remote control is opt-in even though the audio allow-list already gates who's
@@ -144,6 +144,16 @@ public sealed class RemSoundSettingsStore
Save(s);
}
public HotkeyInfo LoadSpeakStatusLineHotkey() =>
Try(() => Load()?.SpeakStatusLineHotkey?.ToHotkeyInfo()) ?? HotkeyInfo.Unset;
public void SaveSpeakStatusLineHotkey(HotkeyInfo hotkey)
{
var s = Load() ?? new Settings();
s.SpeakStatusLineHotkey = HotkeySetting.From(hotkey);
Save(s);
}
public bool LoadAcceptRemoteVolumeCommands(bool defaultValue = false) =>
Try(() => Load()?.AcceptRemoteVolumeCommands) ?? defaultValue;
@@ -593,6 +603,7 @@ public sealed class RemSoundSettingsStore
SystemVolumeDownHotkey = profile.SystemVolumeDownHotkey is null ? null : HotkeySettingFromRecord(profile.SystemVolumeDownHotkey),
SystemMuteToggleHotkey = profile.SystemMuteToggleHotkey is null ? null : HotkeySettingFromRecord(profile.SystemMuteToggleHotkey),
QuickProfileSwitchHotkey = profile.QuickProfileSwitchHotkey is null ? null : HotkeySettingFromRecord(profile.QuickProfileSwitchHotkey),
SpeakStatusLineHotkey = profile.SpeakStatusLineHotkey is null ? null : HotkeySettingFromRecord(profile.SpeakStatusLineHotkey),
AcceptRemoteVolumeCommands = profile.AcceptRemoteVolumeCommands,
MaxLatencyMs = profile.MaxLatencyMs,
Codec = profile.Codec,
@@ -649,6 +660,7 @@ public sealed class RemSoundSettingsStore
profile.SystemVolumeDownHotkey = s.SystemVolumeDownHotkey is null ? null : HotkeyRecordFromSetting(s.SystemVolumeDownHotkey);
profile.SystemMuteToggleHotkey = s.SystemMuteToggleHotkey is null ? null : HotkeyRecordFromSetting(s.SystemMuteToggleHotkey);
profile.QuickProfileSwitchHotkey = s.QuickProfileSwitchHotkey is null ? null : HotkeyRecordFromSetting(s.QuickProfileSwitchHotkey);
profile.SpeakStatusLineHotkey = s.SpeakStatusLineHotkey is null ? null : HotkeyRecordFromSetting(s.SpeakStatusLineHotkey);
if (s.AcceptRemoteVolumeCommands is bool arvc) profile.AcceptRemoteVolumeCommands = arvc;
if (s.MaxLatencyMs is int ml) profile.MaxLatencyMs = ml;
if (s.Codec is AudioTransportCodec c) profile.Codec = c;
@@ -716,6 +728,7 @@ public sealed class RemSoundSettingsStore
public HotkeySetting? SystemVolumeDownHotkey { get; set; }
public HotkeySetting? SystemMuteToggleHotkey { get; set; }
public HotkeySetting? QuickProfileSwitchHotkey { get; set; }
public HotkeySetting? SpeakStatusLineHotkey { get; set; }
public bool? AcceptRemoteVolumeCommands { get; set; }
public int? MaxLatencyMs { get; set; }
public AudioTransportCodec? Codec { get; set; }