From 4b95efda4989ef3b57044e339eedbb77afe57461 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Jan 2024 22:36:36 +0300 Subject: [PATCH] move hid and snake apps into main repo [ci skip] --- applications/system/hid_app/application.fam | 24 + .../system/hid_app/assets/Arr_dwn_7x9.png | Bin 0 -> 3602 bytes .../system/hid_app/assets/Arr_up_7x9.png | Bin 0 -> 3605 bytes .../hid_app/assets/Ble_connected_15x15.png | Bin 0 -> 3634 bytes .../hid_app/assets/Ble_disconnected_15x15.png | Bin 0 -> 657 bytes .../hid_app/assets/BrokenButton_15x15.png | Bin 0 -> 340 bytes .../system/hid_app/assets/BtnBackV_9x9.png | Bin 0 -> 362 bytes .../hid_app/assets/BtnFrameLeft_3x18.png | Bin 0 -> 362 bytes .../hid_app/assets/BtnFrameRight_2x18.png | Bin 0 -> 362 bytes .../system/hid_app/assets/BtnLeft_9x9.png | Bin 0 -> 362 bytes .../system/hid_app/assets/ButtonDown_7x4.png | Bin 0 -> 102 bytes .../system/hid_app/assets/ButtonF10_5x8.png | Bin 0 -> 172 bytes .../system/hid_app/assets/ButtonF11_5x8.png | Bin 0 -> 173 bytes .../system/hid_app/assets/ButtonF12_5x8.png | Bin 0 -> 180 bytes .../system/hid_app/assets/ButtonF1_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF2_5x8.png | Bin 0 -> 179 bytes .../system/hid_app/assets/ButtonF3_5x8.png | Bin 0 -> 178 bytes .../system/hid_app/assets/ButtonF4_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF5_5x8.png | Bin 0 -> 178 bytes .../system/hid_app/assets/ButtonF6_5x8.png | Bin 0 -> 177 bytes .../system/hid_app/assets/ButtonF7_5x8.png | Bin 0 -> 176 bytes .../system/hid_app/assets/ButtonF8_5x8.png | Bin 0 -> 176 bytes .../system/hid_app/assets/ButtonF9_5x8.png | Bin 0 -> 179 bytes .../system/hid_app/assets/ButtonLeft_4x7.png | Bin 0 -> 1415 bytes .../system/hid_app/assets/ButtonRight_4x7.png | Bin 0 -> 1839 bytes .../system/hid_app/assets/ButtonUp_7x4.png | Bin 0 -> 102 bytes .../system/hid_app/assets/Button_18x18.png | Bin 0 -> 3609 bytes .../system/hid_app/assets/Circles_47x47.png | Bin 0 -> 3712 bytes .../system/hid_app/assets/Hand_8x10.png | Bin 0 -> 156 bytes .../system/hid_app/assets/Help_exit_64x9.png | Bin 0 -> 404 bytes .../system/hid_app/assets/Help_top_64x17.png | Bin 0 -> 470 bytes .../system/hid_app/assets/Hold_15x5.png | Bin 0 -> 356 bytes .../hid_app/assets/KB_key_Alt_17x10.png | Bin 0 -> 162 bytes .../hid_app/assets/KB_key_Cmd_17x10.png | Bin 0 -> 163 bytes .../hid_app/assets/KB_key_Ctl_17x10.png | Bin 0 -> 161 bytes .../hid_app/assets/KB_key_Del_17x10.png | Bin 0 -> 165 bytes .../hid_app/assets/KB_key_Esc_17x10.png | Bin 0 -> 164 bytes .../hid_app/assets/KB_key_Tab_17x10.png | Bin 0 -> 163 bytes .../hid_app/assets/Left_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../system/hid_app/assets/Like_def_11x9.png | Bin 0 -> 3616 bytes .../hid_app/assets/Like_pressed_17x17.png | Bin 0 -> 3643 bytes .../system/hid_app/assets/Mic_7x11.png | Bin 0 -> 356 bytes .../assets/MicrophoneCrossed_16x16.png | Bin 0 -> 341 bytes .../assets/MicrophonePressedBtn_16x16.png | Bin 0 -> 329 bytes .../MicrophonePressedCrossedBtn_16x16.png | Bin 0 -> 339 bytes .../system/hid_app/assets/Ok_btn_9x9.png | Bin 0 -> 3605 bytes .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin 0 -> 3625 bytes .../hid_app/assets/OutCircles_70x51.png | Bin 0 -> 2469 bytes .../system/hid_app/assets/Pause_icon_9x9.png | Bin 0 -> 1743 bytes .../hid_app/assets/Pin_arrow_down_7x9.png | Bin 0 -> 3607 bytes .../hid_app/assets/Pin_arrow_left_9x7.png | Bin 0 -> 3603 bytes .../hid_app/assets/Pin_arrow_right_9x7.png | Bin 0 -> 3602 bytes .../hid_app/assets/Pin_arrow_up_7x9.png | Bin 0 -> 3603 bytes .../hid_app/assets/Pin_back_arrow_10x10.png | Bin 0 -> 4575 bytes .../hid_app/assets/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes .../assets/Pin_back_arrow_rotated_8x10.png | Bin 0 -> 959 bytes .../hid_app/assets/Pressed_Button_13x13.png | Bin 0 -> 3606 bytes .../hid_app/assets/Pressed_Button_19x19.png | Bin 0 -> 1790 bytes .../hid_app/assets/Right_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../assets/RoundButtonPressed_16x16.png | Bin 0 -> 320 bytes .../assets/RoundButtonUnpressed_16x16.png | Bin 0 -> 323 bytes .../system/hid_app/assets/S_DOWN_31x15.png | Bin 0 -> 1893 bytes .../system/hid_app/assets/S_LEFT_15x31.png | Bin 0 -> 1906 bytes .../system/hid_app/assets/S_RIGHT_15x31.png | Bin 0 -> 1902 bytes .../system/hid_app/assets/S_UP_31x15.png | Bin 0 -> 1886 bytes .../system/hid_app/assets/Space_60x18.png | Bin 0 -> 2871 bytes .../system/hid_app/assets/Space_65x18.png | Bin 0 -> 3619 bytes .../system/hid_app/assets/Voldwn_6x6.png | Bin 0 -> 4556 bytes .../system/hid_app/assets/Volup_8x6.png | Bin 0 -> 4564 bytes .../system/hid_app/assets/for_help_27x5.png | Bin 0 -> 162 bytes applications/system/hid_app/hid.c | 480 +++++++++++ applications/system/hid_app/hid.h | 75 ++ applications/system/hid_app/hid_ble_10px.png | Bin 0 -> 151 bytes applications/system/hid_app/hid_usb_10px.png | Bin 0 -> 174 bytes applications/system/hid_app/views.h | 15 + .../system/hid_app/views/hid_keyboard.c | 411 +++++++++ .../system/hid_app/views/hid_keyboard.h | 14 + .../system/hid_app/views/hid_keynote.c | 312 +++++++ .../system/hid_app/views/hid_keynote.h | 16 + applications/system/hid_app/views/hid_media.c | 234 +++++ applications/system/hid_app/views/hid_media.h | 13 + applications/system/hid_app/views/hid_mouse.c | 243 ++++++ applications/system/hid_app/views/hid_mouse.h | 17 + .../system/hid_app/views/hid_mouse_clicker.c | 214 +++++ .../system/hid_app/views/hid_mouse_clicker.h | 14 + .../system/hid_app/views/hid_mouse_jiggler.c | 183 ++++ .../system/hid_app/views/hid_mouse_jiggler.h | 16 + applications/system/hid_app/views/hid_movie.c | 235 +++++ applications/system/hid_app/views/hid_movie.h | 14 + .../system/hid_app/views/hid_numpad.c | 318 +++++++ .../system/hid_app/views/hid_numpad.h | 14 + applications/system/hid_app/views/hid_ptt.c | 815 ++++++++++++++++++ applications/system/hid_app/views/hid_ptt.h | 19 + .../system/hid_app/views/hid_ptt_menu.c | 413 +++++++++ .../system/hid_app/views/hid_ptt_menu.h | 27 + .../system/hid_app/views/hid_tikshorts.c | 271 ++++++ .../system/hid_app/views/hid_tikshorts.h | 14 + .../system/snake_game/application.fam | 11 + applications/system/snake_game/snake_10px.png | Bin 0 -> 158 bytes applications/system/snake_game/snake_game.c | 409 +++++++++ 100 files changed, 4841 insertions(+) create mode 100644 applications/system/hid_app/application.fam create mode 100644 applications/system/hid_app/assets/Arr_dwn_7x9.png create mode 100644 applications/system/hid_app/assets/Arr_up_7x9.png create mode 100644 applications/system/hid_app/assets/Ble_connected_15x15.png create mode 100644 applications/system/hid_app/assets/Ble_disconnected_15x15.png create mode 100644 applications/system/hid_app/assets/BrokenButton_15x15.png create mode 100644 applications/system/hid_app/assets/BtnBackV_9x9.png create mode 100644 applications/system/hid_app/assets/BtnFrameLeft_3x18.png create mode 100644 applications/system/hid_app/assets/BtnFrameRight_2x18.png create mode 100644 applications/system/hid_app/assets/BtnLeft_9x9.png create mode 100644 applications/system/hid_app/assets/ButtonDown_7x4.png create mode 100644 applications/system/hid_app/assets/ButtonF10_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF11_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF12_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF1_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF2_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF3_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF4_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF5_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF6_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF7_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF8_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonF9_5x8.png create mode 100644 applications/system/hid_app/assets/ButtonLeft_4x7.png create mode 100644 applications/system/hid_app/assets/ButtonRight_4x7.png create mode 100644 applications/system/hid_app/assets/ButtonUp_7x4.png create mode 100644 applications/system/hid_app/assets/Button_18x18.png create mode 100644 applications/system/hid_app/assets/Circles_47x47.png create mode 100644 applications/system/hid_app/assets/Hand_8x10.png create mode 100644 applications/system/hid_app/assets/Help_exit_64x9.png create mode 100644 applications/system/hid_app/assets/Help_top_64x17.png create mode 100644 applications/system/hid_app/assets/Hold_15x5.png create mode 100644 applications/system/hid_app/assets/KB_key_Alt_17x10.png create mode 100644 applications/system/hid_app/assets/KB_key_Cmd_17x10.png create mode 100644 applications/system/hid_app/assets/KB_key_Ctl_17x10.png create mode 100644 applications/system/hid_app/assets/KB_key_Del_17x10.png create mode 100644 applications/system/hid_app/assets/KB_key_Esc_17x10.png create mode 100644 applications/system/hid_app/assets/KB_key_Tab_17x10.png create mode 100644 applications/system/hid_app/assets/Left_mouse_icon_9x9.png create mode 100644 applications/system/hid_app/assets/Like_def_11x9.png create mode 100644 applications/system/hid_app/assets/Like_pressed_17x17.png create mode 100644 applications/system/hid_app/assets/Mic_7x11.png create mode 100644 applications/system/hid_app/assets/MicrophoneCrossed_16x16.png create mode 100644 applications/system/hid_app/assets/MicrophonePressedBtn_16x16.png create mode 100644 applications/system/hid_app/assets/MicrophonePressedCrossedBtn_16x16.png create mode 100644 applications/system/hid_app/assets/Ok_btn_9x9.png create mode 100644 applications/system/hid_app/assets/Ok_btn_pressed_13x13.png create mode 100644 applications/system/hid_app/assets/OutCircles_70x51.png create mode 100644 applications/system/hid_app/assets/Pause_icon_9x9.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_down_7x9.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_left_9x7.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_right_9x7.png create mode 100644 applications/system/hid_app/assets/Pin_arrow_up_7x9.png create mode 100644 applications/system/hid_app/assets/Pin_back_arrow_10x10.png create mode 100644 applications/system/hid_app/assets/Pin_back_arrow_10x8.png create mode 100644 applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png create mode 100644 applications/system/hid_app/assets/Pressed_Button_13x13.png create mode 100644 applications/system/hid_app/assets/Pressed_Button_19x19.png create mode 100644 applications/system/hid_app/assets/Right_mouse_icon_9x9.png create mode 100644 applications/system/hid_app/assets/RoundButtonPressed_16x16.png create mode 100644 applications/system/hid_app/assets/RoundButtonUnpressed_16x16.png create mode 100644 applications/system/hid_app/assets/S_DOWN_31x15.png create mode 100644 applications/system/hid_app/assets/S_LEFT_15x31.png create mode 100644 applications/system/hid_app/assets/S_RIGHT_15x31.png create mode 100644 applications/system/hid_app/assets/S_UP_31x15.png create mode 100644 applications/system/hid_app/assets/Space_60x18.png create mode 100644 applications/system/hid_app/assets/Space_65x18.png create mode 100644 applications/system/hid_app/assets/Voldwn_6x6.png create mode 100644 applications/system/hid_app/assets/Volup_8x6.png create mode 100644 applications/system/hid_app/assets/for_help_27x5.png create mode 100644 applications/system/hid_app/hid.c create mode 100644 applications/system/hid_app/hid.h create mode 100644 applications/system/hid_app/hid_ble_10px.png create mode 100644 applications/system/hid_app/hid_usb_10px.png create mode 100644 applications/system/hid_app/views.h create mode 100644 applications/system/hid_app/views/hid_keyboard.c create mode 100644 applications/system/hid_app/views/hid_keyboard.h create mode 100644 applications/system/hid_app/views/hid_keynote.c create mode 100644 applications/system/hid_app/views/hid_keynote.h create mode 100644 applications/system/hid_app/views/hid_media.c create mode 100644 applications/system/hid_app/views/hid_media.h create mode 100644 applications/system/hid_app/views/hid_mouse.c create mode 100644 applications/system/hid_app/views/hid_mouse.h create mode 100644 applications/system/hid_app/views/hid_mouse_clicker.c create mode 100644 applications/system/hid_app/views/hid_mouse_clicker.h create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler.c create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler.h create mode 100644 applications/system/hid_app/views/hid_movie.c create mode 100644 applications/system/hid_app/views/hid_movie.h create mode 100644 applications/system/hid_app/views/hid_numpad.c create mode 100644 applications/system/hid_app/views/hid_numpad.h create mode 100644 applications/system/hid_app/views/hid_ptt.c create mode 100644 applications/system/hid_app/views/hid_ptt.h create mode 100644 applications/system/hid_app/views/hid_ptt_menu.c create mode 100644 applications/system/hid_app/views/hid_ptt_menu.h create mode 100644 applications/system/hid_app/views/hid_tikshorts.c create mode 100644 applications/system/hid_app/views/hid_tikshorts.h create mode 100644 applications/system/snake_game/application.fam create mode 100644 applications/system/snake_game/snake_10px.png create mode 100644 applications/system/snake_game/snake_game.c diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam new file mode 100644 index 000000000..c27ad9c81 --- /dev/null +++ b/applications/system/hid_app/application.fam @@ -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", +) diff --git a/applications/system/hid_app/assets/Arr_dwn_7x9.png b/applications/system/hid_app/assets/Arr_dwn_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..d4034efc432b102f6f751d001dc6891b0763c55c GIT binary patch literal 3602 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 JTZ;Aw|2NmVF(Lo} literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Arr_up_7x9.png b/applications/system/hid_app/assets/Arr_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..28b4236a292708b412629ffafe30f6d011491505 GIT binary patch literal 3605 zcmaJ@c{r47|9>2^ZpP~Xl@BrVPMsS}}K`)IgU>xHk zuQ{^ZlqErKm`jmL$vXO)Qi=!THE;DRyVh>Cu@O^m&WRUIOpLs&>}nu;QTm<4xaRG| z^LOGewyt~#yA#k?we+cd{qb9i$>Mo_d8b5;q-?6ak*i6hYyoEX*7xU|8X7;0L#(2t zwb_88WI07MXiZB5SdK6^-v_Rdcn*jJ_sB>BHTbL=*siz@g)f+lqau+PL~6Ln`yC}C zl>n>IM9e+F%2p(jpRVH$QdDDFIb(FP#G03|=i1|;y#5P&&&`q={yo&Yr+iZW$@q$~h)jgQ$2h=l<@&01Q) zz=aGz$#%}u{EvO5ij(@nN@bLpS7;+`qP!&y10_5?A-nZD98~uynUa1XWm-Y%LNe44 zQN{}I=U)LpPO`Ev+xfNN4*AlK4%0+|{0YM^FT^*%zP@AY6P-nDD**Vwjp$l8fR^u! zJRly)SiikzM$G@XOwQ@0OMYbvR*!+4sR7S<_GWEtZe6M9@1GbSe|N9}<4tPy3}2_! zov86#JN0LT`RdZ*`{y6EqY%fU?8KJe*S%VB%H7p@RqBH8(5EE3)h99=s~SDv1_$2? zqQ26Y>$bo|T;}C@L@qc1b9L{_J>46WkD~@Fq86hjz=M+(B4Npf`Nznj-yC%niQJlx zO8_ue$*O&$Cn*}~fBr)!Z)4VS%`RsT5b5V|H4p%f?2-LmfsDBTb3i#qrr&9F5V7ZGWJl?*n~frD0s->K~iJmWR}N zJe5bY6~2=svupLLqNK#En)?ZviT(gwA}E4hLllTGa5 zZWjq44||O{H0Kv&+)>+S$p@MNMD%KGl^y(ARGBOKjqGD=MZVe23%0jqUQ@X6%p{eZ ztk;}JJJFX-Z%w`~@>dv0vcNXMYCi9fFlsmjgEZD-9_}}gN+GvB1Q*K|HSTvGJ3i8Rw)M}3 z9li*79MRrDt8ZJ>$ZH0mea$iB{PFs6qjB|d%{gyrzOPl_-DUTWdTy;J52{TlP8d&!Q_~UF9(OX` zhVyR`wwfdz!Iaz*xZQV+%inH%IuqG`Ud6#Nx8(Nqo}K=x{!8@xpSjPr4qxBxoc7wY zyKTzubJ}Oo1)i*2tn&G$c$%JC)((jsG&SCi`{_>i)Os$dH4$KD@UQ8U844LJ52C(6 z|EzLytMv7Q*LAL|>q7|zh4%_a3S~UzJ=zFK1;^dPOKm-j+{X%}-lP_J6!H&!bys(% z6&%QqE2QPK2$pvvyw(!Lz3QFnU9fjua~_@;t7-(vkk!hA4KxGfiegVknKbA;Z0|pN zM!zzBO{4M>y0G9D5^HqO$g|vS{+geq#8`UZ@(r%D)TCZs+I+;t5vAF^ANQ)?Gj^(g zQ;!A|rlzG5i|mVBi|oEuo0d-J@$XgJRC=vM$y+xa)IF+eM@#D1!k={ScOTA^&Qrmo zQH!OJ!hl@$Ta`H83ufL-diL|*U{8* z#DBrhWV+!i?(MyI!0CWfQ~Rs-+wFZBCRu3sTf}76WY*iP(I-Aff{z#o@&!++4rSv< z?s?4!s+ciHkY2e&k0Zy*ZAL2_eXb}`VQF}1)PJFOb zzz~F!XuhhnCofCuXHu$D!k>lzwuY9Fi|dy!(m0|K5%h?oggT5G$?Ui>V;TN(A$1B$ zBX%lwzB3vVY;W7!K_1Mu=X%#`|=i@IWI7YWY(kviZ>W#zA)#C@bi-E^Jgmy3T zv&ysTrt=5y&zR28XX1u#zB0bKH`~i7=yiQF_Py&wm!-_j>#%^);s_V4OBC(#q!yG6 zP4+B#``}3~uW*Spt7`Ghf^&1sV$9rZ1To@u;+0v=ljbLFF7>SJ6EUOMb6OjejnIuQ zATM%{2u(C0$~wyXmzCwvvzjjwEm4EiZ)N?{)|YcCtd*^kqD!JDYD+Zzn}5GjqPaAg z-jUovmybCV@wxA{1nCp$QhkK1ZcJQ^XRKu+JD#|+3!Y}e>l(rajpDxJQgI_$G`I`$ zzTrU=eTzcKN%H}-XU5Mg8zFvPuX>4mqQfc2T}X(2sVVc+^U>Am`M8h#k1}Ins_D?? zW9*Py9d!#ac`5~vZ3d`RE2ntp{n!3wt*D=`a(U0(cHW*u>5w{&IvN<-W!e@04trF8 zxAUC6K0fs7@5xmrA=)pEat$UbF6b6qsdAEY8qPvxt7M)5F%W1}HT?Y5i5-kDcSBkfI8A=N<_dXMj=)KjKD5Ft5{a&;uv?5cB zviG%5zbbDXykd4^_U6X)wz_Q}t_pHv9X$;-h@Yy9Pa@0A149O-$CS71i#;q}Z2t73 zK%dd;QZ((ERvJ;Q6N(RrI$qlvUHe!h;H!*>^h8Yf*P*x5$6Sa|uhGY(@3DM!3+051 zrAmXUY0Br`=?w)>sK>EdUt|njdsI-=P(kVR>-L-aG-8 z_YQhjEv;F!JRkHB@xb@`^-@oYq zy3qu;q`rM$?c|$&eZJ10~S zzMj(K(o}h)GPAVeXh6kGX!YYTzojYlY_pExh3b$$R5tp0vytfG>iJOC(#xgAQI+8c zj_z7VTV+2_cc!GurRv0j)wFd#b~vur(tCaA-R#i0lQq1Y`K}?mCGnW^o$JYqNeb94 zNf}9Pv2w9rv-evdksmENYg4Ov*iK5PPPXd$?e(@&RTXH&a_`r-9bM^Nx6(?43~sm+`Zpb9x*8e?DAvf1S6IqLz}f zAtstWzdCDjEn4_rsm8S-a@|>eTpo!-1*|D7UnJ$N^L?$d^i^GtuDL$`@b|oq`5?n&4r0HkRs7w-4n| z-9w!Tzk^;800GS7)gaQmImjnuCoMHx{g3;i=bWy_frWpzb{RQC$puztMiikf1 z!m>D2kQoGSNQS{+ATuO{N+BV9jr>St0}uj+fJ5QJ$IK9JhC&#j;7HKl11xmNq4=TP zaJGND6YkJpe=e7eftxd%|(NS!Tu);2KygbX3*c264neFOkzXf5ZGo`KY)1r|AsOc|Dc1o zZq)zA`~M0D5klBhs2eqib(%vKo}Hi8rYklI%b}9EEDnLiI`yNFhx}PwR**l74MG?} z;2=FbiA-m1TK4`$!Q)X5%pfj_Nv1mB&|skmgifcR%;2U*FcU1!2#Z0&;WoJaSgaY= z2xEf7?Z;qEk(eJ`9E*IKL1l7(a4G-g+WeHe*$@o2&@+z8p`W2rY&k3j=&!6%^q)gyir`390UNO7E~>RB=X1PyRqDR|dedGy-I3dS}z{FW`l zMNSyxg1Htho2ag(A|ib>R^?v5oOA7N3kw0I=B!x$`1tVaa?aY~S4I1TCROgoUw#mK zwRK}G^nw3}s#n;`x{ZyFXoSYG@prgqTH$sxbj+ z;W8hUz)e*?U_A_lIt;E6dIj(W^@s@rHTD@bu>CRHQeQA>C-}mz@YS#rjctX)WdXC0 zcuWppX2}=MO;vXVvIGFHHj?)Q;G_e1X7n-P(cap^a)mB5Az^)lz1AwJU zM(uk|Vg7Kx%VV9K?M2f~tE_`SxUbF40020JQ-k1J%S@Yu0RWd3q4n5YX{C0rc8%cv z+Fe7nV&AM+t6QJ?VrEU!aFkr>VB_Q%RvUeNbu%KA0Ve$h!xNl2aB3rRFn z>KjowvsSYzLPWs4S$GdoWgwQ%`zk>-URWV5YF(w)T0rKS8mJ{!)){P@XkZO@xrzt5 zSt~E0SwA6SPFTK7Jkkv4Mt+a3vVz}=D0N1^7k`GW$TQk^#qz$`J0CVYJwZMz;~nei zKJ<0Ndo%9}{iFsGOt4L`n$LTM^cv2>AdU5yC&t<$Nu;(X;3DzD#(j^E74cWbt&%#Q za0Fx`ENVmy1vnTG@qoEC!H(e2XPpPyucp6yK*UId|B7>+1~@6t_Nn^I-M=^N_11;Q z5UjOTKgcBPfl7zQVjGOqWa6;88WlHwvU&0l-!0Q^*-dv*oz>3I(6`>Fn$$Aj<6kO- zxTOs`+#EH@ovfeKn^c-qS@IO+dYc72Tz4JUbZI?vRB=jrN`Fd_oT_W?_8{G5IPV^Q zw?V>jO!2*Pmq*Sqd3*HFr6bxe%iGvy7vI0#v(Hb#Z;krsGyCQ4;oAosQr@|Dx6N98 zPWjBg!V#B&r?OXhRAIn@@G9vcyo=1oU6PH0$B5;}HqXI%SThjT@9U7hhidWfLtV5z{YOsC-;GEbu8y7I_RglHPG=!Sv#rmE>6{h0rP8 z*{3&AzNhU_1C{HV(PKqXpi~52UXHyMXB*iDNil(BC^Zf@S5F>guLhhP3+Z0vW|U>r z&F2k1S}P^O1o;Jf-}>?h}`E>p3)w_*OHMPZIu#|X-^8C56=n&@8q z@$vI)PQe;+QNiS^3G42J$pp%1M0dpF^jo8v=grUC9P1gGr=v!(msGcXwnMhNfZXtd zd=&n;2=fTfpElM*E~vbYH$@JTzn1pTn_thWFqbn=h%Anrsx4OWYyR~{vC7&^YDZ!R zRWiyc?DL0rLd0p}wfZn|ji{I?_h{32W-MV}7d*v)(=~(*9L0UZCF4diC~!x_Bb}oL zS|$aMGpGThm-;VF8zH_PZ+i(`g3Vdm{RoIwi6Q;$tI_ZC%Q55Jaj}U|g;Z$sNoMf9 zj=GhoT={&6j5ada%r4f!_}0J7rM2?puOD36!#Nl)8eFGbM*%~-47+0cuqU(*I4oIf z*@xWxHL=PdSnZ8ow)RxT6^;BGRdy0~!x_j-`SkN3nl2hy4ZnOd@kRiqK*c_(obrV- z?R&nhh#XbA^@e`!IrPA7p%(wL8%4W3bVSQBIiK;zH9u+zl~Ty=zOUQkS`o>GnTOlw z-hs$i-bPksVY> zk-OBVITSRd6vJqJoi=pqX?|ftg-@q%x9{xqh)$-bWO6~ubc!ThqJQA2#OSf7^Q&Ji z2B9hKnuC>>%dr&?UZY-Ak#k!*+K-sxAL3W=-|&VD-NVm_AJ^$!3re9?U-f_O9rUbP z+car;HR#6YX5Z`EOWv^AC|ffvi7S|0Pu`%NEOwv;%s26O^KS~NN|t}Dc;BnsjmEnq zd^kL3CE4`zt1a##M@Pa?!tIwkjpM3JT=3-Vn#kzd0SV;5`Rk!YV?sSYpI4?RL(gE+ zm(ndWT+=r^y**z#zBTFk@MR?AyVc;&Qg`%G9>GVK@h#MW*~p$G%2MZb?rrYHFv#yi zUW50`LuW`Gqi3WTi!Y_wW8D_p*Jh4X9qBl+^n$%qIykk*{e^q_Bjjn?7xov_R#J~+ zQ{|n?^pc7b{uK)$)z3nG*JhP6jXH)`s)K)%-~P~>i9iomFNZMJ-mI;T$`6OJG&Vch zD*HJa3&mBARi{_X=FR)D!!f<4o?AnGi$j;r)NrzvyN0aR1fwo@ZY8cJNMUy+q$RXP zOGM9Q8k-;x^jrm&65J!3O!Kjqu1UA9m4oPCr zAjBOXNDz(5LjwTHG>Azg`IFfoZ!(2SM}rqDUxPtZA2itAz#eAL#FG7})*&piYls7$ z6yi@p_<&7KK&T)jkAOyI6G1_=v-Ch@5E}dkFOs+3F+;(iKU~=UXz-t+2=-1OEQ3V` z8A0GWBp3_^GD1MeK15w_JzpY88>9=W8Df{r`8R(f;-hWV?|6 zqxT<)1M$I3GSr0}$T-I$@y^aybte=PiDi+AYz7O@V4VF?NGCrAn-S>8V1jh@AaIbT zJ&{DE?^q7~0kOA7+Ry{pL^_FVgF}OPBoHdq2Wbp5#~AYlILs0bhg;yx4UCM<4Y3%w z0TyRyY-#=ji(`<^(a3c653J9Bu$cde-DwCKlNT9BW>L?ReJoiF8t9L#k<@?CVri+5 z(>FGv;F0+i-FzIXDyH_N{#SL z^YvxW03Nin1C!rAXaP7WMBc(j6m!G#0-up`AMk?0U7xv`NbLe1qw#SdWH%b zzKO}1c_0x@pc3WdxE(xJ7xz zP+tN4r(cm+pl_&Wpbs}0sL=-KM=R%|)WnkfqLBRj96LgRY@?5^1L_1DeUQ75+zAN; zuqZGT?6`nBVIgYAb%S||8!(VPJY5_^IAl}%*|``Lc$i=Rx6f4*(G6O!%$6?Eu2`g+ zI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9f$?sa@Dd=8y~NYkmHj!ll$e0Z!)`qz zppb>9i(?4Kbw*u=OaB@E{bTs?kKyk*h8LR{zE?5)wPW~K$MF9j!~1Ux?|v~%pT=N) zj^Y14hW~Ovm6qHRVcFZaDFDq-Epd$~Nl7e8wMs5Z1yT$~21drZhK9OEW+6sKR)(fl z#wOYZ237_JcV?cKMbVI(pOTqYiLAlU+{(b%%Gdy+;Y(=NU!VpJxD6$lxv9k^iMa*H ddO((#Ss9x_^t4s}2?6S1@O1TaS?83{1OTx;W4r(W literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/BtnBackV_9x9.png b/applications/system/hid_app/assets/BtnBackV_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..6aff407a89c47f70758ce705dee33dd2304d8e8b GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^oFL2yBp6P-vfc}%6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9f$?sa@Dd=8v%n*=n1O+B4+t|(-BaWV z6l5>)^mS!_&MhS_qiX%zdOlD{AUV;m3`jQsu>%m71F`&T6N70$TGrFWF@)oKvc-)9 z8`d{2oYAN#-g8G%;*+F<61y}*!e(|Rm)@oFK!vI$t`Q|Ei6yC4$wjF^iowXh$XM6V zP}j&T#K_pn$lS`rT-(6F%D`Z`>1G8K4Y~O#nQ4`{HC)T!o&(gN0k@$fGdH!kBr&%D bU5|y8G1QW(%@x;ydKf%i{an^LB{Ts596MX< literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/BtnFrameLeft_3x18.png b/applications/system/hid_app/assets/BtnFrameLeft_3x18.png new file mode 100644 index 0000000000000000000000000000000000000000..f39e89f8b7771311e6d6be8a394f42d828b478ac GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^%s?!}2qYM`-Hg-$QVPi)LB0$O?feW3JwW^ysHo9_ zfuXjNf#GF01B1;|1_sG9#Wtf(BtDhE$+E0VynwFYdq21j?(H zxJHzuB$lLFB^RXvDF!10BV%1dLtR6Y5FI}`TOwA!0BJ?jM12t&CZ79jiO)V}-%q>9ZfmmW`Wo!h|^Xlxx$3Q)jAU(nP rX(i=}MX3yqDfvmM3ZA)%>8U}fi7AzZCsS>JiWody{an^LB{Ts5HvC}S literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/BtnFrameRight_2x18.png b/applications/system/hid_app/assets/BtnFrameRight_2x18.png new file mode 100644 index 0000000000000000000000000000000000000000..d6edbb713390766575db4a08ce2f54a41c4ee4ee GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^Oh7Ee2qYLHrHzDultQvckS_y6J3j+M4-o$aDr$6K zV5qHRV0hWhz+m%~fkE<2u}y0^P%TrEx4R1i<>&pI|n@?0iJvXge5GW+#>Eal|aXs1Mz=H!98ib7)Qi1l&U&7JoJ!>0Ci)x8$ zL`h0wNvc(HQ7VvPFfuSQ)-^QLH8cq^GPE)>vNE>NHZZUX8KL3C>R| pDNig)WpGT%PfAtr%uP&B4N6T+sVqF1Y6Dcn;OXk;vd$@?2>`iXULpVh literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/BtnLeft_9x9.png b/applications/system/hid_app/assets/BtnLeft_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..1082423acab6aa2de19fafd57cc2b804a01ef352 GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^oFL2yBp6P-vfc}%6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9f$?sa@Dd=8v%n*=n1O+B4+t|(-BaWV z6l5>)^mS!_&MhS_tv@B-#SJJVkeujO2BaN;xB-aeftahSJDwFt%X+#vhHzX@wzzR% z!}`XBvl1G8K4Y~O#nQ4`{HC)T!o&(gN0k@$fGdH!kBr&%D Vx1PpYEoYz}22WQ%mvv4FO#p5XTgd_E)I!3HFqj;YoHDIHH2#}J9|(o>FH3<^BV2haYO z-y5_sM4;GPjq%Ck6>60csmUj6EiNa>ORduPH4*)h!w|e3sE@(Z)z4*}Q$iC10Gods AV*mgE literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF10_5x8.png b/applications/system/hid_app/assets/ButtonF10_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d1a7a04f06dcc4b5446aad5a40a9863038bf56b5 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA~jDJ#}JO|$tewu|Ns9tHZU+a6e)0^L#NYq9;5F(PTxtKzKT}z3_A)0e;QviHwNlp N@O1TaS?83{1OPU#E%g8Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF11_5x8.png b/applications/system/hid_app/assets/ButtonF11_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7e177358e81695342f2a283a220c7cacc7bda939 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOpBPSmNg(OQ{BTAg}b8}PkN*J7rQWHy3QxwWGOEMJPJ$(bh8~Mb6 ziqt(_978y+C#N(t{{R2q*ucQxP^7?t4xLU{IYmyznHN-MN(3-k$uq=X7x{LAUCkJ% Og~8L+&t;ucLK6T7Y%hHP literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF12_5x8.png b/applications/system/hid_app/assets/ButtonF12_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..50d2a7dc63b9d366ccfbacbc05e6bb0d9e335b5b GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk)EfEV+hCf+#W|R1_Pc$J^%l2ww|&zRcE|OcGEe{j literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF1_5x8.png b/applications/system/hid_app/assets/ButtonF1_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..7394d27105fd0a27067803bfd633a26bedd0f1d5 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|$tewu|Ns9tHZU+a6e)0^L+9jy0|#0HPM+X+s?4mGa`wiPi$55Iw(~PB TTpxE5sExtX)z4*}Q$iB}`k6I% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF2_5x8.png b/applications/system/hid_app/assets/ButtonF2_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..9d922a3858147116d65b6f03e2b36ea846b2f60c GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk*=qUV+hCf)NV&U1_ho&w~qX;m*hWYIaS!#v1}4USoB8kx*gxNJa@^Tf4zarr_o#d UH)s04hd_-Cp00i_>zopr0BX-NbN~PV literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF3_5x8.png b/applications/system/hid_app/assets/ButtonF3_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..95c2dd4f4198e182a1a62927c4d3627400a7b883 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO zk&dT}V+hCf)GkXd1_g%0LLdIeuWPOlF*aSY$&;z#+9C5#-hV?Uh3H=_oX_AhhhO~n UO!>#_f%+IcUHx3vIVCg!073*Z7ytkO literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF4_5x8.png b/applications/system/hid_app/assets/ButtonF4_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..602466f4b667b6df4d5335517bd9d43e5f0b6e69 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|vE3Va85}s64*o6Qr{DAJBNr3n8pa7vN_+nsOQj%x4ZT`lf}PS TtvtgA)W+cH>gTe~DWM4fb!sy& literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF5_5x8.png b/applications/system/hid_app/assets/ButtonF5_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..d73b5405275f6c53f7a2f157df063db1ca351caa GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOFVdQ&MBb@0Fom!%>V!Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF6_5x8.png b/applications/system/hid_app/assets/ButtonF6_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..c50748257ab8e06f90007e93b913d5be4999d096 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tOA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zB5h9>#}JO|shy5|3<^BWD?a{@Kh_*L{p89i1p$qBwtDvdG5Wpwnq5@=JeG)jb@A^; T#rkJ}+88`t{an^LB{Ts5$FwwN literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF7_5x8.png b/applications/system/hid_app/assets/ButtonF7_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..396c98f5104f94b6310593ce6c7e6ce3d2369ef3 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO+nf~Hxmr&P}udD~(3jVRo RuLQY`!PC{xWt~$(69B*FH3|R# literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonF8_5x8.png b/applications/system/hid_app/assets/ButtonF8_5x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6304d7fb888b2cf38c54e7124aaa604d1610629c GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!2~4tO5(ej@)Wnk16ovB4k_-iRPv3y>Mm}+% zA}voB#}JO|wVjT93<^BWEB^mCmh0Jd#>H=GOE1@xHLh7tre2KydVRe*qgvi_@vq`% Sf29C*F?hQAxvXEZzkxv|` zNY~TFF@)oKa!Nzv|NsAu4GatpMG73~&^g&~GSNx=@BjbyU3DUS%F5hxSQ8l-I!}xL U>{@7g2&j?4)78&qol`;+0Ic0IyZ`_I literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonLeft_4x7.png b/applications/system/hid_app/assets/ButtonLeft_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4655d43247083aa705620e9836ac415b42ca46 GIT binary patch literal 1415 zcmbVM+iKK67*5rq)>aU2M7$VM1Vxif;vTv~W2u`S7ED{V3s&&L*<`XiG|9wd+THd> z5CnY!sdyuJtrvQyAo>KpiLcV|{Tkc)riAbluXfwSZCApL`ztB&p zx6LGKvks4K_4~)qD&oGa-YdJlW)hAKMNJd7<=t?6c^RI1>c$ifyjaM>^|&8!ey zB4!nh9u>5uen6Ve@<H5rru6h<2Ef#GQdQ*CmZOlQi~N!?9H`Rp;C% zU}CB21#?;r`&0|6C0}b-=jODa5|nEJ#ntxQ&{~jpgtwDta4hftr~G=#p@V36e4Zjh zq%J~{y26Jjn=1Nw-l*3%QW5YFE*v4z3gt0$&(*xf2en34c?JpH8+FYldo+Alvg8af-pG4(=!fyUi-Wsg z`g#n9VUcf(DFr{poMSNzw-lz>w+HV+n1ELr&SLA#LHUb0p(xWQ(1*vJ-i+1!`swxZ Z!O7;c$;lT_->m1Ovaz)0yuI`A$q$F8u*d)a literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonRight_4x7.png b/applications/system/hid_app/assets/ButtonRight_4x7.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1c74c1c0038ea55172f19ac875003fc80c2d06 GIT binary patch literal 1839 zcmcIlO>f*p7#Yw)M6zw!O+@VZ{?d|D~WYi~8rHRY?X-&T}Yen`g$^+EJ;z+|RV zE@PoDvZ9%#+_}3bC_5Cj8jDGq541mi{7F+&KF}W65sr$Xn5H|YrMQ2(J7%Yc%;(zO z57ax000=TsQ+1Ke@+w#iw3au3cGGQWY740k2ijH>P(6tD)S)be>gX6Tj7`<`b>di- zgWp$8Y+?i31~CzF0&E4uRlA=C(Mp~K`{74jEchB|)4DDK!ZVhSwdFyw0YIZ1cDh0S{OvfO-U_~ zvmRF*m9sWDXNH)GOyqS1Skhxbr6}s*7t&@~kFM(NW5}qh?Lu@lJ}HE;FDiLdGO>LO z5pS*%E2grR)l^;|?O5b_?u0me&c1U}%jrk8*%=Wk%i)8yp2P|kuxmKg<=(u_`oQRI_0 zS`-DNysBx=#3&qSkgA@hJP>~D+ZM(s5jI6Owp`?yE=3e`YGUqkVOp#Cp=3wR3O4hX zX6BLsN3UBzV(vI5;|SZHgOb=HD0VFjpTyfFW}GnQuh>2*Q`k>*cAmA#iUT7EXSpo# zkPm5~#I-o^cpgfe#P$=4-Pi*SpT!-@nJgp8L347xe>5EKl`=_ZFc8XGy+_j=_R_7! z@vZZMowS1GJ?Zw)eetks%~G{BTR>T}9|jt0j3Btyb*C3-`C?fwY3EY`q*oYZ39DpM z&uJ;PCZPLs4QO1Jd_|A1PF)azZJ)RZ`^-VMWr6e#XUOA%3eLG_Ch@BDOHzMk*MF0G zCo7xMd?Mg*HMIXw%nNz?%60fZiZPlqb?GqUpXO`F&Yi!okZl(n>P@r1P2i)yk3DgRwbHeNn6e|;J^SK4TM LH~i+q&mR8;k>NTA literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/ButtonUp_7x4.png b/applications/system/hid_app/assets/ButtonUp_7x4.png new file mode 100644 index 0000000000000000000000000000000000000000..1be79328b40a93297a5609756328406565c437c0 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)I!3HFqj;YoHDIHH2#}J8d-yTOk1_O>mFaFD) zeWb+ZHz{mGZZ1QpXe09^4tcYT#4oe=UbmGC^A-KE*|F&zP#=S*tDnm{r-UX30HgpM AM*si- literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Button_18x18.png b/applications/system/hid_app/assets/Button_18x18.png new file mode 100644 index 0000000000000000000000000000000000000000..30a5b4fab236d8b57242559ef94fb1c5dbb5d10a GIT binary patch literal 3609 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Circles_47x47.png b/applications/system/hid_app/assets/Circles_47x47.png new file mode 100644 index 0000000000000000000000000000000000000000..6a16ebf7bbe999fa143c683bef713a6e2f466cbb GIT binary patch literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Hand_8x10.png b/applications/system/hid_app/assets/Hand_8x10.png new file mode 100644 index 0000000000000000000000000000000000000000..8defeed4d260b52446ae712327761b5c07a95e08 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^AT|#N8<337)>#0g7>k44ofy`glX(f`gn7C+hG+!$ zP7LH?P~dRB`&tTBi21sKj)Sb7h~uWU0*U(Vc$SlOj$jaEv z%EV0Dz`)AD;M0t&?@=`5=BH$)Rbpx|Hi2l6n_rp?)Sv;kp(HamwYVfPw*WFVdQ&MBb@0P@UvPyhe` literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Help_top_64x17.png b/applications/system/hid_app/assets/Help_top_64x17.png new file mode 100644 index 0000000000000000000000000000000000000000..ecc0e66474383bdef50f6f342c00e67885afe24e GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^4nQo(2qYL>4@_4CQVPi)LB0$ORcZ_j4J`}|zkosw zFBlj~4Hy_+B``2p&0t^21sKj)Sb7gDm8Y2E}B5=c(;D+AIFK->VtAdseGU!)48J3U<-LpZJ{XZ+x2 zz@SY;J9ze*I^^|NsAg zc9l)Lh{TY+Q$*H8NYpON8TaIAAgv(`1Bqf|>=BT7;dOH!?p zi&B9UgOP!ev96(^u8~=Yk&%_LnU#r|wt<0_fx)L4SKp&($jwj5OsmAyU~B@>AUD4> s8K^-6ZbM0CZfbE!Vr~J79%Cy*3oDqOOFLP-fqED`UHx3vIVCg!0H%+cNdN!< literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Hold_15x5.png b/applications/system/hid_app/assets/Hold_15x5.png new file mode 100644 index 0000000000000000000000000000000000000000..102d0bd7a964aea46997aebf8f0748b4159ac92b GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngg2qYLbnRPb;DTQQ@AYTTCDm4a%h86~fUqGRT z7Yq!g1`G_Z5*Qe)W-u^_7tGleXakgBO7eDhVPL%5CAVt>_A*8EWY>vkQVoJaSY+Op4_oL zvE$xBgBQ;SOya|=-P d7+V>eTbUR_^e8JI*a_6b;OXk;vd$@?2>^sUS`7dI literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/KB_key_Alt_17x10.png b/applications/system/hid_app/assets/KB_key_Alt_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..26c4f73152d43767d528a0ee9275f10bd586fb89 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^fNn{1`*#dk*T!Hle|NocXoPQU{;wb|fr6@@E{-7*my>_| z-{1IfZz=QPue}a?b~h%P3z$2Nn{1`*#dk*T!Hle|NocXoPQU{;wb|fr4tDE{-7*my>_| z-{1J~&wjSHw+?%bFKSH8mte_feY9DSH{s|1|I!Q-gM^QtySldxsDr`N)z4*}Q$iB} DVGlC} literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/KB_key_Ctl_17x10.png b/applications/system/hid_app/assets/KB_key_Ctl_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..01f3157c98e45f418180364635a40bf014e8efba GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^fNn{1`*#dk*T!Hle|NocXoPQU{;wb|fr2WYE{-7*my>_| z-{1JKB9-~D-fgzFzgs!>NO$y~&tN$H|NngkhN=_7wb$6Q1%T=qJYD@<);T3K0RYu< BFsc9m literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/KB_key_Del_17x10.png b/applications/system/hid_app/assets/KB_key_Del_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..62bad18b5456eac36a6688692705799776f5a79f GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^fNn{1`*#dk*T!Hle|NocXoPQU{;wb|fr1*IE{-7*my>_| z-`}|KZ#whgcl#Z5ZXek2T36sRli`H>=Poi#`~QDGBSYUW;r+ep?vg+~44$rjF6*2U FngHj)G%)}G literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/KB_key_Esc_17x10.png b/applications/system/hid_app/assets/KB_key_Esc_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..7a9fdda225b3c1bcaee0a54a5f6714d01f0f7827 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^fNn{1`*#dk*T!Hle|NocXoPQU{;wb|fr9FuE{-7*my>_| z-`}`Uvg3R6h7@M!+vWNn{1`*#dk*T!Hle|NocXoPQU{;wb|fr4tDE{-7*my>_| z-`}|KXEpQTYK1!W8wWmolM?Y~{=_VO;M4#A=0IbGMHhM9^2!0~VDNPHb6Mw<&;$TC CXfa0s literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Left_mouse_icon_9x9.png b/applications/system/hid_app/assets/Left_mouse_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..c533d85729f9b778cba33227141c90dea298f5e3 GIT binary patch literal 3622 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Like_def_11x9.png b/applications/system/hid_app/assets/Like_def_11x9.png new file mode 100644 index 0000000000000000000000000000000000000000..555bea3d4b0239ae1be630d0306316d3e9494c4c GIT binary patch literal 3616 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 bOpL5dj3IiKSzNmW)WhKE>gTe~DWM4fZ^Bs2 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/MicrophoneCrossed_16x16.png b/applications/system/hid_app/assets/MicrophoneCrossed_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0e91d2f86d5e0db4dc025cfc3f00311c432a9448 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~3dtTpz6=aiY77hwEes65fI`k0jPyGmLm`c;l(zjeE&6QcoM~owo4& zq=z4p+t}am*gs)xZs))9T)9UGXozZwYeY#(Vo9o1a#1RfVlXl=GS)RT)HN~-F*33; zGPE+b&^9ozGBD^iNJiC=o1c=IR*74~uiV?~fHr8rZ79jiO)V}-%q>9IV`ODu01G@N literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/MicrophonePressedBtn_16x16.png b/applications/system/hid_app/assets/MicrophonePressedBtn_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..911fe1b22fe06fa837d51fbda320bd13a25fc035 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~3dtTpz6=aiY77hwEes65fIzX;D#n%wH3I|91{WzQ$0v+w6E?|zVko!Z z*S&Gcej8AuYKdz^NlIc#s#S7PDv)9@GB7gMH8j*UG7B*>vNE)^GB(gQFt9Q(FnYz^ zfTAHcKP5A*61N8D?VCYv*MQqll9`)YT#}eufTG9P%D~df#0+9d;`$Y>Ks^keu6{1- HoD!MT1I*f zP{`cV#W95Adh!p&^B)@KK6Eg4e(c$HtedBYTOxv4B0|r=z%L;oC?Ui4#D$9|E>ui- z$YAVolfRN7<161?+%p{13v zfwqBxm4SiLEA9pq4Y~O#nQ4`{H8^kI4Dy8r+=i0O+|=Td#M}ZDJ;qjs25?JE6%*C~ P^)Pt4`njxgN@xNAngU$S literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Ok_btn_9x9.png b/applications/system/hid_app/assets/Ok_btn_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9a1539da2049f12f7b25f96b11a9c40cd8227302 GIT binary patch literal 3605 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GQ#q8r!JE7=ytqjlqnNNGaK}Wlbolp-q`& zs|bxHiiEP0&{#s&zVZIv-rx7f*Y_O9^W67+-RF5;*L_{ra~$^-2RmyaK{-JH0EBE1 z7AVdru>JD$aK0bym%#uaXpT2Gcd#)x2azcxAABGV0BC)Aj-lw(6)B^^6`Y8RS?}DV z%)ko(See1!Eb3M$dL6)A6csaRjExg?k&xVzi*Rm;?iNJk#f=mkVEUR~jXN3dd|Lmz z;y}sMh%ol-?E1&`>dD;6jdps6NYoxN)s%@sf4~40YY6LAOtMEbwA4g#OCpANL823^ zSH66W05Hcxr$tg98gFntAOYL}xm$C;Skv&Ym?{TVR{)d(41vWacX1`7fM!jnW(lBK z26*WB#9I(Z1Ast!xEUC@Cj`v=urcBTdP`FWq=DYTy`}s>0vC{VzHdNRvxNFy}ir1|g=xDsrFP&l1P<-Sv zXLqYVYz{b^ZIV@1Ulg->7DEgvM*Min&Y8{8QW! z$_pA434?^wCTq$4%^>Zo8&|8XwbCv;KEd;WJJ{s;T}8R8Zwi7ssk$QWQ5l5+opKfX z;8D*COFEB#4W^*FIrRU%PDSc?B(}+9ZV?N9(yH>0uSnM?xg!>+>;e z{{7tXQQ|ZFXD*7q3XD!pwnih-=66+Qlqtl9;N-D|PHoI&B5d8>^V#i{mE>V0gQgu3+(DG%B z|8W!pl$lbQERt-0eZA%NSfvE4F>VAYP`DpeoF;Zm4`)2id;6xgSysWl6K$pWANcRZ z!ETRXKIU9G=@9lEB?<{ivj7!8FE9WN;qoo2Lr0#c@DmcF=JzU<73PmM3 zbe!-gs`c26Uc(AKz7%U!a0yZ5gsprdo1i51MjJPeHtV6d@Jy=*+_3dJ^>}p#8N#kPK_4t?hltq>u=?m+t z?em(Y%u3Bp_pyV?c_w-4c}p+?Y$aHr>TuPGs@SUj;Er!b@3GVLDS@T8OTts1JFS-p zKZ=&5zp;DRor*`Gy8MTeWdpVJv2(4-*slRM@XXG+i^F&Ku>7i08vKenZHoS4s(!!h zJE}*MHu7PR_IfdNzu*P}3^87K?f&A1;>NMsgKcR6**;aB74NC7tR(NB?{dHT-9QhXa*KoG!kGU1}$l2D>ypo)fSBuG$ zkTW4?+|I1m?6ZH8tD4^fB{cUpoEoZOo%4hl!EtNtQ#?j*jJR)x-Mn0TrxrX2uT_rh ziOh=Jxsktqbd9x{^s{c5z92Pk$LGoQl53o+=7QXXCp-Z>io998w|DCCCGfr20oiRN zX|`KH$W4)wN~)J$kYB~>4EU;NcS^qH&yzeUzXokpMegg_lX$6ve^4}%bY~Sg)%uJ- zZpb$p4x^GS5d{XJP=STbfpHV`58UBH& zKFg&BgS6bV+#-|^KBGeIBee2B zrM-`uTB^_(eS+{-KK1h3l`-Yjpv8X4z*uBwQ3a~pL0Ae2xvNGyC3A|#MARToe$W~8 z+4{DsyenENye9df1M}gNUM9_Leh6G=`9exL-cdSKQ_CGyEdZ3W5uoR!Lb^D)9!bd=7h@R=M%=|JqX9XP;Z6# zFD15Bw7qTP(ZlG?o@#x@=wG;XxM(>n@4P$9WwY#lW$h=`zMi_zq30HbV-zHheqpE0 zR6kXtxdzl&Ml2D#zDIvflJkb*e zIAI?GMjp?JBK76WW`{l{pFAY|%5?nYUxRnT&y6~Kz19AD;C0(z*7?dM{%HhVtqWEc z%+M$z6u@uQu)kg_%2PO_U|n1JE0V1>iVbekOLEOG$U6X^Umc519WC)L$t%`#Di0$ zY1|5H*440_`onhmXeayq`8EIg?x2r9KWe()q}QayqCMEC?c4meb4}#i`HHPaxO&3SPtSVKj@ND?Y+-@R`CDnf-d`T>vTn8RR<=@3 zNXk=Gloyh#S@3R89WHrXBHr;f(&ZO@I_Uo7;O5Bs@ecGx@7%7{_>Q`Adg&sCeZTYp ztVy{^vAUfOpTDzF*4`h%X0odWn`#uZ4s4igIV^UrVVg?c*{>K)hHq^^RxU2CM;WN> z;oK@^sg`J}BguyvilN{DQ*V+N4rD{X_~KAFj5qyk3(gP#cvSIDXe!zk3B!^InwV{j zCXGPmumQl(m`28618`K37tR+?goD{H>cAkpHyrG$XA89@o8$cOh%gGyG0e^h8y0{y z@CF+jfedLdjsO8i#eispKw=P#1_%GG3**eU%@8o?ZwNI24*pM2Xj=!6If;S;9nsX% zz(S!=&=CVoZ;TfP>*b{m(uQhlL7=)2EnN*L6sBVU)71t2^ME<-DBeCWl!etl&NwSL z*pEsj!yu5*&``}#9ZeF&7oufgU;u$?L$tLuI0%g(I+2Q@X%K^ye=Atvg0K`knTjV7 zLEDNLFH$fS4(5dVpED51|H=}B{>c+3V-OmK4AIhrZlCEl(AM_T0=zuK- zizjYd4*pHCwT0ObgQyrH7H4At2XjO;@px~TsgAA%R9|05PuEIcOUu&SOwUTs^00xK zshI`T;)sF%Z>|Li8%)3vslU12|K;lbk-Oav1Tx371&)Fb!FgLzNCeQ|r-tGG9E;W; z_5R^{|2Y=zKXM_QU?AJI{a>~IZQ?Z0_VnM@{Vn_(lP!vI=DFY(X1wo}3 z6%<84X#!FOq&E=|(E;92gpu~b%sB7;nD_3w_nve1+TXXoz0W>totP7L<|2Y}f&c)B zSX$s5_r|@CpPTbH)s?gZ06|kK7JI@Hiv=;5bT8@!G5`dOWI9psPV>^}^@&xCb#&+* zYr3NpKgbbtGgLA`MO{%q+$vfzXIRRie!rk8K_zR)VcF)&~UC~C9|TNuZ~|h*+SbvH&nO~b9n!U@Rp|LsTqiIn4mHP z5a+M(RP^6g;sQ28P^e?zI=)u`S3sW-KTv0zQKxk%YFF$FChas==yk3-R>E;>{!mH4 zI4BO22N;`ig=VIzI04x_fP1?KX&N}83An3X{nQ79W^SYfa{+F56s5Sb69CWwax@O` zHULVxPu?&E2wH%omvs{Y7}5l^EM2@TfXB~)x-M~{a)4hL&~k{5I12Ct1MaO#N&&$2 zG(gg9*#-66u`=;Fbxx(y%28Fy2-7e(eoa3<7Z=E3wJuAUW0HErpNQ$kkcPlCS$LR^ z*oT!40LV^|;$*wB9nd9O*43pKS1Ec<^UG`AT`-9>y))Zg%rFLkDOO0&js~o>j1#f+Z;+4CbVD~!F`nC9H78XlgVnHjQb!nhIJT(0a;8qU?Z zY+v|21huuk_Tkk>`~ZN<4pV<@BEMRHP@|6b zQ2oBKdZ8_Mz3Uj|rUr~SM$j|#5Yzo=$u*2xWancAb$94{V+EZ$2k*#4hA5=L`GqK& zA@-ffpH;6`6DGi8(#n5;s5lbMMY=&yisP3_i`Y=Cx8RYusSJ7>E$INZPSCZ0Io`m7 zoGlcV(afI^QK!vbCK$8=@M~LOLRj({8$;1!-=?JUOl*km%9=1Y9Cq+${I_WC?e5%$i5{ z6E=@Tm}#AW9uFG>A|5ueAlMM>hAav|hm>{pj|k`sa9?+5Pz5IzSU**Hx&Qa3gCsaC zieRCkG$0Xw04g3Fjcw9bmWaW^RjY3OWclPFzE`5xtk>63X)yr%yQ_ z;*JLBSZl;g=1k*^_Kf_D;osVrg_|f_kO;WvPTV z!6d6Bl_Ys}D88^LuV|u3$a%%N9UotK*6B)_nX|UjbfLie5|fB2Q`Zx!dQcDg&3-Wxi={T7o>rcwHPf0OsPL*Ns#x28v0Y4e zw5`fJnrC2RVAIms(RsgfAWb&|4I6~dWz1y^W=uYJKNWCFqq3m#1=+HE=2V{RVr7kQ z#3_VpF2VWKnF_Pg%+ezR)uq+>`}3>p677n!1}Ke>f2(|3S@>M`@$3-qXjvt#@(Phc zlA%0*Q`WecSetm|<&|Hy(R?CN!=l9srxZf`pE4zpCy^8BU3V9auDn@Io`+Hh-QwLt z+S8Q>+K)C-Go3Q}%qcRID*y16=$kRt*V-W|hL8;T=JD3r87tPB-sIhw;I`@udxoZ2rYiz}SaG32e61tb96Rzhv^y{9tK5w^gq-ULrn8aRH+V$KG+U)`ILyvG# zxMRXh!rXq^+z7g?_&UxAIZFOkKD=NOn_XohWfFg_^xABFsiJr5ueVAS*XL5Z61u3O z5hp@E54__eej?s%3=vk1h>CEDG>T(H6XbeeDZ1>QF|7Y2?mI3SH<3Ys*&`llTIs4A z7D3LVM)Y6myfkWtc)51;6EX>w7pxBvyIBXNR(4GIkuFtkUnCwd5bTK%xyvW2>B z(CuFnYIFmY-)QG*%vN1jExc7@BVse2fy|OlzXYPe(a2g@`0a#SewZRf+r&!B7s@BE zOYJ4(i1M8`zBivk4=3@x^{Kd3vd>jhuo9E^8GlM`P@S)wLU!?b-5Jw{NG{Gg*16D8 z(KdQZ|L)Sg-35sTiK*L_xslc`nhJzZwI$~f1XyNYV-sV#htsJa+->=Y%#yiFj z9Q$f6+VbllzAlt^81+k z=>5vzIghT%^J4U+m*T9cUen#1a|SgAU8k2{u$Ie5XAii%a7llJJV*P&`hwa??6YsF zzFVDMR(0B^YB8wxS+LjoynL2^*Z68};BV5q1N~VD^my$`5Pkj4`r4%QcnDKZZ5p#QfD<9kK*{zZ#vvYr^y-Y?L8nV&J?JzD zanA=5Kx1&w0Dv+IU=Tfg$Se?vOriRs!AsSz!62$98tkHLt7Xf;lD(-GK}@n!kR9G5 z$j1ZW2{tkWp#qQ`0vee`1O?D8`1&IQ(BMCKk(~LS843pd;llDkgZ~souss37(wStC zJ_M%ep{1n-(nmnZoDNfCx0YnBA2GQEf>W8DP?f-YB(f;=KXE~Dp zqxT<){qcbeGSrdmPru0Y;Ow23(q1SA63ZkLS#&0zPQUP@kSDz9EV{opodJStLtr2^ zTcQWmch7S44~VTT($d$TMfCL`TjJ1Q4he)x^+f98FuE`;eI1yVnJx@wiZj7sk7ICf z3|1em4MV{7e_(NRkBc<2FY5=^^FLS){(oTi8iK~)M8=Vs)JtSfGbWt|`Xg&3^&hlg z5ilLB-f;wnPv@Vt{E7Aa2Q7bLP5vhq$`J$I+uQ%z>mMdg1MN-!ZeGsf@AfDAa(bT0 zY3`!iXF2z3K;VQ8-jp-$h5$Pu0Q7Lr69@cE tNU_5F5@tDqw>z-XxMyOWnyrgG{91sV9boy3dsxXH+S1exSB7!F_HPPcG0^}3 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/OutCircles_70x51.png b/applications/system/hid_app/assets/OutCircles_70x51.png new file mode 100644 index 0000000000000000000000000000000000000000..f34d2687a6b1c03b312899572ce3dc6c2fb7af13 GIT binary patch literal 2469 zcmcIm32+lt7*6Xzu>~1H$_+b8NG&vlD9qT+?j~JHvLU;rX|*a) zs8AWFWu$-{au?(nK~W9`idGl|vC5${fH*Bh1Um@jC`WypCIu9bVaCjC_U-0--~YY; z|Ni$fJ3DJ+m-s&M8jYrlEz>d%+(&?m>=X;WSFikiIk-I?$b3cCXcD@sSBz%S;$9j} z94tDViqk%Z;Uu4yFbEgq-OM=4h)6K- zWP+4~o+?jba2!5C}jZa*i-+|E7;{iy z6)EVDByY4*+0lp)L1}R)d4lNYr9wGHRTP;ZSXf0c!Juj+f>8*eb6_|q8AH;k7|CE* z8`KVrz_SYb53tdJVQPs<-F;w*7u`jV1GU>3n_pH~KPT8MCK!m)iXzYOdW>dy%7xGb z>qc}2%7D0Vy&Dl&(q$kyo~ML1U0}XN;tJGEsPo%=%S#;KXwN1?=}3-rQ5-_hm>bbk zlpdj7f&rl@N=K8Fo7ItIc$I+d<)8>!hxOjes;c4vBgPqd4%3qeM;ToR##1z6Bv=kH zP;MjPa=Qo&*TK-;xMs;K1?>_KvJ3cmj&Vyt9}D~=`dE*E2K*k?&43-m%@k!&x}r#b zU9&$sYMoeIPzM2K=vL_XB%x{z5qxcQT#TTEV-3JHYj9xK&JoG zT%{}<_zv~u~Z-y?vX z6NR6AF^}is2iD==ef1=Dc<^FSb-!$L82V51-8FBt0Wv^qs@9YvH66}++N+x$?F_>R z%t%?xdYzffz;UZN!%C3DjTns^F{1&)N2G;gdPOiZJZd9B9~~`>yl0<}P6mgEI&43- zz(f1NC-_0lg5X%|-nVX+MiZN2vt&5(ryYHD@0NW~r=P~$X{{v9UJ zdf=towjG;u{Pm3o>7m!thi*)$?)>%1^j6&0!?R;?-?M*ATQTqFhTa`*eRl1AN>_F+ zhE1?-=v8JWvz89&vZ!0x1#O1|302ABk?#AKl-+v&06cZgsxSN1^eF#+&sfv;qQzz< znHt+~PII6`-P}qhXEHM1wzgA!O9eIXo#%5e*vfy~kz^Y@bkYaY2i7E=?e^JAxub8D zY%;Tz&XXNac|V*~J}I6jrZokYjNY=aS4wRoeQa>ogEO5MQ!h1V`p$gZ^~CKh>xtd5 ziNPUPW;o#UDI+&rABkip5^*tj!_hPEZV+wzbH&&U^pa=Hl>QTh^w6I>500v@z0_Ej zI;*;6!K&EmmZhtpthvp(HtYCi)@IEX=GT8wku$J(>#)K7L$}4$8z1$&IpgwL z^Lv}U*LpiIp%u#uD^84ESJqHrxtUw}4oI0O_OR;l3sWd-MV>MdBcL;m5JV#(iu|X&A6Jzl|RhB?A;}_+@tDm{t;n}&l*S8-} z{k)|9o7MrN&Yga44D{82+r#TRUranZF@JsMrmlJ7$o_ra>QpxYS-Y@$_3~vU-yU!( zNrz&$TG6XzdBw%I$a52GoBp~p@k)6u(;8R&$%fv@*_6ZTA1RwP%d%;B-t@l#cgkqJ literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pause_icon_9x9.png b/applications/system/hid_app/assets/Pause_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..fe16dc03e2629da128e37a7b9e1ccb11b4c92b78 GIT binary patch literal 1743 zcmcIlO>f*p7+yevQW|kULRGm~?u81*p0U@TsvB)~H)JIoLb|A_xO6---nFv!xVDqs z1Al-6LFyqK`3)R6B5~ptUhmr5g4#3}EO|YiectDJ=6zq!e7m>%*~ax- z*E^lg#%O1-56^ete(&0w@O$^K&ILT)&37J_F!w=yztQ>rhnt2-{rblpTsniNqwy0`uFt8mXPW&PT z@N#IHuF+*RJO-7$M=`Z*Dg`kxz#!}AOO6|3ZiqX}G^6`Tt<8of!sU0-|Vn8bqs8BT% z;Vv?)F_erD-*7&`rjPLo8bTv*TG6Y4B~Rj$t3bohA7y1lGbToZKm}7;l5pR3B4OD! zK~zKzVpf+T*X9)2kz*0lb|X&M(ig-JBs;1zQOB?PmP-a4>umbAWfLqsfg;Bh$c%u^ zi;0K0$RP4vZ|q+%pUdVM;CJxm^>{*fkI%HjZPb3+4<} zd9K@+Gg*X^Lv~G7;mnU^ky7ZNB&8Ff=h>w0W?+X^>?9?W?xguX?u*CGXqG6;rt>MQ zdp%HDU1Y)4W;%IE_O9l0MLVAgAibpcD_AMxYEBE$n?R;t+g$pq%$M}|a_d(s{QqdZ zNh_PuY$9Oi)YW=1dcDQ0ilYCwUeD{@_Dm1Dxvpl}q+yFr)(N@SHj}fB-R_`oTYa-f z$gtlV8s?UZJ>p{5!DM@-IZImg6^VYi+S2C>TVopx`)xg3@AUApJ}X29%Pim|c=*xp zf50iCj0U~&weJ{zR9}BzTx>r5<^25o?Ck8>HybzJ|FZMz*Kd9J__xKg`p6j#cLzUi HJ$mvFBIhBe literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_down_7x9.png b/applications/system/hid_app/assets/Pin_arrow_down_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..9687397afa81c92fb727bd40542830392fabdccb GIT binary patch literal 3607 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 QX&eA7&1_6djNPvM5BL~PlmGw# literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_left_9x7.png b/applications/system/hid_app/assets/Pin_arrow_left_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4ded78fde8d1bf4f053ba0b353e7acf14f031a GIT binary patch literal 3603 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_arrow_up_7x9.png b/applications/system/hid_app/assets/Pin_arrow_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..a91a6fd5e99a72112e28865cd8a004c7d1933fff GIT binary patch literal 3603 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 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png b/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png new file mode 100644 index 0000000000000000000000000000000000000000..929992022b30a9218b07f155aecd33ad3e305465 GIT binary patch literal 959 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 O7(8A5T-G@yGywo*m|J21 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/S_DOWN_31x15.png b/applications/system/hid_app/assets/S_DOWN_31x15.png new file mode 100644 index 0000000000000000000000000000000000000000..eac667aa05f35a9f9979f1c9627a77d6813f3e54 GIT binary patch literal 1893 zcmcIlPl()99F7Xot<(yFmVy|vQY<#f%fHDVc3Ng#0B+@wys;lfr* z&u%TY$<`XNec{Y$erDhRK}2)R529Y2IfJS&Onl#Z-uD2B&xxsj7@DS*eJM!5B zFZDHvr5#zZZCggFtg0d)#B4Lp@j#5T=`lltW+V-hJY+FnFk+9b=T!luj^+?06WTZ{ zeFDei0ZwE^LdBfMK-b+L8buRymNz$`N~w2G;;g-yP;}et`nmMdbk6z&Wi7&^1j&x~N)hgdA0K6#FrxIS7rQE(F2HIX)Pe`C+hm5UA@qtvJhZ zOlYf76+n~}hQ8w=6ZxK@iK+>wprRuYBTW&hs;S7Rn3@?_LtQYw!N_{yL@|DpTb~i& zn7HXETA=w}#SlGgV$rm9Q^dMri*_YYk*^V5LCTozsWgO72lsZf7OH$;R6U{^*fK>E zS4>g2bs~BSkok&bnM60KZGftsx^>oLsmpwDR}*-jH~~u|4EYHo+@W$3cZxIvJIrDs z%%F9f<@a%xzEIxvLVg%J2a_V}Re=;uroK?V4)<{HU^+*%{VoO4eVRXlWh}`1IHk1? zIQ^gDl5ZzI!yEfcKUm}cN9m=lq>JMYg_Tnlipj{u5;JOw{O^A4^n09{8l<`CW>(rT zi#roTPVXI2-Oz~UL7-F{1!6_lE28aD2$`-Mwx$Qz(6o_?Ubw?G4yHMWEWJ^$7#qwpu1V*)xB1oYKi$4>>hY(WkG#Rn#lIh`|8RZg z;|srCI`PiC^Q{M+>tB5P-6?+hql+)!{7{VSjJ`O3tNHb3e{Mh2U;Xj&&&_LBzWVZs lm1llBdNS$U^e@tF?tx!srfzgTf2r6ATg}DBC$rDJ@;C6_S*-v7 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/S_LEFT_15x31.png b/applications/system/hid_app/assets/S_LEFT_15x31.png new file mode 100644 index 0000000000000000000000000000000000000000..13c9f51b4ee428f92a995efb2bb961c76f7dc247 GIT binary patch literal 1906 zcmcIlONbmr819IM7&n_k4$)j}ItL9}(^cI){bDD(voo{qKxSPwgINzks;jDJ+MVg@ zbocDcE(ks_5Qv}=1w|C`D7g3lbt6Pn#GHIU4iUUY52APw)Pq_*uU%!c?!|$getq@* zUw!}ARTmZ)9yoOH*g;8>4pryNOX7T39CshMMf|_-N$17s_Gtd$M3U|}k{$b`mtVP4 zlJ>tI)R)uc+9{Xtb`DeSv0Sem3A7|lP4^;9R#+-~tPz9-<>$*+6gi*;(Meia6;+0McO*{gYY|hISi(CoVU<-DrpHIc zq9#vy(&RVMHh%pNg_Mj7MxD6Tw}%pywOCtlCjyK5`XaX!Go13c&UtIpsl`!6Su=7- zzV}QJQr=BYWQv9ZSQ)3Rpop&N02)BE>kzt{;~M6DPz>ibKMx<$@wa<${2^3|q*|TNn^eqX7B_0uE+6z{I1FBGWP%>gy8Y%bavFC$jN_ z+)_>i$Jk95X^c=4+JJeM2Otq_n0g*Sp3XF%>b{S?5!-`tAU+-3+RmD(qJmMtnMsiW zu#GHXl;Hq}Il?upQOkxpAyn{e)h+QBk87L?cXdUk#PxaH#v(sK8#kC5g^euDf?dpF zK1f9C)`s85X?AXS)e7W(=v+*)tQQoLHJMRm_&C_Xy}jui&~}?lknYm_0W9Hu+Ql&| zHiXmv87}o^;uE~OyY#&^{(qD{w3Re**kEGiRF!NpYPQ4-nxg)@Ut9eiWu_?7oOLr9 z+Axb7V?$2v98n{$6Wuci&>ZLk(=bfnc+3KZVdNd%@Nr((2P#^@7S|}4)*Q5ra>>k> z^ddr)Qn8}xvo>^)4Q&gexoso+GZ_d{cURs=Zd+zYHi)%5yBzNt;%2<>uuxbQi(Td7 zPjpJ`NZo3=Sht_Kwp5(_Se|(F_rG4djxV4W9=iP5ryo?GID4)7?X6ww-I>XmcQzVt zyjuC@aqlB-v+B=0Vq7$@fBh_2x$WxtKi<1~wDacE&z(AY&r=(}{`vOizDvz3Uw!!7 zjg3$6OP^2Qb?(6TvzuQ|p7>(_rK0)bcj@y#9y|Qlk;=p`Hzt0_cExICq5RhD*(d)7 DzvyO% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/S_RIGHT_15x31.png b/applications/system/hid_app/assets/S_RIGHT_15x31.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ba2afd1aefe680ef730165fab4c73892d67709 GIT binary patch literal 1902 zcmcIlONbmr7;Z(DWOWf=APd1x=i(zZUHzEut|pT>J2UGJY_FTmglzN>tE;PLnw{=y zyL)$LS49Xx2@>%j9()`k7{r6%B>|PqMK5{~!3TIXiiqe%4CWwK&-Bc$vRU`yKtJcJ z@BixizpgsJxbWE2nFOH2JQ=4e4Ua;zU?vO+V_rR^Z9NWXn_RT2ZQBAqHb(1~lb z6U=QS^u)$Oi)^eATnKW zJf@A(!YA&S+{dx3lwdxm5zujVhlb%8oo0nl@C|6Vrpmm(lip0C70jLAjYC&i#x~*D|K+T452z7W6Je=XZfteTh>wAGZ zj@^vX3`c}E>lKNMDg}XOYo6gNu8Dw7u>}y-Ens<&0*n;uB7QbaI?%<(*BM#Ooyf-z zbL%m}ImT|Q@Q zO`utZ2~0yb05UKFL?dpQdJqv;k;JXKHP&T`!#wV;CW#)geU^ln=O+kpo62$2&eP1< z`7Gvwl(%lZ_&!e1r;EF8APz!jW0GgRB9Xkw)RT(W!4B^2P3Mrd*P)zrm*x*)Df6=) zPH45wo&L{o$u|?9;GhgX zu4z?8Y1FC>RhzLOQY>hhP?_B}axjyjAbEG?1LU@4c4PxzyYt)euE8J1`woq`WeML^ zl9S)|`Hqw}>(!QZ;fGhNlaC(US6}`0`Sz3d9=vqv*(aXsAvST>`Np}AU0+fkzWevX zFMjjx2d8h$e|_Lu$7-I9_J8`-$uB3azVgx^*ALwH-JchJdF>*)F8}n`=9z1ooA3Sn z*8Iec=!rusBo2rlA)$&3ESyk<+5_T%Km~3H4&2I#BEbc?mT}@_7qngWLL|q3Z{GLj zea|zmwHBT~bny5=Nszj6H1pW^gDJbyNoqz50#kA2eH?;ew+ z``!=Q%h|H`f=hU)V#+&A9fq+$OVY`iVT{QN%j6F022n-%{l@2t9MFogWO>kwYpfT{ zZzSx@#zLEHtPqDPGpFQ}Lst-lEW`3J>_@3PtSBR1S6t_hHANmlvXzQb%?rxQUQ4d= zgvpKy0f7+8Wk=P^Ix))rv$6pVLxZ-amw;}&&~T9{7e7UmOQ`QI*5`_1;;y3fvMhEr zZ7>+915@QmSJNHG(V(Fj1`r64u16Ujf+#&PVW_i|Bte`7Jd$%p+~KQPMG;cRa|q)p zZIl*13CFY{jx}9{`J5&|&)XduhEsH!HP%FxLhp{mX?s0p+9FH&YC>3JjYZjsNwK)c zGoJSNU9?SK|3e`q(}MA8(jVDFiN^XY6x^x6>LY!zTT2+uc+%#)Kj~C!Qc*ULs>@F< z1rg7?F@V)^#wv>Fnh78RhTVqHg(VlEXQ1gqIEH#62oz^{Z(vRXe|;}d&vTnm zn&F7BX1$^aQECuSmonSYZ3+P``wSQaQGj*IfNwe2LIe^UnIm0de4Ufk+=+brIJcA& z!7+8yrM_GA_CxQ5MaDO{%u8IoA4zUc!)G>f2 zB7l_;1|8kPfUpjR4)J{hV@2MoTjPD6c$^A%g<48npC=&}`3XYYWm+6{^E3-~F^l;i z6|Gw@zK=8PmEx)&$fMA?nB-ZnC?sz(qe}5O*ulNM=^WDzdQ6b+()=+j<$gB639EL6 z)BhPR?QY^zytcday*2)SlwR0MdN}Gbv2tokJ{c`vV#ZC;{@t&weorz}6=}}9nHDz8 z;_lRtQ#(f#Stc<%B2|V1eNaNE1e^{l1B8&}n8?SLX^vI&gDtK}Fw1k$M)g|Bs+mIx&3d(MU;gs@FOEt_4&2-N>CT<>@ZpDEx@=s3>-NW&_Pq!`m0nlRe|7D<#)WS; z&wuvB)nBWFcOL&GyZ+ZlS2RQU=ITZN=J4weZoGF4x*hA*ADj2z^TW9xXVMvR?izUR dLThDn|C>wR&!;bbcqQKfn~jC~C$lfV`Zrq^TDbrK literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Space_60x18.png b/applications/system/hid_app/assets/Space_60x18.png new file mode 100644 index 0000000000000000000000000000000000000000..e29f50ae9220d2f9a9753850dedcc6be0a211e76 GIT binary patch literal 2871 zcmV-73&`||P)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000Uv 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 VN_%sSbR7Ty002ovPDHLkV1hy!X)pi) literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Space_65x18.png b/applications/system/hid_app/assets/Space_65x18.png new file mode 100644 index 0000000000000000000000000000000000000000..b60ae50970b8be827ae32ddbd9e1b0d28c8b3a9a GIT binary patch literal 3619 zcmaJ@c{r5q+kR|?vSeS9G2*Q(Gqz$f_GOf18r!JE7=ytq%?xHFO-U))vSm#u)X=6# zwu*&|8!^APw~9?k(a6Vz_{`1J?VwO%hlm80mweJ_2Ll%+w5Jal|B#ZToHj zkX!A1wWV(yKRGcrJmE7L$o^5EyA?1;0vjpK4{lZ7;N}HH?K{|g9^>OZJmdzhM?p0K zMW=v17r<|D)m^7wAm^muyU^8WhW>`hzU({5Mni?Yg1dIjs(9V0f{sQT{n8mG4Mm49 zbG~l%ht2_K(@oNfYx5#D&tizdC8*fR7G5(g;>x7*Rzu{4&DevTBf5`It4m&=M_(&P zg6$d@FHi{BFL>ue9`qCWpjMUz{dO z@9>n#el1gZMS$0|kzX961dH0^726AL=a){4^e8+sFE>V1@t?G01xkRvR~uHtV6d@Jy=*+_LjJ^<;I%HkfZ+ zJ{WS&*3q1L--qRs;FC3Rwv9{p?cw*6_FMFK^@Q9yZ8!?f0Ei>znMIVlCNa;%nYvD_=OIcyvaxrpYxGcGRWZCqbo>reG^tc8h zWbb>x%$fc-l1kK>Pg=_9^WFC8k{QaNGP~oK)fB= zk~}W=y`t;c`=z{$ml*@ap9mj5x5DesKUlZZ%#d$#e*hBLJ2$e|kFK?B#{H}rW-Lg}+w*yHz2X|@s=6q5@hMLLk0Ngx@77A0z{8^GG<=3FCsOHJ6w{_pD*!j4k8!wLb`#+}y`?CB4 zQGwW*jB;lA{ql?St3NI0Q^jcF`vqpNjn(zm!LN-{xhDhDbu!1&ol`GQ%#aWnhw%cUor3tn<%~!N%j(>i+!K$>%8wb|oXB!X zUe^D7^t}0+-xUX|ptm{#4k$H7g6z!~%8Pa`7Cm2B9iPsA(lAKMOv=nd3E@*p)jmSY z4wO0gsHr6ijWH$&&GLy?n^(q^SE-Brl7W%7oq46G5~Q${Eu>J5eoE#Py&O@6IQcl%pM`Lo~JAQ5D{F{9M=h7QdD!DVxX< zG|G9wpE0lyi;C#Fd)Hj;lB;fVQBqS2vE;|e7g$M5vbQtaKehXm%Y{SI$sQ~+tFYwf zBdhX>5m$SU?yw~Wp|9`Dv9jjbX~cB?G?BI9R`c*!mA`5CyDM`-#q#qpezqJMP@wb32zU+0*_sQsBVDnwlp9 z1k~Y}eFzwNJcCK<%a~0Mc}6~YNcgqs_^ZDL?}eQkMSi{0{$}7!+hE#-vL*g$1VgP0 zRujb1$Rp&y?^LnB-pI>RIHO=)UG^)Stu=}bYS4>w&Cba>0H0qSyOcOu;9ZcNWp51s zkT$?rvE4`ua6jQ*ND(zN(xGR}RjlKca_;?=KGcDxu~0=Et)Zw@0K zo+3@-R$69V4NGW0?52-)vfp1=^RMlue*F1S)BQH1iv4y*zKp2)d2hK&#nR8<o8NY>iF~_Iy7d@WOBnj;S?k&H#!ZAREO0e@E9uw!tHWK^t=8Sj zR?0DPS&EACLUL6L-tCFQ1y2gZJDS5?ele!04<-jUN7j#bpf`HwcCAKt)RZua7Afop zMGs*O$_4u!*bGtM^Q3;}>g74L+mq3vv8SQ0@K zvyIWD6UZDk02mt6$rx+^jt26=`QnLiF#BZ<7=-tRgI)FPpmt<)oF5($O2IjX+B;!G z1F#0(U}GbYAsxmMAmC^i5S7-)K9yf9cVFLjVMR9g!I)rDy3YCxed9RrxIF6f^D=D4GH`@m2ZR{uET z?BHNO8jTEtKte)7G(&VWNfcj*mVto*1gZ_u*4E%4G^h+B4MW!;Qk8!zSm3Bw3Z6{E zlZc>gMT{3Ihz199LjBJf2;_fdiPV4c#K{#)rmpIK~Oj6!<%hNIw#dMD-()LE1W+P|yK8 z3>Ht^wjBJMVrK`lAyR1=A{J+30S9wLH1T+En5mAg1yo=Eh@P&MzLu7yxtX4op5xA}2IPRCO?t!+X9zvoGZ%D;b1(Y*s+gO-7(fhnSIU^r{GM99afXqQXz`L$cAW!v1IOaB9=s#hui literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Voldwn_6x6.png b/applications/system/hid_app/assets/Voldwn_6x6.png new file mode 100644 index 0000000000000000000000000000000000000000..ce487b546812e471bfaf380f3849f954e6c757ac GIT binary patch literal 4556 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Volup_8x6.png b/applications/system/hid_app/assets/Volup_8x6.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9045f7fde3e4c5e7104bdee4919d7694e34d7c GIT binary patch literal 4564 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 M`5v=e6Zez<0(~)a_5c6? literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/for_help_27x5.png b/applications/system/hid_app/assets/for_help_27x5.png new file mode 100644 index 0000000000000000000000000000000000000000..20bb30a08682194abaeb6a6021357e50c805a035 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^(m>40!3HF^a7^C^q!^2X+?^QKos)S99WdZ%`YnH}b*bKw%(wov3pjc@7#Jp5EWGsk_J-j3N~_Q6iC4>A*8LpSTl~#^ z+JRrYL%v#l$~%@lt19=;jsCgPGefM>g5I^SHM^%H{nqGdUnPUhmzuAf%ie%2W$<+M Kb6Mw<&;$T{m_9)O literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c new file mode 100644 index 000000000..bf7c39914 --- /dev/null +++ b/applications/system/hid_app/hid.c @@ -0,0 +1,480 @@ +#include "hid.h" +#include "views.h" +#include +#include + +#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; +} diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h new file mode 100644 index 000000000..ccbbb02d7 --- /dev/null +++ b/applications/system/hid_app/hid.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#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); diff --git a/applications/system/hid_app/hid_ble_10px.png b/applications/system/hid_app/hid_ble_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d30afe0465e3bd0d87355a09bbdfc6c44aa5d9 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlDSNs& zhE&W+PH5C8xG literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/hid_usb_10px.png b/applications/system/hid_app/hid_usb_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..7649138eb70ee33ccc98aba2252999969c7569a4 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4v7|ftIx;Y9?C1WI$O`0h7I;J! zGcf2WfiUB$M|URy1p_=?978mMd;1x=7!)~Jw*LRWb5?>aXRoxz$+XEed20^d^}YW1 z4X?+E4eQjmi&=L1+cVk^B@OehhySjf>x0qUfxcxlr SbuJ%hHiM_DpUXO@geCx&K0Pe} literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h new file mode 100644 index 000000000..961faec52 --- /dev/null +++ b/applications/system/hid_app/views.h @@ -0,0 +1,15 @@ +typedef enum { + HidViewSubmenu, + HidViewKeynote, + HidViewKeyboard, + HidViewNumpad, + HidViewMedia, + HidViewMovie, + HidViewMouse, + HidViewMouseClicker, + HidViewMouseJiggler, + BtHidViewTikShorts, + HidViewPushToTalk, + HidViewPushToTalkMenu, + HidViewPushToTalkHelp, +} HidView; diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c new file mode 100644 index 000000000..1ce0285b2 --- /dev/null +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -0,0 +1,411 @@ +#include "hid_keyboard.h" +#include +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_keyboard.h b/applications/system/hid_app/views/hid_keyboard.h new file mode 100644 index 000000000..712771364 --- /dev/null +++ b/applications/system/hid_app/views/hid_keyboard.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c new file mode 100644 index 000000000..7d0e125d7 --- /dev/null +++ b/applications/system/hid_app/views/hid_keynote.c @@ -0,0 +1,312 @@ +#include "hid_keynote.h" +#include +#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); + } +} diff --git a/applications/system/hid_app/views/hid_keynote.h b/applications/system/hid_app/views/hid_keynote.h new file mode 100644 index 000000000..84bfed4ce --- /dev/null +++ b/applications/system/hid_app/views/hid_keynote.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c new file mode 100644 index 000000000..849c511d9 --- /dev/null +++ b/applications/system/hid_app/views/hid_media.c @@ -0,0 +1,234 @@ +#include "hid_media.h" +#include +#include +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_media.h b/applications/system/hid_app/views/hid_media.h new file mode 100644 index 000000000..4aa51dc17 --- /dev/null +++ b/applications/system/hid_app/views/hid_media.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c new file mode 100644 index 000000000..3ae7c8145 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse.c @@ -0,0 +1,243 @@ +#include "hid_mouse.h" +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_mouse.h b/applications/system/hid_app/views/hid_mouse.h new file mode 100644 index 000000000..d9fb2fd88 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#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); diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c new file mode 100644 index 000000000..d85affc43 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -0,0 +1,214 @@ +#include "hid_mouse_clicker.h" +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_mouse_clicker.h b/applications/system/hid_app/views/hid_mouse_clicker.h new file mode 100644 index 000000000..d72847baa --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_clicker.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c new file mode 100644 index 000000000..09c14c668 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -0,0 +1,183 @@ +#include "hid_mouse_jiggler.h" +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.h b/applications/system/hid_app/views/hid_mouse_jiggler.h new file mode 100644 index 000000000..025a86385 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#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); diff --git a/applications/system/hid_app/views/hid_movie.c b/applications/system/hid_app/views/hid_movie.c new file mode 100644 index 000000000..229f7299a --- /dev/null +++ b/applications/system/hid_app/views/hid_movie.c @@ -0,0 +1,235 @@ +#include "hid_movie.h" +#include +#include +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_movie.h b/applications/system/hid_app/views/hid_movie.h new file mode 100644 index 000000000..52dedc988 --- /dev/null +++ b/applications/system/hid_app/views/hid_movie.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_numpad.c b/applications/system/hid_app/views/hid_numpad.c new file mode 100644 index 000000000..bd4788b83 --- /dev/null +++ b/applications/system/hid_app/views/hid_numpad.c @@ -0,0 +1,318 @@ +#include "hid_numpad.h" +#include +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_numpad.h b/applications/system/hid_app/views/hid_numpad.h new file mode 100644 index 000000000..d9bf54df9 --- /dev/null +++ b/applications/system/hid_app/views/hid_numpad.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c new file mode 100644 index 000000000..86e9f766f --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt.c @@ -0,0 +1,815 @@ +#include "hid_ptt.h" +#include "hid_ptt_menu.h" +#include +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_ptt.h b/applications/system/hid_app/views/hid_ptt.h new file mode 100644 index 000000000..44883edd2 --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +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, +}; diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c new file mode 100644 index 000000000..d84a394f4 --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -0,0 +1,413 @@ +#include "hid_ptt_menu.h" +#include "hid_ptt.h" +#include +#include +#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); +} diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h new file mode 100644 index 000000000..b273ab74d --- /dev/null +++ b/applications/system/hid_app/views/hid_ptt_menu.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +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); \ No newline at end of file diff --git a/applications/system/hid_app/views/hid_tikshorts.c b/applications/system/hid_app/views/hid_tikshorts.c new file mode 100644 index 000000000..6965c1331 --- /dev/null +++ b/applications/system/hid_app/views/hid_tikshorts.c @@ -0,0 +1,271 @@ +#include "hid_tikshorts.h" +#include "../hid.h" +#include + +#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); +} diff --git a/applications/system/hid_app/views/hid_tikshorts.h b/applications/system/hid_app/views/hid_tikshorts.h new file mode 100644 index 000000000..5604962ee --- /dev/null +++ b/applications/system/hid_app/views/hid_tikshorts.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +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); diff --git a/applications/system/snake_game/application.fam b/applications/system/snake_game/application.fam new file mode 100644 index 000000000..b05426e9d --- /dev/null +++ b/applications/system/snake_game/application.fam @@ -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", +) diff --git a/applications/system/snake_game/snake_10px.png b/applications/system/snake_game/snake_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..52d9fa7e0e1b884774a6e58abb1965b1b1905767 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxc>kDAIJlX?eOh zhE&W+PDn^dVNp_G*rbrd!ONE5$kL$2+HH6!MA=j6#)(e`V#>-4Tp0`o%bUNP1L~43 zag8Vm&QB{TPb^AhaL6gmODsst%q!6^$V=Bv&QD2A{^~3#2UN)5>FVdQ&MBb@0GSOf APyhe` literal 0 HcmV?d00001 diff --git a/applications/system/snake_game/snake_game.c b/applications/system/snake_game/snake_game.c new file mode 100644 index 000000000..185f75578 --- /dev/null +++ b/applications/system/snake_game/snake_game.c @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#include +#include +#include + +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; +}