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:
co-authored by
Claude Opus 4.8
parent
034a7de632
commit
fd5c31740a
@@ -0,0 +1,70 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RemSound.App;
|
||||
|
||||
/// <summary>
|
||||
/// Tolk-backed <see cref="IScreenReaderOutput"/>. Tolk (https://github.com/dkager/tolk, LGPL-3.0) is a
|
||||
/// screen-reader abstraction DLL that auto-detects whichever reader is running (NVDA, JAWS, Window-Eyes,
|
||||
/// SuperNova, System Access, ZoomText) and falls back to SAPI, then routes speech to it. The native
|
||||
/// <c>Tolk.dll</c> plus its helpers (<c>nvdaControllerClient64.dll</c>, <c>SAAPI64.dll</c>) ship flat
|
||||
/// next to the exe (see the .csproj Content items) — Tolk.dll loads those helpers from the same folder.
|
||||
///
|
||||
/// Loaded lazily on the first <see cref="Speak"/> and never re-attempted once it fails, so a missing DLL
|
||||
/// or absent screen reader just means silence, never an exception. All entry points are wrapped in
|
||||
/// try/catch and guarded by a lock, so it's safe to call from any thread (e.g. a hotkey callback).
|
||||
/// </summary>
|
||||
internal sealed class TolkScreenReaderOutput : IScreenReaderOutput
|
||||
{
|
||||
private readonly object sync = new();
|
||||
private bool loadAttempted;
|
||||
private bool loaded;
|
||||
|
||||
public bool Speak(string text, bool interrupt = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return false;
|
||||
if (!EnsureLoaded()) return false;
|
||||
try { return Tolk_Output(text, interrupt); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
if (!loaded) return;
|
||||
try { Tolk_Unload(); } catch { /* best-effort */ }
|
||||
loaded = false;
|
||||
loadAttempted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Load Tolk once. Succeeds only if Tolk loads AND reports a working speech channel, so a
|
||||
/// machine with the DLLs present but no screen reader running stays silent instead of half-init'd.</summary>
|
||||
private bool EnsureLoaded()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
if (loaded) return true;
|
||||
if (loadAttempted) return false; // already tried and failed — don't spam load attempts
|
||||
loadAttempted = true;
|
||||
try { loaded = Tolk_Load() && Tolk_HasSpeech(); }
|
||||
catch { loaded = false; }
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Tolk.dll", CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool Tolk_Load();
|
||||
|
||||
[DllImport("Tolk.dll")]
|
||||
private static extern void Tolk_Unload();
|
||||
|
||||
[DllImport("Tolk.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool Tolk_HasSpeech();
|
||||
|
||||
[DllImport("Tolk.dll", CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool Tolk_Output(string text, [MarshalAs(UnmanagedType.Bool)] bool interrupt);
|
||||
}
|
||||
Reference in New Issue
Block a user