Run the digital bridge on Windows

The bridge launched its helper only through Wine. It now starts any
process, so on Windows it runs the helper directly. WineBridgeChannel is
renamed ProcessBridgeChannel.

WindowsControlSetup sets up the control side on Windows. The digital
dialog, AppSession, README and docs/digital-bridge.md describe the
Windows setup. publish-windows.sh builds a Windows release.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 21:56:47 +00:00
co-authored by Claude Sonnet 5.5
parent 0ad894bfae
commit c18d79ba5a
11 changed files with 211 additions and 27 deletions
+4 -2
View File
@@ -699,10 +699,12 @@ public sealed class AppSession : IDisposable
OnTop = Settings.DigitalEngineOnTop,
PttPort = Settings.DigitalPttPort.Trim().Length > 0 ? Settings.DigitalPttPort : null,
};
WineBridgeChannel channel = new(
ProcessBridgeChannel channel = new(
Settings.DigitalBridgePath,
Settings.DigitalWinePrefix.Trim().Length > 0 ? Settings.DigitalWinePrefix : null,
Settings.DigitalWineCommand.Trim().Length > 0 ? Settings.DigitalWineCommand : "wine",
OperatingSystem.IsWindows()
? null
: Settings.DigitalWineCommand.Trim().Length > 0 ? Settings.DigitalWineCommand : "wine",
Paths.Diagnostics);
MmttyEngine started = new(channel, options);
await started.StartAsync();
+3 -3
View File
@@ -124,7 +124,7 @@
<TabItem Header="Engine">
<ScrollViewer>
<StackPanel Margin="4,8,10,4">
<TextBlock TextWrapping="Wrap" FontSize="11" Opacity="0.75"
<TextBlock Name="EngineNote" TextWrapping="Wrap" FontSize="11" Opacity="0.75"
Text="MMTTY and 2Tone are Windows programs. They run under Wine, driven through the same XMMT.ocx control N1MM uses, with a small bridge program in between. See docs/digital-bridge.md for setting the Wine prefix up." />
<TextBlock Classes="label" Text="Engine program (mmtty.exe or 2Tone.exe)" />
<Grid ColumnDefinitions="*,6,Auto">
@@ -136,13 +136,13 @@
<TextBox Name="BridgeBox" />
<Button Grid.Column="2" Content="Browse…" Click="OnBrowseBridge" />
</Grid>
<Grid ColumnDefinitions="*,8,160" RowDefinitions="Auto,Auto">
<Grid Name="WineRow" ColumnDefinitions="*,8,160" RowDefinitions="Auto,Auto">
<TextBlock Classes="label" Text="Wine prefix (empty for Wine's own)" />
<TextBox Name="PrefixBox" Grid.Row="1" />
<TextBlock Grid.Column="2" Classes="label" Text="Wine command" />
<TextBox Name="WineBox" Grid.Row="1" Grid.Column="2" />
</Grid>
<TextBlock Classes="label" Text="Control (XMMT.ocx), registered in the prefix above" />
<TextBlock Name="ControlLabel" Classes="label" Text="Control (XMMT.ocx), registered in the prefix above" />
<Grid ColumnDefinitions="*,6,Auto,6,Auto">
<TextBox Name="ControlBox" />
<Button Grid.Column="2" Content="Browse…" Click="OnBrowseControl" />
+20 -4
View File
@@ -106,6 +106,14 @@ public sealed partial class DigitalDialog : Window
ControlBox.Text = settings.DigitalControlPath;
PrefixBox.Text = settings.DigitalWinePrefix;
WineBox.Text = settings.DigitalWineCommand;
if (OperatingSystem.IsWindows())
{
WineRow.IsVisible = false;
EngineNote.Text = "MMTTY and 2Tone are driven through the same XMMT.ocx control N1MM uses, "
+ "with a small bridge program in between. Register the control once, before the first start. "
+ "See docs/digital-bridge.md.";
ControlLabel.Text = "Control (XMMT.ocx), registered where it is. Keep the file there";
}
PttBox.Text = settings.DigitalPttPort;
}
@@ -251,12 +259,20 @@ public sealed partial class DigitalDialog : Window
{
RegisterButton.IsEnabled = false;
Say("Registering…");
WinePrefixSetup setup = new(
PrefixBox.Text?.Trim() is { Length: > 0 } prefix ? prefix : null,
WineBox.Text?.Trim() is { Length: > 0 } wine ? wine : "wine");
string control = ControlBox.Text?.Trim() ?? "";
try
{
Say(await setup.RegisterAsync(ControlBox.Text?.Trim() ?? ""));
if (OperatingSystem.IsWindows())
{
Say(await new WindowsControlSetup().RegisterAsync(control));
}
else
{
WinePrefixSetup setup = new(
PrefixBox.Text?.Trim() is { Length: > 0 } prefix ? prefix : null,
WineBox.Text?.Trim() is { Length: > 0 } wine ? wine : "wine");
Say(await setup.RegisterAsync(control));
}
}
catch (Exception failure) when (failure is IOException or InvalidOperationException)
{