Merge branch 'dev' into leptoptilos
24
applications/system/hid_app/application.fam
Normal file
|
@ -0,0 +1,24 @@
|
|||
App(
|
||||
appid="hid_usb",
|
||||
name="USB Keyboard & Mouse",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="hid_usb_app",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="USB",
|
||||
fap_icon="hid_usb_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_icon_assets_symbol="hid",
|
||||
)
|
||||
|
||||
|
||||
App(
|
||||
appid="hid_ble",
|
||||
name="Bluetooth Remote",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="hid_ble_app",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Bluetooth",
|
||||
fap_icon="hid_ble_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_icon_assets_symbol="hid",
|
||||
)
|
BIN
applications/system/hid_app/assets/Arr_dwn_7x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Arr_up_7x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Ble_connected_15x15.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Ble_disconnected_15x15.png
Normal file
After Width: | Height: | Size: 657 B |
BIN
applications/system/hid_app/assets/BrokenButton_15x15.png
Normal file
After Width: | Height: | Size: 340 B |
BIN
applications/system/hid_app/assets/BtnBackV_9x9.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/BtnFrameLeft_3x18.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/BtnFrameRight_2x18.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/BtnLeft_9x9.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/ButtonDown_7x4.png
Normal file
After Width: | Height: | Size: 102 B |
BIN
applications/system/hid_app/assets/ButtonF10_5x8.png
Normal file
After Width: | Height: | Size: 172 B |
BIN
applications/system/hid_app/assets/ButtonF11_5x8.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
applications/system/hid_app/assets/ButtonF12_5x8.png
Normal file
After Width: | Height: | Size: 180 B |
BIN
applications/system/hid_app/assets/ButtonF1_5x8.png
Normal file
After Width: | Height: | Size: 177 B |
BIN
applications/system/hid_app/assets/ButtonF2_5x8.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
applications/system/hid_app/assets/ButtonF3_5x8.png
Normal file
After Width: | Height: | Size: 178 B |
BIN
applications/system/hid_app/assets/ButtonF4_5x8.png
Normal file
After Width: | Height: | Size: 177 B |
BIN
applications/system/hid_app/assets/ButtonF5_5x8.png
Normal file
After Width: | Height: | Size: 178 B |
BIN
applications/system/hid_app/assets/ButtonF6_5x8.png
Normal file
After Width: | Height: | Size: 177 B |
BIN
applications/system/hid_app/assets/ButtonF7_5x8.png
Normal file
After Width: | Height: | Size: 176 B |
BIN
applications/system/hid_app/assets/ButtonF8_5x8.png
Normal file
After Width: | Height: | Size: 176 B |
BIN
applications/system/hid_app/assets/ButtonF9_5x8.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
applications/system/hid_app/assets/ButtonLeft_4x7.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
applications/system/hid_app/assets/ButtonRight_4x7.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/system/hid_app/assets/ButtonUp_7x4.png
Normal file
After Width: | Height: | Size: 102 B |
BIN
applications/system/hid_app/assets/Button_18x18.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Circles_47x47.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/system/hid_app/assets/Hand_8x10.png
Normal file
After Width: | Height: | Size: 156 B |
BIN
applications/system/hid_app/assets/Help_exit_64x9.png
Normal file
After Width: | Height: | Size: 404 B |
BIN
applications/system/hid_app/assets/Help_top_64x17.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
applications/system/hid_app/assets/Hold_15x5.png
Normal file
After Width: | Height: | Size: 356 B |
BIN
applications/system/hid_app/assets/KB_key_Alt_17x10.png
Normal file
After Width: | Height: | Size: 162 B |
BIN
applications/system/hid_app/assets/KB_key_Cmd_17x10.png
Normal file
After Width: | Height: | Size: 163 B |
BIN
applications/system/hid_app/assets/KB_key_Ctl_17x10.png
Normal file
After Width: | Height: | Size: 161 B |
BIN
applications/system/hid_app/assets/KB_key_Del_17x10.png
Normal file
After Width: | Height: | Size: 165 B |
BIN
applications/system/hid_app/assets/KB_key_Esc_17x10.png
Normal file
After Width: | Height: | Size: 164 B |
BIN
applications/system/hid_app/assets/KB_key_Tab_17x10.png
Normal file
After Width: | Height: | Size: 163 B |
BIN
applications/system/hid_app/assets/Left_mouse_icon_9x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Like_def_11x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Like_pressed_17x17.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
applications/system/hid_app/assets/Mic_7x11.png
Normal file
After Width: | Height: | Size: 356 B |
BIN
applications/system/hid_app/assets/MicrophoneCrossed_16x16.png
Normal file
After Width: | Height: | Size: 341 B |
After Width: | Height: | Size: 329 B |
After Width: | Height: | Size: 339 B |
BIN
applications/system/hid_app/assets/Ok_btn_9x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Ok_btn_pressed_13x13.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/OutCircles_70x51.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Pause_icon_9x9.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
applications/system/hid_app/assets/Pin_arrow_down_7x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Pin_arrow_left_9x7.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Pin_arrow_right_9x7.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Pin_arrow_up_7x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Pin_back_arrow_10x10.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
applications/system/hid_app/assets/Pin_back_arrow_10x8.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 959 B |
BIN
applications/system/hid_app/assets/Pressed_Button_13x13.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Pressed_Button_19x19.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
applications/system/hid_app/assets/Right_mouse_icon_9x9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/RoundButtonPressed_16x16.png
Normal file
After Width: | Height: | Size: 320 B |
After Width: | Height: | Size: 323 B |
BIN
applications/system/hid_app/assets/S_DOWN_31x15.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/system/hid_app/assets/S_LEFT_15x31.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/system/hid_app/assets/S_RIGHT_15x31.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/system/hid_app/assets/S_UP_31x15.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
applications/system/hid_app/assets/Space_60x18.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
applications/system/hid_app/assets/Space_65x18.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
applications/system/hid_app/assets/Voldwn_6x6.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
applications/system/hid_app/assets/Volup_8x6.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
applications/system/hid_app/assets/for_help_27x5.png
Normal file
After Width: | Height: | Size: 162 B |
480
applications/system/hid_app/hid.c
Normal file
|
@ -0,0 +1,480 @@
|
|||
#include "hid.h"
|
||||
#include "views.h"
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "HidApp"
|
||||
|
||||
enum HidDebugSubmenuIndex {
|
||||
HidSubmenuIndexKeynote,
|
||||
HidSubmenuIndexKeynoteVertical,
|
||||
HidSubmenuIndexKeyboard,
|
||||
HidSubmenuIndexNumpad,
|
||||
HidSubmenuIndexMedia,
|
||||
HidSubmenuIndexMovie,
|
||||
HidSubmenuIndexTikShorts,
|
||||
HidSubmenuIndexMouse,
|
||||
HidSubmenuIndexMouseClicker,
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
HidSubmenuIndexPushToTalk,
|
||||
};
|
||||
|
||||
static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
if(index == HidSubmenuIndexKeynote) {
|
||||
app->view_id = HidViewKeynote;
|
||||
hid_keynote_set_orientation(app->hid_keynote, false);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
|
||||
} else if(index == HidSubmenuIndexKeynoteVertical) {
|
||||
app->view_id = HidViewKeynote;
|
||||
hid_keynote_set_orientation(app->hid_keynote, true);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
|
||||
} else if(index == HidSubmenuIndexKeyboard) {
|
||||
app->view_id = HidViewKeyboard;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard);
|
||||
} else if(index == HidSubmenuIndexNumpad) {
|
||||
app->view_id = HidViewNumpad;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewNumpad);
|
||||
} else if(index == HidSubmenuIndexMedia) {
|
||||
app->view_id = HidViewMedia;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia);
|
||||
} else if(index == HidSubmenuIndexMovie) {
|
||||
app->view_id = HidViewMovie;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie);
|
||||
} else if(index == HidSubmenuIndexMouse) {
|
||||
app->view_id = HidViewMouse;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse);
|
||||
} else if(index == HidSubmenuIndexTikShorts) {
|
||||
app->view_id = BtHidViewTikShorts;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikShorts);
|
||||
} else if(index == HidSubmenuIndexMouseClicker) {
|
||||
app->view_id = HidViewMouseClicker;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker);
|
||||
} else if(index == HidSubmenuIndexMouseJiggler) {
|
||||
app->view_id = HidViewMouseJiggler;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
} else if(index == HidSubmenuIndexPushToTalk) {
|
||||
app->view_id = HidViewPushToTalkMenu;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
Hid* hid = context;
|
||||
bool connected = (status == BtStatusConnected);
|
||||
if(hid->transport == HidTransportBle) {
|
||||
if(connected) {
|
||||
notification_internal_message(hid->notifications, &sequence_set_blue_255);
|
||||
} else {
|
||||
notification_internal_message(hid->notifications, &sequence_reset_blue);
|
||||
}
|
||||
}
|
||||
hid_keynote_set_connected_status(hid->hid_keynote, connected);
|
||||
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
|
||||
hid_numpad_set_connected_status(hid->hid_numpad, connected);
|
||||
hid_media_set_connected_status(hid->hid_media, connected);
|
||||
hid_movie_set_connected_status(hid->hid_movie, connected);
|
||||
hid_mouse_set_connected_status(hid->hid_mouse, connected);
|
||||
hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
|
||||
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
|
||||
hid_ptt_set_connected_status(hid->hid_ptt, connected);
|
||||
hid_tikshorts_set_connected_status(hid->hid_tikshorts, connected);
|
||||
}
|
||||
|
||||
static uint32_t hid_menu_view(void* context) {
|
||||
UNUSED(context);
|
||||
return HidViewSubmenu;
|
||||
}
|
||||
|
||||
static uint32_t hid_exit(void* context) {
|
||||
UNUSED(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;
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
// Bt
|
||||
app->bt = furi_record_open(RECORD_BT);
|
||||
|
||||
// Notifications
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
// Device Type Submenu view
|
||||
app->device_type_submenu = submenu_alloc();
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Keynote Vertical",
|
||||
HidSubmenuIndexKeynoteVertical,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app);
|
||||
if(app->transport == HidTransportBle) {
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"TikTok / YT Shorts",
|
||||
HidSubmenuIndexTikShorts,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
}
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Mouse Clicker",
|
||||
HidSubmenuIndexMouseClicker,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Mouse Jiggler",
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
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));
|
||||
app->view_id = HidViewSubmenu;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
|
||||
return app;
|
||||
}
|
||||
|
||||
Hid* hid_app_alloc_view(void* context) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
|
||||
// Keynote view
|
||||
app->hid_keynote = hid_keynote_alloc(app);
|
||||
view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
|
||||
|
||||
// Keyboard view
|
||||
app->hid_keyboard = hid_keyboard_alloc(app);
|
||||
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard));
|
||||
|
||||
//Numpad keyboard view
|
||||
app->hid_numpad = hid_numpad_alloc(app);
|
||||
view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewNumpad, hid_numpad_get_view(app->hid_numpad));
|
||||
|
||||
// Media view
|
||||
app->hid_media = hid_media_alloc(app);
|
||||
view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media));
|
||||
|
||||
// Movie view
|
||||
app->hid_movie = hid_movie_alloc(app);
|
||||
view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMovie, hid_movie_get_view(app->hid_movie));
|
||||
|
||||
// TikTok / YT Shorts view
|
||||
app->hid_tikshorts = hid_tikshorts_alloc(app);
|
||||
view_set_previous_callback(hid_tikshorts_get_view(app->hid_tikshorts), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewTikShorts, hid_tikshorts_get_view(app->hid_tikshorts));
|
||||
|
||||
// Mouse view
|
||||
app->hid_mouse = hid_mouse_alloc(app);
|
||||
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
|
||||
|
||||
// Mouse clicker view
|
||||
app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
|
||||
view_set_previous_callback(
|
||||
hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HidViewMouseClicker,
|
||||
hid_mouse_clicker_get_view(app->hid_mouse_clicker));
|
||||
|
||||
// Mouse jiggler view
|
||||
app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
|
||||
view_set_previous_callback(
|
||||
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HidViewMouseJiggler,
|
||||
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler));
|
||||
|
||||
// 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_ptt_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewPushToTalk, hid_ptt_get_view(app->hid_ptt));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void hid_free(Hid* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Reset notification
|
||||
if(app->transport == HidTransportBle) {
|
||||
notification_internal_message(app->notifications, &sequence_reset_blue);
|
||||
}
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu);
|
||||
submenu_free(app->device_type_submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
|
||||
hid_keynote_free(app->hid_keynote);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
|
||||
hid_keyboard_free(app->hid_keyboard);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewNumpad);
|
||||
hid_numpad_free(app->hid_numpad);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia);
|
||||
hid_media_free(app->hid_media);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMovie);
|
||||
hid_movie_free(app->hid_movie);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
|
||||
hid_mouse_free(app->hid_mouse);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
|
||||
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, 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);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
app->notifications = NULL;
|
||||
furi_record_close(RECORD_BT);
|
||||
app->bt = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
void hid_hal_keyboard_press(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_kb_press(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_kb_press(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_keyboard_release(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_kb_release(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_kb_release(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_keyboard_release_all(Hid* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_consumer_key_press(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_consumer_key_press(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_consumer_key_press(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_consumer_key_release(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_consumer_key_release(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_consumer_key_release(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_consumer_key_release_all(Hid* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_consumer_key_release_all();
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_mouse_move(dx, dy);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_mouse_move(dx, dy);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_mouse_scroll(Hid* instance, int8_t delta) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_mouse_scroll(delta);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_mouse_scroll(delta);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_mouse_press(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_mouse_press(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_mouse_press(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_mouse_release(Hid* instance, uint16_t event) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_mouse_release(event);
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_mouse_release(event);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void hid_hal_mouse_release_all(Hid* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->transport == HidTransportBle) {
|
||||
furi_hal_bt_hid_mouse_release_all();
|
||||
} else if(instance->transport == HidTransportUsb) {
|
||||
furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||
furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t hid_usb_app(void* p) {
|
||||
UNUSED(p);
|
||||
Hid* app = hid_alloc(HidTransportUsb);
|
||||
app = hid_app_alloc_view(app);
|
||||
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||
furi_hal_usb_unlock();
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
|
||||
|
||||
bt_hid_connection_status_changed_callback(BtStatusConnected, app);
|
||||
|
||||
dolphin_deed(DolphinDeedPluginStart);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
|
||||
hid_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t hid_ble_app(void* p) {
|
||||
UNUSED(p);
|
||||
Hid* app = hid_alloc(HidTransportBle);
|
||||
app = hid_app_alloc_view(app);
|
||||
|
||||
bt_disconnect(app->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
// Migrate data from old sd-card folder
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
storage_common_migrate(
|
||||
storage,
|
||||
EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME),
|
||||
APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
|
||||
bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard));
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
|
||||
|
||||
dolphin_deed(DolphinDeedPluginStart);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
bt_set_status_changed_callback(app->bt, NULL, NULL);
|
||||
|
||||
bt_disconnect(app->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_default_path(app->bt);
|
||||
|
||||
furi_check(bt_set_profile(app->bt, BtProfileSerial));
|
||||
|
||||
hid_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
75
applications/system/hid_app/hid.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <notification/notification.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include "views/hid_keynote.h"
|
||||
#include "views/hid_keyboard.h"
|
||||
#include "views/hid_numpad.h"
|
||||
#include "views/hid_media.h"
|
||||
#include "views/hid_movie.h"
|
||||
#include "views/hid_mouse.h"
|
||||
#include "views/hid_mouse_clicker.h"
|
||||
#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"
|
||||
|
||||
typedef enum {
|
||||
HidTransportUsb,
|
||||
HidTransportBle,
|
||||
} HidTransport;
|
||||
|
||||
typedef struct Hid Hid;
|
||||
|
||||
struct Hid {
|
||||
Bt* bt;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* device_type_submenu;
|
||||
DialogEx* dialog;
|
||||
HidKeynote* hid_keynote;
|
||||
HidKeyboard* hid_keyboard;
|
||||
HidNumpad* hid_numpad;
|
||||
HidMedia* hid_media;
|
||||
HidMovie* hid_movie;
|
||||
HidMouse* hid_mouse;
|
||||
HidMouseClicker* hid_mouse_clicker;
|
||||
HidMouseJiggler* hid_mouse_jiggler;
|
||||
HidTikShorts* hid_tikshorts;
|
||||
HidPushToTalk* hid_ptt;
|
||||
HidPushToTalkMenu* hid_ptt_menu;
|
||||
|
||||
HidTransport transport;
|
||||
uint32_t view_id;
|
||||
};
|
||||
|
||||
void hid_hal_keyboard_press(Hid* instance, uint16_t event);
|
||||
void hid_hal_keyboard_release(Hid* instance, uint16_t event);
|
||||
void hid_hal_keyboard_release_all(Hid* instance);
|
||||
|
||||
void hid_hal_consumer_key_press(Hid* instance, uint16_t event);
|
||||
void hid_hal_consumer_key_release(Hid* instance, uint16_t event);
|
||||
void hid_hal_consumer_key_release_all(Hid* instance);
|
||||
|
||||
void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy);
|
||||
void hid_hal_mouse_scroll(Hid* instance, int8_t delta);
|
||||
void hid_hal_mouse_press(Hid* instance, uint16_t event);
|
||||
void hid_hal_mouse_release(Hid* instance, uint16_t event);
|
||||
void hid_hal_mouse_release_all(Hid* instance);
|
BIN
applications/system/hid_app/hid_ble_10px.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
applications/system/hid_app/hid_usb_10px.png
Normal file
After Width: | Height: | Size: 174 B |
15
applications/system/hid_app/views.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
typedef enum {
|
||||
HidViewSubmenu,
|
||||
HidViewKeynote,
|
||||
HidViewKeyboard,
|
||||
HidViewNumpad,
|
||||
HidViewMedia,
|
||||
HidViewMovie,
|
||||
HidViewMouse,
|
||||
HidViewMouseClicker,
|
||||
HidViewMouseJiggler,
|
||||
BtHidViewTikShorts,
|
||||
HidViewPushToTalk,
|
||||
HidViewPushToTalkMenu,
|
||||
HidViewPushToTalkHelp,
|
||||
} HidView;
|
411
applications/system/hid_app/views/hid_keyboard.c
Normal file
|
@ -0,0 +1,411 @@
|
|||
#include "hid_keyboard.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include "../hid.h"
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidKeyboard"
|
||||
|
||||
struct HidKeyboard {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool shift;
|
||||
bool alt;
|
||||
bool ctrl;
|
||||
bool gui;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t last_key_code;
|
||||
uint16_t modifier_code;
|
||||
bool ok_pressed;
|
||||
bool back_pressed;
|
||||
bool connected;
|
||||
char key_string[5];
|
||||
HidTransport transport;
|
||||
} HidKeyboardModel;
|
||||
|
||||
typedef struct {
|
||||
uint8_t width;
|
||||
char* key;
|
||||
const Icon* icon;
|
||||
char* shift_key;
|
||||
uint8_t value;
|
||||
} HidKeyboardKey;
|
||||
|
||||
typedef struct {
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
} HidKeyboardPoint;
|
||||
// 4 BY 12
|
||||
#define MARGIN_TOP 0
|
||||
#define MARGIN_LEFT 4
|
||||
#define KEY_WIDTH 9
|
||||
#define KEY_HEIGHT 12
|
||||
#define KEY_PADDING 1
|
||||
#define ROW_COUNT 7
|
||||
#define COLUMN_COUNT 12
|
||||
|
||||
// 0 width items are not drawn, but there value is used
|
||||
const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||
{
|
||||
{.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1},
|
||||
{.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2},
|
||||
{.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3},
|
||||
{.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4},
|
||||
{.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5},
|
||||
{.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6},
|
||||
{.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7},
|
||||
{.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8},
|
||||
{.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9},
|
||||
{.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10},
|
||||
{.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11},
|
||||
{.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
|
||||
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
|
||||
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
|
||||
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
|
||||
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
|
||||
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
|
||||
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
|
||||
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
|
||||
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
|
||||
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
|
||||
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
|
||||
{.width = 0, .value = HID_KEYBOARD_DELETE},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
|
||||
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
|
||||
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
|
||||
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
|
||||
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
|
||||
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
|
||||
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
|
||||
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
|
||||
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
|
||||
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
|
||||
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
|
||||
{.width = 1,
|
||||
.icon = NULL,
|
||||
.key = "]",
|
||||
.shift_key = "}",
|
||||
.value = HID_KEYBOARD_CLOSE_BRACKET},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
|
||||
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
|
||||
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
|
||||
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
|
||||
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
|
||||
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
|
||||
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
|
||||
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
|
||||
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
|
||||
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
|
||||
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
|
||||
{.width = 0, .value = HID_KEYBOARD_RETURN},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
|
||||
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
|
||||
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
|
||||
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
|
||||
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
|
||||
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
|
||||
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
|
||||
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
|
||||
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
|
||||
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
|
||||
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
|
||||
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA},
|
||||
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
|
||||
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
|
||||
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
|
||||
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
|
||||
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
|
||||
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
|
||||
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
|
||||
},
|
||||
{
|
||||
{.width = 2, .icon = &I_KB_key_Ctl_17x10, .value = HID_KEYBOARD_L_CTRL},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
|
||||
{.width = 2, .icon = &I_KB_key_Alt_17x10, .value = HID_KEYBOARD_L_ALT},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
|
||||
{.width = 2, .icon = &I_KB_key_Cmd_17x10, .value = HID_KEYBOARD_L_GUI},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
|
||||
{.width = 2, .icon = &I_KB_key_Tab_17x10, .value = HID_KEYBOARD_TAB},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
|
||||
{.width = 2, .icon = &I_KB_key_Esc_17x10, .value = HID_KEYBOARD_ESCAPE},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_ESCAPE},
|
||||
{.width = 2, .icon = &I_KB_key_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD},
|
||||
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_DELETE_FORWARD},
|
||||
},
|
||||
};
|
||||
|
||||
static void hid_keyboard_to_upper(char* str) {
|
||||
while(*str) {
|
||||
*str = toupper((unsigned char)*str);
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_keyboard_draw_key(
|
||||
Canvas* canvas,
|
||||
HidKeyboardModel* model,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
HidKeyboardKey key,
|
||||
bool selected) {
|
||||
if(!key.width) return;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
|
||||
if(selected) {
|
||||
// Draw a filled box
|
||||
elements_slightly_rounded_box(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||
keyWidth,
|
||||
KEY_HEIGHT);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
// Draw a framed box
|
||||
elements_slightly_rounded_frame(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||
keyWidth,
|
||||
KEY_HEIGHT);
|
||||
}
|
||||
if(key.icon != NULL) {
|
||||
// Draw the icon centered on the button
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
|
||||
key.icon);
|
||||
} else {
|
||||
// If shift is toggled use the shift key when available
|
||||
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
|
||||
// Upper case if ctrl or alt was toggled true
|
||||
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
|
||||
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
|
||||
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
|
||||
hid_keyboard_to_upper(model->key_string);
|
||||
}
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
model->key_string);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_keyboard_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidKeyboardModel* model = context;
|
||||
|
||||
// Header
|
||||
if((!model->connected) && (model->transport == HidTransportBle)) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
|
||||
|
||||
canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit");
|
||||
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
|
||||
return; // Dont render the keyboard if we are not yet connected
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
// Start shifting the all keys up if on the next row (Scrolling)
|
||||
uint8_t initY = model->y == 0 ? 0 : 1;
|
||||
|
||||
if(model->y > 5) {
|
||||
initY = model->y - 4;
|
||||
}
|
||||
|
||||
for(uint8_t y = initY; y < ROW_COUNT; y++) {
|
||||
const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y];
|
||||
uint8_t x = 0;
|
||||
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
|
||||
HidKeyboardKey key = keyboardKeyRow[i];
|
||||
// Select when the button is hovered
|
||||
// Select if the button is hovered within its width
|
||||
// Select if back is clicked and its the backspace key
|
||||
// Deselect when the button clicked or not hovered
|
||||
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
|
||||
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
|
||||
hid_keyboard_draw_key(
|
||||
canvas,
|
||||
model,
|
||||
x,
|
||||
y - initY,
|
||||
key,
|
||||
(!model->ok_pressed && keySelected) || backSelected);
|
||||
x += key.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) {
|
||||
HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x];
|
||||
return key.value;
|
||||
}
|
||||
|
||||
static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) {
|
||||
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
|
||||
do {
|
||||
const int delta_sum = model->y + delta.y;
|
||||
model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT;
|
||||
} while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0);
|
||||
|
||||
do {
|
||||
const int delta_sum = model->x + delta.x;
|
||||
model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT;
|
||||
} while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width ==
|
||||
0); // Skip zero width keys, pretend they are one key
|
||||
}
|
||||
|
||||
static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_keyboard->view,
|
||||
HidKeyboardModel * model,
|
||||
{
|
||||
if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->ok_pressed = true;
|
||||
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
|
||||
model->last_key_code = hid_keyboard_get_selected_key(model);
|
||||
|
||||
// Toggle the modifier key when clicked, and click the key
|
||||
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
|
||||
model->shift = !model->shift;
|
||||
if(model->shift)
|
||||
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
|
||||
else
|
||||
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
|
||||
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
|
||||
model->alt = !model->alt;
|
||||
if(model->alt)
|
||||
model->modifier_code |= KEY_MOD_LEFT_ALT;
|
||||
else
|
||||
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
|
||||
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
|
||||
model->ctrl = !model->ctrl;
|
||||
if(model->ctrl)
|
||||
model->modifier_code |= KEY_MOD_LEFT_CTRL;
|
||||
else
|
||||
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
|
||||
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
|
||||
model->gui = !model->gui;
|
||||
if(model->gui)
|
||||
model->modifier_code |= KEY_MOD_LEFT_GUI;
|
||||
else
|
||||
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
|
||||
}
|
||||
hid_hal_keyboard_press(
|
||||
hid_keyboard->hid, model->modifier_code | model->last_key_code);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
// Release happens after short and long presses
|
||||
hid_hal_keyboard_release(
|
||||
hid_keyboard->hid, model->modifier_code | model->last_key_code);
|
||||
model->ok_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
// If back is pressed for a short time, backspace
|
||||
if(event->type == InputTypePress) {
|
||||
model->back_pressed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE);
|
||||
hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
// Cycle the selected keys
|
||||
if(event->key == InputKeyUp) {
|
||||
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1});
|
||||
} else if(event->key == InputKeyDown) {
|
||||
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1});
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0});
|
||||
} else if(event->key == InputKeyRight) {
|
||||
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0});
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_keyboard_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidKeyboard* hid_keyboard = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_release_all(hid_keyboard->hid);
|
||||
} else {
|
||||
hid_keyboard_process(hid_keyboard, event);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) {
|
||||
HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard));
|
||||
hid_keyboard->view = view_alloc();
|
||||
hid_keyboard->hid = bt_hid;
|
||||
view_set_context(hid_keyboard->view, hid_keyboard);
|
||||
view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel));
|
||||
view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback);
|
||||
view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_keyboard->view,
|
||||
HidKeyboardModel * model,
|
||||
{
|
||||
model->transport = bt_hid->transport;
|
||||
model->y = 1;
|
||||
},
|
||||
true);
|
||||
|
||||
return hid_keyboard;
|
||||
}
|
||||
|
||||
void hid_keyboard_free(HidKeyboard* hid_keyboard) {
|
||||
furi_assert(hid_keyboard);
|
||||
view_free(hid_keyboard->view);
|
||||
free(hid_keyboard);
|
||||
}
|
||||
|
||||
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) {
|
||||
furi_assert(hid_keyboard);
|
||||
return hid_keyboard->view;
|
||||
}
|
||||
|
||||
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) {
|
||||
furi_assert(hid_keyboard);
|
||||
with_view_model(
|
||||
hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true);
|
||||
}
|
14
applications/system/hid_app/views/hid_keyboard.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidKeyboard HidKeyboard;
|
||||
|
||||
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_keyboard_free(HidKeyboard* hid_keyboard);
|
||||
|
||||
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard);
|
||||
|
||||
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected);
|
312
applications/system/hid_app/views/hid_keynote.c
Normal file
|
@ -0,0 +1,312 @@
|
|||
#include "hid_keynote.h"
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidKeynote"
|
||||
|
||||
struct HidKeynote {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool back_pressed;
|
||||
bool connected;
|
||||
HidTransport transport;
|
||||
} HidKeynoteModel;
|
||||
|
||||
static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_line(canvas, x, y + 6, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_line(canvas, x, y - 6, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_line(canvas, x + 6, y, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_line(canvas, x - 6, y, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_keynote_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidKeynoteModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
|
||||
|
||||
canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
|
||||
|
||||
// Up
|
||||
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
|
||||
if(model->up_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
|
||||
if(model->down_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
|
||||
if(model->left_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
|
||||
if(model->right_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
|
||||
if(model->ok_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
|
||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
||||
if(model->back_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
|
||||
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
|
||||
}
|
||||
|
||||
static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidKeynoteModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
|
||||
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote");
|
||||
} else {
|
||||
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote");
|
||||
}
|
||||
|
||||
canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
|
||||
|
||||
const uint8_t x_2 = 23;
|
||||
const uint8_t x_1 = 2;
|
||||
const uint8_t x_3 = 44;
|
||||
|
||||
const uint8_t y_1 = 44;
|
||||
const uint8_t y_2 = 65;
|
||||
|
||||
// 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);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18);
|
||||
if(model->down_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom);
|
||||
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);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
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);
|
||||
}
|
||||
hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 2, 86, &I_Space_60x18);
|
||||
if(model->ok_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 5, 88, 55, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9);
|
||||
elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space");
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
canvas_draw_icon(canvas, 2, 107, &I_Space_60x18);
|
||||
if(model->back_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 5, 109, 55, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8);
|
||||
elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back");
|
||||
}
|
||||
|
||||
static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_keynote->view,
|
||||
HidKeynoteModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = true;
|
||||
}
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE);
|
||||
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE);
|
||||
hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK);
|
||||
hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK);
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_keynote_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidKeynote* hid_keynote = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_release_all(hid_keynote->hid);
|
||||
} else {
|
||||
hid_keynote_process(hid_keynote, event);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidKeynote* hid_keynote_alloc(Hid* hid) {
|
||||
HidKeynote* hid_keynote = malloc(sizeof(HidKeynote));
|
||||
hid_keynote->view = view_alloc();
|
||||
hid_keynote->hid = hid;
|
||||
view_set_context(hid_keynote->view, hid_keynote);
|
||||
view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel));
|
||||
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
|
||||
view_set_input_callback(hid_keynote->view, hid_keynote_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true);
|
||||
|
||||
return hid_keynote;
|
||||
}
|
||||
|
||||
void hid_keynote_free(HidKeynote* hid_keynote) {
|
||||
furi_assert(hid_keynote);
|
||||
view_free(hid_keynote->view);
|
||||
free(hid_keynote);
|
||||
}
|
||||
|
||||
View* hid_keynote_get_view(HidKeynote* hid_keynote) {
|
||||
furi_assert(hid_keynote);
|
||||
return hid_keynote->view;
|
||||
}
|
||||
|
||||
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) {
|
||||
furi_assert(hid_keynote);
|
||||
with_view_model(
|
||||
hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
|
||||
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) {
|
||||
furi_assert(hid_keynote);
|
||||
|
||||
if(vertical) {
|
||||
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback);
|
||||
view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip);
|
||||
|
||||
} else {
|
||||
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
|
||||
view_set_orientation(hid_keynote->view, ViewOrientationHorizontal);
|
||||
}
|
||||
}
|
16
applications/system/hid_app/views/hid_keynote.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidKeynote HidKeynote;
|
||||
|
||||
HidKeynote* hid_keynote_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_keynote_free(HidKeynote* hid_keynote);
|
||||
|
||||
View* hid_keynote_get_view(HidKeynote* hid_keynote);
|
||||
|
||||
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected);
|
||||
|
||||
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical);
|
234
applications/system/hid_app/views/hid_media.c
Normal file
|
@ -0,0 +1,234 @@
|
|||
#include "hid_media.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMedia"
|
||||
|
||||
struct HidMedia {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
bool back_pressed;
|
||||
HidTransport transport;
|
||||
} HidMediaModel;
|
||||
|
||||
static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_dot(canvas, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_dot(canvas, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_dot(canvas, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_dot(canvas, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_media_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMediaModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft);
|
||||
hid_media_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
|
||||
canvas_draw_line(canvas, 64, 26, 64, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
|
||||
hid_media_draw_arrow(canvas, 99, 28, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 102, 26, 102, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_media_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 84, 26, 84, 30);
|
||||
canvas_draw_line(canvas, 86, 26, 86, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Exit
|
||||
if(model->back_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_media->view,
|
||||
HidMediaModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_media->view,
|
||||
HidMediaModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_media_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMedia* hid_media = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_release_all(hid_media->hid);
|
||||
} else {
|
||||
consumed = true;
|
||||
if(event->type == InputTypePress) {
|
||||
hid_media_process_press(hid_media, event);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
hid_media_process_release(hid_media, event);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMedia* hid_media_alloc(Hid* hid) {
|
||||
HidMedia* hid_media = malloc(sizeof(HidMedia));
|
||||
hid_media->view = view_alloc();
|
||||
hid_media->hid = hid;
|
||||
view_set_context(hid_media->view, hid_media);
|
||||
view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel));
|
||||
view_set_draw_callback(hid_media->view, hid_media_draw_callback);
|
||||
view_set_input_callback(hid_media->view, hid_media_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true);
|
||||
|
||||
return hid_media;
|
||||
}
|
||||
|
||||
void hid_media_free(HidMedia* hid_media) {
|
||||
furi_assert(hid_media);
|
||||
view_free(hid_media->view);
|
||||
free(hid_media);
|
||||
}
|
||||
|
||||
View* hid_media_get_view(HidMedia* hid_media) {
|
||||
furi_assert(hid_media);
|
||||
return hid_media->view;
|
||||
}
|
||||
|
||||
void hid_media_set_connected_status(HidMedia* hid_media, bool connected) {
|
||||
furi_assert(hid_media);
|
||||
with_view_model(
|
||||
hid_media->view, HidMediaModel * model, { model->connected = connected; }, true);
|
||||
}
|
13
applications/system/hid_app/views/hid_media.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct HidMedia HidMedia;
|
||||
|
||||
HidMedia* hid_media_alloc();
|
||||
|
||||
void hid_media_free(HidMedia* hid_media);
|
||||
|
||||
View* hid_media_get_view(HidMedia* hid_media);
|
||||
|
||||
void hid_media_set_connected_status(HidMedia* hid_media, bool connected);
|
243
applications/system/hid_app/views/hid_mouse.c
Normal file
|
@ -0,0 +1,243 @@
|
|||
#include "hid_mouse.h"
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMouse"
|
||||
|
||||
struct HidMouse {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool left_mouse_pressed;
|
||||
bool left_mouse_held;
|
||||
bool right_mouse_pressed;
|
||||
bool connected;
|
||||
uint8_t acceleration;
|
||||
HidTransport transport;
|
||||
} HidMouseModel;
|
||||
|
||||
static void hid_mouse_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
if(model->left_mouse_held == true) {
|
||||
elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting...");
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 8, &I_Pin_arrow_up_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 40, &I_Pin_arrow_down_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 63, 25, &I_Pin_arrow_left_9x7);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 95, 25, &I_Pin_arrow_right_9x7);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->left_mouse_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 79, 24, &I_Left_mouse_icon_9x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
if(model->right_mouse_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 112, 38, &I_Right_mouse_icon_9x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_mouse->view,
|
||||
HidMouseModel * model,
|
||||
{
|
||||
model->acceleration = (event->type == InputTypePress) ? 1 :
|
||||
(event->type == InputTypeRelease) ? 0 :
|
||||
(model->acceleration >= 20) ? 20 :
|
||||
model->acceleration + 1;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(event->type == InputTypeShort) {
|
||||
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
|
||||
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
|
||||
} else if(event->type == InputTypePress) {
|
||||
model->right_mouse_pressed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->right_mouse_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypeShort) {
|
||||
// Just release if it was being held before
|
||||
if(!model->left_mouse_held)
|
||||
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
|
||||
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
|
||||
model->left_mouse_held = false;
|
||||
} else if(event->type == InputTypeLong) {
|
||||
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
|
||||
model->left_mouse_held = true;
|
||||
model->left_mouse_pressed = true;
|
||||
} else if(event->type == InputTypePress) {
|
||||
model->left_mouse_pressed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
// Only release if it wasn't a long press
|
||||
if(!model->left_mouse_held) model->left_mouse_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0);
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
for(uint8_t i = model->acceleration; i > 1; i -= 2)
|
||||
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->right_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0);
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
for(uint8_t i = model->acceleration; i > 1; i -= 2)
|
||||
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->left_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT);
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
for(uint8_t i = model->acceleration; i > 1; i -= 2)
|
||||
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG);
|
||||
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->down_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyUp) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT);
|
||||
} else if(event->type == InputTypeRepeat) {
|
||||
for(uint8_t i = model->acceleration; i > 1; i -= 2)
|
||||
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->up_pressed = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_mouse_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouse* hid_mouse = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_mouse_release_all(hid_mouse->hid);
|
||||
} else {
|
||||
hid_mouse_process(hid_mouse, event);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMouse* hid_mouse_alloc(Hid* hid) {
|
||||
HidMouse* hid_mouse = malloc(sizeof(HidMouse));
|
||||
hid_mouse->view = view_alloc();
|
||||
hid_mouse->hid = hid;
|
||||
view_set_context(hid_mouse->view, hid_mouse);
|
||||
view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel));
|
||||
view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback);
|
||||
view_set_input_callback(hid_mouse->view, hid_mouse_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true);
|
||||
|
||||
return hid_mouse;
|
||||
}
|
||||
|
||||
void hid_mouse_free(HidMouse* hid_mouse) {
|
||||
furi_assert(hid_mouse);
|
||||
view_free(hid_mouse->view);
|
||||
free(hid_mouse);
|
||||
}
|
||||
|
||||
View* hid_mouse_get_view(HidMouse* hid_mouse) {
|
||||
furi_assert(hid_mouse);
|
||||
return hid_mouse->view;
|
||||
}
|
||||
|
||||
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) {
|
||||
furi_assert(hid_mouse);
|
||||
with_view_model(
|
||||
hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true);
|
||||
}
|
17
applications/system/hid_app/views/hid_mouse.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#define MOUSE_MOVE_SHORT 5
|
||||
#define MOUSE_MOVE_LONG 20
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMouse HidMouse;
|
||||
|
||||
HidMouse* hid_mouse_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_mouse_free(HidMouse* hid_mouse);
|
||||
|
||||
View* hid_mouse_get_view(HidMouse* hid_mouse);
|
||||
|
||||
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected);
|
214
applications/system/hid_app/views/hid_mouse_clicker.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
#include "hid_mouse_clicker.h"
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMouseClicker"
|
||||
#define DEFAULT_CLICK_RATE 1
|
||||
#define MAXIMUM_CLICK_RATE 60
|
||||
|
||||
struct HidMouseClicker {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool connected;
|
||||
bool running;
|
||||
int rate;
|
||||
HidTransport transport;
|
||||
} HidMouseClickerModel;
|
||||
|
||||
static void hid_mouse_clicker_start_or_restart_timer(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
|
||||
if(furi_timer_is_running(hid_mouse_clicker->timer)) {
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
furi_timer_start(
|
||||
hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClickerModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker");
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
|
||||
if(model->running) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
FuriString* rate_label = furi_string_alloc();
|
||||
furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate);
|
||||
elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label));
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
furi_string_free(rate_label);
|
||||
|
||||
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
|
||||
if(model->running) {
|
||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
|
||||
} else {
|
||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
|
||||
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
if(model->running) {
|
||||
hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
|
||||
hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_enter_callback(void* context) {
|
||||
hid_mouse_clicker_start_or_restart_timer(context);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
}
|
||||
|
||||
static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
|
||||
bool consumed = false;
|
||||
bool rate_changed = false;
|
||||
|
||||
if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
switch(event->key) {
|
||||
case InputKeyOk:
|
||||
model->running = !model->running;
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyUp:
|
||||
if(model->rate < MAXIMUM_CLICK_RATE) {
|
||||
model->rate++;
|
||||
}
|
||||
rate_changed = true;
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(model->rate > 1) {
|
||||
model->rate--;
|
||||
}
|
||||
rate_changed = true;
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
if(rate_changed) {
|
||||
hid_mouse_clicker_start_or_restart_timer(context);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) {
|
||||
HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker));
|
||||
|
||||
hid_mouse_clicker->view = view_alloc();
|
||||
view_set_context(hid_mouse_clicker->view, hid_mouse_clicker);
|
||||
view_allocate_model(
|
||||
hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel));
|
||||
view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback);
|
||||
view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback);
|
||||
view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback);
|
||||
view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback);
|
||||
|
||||
hid_mouse_clicker->hid = hid;
|
||||
|
||||
hid_mouse_clicker->timer = furi_timer_alloc(
|
||||
hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker);
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
model->transport = hid->transport;
|
||||
model->rate = DEFAULT_CLICK_RATE;
|
||||
},
|
||||
true);
|
||||
|
||||
return hid_mouse_clicker;
|
||||
}
|
||||
|
||||
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
furi_timer_free(hid_mouse_clicker->timer);
|
||||
|
||||
view_free(hid_mouse_clicker->view);
|
||||
|
||||
free(hid_mouse_clicker);
|
||||
}
|
||||
|
||||
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
return hid_mouse_clicker->view;
|
||||
}
|
||||
|
||||
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{ model->connected = connected; },
|
||||
true);
|
||||
}
|
14
applications/system/hid_app/views/hid_mouse_clicker.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMouseClicker HidMouseClicker;
|
||||
|
||||
HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker);
|
||||
|
||||
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker);
|
||||
|
||||
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected);
|
183
applications/system/hid_app/views/hid_mouse_jiggler.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
#include "hid_mouse_jiggler.h"
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMouseJiggler"
|
||||
|
||||
struct HidMouseJiggler {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool connected;
|
||||
bool running;
|
||||
int interval_idx;
|
||||
uint8_t counter;
|
||||
HidTransport transport;
|
||||
} HidMouseJigglerModel;
|
||||
|
||||
const int intervals[6] = {500, 2000, 5000, 10000, 30000, 60000};
|
||||
|
||||
static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseJigglerModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 2, AlignLeft, AlignTop, "Mouse Jiggler");
|
||||
|
||||
// Timeout
|
||||
elements_multiline_text(canvas, AlignLeft, 26, "Interval (ms):");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->interval_idx != 0) canvas_draw_icon(canvas, 74, 19, &I_ButtonLeft_4x7);
|
||||
if(model->interval_idx != (int)COUNT_OF(intervals) - 1)
|
||||
canvas_draw_icon(canvas, 80, 19, &I_ButtonRight_4x7);
|
||||
FuriString* interval_str = furi_string_alloc_printf("%d", intervals[model->interval_idx]);
|
||||
elements_multiline_text(canvas, 91, 26, furi_string_get_cstr(interval_str));
|
||||
furi_string_free(interval_str);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text(canvas, AlignLeft, 40, "Press Start\nto jiggle");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 30, &I_Space_65x18);
|
||||
if(model->running) {
|
||||
elements_slightly_rounded_box(canvas, 66, 32, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 34, &I_Ok_btn_9x9);
|
||||
if(model->running) {
|
||||
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Stop");
|
||||
} else {
|
||||
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Start");
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
canvas_draw_icon(canvas, 74, 54, &I_Pin_back_arrow_10x8);
|
||||
elements_multiline_text_aligned(canvas, 91, 62, AlignLeft, AlignBottom, "Quit");
|
||||
}
|
||||
|
||||
static void hid_mouse_jiggler_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseJiggler* hid_mouse_jiggler = context;
|
||||
with_view_model(
|
||||
hid_mouse_jiggler->view,
|
||||
HidMouseJigglerModel * model,
|
||||
{
|
||||
if(model->running) {
|
||||
model->counter++;
|
||||
hid_hal_mouse_move(
|
||||
hid_mouse_jiggler->hid,
|
||||
(model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT,
|
||||
0);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
static void hid_mouse_jiggler_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseJiggler* hid_mouse_jiggler = context;
|
||||
furi_timer_stop(hid_mouse_jiggler->timer);
|
||||
}
|
||||
|
||||
static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseJiggler* hid_mouse_jiggler = context;
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_jiggler->view,
|
||||
HidMouseJigglerModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress && event->key == InputKeyOk) {
|
||||
model->running = !model->running;
|
||||
if(model->running) {
|
||||
furi_timer_stop(hid_mouse_jiggler->timer);
|
||||
furi_timer_start(hid_mouse_jiggler->timer, intervals[model->interval_idx]);
|
||||
};
|
||||
consumed = true;
|
||||
}
|
||||
if(event->type == InputTypePress && event->key == InputKeyRight && !model->running &&
|
||||
model->interval_idx < (int)COUNT_OF(intervals) - 1) {
|
||||
model->interval_idx++;
|
||||
consumed = true;
|
||||
}
|
||||
if(event->type == InputTypePress && event->key == InputKeyLeft && !model->running &&
|
||||
model->interval_idx > 0) {
|
||||
model->interval_idx--;
|
||||
consumed = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) {
|
||||
HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler));
|
||||
|
||||
hid_mouse_jiggler->view = view_alloc();
|
||||
view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler);
|
||||
view_allocate_model(
|
||||
hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel));
|
||||
view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback);
|
||||
view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback);
|
||||
view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback);
|
||||
|
||||
hid_mouse_jiggler->hid = hid;
|
||||
|
||||
hid_mouse_jiggler->timer = furi_timer_alloc(
|
||||
hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler);
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_jiggler->view,
|
||||
HidMouseJigglerModel * model,
|
||||
{
|
||||
model->transport = hid->transport;
|
||||
model->interval_idx = 2;
|
||||
},
|
||||
true);
|
||||
|
||||
return hid_mouse_jiggler;
|
||||
}
|
||||
|
||||
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) {
|
||||
furi_assert(hid_mouse_jiggler);
|
||||
|
||||
furi_timer_stop(hid_mouse_jiggler->timer);
|
||||
furi_timer_free(hid_mouse_jiggler->timer);
|
||||
|
||||
view_free(hid_mouse_jiggler->view);
|
||||
|
||||
free(hid_mouse_jiggler);
|
||||
}
|
||||
|
||||
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) {
|
||||
furi_assert(hid_mouse_jiggler);
|
||||
return hid_mouse_jiggler->view;
|
||||
}
|
||||
|
||||
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) {
|
||||
furi_assert(hid_mouse_jiggler);
|
||||
with_view_model(
|
||||
hid_mouse_jiggler->view,
|
||||
HidMouseJigglerModel * model,
|
||||
{ model->connected = connected; },
|
||||
true);
|
||||
}
|
16
applications/system/hid_app/views/hid_mouse_jiggler.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#define MOUSE_MOVE_SHORT 5
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMouseJiggler HidMouseJiggler;
|
||||
|
||||
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler);
|
||||
|
||||
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler);
|
||||
|
||||
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected);
|
235
applications/system/hid_app/views/hid_movie.c
Normal file
|
@ -0,0 +1,235 @@
|
|||
#include "hid_movie.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMovie"
|
||||
|
||||
struct HidMovie {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
bool back_pressed;
|
||||
HidTransport transport;
|
||||
} HidMovieModel;
|
||||
|
||||
static void hid_movie_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_dot(canvas, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_dot(canvas, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_dot(canvas, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_dot(canvas, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_movie_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMovieModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft);
|
||||
hid_movie_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
|
||||
hid_movie_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 84, 26, 84, 30);
|
||||
canvas_draw_line(canvas, 86, 26, 86, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Exit
|
||||
if(model->back_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
static void hid_movie_process_press(HidMovie* hid_movie, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_movie->view,
|
||||
HidMovieModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_movie_process_release(HidMovie* hid_movie, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_movie->view,
|
||||
HidMovieModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_movie_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMovie* hid_movie = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_release_all(hid_movie->hid);
|
||||
} else {
|
||||
consumed = true;
|
||||
if(event->type == InputTypePress) {
|
||||
hid_movie_process_press(hid_movie, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
hid_movie_process_release(hid_movie, event);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMovie* hid_movie_alloc(Hid* hid) {
|
||||
HidMovie* hid_movie = malloc(sizeof(HidMovie));
|
||||
hid_movie->view = view_alloc();
|
||||
hid_movie->hid = hid;
|
||||
view_set_context(hid_movie->view, hid_movie);
|
||||
view_allocate_model(hid_movie->view, ViewModelTypeLocking, sizeof(HidMovieModel));
|
||||
view_set_draw_callback(hid_movie->view, hid_movie_draw_callback);
|
||||
view_set_input_callback(hid_movie->view, hid_movie_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_movie->view, HidMovieModel * model, { model->transport = hid->transport; }, true);
|
||||
|
||||
return hid_movie;
|
||||
}
|
||||
|
||||
void hid_movie_free(HidMovie* hid_movie) {
|
||||
furi_assert(hid_movie);
|
||||
view_free(hid_movie->view);
|
||||
free(hid_movie);
|
||||
}
|
||||
|
||||
View* hid_movie_get_view(HidMovie* hid_movie) {
|
||||
furi_assert(hid_movie);
|
||||
return hid_movie->view;
|
||||
}
|
||||
|
||||
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected) {
|
||||
furi_assert(hid_movie);
|
||||
with_view_model(
|
||||
hid_movie->view, HidMovieModel * model, { model->connected = connected; }, true);
|
||||
}
|
14
applications/system/hid_app/views/hid_movie.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMovie HidMovie;
|
||||
|
||||
HidMovie* hid_movie_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_movie_free(HidMovie* hid_movie);
|
||||
|
||||
View* hid_movie_get_view(HidMovie* hid_movie);
|
||||
|
||||
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected);
|
318
applications/system/hid_app/views/hid_numpad.c
Normal file
|
@ -0,0 +1,318 @@
|
|||
#include "hid_numpad.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include "../hid.h"
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidNumpad"
|
||||
|
||||
struct HidNumpad {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t last_x;
|
||||
uint8_t last_y;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t last_key_code;
|
||||
uint16_t modifier_code;
|
||||
bool ok_pressed;
|
||||
bool back_pressed;
|
||||
bool connected;
|
||||
char key_string[5];
|
||||
HidTransport transport;
|
||||
} HidNumpadModel;
|
||||
|
||||
typedef struct {
|
||||
uint8_t width;
|
||||
char* key;
|
||||
uint8_t height;
|
||||
const Icon* icon;
|
||||
uint8_t value;
|
||||
} HidNumpadKey;
|
||||
|
||||
typedef struct {
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
} HidNumpadPoint;
|
||||
|
||||
#define MARGIN_TOP 32
|
||||
#define MARGIN_LEFT 1
|
||||
#define KEY_WIDTH 20
|
||||
#define KEY_HEIGHT 15
|
||||
#define KEY_PADDING 1
|
||||
#define ROW_COUNT 6
|
||||
#define COLUMN_COUNT 3
|
||||
|
||||
const HidNumpadKey hid_numpad_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||
{
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "NL", .value = HID_KEYPAD_NUMLOCK},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "/", .value = HID_KEYPAD_SLASH},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "*", .value = HID_KEYPAD_ASTERISK},
|
||||
// {.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
|
||||
},
|
||||
{
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "7", .value = HID_KEYPAD_7},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "8", .value = HID_KEYBOARD_8},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "9", .value = HID_KEYBOARD_9},
|
||||
// {.width = 1, .height = 2, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
|
||||
},
|
||||
{
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "4", .value = HID_KEYPAD_4},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "5", .value = HID_KEYPAD_5},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "6", .value = HID_KEYPAD_6},
|
||||
},
|
||||
{
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "1", .value = HID_KEYPAD_1},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "2", .value = HID_KEYPAD_2},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "3", .value = HID_KEYPAD_3},
|
||||
// {.width = 1, .height = 2, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
|
||||
},
|
||||
{
|
||||
{.width = 2, .height = 1, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
||||
{.width = 0, .height = 0, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = ".", .value = HID_KEYPAD_DOT},
|
||||
},
|
||||
{
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
|
||||
{.width = 1, .height = 1, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
|
||||
},
|
||||
};
|
||||
|
||||
static void hid_numpad_draw_key(
|
||||
Canvas* canvas,
|
||||
HidNumpadModel* model,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
HidNumpadKey key,
|
||||
bool selected) {
|
||||
if(!key.width || !key.height) return;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
|
||||
uint8_t keyHeight = KEY_HEIGHT * key.height + KEY_PADDING * (key.height - 1);
|
||||
if(selected) {
|
||||
elements_slightly_rounded_box(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||
keyWidth,
|
||||
keyHeight);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
elements_slightly_rounded_frame(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||
keyWidth,
|
||||
keyHeight);
|
||||
}
|
||||
if(key.icon != NULL) {
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + keyHeight / 2 - key.icon->height / 2,
|
||||
key.icon);
|
||||
} else {
|
||||
strcpy(model->key_string, key.key);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
|
||||
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + keyHeight / 2 + 1,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
model->key_string);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_numpad_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidNumpadModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 7, 60, AlignLeft, AlignBottom, "Waiting for\nConnection...");
|
||||
}
|
||||
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Numpad");
|
||||
|
||||
} else {
|
||||
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad");
|
||||
}
|
||||
|
||||
canvas_draw_icon(canvas, 3, 18, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
|
||||
|
||||
if(!model->connected && (model->transport == HidTransportBle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
uint8_t initY = 0; // = model->y == 0 ? 0 : 1;
|
||||
|
||||
// if(model->y > ROW_COUNT) {
|
||||
// initY = model->y - (ROW_COUNT - 1);
|
||||
// }
|
||||
|
||||
for(uint8_t y = initY; y < ROW_COUNT; y++) {
|
||||
const HidNumpadKey* numpadKeyRow = hid_numpad_keyset[y];
|
||||
uint8_t x = 0;
|
||||
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
|
||||
HidNumpadKey key = numpadKeyRow[i];
|
||||
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
|
||||
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
|
||||
hid_numpad_draw_key(
|
||||
canvas,
|
||||
model,
|
||||
x,
|
||||
y - initY,
|
||||
key,
|
||||
(!model->ok_pressed && keySelected) || backSelected);
|
||||
x += key.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t hid_numpad_get_selected_key(HidNumpadModel* model) {
|
||||
HidNumpadKey key = hid_numpad_keyset[model->y][model->x];
|
||||
return key.value;
|
||||
}
|
||||
|
||||
static void hid_numpad_get_select_key(HidNumpadModel* model, HidNumpadPoint delta) {
|
||||
do {
|
||||
const int delta_sum = model->y + delta.y;
|
||||
model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT;
|
||||
} while(delta.y != 0 && hid_numpad_keyset[model->y][model->x].value == 0);
|
||||
|
||||
do {
|
||||
const int delta_sum = model->x + delta.x;
|
||||
model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT;
|
||||
} while(delta.x != 0 && hid_numpad_keyset[model->y][model->x].width == 0);
|
||||
}
|
||||
|
||||
static void hid_numpad_process(HidNumpad* hid_numpad, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_numpad->view,
|
||||
HidNumpadModel * model,
|
||||
{
|
||||
if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->ok_pressed = true;
|
||||
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
|
||||
model->last_key_code = hid_numpad_get_selected_key(model);
|
||||
hid_hal_keyboard_press(
|
||||
hid_numpad->hid, model->modifier_code | model->last_key_code);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
hid_hal_keyboard_release(
|
||||
hid_numpad->hid, model->modifier_code | model->last_key_code);
|
||||
model->ok_pressed = false;
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
if(event->type == InputTypePress) {
|
||||
model->back_pressed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
hid_hal_keyboard_press(hid_numpad->hid, HID_KEYBOARD_DELETE);
|
||||
hid_hal_keyboard_release(hid_numpad->hid, HID_KEYBOARD_DELETE);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||
if(event->key == InputKeyUp) {
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 0, .y = -1});
|
||||
} else if(event->key == InputKeyDown) {
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 0, .y = 1});
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(model->last_x == 2 && model->last_y == 2 && model->y == 1 &&
|
||||
model->x == 3) {
|
||||
model->x = model->last_x;
|
||||
model->y = model->last_y;
|
||||
} else if(
|
||||
model->last_x == 2 && model->last_y == 4 && model->y == 3 &&
|
||||
model->x == 3) {
|
||||
model->x = model->last_x;
|
||||
model->y = model->last_y;
|
||||
} else
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = -1, .y = 0});
|
||||
model->last_x = 0;
|
||||
model->last_y = 0;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(model->x == 2 && model->y == 2) {
|
||||
model->last_x = model->x;
|
||||
model->last_y = model->y;
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = -1});
|
||||
} else if(model->x == 2 && model->y == 4) {
|
||||
model->last_x = model->x;
|
||||
model->last_y = model->y;
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = -1});
|
||||
} else {
|
||||
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_numpad_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidNumpad* hid_numpad = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
hid_hal_keyboard_release_all(hid_numpad->hid);
|
||||
} else {
|
||||
hid_numpad_process(hid_numpad, event);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidNumpad* hid_numpad_alloc(Hid* bt_hid) {
|
||||
HidNumpad* hid_numpad = malloc(sizeof(HidNumpad));
|
||||
hid_numpad->view = view_alloc();
|
||||
hid_numpad->hid = bt_hid;
|
||||
view_set_context(hid_numpad->view, hid_numpad);
|
||||
view_allocate_model(hid_numpad->view, ViewModelTypeLocking, sizeof(HidNumpadModel));
|
||||
view_set_orientation(hid_numpad->view, ViewOrientationVertical);
|
||||
view_set_draw_callback(hid_numpad->view, hid_numpad_draw_callback);
|
||||
view_set_input_callback(hid_numpad->view, hid_numpad_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_numpad->view,
|
||||
HidNumpadModel * model,
|
||||
{
|
||||
model->transport = bt_hid->transport;
|
||||
model->y = 0;
|
||||
},
|
||||
true);
|
||||
|
||||
return hid_numpad;
|
||||
}
|
||||
|
||||
void hid_numpad_free(HidNumpad* hid_numpad) {
|
||||
furi_assert(hid_numpad);
|
||||
view_free(hid_numpad->view);
|
||||
free(hid_numpad);
|
||||
}
|
||||
|
||||
View* hid_numpad_get_view(HidNumpad* hid_numpad) {
|
||||
furi_assert(hid_numpad);
|
||||
return hid_numpad->view;
|
||||
}
|
||||
|
||||
void hid_numpad_set_connected_status(HidNumpad* hid_numpad, bool connected) {
|
||||
furi_assert(hid_numpad);
|
||||
with_view_model(
|
||||
hid_numpad->view, HidNumpadModel * model, { model->connected = connected; }, true);
|
||||
}
|
14
applications/system/hid_app/views/hid_numpad.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidNumpad HidNumpad;
|
||||
|
||||
HidNumpad* hid_numpad_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_numpad_free(HidNumpad* hid_numpad);
|
||||
|
||||
View* hid_numpad_get_view(HidNumpad* hid_numpad);
|
||||
|
||||
void hid_numpad_set_connected_status(HidNumpad* hid_numpad, bool connected);
|
815
applications/system/hid_app/views/hid_ptt.c
Normal file
|
@ -0,0 +1,815 @@
|
|||
#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 "HidPushToTalk"
|
||||
|
||||
struct HidPushToTalk {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
Widget* help;
|
||||
};
|
||||
|
||||
typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt);
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool muted;
|
||||
bool ptt_pressed;
|
||||
bool mic_pressed;
|
||||
bool connected;
|
||||
FuriString *os;
|
||||
FuriString *app;
|
||||
size_t osIndex;
|
||||
size_t appIndex;
|
||||
size_t window_position;
|
||||
HidTransport transport;
|
||||
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_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_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_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);
|
||||
HidPushToTalkModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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_2 = y_1 + 19;
|
||||
const uint8_t y_3 = y_2 + 19;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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_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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Ok / PTT
|
||||
const uint8_t x_ptt_margin = 4;
|
||||
const uint8_t x_ptt_width = 17;
|
||||
const uint8_t x_ptt = x_1 + 19;
|
||||
canvas_draw_icon(canvas, x_ptt , y_2 , &I_BtnFrameLeft_3x18);
|
||||
canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2 , &I_BtnFrameRight_2x18);
|
||||
canvas_draw_line(canvas, x_ptt + 3 , y_2 , x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2);
|
||||
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) {
|
||||
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_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_ptt->view,
|
||||
HidPushToTalkModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress && !model->ptt_pressed) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ptt_pressed = true;
|
||||
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;
|
||||
}
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
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->ptt_pressed){
|
||||
hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ptt_pressed = false;
|
||||
if(!model->mic_pressed) {
|
||||
if (model->muted) {
|
||||
model->callback_stop_ptt ? model->callback_stop_ptt(hid_ptt):0;
|
||||
} else {
|
||||
model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
|
||||
model->muted = true;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
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;
|
||||
view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
|
||||
}
|
||||
//LED
|
||||
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);
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_ptt_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
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;
|
||||
}
|
||||
|
||||
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(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, HidPushToTalkModel * model, {
|
||||
model->transport = hid->transport;
|
||||
model->muted = true; // assume we're muted
|
||||
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(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);
|
||||
}
|
||||
|
||||
void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected) {
|
||||
furi_assert(hid_ptt);
|
||||
with_view_model(
|
||||
hid_ptt->view, HidPushToTalkModel * model, {
|
||||
if (!connected && model->connected) {
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
model->connected = connected;
|
||||
}, true);
|
||||
}
|
19
applications/system/hid_app/views/hid_ptt.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidPushToTalk HidPushToTalk;
|
||||
|
||||
HidPushToTalk* hid_ptt_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_ptt_free(HidPushToTalk* hid_ptt);
|
||||
|
||||
View* hid_ptt_get_view(HidPushToTalk* hid_ptt);
|
||||
|
||||
void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected);
|
||||
|
||||
enum HidPushToTalkOSes {
|
||||
HidPushToTalkMacOS,
|
||||
HidPushToTalkLinux,
|
||||
};
|
413
applications/system/hid_app/views/hid_ptt_menu.c
Normal file
|
@ -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
applications/system/hid_app/views/hid_ptt_menu.h
Normal file
|
@ -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);
|
271
applications/system/hid_app/views/hid_tikshorts.c
Normal file
|
@ -0,0 +1,271 @@
|
|||
#include "hid_tikshorts.h"
|
||||
#include "../hid.h"
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidTikShorts"
|
||||
|
||||
struct HidTikShorts {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
bool is_cursor_set;
|
||||
bool back_mouse_pressed;
|
||||
HidTransport transport;
|
||||
} HidTikShortsModel;
|
||||
|
||||
static void hid_tikshorts_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidTikShortsModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok /");
|
||||
elements_multiline_text_aligned(canvas, 3, 18, AlignLeft, AlignTop, "YT Shorts");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
|
||||
|
||||
// Pause
|
||||
if(model->back_mouse_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Exit
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
static void hid_tikshorts_reset_cursor(HidTikShorts* hid_tikshorts) {
|
||||
// Set cursor to the phone's left up corner
|
||||
// Delays to guarantee one packet per connection interval
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
hid_hal_mouse_move(hid_tikshorts->hid, -127, -127);
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
// Move cursor from the corner
|
||||
hid_hal_mouse_move(hid_tikshorts->hid, 20, 120);
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
static void hid_tikshorts_process_press(
|
||||
HidTikShorts* hid_tikshorts,
|
||||
HidTikShortsModel* model,
|
||||
InputEvent* event) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_mouse_pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_tikshorts_process_release(
|
||||
HidTikShorts* hid_tikshorts,
|
||||
HidTikShortsModel* model,
|
||||
InputEvent* event) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_mouse_pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool hid_tikshorts_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidTikShorts* hid_tikshorts = context;
|
||||
bool consumed = false;
|
||||
|
||||
with_view_model(
|
||||
hid_tikshorts->view,
|
||||
HidTikShortsModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
hid_tikshorts_process_press(hid_tikshorts, model, event);
|
||||
if(model->connected && !model->is_cursor_set) {
|
||||
hid_tikshorts_reset_cursor(hid_tikshorts);
|
||||
model->is_cursor_set = true;
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
hid_tikshorts_process_release(hid_tikshorts, model, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(25);
|
||||
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(100);
|
||||
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(25);
|
||||
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
// Swipe to next video
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, 6);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, 8);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, 10);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, 8);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, 6);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
// Swipe to previous video
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, -6);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, -8);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, -10);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, -8);
|
||||
hid_hal_mouse_scroll(hid_tikshorts->hid, -6);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
// Pause
|
||||
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
furi_delay_ms(50);
|
||||
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->type == InputTypeLong) {
|
||||
if(event->key == InputKeyBack) {
|
||||
hid_hal_consumer_key_release_all(hid_tikshorts->hid);
|
||||
model->is_cursor_set = false;
|
||||
consumed = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid) {
|
||||
HidTikShorts* hid_tikshorts = malloc(sizeof(HidTikShorts));
|
||||
hid_tikshorts->hid = bt_hid;
|
||||
hid_tikshorts->view = view_alloc();
|
||||
view_set_context(hid_tikshorts->view, hid_tikshorts);
|
||||
view_allocate_model(hid_tikshorts->view, ViewModelTypeLocking, sizeof(HidTikShortsModel));
|
||||
view_set_draw_callback(hid_tikshorts->view, hid_tikshorts_draw_callback);
|
||||
view_set_input_callback(hid_tikshorts->view, hid_tikshorts_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_tikshorts->view,
|
||||
HidTikShortsModel * model,
|
||||
{ model->transport = bt_hid->transport; },
|
||||
true);
|
||||
|
||||
return hid_tikshorts;
|
||||
}
|
||||
|
||||
void hid_tikshorts_free(HidTikShorts* hid_tikshorts) {
|
||||
furi_assert(hid_tikshorts);
|
||||
view_free(hid_tikshorts->view);
|
||||
free(hid_tikshorts);
|
||||
}
|
||||
|
||||
View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts) {
|
||||
furi_assert(hid_tikshorts);
|
||||
return hid_tikshorts->view;
|
||||
}
|
||||
|
||||
void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected) {
|
||||
furi_assert(hid_tikshorts);
|
||||
with_view_model(
|
||||
hid_tikshorts->view,
|
||||
HidTikShortsModel * model,
|
||||
{
|
||||
model->connected = connected;
|
||||
model->is_cursor_set = false;
|
||||
},
|
||||
true);
|
||||
}
|
14
applications/system/hid_app/views/hid_tikshorts.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidTikShorts HidTikShorts;
|
||||
|
||||
HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_tikshorts_free(HidTikShorts* hid_tikshorts);
|
||||
|
||||
View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts);
|
||||
|
||||
void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected);
|
11
applications/system/snake_game/application.fam
Normal file
|
@ -0,0 +1,11 @@
|
|||
App(
|
||||
appid="snake",
|
||||
name="Snake Game",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="snake_game_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
fap_icon="snake_10px.png",
|
||||
fap_category="Games",
|
||||
)
|
BIN
applications/system/snake_game/snake_10px.png
Normal file
After Width: | Height: | Size: 158 B |
409
applications/system/snake_game/snake_game.c
Normal file
|
@ -0,0 +1,409 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
typedef struct {
|
||||
// +-----x
|
||||
// |
|
||||
// |
|
||||
// y
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
} Point;
|
||||
|
||||
typedef enum {
|
||||
GameStateLife,
|
||||
|
||||
// https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
|
||||
// Armanto: While testing the early versions of the game, I noticed it was hard
|
||||
// to control the snake upon getting close to and edge but not crashing — especially
|
||||
// in the highest speed levels. I wanted the highest level to be as fast as I could
|
||||
// possibly make the device "run," but on the other hand, I wanted to be friendly
|
||||
// and help the player manage that level. Otherwise it might not be fun to play. So
|
||||
// I implemented a little delay. A few milliseconds of extra time right before
|
||||
// the player crashes, during which she can still change the directions. And if
|
||||
// she does, the game continues.
|
||||
GameStateLastChance,
|
||||
|
||||
GameStateGameOver,
|
||||
} GameState;
|
||||
|
||||
// Note: do not change without purpose. Current values are used in smart
|
||||
// orthogonality calculation in `snake_game_get_turn_snake`.
|
||||
typedef enum {
|
||||
DirectionUp,
|
||||
DirectionRight,
|
||||
DirectionDown,
|
||||
DirectionLeft,
|
||||
} Direction;
|
||||
|
||||
#define MAX_SNAKE_LEN 128 * 64 / 4
|
||||
|
||||
typedef struct {
|
||||
Point points[MAX_SNAKE_LEN];
|
||||
uint16_t len;
|
||||
Direction currentMovement;
|
||||
Direction nextMovement; // if backward of currentMovement, ignore
|
||||
Point fruit;
|
||||
GameState state;
|
||||
FuriMutex* mutex;
|
||||
} SnakeState;
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} SnakeEvent;
|
||||
|
||||
const NotificationSequence sequence_fail = {
|
||||
&message_vibro_on,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_eat = {
|
||||
&message_note_c7,
|
||||
&message_delay_50,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
const SnakeState* snake_state = ctx;
|
||||
|
||||
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
|
||||
|
||||
// Frame
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
// Fruit
|
||||
Point f = snake_state->fruit;
|
||||
f.x = f.x * 4 + 1;
|
||||
f.y = f.y * 4 + 1;
|
||||
canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
|
||||
|
||||
// Snake
|
||||
for(uint16_t i = 0; i < snake_state->len; i++) {
|
||||
Point p = snake_state->points[i];
|
||||
p.x = p.x * 4 + 2;
|
||||
p.y = p.y * 4 + 2;
|
||||
canvas_draw_box(canvas, p.x, p.y, 4, 4);
|
||||
}
|
||||
|
||||
// Game Over banner
|
||||
if(snake_state->state == GameStateGameOver) {
|
||||
// Screen is 128x64 px
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 34, 20, 62, 24);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, 34, 20, 62, 24);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 37, 31, "Game Over");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buffer[12];
|
||||
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
|
||||
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
|
||||
}
|
||||
|
||||
furi_mutex_release(snake_state->mutex);
|
||||
}
|
||||
|
||||
static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
SnakeEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
static void snake_game_init_game(SnakeState* const snake_state) {
|
||||
Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
|
||||
memcpy(snake_state->points, p, sizeof(p)); //-V1086
|
||||
|
||||
snake_state->len = 7;
|
||||
|
||||
snake_state->currentMovement = DirectionRight;
|
||||
|
||||
snake_state->nextMovement = DirectionRight;
|
||||
|
||||
Point f = {18, 6};
|
||||
snake_state->fruit = f;
|
||||
|
||||
snake_state->state = GameStateLife;
|
||||
}
|
||||
|
||||
static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
|
||||
// 1 bit for each point on the playing field where the snake can turn
|
||||
// and where the fruit can appear
|
||||
uint16_t buffer[8];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
uint8_t empty = 8 * 16;
|
||||
|
||||
for(uint16_t i = 0; i < snake_state->len; i++) {
|
||||
Point p = snake_state->points[i];
|
||||
|
||||
if(p.x % 2 != 0 || p.y % 2 != 0) {
|
||||
continue;
|
||||
}
|
||||
p.x /= 2;
|
||||
p.y /= 2;
|
||||
|
||||
buffer[p.y] |= 1 << p.x;
|
||||
empty--;
|
||||
}
|
||||
// Bit set if snake use that playing field
|
||||
|
||||
uint16_t newFruit = rand() % empty;
|
||||
|
||||
// Skip random number of _empty_ fields
|
||||
for(uint8_t y = 0; y < 8; y++) {
|
||||
for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) {
|
||||
if((buffer[y] & mask) == 0) {
|
||||
if(newFruit == 0) {
|
||||
Point p = {
|
||||
.x = x * 2,
|
||||
.y = y * 2,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
newFruit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We will never be here
|
||||
Point p = {0, 0};
|
||||
return p;
|
||||
}
|
||||
|
||||
static bool snake_game_collision_with_frame(Point const next_step) {
|
||||
// if x == 0 && currentMovement == left then x - 1 == 255 ,
|
||||
// so check only x > right border
|
||||
return next_step.x > 30 || next_step.y > 14;
|
||||
}
|
||||
|
||||
static bool
|
||||
snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
|
||||
for(uint16_t i = 0; i < snake_state->len; i++) {
|
||||
Point p = snake_state->points[i];
|
||||
if(p.x == next_step.x && p.y == next_step.y) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
|
||||
// Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
|
||||
bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
|
||||
return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
|
||||
}
|
||||
|
||||
static Point snake_game_get_next_step(SnakeState const* const snake_state) {
|
||||
Point next_step = snake_state->points[0];
|
||||
switch(snake_state->currentMovement) {
|
||||
// +-----x
|
||||
// |
|
||||
// |
|
||||
// y
|
||||
case DirectionUp:
|
||||
next_step.y--;
|
||||
break;
|
||||
case DirectionRight:
|
||||
next_step.x++;
|
||||
break;
|
||||
case DirectionDown:
|
||||
next_step.y++;
|
||||
break;
|
||||
case DirectionLeft:
|
||||
next_step.x--;
|
||||
break;
|
||||
}
|
||||
return next_step;
|
||||
}
|
||||
|
||||
static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
|
||||
memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
|
||||
snake_state->points[0] = next_step;
|
||||
}
|
||||
|
||||
static void
|
||||
snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) {
|
||||
if(snake_state->state == GameStateGameOver) {
|
||||
return;
|
||||
}
|
||||
|
||||
snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
|
||||
|
||||
Point next_step = snake_game_get_next_step(snake_state);
|
||||
|
||||
bool crush = snake_game_collision_with_frame(next_step);
|
||||
if(crush) {
|
||||
if(snake_state->state == GameStateLife) {
|
||||
snake_state->state = GameStateLastChance;
|
||||
return;
|
||||
} else if(snake_state->state == GameStateLastChance) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message_block(notification, &sequence_fail);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if(snake_state->state == GameStateLastChance) {
|
||||
snake_state->state = GameStateLife;
|
||||
}
|
||||
}
|
||||
|
||||
crush = snake_game_collision_with_tail(snake_state, next_step);
|
||||
if(crush) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message_block(notification, &sequence_fail);
|
||||
return;
|
||||
}
|
||||
|
||||
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
||||
if(eatFruit) {
|
||||
snake_state->len++;
|
||||
if(snake_state->len >= MAX_SNAKE_LEN) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message_block(notification, &sequence_fail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snake_game_move_snake(snake_state, next_step);
|
||||
|
||||
if(eatFruit) {
|
||||
snake_state->fruit = snake_game_get_new_fruit(snake_state);
|
||||
notification_message(notification, &sequence_eat);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t snake_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
|
||||
|
||||
SnakeState* snake_state = malloc(sizeof(SnakeState));
|
||||
snake_game_init_game(snake_state);
|
||||
|
||||
snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
if(!snake_state->mutex) {
|
||||
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(snake_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state);
|
||||
view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
|
||||
SnakeEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
||||
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
snake_state->nextMovement = DirectionUp;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
snake_state->nextMovement = DirectionDown;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
snake_state->nextMovement = DirectionRight;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
snake_state->nextMovement = DirectionLeft;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(snake_state->state == GameStateGameOver) {
|
||||
snake_game_init_game(snake_state);
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
snake_game_process_game_step(snake_state, notification);
|
||||
}
|
||||
} else {
|
||||
// event timeout
|
||||
}
|
||||
|
||||
furi_mutex_release(snake_state->mutex);
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
// Return backlight to normal state
|
||||
notification_message(notification, &sequence_display_backlight_enforce_auto);
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_mutex_free(snake_state->mutex);
|
||||
free(snake_state);
|
||||
|
||||
return 0;
|
||||
}
|