From 637258ffbb08801f1a7675b8a81ad2a3c26b17fb Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:03:51 +0300 Subject: [PATCH 01/16] Changing zoom global mic shortcut to avoid conflict with terminal --- applications/system/hid_app/views/hid_ptt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 8a28cd292..e9b1849a0 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -128,9 +128,9 @@ static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) { // zoom global macos static void hid_ptt_trigger_mute_macos_zoom_global(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press( - hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_N); hid_hal_keyboard_release( - hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_N); } static void hid_ptt_trigger_camera_macos_zoom_global(HidPushToTalk* hid_ptt) { From fe66188541eb56a1a6d283b52c0e1d35413e7b07 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:38:23 +0300 Subject: [PATCH 02/16] Adding status bar --- applications/system/hid_app/views/hid_ptt.c | 115 +++++++++++++++----- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index e9b1849a0..b688de89c 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -1,8 +1,11 @@ #include "hid_ptt.h" #include "hid_ptt_menu.h" #include +#include #include #include +#include +#include #include "../hid.h" #include "../views.h" @@ -673,43 +676,94 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } +static void hid_ptt_draw_status_bar(Canvas* canvas, bool connected) { + char time_str[16]; + DateTime dt; + furi_hal_rtc_get_datetime(&dt); + + uint8_t hour = dt.hour; + if(locale_get_time_format() == LocaleTimeFormat12h) { + if(hour > 12) { + hour -= 12; + } + if(hour == 0) { + hour = 12; + } + } + snprintf(time_str, sizeof(time_str), "%02u:%02u", hour, dt.minute); + + uint8_t battery = furi_hal_power_get_pct(); + if(battery > 100) { + battery = 100; + } + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 64, 11); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); + canvas_draw_line(canvas, 1, 9, 62, 9); + + if(connected) { + canvas_draw_icon(canvas, 1, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 1, 0, &I_Ble_disconnected_15x15); + } + + const uint8_t battery_x = 47; + const uint8_t battery_y = 2; + const uint8_t battery_w = 13; + const uint8_t battery_h = 6; + canvas_draw_frame(canvas, battery_x, battery_y, battery_w, battery_h); + canvas_draw_box(canvas, battery_x + battery_w, battery_y + 2, 1, 2); + canvas_draw_box(canvas, battery_x + 1, battery_y + 1, ((battery_w - 2) * battery) / 100, 4); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 31, 8, AlignCenter, AlignBottom, time_str); +} + static void hid_ptt_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkModel* model = context; - // Header - canvas_set_font(canvas, FontPrimary); -#ifdef HID_TRANSPORT_BLE - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); - } -#endif - - // OS and App labels - canvas_set_font(canvas, FontSecondary); - hid_ptt_draw_text_centered(canvas, 73, model->app); - hid_ptt_draw_text_centered(canvas, 84, model->os); - - // Help label - canvas_draw_icon(canvas, 0, 88, &I_Help_top_64x17); - canvas_draw_line(canvas, 4, 105, 4, 114); - canvas_draw_line(canvas, 63, 105, 63, 114); - canvas_draw_icon(canvas, 7, 107, &I_Hold_15x5); - canvas_draw_icon(canvas, 24, 105, &I_BtnLeft_9x9); - canvas_draw_icon(canvas, 34, 108, &I_for_help_27x5); - canvas_draw_icon(canvas, 0, 115, &I_Help_exit_64x9); - canvas_draw_icon(canvas, 24, 115, &I_BtnBackV_9x9); + const uint8_t top_offset = 11; + const uint8_t helper_top_y = 92; const uint8_t x_1 = 0; const uint8_t x_2 = x_1 + 19 + 4; const uint8_t x_3 = x_1 + 19 * 2 + 8; - const uint8_t y_1 = 3; + const uint8_t y_1 = top_offset + 3; const uint8_t y_2 = y_1 + 19; const uint8_t y_3 = y_2 + 19; + const uint8_t controls_bottom_y = y_3 + 18; + const uint8_t labels_center_y = (controls_bottom_y + helper_top_y) / 2; + const uint8_t app_label_y = labels_center_y - 5; + const uint8_t os_label_y = app_label_y + 11; + + // Header + canvas_set_font(canvas, FontPrimary); +#ifdef HID_TRANSPORT_BLE + hid_ptt_draw_status_bar(canvas, model->connected); +#else + hid_ptt_draw_status_bar(canvas, false); +#endif + + // OS and App labels + canvas_set_font(canvas, FontSecondary); + hid_ptt_draw_text_centered(canvas, app_label_y, model->app); + hid_ptt_draw_text_centered(canvas, os_label_y, model->os); + + // Help label + canvas_draw_icon(canvas, 0, helper_top_y, &I_Help_top_64x17); + canvas_draw_line(canvas, 4, 109, 4, 118); + canvas_draw_line(canvas, 63, 109, 63, 118); + canvas_draw_icon(canvas, 7, 111, &I_Hold_15x5); + canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9); + canvas_draw_icon(canvas, 34, 112, &I_for_help_27x5); + canvas_draw_icon(canvas, 0, 119, &I_Help_exit_64x9); + canvas_draw_icon(canvas, 24, 119, &I_BtnBackV_9x9); + // Up canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); if(model->up_pressed) { @@ -756,23 +810,24 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Back / Mic const uint8_t x_mic = x_3; - canvas_draw_icon(canvas, x_mic, 0, &I_RoundButtonUnpressed_16x16); + const uint8_t y_mic = top_offset; + canvas_draw_icon(canvas, x_mic, y_mic, &I_RoundButtonUnpressed_16x16); if(!(!model->muted || (model->ptt_pressed))) { // show muted if(model->mic_pressed) { // canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressedCrossed_15x15); - canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedCrossedBtn_16x16); + canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophonePressedCrossedBtn_16x16); } else { - canvas_draw_icon(canvas, x_mic, 0, &I_MicrophoneCrossed_16x16); + canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophoneCrossed_16x16); } } else { // show unmuted if(model->mic_pressed) { // canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressed_15x15); - canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedBtn_16x16); + canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophonePressedBtn_16x16); } else { - canvas_draw_icon(canvas, x_mic + 5, 2, &I_Mic_7x11); + canvas_draw_icon(canvas, x_mic + 5, y_mic + 2, &I_Mic_7x11); } } From 74c69f74eabcd658f05771f576543bcb96155af8 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:50:59 +0300 Subject: [PATCH 03/16] Fixing text position --- applications/system/hid_app/views/hid_ptt.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index b688de89c..0c9ed837b 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -726,6 +726,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { HidPushToTalkModel* model = context; const uint8_t top_offset = 11; + const uint8_t status_bar_bottom_y = top_offset; const uint8_t helper_top_y = 92; const uint8_t x_1 = 0; @@ -738,7 +739,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t controls_bottom_y = y_3 + 18; const uint8_t labels_center_y = (controls_bottom_y + helper_top_y) / 2; - const uint8_t app_label_y = labels_center_y - 5; + const uint8_t app_label_y = labels_center_y - 1; const uint8_t os_label_y = app_label_y + 11; // Header @@ -810,7 +811,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Back / Mic const uint8_t x_mic = x_3; - const uint8_t y_mic = top_offset; + const uint8_t y_mic = status_bar_bottom_y + ((y_2 - status_bar_bottom_y - 16) / 2); canvas_draw_icon(canvas, x_mic, y_mic, &I_RoundButtonUnpressed_16x16); if(!(!model->muted || (model->ptt_pressed))) { From a9438319d60d11b852eb595961d3b0c26b0938c2 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:08:10 +0300 Subject: [PATCH 04/16] Moving BT icon to status bar --- applications/system/hid_app/views/hid_ptt.c | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 0c9ed837b..f4f476e31 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -676,7 +676,19 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_status_bar(Canvas* canvas, bool connected) { +static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { + // Home-like compact Bluetooth glyph with activity line and end dot. + canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); + canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); + canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + + canvas_draw_line(canvas, x + 6, y + 4, x + 12, y + 4); + canvas_draw_disc(canvas, x + 14, y + 4, 1); +} + +static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; furi_hal_rtc_get_datetime(&dt); @@ -703,10 +715,8 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool connected) { canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); canvas_draw_line(canvas, 1, 9, 62, 9); - if(connected) { - canvas_draw_icon(canvas, 1, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 1, 0, &I_Ble_disconnected_15x15); + if(show_bt && connected) { + hid_ptt_draw_bt_connected(canvas, 3, 1); } const uint8_t battery_x = 47; @@ -745,9 +755,9 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Header canvas_set_font(canvas, FontPrimary); #ifdef HID_TRANSPORT_BLE - hid_ptt_draw_status_bar(canvas, model->connected); + hid_ptt_draw_status_bar(canvas, true, model->connected); #else - hid_ptt_draw_status_bar(canvas, false); + hid_ptt_draw_status_bar(canvas, false, false); #endif // OS and App labels From 3457a32621ef05aa5edd961fed789cd7a9423690 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:23:53 +0300 Subject: [PATCH 05/16] Fixing bt status --- applications/system/hid_app/views/hid_ptt.c | 35 ++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index f4f476e31..999888641 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -679,15 +679,24 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { // Home-like compact Bluetooth glyph with activity line and end dot. canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); - canvas_draw_line(canvas, x + 6, y + 4, x + 12, y + 4); + canvas_draw_line(canvas, x + 7, y + 4, x + 12, y + 4); canvas_draw_disc(canvas, x + 14, y + 4, 1); } +static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { + // Home-like disconnected icon: only Bluetooth glyph. + canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); + canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); + canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); +} + static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; @@ -710,17 +719,21 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected } canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 0, 0, 64, 11); + canvas_draw_box(canvas, 0, 0, 64, 13); canvas_set_color(canvas, ColorBlack); - canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); - canvas_draw_line(canvas, 1, 9, 62, 9); + canvas_draw_rframe(canvas, 0, 0, 64, 13, 1); + canvas_draw_line(canvas, 1, 11, 62, 11); - if(show_bt && connected) { - hid_ptt_draw_bt_connected(canvas, 3, 1); + if(show_bt) { + if(connected) { + hid_ptt_draw_bt_connected(canvas, 3, 2); + } else { + hid_ptt_draw_bt_idle(canvas, 3, 2); + } } const uint8_t battery_x = 47; - const uint8_t battery_y = 2; + const uint8_t battery_y = 3; const uint8_t battery_w = 13; const uint8_t battery_h = 6; canvas_draw_frame(canvas, battery_x, battery_y, battery_w, battery_h); @@ -728,14 +741,14 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected canvas_draw_box(canvas, battery_x + 1, battery_y + 1, ((battery_w - 2) * battery) / 100, 4); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 31, 8, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 31, 9, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkModel* model = context; - const uint8_t top_offset = 11; + const uint8_t top_offset = 13; const uint8_t status_bar_bottom_y = top_offset; const uint8_t helper_top_y = 92; From 0f6a6d1a52659d0eec9f2f9b258a83255745110c Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:27:41 +0300 Subject: [PATCH 06/16] Fixing clock in status bar --- applications/system/hid_app/views/hid_ptt.c | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 999888641..b465d7848 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -676,25 +676,28 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { - // Home-like compact Bluetooth glyph with activity line and end dot. +static void hid_ptt_draw_bt_glyph(Canvas* canvas, uint8_t x, uint8_t y) { + // Compact 5x8 Bluetooth rune. canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); +} - canvas_draw_line(canvas, x + 7, y + 4, x + 12, y + 4); - canvas_draw_disc(canvas, x + 14, y + 4, 1); +static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { + // Connected icon: rune + activity pixels/line + end circle. + hid_ptt_draw_bt_glyph(canvas, x, y); + + canvas_draw_dot(canvas, x + 6, y + 4); + canvas_draw_dot(canvas, x + 8, y + 4); + canvas_draw_line(canvas, x + 10, y + 4, x + 12, y + 4); + canvas_draw_circle(canvas, x + 14, y + 4, 1); } static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { - // Home-like disconnected icon: only Bluetooth glyph. - canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); - canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); + // Disconnected icon: only Bluetooth rune. + hid_ptt_draw_bt_glyph(canvas, x, y); } static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { @@ -741,7 +744,7 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected canvas_draw_box(canvas, battery_x + 1, battery_y + 1, ((battery_w - 2) * battery) / 100, 4); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 31, 9, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 31, 10, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { From a8de70b08d5ecbc8705173e6fd4dfe139444836c Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:40:02 +0300 Subject: [PATCH 07/16] Fixing bt icon and crash on bt disconnect --- applications/system/hid_app/views/hid_ptt.c | 40 ++++++++------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index b465d7848..92e2afc9c 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -13,6 +13,18 @@ #define TAG "HidPushToTalk" +// Exact home status-bar Bluetooth icon pixels (from assets/icons/StatusBar). +// Bitmap format for canvas_draw_bitmap(): row-major, 1-bit. +// Full compressed frame data including leading 0x00 heatshrink marker, +// copied verbatim from build/f7-firmware-D/assets/compiled/assets_icons.c +static const uint8_t hid_ptt_bluetooth_connected_16x8_bits[] = { + 0x00, 0x04, 0x00, 0x0d, 0x00, 0x16, 0x60, 0x4c, + 0x97, 0x4c, 0x97, 0x16, 0x60, 0x0d, 0x00, 0x04, 0x00, +}; +static const uint8_t hid_ptt_bluetooth_idle_5x8_bits[] = { + 0x00, 0x04, 0x0d, 0x16, 0x0c, 0x0c, 0x16, 0x0d, 0x04, +}; + struct HidPushToTalk { View* view; Hid* hid; @@ -676,30 +688,6 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_bt_glyph(Canvas* canvas, uint8_t x, uint8_t y) { - // Compact 5x8 Bluetooth rune. - canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); - canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); -} - -static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { - // Connected icon: rune + activity pixels/line + end circle. - hid_ptt_draw_bt_glyph(canvas, x, y); - - canvas_draw_dot(canvas, x + 6, y + 4); - canvas_draw_dot(canvas, x + 8, y + 4); - canvas_draw_line(canvas, x + 10, y + 4, x + 12, y + 4); - canvas_draw_circle(canvas, x + 14, y + 4, 1); -} - -static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { - // Disconnected icon: only Bluetooth rune. - hid_ptt_draw_bt_glyph(canvas, x, y); -} - static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; @@ -729,9 +717,9 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected if(show_bt) { if(connected) { - hid_ptt_draw_bt_connected(canvas, 3, 2); + canvas_draw_bitmap(canvas, 3, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); } else { - hid_ptt_draw_bt_idle(canvas, 3, 2); + canvas_draw_bitmap(canvas, 3, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); } } From 3a6d7173f4909b9fc8f0bde024dcac3c1657bf1d Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:45:13 +0300 Subject: [PATCH 08/16] Fixing overlapping icons in the statusbar --- applications/system/hid_app/views/hid_ptt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 92e2afc9c..a72a2420a 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -717,13 +717,13 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected if(show_bt) { if(connected) { - canvas_draw_bitmap(canvas, 3, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); + canvas_draw_bitmap(canvas, 2, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); } else { - canvas_draw_bitmap(canvas, 3, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); + canvas_draw_bitmap(canvas, 2, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); } } - const uint8_t battery_x = 47; + const uint8_t battery_x = 48; const uint8_t battery_y = 3; const uint8_t battery_w = 13; const uint8_t battery_h = 6; @@ -732,7 +732,7 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected canvas_draw_box(canvas, battery_x + 1, battery_y + 1, ((battery_w - 2) * battery) / 100, 4); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 31, 10, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 33, 10, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { From 02d8a12924f5bfa0145412cd41d386b2ceffe90e Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:03:16 +0300 Subject: [PATCH 09/16] Moving help to app selection menu --- applications/system/hid_app/views/hid_ptt.c | 154 ++++++++++-------- .../system/hid_app/views/hid_ptt_menu.c | 40 ++++- .../system/hid_app/views/hid_ptt_menu.h | 12 ++ 3 files changed, 133 insertions(+), 73 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index a72a2420a..27b1d211f 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -402,6 +402,83 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); } + static void hid_ptt_populate_help(HidPushToTalk* hid_ptt, uint32_t appIndex) { + widget_reset(hid_ptt->help); + char* app_specific_help = ""; + switch(appIndex) { + case HidPushToTalkAppIndexGoogleMeet: + app_specific_help = + "Google Meet:\n" + "This feature is off by default in your audio settings " + "and may not work for Windows users who use their screen " + "reader. In this situation, the spacebar performs a different action.\n\n"; + break; + case HidPushToTalkAppIndexGoogleMeetGlobal: + app_specific_help = "Google Meet (Global):\n" + "1. Install \"Google Meet - Global Shortcuts\" extension.\n" + "2. Open chrome://extensions/shortcuts.\n" + "3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n" + "4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n" + "5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n"; + break; + case HidPushToTalkAppIndexDiscord: + app_specific_help = + "Discord:\n" + "1. Under App Settings, click Voice & Video. Under Input Mode, " + "check the box next to Push to Talk.\n" + "2. Scroll down to SHORTCUT, click Record Keybinder.\n" + "3. Press PTT in the app to bind it." + "4. Go to Keybinds and assign mute button.\n\n"; + break; + case HidPushToTalkAppIndexTeamSpeak: + app_specific_help = "TeamSpeak:\n" + "To make keys working bind them in TeamSpeak settings.\n\n"; + break; + case HidPushToTalkAppIndexTeams: + app_specific_help = + "Teams:\n" + "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"; + break; + case HidPushToTalkAppIndexZoomGlobal: + app_specific_help = "Zoom (Global):\n" + "1. Go to Settings > Keyboard Shortcuts.\n" + "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" + "3. Press the Mute button in the app to bind it.\n" + "4. Check global checkbox.\n" + "5. Repeat for video and hand shortcuts.\n\n"; + break; + } + FuriString* msg = furi_string_alloc(); + furi_string_cat_printf( + msg, + "%sGeneral:\n" + "To operate properly flipper microphone " + "status must be in sync with your computer.\n" + "Hold > to change mic status.\n" + "Long-press OK in menu to open this help.\n" + "Press BACK to switch mic on/off.\n" + "Hold 'o' for PTT mode (mic will be off once you release 'o')\n" + "Hold BACK to exit.", + app_specific_help); + widget_add_text_scroll_element(hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg)); + furi_string_free(msg); + } + + static void hid_ptt_menu_help_callback( + void* context, + uint32_t osIndex, + FuriString* osLabel, + uint32_t appIndex, + FuriString* appLabel) { + UNUSED(osIndex); + UNUSED(osLabel); + UNUSED(appLabel); + furi_assert(context); + HidPushToTalk* hid_ptt = context; + hid_ptt_populate_help(hid_ptt, appIndex); + view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp); + } + static void hid_ptt_menu_callback( void* context, uint32_t osIndex, @@ -609,67 +686,9 @@ static void hid_ptt_menu_callback( } } - char* app_specific_help = ""; - switch(appIndex) { - case HidPushToTalkAppIndexGoogleMeet: - app_specific_help = - "Google Meet:\n" - "This feature is off by default in your audio settings " - "and may not work for Windows users who use their screen " - "reader. In this situation, the spacebar performs a different action.\n\n"; - break; - case HidPushToTalkAppIndexGoogleMeetGlobal: - app_specific_help = "Google Meet (Global):\n" - "1. Install \"Google Meet - Global Shortcuts\" extension.\n" - "2. Open chrome://extensions/shortcuts.\n" - "3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n" - "4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n" - "5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n"; - break; - case HidPushToTalkAppIndexDiscord: - app_specific_help = - "Discord:\n" - "1. Under App Settings, click Voice & Video. Under Input Mode, " - "check the box next to Push to Talk.\n" - "2. Scroll down to SHORTCUT, click Record Keybinder.\n" - "3. Press PTT in the app to bind it." - "4. Go to Keybinds and assign mute button.\n\n"; - break; - case HidPushToTalkAppIndexTeamSpeak: - app_specific_help = "TeamSpeak:\n" - "To make keys working bind them in TeamSpeak settings.\n\n"; - break; - case HidPushToTalkAppIndexTeams: - app_specific_help = - "Teams:\n" - "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"; - break; - case HidPushToTalkAppIndexZoomGlobal: - app_specific_help = "Zoom (Global):\n" - "1. Go to Settings > Keyboard Shortcuts.\n" - "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" - "3. Press the Mute button in the app to bind it.\n" - "4. Check global checkbox.\n" - "5. Repeat for video and hand shortcuts.\n\n"; - } - - FuriString* msg = furi_string_alloc(); - furi_string_cat_printf( - msg, - "%sGeneral:\n" - "To operate properly flipper microphone " - "status must be in sync with your computer.\n" - "Hold > to change mic status.\n" - "Hold < to open this help.\n" - "Press BACK to switch mic on/off.\n" - "Hold 'o' for PTT mode (mic will be off once you release 'o')\n" - "Hold BACK to exit.", - app_specific_help); - widget_add_text_scroll_element( - hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg)); - furi_string_free(msg); }, true); + hid_ptt_populate_help(hid_ptt, appIndex); view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk); } @@ -771,11 +790,6 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Help label canvas_draw_icon(canvas, 0, helper_top_y, &I_Help_top_64x17); - canvas_draw_line(canvas, 4, 109, 4, 118); - canvas_draw_line(canvas, 63, 109, 63, 118); - canvas_draw_icon(canvas, 7, 111, &I_Hold_15x5); - canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9); - canvas_draw_icon(canvas, 34, 112, &I_for_help_27x5); canvas_draw_icon(canvas, 0, 119, &I_Help_exit_64x9); canvas_draw_icon(canvas, 24, 119, &I_BtnBackV_9x9); @@ -935,11 +949,6 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeLong && event->key == InputKeyRight) { model->muted = !model->muted; notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); - } else if(event->type == InputTypeLong && event->key == InputKeyLeft) { - notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); - model->left_pressed = false; - view_dispatcher_switch_to_view( - hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp); } //LED if(!model->muted || (model->ptt_pressed)) { @@ -971,9 +980,9 @@ View* hid_ptt_get_view(HidPushToTalk* hid_ptt) { return hid_ptt->view; } -static uint32_t hid_ptt_view(void* context) { +static uint32_t hid_ptt_menu_view(void* context) { UNUSED(context); - return HidViewPushToTalk; + return HidViewPushToTalkMenu; } HidPushToTalk* hid_ptt_alloc(Hid* hid) { @@ -1204,9 +1213,10 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { hid_ptt); hid_ptt->help = widget_alloc(); - view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_view); + view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_menu_view); view_dispatcher_add_view( hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help)); + ptt_menu_set_long_ok_callback(hid->hid_ptt_menu, hid_ptt_menu_help_callback, hid_ptt); return hid_ptt; } diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 074c85ba7..439ca4732 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -10,6 +10,8 @@ struct HidPushToTalkMenu { View* view; Hid* hid; + PushToTalkMenuLongOkCallback long_ok_callback; + void* long_ok_callback_context; }; typedef struct { @@ -338,6 +340,39 @@ void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) { } } + void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) { + PushToTalkMenuList* list = NULL; + PushToTalkMenuItem* item = NULL; + with_view_model( + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { + list = &model->lists[model->list_position]; + const size_t items_size = PushToTalkMenuItemArray_size(list->items); + if(model->position < items_size) { + item = PushToTalkMenuItemArray_get(list->items, model->position); + } + }, + false); + if(item && list && hid_ptt_menu->long_ok_callback) { + hid_ptt_menu->long_ok_callback( + hid_ptt_menu->long_ok_callback_context, + list->index, + list->label, + item->index, + item->label); + } + } + + void ptt_menu_set_long_ok_callback( + HidPushToTalkMenu* hid_ptt_menu, + PushToTalkMenuLongOkCallback callback, + void* callback_context) { + furi_assert(hid_ptt_menu); + hid_ptt_menu->long_ok_callback = callback; + hid_ptt_menu->long_ok_callback_context = callback_context; + } + static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) { furi_assert(context); HidPushToTalkMenu* hid_ptt_menu = context; @@ -375,7 +410,10 @@ static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) { consumed = true; ptt_menu_process_down(hid_ptt_menu); } - } + } else if(event->type == InputTypeLong && event->key == InputKeyOk) { + consumed = true; + ptt_menu_process_long_ok(hid_ptt_menu); + } return consumed; } diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h index 2defd0569..df6fcb015 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.h +++ b/applications/system/hid_app/views/hid_ptt_menu.h @@ -12,6 +12,13 @@ typedef void (*PushToTalkMenuItemCallback)( uint32_t itemIndex, FuriString* itemLabel); + typedef void (*PushToTalkMenuLongOkCallback)( + void* context, + uint32_t listIndex, + FuriString* listLabel, + uint32_t itemIndex, + FuriString* itemLabel); + HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* bt_hid); void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu); @@ -27,3 +34,8 @@ void ptt_menu_add_item_to_list( void* callback_context); void ptt_menu_add_list(HidPushToTalkMenu* hid_ptt_menu, const char* label, uint32_t index); + +void ptt_menu_set_long_ok_callback( + HidPushToTalkMenu* hid_ptt_menu, + PushToTalkMenuLongOkCallback callback, + void* callback_context); From 093d611091caef18ba35b765ff1c5817523f21b2 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:17:24 +0300 Subject: [PATCH 10/16] Adding long press left to send enter for zoom --- applications/system/hid_app/views/hid_ptt.c | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 27b1d211f..9e566fb6e 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -445,8 +445,21 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" "3. Press the Mute button in the app to bind it.\n" "4. Check global checkbox.\n" - "5. Repeat for video and hand shortcuts.\n\n"; + "5. Repeat for video and hand shortcuts.\n" + "6. Long-press < to send Enter key.\n\n"; break; + case HidPushToTalkAppIndexZoom: + app_specific_help = "Zoom:\n" + "1. Go to Settings > Keyboard Shortcuts.\n" + "2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n" + "3. Press the Mute button in the app to bind it.\n" + "4. Repeat for video and hand shortcuts.\n" + "5. Long-press < to send Enter key.\n\n"; + break; + } + char* left_button_help = ""; + if(appIndex == HidPushToTalkAppIndexZoom || appIndex == HidPushToTalkAppIndexZoomGlobal) { + left_button_help = "Long-press < sends Enter.\n"; } FuriString* msg = furi_string_alloc(); furi_string_cat_printf( @@ -455,11 +468,13 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { "To operate properly flipper microphone " "status must be in sync with your computer.\n" "Hold > to change mic status.\n" + "%s" "Long-press OK in menu to open this help.\n" "Press BACK to switch mic on/off.\n" "Hold 'o' for PTT mode (mic will be off once you release 'o')\n" "Hold BACK to exit.", - app_specific_help); + app_specific_help, + left_button_help); widget_add_text_scroll_element(hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg)); furi_string_free(msg); } @@ -949,6 +964,14 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeLong && event->key == InputKeyRight) { model->muted = !model->muted; notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } else if(event->type == InputTypeLong && event->key == InputKeyLeft) { + if( + model->appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_RETURN); + hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_RETURN); + notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } } //LED if(!model->muted || (model->ptt_pressed)) { From dfd5867ed4a731ac0ef26e86e155267b95e9d0e4 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:29:19 +0300 Subject: [PATCH 11/16] Fixing lower helper for non-zoom apps --- applications/system/hid_app/views/hid_ptt.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 9e566fb6e..d26819aad 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -775,7 +775,14 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t top_offset = 13; const uint8_t status_bar_bottom_y = top_offset; - const uint8_t helper_top_y = 92; + + // For Zoom/Zoom Global, keep helper banner higher to show Enter key hint space + // For other apps, move it down to close the gap + uint8_t helper_top_y = 102; + if(model->appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + helper_top_y = 92; + } const uint8_t x_1 = 0; const uint8_t x_2 = x_1 + 19 + 4; From 0f877005e389b4ef1e43e39ffaab18bac31f5380 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:44:26 +0300 Subject: [PATCH 12/16] Fixing helper view for zoom --- applications/system/hid_app/assets/for_11x5.png | Bin 0 -> 335 bytes applications/system/hid_app/views/hid_ptt.c | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 applications/system/hid_app/assets/for_11x5.png diff --git a/applications/system/hid_app/assets/for_11x5.png b/applications/system/hid_app/assets/for_11x5.png new file mode 100644 index 0000000000000000000000000000000000000000..3ebc4f23c723185c368b7be348c653ef75427955 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^+(69A2qYMG-_`yNq!f}pf_xbms?-=58d?|_egTCV zUNA6}8Za=tN?>5Hn!&&zUNC1@pbb!hDaqU2g@N&Im+%rGkG;gx*OmPhi;Re>^xMDZ zSb#zT$%%etKw27zS%DY~yz;AO0U6?+E{-7_*OPbrZ%8a>K75UZ?IKItT}}prjjRjA zbDC!Zm8h1uMwFx^mZVxG7o`Fz1|tI_GhG7{T_cMSBLgdA6Dwm2Z36=<1A{-hrCul+ za`RI%(<(t4Omz(nbPX&+jEt-djjasKAsQZ3nC}H@(16=el9`)YT#}eufNqJol@U}A T_XhP?pdJQKS3j3^P6appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + canvas_draw_icon(canvas, 7, 111, &I_Hold_15x5); + canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9); + canvas_draw_icon(canvas, 35, 112, &I_for_11x5); + canvas_draw_icon(canvas, 48, 112, &I_Return_10x7); + } + // Up canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); if(model->up_pressed) { From b05dad33863b84339e96b2fce38bbeff4193b3d9 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:51:12 +0300 Subject: [PATCH 13/16] Adding helper info to the menu view --- .../system/hid_app/views/hid_ptt_menu.c | 197 ++++++++++++++---- 1 file changed, 160 insertions(+), 37 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 439ca4732..6c8b1fa65 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -2,16 +2,22 @@ #include "hid_ptt.h" #include #include +#include #include "../hid.h" #include "../views.h" +#include "hid_icons.h" + #define TAG "HidPushToTalkMenu" +#define PTT_MENU_HELP_HINT_DELAY_MS 5000U +#define PTT_MENU_HINT_TIMER_PERIOD_MS 150U struct HidPushToTalkMenu { View* view; Hid* hid; - PushToTalkMenuLongOkCallback long_ok_callback; - void* long_ok_callback_context; + PushToTalkMenuLongOkCallback long_ok_callback; + void* long_ok_callback_context; + FuriTimer* hint_timer; }; typedef struct { @@ -62,8 +68,71 @@ typedef struct { size_t window_position; PushToTalkMenuList* lists; int lists_count; + uint32_t last_interaction_tick; + size_t hint_list_position; + size_t hint_item_position; + bool hint_visible; + uint16_t hint_scroll_tick; } HidPushToTalkMenuModel; +static void hid_ptt_menu_mark_interaction(HidPushToTalkMenu* hid_ptt_menu) { + with_view_model( + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { + model->last_interaction_tick = furi_get_tick(); + model->hint_list_position = model->list_position; + model->hint_item_position = model->position; + model->hint_visible = false; + model->hint_scroll_tick = 0; + }, + true); +} + +static void hid_ptt_menu_hint_timer_callback(void* context) { + furi_assert(context); + HidPushToTalkMenu* hid_ptt_menu = context; + with_view_model( + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { + const uint32_t now = furi_get_tick(); + const bool selection_changed = + (model->list_position != model->hint_list_position) || + (model->position != model->hint_item_position); + + if(selection_changed) { + model->hint_list_position = model->list_position; + model->hint_item_position = model->position; + model->last_interaction_tick = now; + model->hint_visible = false; + model->hint_scroll_tick = 0; + } else if(!model->hint_visible) { + if((now - model->last_interaction_tick) >= PTT_MENU_HELP_HINT_DELAY_MS) { + model->hint_visible = true; + model->hint_scroll_tick = 0; + } + } else { + model->hint_scroll_tick++; + } + }, + true); +} + +static void hid_ptt_menu_enter_callback(void* context) { + furi_assert(context); + HidPushToTalkMenu* hid_ptt_menu = context; + hid_ptt_menu_mark_interaction(hid_ptt_menu); + furi_timer_start( + hid_ptt_menu->hint_timer, furi_ms_to_ticks(PTT_MENU_HINT_TIMER_PERIOD_MS)); +} + +static void hid_ptt_menu_exit_callback(void* context) { + furi_assert(context); + HidPushToTalkMenu* hid_ptt_menu = context; + furi_timer_stop(hid_ptt_menu->hint_timer); +} + static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) { furi_assert(context); @@ -96,7 +165,12 @@ static void FuriString* disp_str; disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label); - elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); + size_t item_text_width = item_width - (6 * 2); + if((position == model->position) && model->hint_visible) { + // Reserve space for hint near selected item. + item_text_width = item_width / 2; + } + elements_string_fit_width(canvas, disp_str, item_text_width); canvas_draw_str( canvas, @@ -104,6 +178,38 @@ static void y_offset + (item_position * item_height) + item_height - 4, furi_string_get_cstr(disp_str)); + if((position == model->position) && model->hint_visible) { + const char* hint_prefix = "Long press"; + const char* hint_suffix = "for help"; + const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4; + const int32_t icon_y = text_y - 8; + const size_t selected_text_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + const int32_t hint_start_x = 6 + selected_text_w + 3; + const int32_t row_right_x = item_width - 2; + + if(hint_start_x < row_right_x) { + const size_t prefix_w = canvas_string_width(canvas, hint_prefix); + const size_t icon_w = 9; + const size_t suffix_w = canvas_string_width(canvas, hint_suffix); + const size_t hint_w = prefix_w + 2 + icon_w + 2 + suffix_w; + const size_t available = row_right_x - hint_start_x; + + int32_t scroll_offset = 0; + if(hint_w > available) { + const size_t overflow = hint_w - available; + const size_t cycle = overflow * 2; + const size_t step = cycle ? (model->hint_scroll_tick % cycle) : 0; + scroll_offset = (step <= overflow) ? step : (cycle - step); + } + + const int32_t draw_x = hint_start_x - scroll_offset; + canvas_draw_str(canvas, draw_x, text_y, hint_prefix); + const int32_t icon_x = draw_x + prefix_w + 2; + canvas_draw_icon(canvas, icon_x, icon_y, &I_Ok_btn_9x9); + canvas_draw_str(canvas, icon_x + icon_w + 2, text_y, hint_suffix); + } + } + furi_string_free(disp_str); } @@ -340,38 +446,38 @@ void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) { } } - void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) { - PushToTalkMenuList* list = NULL; - PushToTalkMenuItem* item = NULL; - with_view_model( - hid_ptt_menu->view, - HidPushToTalkMenuModel * model, - { - list = &model->lists[model->list_position]; - const size_t items_size = PushToTalkMenuItemArray_size(list->items); - if(model->position < items_size) { - item = PushToTalkMenuItemArray_get(list->items, model->position); - } - }, - false); - if(item && list && hid_ptt_menu->long_ok_callback) { - hid_ptt_menu->long_ok_callback( - hid_ptt_menu->long_ok_callback_context, - list->index, - list->label, - item->index, - item->label); - } +void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) { + PushToTalkMenuList* list = NULL; + PushToTalkMenuItem* item = NULL; + with_view_model( + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { + list = &model->lists[model->list_position]; + const size_t items_size = PushToTalkMenuItemArray_size(list->items); + if(model->position < items_size) { + item = PushToTalkMenuItemArray_get(list->items, model->position); + } + }, + false); + if(item && list && hid_ptt_menu->long_ok_callback) { + hid_ptt_menu->long_ok_callback( + hid_ptt_menu->long_ok_callback_context, + list->index, + list->label, + item->index, + item->label); } +} - void ptt_menu_set_long_ok_callback( - HidPushToTalkMenu* hid_ptt_menu, - PushToTalkMenuLongOkCallback callback, - void* callback_context) { - furi_assert(hid_ptt_menu); - hid_ptt_menu->long_ok_callback = callback; - hid_ptt_menu->long_ok_callback_context = callback_context; - } +void ptt_menu_set_long_ok_callback( + HidPushToTalkMenu* hid_ptt_menu, + PushToTalkMenuLongOkCallback callback, + void* callback_context) { + furi_assert(hid_ptt_menu); + hid_ptt_menu->long_ok_callback = callback; + hid_ptt_menu->long_ok_callback_context = callback_context; +} static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) { furi_assert(context); @@ -410,10 +516,15 @@ static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) { consumed = true; ptt_menu_process_down(hid_ptt_menu); } - } else if(event->type == InputTypeLong && event->key == InputKeyOk) { - consumed = true; - ptt_menu_process_long_ok(hid_ptt_menu); - } + } else if(event->type == InputTypeLong && event->key == InputKeyOk) { + consumed = true; + ptt_menu_process_long_ok(hid_ptt_menu); + } + + if(event->type != InputTypeRelease) { + hid_ptt_menu_mark_interaction(hid_ptt_menu); + } + return consumed; } @@ -430,6 +541,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) { view_allocate_model(hid_ptt_menu->view, ViewModelTypeLocking, sizeof(HidPushToTalkMenuModel)); view_set_draw_callback(hid_ptt_menu->view, hid_ptt_menu_draw_callback); view_set_input_callback(hid_ptt_menu->view, hid_ptt_menu_input_callback); + view_set_enter_callback(hid_ptt_menu->view, hid_ptt_menu_enter_callback); + view_set_exit_callback(hid_ptt_menu->view, hid_ptt_menu_exit_callback); + + hid_ptt_menu->hint_timer = + furi_timer_alloc(hid_ptt_menu_hint_timer_callback, FuriTimerTypePeriodic, hid_ptt_menu); with_view_model( hid_ptt_menu->view, @@ -438,6 +554,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) { model->lists_count = 0; model->position = 0; model->window_position = 0; + model->last_interaction_tick = furi_get_tick(); + model->hint_list_position = 0; + model->hint_item_position = 0; + model->hint_visible = false; + model->hint_scroll_tick = 0; }, true); return hid_ptt_menu; @@ -445,6 +566,8 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) { void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) { furi_assert(hid_ptt_menu); + furi_timer_stop(hid_ptt_menu->hint_timer); + furi_timer_free(hid_ptt_menu->hint_timer); with_view_model( hid_ptt_menu->view, HidPushToTalkMenuModel * model, From 65a12073685a7d32e33b8ab0d349821690278ff6 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:00:02 +0300 Subject: [PATCH 14/16] Improving helper display in menu --- .../system/hid_app/views/hid_ptt_menu.c | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 6c8b1fa65..7072eff57 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -165,49 +165,43 @@ static void FuriString* disp_str; disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label); - size_t item_text_width = item_width - (6 * 2); - if((position == model->position) && model->hint_visible) { - // Reserve space for hint near selected item. - item_text_width = item_width / 2; - } - elements_string_fit_width(canvas, disp_str, item_text_width); - - canvas_draw_str( - canvas, - 6, - y_offset + (item_position * item_height) + item_height - 4, - furi_string_get_cstr(disp_str)); + const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4; + const int32_t row_left_x = 6; + const int32_t row_right_x = item_width - 2; + const size_t row_width = row_right_x - row_left_x; if((position == model->position) && model->hint_visible) { - const char* hint_prefix = "Long press"; + const char* hint_prefix = " | Long press"; const char* hint_suffix = "for help"; - const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4; const int32_t icon_y = text_y - 8; - const size_t selected_text_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); - const int32_t hint_start_x = 6 + selected_text_w + 3; - const int32_t row_right_x = item_width - 2; - if(hint_start_x < row_right_x) { - const size_t prefix_w = canvas_string_width(canvas, hint_prefix); - const size_t icon_w = 9; - const size_t suffix_w = canvas_string_width(canvas, hint_suffix); - const size_t hint_w = prefix_w + 2 + icon_w + 2 + suffix_w; - const size_t available = row_right_x - hint_start_x; + const size_t label_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + const size_t prefix_w = canvas_string_width(canvas, hint_prefix); + const size_t icon_w = 9; + const size_t suffix_w = canvas_string_width(canvas, hint_suffix); - int32_t scroll_offset = 0; - if(hint_w > available) { - const size_t overflow = hint_w - available; - const size_t cycle = overflow * 2; - const size_t step = cycle ? (model->hint_scroll_tick % cycle) : 0; - scroll_offset = (step <= overflow) ? step : (cycle - step); - } + const size_t group_w = label_w + 2 + prefix_w + 2 + icon_w + 2 + suffix_w; - const int32_t draw_x = hint_start_x - scroll_offset; - canvas_draw_str(canvas, draw_x, text_y, hint_prefix); - const int32_t icon_x = draw_x + prefix_w + 2; - canvas_draw_icon(canvas, icon_x, icon_y, &I_Ok_btn_9x9); - canvas_draw_str(canvas, icon_x + icon_w + 2, text_y, hint_suffix); + int32_t scroll_offset = 0; + if(group_w > row_width) { + const size_t overflow = group_w - row_width; + const size_t cycle = overflow * 2; + const size_t step = cycle ? (model->hint_scroll_tick % cycle) : 0; + scroll_offset = (step <= overflow) ? step : (cycle - step); } + + const int32_t draw_x = row_left_x - scroll_offset; + canvas_draw_str(canvas, draw_x, text_y, furi_string_get_cstr(disp_str)); + + int32_t hint_x = draw_x + label_w + 2; + canvas_draw_str(canvas, hint_x, text_y, hint_prefix); + hint_x += prefix_w + 2; + canvas_draw_icon(canvas, hint_x, icon_y, &I_Ok_btn_9x9); + hint_x += icon_w + 2; + canvas_draw_str(canvas, hint_x, text_y, hint_suffix); + } else { + elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); + canvas_draw_str(canvas, row_left_x, text_y, furi_string_get_cstr(disp_str)); } furi_string_free(disp_str); From 2c1d9bd9e80e5b2c0eb970b3a7cd31821e34be8a Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:12:01 +0300 Subject: [PATCH 15/16] Visialazing long left press for zoom --- applications/system/hid_app/views/hid_ptt.c | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 045b82f1b..9ae4eed0e 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -6,12 +6,15 @@ #include #include #include +#include #include "../hid.h" #include "../views.h" #include "hid_icons.h" #define TAG "HidPushToTalk" +#define HID_PTT_LEFT_HOLD_ANIM_STEP_MS 100U +#define HID_PTT_LEFT_HOLD_ANIM_STEPS 5U // Exact home status-bar Bluetooth icon pixels (from assets/icons/StatusBar). // Bitmap format for canvas_draw_bitmap(): row-major, 1-bit. @@ -29,6 +32,7 @@ struct HidPushToTalk { View* view; Hid* hid; Widget* help; + FuriTimer* left_hold_timer; }; typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt); @@ -47,6 +51,7 @@ typedef struct { size_t osIndex; size_t appIndex; size_t window_position; + uint8_t left_hold_progress; PushToTalkActionCallback callback_trigger_mute; PushToTalkActionCallback callback_trigger_camera; PushToTalkActionCallback callback_trigger_hand; @@ -74,6 +79,66 @@ enum HidPushToTalkAppIndex { HidPushToTalkAppIndexSize, }; +static bool hid_ptt_is_zoom_app(size_t app_index) { + return (app_index == HidPushToTalkAppIndexZoom) || + (app_index == HidPushToTalkAppIndexZoomGlobal); +} + +static void hid_ptt_left_hold_timer_callback(void* context) { + furi_assert(context); + HidPushToTalk* hid_ptt = context; + with_view_model( + hid_ptt->view, + HidPushToTalkModel * model, + { + if(model->left_pressed && hid_ptt_is_zoom_app(model->appIndex)) { + if(model->left_hold_progress < HID_PTT_LEFT_HOLD_ANIM_STEPS) { + model->left_hold_progress++; + } + } else { + model->left_hold_progress = 0; + furi_timer_stop(hid_ptt->left_hold_timer); + } + }, + true); +} + +static void hid_ptt_draw_zoom_enter_hint( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t progress, + bool pressed) { + const uint8_t width = 18; + const uint8_t height = 10; + const uint8_t inner_x = x + 1; + const uint8_t inner_y = y + 1; + const uint8_t inner_w = width - 2; + const uint8_t inner_h = height - 1; + + if(progress > 0) { + const uint8_t fill_w = (inner_w * progress) / HID_PTT_LEFT_HOLD_ANIM_STEPS; + if(fill_w > 0) { + canvas_draw_box(canvas, inner_x, inner_y, fill_w, inner_h - 1); + } + } + + for(uint8_t dot_x = x; dot_x < x + width; dot_x += 2) { + canvas_draw_dot(canvas, dot_x, y + height - 1); + } + for(uint8_t dot_y = y + 1; dot_y < y + height; dot_y += 2) { + canvas_draw_dot(canvas, x, dot_y); + canvas_draw_dot(canvas, x + width - 1, dot_y); + } + + UNUSED(pressed); + if(progress > 0) { + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, x + 4, y + 1, &I_Enter_11x7); + canvas_set_color(canvas, ColorBlack); +} + // meet, zoom static void hid_ptt_start_ptt_meet_zoom(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); @@ -857,6 +922,10 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { } canvas_set_color(canvas, ColorBlack); + if(hid_ptt_is_zoom_app(model->appIndex)) { + hid_ptt_draw_zoom_enter_hint(canvas, x_1, y_2 + 18, model->left_hold_progress, model->left_pressed); + } + // Right / Camera canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18); if(model->right_pressed) { @@ -930,6 +999,12 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = true; + if(hid_ptt_is_zoom_app(model->appIndex)) { + model->left_hold_progress = 0; + furi_timer_start( + hid_ptt->left_hold_timer, + furi_ms_to_ticks(HID_PTT_LEFT_HOLD_ANIM_STEP_MS)); + } } else if(event->key == InputKeyRight) { model->right_pressed = true; } else if(event->key == InputKeyOk) { @@ -953,6 +1028,8 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } } else if(event->key == InputKeyLeft) { model->left_pressed = false; + model->left_hold_progress = 0; + furi_timer_stop(hid_ptt->left_hold_timer); } else if(event->key == InputKeyRight) { model->right_pressed = false; @@ -986,6 +1063,8 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { if( model->appIndex == HidPushToTalkAppIndexZoom || model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + model->left_hold_progress = HID_PTT_LEFT_HOLD_ANIM_STEPS; + furi_timer_stop(hid_ptt->left_hold_timer); hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_RETURN); hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_RETURN); notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); @@ -1006,6 +1085,7 @@ static bool hid_ptt_input_callback(InputEvent* event, void* context) { HidPushToTalk* hid_ptt = context; bool consumed = false; if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_timer_stop(hid_ptt->left_hold_timer); hid_hal_keyboard_release_all(hid_ptt->hid); notification_message(hid_ptt->hid->notifications, &sequence_double_vibro); widget_reset(hid_ptt->help); @@ -1029,6 +1109,8 @@ static uint32_t hid_ptt_menu_view(void* context) { HidPushToTalk* hid_ptt_alloc(Hid* hid) { HidPushToTalk* hid_ptt = malloc(sizeof(HidPushToTalk)); hid_ptt->hid = hid; + hid_ptt->left_hold_timer = + furi_timer_alloc(hid_ptt_left_hold_timer_callback, FuriTimerTypePeriodic, hid_ptt); hid_ptt->view = view_alloc(); view_set_context(hid_ptt->view, hid_ptt); view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPushToTalkModel)); @@ -1041,6 +1123,7 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { HidPushToTalkModel * model, { model->muted = true; // assume we're muted + model->left_hold_progress = 0; model->os = furi_string_alloc(); model->app = furi_string_alloc(); }, @@ -1263,6 +1346,8 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { void hid_ptt_free(HidPushToTalk* hid_ptt) { furi_assert(hid_ptt); + furi_timer_stop(hid_ptt->left_hold_timer); + furi_timer_free(hid_ptt->left_hold_timer); notification_message(hid_ptt->hid->notifications, &sequence_reset_red); with_view_model( hid_ptt->view, From 2a7e048cd8cb8874c7f265d3f06b554a9fc92af4 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:29:30 +0300 Subject: [PATCH 16/16] Fixing google meet global screen --- applications/system/hid_app/views/hid_ptt.c | 49 ++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 9ae4eed0e..2696c22c4 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -787,6 +787,35 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } +static void hid_ptt_draw_app_label(Canvas* canvas, uint8_t first_line_y, FuriString* app) { + FuriString* first_line = furi_string_alloc_set(app); + if(canvas_string_width(canvas, furi_string_get_cstr(first_line)) <= canvas_width(canvas)) { + hid_ptt_draw_text_centered(canvas, first_line_y, first_line); + furi_string_free(first_line); + return; + } + + const char* app_cstr = furi_string_get_cstr(app); + const char* split = strrchr(app_cstr, ' '); + if(!split) { + hid_ptt_draw_text_centered(canvas, first_line_y, first_line); + furi_string_free(first_line); + return; + } + + FuriString* second_line = furi_string_alloc(); + furi_string_set_strn(first_line, app_cstr, split - app_cstr); + furi_string_set_str(second_line, split + 1); + + elements_string_fit_width(canvas, first_line, canvas_width(canvas)); + elements_string_fit_width(canvas, second_line, canvas_width(canvas)); + hid_ptt_draw_text_centered(canvas, first_line_y, first_line); + hid_ptt_draw_text_centered(canvas, first_line_y + 10, second_line); + + furi_string_free(second_line); + furi_string_free(first_line); +} + static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; @@ -838,14 +867,18 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkModel* model = context; + const bool app_label_needs_two_lines = + canvas_string_width(canvas, furi_string_get_cstr(model->app)) > canvas_width(canvas); + const uint8_t top_offset = 13; const uint8_t status_bar_bottom_y = top_offset; - + // For Zoom/Zoom Global, keep helper banner higher to show Enter key hint space // For other apps, move it down to close the gap uint8_t helper_top_y = 102; - if(model->appIndex == HidPushToTalkAppIndexZoom || - model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + if((model->appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) && + !app_label_needs_two_lines) { helper_top_y = 92; } @@ -859,8 +892,8 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t controls_bottom_y = y_3 + 18; const uint8_t labels_center_y = (controls_bottom_y + helper_top_y) / 2; - const uint8_t app_label_y = labels_center_y - 1; - const uint8_t os_label_y = app_label_y + 11; + const uint8_t app_label_y = app_label_needs_two_lines ? (controls_bottom_y + 10) : (labels_center_y - 1); + const uint8_t os_label_y = app_label_needs_two_lines ? (app_label_y + 20) : (app_label_y + 11); // Header canvas_set_font(canvas, FontPrimary); @@ -872,7 +905,11 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // OS and App labels canvas_set_font(canvas, FontSecondary); - hid_ptt_draw_text_centered(canvas, app_label_y, model->app); + if(app_label_needs_two_lines) { + hid_ptt_draw_app_label(canvas, app_label_y, model->app); + } else { + hid_ptt_draw_text_centered(canvas, app_label_y, model->app); + } hid_ptt_draw_text_centered(canvas, os_label_y, model->os); // Help label