From c18d79ba5a2fa40ece151c0edb2371e1210ff526 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 23 Oct 2026 21:56:47 +0000 Subject: [PATCH] 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 --- .gitignore | 1 + README.md | 38 ++++++++++-- docs/digital-bridge.md | 24 +++++++- publish-windows.sh | 15 +++++ src/Nonemm.App/AppSession.cs | 6 +- src/Nonemm.App/Dialogs/DigitalDialog.axaml | 6 +- src/Nonemm.App/Dialogs/DigitalDialog.axaml.cs | 24 ++++++-- ...idgeChannel.cs => ProcessBridgeChannel.cs} | 30 ++++++---- src/Nonemm.Digital/WindowsControlSetup.cs | 58 +++++++++++++++++++ .../WindowsControlSetupTests.cs | 34 +++++++++++ tools/Nonemm.EngineProbe/Program.cs | 2 +- 11 files changed, 211 insertions(+), 27 deletions(-) create mode 100755 publish-windows.sh rename src/Nonemm.Digital/{WineBridgeChannel.cs => ProcessBridgeChannel.cs} (88%) create mode 100644 src/Nonemm.Digital/WindowsControlSetup.cs create mode 100644 tests/Nonemm.Digital.Tests/WindowsControlSetupTests.cs diff --git a/.gitignore b/.gitignore index 33d6544..f2bb995 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ obj/ /mmtty/ /bridge/XMMT.ocx engine-probe.log +/dist/ diff --git a/README.md b/README.md index 7a4e01b..09d415b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,36 @@ Everything that judges a station — the bar under the callsign box, a row in th check window, a spot on the bandmap, a line in the log — is coloured by the same scorer: red for a dupe, green for a new multiplier, blue for points. +## Windows + +Build the Windows copy on any machine that has the .NET 10 SDK: + +```sh +./publish-windows.sh # writes dist/nonemm-win-x64/ and dist/nonemm-win-x64.zip +``` + +On Windows itself, run this from PowerShell instead: + +```powershell +dotnet publish src\Nonemm.App -c Release -r win-x64 --self-contained -o dist\nonemm-win-x64 +``` + +The result needs no .NET install. Unzip it anywhere you can write to and run +`Nonemm.App.exe`. Files go in `Documents\N1MM Logger+` and `Documents\Nonemm`, +or in the folder `NONEMM_HOME` names. + +What differs from Linux: + +| Part | On Windows | +| --- | --- | +| MMTTY and 2Tone | The bridge starts directly, with no Wine. Register `bridge\XMMT.ocx` once with the **Register** button in the digital settings. Windows asks for administrator rights, because the control is written to the registry. Keep the file where it is afterwards. See `docs/digital-bridge.md` | +| CW | `cwdaemon` has no Windows build. Use a WinKeyer on its COM port | +| Radio | Start `rigctld` from the Windows hamlib build, with a COM port: `rigctld -m 2028 -r COM3` | +| Voice playback | Plays through PowerShell's `Media.SoundPlayer`. The command is a setting | +| Voice recording | Windows has no recorder to call. Install `sox` or `ffmpeg` and put its command in the keyer settings | + +No part of this has been run on Windows yet. The tests pass on Linux only. + ## What it does | | | @@ -56,13 +86,13 @@ scorer: red for a dupe, green for a new multiplier, blue for points. | Bandmap | drawn like N1MM's: a frequency scale with the receiver on it and callsigns beside it, joined by leader lines | | Network | contacts shared with the other stations of a multi-operator entry, in N1MM's own contact message | | Keying | CW through `cwdaemon` or a WinKeyer, with N1MM's message macros; phone through the voice keyer, which plays each operator's own recordings | -| Digital | N1MM's digital interface window, running MMTTY or 2Tone under Wine through the same `XMMT.ocx` N1MM drives them with | +| Digital | N1MM's digital interface window, running MMTTY or 2Tone (under Wine on Linux, directly on Windows) through the same `XMMT.ocx` N1MM drives them with | ### The digital window **Window ▸ Digital Interface** opens N1MM's digital interface. **Engine ▸ -Settings** says where MMTTY or 2Tone is; **Engine ▸ Start** runs it under Wine. -`docs/digital-bridge.md` covers setting the Wine prefix up. +Settings** says where MMTTY or 2Tone is; **Engine ▸ Start** runs it: under Wine on Linux, directly on Windows. +`docs/digital-bridge.md` covers setting up the Wine prefix or, on Windows, registering the control. What the window does, and what each part of it is for: @@ -1040,7 +1070,7 @@ windows redraw where they stand. | `Nonemm.Spotting` | Spots, the bandmap, the DX cluster client | | `Nonemm.Network` | Contacts shared between the stations of a multi-operator entry | | `Nonemm.Keying` | CW through `cwdaemon` or a WinKeyer, and the voice keyer's recordings | -| `Nonemm.Digital` | MMTTY and 2Tone, over the Wine bridge in `bridge/` | +| `Nonemm.Digital` | MMTTY and 2Tone, over the bridge in `bridge/` | | `Nonemm.Session` | The contest in progress, and one entry position per radio — no UI toolkit | | `Nonemm.App` | The Avalonia windows | diff --git a/docs/digital-bridge.md b/docs/digital-bridge.md index bbb7e3c..9ce1be5 100644 --- a/docs/digital-bridge.md +++ b/docs/digital-bridge.md @@ -1,9 +1,9 @@ -# MMTTY and 2Tone through Wine +# MMTTY and 2Tone through the bridge MMTTY is a Windows program with no socket, pipe or file interface. N1MM does not talk to it directly either: it hosts `XMMT.ocx`, a 32-bit ActiveX control that starts `MMTTY.EXE` and exchanges window messages with it. A Linux process cannot -load that control, so Nonemm runs a small Windows program under Wine — +load that control, so Nonemm runs a small Windows program (under Wine on Linux) — `bridge/nonemm-mmtty-bridge.exe` — which hosts the control and passes lines over its standard input and output. @@ -98,7 +98,25 @@ Three things cost a day between them, so they are written down: message, closes the engine's windows, and kills the process if it is still there five seconds later. -## Setting it up +## Setting it up on Windows + +There is no Wine. `ProcessBridgeChannel` starts `nonemm-mmtty-bridge.exe` +directly, and the engine path goes to the bridge as it is. + +The control must be registered once. The Register button in Config > Digital +does it with `WindowsControlSetup`, which runs the 32-bit `regsvr32` (the one in +`SysWOW64` on 64-bit Windows) on `XMMT.ocx` in place. Windows shows the +administrator prompt, because registering writes to `HKEY_CLASSES_ROOT`. The same +by hand, from an administrator prompt: + +```bat +%WINDIR%\SysWOW64\regsvr32.exe bridge\XMMT.ocx +``` + +Do not move `XMMT.ocx` after that, or register it again. Nothing in this path +has been run on Windows. + +## Setting it up under Wine Config > Digital has a Register button. It does what the next paragraph describes, in the prefix named in the same dialog, and writes the result under diff --git a/publish-windows.sh b/publish-windows.sh new file mode 100755 index 0000000..24b56e6 --- /dev/null +++ b/publish-windows.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Builds a self-contained Windows x64 copy in dist/nonemm-win-x64 and zips it. +# The target machine needs no .NET install. Pass another runtime id as the +# first argument, for example win-arm64 (the bridge and XMMT.ocx are 32 bit +# x86 either way). +rid="${1:-win-x64}" +cd "$(dirname "$0")" || exit 1 +out="dist/nonemm-$rid" +rm -rf "$out" +./build.sh publish src/Nonemm.App -c Release -r "$rid" --self-contained -o "$out" || exit 1 +rm -f "$out"/*.pdb +rm -f "dist/nonemm-$rid.zip" +python3 -c 'import shutil, sys; shutil.make_archive(sys.argv[1], "zip", "dist", sys.argv[2])' \ + "dist/nonemm-$rid" "nonemm-$rid" || exit 1 +echo "dist/nonemm-$rid.zip" diff --git a/src/Nonemm.App/AppSession.cs b/src/Nonemm.App/AppSession.cs index 2fa1330..94d523f 100644 --- a/src/Nonemm.App/AppSession.cs +++ b/src/Nonemm.App/AppSession.cs @@ -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(); diff --git a/src/Nonemm.App/Dialogs/DigitalDialog.axaml b/src/Nonemm.App/Dialogs/DigitalDialog.axaml index 0d4cc05..081754e 100644 --- a/src/Nonemm.App/Dialogs/DigitalDialog.axaml +++ b/src/Nonemm.App/Dialogs/DigitalDialog.axaml @@ -124,7 +124,7 @@ - @@ -136,13 +136,13 @@