Add the digital interface, running MMTTY under Wine
MMTTY and 2Tone have no socket or pipe interface: N1MM hosts XMMT.ocx and exchanges window messages with the engine. A Linux process cannot load that control, so bridge/nonemm-mmtty-bridge.exe hosts it under Wine and passes lines over its standard input and output. docs/digital-bridge.md states the protocol and what the control needs. The window is N1MM's: receive pane with coloured callsigns, grab list, call stacking, twenty-four macro buttons and the engine controls. One left click copies what is under it — a callsign to the callsign box, anything else to the exchange box the contest keeps for that kind of value. Config > Digital registers XMMT.ocx in the Wine prefix on its own, making the prefix first if it is not there. The engine, the bridge and the control start at the copies shipped beside the program. The bridge reads the control's own events. OnTranslateMessage carries only the messages the control has no event for, so nothing was ever decoded through it. hamlib's data mode names read as digital modes now, and a mode typed into the callsign box changes mode the way a frequency changes band, so a station with no radio can reach RTTY at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
This commit is contained in:
@@ -186,6 +186,79 @@ public sealed record Settings
|
||||
|
||||
public IReadOnlyList<string> PhoneMessages { get; init; } = [];
|
||||
|
||||
/// The digital window's twenty-four macro buttons, in the same
|
||||
/// `label,message` lines as a function key file. Empty means the built-in
|
||||
/// set.
|
||||
public string DigitalMessageFile { get; init; } = "";
|
||||
|
||||
public bool DigitalEnabled { get; init; }
|
||||
|
||||
/// The engine to run: MMTTY or 2Tone. The path is a Linux path; it is
|
||||
/// handed to Wine in the form Wine expects. It starts at the copy shipped
|
||||
/// beside the program.
|
||||
public string DigitalEnginePath { get; init; } = ProgramFile("mmtty", "mmtty.exe");
|
||||
|
||||
public string DigitalBridgePath { get; init; } =
|
||||
ProgramFile("bridge", "nonemm-mmtty-bridge.exe");
|
||||
|
||||
/// The XMMT.ocx the bridge hosts, as it sits beside the program. It is
|
||||
/// only read when the control is registered in the Wine prefix; after that
|
||||
/// the copy in the prefix is the one that is loaded.
|
||||
public string DigitalControlPath { get; init; } = ProgramFile("bridge", "XMMT.ocx");
|
||||
|
||||
/// Empty means Wine's own default prefix.
|
||||
public string DigitalWinePrefix { get; init; } = "";
|
||||
|
||||
public string DigitalWineCommand { get; init; } = "wine";
|
||||
|
||||
/// `Normal`, `Small`, `Medium1` or `Medium2`, which are MMTTY's four
|
||||
/// window sizes.
|
||||
public string DigitalEngineWindow { get; init; } = "Normal";
|
||||
|
||||
public bool DigitalEngineOnTop { get; init; } = true;
|
||||
|
||||
/// The port MMTTY keys FSK and PTT on. Empty reads it from Mmtty.INI, which
|
||||
/// is where MMTTY itself keeps it.
|
||||
public string DigitalPttPort { get; init; } = "";
|
||||
|
||||
/// The mark tone the Align button moves the engine to. MMTTY's own default.
|
||||
public int DigitalMarkHertz { get; init; } = 2125;
|
||||
|
||||
/// Colour the callsign itself, or the ground behind it. N1MM offers both.
|
||||
public bool DigitalHighlightBackground { get; init; }
|
||||
|
||||
/// `everything`, `notdupes` or `knowncalls`.
|
||||
public string DigitalGrabFilter { get; init; } = "everything";
|
||||
|
||||
public bool DigitalGrabNewestFirst { get; init; } = true;
|
||||
|
||||
/// `multipliers`, `firstin`, `lastin` or `disabled`.
|
||||
public string DigitalCallStacking { get; init; } = "disabled";
|
||||
|
||||
/// N1MM's right-click option: a right click in the receive pane presses
|
||||
/// Enter in the entry window instead of opening the menu.
|
||||
public bool DigitalRightClickSendsEnter { get; init; }
|
||||
|
||||
/// With the right click sending Enter, whether the call stays in the box
|
||||
/// afterwards. N1MM's "don't drop call".
|
||||
public bool DigitalRightClickKeepsCall { get; init; }
|
||||
|
||||
/// A grabbed call is followed by a space, which moves to the exchange and
|
||||
/// fills the report in, the way typing one does.
|
||||
public bool DigitalGrabSendsSpace { get; init; } = true;
|
||||
|
||||
/// N1MM caps the receive pane's font at 14 points.
|
||||
public int DigitalFontSize { get; init; } = 12;
|
||||
|
||||
/// A file shipped beside the program. MMTTY and the bridge are distributed
|
||||
/// with Nonemm, so the paths are filled in already; a file that is not
|
||||
/// there leaves the setting empty and the operator browses for it.
|
||||
private static string ProgramFile(string folder, string name)
|
||||
{
|
||||
string path = Path.Combine(AppContext.BaseDirectory, folder, name);
|
||||
return File.Exists(path) ? path : "";
|
||||
}
|
||||
|
||||
/// Reflection rather than a generated serializer: the generated one hands
|
||||
/// back null for every property the file leaves out instead of the value
|
||||
/// the property is declared with.
|
||||
@@ -322,6 +395,21 @@ public sealed record Settings
|
||||
PhoneMessages = [],
|
||||
};
|
||||
}
|
||||
if (settings.DigitalEnginePath.Length == 0)
|
||||
{
|
||||
settings = settings with { DigitalEnginePath = ProgramFile("mmtty", "mmtty.exe") };
|
||||
}
|
||||
if (settings.DigitalBridgePath.Length == 0)
|
||||
{
|
||||
settings = settings with
|
||||
{
|
||||
DigitalBridgePath = ProgramFile("bridge", "nonemm-mmtty-bridge.exe"),
|
||||
};
|
||||
}
|
||||
if (settings.DigitalControlPath.Length == 0)
|
||||
{
|
||||
settings = settings with { DigitalControlPath = ProgramFile("bridge", "XMMT.ocx") };
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user