Procházet zdrojové kódy

Merge pull request #31 from hryamzik/ptt

PTT: added menu, new apps and improved controls
MMX před 2 roky
rodič
revize
21ba455d59

binární
base_pack/hid_app/assets/BrokenButton_15x15.png


binární
base_pack/hid_app/assets/BtnBackV_9x9.png


binární
base_pack/hid_app/assets/BtnLeft_9x9.png


binární
base_pack/hid_app/assets/Hand_8x10.png


binární
base_pack/hid_app/assets/Help_exit_64x9.png


binární
base_pack/hid_app/assets/Help_top_64x17.png


binární
base_pack/hid_app/assets/Hold_15x5.png


binární
base_pack/hid_app/assets/Mic_7x11.png


binární
base_pack/hid_app/assets/Mic_btn_8x10.png


binární
base_pack/hid_app/assets/MicrophoneCrossed_16x16.png


binární
base_pack/hid_app/assets/MicrophonePressedBtn_16x16.png


binární
base_pack/hid_app/assets/MicrophonePressedCrossedBtn_16x16.png


binární
base_pack/hid_app/assets/RoundButtonPressed_16x16.png


binární
base_pack/hid_app/assets/RoundButtonUnpressed_16x16.png


binární
base_pack/hid_app/assets/for_help_27x5.png


+ 20 - 9
base_pack/hid_app/hid.c

@@ -16,7 +16,7 @@ enum HidDebugSubmenuIndex {
     HidSubmenuIndexMouse,
     HidSubmenuIndexMouseClicker,
     HidSubmenuIndexMouseJiggler,
-    HidSubmenuIndexPtt,
+    HidSubmenuIndexPushToTalk,
 };
 
 static void hid_submenu_callback(void* context, uint32_t index) {
@@ -54,9 +54,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
     } else if(index == HidSubmenuIndexMouseJiggler) {
         app->view_id = HidViewMouseJiggler;
         view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
-    } else if(index == HidSubmenuIndexPtt) {
-        app->view_id = HidViewPtt;
-        view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPtt);
+    } else if(index == HidSubmenuIndexPushToTalk) {
+        app->view_id = HidViewPushToTalkMenu;
+        view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu);
     }
 }
 
@@ -93,6 +93,11 @@ static uint32_t hid_exit(void* context) {
     return VIEW_NONE;
 }
 
+static uint32_t hid_ptt_menu_view(void* context) {
+    UNUSED(context);
+    return HidViewPushToTalkMenu;
+}
+
 Hid* hid_alloc(HidTransport transport) {
     Hid* app = malloc(sizeof(Hid));
     app->transport = transport;
@@ -151,7 +156,7 @@ Hid* hid_alloc(HidTransport transport) {
         hid_submenu_callback,
         app);
     submenu_add_item(
-        app->device_type_submenu, "PTT", HidSubmenuIndexPtt, hid_submenu_callback, app);
+        app->device_type_submenu, "PushToTalk", HidSubmenuIndexPushToTalk, hid_submenu_callback, app);
     view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit);
     view_dispatcher_add_view(
         app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu));
@@ -224,11 +229,15 @@ Hid* hid_app_alloc_view(void* context) {
         HidViewMouseJiggler,
         hid_mouse_jiggler_get_view(app->hid_mouse_jiggler));
 
-    // Ptt view
+    // PushToTalk view
+    app->hid_ptt_menu = hid_ptt_menu_alloc(app);
+    view_set_previous_callback(hid_ptt_menu_get_view(app->hid_ptt_menu), hid_menu_view);
+    view_dispatcher_add_view(
+      app->view_dispatcher, HidViewPushToTalkMenu, hid_ptt_menu_get_view(app->hid_ptt_menu));
     app->hid_ptt = hid_ptt_alloc(app);
-    view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_menu_view);
+    view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_ptt_menu_view);
     view_dispatcher_add_view(
-      app->view_dispatcher, HidViewPtt, hid_ptt_get_view(app->hid_ptt));
+        app->view_dispatcher, HidViewPushToTalk, hid_ptt_get_view(app->hid_ptt));
 
     return app;
 }
@@ -260,7 +269,9 @@ void hid_free(Hid* app) {
     hid_mouse_clicker_free(app->hid_mouse_clicker);
     view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
     hid_mouse_jiggler_free(app->hid_mouse_jiggler);
-    view_dispatcher_remove_view(app->view_dispatcher, HidViewPtt);
+    view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalkMenu);
+    hid_ptt_menu_free(app->hid_ptt_menu);
+    view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalk);
     hid_ptt_free(app->hid_ptt);
     view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikShorts);
     hid_tikshorts_free(app->hid_tikshorts);

+ 3 - 1
base_pack/hid_app/hid.h

@@ -26,6 +26,7 @@
 #include "views/hid_mouse_jiggler.h"
 #include "views/hid_tikshorts.h"
 #include "views/hid_ptt.h"
+#include "views/hid_ptt_menu.h"
 
 #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
 
@@ -52,7 +53,8 @@ struct Hid {
     HidMouseClicker* hid_mouse_clicker;
     HidMouseJiggler* hid_mouse_jiggler;
     HidTikShorts* hid_tikshorts;
-    HidPtt* hid_ptt;
+    HidPushToTalk* hid_ptt;
+    HidPushToTalkMenu* hid_ptt_menu;
 
     HidTransport transport;
     uint32_t view_id;

binární
base_pack/hid_app/hid_usb_10px.png


+ 3 - 1
base_pack/hid_app/views.h

@@ -9,5 +9,7 @@ typedef enum {
     HidViewMouseClicker,
     HidViewMouseJiggler,
     BtHidViewTikShorts,
-    HidViewPtt,
+    HidViewPushToTalk,
+    HidViewPushToTalkMenu,
+    HidViewPushToTalkHelp,
 } HidView;

+ 663 - 304
base_pack/hid_app/views/hid_ptt.c

@@ -1,18 +1,23 @@
 #include "hid_ptt.h"
+#include "hid_ptt_menu.h"
 #include <gui/elements.h>
 #include <notification/notification_messages.h>
+#include <gui/modules/widget.h>
 #include "../hid.h"
 #include "../views.h"
 
 #include "hid_icons.h"
 
-#define TAG "HidPtt"
+#define TAG "HidPushToTalk"
 
-struct HidPtt {
+struct HidPushToTalk {
     View* view;
     Hid* hid;
+    Widget* help;
 };
 
+typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt);
+
 typedef struct {
     bool left_pressed;
     bool up_pressed;
@@ -21,24 +26,492 @@ typedef struct {
     bool muted;
     bool ptt_pressed;
     bool mic_pressed;
-    bool mic_sync_pressed;
     bool connected;
-    bool is_mac_os;
-    uint32_t appIndex;
+    FuriString *os;
+    FuriString *app;
+    size_t osIndex;
+    size_t appIndex;
+    size_t window_position;
     HidTransport transport;
-} HidPttModel;
-
-enum HidPttAppIndex {
-    HidPttAppIndexGoogleMeet,
-    HidPttAppIndexZoom,
-    HidPttAppIndexFaceTime,
-    HidPttAppIndexSkype,
-    HidPttAppIndexSize,
+    PushToTalkActionCallback callback_trigger_mute;
+    PushToTalkActionCallback callback_trigger_camera;
+    PushToTalkActionCallback callback_trigger_hand;
+    PushToTalkActionCallback callback_start_ptt;
+    PushToTalkActionCallback callback_stop_ptt;
+} HidPushToTalkModel;
+
+enum HidPushToTalkAppIndex {
+    HidPushToTalkAppIndexDiscord,
+    HidPushToTalkAppIndexFaceTime,
+    HidPushToTalkAppIndexGoogleMeet,
+    HidPushToTalkAppIndexGoogleHangouts,
+    HidPushToTalkAppIndexJamulus,
+    HidPushToTalkAppIndexSignal,
+    HidPushToTalkAppIndexSkype,
+    HidPushToTalkAppIndexSlackCall,
+    HidPushToTalkAppIndexSlackHubble,
+    HidPushToTalkAppIndexTeams,
+    HidPushToTalkAppIndexTeamSpeak,
+    HidPushToTalkAppIndexWebex,
+    HidPushToTalkAppIndexZoom,
+    HidPushToTalkAppIndexSize,
 };
 
+// meet, zoom
+static void hid_ptt_start_ptt_meet_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_stop_ptt_meet_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_trigger_mute_macos_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
+}
+static void hid_ptt_trigger_mute_linux_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
+}
+static void hid_ptt_trigger_camera_macos_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
+}
+static void hid_ptt_trigger_camera_linux_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E );
+}
+static void hid_ptt_trigger_hand_macos_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H);
+}
+static void hid_ptt_trigger_hand_linux_meet(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H);
+}
+static void hid_ptt_trigger_mute_macos_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
+}
+static void hid_ptt_trigger_mute_linux_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A);
+}
+static void hid_ptt_trigger_camera_macos_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+}
+static void hid_ptt_trigger_camera_linux_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
+}
+static void hid_ptt_trigger_hand_macos_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| HID_KEYBOARD_Y);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| HID_KEYBOARD_Y);
+}
+static void hid_ptt_trigger_hand_linux_zoom(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y);
+}
+
+// this one is widely used across different apps
+static void hid_ptt_trigger_cmd_shift_m(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+}
+
+// Hangouts HidPushToTalkAppIndexGoogleHangouts
+static void hid_ptt_trigger_mute_macos_hangouts(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
+}
+static void hid_ptt_trigger_mute_linux_hangouts(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
+}
+static void hid_ptt_trigger_camera_macos_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
+}
+static void hid_ptt_trigger_camera_linux_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
+}
+
+// Signal
+static void hid_ptt_trigger_mute_signal(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_trigger_camera_signal(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+}
+
+// skype
+static void hid_ptt_trigger_mute_linux_skype(HidPushToTalk* hid_ptt) { // and webex
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
+}
+static void hid_ptt_trigger_camera_macos_skype(HidPushToTalk* hid_ptt) { // and hand in teams
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
+}
+static void hid_ptt_trigger_camera_linux_skype(HidPushToTalk* hid_ptt) { // and hand in teams
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
+}
+
+// slack call
+static void hid_ptt_trigger_mute_slack_call(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_M);
+}
+static void hid_ptt_trigger_camera_slack_call(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, HID_KEYBOARD_V);
+    hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_V);
+}
+
+// slack hubble
+static void hid_ptt_trigger_mute_macos_slack_hubble(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_trigger_mute_linux_slack_hubble(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
+}
+
+// discord
+static void hid_ptt_trigger_mute_macos_discord(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_start_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
+}
+static void hid_ptt_stop_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
+}
+static void hid_ptt_trigger_mute_linux_discord(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_start_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
+}
+static void hid_ptt_stop_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
+}
+
+// teamspeak
+static void hid_ptt_trigger_mute_macos_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_start_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
+}
+static void hid_ptt_stop_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);   
+}
+static void hid_ptt_trigger_mute_linux_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_start_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
+}
+static void hid_ptt_stop_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);   
+}
+
+// teams
+static void hid_ptt_start_ptt_macos_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_start_ptt_linux_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_stop_ptt_macos_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_stop_ptt_linux_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR);
+}
+static void hid_ptt_trigger_mute_linux_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
+}
+static void hid_ptt_trigger_camera_macos_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
+}
+static void hid_ptt_trigger_camera_linux_teams(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT |HID_KEYBOARD_O);
+}
+
+// Jamulus
+static void hid_ptt_trigger_mute_jamulus(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M);
+}
+
+// webex
+
+
+static void hid_ptt_trigger_camera_webex(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
+}
+static void hid_ptt_trigger_hand_macos_webex(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
+}
+static void hid_ptt_trigger_hand_linux_webex(HidPushToTalk* hid_ptt) {
+    hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
+    hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
+}
+
+static void hid_ptt_menu_callback(void* context, uint32_t osIndex, FuriString* osLabel, uint32_t appIndex, FuriString* appLabel) {
+    furi_assert(context);
+    HidPushToTalk* hid_ptt = context;
+        with_view_model(
+        hid_ptt->view, HidPushToTalkModel * model, {
+            furi_string_set(model->os, osLabel);
+            furi_string_set(model->app, appLabel);
+            model->osIndex = osIndex;
+            model->appIndex = appIndex;
+            model->callback_trigger_mute   = NULL;
+            model->callback_trigger_camera = NULL;
+            model->callback_trigger_hand   = NULL;
+            model->callback_start_ptt      = NULL;
+            model->callback_stop_ptt       = NULL;
+            FURI_LOG_E(TAG, "appIndex: %lu", appIndex);
+            if(osIndex == HidPushToTalkMacOS) {
+                switch(appIndex) {
+                case HidPushToTalkAppIndexDiscord:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_discord;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_macos_discord;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_macos_discord;
+                    break;
+                case HidPushToTalkAppIndexFaceTime:
+                    model->callback_trigger_mute   = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_start_ptt      = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_stop_ptt       = hid_ptt_trigger_cmd_shift_m;
+                    break;
+                case HidPushToTalkAppIndexGoogleHangouts:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_hangouts;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_macos_hangouts;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_macos_hangouts;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_macos_hangouts;
+                    break;
+                case HidPushToTalkAppIndexGoogleMeet:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_meet;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_macos_meet;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_macos_meet;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_meet_zoom;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_meet_zoom;
+                    break;
+                case HidPushToTalkAppIndexJamulus:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_jamulus;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_jamulus;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_jamulus;
+                    break;
+                case HidPushToTalkAppIndexTeams:
+                    model->callback_trigger_mute   = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_macos_teams;
+                    model->callback_trigger_hand   = hid_ptt_trigger_camera_macos_skype;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_macos_teams;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_macos_teams;
+                    break;
+                case HidPushToTalkAppIndexTeamSpeak:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_teamspeak;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_macos_teamspeak;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_macos_teamspeak;
+                    break;
+                case HidPushToTalkAppIndexSignal:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_signal;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_signal;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_signal;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_signal;
+                    break;
+                case HidPushToTalkAppIndexSkype:
+                    model->callback_trigger_mute   = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_macos_skype;
+                    model->callback_start_ptt      = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_stop_ptt       = hid_ptt_trigger_cmd_shift_m;
+                    break;
+                case HidPushToTalkAppIndexSlackCall:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_slack_call;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_slack_call;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_slack_call;
+                    break;
+                case HidPushToTalkAppIndexSlackHubble:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_slack_hubble;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_macos_slack_hubble;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_macos_slack_hubble;
+                    break;
+                case HidPushToTalkAppIndexWebex:
+                    model->callback_trigger_mute   = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_webex;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_macos_webex;
+                    model->callback_start_ptt      = hid_ptt_trigger_cmd_shift_m;
+                    model->callback_stop_ptt       = hid_ptt_trigger_cmd_shift_m;
+                    break;
+                case HidPushToTalkAppIndexZoom:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_macos_zoom;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_macos_zoom;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_macos_zoom;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_meet_zoom;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_meet_zoom;
+                    break;
+                }
+            } else if (osIndex == HidPushToTalkLinux) {
+                switch(appIndex) {
+                case HidPushToTalkAppIndexDiscord:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_discord;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_linux_discord;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_linux_discord;
+                    break;
+                case HidPushToTalkAppIndexGoogleHangouts:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_hangouts;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_linux_hangouts;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_linux_hangouts;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_linux_hangouts;
+                    break;
+                case HidPushToTalkAppIndexGoogleMeet:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_meet;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_linux_meet;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_linux_meet;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_meet_zoom;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_meet_zoom;
+                    break;
+                case HidPushToTalkAppIndexJamulus:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_jamulus;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_jamulus;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_jamulus;
+                    break;
+                case HidPushToTalkAppIndexTeams:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_teams;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_linux_teams;
+                    model->callback_trigger_hand   = hid_ptt_trigger_camera_linux_skype;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_linux_teams;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_linux_teams;
+                    break;
+                case HidPushToTalkAppIndexTeamSpeak:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_teamspeak;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_linux_teamspeak;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_linux_teamspeak;
+                    break;
+                case HidPushToTalkAppIndexSignal:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_signal;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_signal;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_signal;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_signal;
+                    break;
+                case HidPushToTalkAppIndexSkype:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_skype;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_linux_skype;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_linux_skype;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_linux_skype;
+                    break;
+                case HidPushToTalkAppIndexSlackCall:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_slack_call;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_slack_call;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_slack_call;
+                    break;
+                case HidPushToTalkAppIndexSlackHubble:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_slack_hubble;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_linux_slack_hubble;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_linux_slack_hubble;
+                    break;
+                case HidPushToTalkAppIndexZoom:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_zoom;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_linux_zoom;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_linux_zoom;
+                    model->callback_start_ptt      = hid_ptt_start_ptt_meet_zoom;
+                    model->callback_stop_ptt       = hid_ptt_stop_ptt_meet_zoom;
+                    break;
+                case HidPushToTalkAppIndexWebex:
+                    model->callback_trigger_mute   = hid_ptt_trigger_mute_linux_skype;
+                    model->callback_trigger_camera = hid_ptt_trigger_camera_webex;
+                    model->callback_trigger_hand   = hid_ptt_trigger_hand_linux_webex;
+                    model->callback_start_ptt      = hid_ptt_trigger_mute_linux_skype;
+                    model->callback_stop_ptt       = hid_ptt_trigger_mute_linux_skype;
+                    break;
+                }
+            }
+
+            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 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;
+            }
+            
+            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);
+    view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk);
+}
+
+static void hid_ptt_draw_camera(Canvas* canvas, uint8_t x, uint8_t y) {
+    canvas_draw_icon(canvas, x + 7, y, &I_ButtonLeft_4x7);
+    canvas_draw_box(canvas, x, y, 7, 7);
+}
+
+static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* str) {
+    FuriString* disp_str;
+    disp_str = furi_string_alloc_set(str);
+    elements_string_fit_width(canvas, disp_str, canvas_width(canvas));
+    uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2;
+    canvas_draw_str(canvas,x_pos,y,furi_string_get_cstr(disp_str));
+    furi_string_free(disp_str);
+}
+
 static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
     furi_assert(context);
-    HidPttModel* model = context;
+    HidPushToTalkModel* model = context;
 
     // Header
     canvas_set_font(canvas, FontPrimary);
@@ -50,127 +523,96 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
         }
     }
 
-    // App selection
-    const uint8_t y_app = 78;
+    // OS and App labels
     canvas_set_font(canvas, FontSecondary);
-    canvas_draw_icon(canvas, 0, y_app, &I_ButtonLeft_4x7);
-    if(model->appIndex == HidPttAppIndexGoogleMeet) {
-        elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Google Meet");
-    } else if(model->appIndex == HidPttAppIndexZoom) {
-        elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Zoom");
-    } else if(model->appIndex == HidPttAppIndexFaceTime) {
-        elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "FaceTime");
-    } else if(model->appIndex == HidPttAppIndexSkype) {
-        elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Skype");
-    }
-    canvas_draw_icon(canvas, 60, y_app, &I_ButtonRight_4x7);
-
-    // OS selection
-    const uint8_t y_os = 88;
-    const uint8_t x_os = 7;
-    // elements_slightly_rounded_box(canvas, model->is_mac_os ? 0 : 26, y_os, model->is_mac_os ? 21 : 26, 11);
-    elements_slightly_rounded_box(canvas, model->is_mac_os ? x_os : x_os + 26, y_os, model->is_mac_os ? 21 : 26, 11);
-    canvas_set_color(canvas, model->is_mac_os ? ColorWhite : ColorBlack);
-    elements_multiline_text_aligned(canvas, x_os + 2, y_os + 1, AlignLeft, AlignTop, "Mac");
-    canvas_set_color(canvas, ColorBlack);
-    if (model->appIndex != HidPttAppIndexFaceTime) {
-        elements_multiline_text_aligned(canvas, x_os + 23, y_os + 2, AlignLeft, AlignTop, "|");
-        canvas_set_color(canvas, model->is_mac_os ? ColorBlack : ColorWhite);
-        elements_multiline_text_aligned(canvas, x_os + 28, y_os + 2, AlignLeft, AlignTop, "Linux");
-        canvas_set_color(canvas, ColorBlack);
-    }
-
-    // Mic label
-    const uint8_t y_mic = 102;
-    canvas_draw_icon(canvas, 0, y_mic - 1, &I_Pin_back_arrow_rotated_8x10);
-    canvas_draw_icon(canvas, 18, y_mic - 1, &I_Ok_btn_9x9);
-    elements_multiline_text_aligned(canvas, 11, y_mic, AlignLeft, AlignTop, "+      to sync");
-    elements_multiline_text_aligned(canvas, 20, y_mic+10, AlignLeft, AlignTop, "mic status");
-
-    // Exit label
-    canvas_draw_icon(canvas, 20, 121, &I_ButtonLeft_4x7);
-    elements_multiline_text_aligned(canvas, 0, 121, AlignLeft, AlignTop, "Hold    to exit");
+    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 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 = 19;
+    const uint8_t y_1 = 3;
     const uint8_t y_2 = y_1 + 19;
     const uint8_t y_3 = y_2 + 19;
-    if(!model->ptt_pressed || model->mic_pressed || model->up_pressed || model->down_pressed || model->left_pressed || model->right_pressed || model->mic_sync_pressed) {
-        // Up
-        canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
-        if(model->up_pressed) {
-            elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
-            canvas_set_color(canvas, ColorWhite);
-        }
-        if(model->mic_pressed) {
-            if (model->appIndex != HidPttAppIndexFaceTime) {
-                elements_multiline_text_aligned(canvas, x_2 + 4, y_1 + 5, AlignLeft, AlignTop, "OS");
-            }
-        } else {
-            canvas_draw_icon(canvas, x_2 + 5, y_1 + 5, &I_Volup_8x6);
-        }
-        canvas_set_color(canvas, ColorBlack);
-
-        // Down
-        canvas_draw_icon(canvas, x_2, y_3, &I_Button_18x18);
-        if(model->down_pressed) {
-            elements_slightly_rounded_box(canvas, x_2 + 3, y_3 + 2, 13, 13);
-            canvas_set_color(canvas, ColorWhite);
-        }
-        if(!model->mic_pressed) {
-            canvas_draw_icon(canvas, x_2 + 6, y_3 + 5, &I_Voldwn_6x6);
-        }
-        canvas_set_color(canvas, ColorBlack);
+    
+    // Up
+    canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
+    if(model->up_pressed) {
+        elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    canvas_draw_icon(canvas, x_2 + 5, y_1 + 5, &I_Volup_8x6);
+    canvas_set_color(canvas, ColorBlack);
 
-        // Left
-        canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
-        if(model->left_pressed) {                                             
-            elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
-            canvas_set_color(canvas, ColorWhite);
-        }
-        if (model->mic_pressed) {
-            canvas_draw_icon(canvas, x_1 + 7, y_2 + 5, &I_ButtonLeft_4x7);
-        } else {  
-            canvas_draw_icon(canvas, x_1 + 4, y_2 + 5, &I_Pin_back_arrow_10x8);
-        }
-        canvas_set_color(canvas, ColorBlack);
+    // Down
+    canvas_draw_icon(canvas, x_2, y_3, &I_Button_18x18);
+    if(model->down_pressed) {
+        elements_slightly_rounded_box(canvas, x_2 + 3, y_3 + 2, 13, 13);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    canvas_draw_icon(canvas, x_2 + 6, y_3 + 5, &I_Voldwn_6x6);
+    canvas_set_color(canvas, ColorBlack);
 
-        // Right / Camera
-        canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
-        if(model->right_pressed) {
-            elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
-            canvas_set_color(canvas, ColorWhite);
-        }
-        if(!model->mic_pressed) {
-            if (model->appIndex != HidPttAppIndexFaceTime) {
-                canvas_draw_icon(canvas, x_3 + 11, y_2 + 5, &I_ButtonLeft_4x7);
-                canvas_draw_box(canvas, x_3 + 4, y_2 + 5, 7, 7);
-            }
-        } else {
-            canvas_draw_icon(canvas, x_3 + 8, y_2 + 5, &I_ButtonRight_4x7);
-        }
-        canvas_set_color(canvas, ColorBlack);
+    // Left / Help
+    canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
+    if(model->left_pressed) {                                             
+        elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    if (model->callback_trigger_hand) {
+        canvas_draw_icon(canvas, x_1 + 4, y_2 + 3, &I_Hand_8x10);
+    } else {
+        canvas_draw_icon(canvas, x_1 + 2, y_2 + 1, &I_BrokenButton_15x15);
+    }
+    canvas_set_color(canvas, ColorBlack);
 
+    // Right / Camera
+    canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
+    if(model->right_pressed) {
+        elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    if (model->callback_trigger_camera) {
+        hid_ptt_draw_camera(canvas, x_3 + 4, y_2 + 5);
+    } else {
+        canvas_draw_icon(canvas, x_3 + 2, y_2 + 1, &I_BrokenButton_15x15);
     }
+    canvas_set_color(canvas, ColorBlack);
+
+    
     // Back / Mic
     const uint8_t x_mic = x_3;
-    canvas_draw_icon(canvas, x_mic, 0, &I_Button_18x18);
-    if(model->mic_pressed) {
-        elements_slightly_rounded_box(canvas, x_mic + 3, 2, 13, 13);
-        canvas_set_color(canvas, ColorWhite);
-    }
-    canvas_draw_icon(canvas, x_mic + 5, 4, &I_Mic_btn_8x10);
+    canvas_draw_icon(canvas, x_mic, 0, &I_RoundButtonUnpressed_16x16);
     
-    if (!(!model->muted || (model->ptt_pressed && !model->mic_sync_pressed))) {
-        canvas_draw_line(canvas, x_mic + 3, 2     , x_mic + 3 + 13, 2 + 13);
-        canvas_draw_line(canvas, x_mic + 2, 2     , x_mic + 2 + 13, 2 + 13);
-        canvas_draw_line(canvas, x_mic + 3, 2 + 13, x_mic + 3 + 13, 2);
-        canvas_draw_line(canvas, x_mic + 2, 2 + 13, x_mic + 2 + 13, 2);
+    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);
+        } else {
+            canvas_draw_icon(canvas, x_mic, 0, &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);
+        } else {
+            canvas_draw_icon(canvas, x_mic + 5, 2, &I_Mic_7x11);
+        }
     }
-    canvas_set_color(canvas, ColorBlack);
 
     // Ok / PTT
     const uint8_t x_ptt_margin = 4;
@@ -182,175 +624,37 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
     canvas_draw_line(canvas, x_ptt + 3                             , y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16);
     canvas_draw_line(canvas, x_ptt + 3                             , y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17);
 
-    if (!model->ptt_pressed && !model->muted && model->mic_pressed) {
-        elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, (x_ptt_width + x_ptt_margin) / 2, 13);
-        canvas_set_color(canvas, ColorWhite);
-    }
-    if (model->mic_pressed) {
-        canvas_draw_icon(canvas, x_ptt + 4, y_2 + 4, &I_Mic_btn_8x10);
-    }
-    canvas_set_color(canvas, ColorBlack);
-    if(!model->ptt_pressed && model->muted && model->mic_pressed) {
-        elements_slightly_rounded_box(canvas, x_ptt + 3 + (x_ptt_width + x_ptt_margin) / 2, y_2 + 2, (x_ptt_width + x_ptt_margin) / 2, 13);
+
+    if (model->ptt_pressed) {
+        elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13);
         canvas_set_color(canvas, ColorWhite);
     }
-    if (model->mic_pressed) {
-        canvas_draw_icon(canvas, x_ptt + 14, y_2 + 4, &I_Mic_btn_8x10);
-        canvas_draw_line(canvas, x_ptt + 13, y_2 + 3 , x_ptt + 22, y_2 + 14);
-        canvas_draw_line(canvas, x_ptt + 13, y_2 + 14, x_ptt + 22, y_2 + 3);
-    } else {
-        if (model->ptt_pressed && !model->mic_sync_pressed) {
-            elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13);
-            canvas_set_color(canvas, ColorWhite);
-        }
-        canvas_set_font(canvas, FontPrimary);
-        elements_multiline_text_aligned(canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT");
-        canvas_set_font(canvas, FontSecondary);
-    }
+    canvas_set_font(canvas, FontPrimary);
+    elements_multiline_text_aligned(canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT");
+    canvas_set_font(canvas, FontSecondary);
     canvas_set_color(canvas, ColorBlack);
 }
 
-static void hid_ptt_trigger_mute(HidPtt* hid_ptt, HidPttModel * model) {
-    if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D );
-    } else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D );
-    } else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A );
-    } else if(model->appIndex == HidPttAppIndexFaceTime) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M );
-    }
-}
-
-static void hid_ptt_trigger_camera(HidPtt* hid_ptt, HidPttModel * model) {
-    if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E );
-    } else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E );
-    } else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V );
-    } else if(model->appIndex == HidPttAppIndexZoom && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V );
-    } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K );
-    } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K );
-    }
-}
-
-static void hid_ptt_start_ptt(HidPtt* hid_ptt, HidPttModel * model) {
-    if(model->appIndex == HidPttAppIndexGoogleMeet) {
-        hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
-    } else if(model->appIndex == HidPttAppIndexZoom) {
-        hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
-    } else if(model->appIndex == HidPttAppIndexFaceTime) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M );   
-    }
-}
-
-static void hid_ptt_stop_ptt(HidPtt* hid_ptt, HidPttModel * model) {
-    if(model->appIndex == HidPttAppIndexGoogleMeet) {
-        hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
-    } else if(model->appIndex == HidPttAppIndexZoom) {
-        hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
-    } else if(model->appIndex == HidPttAppIndexFaceTime) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M );
-    } else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
-        hid_hal_keyboard_press(  hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
-        hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M );
-    }
-}
-
-// Supports only ±1
-static void hid_ptt_shift_app(HidPttModel * model, int shift) {
-    int i = (short) model->appIndex;
-    if (i + shift >= HidPttAppIndexSize) {
-        model->appIndex = 0;
-    } else if(i + shift <= 0) {
-        model->appIndex = HidPttAppIndexSize - 1;
-    } else {
-        model->appIndex += shift;
-    }
-    // Avoid showing facetime if not macos
-    if (model->appIndex == HidPttAppIndexFaceTime && !model->is_mac_os) {
-        hid_ptt_shift_app(model, shift);
-    }
-}
-
-static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) {
+static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
     with_view_model(
         hid_ptt->view,
-        HidPttModel * model,
+        HidPushToTalkModel * model,
         {
             if(event->type == InputTypePress && !model->ptt_pressed) {
                 if(event->key == InputKeyUp) {
                     model->up_pressed = true;
-                    if (!model->mic_pressed){
-                        hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
-                    } else {
-                        if (model->appIndex != HidPttAppIndexFaceTime) {
-                            model->is_mac_os = !model->is_mac_os;
-                            notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
-                        }
-                    }
+                    hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
                 } else if(event->key == InputKeyDown) {
                     model->down_pressed = true;
-                    if (!model->mic_pressed){
-                        hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
-                    } else if (!model->mic_pressed) {
-                        hid_ptt_shift_app(model, - 1);
-                        notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
-                    }
+                    hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
                 } else if(event->key == InputKeyLeft) {
                     model->left_pressed = true;
-                    if (model->mic_pressed){
-                        hid_ptt_shift_app(model, 1);
-                        notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
-                    }
                 } else if(event->key == InputKeyRight) {
                     model->right_pressed = true;
-                    if (model->mic_pressed){
-                        hid_ptt_shift_app(model, - 1);
-                        notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
-                    }
                 } else if(event->key == InputKeyOk) {
                     model->ptt_pressed = true;
-                    if (model->mic_pressed){
-                        // Change local mic status
-                        model->muted = !model->muted;
-                        model->mic_sync_pressed = true;
-                        notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
-                    } else {
-                        if (model->muted) {
-                            hid_ptt_start_ptt(hid_ptt, model);
-                        }
+                    if (!model->mic_pressed && model->muted){
+                        model->callback_start_ptt ? model->callback_start_ptt(hid_ptt):0;
                     }
                 } else if(event->key == InputKeyBack) {
                     model->mic_pressed = true;
@@ -358,12 +662,12 @@ static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) {
             } else if(event->type == InputTypeRelease) {
                 if(event->key == InputKeyUp) {
                     model->up_pressed = false;
-                    if (!model->mic_pressed && !model->ptt_pressed){
+                    if (!model->ptt_pressed){
                         hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
                     }
                 } else if(event->key == InputKeyDown) {
                     model->down_pressed = false;
-                    if (!model->mic_pressed && !model->ptt_pressed){
+                    if (!model->ptt_pressed){
                         hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
                     }
                 } else if(event->key == InputKeyLeft) {
@@ -373,38 +677,36 @@ static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) {
 
                 } else if(event->key == InputKeyOk) {
                     model->ptt_pressed = false;
-                    if(!model->mic_pressed && !model->mic_sync_pressed) {
+                    if(!model->mic_pressed) {
                         if (model->muted) {
-                            hid_ptt_stop_ptt(hid_ptt, model);
+                            model->callback_stop_ptt ? model->callback_stop_ptt(hid_ptt):0;
                         } else {
-                            hid_ptt_trigger_mute(hid_ptt, model);
+                            model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
                             model->muted = true;
                         }
                     }
-                    model->mic_sync_pressed = false;
                 } else if(event->key == InputKeyBack) {
                     model->mic_pressed = false;
                 }
             } else if(event->type == InputTypeShort && !model->ptt_pressed) {
                 if(event->key == InputKeyBack ) { // no changes if PTT is pressed
                     model->muted = !model->muted;
-                    hid_ptt_trigger_mute(hid_ptt, model);
+                    model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
                 } else if(event->key == InputKeyRight) {
-                    if (!model->mic_pressed){
-                        hid_ptt_trigger_camera(hid_ptt, model);
-                    }
+                    model->callback_trigger_camera ? model->callback_trigger_camera(hid_ptt):0;
+                } else if(event->key == InputKeyLeft) {
+                    model->callback_trigger_hand ? model->callback_trigger_hand(hid_ptt):0;
                 }
+            } 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;
-                if (!model->ptt_pressed){
-                    hid_hal_keyboard_release_all(hid_ptt->hid);
-                    view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewSubmenu);
-                    // sequence_double_vibro to notify that we quit PTT
-                    notification_message(hid_ptt->hid->notifications, &sequence_double_vibro);
-                }
+                view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
             }
             //LED
-            if (!model->muted || (model->ptt_pressed && !model->mic_sync_pressed)) {
+            if (!model->muted || (model->ptt_pressed)) {
                 notification_message(hid_ptt->hid->notifications, &sequence_set_red_255);
             } else {
                 notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
@@ -415,46 +717,103 @@ static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) {
 
 static bool hid_ptt_input_callback(InputEvent* event, void* context) {
     furi_assert(context);
-    HidPtt* hid_ptt = context;
-    bool consumed = true;
-    hid_ptt_process(hid_ptt, event);
+    HidPushToTalk* hid_ptt = context;
+    bool consumed = false;
+    if(event->type == InputTypeLong && event->key == InputKeyBack) {
+        hid_hal_keyboard_release_all(hid_ptt->hid);
+        notification_message(hid_ptt->hid->notifications, &sequence_double_vibro);
+        widget_reset(hid_ptt->help);
+    } else {
+        consumed = true;
+        hid_ptt_process(hid_ptt, event);
+    }
     return consumed;
 }
 
-HidPtt* hid_ptt_alloc(Hid* hid) {
-    HidPtt* hid_ptt = malloc(sizeof(HidPtt));
-    hid_ptt->view = view_alloc();
+View* hid_ptt_get_view(HidPushToTalk* hid_ptt) {
+    furi_assert(hid_ptt);
+    return hid_ptt->view;
+}
+
+static uint32_t hid_ptt_view(void* context) {
+    UNUSED(context);
+    return HidViewPushToTalk;
+}
+
+HidPushToTalk* hid_ptt_alloc(Hid* hid) {
+    HidPushToTalk* hid_ptt = malloc(sizeof(HidPushToTalk));
     hid_ptt->hid = hid;
+    hid_ptt->view = view_alloc();
     view_set_context(hid_ptt->view, hid_ptt);
-    view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPttModel));
+    view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPushToTalkModel));
     view_set_draw_callback(hid_ptt->view, hid_ptt_draw_callback);
     view_set_input_callback(hid_ptt->view, hid_ptt_input_callback);
     view_set_orientation(hid_ptt->view, ViewOrientationVerticalFlip);
 
     with_view_model(
-        hid_ptt->view, HidPttModel * model, {
+        hid_ptt->view, HidPushToTalkModel * model, {
             model->transport = hid->transport;
             model->muted = true; // assume we're muted
-            model->is_mac_os = true;
-            model->mic_sync_pressed = false;
+            model->os = furi_string_alloc();
+            model->app = furi_string_alloc();
         }, true);
+
+    FURI_LOG_I(TAG, "Calling adding list");
+    ptt_menu_add_list(hid->hid_ptt_menu, "macOS", HidPushToTalkMacOS);
+    ptt_menu_add_list(hid->hid_ptt_menu, "Win/Linux", HidPushToTalkLinux);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Meet",     HidPushToTalkAppIndexGoogleMeet,     hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Meet",     HidPushToTalkAppIndexGoogleMeet,     hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Discord",         HidPushToTalkAppIndexDiscord,        hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Discord",         HidPushToTalkAppIndexDiscord,        hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "FaceTime",        HidPushToTalkAppIndexFaceTime,       hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Jamulus",         HidPushToTalkAppIndexJamulus,        hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Jamulus",         HidPushToTalkAppIndexJamulus,        hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Signal",          HidPushToTalkAppIndexSignal,         hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Signal",          HidPushToTalkAppIndexSignal,         hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Skype",           HidPushToTalkAppIndexSkype,          hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Skype",           HidPushToTalkAppIndexSkype,          hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Call",      HidPushToTalkAppIndexSlackCall,      hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Call",      HidPushToTalkAppIndexSlackCall,      hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Hubble",    HidPushToTalkAppIndexSlackHubble,    hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Hubble",    HidPushToTalkAppIndexSlackHubble,    hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "TeamSpeak",       HidPushToTalkAppIndexTeamSpeak,      hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "TeamSpeak",       HidPushToTalkAppIndexTeamSpeak,      hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Teams",           HidPushToTalkAppIndexTeams,          hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Teams",           HidPushToTalkAppIndexTeams,          hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Zoom",            HidPushToTalkAppIndexZoom,           hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Zoom",            HidPushToTalkAppIndexZoom,           hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Webex",           HidPushToTalkAppIndexWebex,          hid_ptt_menu_callback, hid_ptt);
+    ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Webex",           HidPushToTalkAppIndexWebex,          hid_ptt_menu_callback, hid_ptt);
+
+    hid_ptt->help = widget_alloc();
+    view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_view);
+    view_dispatcher_add_view(hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help));
     return hid_ptt;
 }
 
-void hid_ptt_free(HidPtt* hid_ptt) {
+void hid_ptt_free(HidPushToTalk* hid_ptt) {
     furi_assert(hid_ptt);
     notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
+    with_view_model(
+    hid_ptt->view, HidPushToTalkModel * model, {
+        furi_string_free(model->os);
+        furi_string_free(model->app);
+    }, true);
+    view_dispatcher_remove_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
+    widget_free(hid_ptt->help);
     view_free(hid_ptt->view);
     free(hid_ptt);
 }
 
-View* hid_ptt_get_view(HidPtt* hid_ptt) {
-    furi_assert(hid_ptt);
-    return hid_ptt->view;
-}
-
-void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected) {
+void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected) {
     furi_assert(hid_ptt);
     with_view_model(
-        hid_ptt->view, HidPttModel * model, { model->connected = connected; }, true);
+        hid_ptt->view, HidPushToTalkModel * model, {
+            if (!connected && model->connected) {
+                notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
+            }
+            model->connected = connected;
+        }, true);
 }

+ 10 - 5
base_pack/hid_app/views/hid_ptt.h

@@ -3,12 +3,17 @@
 #include <gui/view.h>
 
 typedef struct Hid Hid;
-typedef struct HidPtt HidPtt;
+typedef struct HidPushToTalk HidPushToTalk;
 
-HidPtt* hid_ptt_alloc(Hid* bt_hid);
+HidPushToTalk* hid_ptt_alloc(Hid* bt_hid);
 
-void hid_ptt_free(HidPtt* hid_ptt);
+void hid_ptt_free(HidPushToTalk* hid_ptt);
 
-View* hid_ptt_get_view(HidPtt* hid_ptt);
+View* hid_ptt_get_view(HidPushToTalk* hid_ptt);
 
-void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected);
+void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected);
+
+enum HidPushToTalkOSes {
+    HidPushToTalkMacOS,
+    HidPushToTalkLinux,    
+};

+ 413 - 0
base_pack/hid_app/views/hid_ptt_menu.c

@@ -0,0 +1,413 @@
+#include "hid_ptt_menu.h"
+#include "hid_ptt.h"
+#include <gui/elements.h>
+#include <m-array.h>
+#include "../hid.h"
+#include "../views.h"
+
+#define TAG "HidPushToTalkMenu"
+
+struct HidPushToTalkMenu {
+    View* view;
+    Hid* hid;
+};
+
+typedef struct {
+    FuriString* label;
+    uint32_t index;
+    PushToTalkMenuItemCallback callback;
+    void* callback_context;
+} PushToTalkMenuItem;
+
+// Menu item
+static void PushToTalkMenuItem_init(PushToTalkMenuItem* item) {
+    item->label = furi_string_alloc();
+    item->index = 0;
+}
+
+static void PushToTalkMenuItem_init_set(PushToTalkMenuItem* item, const PushToTalkMenuItem* src) {
+    item->label = furi_string_alloc_set(src->label);
+    item->index = src->index;
+}
+
+static void PushToTalkMenuItem_set(PushToTalkMenuItem* item, const PushToTalkMenuItem* src) {
+    furi_string_set(item->label, src->label);
+    item->index = src->index;
+}
+
+static void PushToTalkMenuItem_clear(PushToTalkMenuItem* item) {
+    furi_string_free(item->label);
+}
+
+ARRAY_DEF(
+    PushToTalkMenuItemArray,
+    PushToTalkMenuItem,
+    (INIT(API_2(PushToTalkMenuItem_init)),
+     SET(API_6(PushToTalkMenuItem_set)),
+     INIT_SET(API_6(PushToTalkMenuItem_init_set)),
+     CLEAR(API_2(PushToTalkMenuItem_clear))))
+
+// Menu list (horisontal, 2d array)
+typedef struct {
+    FuriString* label;
+    uint32_t index;
+    PushToTalkMenuItemArray_t items;
+} PushToTalkMenuList;
+
+typedef struct {
+    size_t list_position;
+    size_t position;
+    size_t window_position;
+    PushToTalkMenuList *lists;
+    int lists_count;
+} HidPushToTalkMenuModel;
+
+static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) {
+    furi_assert(context);
+    HidPushToTalkMenuModel* model = context;
+    const uint8_t item_height = 16;
+    uint8_t item_width = canvas_width(canvas) - 5;
+
+    canvas_set_font(canvas, FontSecondary);
+    size_t position = 0;
+    PushToTalkMenuItemArray_it_t it;
+    for(PushToTalkMenuItemArray_it(it, items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
+        const size_t item_position = position - model->window_position;
+        const size_t items_on_screen = 3;
+        uint8_t y_offset = 16;
+
+        if(item_position < items_on_screen) {
+            if(position == model->position) {
+                canvas_set_color(canvas, ColorBlack);
+                elements_slightly_rounded_box(
+                    canvas,
+                    0,
+                    y_offset + (item_position * item_height) + 1,
+                    item_width,
+                    item_height - 2);
+                canvas_set_color(canvas, ColorWhite);
+            } else {
+                canvas_set_color(canvas, ColorBlack);
+            }
+
+            FuriString* disp_str;
+            disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label);
+            elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
+
+            canvas_draw_str(
+                canvas,
+                6,
+                y_offset + (item_position * item_height) + item_height - 4,
+                furi_string_get_cstr(disp_str));
+
+            furi_string_free(disp_str);
+        }
+
+        position++;
+    }
+    elements_scrollbar_pos(canvas, 128 , 17, 46, model->position, PushToTalkMenuItemArray_size(items));
+}
+
+PushToTalkMenuList * hid_ptt_menu_get_list_at_index(
+    void* context,
+    uint32_t index) {
+    furi_assert(context);
+    HidPushToTalkMenuModel* model = context;
+    for (int i = 0; i < model->lists_count; i++) {
+        PushToTalkMenuList* list = &model->lists[i];
+        if(index == list->index) {
+            return list;
+        }
+    }
+    return NULL;
+}
+
+static void hid_ptt_menu_draw_callback(Canvas* canvas, void* context) {
+    furi_assert(context);
+    HidPushToTalkMenuModel* model = context;
+    if (model->lists_count == 0){
+        return;
+    }
+    uint8_t item_width = canvas_width(canvas) - 5;
+
+    canvas_clear(canvas);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 4, 11, "<");
+    canvas_draw_str(canvas, 121, 11, ">");
+
+    PushToTalkMenuList* list = &model->lists[model->list_position];
+    FuriString* disp_str;
+    disp_str = furi_string_alloc_set(list->label);
+    elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
+    uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2;
+    canvas_draw_str(canvas,x_pos,11,furi_string_get_cstr(disp_str));
+    furi_string_free(disp_str);
+    canvas_set_font(canvas, FontSecondary);
+    hid_ptt_menu_draw_list(
+        canvas,
+        context,
+        list->items
+    );
+}
+
+void ptt_menu_add_list(
+    HidPushToTalkMenu* hid_ptt_menu,
+    const char* label,
+    uint32_t index) {
+    furi_assert(label);
+    furi_assert(hid_ptt_menu);
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model,
+        {
+            if (model->lists_count == 0) {
+                model->lists = (PushToTalkMenuList *)malloc(sizeof(PushToTalkMenuList));
+            } else {
+                model->lists = (PushToTalkMenuList *)realloc(model->lists, (model->lists_count + 1) * sizeof(PushToTalkMenuList));
+            }
+            if (model->lists == NULL) {
+                FURI_LOG_E(TAG, "Memory reallocation failed (%i)", model->lists_count);
+                return;
+            }
+            PushToTalkMenuList* list = &model->lists[model->lists_count];
+            PushToTalkMenuItemArray_init(list->items);
+            list->label = furi_string_alloc_set(label);
+            list->index = index;
+            model->lists_count += 1;
+        },
+        true);
+}
+
+
+void ptt_menu_add_item_to_list(
+    HidPushToTalkMenu* hid_ptt_menu,
+    uint32_t list_index,
+    const char* label,
+    uint32_t index,
+    PushToTalkMenuItemCallback callback,
+    void* callback_context) {
+    PushToTalkMenuItem* item = NULL;
+    furi_assert(label);
+    furi_assert(hid_ptt_menu);
+    UNUSED(list_index);
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model,
+        {
+            PushToTalkMenuList* list = hid_ptt_menu_get_list_at_index(model, list_index);
+            if (list == NULL){
+                FURI_LOG_E(TAG, "Adding item %s to unknown index %li", label, list_index);
+                return;
+            }
+            item = PushToTalkMenuItemArray_push_new(list->items);
+            furi_string_set_str(item->label, label);
+            item->index = index;
+            item->callback = callback;
+            item->callback_context = callback_context;
+        },
+        true);
+}
+
+void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){
+    size_t new_position = 0;
+    uint32_t index = 0;
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model,
+        {
+            int new_list_position = (short) model->list_position + shift;
+            if (new_list_position >= model->lists_count) {
+                new_list_position = 0;
+            } else if (new_list_position < 0) {
+                new_list_position = model->lists_count - 1;
+            }
+            PushToTalkMenuList* list = &model->lists[model->list_position];
+            PushToTalkMenuList* new_list = &model->lists[new_list_position];
+            size_t new_window_position = model->window_position;
+            const size_t items_size = PushToTalkMenuItemArray_size(new_list->items);
+            size_t position = 0;
+            // Find item index from current list
+            PushToTalkMenuItemArray_it_t it;
+            for(PushToTalkMenuItemArray_it(it, list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
+                if (position == model->position){
+                    index = PushToTalkMenuItemArray_cref(it)->index;
+                    break;
+                }
+                position++;
+            }
+            // Try to find item with the same index in a new list
+            position = 0;
+            bool item_exists_in_new_list = false;
+            for(PushToTalkMenuItemArray_it(it, new_list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
+                if (PushToTalkMenuItemArray_cref(it)->index == index) {
+                    item_exists_in_new_list = true;
+                    new_position = position;
+                    break;
+                }
+                position++;
+            }
+
+            // This list item is not presented in a new list, let's try to keep position as is.
+            // If it's out of range for the new list set it to the end
+            if (!item_exists_in_new_list) {
+                new_position = items_size - 1 < model->position ? items_size - 1 : model->position;
+            }
+
+            // Tune window position. As we have 3 items on screen, keep focus centered
+            const size_t items_on_screen = 3;
+
+            if (new_position >= items_size - 1) {
+                if (items_size < items_on_screen + 1) {
+                    new_window_position = 0;
+                } else {
+                    new_window_position = items_size - items_on_screen;
+                }
+            } else if (new_position < items_on_screen - 1) {
+                new_window_position = 0;
+            } else {
+                new_window_position = new_position - 1;
+            }
+            model->list_position = new_list_position;
+            model->position = new_position;
+            model->window_position = new_window_position;
+        },
+        true);
+}
+
+void ptt_menu_process_up(HidPushToTalkMenu* hid_ptt_menu) {
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model,
+        {
+            PushToTalkMenuList* list = &model->lists[model->list_position];
+            const size_t items_on_screen = 3;
+            const size_t items_size = PushToTalkMenuItemArray_size(list->items);
+
+            if(model->position > 0) {
+                model->position--;
+                if((model->position == model->window_position) && (model->window_position > 0)) {
+                    model->window_position--;
+                }
+            } else {
+                model->position = items_size - 1;
+                if(model->position > items_on_screen - 1) {
+                    model->window_position = model->position - (items_on_screen - 1);
+                }
+            }
+        },
+        true);
+}
+
+void ptt_menu_process_down(HidPushToTalkMenu* hid_ptt_menu) {
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model,
+        {
+            PushToTalkMenuList* list = &model->lists[model->list_position];
+            const size_t items_on_screen = 3;
+            const size_t items_size = PushToTalkMenuItemArray_size(list->items);
+
+            if(model->position < items_size - 1) {
+                model->position++;
+                if((model->position - model->window_position > items_on_screen - 2) &&
+                   (model->window_position < items_size - items_on_screen)) {
+                    model->window_position++;
+                }
+            } else {
+                model->position = 0;
+                model->window_position = 0;
+            }
+        },
+        true);
+}
+
+void ptt_menu_process_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);
+            }
+        },
+        true);
+    if(item && list && item->callback) {
+        item->callback(item->callback_context, list->index, list->label, item->index, item->label);
+    }
+}
+
+static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+    HidPushToTalkMenu* hid_ptt_menu = context;
+    bool consumed = false;
+    if(event->type == InputTypeShort) {
+        switch(event->key) {
+        case InputKeyUp:
+            consumed = true;
+            ptt_menu_process_up(hid_ptt_menu);
+            break;
+        case InputKeyDown:
+            consumed = true;
+            ptt_menu_process_down(hid_ptt_menu);
+            break;
+        case InputKeyLeft:
+            consumed = true;
+            ptt_menu_shift_list(hid_ptt_menu, -1);
+            break;
+        case InputKeyRight:
+            consumed = true;
+            ptt_menu_shift_list(hid_ptt_menu, +1);
+            break;
+        case InputKeyOk:
+            consumed = true;
+            ptt_menu_process_ok(hid_ptt_menu);
+            break;
+        default:
+            break;
+        }
+    } else if(event->type == InputTypeRepeat) {
+        if(event->key == InputKeyUp) {
+            consumed = true;
+            ptt_menu_process_up(hid_ptt_menu);
+        } else if(event->key == InputKeyDown) {
+            consumed = true;
+            ptt_menu_process_down(hid_ptt_menu);
+        }
+    }
+    return consumed;
+}
+
+View* hid_ptt_menu_get_view(HidPushToTalkMenu* hid_ptt_menu) {
+    furi_assert(hid_ptt_menu);
+    return hid_ptt_menu->view;
+}
+
+HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
+    HidPushToTalkMenu* hid_ptt_menu = malloc(sizeof(HidPushToTalkMenu));
+    hid_ptt_menu->hid = hid;
+    hid_ptt_menu->view = view_alloc();
+    view_set_context(hid_ptt_menu->view, hid_ptt_menu);
+    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);
+
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model, {
+            model->lists_count = 0;
+            model->position = 0;
+            model->window_position = 0;
+        }, true);
+    return hid_ptt_menu;
+}
+
+void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) {
+    furi_assert(hid_ptt_menu);
+    with_view_model(
+        hid_ptt_menu->view, HidPushToTalkMenuModel * model, {
+            for (int i = 0; i < model->lists_count; i++) {
+                PushToTalkMenuItemArray_clear(model->lists[i].items);
+                furi_string_free(model->lists[i].label);
+            }
+            free(model->lists);
+        }, true);
+    view_free(hid_ptt_menu->view);
+    free(hid_ptt_menu);
+}

+ 27 - 0
base_pack/hid_app/views/hid_ptt_menu.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <gui/view.h>
+
+typedef struct Hid Hid;
+typedef struct HidPushToTalkMenu HidPushToTalkMenu;
+
+typedef void (*PushToTalkMenuItemCallback)(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);
+
+View* hid_ptt_menu_get_view(HidPushToTalkMenu* hid_ptt_menu);
+
+void ptt_menu_add_item_to_list(
+    HidPushToTalkMenu* hid_ptt_menu,
+    uint32_t list_index,
+    const char* label,
+    uint32_t index,
+    PushToTalkMenuItemCallback callback,
+    void* callback_context);
+
+void ptt_menu_add_list(
+    HidPushToTalkMenu* hid_ptt_menu,
+    const char* label,
+    uint32_t index);