From 9574376d3816fbc3f257a61b4c113f567f46c59b Mon Sep 17 00:00:00 2001 From: mrkusypl Date: Mon, 16 Mar 2026 00:52:28 +0100 Subject: [PATCH 1/4] Code refactoring; improvements to K5Viewer and the UI --- App/app/aircopy.c | 27 ++++++-------- App/app/app.c | 10 +++-- App/app/main.c | 87 +++++++++++++++++++++---------------------- App/app/menu.c | 83 ++++++++++++++++++++++++----------------- App/driver/keyboard.c | 62 +++++++++++++++++++++++++----- App/driver/keyboard.h | 19 +++++++--- App/driver/uart.c | 54 +-------------------------- App/driver/vcp.c | 71 +++++------------------------------ App/misc.c | 12 ++++++ App/misc.h | 2 + App/screenshot.c | 31 ++++++--------- App/ui/fmradio.c | 6 +++ App/ui/status.c | 46 ++++++++++------------- App/ui/welcome.c | 6 +-- 14 files changed, 240 insertions(+), 276 deletions(-) diff --git a/App/app/aircopy.c b/App/app/aircopy.c index 34276077..5469818a 100644 --- a/App/app/aircopy.c +++ b/App/app/aircopy.c @@ -331,10 +331,6 @@ void AIRCOPY_StorePacket(void) static void AIRCOPY_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { - if (bKeyHeld || !bKeyPressed) { - return; - } - INPUTBOX_Append(Key); gRequestDisplayScreen = DISPLAY_AIRCOPY; @@ -379,10 +375,6 @@ static void AIRCOPY_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) static void AIRCOPY_Key_EXIT(bool bKeyPressed, bool bKeyHeld) { - if (bKeyHeld || !bKeyPressed) { - return; - } - if (gInputBoxIndex == 0) { gAircopyStep = 1; gFSKWriteIndex = 0; @@ -405,10 +397,6 @@ static void AIRCOPY_Key_EXIT(bool bKeyPressed, bool bKeyHeld) static void AIRCOPY_Key_MENU(bool bKeyPressed, bool bKeyHeld) { - if (bKeyHeld || !bKeyPressed) { - return; - } - gAircopyStep = 1; gFSKWriteIndex = 0; gAirCopyBlockNumber = 0; @@ -427,10 +415,6 @@ static void AIRCOPY_Key_MENU(bool bKeyPressed, bool bKeyHeld) static void AIRCOPY_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) { - if (bKeyHeld || !bKeyPressed) { - return; - } - if (!gEeprom.SET_NAV) { Direction = -Direction; } @@ -450,6 +434,14 @@ static void AIRCOPY_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Directio void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { + if (bKeyHeld || !bKeyPressed) { + return; + } + + if (Key != KEY_PTT) { + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; + } + switch (Key) { case KEY_0...KEY_9: AIRCOPY_Key_DIGITS(Key, bKeyPressed, bKeyHeld); @@ -464,7 +456,10 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) case KEY_DOWN: AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1); break; + case KEY_PTT: + break; default: + gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; break; } } diff --git a/App/app/app.c b/App/app/app.c index 7f172faf..30a937e0 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -1609,9 +1609,11 @@ void APP_TimeSlice500ms(void) } #endif + const int m = UI_MENU_GetCurrentMenuId(); + if (gBacklightCountdown_500ms > 0 && !gAskToSave && !gCssBackgroundScan // don't turn off backlight if user is in backlight menu option - && !(gScreenToDisplay == DISPLAY_MENU && (UI_MENU_GetCurrentMenuId() == MENU_ABR || UI_MENU_GetCurrentMenuId() == MENU_ABR_MAX)) + && !(gScreenToDisplay == DISPLAY_MENU && (m == MENU_ABR || m == MENU_ABR_MAX || m == MENU_ABR_MIN)) && --gBacklightCountdown_500ms == 0 && gEeprom.BACKLIGHT_TIME < 61 ) { @@ -1729,8 +1731,10 @@ void APP_TimeSlice500ms(void) if (exit_menu) { gMenuCountdown = 0; - if (gEeprom.BACKLIGHT_TIME == 0) { - BACKLIGHT_TurnOff(); + const int m = UI_MENU_GetCurrentMenuId(); + + if (gScreenToDisplay == DISPLAY_MENU && (m == MENU_ABR || m == MENU_ABR_MAX || m == MENU_ABR_MIN)) { + BACKLIGHT_TurnOn(); } if (gInputBoxIndex > 0 || gDTMF_InputMode) { diff --git a/App/app/main.c b/App/app/main.c index 69f0e4c9..8bef1e97 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -99,16 +99,15 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) { uint8_t Vfo = gEeprom.TX_VFO; -#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if(gEeprom.MENU_LOCK == true) { - if(Key == 2) { // Enable A/B only - gVfoConfigureMode = VFO_CONFIGURE; - COMMON_SwitchVFOs(); - if (beep) - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; - } + if (beep) + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; - return; // prevent F function if MENU LOCK is true +#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS + if(gEeprom.MENU_LOCK == true && Key != 2) { + gUpdateStatus = true; + gWasFKeyPressed = false; + + return; } #endif @@ -128,7 +127,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) if (!IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE)) { gWasFKeyPressed = false; gUpdateStatus = true; - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; #ifdef ENABLE_COPY_CHAN_TO_VFO if (!gEeprom.VFO_OPEN || gCssBackgroundScan) { @@ -192,9 +190,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) gRequestDisplayScreen = DISPLAY_MAIN; - if (beep) - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; - break; case KEY_2: @@ -202,8 +197,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) gVfoConfigureMode = VFO_CONFIGURE; #endif COMMON_SwitchVFOs(); - if (beep) - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; break; case KEY_3: @@ -211,8 +204,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) gVfoConfigureMode = VFO_CONFIGURE; #endif COMMON_SwitchVFOMode(); - if (beep) - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; break; @@ -222,8 +213,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) gBackup_CROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX; gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF; gUpdateStatus = true; - if (beep) - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; SCANNER_Start(false); gRequestDisplayScreen = DISPLAY_SCANNER; @@ -550,7 +539,13 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) return; } - gKeyInputCountdown = (gInputBoxIndex == totalDigits) ? (key_input_timeout_500ms / 16) : (key_input_timeout_500ms / 3); + gKeyInputCountdown = (gInputBoxIndex >= totalDigits) ? (key_input_timeout_500ms / 16) : (key_input_timeout_500ms / 3); + + if (gInputBoxIndex >= totalDigits) { + gInputBoxIndex = totalDigits; + + return; + } const char *inputStr = INPUTBOX_GetAscii(); uint8_t inputLength = gInputBoxIndex; @@ -643,6 +638,12 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gWasFKeyPressed = false; gUpdateStatus = true; + #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS + if(gEeprom.MENU_LOCK == true && Key != 2) { + return; + } + #endif + if(Key == 8) { ACTION_BackLightOnDemand(); @@ -656,11 +657,6 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) #ifdef ENABLE_FEAT_F4HWN_GAME else if(Key == 7) { - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if(gEeprom.MENU_LOCK == true) { - return; - } - #endif APP_RunBreakout(); return; } @@ -839,7 +835,12 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) } #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if(gEeprom.MENU_LOCK == false) { + if(gEeprom.MENU_LOCK == true) { + gUpdateStatus = true; + gWasFKeyPressed = false; + + return; + } #endif gFlagRefreshSetting = true; @@ -847,10 +848,6 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) #ifdef ENABLE_VOICE gAnotherVoiceID = VOICE_ID_MENU; #endif - - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - } - #endif } else { gRequestDisplayScreen = DISPLAY_MAIN; @@ -860,13 +857,6 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld) { - -#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if(gEeprom.MENU_LOCK == true) { - return; // prevent F function if MENU LOCK is true - } -#endif - if (gCurrentFunction == FUNCTION_TRANSMIT) return; @@ -876,6 +866,20 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld) return; } + if (!bKeyHeld && bKeyPressed) { // star key pressed + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; // beep when key is pressed + return; // don't use the key till it's released + } + + #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS + if(gEeprom.MENU_LOCK == true) { + gUpdateStatus = true; + gWasFKeyPressed = false; + + return; // prevent F function if MENU LOCK is true + } + #endif + if (bKeyHeld && !gWasFKeyPressed){ // long press if (!bKeyPressed) // released return; @@ -898,12 +902,6 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld) gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; return; } - - if (bKeyPressed) { // just pressed - return; - } - - // just released if (!gWasFKeyPressed) // pressed without the F-key { @@ -916,7 +914,6 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld) #endif ) { // start entering a DTMF string - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; memcpy(gDTMF_InputBox, gDTMF_String, MIN(sizeof(gDTMF_InputBox), sizeof(gDTMF_String) - 1)); gDTMF_InputBox_Index = 0; gDTMF_InputMode = true; @@ -958,7 +955,7 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) #ifdef ENABLE_FEAT_F4HWN // Set Squelch F + UP or Down if(gWasFKeyPressed) { - processFKeyFunction(Direction == 1 ? KEY_UP : KEY_DOWN, false); + processFKeyFunction(Direction == 1 ? KEY_UP : KEY_DOWN, true); return; } #endif diff --git a/App/app/menu.c b/App/app/menu.c index 9c307ef0..6d58b49a 100644 --- a/App/app/menu.c +++ b/App/app/menu.c @@ -1547,12 +1547,14 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) return; } - if (UI_MENU_GetCurrentMenuId() == MENU_MEM_CH || - UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || - UI_MENU_GetCurrentMenuId() == MENU_1_CALL || - UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_1 || - UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_2 || - UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) + const int m = UI_MENU_GetCurrentMenuId(); + + if (m == MENU_MEM_CH || + m == MENU_DEL_CH || + m == MENU_1_CALL || + m == MENU_S_PRI_CH_1 || + m == MENU_S_PRI_CH_2 || + m == MENU_MEM_NAME) { // enter 4-digit channel number if (gInputBoxIndex < 4) @@ -1692,19 +1694,21 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) if (!gIsInSubMenu) { + const int m = UI_MENU_GetCurrentMenuId(); + #ifdef ENABLE_VOICE - if (UI_MENU_GetCurrentMenuId() != MENU_SCR) + if (m != MENU_SCR) gAnotherVoiceID = MenuList[gMenuCursor].voice_id; #endif - if (UI_MENU_GetCurrentMenuId() == MENU_UPCODE - || UI_MENU_GetCurrentMenuId() == MENU_DWCODE + if (m == MENU_UPCODE + || m == MENU_DWCODE #ifdef ENABLE_DTMF_CALLING - || UI_MENU_GetCurrentMenuId() == MENU_ANI_ID + || m == MENU_ANI_ID #endif ) return; #if 1 - if (UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) + if (m == MENU_DEL_CH || m == MENU_MEM_NAME) if (!RADIO_CheckValidChannel(gSubMenuSelection, false, 0)) return; // invalid channel #endif @@ -1712,7 +1716,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) gAskForConfirmation = 0; gIsInSubMenu = true; -// if (UI_MENU_GetCurrentMenuId() != MENU_D_LIST) +// if (m != MENU_D_LIST) { gInputBoxIndex = 0; edit_index = -1; @@ -1763,10 +1767,12 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld) if (gIsInSubMenu) { - if (UI_MENU_GetCurrentMenuId() == MENU_RESET || - UI_MENU_GetCurrentMenuId() == MENU_MEM_CH || - UI_MENU_GetCurrentMenuId() == MENU_DEL_CH || - UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) + const int m = UI_MENU_GetCurrentMenuId(); + + if (m == MENU_RESET || + m == MENU_MEM_CH || + m == MENU_DEL_CH || + m == MENU_MEM_NAME) { switch (gAskForConfirmation) { @@ -1853,7 +1859,8 @@ static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld) if (gRxVfo->Modulation == MODULATION_FM) #endif { - if ((UI_MENU_GetCurrentMenuId() == MENU_R_CTCS || UI_MENU_GetCurrentMenuId() == MENU_R_DCS) && gIsInSubMenu) + const int m = UI_MENU_GetCurrentMenuId(); + if ((m == MENU_R_CTCS || m == MENU_R_DCS) && gIsInSubMenu) { // scan CTCSS or DCS to find the tone/code of the incoming signal if (!SCANNER_IsScanning()) MENU_StartCssScan(); @@ -1925,9 +1932,11 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) gRequestDisplayScreen = DISPLAY_MENU; - if (UI_MENU_GetCurrentMenuId() != MENU_ABR - && UI_MENU_GetCurrentMenuId() != MENU_ABR_MIN - && UI_MENU_GetCurrentMenuId() != MENU_ABR_MAX + const int m = UI_MENU_GetCurrentMenuId(); + + if (m != MENU_ABR + && m != MENU_ABR_MIN + && m != MENU_ABR_MAX && gEeprom.BACKLIGHT_TIME == 0) // backlight always off and not in the backlight menu { BACKLIGHT_TurnOff(); @@ -1953,8 +1962,10 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) } VFO = 0; + + const int m = UI_MENU_GetCurrentMenuId(); - switch (UI_MENU_GetCurrentMenuId()) + switch (m) { case MENU_DEL_CH: case MENU_1_CALL: @@ -1970,19 +1981,21 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) return; } - if(UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_1 || UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_2) + if(m == MENU_S_PRI_CH_1 || m == MENU_S_PRI_CH_2) { static int16_t last; - if(Direction > 0 && gSubMenuSelection == MR_CHANNELS_MAX) - { - gSubMenuSelection = -1; - last = -1; - } - else if(Direction < 0 && gSubMenuSelection == MR_CHANNELS_MAX) - { - gSubMenuSelection = MR_CHANNELS_MAX; - last = MR_CHANNELS_MAX; + if(gSubMenuSelection == MR_CHANNELS_MAX) { + if(Direction > 0) + { + gSubMenuSelection = -1; + last = -1; + } + else if(Direction < 0) + { + gSubMenuSelection = MR_CHANNELS_MAX; + last = MR_CHANNELS_MAX; + } } Channel = RADIO_FindNextChannel(gSubMenuSelection + Direction, Direction, bCheckScanList, VFO); @@ -2067,11 +2080,13 @@ void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) if (gScreenToDisplay == DISPLAY_MENU) { - if (UI_MENU_GetCurrentMenuId() == MENU_VOL || + const int m = UI_MENU_GetCurrentMenuId(); + + if (m == MENU_VOL || #ifdef ENABLE_F_CAL_MENU - UI_MENU_GetCurrentMenuId() == MENU_F_CALI || + m == MENU_F_CALI || #endif - UI_MENU_GetCurrentMenuId() == MENU_BATCAL) + m == MENU_BATCAL) { gMenuCountdown = menu_timeout_long_500ms; } diff --git a/App/driver/keyboard.c b/App/driver/keyboard.c index c7f3875c..de3d4a13 100644 --- a/App/driver/keyboard.c +++ b/App/driver/keyboard.c @@ -36,30 +36,72 @@ bool gWasFKeyPressed = false; // Must exceed key_repeat_delay_10ms (40) to trigger ProcessKey(key, true, true). #define SERIAL_KEY_LONG_POLLS 45 +// Packet types for serial key injection (K5Viewer → radio) +#define SERIAL_KEY_TYPE 0x03 +#define SERIAL_KEY_TYPE_LONG 0x04 + volatile KEY_Code_t gKeyFromSerial = KEY_INVALID; static uint8_t gSerialKeyHoldCount = 0; static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press -// Inject a short press from serial (UART or VCP). +// Inject a short or long press from serial (UART or VCP). // KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. -void KEYBOARD_InjectKey(uint8_t keyCode) +void KEYBOARD_InjectKey(uint8_t keyCode, bool keyLong) { if (keyCode < KEY_INVALID && keyCode != KEY_PTT) { gKeyFromSerial = (KEY_Code_t)keyCode; gSerialKeyHoldCount = 0; - gSerialKeyLong = 0; + gSerialKeyLong = keyLong; } } -// Inject a long press from serial (UART or VCP). -// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. -void KEYBOARD_InjectKeyLong(uint8_t keyCode) +bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b) { - if (keyCode < KEY_INVALID && keyCode != KEY_PTT) { - gKeyFromSerial = (KEY_Code_t)keyCode; - gSerialKeyHoldCount = 0; - gSerialKeyLong = 1; + bool connected = false; + + switch (*state) + { + case STATE_IDLE: + if (b == 0x55) *state = STATE_KA_1; + else if (b == 0xAA) *state = STATE_KEY_1; + break; + + case STATE_KA_1: + *state = (b == 0xAA) ? STATE_KA_2 : STATE_IDLE; + break; + + case STATE_KA_2: + *state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE; + break; + + case STATE_KA_3: + if (b == 0x00) connected = true; + *state = STATE_IDLE; + break; + + case STATE_KEY_1: + *state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE; + break; + + case STATE_KEY_2: + if (b == SERIAL_KEY_TYPE) *state = STATE_KEY_3; + else if (b == SERIAL_KEY_TYPE_LONG) *state = STATE_KEY_3L; + else *state = STATE_IDLE; + break; + + case STATE_KEY_3: + case STATE_KEY_3L: + KEYBOARD_InjectKey(b, *state == STATE_KEY_3L); + connected = true; + *state = STATE_IDLE; + break; + + default: + *state = STATE_IDLE; + break; } + + return connected; } #endif diff --git a/App/driver/keyboard.h b/App/driver/keyboard.h index 1decc46b..b9dfb97c 100644 --- a/App/driver/keyboard.h +++ b/App/driver/keyboard.h @@ -45,6 +45,17 @@ enum KEY_Code_e { }; typedef enum KEY_Code_e KEY_Code_t; +typedef enum { + STATE_IDLE = 0, + STATE_KA_1, + STATE_KA_2, + STATE_KA_3, + STATE_KEY_1, + STATE_KEY_2, + STATE_KEY_3, + STATE_KEY_3L, +} ParseState_t; + extern KEY_Code_t gKeyReading0; extern KEY_Code_t gKeyReading1; extern uint16_t gDebounceCounter; @@ -54,11 +65,9 @@ extern bool gWasFKeyPressed; // Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll). extern volatile KEY_Code_t gKeyFromSerial; -// Inject a short press received from serial (UART or VCP). -void KEYBOARD_InjectKey(uint8_t keyCode); - -// Inject a long press received from serial (UART or VCP). -void KEYBOARD_InjectKeyLong(uint8_t keyCode); +// Inject a short or long press received from serial (UART or VCP). +void KEYBOARD_InjectKey(uint8_t keyCode, bool keyLong); +bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b); #endif KEY_Code_t KEYBOARD_Poll(void); diff --git a/App/driver/uart.c b/App/driver/uart.c index 73c87d6b..b9b0e2a6 100644 --- a/App/driver/uart.c +++ b/App/driver/uart.c @@ -25,9 +25,6 @@ #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT #include "driver/keyboard.h" -// Packet types for serial key injection (K5Viewer → radio) -#define UART_TYPE_KEY 0x03 -#define UART_TYPE_KEY_LONG 0x04 #endif #define USARTx USART1 @@ -155,17 +152,6 @@ void UART_LogSend(const void *pBuffer, uint32_t Size) #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT bool UART_IsCableConnected(void) { - typedef enum { - STATE_IDLE = 0, - STATE_KA_1, - STATE_KA_2, - STATE_KA_3, - STATE_KEY_1, - STATE_KEY_2, - STATE_KEY_3, - STATE_KEY_3L, - } ParseState_t; - static uint8_t read_ptr = 0; static ParseState_t state = STATE_IDLE; @@ -182,44 +168,8 @@ void UART_LogSend(const void *pBuffer, uint32_t Size) // read_ptr wraps naturally at 256 since it's uint8_t processed++; - switch (state) - { - case STATE_IDLE: - if (b == 0x55) state = STATE_KA_1; - else if (b == 0xAA) state = STATE_KEY_1; - break; - case STATE_KA_1: - state = (b == 0xAA) ? STATE_KA_2 : STATE_IDLE; - break; - case STATE_KA_2: - state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE; - break; - case STATE_KA_3: - if (b == 0x00) connected = true; - state = STATE_IDLE; - break; - case STATE_KEY_1: - state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE; - break; - case STATE_KEY_2: - if (b == UART_TYPE_KEY) state = STATE_KEY_3; - else if (b == UART_TYPE_KEY_LONG) state = STATE_KEY_3L; - else state = STATE_IDLE; - break; - case STATE_KEY_3: - KEYBOARD_InjectKey(b); - connected = true; - state = STATE_IDLE; - break; - case STATE_KEY_3L: - KEYBOARD_InjectKeyLong(b); - connected = true; - state = STATE_IDLE; - break; - default: - state = STATE_IDLE; - break; - } + if(KEYBOARD_ProcessProtocolByte(&state, b)) + connected = true; } return connected; diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 6082fc72..f9ccbb5e 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -20,9 +20,6 @@ #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT #include "driver/keyboard.h" -// Packet types for serial key injection (K5Viewer → radio) -#define VCP_TYPE_KEY 0x03 -#define VCP_TYPE_KEY_LONG 0x04 #endif uint8_t VCP_RxBuf[VCP_RX_BUF_SIZE]; @@ -63,19 +60,8 @@ bool VCP_ScreenshotPing(void) // KEY_1 → 0x55 → KEY_2 (else IDLE) // KEY_2 → 0x03 → KEY_3 (short press, else check long) // KEY_2 → 0x04 → KEY_3L (long press, else IDLE) - // KEY_3 → → InjectKey(b), IDLE - // KEY_3L → → InjectKeyLong(b), IDLE - - typedef enum { - STATE_IDLE = 0, - STATE_KA_1, - STATE_KA_2, - STATE_KA_3, - STATE_KEY_1, - STATE_KEY_2, - STATE_KEY_3, - STATE_KEY_3L, - } ParseState_t; + // KEY_3 → → KEYBOARD_InjectKey(b, false), IDLE + // KEY_3L → → KEYBOARD_InjectKey(b, true), IDLE static uint32_t read_ptr = 0; static ParseState_t state = STATE_IDLE; @@ -91,57 +77,18 @@ bool VCP_ScreenshotPing(void) while (read_ptr != write_ptr && processed < VCP_RX_BUF_SIZE) { uint8_t b = VCP_RxBuf[read_ptr]; + + // Clear the old byte after reading - prevents the button from being + // pressed automatically after a certain amount of time. + VCP_RxBuf[read_ptr] = 0x00; + read_ptr++; if (read_ptr >= VCP_RX_BUF_SIZE) read_ptr = 0; processed++; - switch (state) - { - case STATE_IDLE: - if (b == 0x55) state = STATE_KA_1; - else if (b == 0xAA) state = STATE_KEY_1; - break; - - case STATE_KA_1: - state = (b == 0xAA) ? STATE_KA_2 : STATE_IDLE; - break; - - case STATE_KA_2: - state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE; - break; - - case STATE_KA_3: - if (b == 0x00) connected = true; - state = STATE_IDLE; - break; - - case STATE_KEY_1: - state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE; - break; - - case STATE_KEY_2: - if (b == VCP_TYPE_KEY) state = STATE_KEY_3; - else if (b == VCP_TYPE_KEY_LONG) state = STATE_KEY_3L; - else state = STATE_IDLE; - break; - - case STATE_KEY_3: - KEYBOARD_InjectKey(b); - connected = true; - state = STATE_IDLE; - break; - - case STATE_KEY_3L: - KEYBOARD_InjectKeyLong(b); - connected = true; - state = STATE_IDLE; - break; - - default: - state = STATE_IDLE; - break; - } + if(KEYBOARD_ProcessProtocolByte(&state, b)) + connected = true; } return connected; diff --git a/App/misc.c b/App/misc.c index b923617e..372d3708 100644 --- a/App/misc.c +++ b/App/misc.c @@ -634,4 +634,16 @@ void MR_PrintCacheStats(void) // cache_hits, cache_misses, MR_GetCacheHitRate()); } +#endif + +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT + bool SCREENSHOT_IsLocked(void) + { + if (gUART_LockScreenshot > 0) { + gUART_LockScreenshot--; + return true; + } + + return false; + } #endif \ No newline at end of file diff --git a/App/misc.h b/App/misc.h index d9179a93..7b1df678 100644 --- a/App/misc.h +++ b/App/misc.h @@ -323,6 +323,8 @@ extern volatile bool gTxTimeoutReached; #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT extern volatile uint8_t gUART_LockScreenshot; // lock screenshot if Chirp is used extern bool gUSB_ScreenshotEnabled; + + bool SCREENSHOT_IsLocked(void); #endif #endif diff --git a/App/screenshot.c b/App/screenshot.c index 7fd1544d..bfef82e8 100644 --- a/App/screenshot.c +++ b/App/screenshot.c @@ -26,17 +26,19 @@ // - No currentFrame or deltaFrame static buffers static uint8_t previousFrame[1024] = {0}; static uint8_t forcedBlock = 0; -static uint8_t keepAlive = 10; -static bool wasConnected = false; +static uint8_t keepAlive = 0; void SCREENSHOT_ParseInput(void) { + if (SCREENSHOT_IsLocked()) + return; + if (UART_IsCableConnected()) { - keepAlive = 10; + keepAlive = 7; gUSB_ScreenshotEnabled = false; } - if (VCP_ScreenshotPing()) { - keepAlive = 10; + else if (VCP_ScreenshotPing()) { + keepAlive = 7; gUSB_ScreenshotEnabled = true; } } @@ -58,21 +60,10 @@ void SCREENSHOT_Update(bool force) uint16_t index = 0; uint8_t acc = 0; uint8_t bitCount = 0; + static bool wasConnected = false; - if (gUART_LockScreenshot > 0) { - gUART_LockScreenshot--; + if (SCREENSHOT_IsLocked()) return; - } - - if (UART_IsCableConnected()) { - keepAlive = 10; - gUSB_ScreenshotEnabled = false; - } - - if (VCP_ScreenshotPing()) { - keepAlive = 10; - gUSB_ScreenshotEnabled = true; - } if (keepAlive > 0) { if (--keepAlive == 0) { @@ -97,7 +88,7 @@ void SCREENSHOT_Update(bool force) uint8_t bit = (gStatusLine[i] >> b) & 0x01; acc |= (bit << bitCount++); if (bitCount == 8) { - frameBuffer[index++] = acc; + frameBuffer[index++] = gSetting_set_inv ? ~acc : acc; acc = 0; bitCount = 0; } @@ -111,7 +102,7 @@ void SCREENSHOT_Update(bool force) uint8_t bit = (gFrameBuffer[l][i] >> b) & 0x01; acc |= (bit << bitCount++); if (bitCount == 8) { - frameBuffer[index++] = acc; + frameBuffer[index++] = gSetting_set_inv ? ~acc : acc; acc = 0; bitCount = 0; } diff --git a/App/ui/fmradio.c b/App/ui/fmradio.c index fc1e3096..02dd5920 100644 --- a/App/ui/fmradio.c +++ b/App/ui/fmradio.c @@ -35,6 +35,12 @@ void UI_DisplayFM(void) char *pPrintStr = String; UI_DisplayClear(); +#ifdef ENABLE_FEAT_F4HWN + if (gEeprom.KEY_LOCK && gKeypadLocked > 0) { + UI_PrintStringSmallBold("UNLOCK KEYBOARD", 12, 0, 5); + } +#endif + UI_PrintString("FM", 2, 0, 0, 8); sprintf(String, "%d%s-%dM", diff --git a/App/ui/status.c b/App/ui/status.c index cb37526e..b2a97bc9 100644 --- a/App/ui/status.c +++ b/App/ui/status.c @@ -173,31 +173,32 @@ void UI_DisplayStatus() else #endif { - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if(gEeprom.MENU_LOCK == true) { - memcpy(line + x + 2, gFontRO, sizeof(gFontRO)); - } - else - { - #endif - uint8_t dw = (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) + (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF) * 2; - if(dw == 1 || dw == 3) { // DWR - dual watch + respond - if(gDualWatchActive) - memcpy(line + x + (dw==1?0:2), gFontDWR, sizeof(gFontDWR) - (dw==1?0:5)); - else - memcpy(line + x + 3, gFontHold, sizeof(gFontHold)); - } - else if(dw == 2) { // XB - crossband - memcpy(line + x + 2, gFontXB, sizeof(gFontXB)); + if(!gAirCopyBootMode) { + #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS + if(gEeprom.MENU_LOCK == true) { + memcpy(line + x + 2, gFontRO, sizeof(gFontRO)); } else { - if(!gAirCopyBootMode) + #endif + uint8_t dw = (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) + (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF) * 2; + if(dw == 1 || dw == 3) { // DWR - dual watch + respond + if(gDualWatchActive) + memcpy(line + x + (dw==1?0:2), gFontDWR, sizeof(gFontDWR) - (dw==1?0:5)); + else + memcpy(line + x + 3, gFontHold, sizeof(gFontHold)); + } + else if(dw == 2) { // XB - crossband + memcpy(line + x + 2, gFontXB, sizeof(gFontXB)); + } + else + { memcpy(line + x + 2, gFontMO, sizeof(gFontMO)); + } + #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS } - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS + #endif } - #endif } } x += sizeof(gFontDWR) + 3; @@ -239,15 +240,8 @@ void UI_DisplayStatus() size = sizeof(gFontKeyLock); } else if (gWasFKeyPressed) { - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - if (!gEeprom.MENU_LOCK) { - src = gFontF; - size = sizeof(gFontF); - } - #else src = gFontF; size = sizeof(gFontF); - #endif } #ifdef ENABLE_FEAT_F4HWN else if (gMute) { diff --git a/App/ui/welcome.c b/App/ui/welcome.c index 0cecf63b..d94057fe 100644 --- a/App/ui/welcome.c +++ b/App/ui/welcome.c @@ -221,8 +221,8 @@ void UI_DisplayWelcome(void) //ST7565_BlitStatusLine(); // blank status line : I think it's useless ST7565_BlitFullScreen(); - #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT - SCREENSHOT_Update(true); - #endif + // #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT + // SCREENSHOT_Update(true); // I think that's useless too + // #endif } } \ No newline at end of file From f4d8abd7d93a12098c131e58034d1ef5869dd30a Mon Sep 17 00:00:00 2001 From: mrkusypl Date: Mon, 16 Mar 2026 14:57:57 +0100 Subject: [PATCH 2/4] Increase the keepAlive value, fix the CHIRP connection while maintaining the fix for the button getting stuck --- App/driver/vcp.c | 13 ++++++++----- App/screenshot.c | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/App/driver/vcp.c b/App/driver/vcp.c index f9ccbb5e..054dcf41 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -78,17 +78,20 @@ bool VCP_ScreenshotPing(void) { uint8_t b = VCP_RxBuf[read_ptr]; - // Clear the old byte after reading - prevents the button from being - // pressed automatically after a certain amount of time. - VCP_RxBuf[read_ptr] = 0x00; - read_ptr++; if (read_ptr >= VCP_RX_BUF_SIZE) read_ptr = 0; processed++; - if(KEYBOARD_ProcessProtocolByte(&state, b)) + if(KEYBOARD_ProcessProtocolByte(&state, b)) { + // Clear the last 4 bytes after reading - prevents the button from + // being pressed automatically after a certain amount of time. + for (uint8_t i = 1; i <= 4; i++) { + VCP_RxBuf[(write_ptr - i) & 0xFF] = 0x00; + } + connected = true; + } } return connected; diff --git a/App/screenshot.c b/App/screenshot.c index bfef82e8..09fe54e2 100644 --- a/App/screenshot.c +++ b/App/screenshot.c @@ -34,11 +34,11 @@ void SCREENSHOT_ParseInput(void) return; if (UART_IsCableConnected()) { - keepAlive = 7; + keepAlive = 15; gUSB_ScreenshotEnabled = false; } else if (VCP_ScreenshotPing()) { - keepAlive = 7; + keepAlive = 15; gUSB_ScreenshotEnabled = true; } } From 67231fd326fae26fed24aa7d8bb8be649c0958d9 Mon Sep 17 00:00:00 2001 From: mrkusypl Date: Mon, 16 Mar 2026 15:04:45 +0100 Subject: [PATCH 3/4] Replacing 'write_ptr' with 'read_ptr' when deleting old bytes --- App/driver/vcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 054dcf41..8e136d9c 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -87,7 +87,7 @@ bool VCP_ScreenshotPing(void) // Clear the last 4 bytes after reading - prevents the button from // being pressed automatically after a certain amount of time. for (uint8_t i = 1; i <= 4; i++) { - VCP_RxBuf[(write_ptr - i) & 0xFF] = 0x00; + VCP_RxBuf[(read_ptr - i) & 0xFF] = 0x00; } connected = true; From 315d7a6b591c9e94a0cc99b0d4f8072fd0779b8b Mon Sep 17 00:00:00 2001 From: mrkusypl Date: Mon, 16 Mar 2026 23:57:17 +0100 Subject: [PATCH 4/4] An even better way to fix a button that keeps getting stuck in K5Viewer --- App/driver/vcp.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 8e136d9c..021b6359 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -63,11 +63,11 @@ bool VCP_ScreenshotPing(void) // KEY_3 → → KEYBOARD_InjectKey(b, false), IDLE // KEY_3L → → KEYBOARD_InjectKey(b, true), IDLE - static uint32_t read_ptr = 0; + static uint8_t read_ptr = 0; static ParseState_t state = STATE_IDLE; bool connected = false; - uint32_t write_ptr = VCP_RxBufPointer; // snapshot once — ISR may update concurrently + uint8_t write_ptr = VCP_RxBufPointer; // snapshot once — ISR may update concurrently // Cap bytes processed per call to VCP_RX_BUF_SIZE. // Prevents unbounded loop if the ISR write pointer laps read_ptr @@ -83,15 +83,8 @@ bool VCP_ScreenshotPing(void) read_ptr = 0; processed++; - if(KEYBOARD_ProcessProtocolByte(&state, b)) { - // Clear the last 4 bytes after reading - prevents the button from - // being pressed automatically after a certain amount of time. - for (uint8_t i = 1; i <= 4; i++) { - VCP_RxBuf[(read_ptr - i) & 0xFF] = 0x00; - } - + if(KEYBOARD_ProcessProtocolByte(&state, b)) connected = true; - } } return connected;