-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppSettings.cs
More file actions
193 lines (157 loc) · 10.3 KB
/
Copy pathAppSettings.cs
File metadata and controls
193 lines (157 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace SDRIQStreamer.App;
/// <summary>
/// App-wide appearance variant (issue #63). Maps one-to-one onto Avalonia's
/// <c>ThemeVariant.Light</c> / <c>ThemeVariant.Dark</c>; there is deliberately no
/// "follow the OS" member.
/// </summary>
public enum AppTheme
{
Light,
Dark
}
/// <summary>
/// Persisted application settings (serialized to JSON).
/// </summary>
public sealed class AppSettings
{
// ── Operating mode (issue #28) ────────────────────────────────────────────
// Last selected top-level mode ("Cw" or "Digital"); restored on next launch.
public string LastMode { get; set; } = "Cw";
// ── CW Skimmer paths ──────────────────────────────────────────────────────
public string CwSkimmerExePath { get; set; } = string.Empty;
public string CwSkimmerIniPath { get; set; } = string.Empty;
// ── Digital engine paths (issue #28) ──────────────────────────────────────
// WSJT-X / JTDX executables. JTDX embeds a version in its path
// (e.g. ...\159\...), so the default is a starting point the operator can
// change on the Digital Config tab via Browse.
public string WsjtXExePath { get; set; } = @"C:\WSJT\wsjtx\bin\wsjtx.exe";
public string JtdxExePath { get; set; } = @"C:\JTDX64\159\bin\jtdx.exe";
// WSJT-Z is a WSJT-X fork whose own executable is named wsjtx.exe and which
// shares the WSJT-X config root (verified 2026-06-11).
public string WsjtZExePath { get; set; } = @"C:\WSJT\wsjtz\bin\wsjtx.exe";
// The one active digital engine ("WsjtX", "Jtdx", or "WsjtZ"). The operator
// runs one at a time, not several; switching engines is a config change. All
// slices launch the active engine.
public string DigitalActiveEngine { get; set; } = "WsjtX";
// Operator identity + recommended defaults seeded into each per-slice
// digital config. MyCall / MyGrid are prepopulated from an existing
// FlexRadio WSJT-X/JTDX profile when present, else entered by the operator.
public string DigitalMyCall { get; set; } = string.Empty;
public string DigitalMyGrid { get; set; } = string.Empty;
public string DigitalRig { get; set; } = "FlexRadio 6xxx";
// Per-slice DAX RX audio channel + CAT/UDP ports. Pre-set to recommended
// defaults (Slice A = DAX RX 1 / CAT 60000 / UDP 2237, B = 2/60001/2238,
// ...) but editable per slice on the Digital Config tab. Empty on first run
// -> seeded with defaults by the ViewModel. TX audio is the shared DAX TX
// (not per slice).
public List<DigitalSliceBinding> DigitalSliceBindings { get; set; } = [];
// ── Timing ────────────────────────────────────────────────────────────────
public int ConnectDelaySeconds { get; set; } = 5;
public int LaunchDelaySeconds { get; set; } = 3;
// ── Session identity ──────────────────────────────────────────────────────
public string Callsign { get; set; } = string.Empty;
public int TelnetPortBase { get; set; } = 7300;
public bool TelnetClusterEnabled { get; set; } = true;
// ── Spot forwarding ───────────────────────────────────────────────────────
public bool SpotForwardingEnabled { get; set; } = true;
public int SpotLifetimeSeconds { get; set; } = 300;
public string SpotColor { get; set; } = "#FF00FFFF";
public string SpotBackgroundColor { get; set; } = "#00000000";
// ── Logging (issue #58) ───────────────────────────────────────────────────
// Symptom (reported 2026-07): logs grew unbounded in the field (~133 MB
// over 104 days), 97% of it per-spot chatter logged three times. Root
// cause: every log writer was unconditional. Fix: per-spot payload lines,
// telnet spot/QSY echoes, and DAX-IQ stream churn log only when this is
// true; lifecycle and error lines always log. One bool chosen over log
// levels as the smallest surface. Startup rotation (LogFiles) is separate
// and unconditional.
public bool DebugLoggingEnabled { get; set; }
// ── Update checks ──────────────────────────────────────────────────────────
public bool UpdateAutoCheckEnabled { get; set; } = true;
public int UpdateCheckIntervalMinutes { get; set; } = 30;
public DateTime? UpdateLastCheckedUtc { get; set; }
// ── Main window placement ─────────────────────────────────────────────────
public double? MainWindowX { get; set; }
public double? MainWindowY { get; set; }
public double? MainWindowWidth { get; set; }
public double? MainWindowHeight { get; set; }
// ── Per-channel CW Skimmer device indices (operator-supplied) ────────────
// 1-based UI numbers as shown in CW Skimmer's Audio tab dropdowns.
// Null = auto-derive at launch (current behavior).
public int? MmeDeviceIndexCh1 { get; set; }
public int? MmeDeviceIndexCh2 { get; set; }
public int? MmeDeviceIndexCh3 { get; set; }
public int? MmeDeviceIndexCh4 { get; set; }
public int? WdmDeviceIndexCh1 { get; set; }
public int? WdmDeviceIndexCh2 { get; set; }
public int? WdmDeviceIndexCh3 { get; set; }
public int? WdmDeviceIndexCh4 { get; set; }
/// <summary>
/// Set to true once the operator has been shown the Reset/Setup wizard at
/// least once after configuring both CW Skimmer paths. Prevents the wizard
/// from re-opening on subsequent launches.
/// </summary>
public bool HasShownSkimmerSetupWizard { get; set; }
/// <summary>
/// CW Skimmer Soundcard Driver mode applied to all generated channel INIs.
/// "MME" (default, recommended) or "WDM" (experimental, requires per-channel
/// indices on PCs where WDM ordering differs from auto-derivation).
/// </summary>
public string SkimmerSoundcardDriverMode { get; set; } = "MME";
// ── Audio-index change-detection baselines (issue #38) ────────────────────
// Records the auto-derived MME DAX-IQ device indices observed at the last
// clean startup. Compared against the live probe on each session to
// detect index shifts caused by SmartSDR / DAX upgrades (e.g. the DAX
// v1 -> v2 renaming in SmartSDR 4.2). Separate from the operator-supplied
// MmeDeviceIndexCh1..4 wizard values, which capture operator intent;
// these track system state.
//
// MME only: WDM index detection is not feasible. CW Skimmer's WDM
// Audio tab uses a private/filtered enumeration that does not match
// either WinMM or DirectSound order (verified Dallas 2026-05-18 —
// CW Skimmer's WDM list had DAX IQ 1 at slot 4 while both WinMM and
// DirectSound put it at slot 13/14). No reliable WDM probe exists from
// outside CW Skimmer; the operator must verify WDM values manually
// after a SmartSDR upgrade.
public int? LastSeenMmeDeviceIndexCh1 { get; set; }
public int? LastSeenMmeDeviceIndexCh2 { get; set; }
public int? LastSeenMmeDeviceIndexCh3 { get; set; }
public int? LastSeenMmeDeviceIndexCh4 { get; set; }
// ── Appearance (issue #63) ───────────────────────────────────────────────
// Two named options, not three: "follow the OS" is a third behaviour to
// explain, test and reason about, and this is a tool configured once and
// then left alone rather than something that should shift underneath the
// operator. Tradeoff accepted: an operator whose desktop is dark gets a
// light app on first launch until they press the Theme button.
//
// Persisted by name so reordering AppTheme cannot silently remap a saved
// setting; the settings store has no global string-enum converter.
[JsonConverter(typeof(JsonStringEnumConverter))]
public AppTheme ThemeMode { get; set; } = AppTheme.Light;
// ── SmartDeck window (issue #59) ─────────────────────────────────────────
// Nullable: null means "never positioned", which is distinct from a real
// 0 coordinate on a multi-monitor desktop where 0,0 is a valid position.
//
// Height is deliberately absent (issue #63): the deck sizes itself to its
// content, so persisting height only preserved dead space and made every
// layout change invisible to anyone who had already run the deck. An older
// settings file still carrying SmartDeckHeight is simply ignored on read.
public double? SmartDeckX { get; set; }
public double? SmartDeckY { get; set; }
public double? SmartDeckWidth { get; set; }
public bool SmartDeckAlwaysOnTop { get; set; }
// Per-band state memory for the SmartDeck band buttons (issue #59 phase 2b,
// widened past frequency to mode, both antennas and AGC-T on 2026-08-03),
// keyed by band label ("20m"). Persisted rather than session-scoped: an
// operator who sets 20m to their CW spot expects the button to return there
// next session, which is the whole point of band memory. Empty until the
// operator first leaves a band.
//
// Replaces SmartDeckBandMemoryMhz, which held bare frequencies. No
// migration by operator's choice: the old key is simply left unread in
// existing settings files, and each band re-learns on its first departure.
public Dictionary<string, BandState> SmartDeckBandMemory { get; set; } = [];
}