From 0fe96373567f0b48aabeed339f30efbf23b3cef3 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:31:03 +0100 Subject: [PATCH] Status speech: read line-by-line, MB->GB on totals, double-press to copy (held for next release) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Andre's feedback on the spoken status (the issue #13 feature): - Speak the status one line at a time instead of collapsing the lines into a run-on sentence — this also removes the doubled "." the collapse produced (each line already ended in a period). - Totals switch to GB once they reach a gigabyte ("4.5 GB" vs "4558.2 MB"), via a FormatDataSize helper used by the readout itself so the on-screen and spoken figures stay consistent. - A quick DOUBLE press of the speak-status hotkey copies the status to the clipboard (with a "copied" announcement) so it can be shared with others. Held for the next release. Co-Authored-By: Claude Opus 4.8 --- src/RemSound.App/MainForm.cs | 54 +++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs index 787a6fb..356681c 100644 --- a/src/RemSound.App/MainForm.cs +++ b/src/RemSound.App/MainForm.cs @@ -267,6 +267,8 @@ public sealed class MainForm : Form // Tracks when the FIRST healthy-peer transition happened in the current "connected" // span. Cleared when no peers are healthy. Used for the uptime line. private DateTime? statusConnectedSinceUtc; + // Time of the last "speak status" hotkey press, for double-press-to-copy detection. + private DateTime lastSpeakStatusPressUtc = DateTime.MinValue; // Per-list state (used by sync helpers — was per-method in the old dialog). private bool suppressConnectedCheck; private bool suppressDiscoveredCheck; @@ -3348,18 +3350,40 @@ public sealed class MainForm : Form statusReadout.Text = text; } - /// Speak the current connection status line aloud through the active screen reader, via - /// Tolk (). 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. + /// Speak the current connection status aloud through the active screen reader, via Tolk + /// (). 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. The + /// status is read line by line (the multi-line text is passed straight to the screen reader, which + /// pauses at each line) rather than collapsed into a run-on sentence. A quick DOUBLE press copies + /// the same text to the clipboard instead, so it can be shared. (Andre's suggestion, 2026-06-24.) private void SpeakStatusLine() { + var now = DateTime.UtcNow; + var doublePress = now - lastSpeakStatusPressUtc <= TimeSpan.FromMilliseconds(600); + lastSpeakStatusPressUtc = now; + var text = statusReadout.Text; - text = string.IsNullOrWhiteSpace(text) - ? "No status information available." - : text.Replace("\r\n", ". ").Replace("\n", ". "); + if (string.IsNullOrWhiteSpace(text)) text = "No status information available."; + + if (doublePress) + { + // Second quick press: copy the status to the clipboard so it can be shared. Clipboard access + // needs the UI thread — this runs on it (the hotkey is marshalled onto the owner form). + try + { + Clipboard.SetText(text); + ScreenReader.Speak("RemSound status copied to the clipboard."); + } + catch (Exception ex) + { + logFile.Event($"speak status: clipboard copy failed: {ex.GetType().Name}: {ex.Message}"); + ScreenReader.Speak("Could not copy the status to the clipboard."); + } + return; + } + + // Single press: read it out, line by line (the screen reader pauses at each line break). ScreenReader.Speak(text); } @@ -3446,10 +3470,20 @@ public sealed class MainForm : Form } sb.AppendLine($"Receiving {rxKbs:0.0} kB/s; sending {txKbs:0.0} kB/s."); - sb.Append($"Total received {receiver.BytesReceived / 1048576.0:0.0} MB; sent {sender.BytesSent / 1048576.0:0.0} MB."); + sb.Append($"Total received {FormatDataSize(receiver.BytesReceived)}; sent {FormatDataSize(sender.BytesSent)}."); return sb.ToString(); } + /// A running data total as MB, switching to GB once it reaches a gigabyte — "935.8 MB", + /// "4.5 GB" — so a big figure reads more naturally (Andre's suggestion). Binary units, matching the + /// 1048576-byte MB used elsewhere. + private static string FormatDataSize(long bytes) + { + const double mb = 1048576.0; + const double gb = 1073741824.0; + return bytes >= gb ? $"{bytes / gb:0.0} GB" : $"{bytes / mb:0.0} MB"; + } + private static string FormatUptime(TimeSpan span) { if (span.TotalSeconds < 1) return "0 seconds";