Follow-up to #3293 (feat(hid): initial Icom RC-28 encoder support). That PR wired up the hardware; this request exposes the configuration that operators need to use it productively.
Request preparation
What would you like?
Problem
The RC-28's three buttons — F1, F2, and TX bar — have hardcoded defaults (F1=StepUp, F2=StepDown, TX bar=PTT) that can only be changed by hand-editing HidKeyAction0/1/2 in AppSettings. There is no UI for it. Three things compound this:
-
Half the hardware is unused. The RC-28 supports two gestures per button (short press and hold), but AetherSDR's parser only emits a single press event. The FlexRC-28 companion app uses a 600 ms threshold to distinguish short press from hold, mapping each F button to two independent action slots. Without hold detection, F1 and F2 each offer one action instead of two.
-
PTT has only one mode. The TX bar is always momentary (hold-to-talk). Many operators want a toggle/latch mode so they can lift their hand off the controller during a QSO. The right place to choose between momentary and latched is a radio button in the mapping dialog — not a timed gesture.
-
No UI indicator of any kind. The RC-28 is silently active once HidEncoderEnabled=True. There is no way to see whether the device is recognized, what path it's on, or debug why a button isn't doing what was set.
Proposed solution
Step 1 — New Settings menu entry
Add "RC-28 Button Mapping..." to the &Settings menu, following the pattern of the existing "MIDI Mapping..." and "Ulanzi Dial Mapping..." entries in MainWindow.cpp. Gated on #ifdef HAVE_HIDAPI. The entry is always visible but opens a dialog that shows a "no device" state when the RC-28 is not enabled or not connected.
Step 2 — RC28MappingDialog
A new RC28MappingDialog : PersistentDialog with four sections:
Section A — Device Info (read-only, live-updating via HidEncoderManager::connectionChanged)
| Field |
Source |
| Status |
Connected / Not connected (colored dot) |
| Device |
"Icom RC-28" (from HidDeviceParser::supportedDevices() name table) |
| VID:PID |
0x0C26:0x001E (from HidEncoderManager::vendorId/productId()) |
| Path |
hid_device_info::path via hid_get_device_info() — e.g. /dev/hidraw0 on Linux |
| Serial |
hid_device_info::serial_number — may be blank on some RC-28 units |
| Firmware |
hid_device_info::release_number (BCD, e.g. 0x0100 → "1.00") |
Requires capturing these fields during HidEncoderManager::open() by calling hid_get_device_info(m_device) after a successful hid_open(), storing path/serial/release as new members with matching getters.
Section B — TX Bar
Two mutually exclusive radio buttons (not a dropdown — only two choices):
- Momentary — hold to transmit, release to stop (current behavior, default)
- Latched — press to transmit, press again to stop
Saves to new key HidRC28PttMode ("Momentary" / "Latched"). Implementation: in the MainWindow buttonPressed lambda, check this key and either use the existing hold-to-talk logic or flip a m_rc28PttLatched bool on each press event. No timer-based auto-latch — the operator chooses the mode explicitly.
Section C — F1 and F2 Button Assignments
Five action slots:
| Slot |
Default |
Settings key |
| F1 — Short press |
StepUp |
HidKeyAction0 (existing) |
| F1 — Hold (600 ms) |
TuneFast |
HidKeyAction0Hold (new) |
| F2 — Short press |
StepDown |
HidKeyAction1 (existing) |
| F2 — Hold (600 ms) |
ModeCycle |
HidKeyAction1Hold (new) |
Each row is a GuardedComboBox. Hold detection: a QTimer started in the buttonPressed lambda on action==0 and cancelled on action==1; fires the hold action at 600 ms.
Full action list (current + additions drawn from the FlexRC-28 companion app):
| ID |
Label |
StepUp |
Step Up |
StepDown |
Step Down |
StepCycle |
Cycle Step Up |
TuneFast |
Toggle Fast Tuning (new) |
LockDial |
Lock/Unlock Encoder (new) |
ToggleRit |
Toggle RIT |
ClearRit |
Clear RIT |
ToggleXit |
Toggle XIT |
ClearXit |
Clear XIT |
ModeCycle |
Cycle Mode (LSB/USB/CW/AM) (new) |
BandCycle |
Cycle Band (160m–6m) (new) |
SnapKHz |
Snap to Nearest 1 kHz (new) |
ToggleMox |
Toggle MOX |
ToggleMute |
Toggle Mute |
ToggleLock |
Toggle Slice Lock |
NextSlice |
Next Slice |
PrevSlice |
Previous Slice |
VolumeUp |
Volume Up |
VolumeDown |
Volume Down |
None |
No Action |
F1 LED lights when TuneFast is the active F1 action and fast-tune mode is on. F2 LED mirrors active slice RIT state. TRANSMIT and LINK LED behavior is unchanged from #3293.
Section D — Debug / Activity Log
A compact scrolling log (QPlainTextEdit, read-only, 50-line cap) showing live button events while the dialog is open. Format: [HH:MM:SS] F1 press → StepUp / [HH:MM:SS] F2 hold → ModeCycle / [HH:MM:SS] TX bar press → TX ON (Latched). A Clear button empties the log. The dialog connects to HidEncoderManager::buttonPressed and resolves the action name before appending.
Implementation notes
- New files:
src/gui/RC28MappingDialog.h / .cpp
HidEncoderManager::open() — call hid_get_device_info() post-open; store m_devicePath (QString), m_serialNumber (QString from wchar_t*), m_releaseNumber (uint16_t); add getters
MainWindow::setupHidEncoder() — add QTimer* m_rc28HoldTimer for F1/F2 hold detection; wire buttonPressed to RC28MappingDialog::appendEvent() when dialog is open
MainWindow::createMenus() — add "RC-28 Button Mapping..." after "MIDI Mapping..." (~line 8212), gated on #ifdef HAVE_HIDAPI
- New settings keys:
HidRC28PttMode, HidKeyAction0Hold, HidKeyAction1Hold
- New action handlers in the
buttonPressed lambda: TuneFast, LockDial, ModeCycle, BandCycle, SnapKHz
Prior art consulted
- FlexRC-28 (
src/controller.js, index.html) — 5-slot button model, 600 ms hold threshold, action set, F1/F2 LED state rules, button assignment grid UI
- wfview (
src/usbcontroller.cpp) — individual per-LED control, device info capture at connect time
src/gui/FlexControlDialog.h — PersistentDialog pattern to follow
MainWindow.cpp:8212 — where the new Settings menu entry lands
Out of scope
- TRANSMIT and LINK LED behavior — unchanged
- Other HID devices (PowerMate, ShuttleXpress, StreamDeck+)
- RC-28 connected to the FlexRadio USB upstream port
Request preparation
What would you like?
Problem
The RC-28's three buttons — F1, F2, and TX bar — have hardcoded defaults (F1=StepUp, F2=StepDown, TX bar=PTT) that can only be changed by hand-editing
HidKeyAction0/1/2inAppSettings. There is no UI for it. Three things compound this:Half the hardware is unused. The RC-28 supports two gestures per button (short press and hold), but AetherSDR's parser only emits a single press event. The FlexRC-28 companion app uses a 600 ms threshold to distinguish short press from hold, mapping each F button to two independent action slots. Without hold detection, F1 and F2 each offer one action instead of two.
PTT has only one mode. The TX bar is always momentary (hold-to-talk). Many operators want a toggle/latch mode so they can lift their hand off the controller during a QSO. The right place to choose between momentary and latched is a radio button in the mapping dialog — not a timed gesture.
No UI indicator of any kind. The RC-28 is silently active once
HidEncoderEnabled=True. There is no way to see whether the device is recognized, what path it's on, or debug why a button isn't doing what was set.Proposed solution
Step 1 — New Settings menu entry
Add
"RC-28 Button Mapping..."to the&Settingsmenu, following the pattern of the existing"MIDI Mapping..."and"Ulanzi Dial Mapping..."entries inMainWindow.cpp. Gated on#ifdef HAVE_HIDAPI. The entry is always visible but opens a dialog that shows a "no device" state when the RC-28 is not enabled or not connected.Step 2 — RC28MappingDialog
A new
RC28MappingDialog : PersistentDialogwith four sections:Section A — Device Info (read-only, live-updating via
HidEncoderManager::connectionChanged)HidDeviceParser::supportedDevices()name table)0x0C26:0x001E(fromHidEncoderManager::vendorId/productId())hid_device_info::pathviahid_get_device_info()— e.g./dev/hidraw0on Linuxhid_device_info::serial_number— may be blank on some RC-28 unitshid_device_info::release_number(BCD, e.g.0x0100→ "1.00")Requires capturing these fields during
HidEncoderManager::open()by callinghid_get_device_info(m_device)after a successfulhid_open(), storing path/serial/release as new members with matching getters.Section B — TX Bar
Two mutually exclusive radio buttons (not a dropdown — only two choices):
Saves to new key
HidRC28PttMode("Momentary"/"Latched"). Implementation: in theMainWindowbuttonPressedlambda, check this key and either use the existing hold-to-talk logic or flip am_rc28PttLatchedbool on each press event. No timer-based auto-latch — the operator chooses the mode explicitly.Section C — F1 and F2 Button Assignments
Five action slots:
StepUpHidKeyAction0(existing)TuneFastHidKeyAction0Hold(new)StepDownHidKeyAction1(existing)ModeCycleHidKeyAction1Hold(new)Each row is a
GuardedComboBox. Hold detection: aQTimerstarted in thebuttonPressedlambda onaction==0and cancelled onaction==1; fires the hold action at 600 ms.Full action list (current + additions drawn from the FlexRC-28 companion app):
StepUpStepDownStepCycleTuneFastLockDialToggleRitClearRitToggleXitClearXitModeCycleBandCycleSnapKHzToggleMoxToggleMuteToggleLockNextSlicePrevSliceVolumeUpVolumeDownNoneF1 LED lights when
TuneFastis the active F1 action and fast-tune mode is on. F2 LED mirrors active slice RIT state. TRANSMIT and LINK LED behavior is unchanged from #3293.Section D — Debug / Activity Log
A compact scrolling log (
QPlainTextEdit, read-only, 50-line cap) showing live button events while the dialog is open. Format:[HH:MM:SS] F1 press → StepUp/[HH:MM:SS] F2 hold → ModeCycle/[HH:MM:SS] TX bar press → TX ON (Latched). A Clear button empties the log. The dialog connects toHidEncoderManager::buttonPressedand resolves the action name before appending.Implementation notes
src/gui/RC28MappingDialog.h/.cppHidEncoderManager::open()— callhid_get_device_info()post-open; storem_devicePath(QString),m_serialNumber(QString fromwchar_t*),m_releaseNumber(uint16_t); add gettersMainWindow::setupHidEncoder()— addQTimer* m_rc28HoldTimerfor F1/F2 hold detection; wirebuttonPressedtoRC28MappingDialog::appendEvent()when dialog is openMainWindow::createMenus()— add"RC-28 Button Mapping..."after"MIDI Mapping..."(~line 8212), gated on#ifdef HAVE_HIDAPIHidRC28PttMode,HidKeyAction0Hold,HidKeyAction1HoldbuttonPressedlambda:TuneFast,LockDial,ModeCycle,BandCycle,SnapKHzPrior art consulted
src/controller.js,index.html) — 5-slot button model, 600 ms hold threshold, action set, F1/F2 LED state rules, button assignment grid UIsrc/usbcontroller.cpp) — individual per-LED control, device info capture at connect timesrc/gui/FlexControlDialog.h—PersistentDialogpattern to followMainWindow.cpp:8212— where the new Settings menu entry landsOut of scope