diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs
index 0b6ccd0..d087e17 100644
--- a/src/RemSound.App/MainForm.cs
+++ b/src/RemSound.App/MainForm.cs
@@ -3621,7 +3621,13 @@ public sealed class MainForm : Form
// The chooser row only makes sense where applications mode is possible.
if (sendModeLabel is not null) sendModeLabel.Visible = supported;
SetRowControlVisible(sendModeList, supported);
- if (!supported && sendModeList.SelectedIndex != SendModeDevicesIndex)
+ // Items.Count > 0 guard: this method is called once EARLY in the constructor (via ApplyAsioMode)
+ // before the Input/Output tab has populated sendModeList, so the list can still be empty here.
+ // Setting SelectedIndex on an empty ListBox throws ArgumentOutOfRangeException. On Windows 10+ the
+ // branch is skipped anyway (process-loopback IS supported), which hid the bug — but on Windows 7
+ // (unsupported) it crashed the app at launch (issue #22). When the list is empty there's nothing to
+ // reset; it's created selecting Devices, and this runs again (line ~3611) once the list is built.
+ if (!supported && sendModeList.Items.Count > 0 && sendModeList.SelectedIndex != SendModeDevicesIndex)
{
suppressSendAppEvents = true;
sendModeList.SelectedIndex = SendModeDevicesIndex;
diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs
index 4e196cf..d59279b 100644
--- a/src/RemSound.App/SelfTest.cs
+++ b/src/RemSound.App/SelfTest.cs
@@ -82,6 +82,7 @@ internal static class SelfTest
RunStep(results, "Auto-save non-read-only profiles (options + guard + silent timer)", AutoSaveNonReadOnlyProfiles);
RunStep(results, "Service verb gate (normal launch stays load-safe)", ServiceVerbGate);
RunStep(results, "Main window builds without loading the service assembly (Win7-safe)", MainWindowServiceAssemblyFree);
+ RunStep(results, "Main window builds where process-loopback is unsupported (Win7 launch, issue #22)", Win7SendModeConstruction);
RunStep(results, "Menu shortcuts don't clash with controls", MenuShortcutsDontClashWithControls);
RunStep(results, "Service log discovery (newest activity log)", ServiceLogDiscovery);
@@ -1449,6 +1450,31 @@ internal static class SelfTest
}
}
+ /// Issue #22: on Windows 7 process-loopback is unsupported, so ApplySendModeVisibility — called
+ /// early in the constructor (via ApplyAsioMode), BEFORE the Input/Output tab populates the send-mode
+ /// list — set SelectedIndex on an EMPTY ListBox and threw ArgumentOutOfRangeException, crashing the app
+ /// at launch. On Windows 10/11 the branch is skipped (process-loopback IS supported), which hid the bug
+ /// from the gate. This forces the unsupported path so the crash is reproduced (and now prevented) on a
+ /// Win10/11 test box.
+ private static string? Win7SendModeConstruction()
+ {
+ var prev = RemSound.Sender.ProcessLoopbackCapture.ForceSupportedForTest;
+ RemSound.Sender.ProcessLoopbackCapture.ForceSupportedForTest = false; // pretend we're on Windows 7
+ try
+ {
+ MainForm? mf = null;
+ Exception? ctorEx = null;
+ try { mf = new MainForm(null, RemSound.Core.Profile.NewBlank(), null, null, headless: true); }
+ catch (Exception ex) { ctorEx = ex; }
+ finally { mf?.Dispose(); }
+
+ Check(ctorEx is null,
+ $"constructing the main window with process-loopback unsupported (the Win7 path) must not throw — got {ctorEx?.GetType().Name}: {ctorEx?.Message}");
+ return "the main window constructs cleanly with process-loopback unsupported (Win7 send-mode path)";
+ }
+ finally { RemSound.Sender.ProcessLoopbackCapture.ForceSupportedForTest = prev; }
+ }
+
private static int FreeUdpPort()
{
using var s = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork,
diff --git a/src/RemSound.Sender/ProcessLoopbackCapture.cs b/src/RemSound.Sender/ProcessLoopbackCapture.cs
index 4ea6f44..d4b63b1 100644
--- a/src/RemSound.Sender/ProcessLoopbackCapture.cs
+++ b/src/RemSound.Sender/ProcessLoopbackCapture.cs
@@ -3,6 +3,9 @@ using System.Threading;
using NAudio.CoreAudioApi;
using NAudio.Wave;
+// Lets the in-app self-tests (assembly "RemSound") flip ForceSupportedForTest to exercise the Win7 paths.
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("RemSound")]
+
namespace RemSound.Sender;
///
@@ -59,9 +62,14 @@ public sealed class ProcessLoopbackCapture : IWaveIn
includeTree = includeProcessTree;
}
+ /// Test-only override: when set, returns this instead of the real OS
+ /// check, so a self-test running on Windows 10/11 can exercise the Windows-7 (unsupported) code paths —
+ /// e.g. the send-mode UI that crashed at launch on Win7 (issue #22). Null = use the real OS check.
+ internal static bool? ForceSupportedForTest;
+
/// True on Windows builds new enough for the process-loopback API (10.0.19041+).
public static bool IsSupported =>
- OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041);
+ ForceSupportedForTest ?? OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041);
public void StartRecording()
{