Skip to content
Merged

2vfo #81

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
a644207
document current state of the VFO architecture
ftl May 6, 2026
5f6ada1
Use the generic Emit function
ftl May 6, 2026
30ee458
define identifiers for two VFOs
ftl May 6, 2026
46d9c86
bump hl-go to v1.0.4
ftl May 9, 2026
7eb682f
add constant for the number of VFOs managed by hellocontest
ftl May 9, 2026
5c490bf
add a representation for the Hamlib names of the two supported VFOs
ftl May 9, 2026
4e9d8b3
extract the connection setup
ftl May 9, 2026
7554087
poll and store the state of both VFOs
ftl May 9, 2026
debdb84
keep track of the current VFO and notify listeners
ftl May 9, 2026
29841d2
only poll the current VFO, ignore its state when the current VFO is not
ftl May 9, 2026
d5d21d6
poll all VFOs when starting
ftl May 9, 2026
2b85a4c
bump hl-go to v1.0.6
ftl May 10, 2026
42ed1a7
remove debug log
ftl May 10, 2026
088f805
use GetRigInfo to poll all available VFOs at once, distinguish between
ftl May 10, 2026
1dd7e4e
handle broken TCP connection to a Hamlib radio gracefully
ftl May 10, 2026
34c55a9
emit the VFO events for both VFOs
ftl May 10, 2026
78814b2
keep track of two VFOs on all core layers
ftl May 10, 2026
30dd30b
show the second VFO in a separate row below the entry area for VFO1
ftl May 10, 2026
3dd72f1
define the ConnectionChangedListener publicly and remove its concurrent
ftl May 14, 2026
5617058
use combo boxes to show band and mode of the 2nd VFO
ftl May 14, 2026
0ec0d32
manage input values for each VFO
ftl May 14, 2026
6128b49
control mode and band for each VFO indiviually
ftl May 14, 2026
7604f51
remove debug log
ftl May 14, 2026
5f38526
fix typo
ftl May 16, 2026
24895ce
update architecture doc
ftl May 16, 2026
a2cbf04
expose the single-vfo flag for both hamlib and tci radios
ftl May 17, 2026
1eecbff
expose the SetCurrentVFO method to the clients
ftl May 17, 2026
a318a64
handle data input for two VFOs in the entry Controller
ftl May 17, 2026
b2695cd
handle callinfo for two VFOs
ftl May 17, 2026
83fae2d
show the widgets for VFO2 entry
ftl May 17, 2026
0278294
wire events for two VFOs
ftl May 17, 2026
27bf244
adapt the configuration
ftl May 17, 2026
30a7811
fix hotkey handling to switch VFOs
ftl May 17, 2026
0a1f09a
apply UI thread confinement for the VFO event handlers
ftl May 17, 2026
9ae20bd
remove implementation tasks
ftl May 17, 2026
5ced15d
make sure the exchange fields for VFO2 are hidden when the radio is a
ftl May 17, 2026
ae6acea
update architecture
ftl May 20, 2026
a5c7c50
add a black box test harness for the entry controller
ftl Jun 4, 2026
0a3d0df
extract the serial number handling into a separate type
ftl Jun 4, 2026
18b9250
update the current state of the VFO architecture document
ftl Jun 4, 2026
5e29844
fix the corrupted update handling for mode and band
ftl Jun 4, 2026
a6d0d4c
show the predicted exchange, supercheck callinfo and message for VFO2
ftl Jun 4, 2026
7579754
hamlib: set the split mode and the TX VFO depending on the currently
ftl Jun 6, 2026
18d1953
tci: set the split mode depending on the currently focused VFO
ftl Jun 6, 2026
3da0142
move my QSO data out of the VFO-specific input data
ftl Jun 6, 2026
60d3b9b
switch the TX VFO only right before transmitting
ftl Jun 6, 2026
5cf6048
allow to optionally switch the TX VFO when changing the focus
ftl Jun 6, 2026
047be7d
use require instead of t.FailNow()
ftl Jun 6, 2026
4af1dae
when the frequency changes, clear the entry field of the corresponding
ftl Jun 6, 2026
bb7652d
also clear the callinfo data when the entry fields of a VFO are cleared
ftl Jun 6, 2026
9491a48
make sure the focused VFO does not change due to frequency changes in
ftl Jun 6, 2026
7fe23b5
show the claimed serial number for each VFO
ftl Jun 6, 2026
ee15c08
add actions to control the audio for each VFO
ftl Jun 6, 2026
a275b4f
make the audio control work
ftl Jun 6, 2026
f9d2e84
synchronize the current VFO between the radio and the application
ftl Jun 7, 2026
f5a302c
fix hamlib detail: use currVFO when toggling the split flag
ftl Jun 7, 2026
ffc2398
add a horizontal separator between the VFOs
ftl Jun 7, 2026
8db6951
visually indicate the currently active VFO
ftl Jun 7, 2026
ecef46d
remove unused whitespace
ftl Jun 7, 2026
9fdc4a6
clean-up widget creation
ftl Jun 11, 2026
732dac5
remove border from VFO label
ftl Jun 14, 2026
23cd749
fix the row for serialClaimLabel
ftl Jun 14, 2026
8662e35
update architecture document
ftl Jun 14, 2026
708dba8
extend and update the test harness
ftl Jun 14, 2026
566aa75
remove the "TX follows focus" option
ftl Jun 14, 2026
b72c8f2
add threshold for hamlib PTT state
ftl Jun 14, 2026
e7693ee
increase PTT threshold
ftl Jun 14, 2026
47ebce1
get indication if the keyer has sent a serial number
ftl Jun 14, 2026
dca1cf4
commit claimed serial numbers only if they were actually sent over the
ftl Jun 14, 2026
efd59c9
add a VFO parameter to the WorkmodeChanged event
ftl Jun 14, 2026
29892b9
handle the per-VFO workmode
ftl Jun 14, 2026
0dd1394
show the current per-VFO workmode in the VFO label
ftl Jun 14, 2026
7f31e7c
track the focused VFO in the workmode component
ftl Jun 14, 2026
bb4ab9c
handle the CallsignEntered event VFO-dependent in Parrot
ftl Jun 14, 2026
2171d90
notify Parrot when transmissions start
ftl Jun 14, 2026
e0b7fab
starting the Parrot clears only the VFO1 inputs
ftl Jun 14, 2026
3c173b1
bugfix: switch macro workmode
ftl Jun 14, 2026
da26091
update docs
ftl Jun 14, 2026
4be4cd3
fix creation of VFO2 widgets in separate windows
ftl Jun 14, 2026
8fe4f9e
set the TX vfo when starting the Parrot
ftl Jun 14, 2026
ab8f1f0
try to prevent VFO switch on Parrot transmission
ftl Jun 14, 2026
6750045
switch the split mode without changing the current VFO
ftl Jun 14, 2026
8991f24
make sure the Parrot is always using the Run macros
ftl Jun 14, 2026
a22bf82
update the docs
ftl Jun 14, 2026
d34df0b
emit when the TX VFO changes
ftl Jul 11, 2026
a4695f7
emit when the TX VFO changes in TCI
ftl Jul 11, 2026
2fe3f59
remove log
ftl Jul 11, 2026
115d796
track the current TX VFO in entry
ftl Jul 11, 2026
b90660d
show the TX VFO indicator in the UI
ftl Jul 11, 2026
434d4a7
stop the parrot on any manual transmission
ftl Jul 11, 2026
888e858
remove parent from file dialogs to make them floating
ftl Jul 11, 2026
94b3307
make sure the TX VFO follows the current VFO when actually sending
ftl Jul 11, 2026
a437fa9
make QTC work in SO2V workmode
ftl Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion core/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,21 @@ const (
ActionEntryWorkmodeRun = "entry.workmode_run"
ActionEntryOfferQTC = "entry.offer_qtc"
ActionEntryRequestQTC = "entry.request_qtc"
ActionEntryToggleFocusedVFO = "entry.toggle_focused_vfo"
ActionEntryFocusVFO1 = "entry.focus_vfo1"
ActionEntryFocusVFO2 = "entry.focus_vfo2"
ActionEntryLogVFO1 = "entry.log_vfo1"
ActionEntryLogVFO2 = "entry.log_vfo2"
ActionEntryClearVFO1 = "entry.clear_vfo1"
ActionEntryClearVFO2 = "entry.clear_vfo2"

ActionRadioXITActive = "radio.xit_active"
ActionRadioXITActive = "radio.xit_active"
ActionRadioMuteAudioVFO1 = "radio.mute_audio_vfo1"
ActionRadioMuteAudioVFO2 = "radio.mute_audio_vfo2"
ActionRadioUnmuteAudioVFO1 = "radio.unmute_audio_vfo1"
ActionRadioUnmuteAudioVFO2 = "radio.unmute_audio_vfo2"
ActionRadioToggleAudioVFO1 = "radio.toggle_audio_vfo1"
ActionRadioToggleAudioVFO2 = "radio.toggle_audio_vfo2"

ActionBandmapMark = "bandmap.mark"
ActionBandmapGotoHighestValueSpot = "bandmap.goto_highest_value_spot"
Expand Down
86 changes: 68 additions & 18 deletions core/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type Controller struct {
callHistoryFinder *callhistory.Finder
hamDXMap *hamdxmap.HamDXMap

VFO *vfo.VFO
VFOs []*vfo.VFO
Logbook *logbook.Logbook
QSOList *logbook.QSOList
QTCList *logbook.QTCList
Expand Down Expand Up @@ -204,27 +204,42 @@ func (c *Controller) Startup() {

c.Workmode = workmode.NewController()
c.Workmode.Notify(c.Entry)
c.Entry.Notify(c.Workmode)
c.QSOList.Notify(c.Workmode)

c.VFO = vfo.NewVFO("VFO 1", c.bandplan, c.Logbook, c.asyncRunner)
c.Entry.SetVFO(c.VFO)
c.VFO.Notify(c.Bandmap)
c.Bandmap.SetVFO(c.VFO)
c.Logbook.Notify(c.VFO)
c.Workmode.Notify(c.VFO)
c.Callinfo = callinfo.New(c.dxccFinder, c.scpFinder, c.callHistoryFinder, c.Logbook, c.Logbook, c.Logbook)
c.Entry.SetCallinfo(c.Callinfo)
c.Callinfo.Notify(c.Entry)

c.Radio = radio.NewController(c.configuration.Radios(), c.configuration.Keyers(), c.bandplan)
c.Radio.Notify(c.ServiceStatus)
c.Radio.Notify(c.Entry)
c.Radio.Notify(c.Callinfo)
c.Radio.Notify(c.Workmode)
c.Entry.SetVFOSwitcher(c.Radio)
c.Bandmap.Notify(c.Radio) // TODO implement Entry... in radio.Controller
c.VFO.SetClient(c.Radio)

c.VFOs = make([]*vfo.VFO, core.VFOCount)
for vfoID := range len(c.VFOs) {
v := vfo.NewVFO(core.VFOID(vfoID), fmt.Sprintf("VFO %d", vfoID+1), c.bandplan, c.Logbook, c.asyncRunner)
v.SetClient(c.Radio)
c.VFOs[vfoID] = v
c.Entry.SetVFO(core.VFOID(vfoID), v)
c.Logbook.Notify(v)
}
c.Bandmap.SetVFO(c.VFOs[core.VFO1])
c.Workmode.Notify(c.VFOs[core.VFO1])

c.Radio.SetSendSpotsToTci(c.session.SendSpotsToTci())
c.Radio.SelectRadio(c.session.Radio1())
c.Radio.SelectKeyer(c.session.Keyer1())

c.Keyer = keyer.New(c.Settings, c.Radio, c.configuration.KeyerSettings(), c.Workmode.Workmode(), c.configuration.KeyerPresets())
c.Keyer.SetVFOSwitcher(c.Radio)
c.Keyer.SetValues(c.Entry.CurrentValues)
c.Keyer.Notify(c.ServiceStatus)
c.Keyer.Notify(c.Entry)
c.Entry.Notify(c.Keyer)
c.Workmode.Notify(c.Keyer)
c.Entry.SetKeyer(c.Keyer)

Expand All @@ -243,21 +258,18 @@ func (c *Controller) Startup() {
}

c.QTCController = qtc.NewController(c.clock, c, c.Logbook, c.QTCList, c.Entry, c.Keyer)
c.VFO.Notify(c.QTCController)

c.Rate = rate.NewCounter(c.clock, c.asyncRunner)
c.QSOList.Notify(logbook.QSOsClearedListenerFunc(c.Rate.Clear))
c.QSOList.Notify(logbook.QSOAddedListenerFunc(c.Rate.Add))

c.Callinfo = callinfo.New(c.dxccFinder, c.scpFinder, c.callHistoryFinder, c.Logbook, c.Logbook, c.Logbook)
c.Entry.SetCallinfo(c.Callinfo)
c.Callinfo.Notify(c.Entry)
c.Bandmap.SetCallinfo(c.Callinfo)
c.Bandmap.Notify(c.Callinfo)
c.Logbook.Notify(c.Callinfo)

c.Parrot = parrot.New(c.Workmode, c.Keyer, c.asyncRunner)
c.Keyer.SetParrot(c.Parrot)
c.Keyer.Notify(c.Parrot)
c.Workmode.Notify(c.Parrot)
c.Entry.Notify(c.Parrot)
c.Parrot.Notify(c.Entry)
Expand Down Expand Up @@ -852,11 +864,23 @@ func (c *Controller) RequestQTC() {
}

func (c *Controller) XITActive() bool {
return c.VFO.XITActive()
return c.VFOs[core.VFO1].XITActive()
}

func (c *Controller) SetXITActive(active bool) {
c.VFO.SetXITActive(active)
c.VFOs[core.VFO1].SetXITActive(active)
}

func (c *Controller) MuteAudio(vfo core.VFOID) {
c.VFOs[vfo].MuteAudio()
}

func (c *Controller) UnmuteAudio(vfo core.VFOID) {
c.VFOs[vfo].UnmuteAudio()
}

func (c *Controller) ToggleAudio(vfo core.VFOID) {
c.VFOs[vfo].ToggleAudio()
}

func (c *Controller) MarkInBandmap() {
Expand Down Expand Up @@ -954,6 +978,20 @@ func (c *Controller) DoAction(id string) error {
c.StartParrot()
case core.ActionEntryNextESMStep:
c.Entry.NextESMStep()
case core.ActionEntryToggleFocusedVFO:
c.Entry.ToggleFocusedVFO()
case core.ActionEntryFocusVFO1:
c.Entry.FocusVFO1()
case core.ActionEntryFocusVFO2:
c.Entry.FocusVFO2()
case core.ActionEntryLogVFO1:
c.Entry.LogVFO(core.VFO1)
case core.ActionEntryLogVFO2:
c.Entry.LogVFO(core.VFO2)
case core.ActionEntryClearVFO1:
c.Entry.ClearVFO(core.VFO1)
case core.ActionEntryClearVFO2:
c.Entry.ClearVFO(core.VFO2)
case core.ActionEntryOfferQTC:
c.OfferQTC()
case core.ActionEntryRequestQTC:
Expand Down Expand Up @@ -981,13 +1019,25 @@ func (c *Controller) DoAction(id string) error {
case core.ActionWindowShowSpots:
c.ShowSpots()
case core.ActionKeyerSendMacro1:
c.Keyer.Send(0)
c.Keyer.SendMacro(0)
case core.ActionKeyerSendMacro2:
c.Keyer.Send(1)
c.Keyer.SendMacro(1)
case core.ActionKeyerSendMacro3:
c.Keyer.Send(2)
c.Keyer.SendMacro(2)
case core.ActionKeyerSendMacro4:
c.Keyer.Send(3)
c.Keyer.SendMacro(3)
case core.ActionRadioMuteAudioVFO1:
c.MuteAudio(core.VFO1)
case core.ActionRadioMuteAudioVFO2:
c.MuteAudio(core.VFO2)
case core.ActionRadioUnmuteAudioVFO1:
c.UnmuteAudio(core.VFO1)
case core.ActionRadioUnmuteAudioVFO2:
c.UnmuteAudio(core.VFO2)
case core.ActionRadioToggleAudioVFO1:
c.ToggleAudio(core.VFO1)
case core.ActionRadioToggleAudioVFO2:
c.ToggleAudio(core.VFO2)
default:
return fmt.Errorf("unknown action: %s", id)
}
Expand Down
16 changes: 13 additions & 3 deletions core/bandmap/bandmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,20 @@ func (m *Bandmap) ScoreChanged(_ core.Score) {
}
}

func (m *Bandmap) VFOFrequencyChanged(frequency core.Frequency) {
func (m *Bandmap) VFOFrequencyChanged(vfo core.VFOID, frequency core.Frequency) {
if vfo != core.VFO1 {
return
}
m.do <- func() {
m.activeFrequency = frequency
// the frame is not updated with every frequency change, this creates too much load
}
}

func (m *Bandmap) VFOBandChanged(band core.Band) {
func (m *Bandmap) VFOBandChanged(vfo core.VFOID, band core.Band) {
if vfo != core.VFO1 {
return
}
m.do <- func() {
if band == m.activeBand {
return
Expand All @@ -254,7 +260,10 @@ func (m *Bandmap) VFOBandChanged(band core.Band) {
}
}

func (m *Bandmap) VFOModeChanged(mode core.Mode) {
func (m *Bandmap) VFOModeChanged(vfo core.VFOID, mode core.Mode) {
if vfo != core.VFO1 {
return
}
m.do <- func() {
if m.activeMode == mode {
return
Expand Down Expand Up @@ -398,6 +407,7 @@ func (v *nullView) RevealEntry(core.BandmapEntry) {}

type nullVFO struct{}

func (n *nullVFO) Name() string { return "" }
func (n *nullVFO) Notify(any) {}
func (n *nullVFO) Active() bool { return false }
func (n *nullVFO) Refresh() {}
Expand Down
69 changes: 41 additions & 28 deletions core/callinfo/callinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ type QTCProvider interface {

// View defines the visual part of the call information window.
type View interface {
ShowFrame(core.CallinfoFrame)
ShowFrame(core.VFOID, core.CallinfoFrame)
SetVFOEnabled(core.VFOID, bool)
}

type CallinfoFrameListener interface {
CallinfoFrameChanged(core.CallinfoFrame)
CallinfoFrameChanged(core.VFOID, core.CallinfoFrame)
}

type Callinfo struct {
Expand All @@ -61,8 +62,9 @@ type Callinfo struct {

theirExchangeFields []core.ExchangeField
qtcsEnabled bool
vfo2Enabled bool

frame core.CallinfoFrame
frames [core.VFOCount]core.CallinfoFrame
}

func New(entities DXCCFinder, callsigns CallsignFinder, callHistory CallHistoryFinder, dupeChecker DupeChecker, valuer Valuer, qtcProvider QTCProvider) *Callinfo {
Expand All @@ -85,6 +87,7 @@ func (c *Callinfo) SetView(view View) {
}

c.view = view
c.view.SetVFOEnabled(core.VFO2, c.vfo2Enabled)
}

func (c *Callinfo) StationChanged(station core.Station) {
Expand All @@ -110,13 +113,13 @@ func (c *Callinfo) Notify(listener any) {
c.listeners = append(c.listeners, listener)
}

func (c *Callinfo) emitFrameChanged() {
func (c *Callinfo) emitFrameChanged(vfo core.VFOID) {
for _, listener := range c.listeners {
if l, ok := listener.(CallinfoFrameListener); ok {
l.CallinfoFrameChanged(c.frame)
l.CallinfoFrameChanged(vfo, c.frames[vfo])
}
}
c.view.ShowFrame(c.frame)
c.view.ShowFrame(vfo, c.frames[vfo])
}

func (c *Callinfo) GetInfo(call core.Callsign, band core.Band, mode core.Mode, currentExchange []string) core.Callinfo {
Expand All @@ -127,39 +130,42 @@ func (c *Callinfo) UpdateValue(info *core.Callinfo, band core.Band, mode core.Mo
return c.collector.UpdateValue(info, band, mode)
}

func (c *Callinfo) InputChanged(call string, band core.Band, mode core.Mode, currentExchange []string) {
func (c *Callinfo) InputChanged(vfo core.VFOID, call string, band core.Band, mode core.Mode, currentExchange []string) {
normalizedCall := normalizeInput(call)

callinfo := c.collector.GetInfoForInput(normalizedCall, band, mode, currentExchange)
supercheck := c.supercheck.Calculate(normalizedCall, band, mode)

c.frame.NormalizedCallInput = normalizedCall
c.frame.DXCCEntity = callinfo.DXCCEntity
c.frame.Azimuth = callinfo.Azimuth
c.frame.Distance = callinfo.Distance
c.frame.UserInfo = callinfo.UserText
frame := &c.frames[vfo]
frame.NormalizedCallInput = normalizedCall
frame.DXCCEntity = callinfo.DXCCEntity
frame.Azimuth = callinfo.Azimuth
frame.Distance = callinfo.Distance
frame.UserInfo = callinfo.UserText

c.frame.Points = callinfo.Points
c.frame.Multis = callinfo.Multis
c.frame.Value = callinfo.Value
c.frame.SentQTCs = callinfo.SentQTCs
c.frame.ReceivedQTCs = callinfo.ReceivedQTCs
frame.Points = callinfo.Points
frame.Multis = callinfo.Multis
frame.Value = callinfo.Value
frame.SentQTCs = callinfo.SentQTCs
frame.ReceivedQTCs = callinfo.ReceivedQTCs

c.frame.PredictedExchange = callinfo.PredictedExchange
c.frame.Supercheck = supercheck
frame.PredictedExchange = callinfo.PredictedExchange
frame.Supercheck = supercheck

c.emitFrameChanged()
c.emitFrameChanged(vfo)
}

// EntryOnFrequency is driven by the bandmap, which is VFO1-only in this step. Updates VFO1's frame only.
func (c *Callinfo) EntryOnFrequency(entry core.BandmapEntry, available bool) {
last := c.frame.CallsignOnFrequency.Callsign.String()
frame := &c.frames[core.VFO1]
last := frame.CallsignOnFrequency.Callsign.String()
if !available {
c.frame.CallsignOnFrequency = core.AnnotatedCallsign{}
} else if c.frame.CallsignOnFrequency.Callsign.String() == entry.Call.String() {
frame.CallsignOnFrequency = core.AnnotatedCallsign{}
} else if frame.CallsignOnFrequency.Callsign.String() == entry.Call.String() {
// go on
} else {
exactMatch := c.frame.NormalizedCallInput == entry.Call.String()
c.frame.CallsignOnFrequency = core.AnnotatedCallsign{
exactMatch := frame.NormalizedCallInput == entry.Call.String()
frame.CallsignOnFrequency = core.AnnotatedCallsign{
Callsign: entry.Call,
Assembly: core.MatchingAssembly{{OP: core.Matching, Value: entry.Call.String()}},
Duplicate: entry.Info.Duplicate,
Expand All @@ -172,11 +178,17 @@ func (c *Callinfo) EntryOnFrequency(entry core.BandmapEntry, available bool) {
}
}

if last != c.frame.CallsignOnFrequency.Callsign.String() {
c.emitFrameChanged()
if last != frame.CallsignOnFrequency.Callsign.String() {
c.emitFrameChanged(core.VFO1)
}
}

// RadioChanged implements core.RadioChangedListener.
func (c *Callinfo) RadioChanged(_ string, singleVFO bool) {
c.vfo2Enabled = !singleVFO
c.view.SetVFOEnabled(core.VFO2, c.vfo2Enabled)
}

func normalizeInput(input string) string {
return strings.TrimSpace(strings.ToUpper(input))
}
Expand All @@ -186,4 +198,5 @@ type nullView struct{}
func (v *nullView) Show() {}
func (v *nullView) Hide() {}
func (v *nullView) SetPredictedExchangeFields([]core.ExchangeField) {}
func (v *nullView) ShowFrame(frame core.CallinfoFrame) {}
func (v *nullView) ShowFrame(core.VFOID, core.CallinfoFrame) {}
func (v *nullView) SetVFOEnabled(core.VFOID, bool) {}
10 changes: 10 additions & 0 deletions core/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ var Default = &Data{
Type: "tci",
Address: "localhost:40001",
Keyer: "radio",
Options: map[string]string{
"single_vfo": "false",
},
},
},
Keyers: []core.Keyer{
Expand Down Expand Up @@ -154,6 +157,13 @@ var Default = &Data{
"entry.workmode_run": "Ctrl+R",
"entry.offer_qtc": "F5",
"entry.request_qtc": "F6",
"entry.toggle_focused_vfo": "F8",
"entry.focus_vfo1": "F9",
"entry.focus_vfo2": "F10",
"entry.log_vfo1": "",
"entry.log_vfo2": "",
"entry.clear_vfo1": "",
"entry.clear_vfo2": "",
"radio.xit_active": "Ctrl+Shift+X",
"bandmap.mark": "Ctrl+M",
"bandmap.goto_highest_value_spot": "Ctrl+Shift+N",
Expand Down
Loading
Loading