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 @@