From 0084443ed7705e6ca25d110b123f139eb20d7616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 26 Dec 2023 14:09:10 +0900 Subject: [PATCH 01/29] [FL-3727] RPC: reverse input (#3304) * RPC: reverse input * Assets: sync protobuf --- applications/services/rpc/rpc_gui.c | 126 +++++++++++++++++----------- assets/protobuf | 2 +- 2 files changed, 76 insertions(+), 52 deletions(-) diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index ca8fc61a45..dd219e2dc4 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -5,6 +5,41 @@ #include #include +// Contract assertion +_Static_assert(InputKeyMAX == 6, "InputKeyMAX"); +_Static_assert(InputTypeMAX == 5, "InputTypeMAX"); + +_Static_assert(InputKeyUp == (int32_t)PB_Gui_InputKey_UP, "InputKeyUp != PB_Gui_InputKey_UP"); +_Static_assert( + InputKeyDown == (int32_t)PB_Gui_InputKey_DOWN, + "InputKeyDown != PB_Gui_InputKey_DOWN"); +_Static_assert( + InputKeyRight == (int32_t)PB_Gui_InputKey_RIGHT, + "InputKeyRight != PB_Gui_InputKey_RIGHT"); +_Static_assert( + InputKeyLeft == (int32_t)PB_Gui_InputKey_LEFT, + "InputKeyLeft != PB_Gui_InputKey_LEFT"); +_Static_assert(InputKeyOk == (int32_t)PB_Gui_InputKey_OK, "InputKeyOk != PB_Gui_InputKey_OK"); +_Static_assert( + InputKeyBack == (int32_t)PB_Gui_InputKey_BACK, + "InputKeyBack != PB_Gui_InputKey_BACK"); + +_Static_assert( + InputTypePress == (int32_t)PB_Gui_InputType_PRESS, + "InputTypePress != PB_Gui_InputType_PRESS"); +_Static_assert( + InputTypeRelease == (int32_t)PB_Gui_InputType_RELEASE, + "InputTypeRelease != PB_Gui_InputType_RELEASE"); +_Static_assert( + InputTypeShort == (int32_t)PB_Gui_InputType_SHORT, + "InputTypeShort != PB_Gui_InputType_SHORT"); +_Static_assert( + InputTypeLong == (int32_t)PB_Gui_InputType_LONG, + "InputTypeLong != PB_Gui_InputType_LONG"); +_Static_assert( + InputTypeRepeat == (int32_t)PB_Gui_InputType_REPEAT, + "InputTypeRepeat != PB_Gui_InputType_REPEAT"); + #define TAG "RpcGui" typedef enum { @@ -168,63 +203,20 @@ static void RpcSession* session = rpc_gui->session; furi_assert(session); - InputEvent event; - - bool invalid = false; - - switch(request->content.gui_send_input_event_request.key) { - case PB_Gui_InputKey_UP: - event.key = InputKeyUp; - break; - case PB_Gui_InputKey_DOWN: - event.key = InputKeyDown; - break; - case PB_Gui_InputKey_RIGHT: - event.key = InputKeyRight; - break; - case PB_Gui_InputKey_LEFT: - event.key = InputKeyLeft; - break; - case PB_Gui_InputKey_OK: - event.key = InputKeyOk; - break; - case PB_Gui_InputKey_BACK: - event.key = InputKeyBack; - break; - default: - // Invalid key - invalid = true; - break; - } + bool is_valid = (request->content.gui_send_input_event_request.key < (int32_t)InputKeyMAX) && + (request->content.gui_send_input_event_request.type < (int32_t)InputTypeMAX); - switch(request->content.gui_send_input_event_request.type) { - case PB_Gui_InputType_PRESS: - event.type = InputTypePress; - break; - case PB_Gui_InputType_RELEASE: - event.type = InputTypeRelease; - break; - case PB_Gui_InputType_SHORT: - event.type = InputTypeShort; - break; - case PB_Gui_InputType_LONG: - event.type = InputTypeLong; - break; - case PB_Gui_InputType_REPEAT: - event.type = InputTypeRepeat; - break; - default: - // Invalid type - invalid = true; - break; - } - - if(invalid) { + if(!is_valid) { rpc_send_and_release_empty( session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } + InputEvent event = { + .key = (int32_t)request->content.gui_send_input_event_request.key, + .type = (int32_t)request->content.gui_send_input_event_request.type, + }; + // Event sequence shenanigans event.sequence_source = INPUT_SEQUENCE_SOURCE_SOFTWARE; if(event.type == InputTypePress) { @@ -264,6 +256,29 @@ static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* canvas_draw_xbm(canvas, 0, 0, canvas->width, canvas->height, rpc_gui->virtual_display_buffer); } +static void rpc_system_gui_virtual_display_input_callback(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(event->key < InputKeyMAX); + furi_assert(event->type < InputTypeMAX); + furi_assert(context); + + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + + FURI_LOG_D(TAG, "VirtulDisplay: SendInputEvent"); + + PB_Main rpc_message = { + .command_id = 0, + .command_status = PB_CommandStatus_OK, + .has_next = false, + .which_content = PB_Main_gui_send_input_event_request_tag, + .content.gui_send_input_event_request.key = (int32_t)event->key, + .content.gui_send_input_event_request.type = (int32_t)event->type, + }; + + rpc_send_and_release(session, &rpc_message); +} + static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -300,6 +315,15 @@ static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, rpc_gui->virtual_display_view_port, rpc_system_gui_virtual_display_render_callback, rpc_gui); + + if(request->content.gui_start_virtual_display_request.send_input) { + FURI_LOG_D(TAG, "VirtulDisplay: input forwarding requested"); + view_port_input_callback_set( + rpc_gui->virtual_display_view_port, + rpc_system_gui_virtual_display_input_callback, + rpc_gui); + } + gui_add_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port, GuiLayerFullscreen); rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); diff --git a/assets/protobuf b/assets/protobuf index 23ad19a756..1956b83bba 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b +Subproject commit 1956b83bba99313ee8d8386e5d35d0549341ca26 From c9e3f203140c7ad230ca58d360db394c92c6aeb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 26 Dec 2023 15:12:17 +0900 Subject: [PATCH 02/29] [FL-3429] About: cn,tw,mx certification information (#3318) --- applications/settings/about/about.c | 87 +++++++++++++----- .../About/CertificationChina0_121x41.png | Bin 0 -> 5314 bytes .../About/CertificationChina1_122x47.png | Bin 0 -> 5215 bytes .../icons/About/CertificationMexico_98x41.png | Bin 0 -> 5219 bytes .../icons/About/CertificationTaiwan_33x32.png | Bin 0 -> 4835 bytes targets/f18/api_symbols.csv | 4 +- .../f18/furi_hal/furi_hal_version_device.c | 8 ++ targets/f7/api_symbols.csv | 4 +- targets/f7/furi_hal/furi_hal_version_device.c | 8 ++ targets/furi_hal_include/furi_hal_version.h | 12 +++ 10 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 assets/icons/About/CertificationChina0_121x41.png create mode 100644 assets/icons/About/CertificationChina1_122x47.png create mode 100644 assets/icons/About/CertificationMexico_98x41.png create mode 100644 assets/icons/About/CertificationTaiwan_33x32.png diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index dcd7656fc4..8f0798d9cf 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -11,7 +11,7 @@ typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); -static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_product(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* screen_header = furi_string_alloc_printf( @@ -31,8 +31,6 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me dialog_message_set_text( message, furi_string_get_cstr(screen_text), 0, 26, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(screen_header); furi_string_free(screen_text); @@ -40,7 +38,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me return result; } -static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_address(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; const char* screen_text = "Flipper Devices Inc\n" @@ -50,12 +48,11 @@ static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* me dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_compliance(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; const char* screen_text = "For all compliance\n" @@ -64,35 +61,71 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_icon1(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; dialog_message_set_icon(message, &I_Certification1_103x56, 13, 0); result = dialog_message_show(dialogs, message); - dialog_message_set_icon(message, NULL, 0, 0); return result; } -static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_icon2(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; dialog_message_set_icon(message, &I_Certification2_46x33, 15, 10); dialog_message_set_text( message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter); result = dialog_message_show(dialogs, message); - dialog_message_set_icon(message, NULL, 0, 0); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_cert_china_0(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationChina0_121x41, 3, 3); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_china_1(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationChina1_122x47, 3, 3); + dialog_message_set_text( + message, furi_hal_version_get_srrc_id(), 55, 11, AlignLeft, AlignBottom); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_taiwan(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationTaiwan_33x32, 3, 10); + dialog_message_set_text( + message, furi_hal_version_get_ncc_id(), 39, 30, AlignLeft, AlignBottom); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_mexico(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationMexico_98x41, 17, 4); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_hw_version(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* buffer; buffer = furi_string_alloc(); @@ -118,14 +151,12 @@ static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_header(message, "HW Version Info:", 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(buffer); return result; } -static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_fw_version(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* buffer; buffer = furi_string_alloc(); @@ -157,21 +188,23 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_header(message, "FW Version Info:", 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(buffer); return result; } const AboutDialogScreen about_screens[] = { - product_screen, - compliance_screen, - address_screen, - icon1_screen, - icon2_screen, - hw_version_screen, - fw_version_screen}; + about_screen_product, + about_screen_compliance, + about_screen_address, + about_screen_icon1, + about_screen_icon2, + about_screen_cert_china_0, + about_screen_cert_china_1, + about_screen_cert_taiwan, + about_screen_cert_mexico, + about_screen_hw_version, + about_screen_fw_version}; int32_t about_settings_app(void* p) { UNUSED(p); @@ -201,6 +234,10 @@ int32_t about_settings_app(void* p) { screen_result = about_screens[screen_index](dialogs, message); + dialog_message_set_icon(message, NULL, 0, 0); + dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); + dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); + if(screen_result == DialogMessageButtonLeft) { if(screen_index <= 0) { break; diff --git a/assets/icons/About/CertificationChina0_121x41.png b/assets/icons/About/CertificationChina0_121x41.png new file mode 100644 index 0000000000000000000000000000000000000000..1d28577ab3e6a1c35bf658087b953acdd0fa104a GIT binary patch literal 5314 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 literal 0 HcmV?d00001 diff --git a/assets/icons/About/CertificationTaiwan_33x32.png b/assets/icons/About/CertificationTaiwan_33x32.png new file mode 100644 index 0000000000000000000000000000000000000000..bf2bfa21a7b7a7045a87bb2c521e50490f3fcfe9 GIT binary patch literal 4835 zcmb7IXIxX+w~Zhj>0QJSss%z3l-?4G5{f_|i1YxVBq0eVM0yvbs|;O11XPfYC<;om zfDsFVp-ERjid1PYfd6sk&Aj)1@58+}=d8Wg*?ZlU{o!7*Ff%&DD#8i?01lZLL(bEl z;!(evZin02owJ)^-Ftn5nuO#!ueG9pj3WC;4G%H~^rjL&Cba`5*~ESEMHj ztp%EU+ynxm+_gZqDli2Y)&S{+GA84YR%A14H?ohLnmb5Gn^lvfP9yL`5?p{JKVLLn zoumcY<5j2q-rWX+fO{bXA1#m_%mQeD!6AVv@+$HQAZ=ElCeGbM{XE3*S2As-1@a;g zu0p3Nxit-BJzlLMIFa!+V3xoZ$qQB+*3I8LJ zfb#es&e=u&5l)lu-%lX9VE-)`2K(Q`{rvvc2A%-DL=)ndsDEqWKL_Bg1F=Z(c_bba zfOA7aFCozci9IEDbx}9K_+oHYG;1KWK&mP#$|}Dh3ls_IYX?F3A<=l6@LC{c<^KjE z{t7DXfqx3O&kV!3(}?%;enTb(1{OGs2g;X*K~Q+CuS?)w@~(pFCVnoSNK+)*li)?m z+-Lm_*rMF`_V+3G_IF$J4cPUD4H`ug<1Z<8zcDS4nkrcF_c4fng=nu!9Y*uDy9>bu z;zFR+r=*~yET^a>r=)7Fa7JBGNnJtpgo2v7g2EovUOdJfCrf0f3*;}2!&s*tZ4ByQLYf+WjAttk zso}pjG!OyKHIpohY!-5WMXBhjwF+R*)Q3|7Uyt290MiAmG)I7CQ*f?sg#cgr(IF~z z8en z9ZH)K#~aUuD_gHcy^~hVV_UERyf~%pbb}$xLuL#CjW}7-VzCyG%G|~icM-Tkq2$X( z*|An0sR@@bxJ7xhW+pR@$r6st3JU1~!e^mBuKjG9S^=QYb7A^pbl(hD|MBLdQENudT(L-`AjR;CZs~(fRnIJ zKUFSP zbbus1Lib?KhtB4BzVWLE9eH&X*XTjq_Z!0l^j;yjWg|8-^xy#jbeXUEK;!Wa^9M(Q zxLADP2S+XLr?HdIF(!*kY{wF{XXu15Vl(mUp_5F#7IQ5&OANqmir&%ei4`_F|32UW{xiW9iRc*?oeS?Z#h^;m?rQEO;=2i|c@^F36Z+)Mcr! z;tkSS=?{*Hu=5riHzI7D<$9Ux!j3qf?&TC|=Em~m91zsgc*yv@`P)}Iur8RyrY+8_ zuRq1is|e}j@@eJvhxW$l@I*kw7mSamCAo|9K$1+2Cd9{I;N0b^giG~S#A8#^Uj*K! zZ-~~^eU$p?g%O_tszly9;`7ZGOVw9;65SAjXjZhDenK}7LDBvSyzuc`@{%N(`$^2D z0`OaHc4v`4v^|p4Tzs!4v^k^fq@`OtT#QokbNm6c8-6 zEo)2btaZFWDzWOZ_9kV8d3Oejo_4=|Tk&?jC+ee{(_8cIx%WdpN-DW3fm!-ph%B3|<*W=9CnZ^S zAI4b4Jj>1!XHjPd*Cj8@8sD0S_CaNt_BQshHE6G8lya?+SBKZ65BS|+X|Ur)CWlFf zp$x>R#HyegI(hAlgmin2J1&!MLURH>f%3?ZxBLXpD9$dtQ2zKrJ*?i=+=y}!jKIV@ zULUl4Vis&VWSC!^U)%%$F7DKlpZXpd;IiL7l710ptIBc#w=-Thy z@9FBz;{j4ZxZz&i?yw#@_4ZT;q3EmPd$jVM%%;p<-=aSM>dBK@0DQ zTDrz3vb(6AI*49-NknIDubV$5&HH6tXN?1@&$rJ9>7nw<`;krAM?ar<6{UhZsj6hYd%D zsHKs)QROJPBgrMoWw1rE-Q#oo=3J&ICj2` zd`X+E`Z)fEIhUJTVj{^)Wa0D2XEww3 zaV%*JN7%_7%U|9M=P|cOv_!m)pz0I!)AV~{`Z;TvyI9K(%^v!}oGow(l*1(^{Pg4| zBPB{^_~SfTq7mc)se!x$@q#`XYG1|0r9o8^^^Krc?G?YFYXWK{ABi)z&bz`}`x9yt zbNMcxNfHt~Y|FnY83Nf{TB~O1jACKY;2o4^l#(}TOn1+aH1{_1kW7Y^n}lTWLaI-{p$;cqd(e@<~IM) zoR0W%cfNM9- zYRIgc+N+e>3LK-t!}zYA@H+V+`shj-PGjACkqNpDwSmsb$e3#@KrhF;pPjajEoCd# zmgA7yMv7>|6v_f0tuLv)KiWCm*{jK$nWx}A28T)+pDdFfB6LB#B2C=t@rc0Kls1% zAGWo!eQisG8``LrSK3z-x+ZJ;d>+^^*vuk48_{1L5vQZ4!5HR#i~4SNxApeq?AB~F znuF@#fJ)JQHE%t*a@|s(8e{=5X;vqdFcq5qblZJ zPx0PMrT66g<@|~z%=0yP$rB$CZ~8_jH{Tplo5OFvWmt)hNyrnNzMy5#xjnH!eWpsO z`+3eWLxlQ-nyqelN%XNOc75#n@CncI#d60+f#9-H^1J2i)9r&(jmH|-XW)Znw)F|< z&_~J}@?X$>T4k5{R}<%KchEZ4liEgOre5hTFk*gqcH21O^SFbcEXo&%WSrv Date: Tue, 26 Dec 2023 21:45:36 +0000 Subject: [PATCH 03/29] Added plugin to read WashCity card balance --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/washcity.c | 200 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/washcity.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 1b999e37e3..ecab91ab51 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -101,6 +101,15 @@ App( sources=["plugins/supported_cards/metromoney.c"], ) +App( + appid="washcity_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="washcity_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/washcity.c"], +) + App( appid="kazan_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c new file mode 100644 index 0000000000..0ba66e8b77 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -0,0 +1,200 @@ +/* + * Parser for WashCity MarkItaly Card (Europe). + * + * Copyright 2023 Filipe Polido (YaBaPT) + * + * Based on MetroMoney by Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include + +#define TAG "WashCity" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair washcity_1k_keys[] = { + {.a = 0xA0A1A2A3A4A5, .b = 0x010155010100}, // Sector 00 + {.a = 0xC78A3D0E1BCD, .b = 0xFFFFFFFFFFFF}, // Sector 01 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 02 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 03 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 04 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 05 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 06 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 07 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 08 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 09 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 10 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 11 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 12 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 13 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 14 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 15 +}; + +static bool washcity_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 0; + const uint8_t ticket_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + + MfClassicKey key = {0}; + nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool washcity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify key + const uint8_t ticket_sector_number = 1; + const uint8_t ticket_block_number = 0; + + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != washcity_1k_keys[ticket_sector_number].a) break; + + // Parse data + const uint8_t start_block_num = + mf_classic_get_first_block_num_of_sector(ticket_sector_number); + + const uint8_t* block_start_ptr = + &data->block[start_block_num + ticket_block_number].data[0]; + + uint32_t balance = nfc_util_bytes2num(block_start_ptr+2, 2); + + uint32_t balance_eur = balance / 100; + uint8_t balance_cents = balance % 100; + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + + // Card Number is printed in HEX (equal to UID) + char card_number[2 * uid_len + 1]; + + for(size_t i = 0; i < uid_len; ++i) { + card_number[2 * i] = "0123456789ABCDEF"[uid[i] >> 4]; + card_number[2 * i + 1] = "0123456789ABCDEF"[uid[i] & 0xF]; + } + + card_number[2 * uid_len] = '\0'; + + furi_string_printf( + parsed_data, + "\e#WashCity\nCard number: %s\nBalance: %lu.%02u EUR", + card_number, + balance_eur, + balancecentsi); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin washcity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = washcity_verify, + .read = washcity_read, + .parse = washcity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor washcity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &washcity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* washcity_plugin_ep() { + return &washcity_plugin_descriptor; +} \ No newline at end of file From ca62254ee7ed922fd195982ade1461a879a1f767 Mon Sep 17 00:00:00 2001 From: YaBa Date: Tue, 26 Dec 2023 23:19:48 +0000 Subject: [PATCH 04/29] Fixed clang format and typo in var balance_cents --- applications/main/nfc/plugins/supported_cards/washcity.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 0ba66e8b77..a0edeef6ad 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -149,14 +149,14 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t* block_start_ptr = &data->block[start_block_num + ticket_block_number].data[0]; - uint32_t balance = nfc_util_bytes2num(block_start_ptr+2, 2); + uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); uint32_t balance_eur = balance / 100; uint8_t balance_cents = balance % 100; size_t uid_len = 0; const uint8_t* uid = mf_classic_get_uid(data, &uid_len); - + // Card Number is printed in HEX (equal to UID) char card_number[2 * uid_len + 1]; @@ -172,7 +172,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { "\e#WashCity\nCard number: %s\nBalance: %lu.%02u EUR", card_number, balance_eur, - balancecentsi); + balance_cents); parsed = true; } while(false); From f7e0338a75153ee47a7e1c1fe931c6f422d55e36 Mon Sep 17 00:00:00 2001 From: YaBa Date: Wed, 27 Dec 2023 00:48:30 +0000 Subject: [PATCH 05/29] Added demo file --- .../main/nfc/resources/nfc/Demo_WC_20E.nfc | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 applications/main/nfc/resources/nfc/Demo_WC_20E.nfc diff --git a/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc b/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc new file mode 100755 index 0000000000..c8c9cd0058 --- /dev/null +++ b/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc @@ -0,0 +1,77 @@ +Filetype: Flipper NFC device +Version: 3 +# Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693 +Device type: Mifare Classic +# UID is common for all formats +UID: 96 00 CA FE +# ISO14443 specific fields +ATQA: 00 04 +SAK: 08 +# Mifare Classic specific data +Mifare Classic type: 1K +Data format version: 2 +# Mifare Classic blocks, '??' means unknown data +Block 0: 96 00 CA FE A2 08 04 00 01 B4 B9 86 13 27 F8 1D +Block 1: FF 00 00 02 00 02 00 02 00 02 00 02 00 02 00 02 +Block 2: 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 3: A0 A1 A2 A3 A4 A5 78 77 88 81 01 01 55 01 01 00 +Block 4: 02 E4 07 D0 80 01 00 00 00 00 00 00 00 00 00 01 +Block 5: 00 00 00 00 00 00 00 00 1B 93 CD 00 00 00 00 FF +Block 6: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 7: C7 8A 3D 0E 1B CD FF 07 80 69 FF FF FF FF FF FF +Block 8: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 10: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 11: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 12: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 14: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 15: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 16: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 17: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 18: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 19: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 20: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 21: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 22: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 23: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 24: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 25: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 26: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 27: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 28: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 29: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 30: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 31: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 32: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +Block 33: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF +Block 34: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 +Block 35: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF +Block 36: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 37: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 38: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 39: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 41: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 42: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 43: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 44: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 45: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 46: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 47: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 48: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 49: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 51: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 52: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 53: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 54: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 55: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 56: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 57: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 58: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 59: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF +Block 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 61: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 62: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Block 63: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF From 76ed466eb41a0450aef249223852a7faccfe9a28 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 27 Dec 2023 21:03:50 -0800 Subject: [PATCH 06/29] Nfc: HID MFC Plugin (#3312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/application.fam | 9 ++ .../main/nfc/plugins/supported_cards/hid.c | 153 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hid.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index ecb61fe60d..4d8cb86c07 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -101,6 +101,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="hid_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hid_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hid.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c new file mode 100644 index 0000000000..66ced4d0c5 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -0,0 +1,153 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "HID" + +static const uint64_t hid_key = 0x484944204953; + +bool hid_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 1; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {}; + nfc_util_num2bytes(hid_key, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool hid_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t get_bit_length(const uint8_t* half_block) { + uint8_t bitLength = 0; + uint32_t* halves = (uint32_t*)half_block; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + bitLength = 63 - leading0s; + } + + return bitLength; +} + +static uint64_t get_pacs_bits(const uint8_t* block, uint8_t bitLength) { + // Remove sentinel bit from credential. Byteswapping to handle array of bytes vs 64bit value + uint64_t sentinel = __builtin_bswap64(1ULL << bitLength); + uint64_t swapped = 0; + memcpy(&swapped, block, sizeof(uint64_t)); + swapped = __builtin_bswap64(swapped ^ sentinel); + FURI_LOG_D(TAG, "PACS: (%d) %016llx", bitLength, swapped); + return swapped; +} + +static bool hid_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // verify key + const uint8_t verify_sector = 1; + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + if(key != hid_key) break; + + // Currently doesn't support bit length > 63 + const uint8_t* credential_block = data->block[5].data + 8; + + uint8_t bitLength = get_bit_length(credential_block); + if(bitLength == 0) break; + + uint64_t credential = get_pacs_bits(credential_block, bitLength); + if(credential == 0) break; + + furi_string_printf(parsed_data, "\e#HID Card\n%dbit\n%llx", bitLength, credential); + + parsed = true; + + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hid_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hid_verify, + .read = hid_read, + .parse = hid_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hid_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hid_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hid_plugin_ep() { + return &hid_plugin_descriptor; +} From f6a38352e88f895154f7ddc751e4a837e4b8a15a Mon Sep 17 00:00:00 2001 From: ry4000 <154689120+ry4000@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:12:13 +1100 Subject: [PATCH 07/29] Update mf_classic_dict.nfc (#3314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This Commit proposes the addition of the 33 Bandai Namco Passport / Sega Aime Card static access keys that has been available to Members of the Flipper Devices Discord Server; for convenience, they are listed in Sector Key A/B order. Sector 0 Key B has two possible access keys; 019761AA8082 [current Bandai Namco Passports] 574343467632 [Sega Aime Cards / early-edition Bandai Namco Passports] Co-authored-by: あく --- .../resources/nfc/assets/mf_classic_dict.nfc | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 85f7193cd4..25f750102f 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1315,4 +1315,39 @@ CE99FBC8BD26 # Volgograd (Russia) Volna transport cards keys 2B787A063D5D -D37C8F1793F7 \ No newline at end of file +D37C8F1793F7 + +# Bandai Namco Passport [fka Banapassport] / Sega Aime Card +6090D00632F5 +019761AA8082 +574343467632 +A99164400748 +62742819AD7C +CC5075E42BA1 +B9DF35A0814C +8AF9C718F23D +58CD5C3673CB +FC80E88EB88C +7A3CDAD7C023 +30424C029001 +024E4E44001F +ECBBFA57C6AD +4757698143BD +1D30972E6485 +F8526D1A8D6D +1300EC8C7E80 +F80A65A87FFA +DEB06ED4AF8E +4AD96BF28190 +000390014D41 +0800F9917CB0 +730050555253 +4146D4A956C4 +131157FBB126 +E69DD9015A43 +337237F254D5 +9A8389F32FBF +7B8FB4A7100B +C8382A233993 +7B304F2A12A6 +FC9418BF788B From 35e74c07d13280dd149bbe61e8c91c00c72d3b91 Mon Sep 17 00:00:00 2001 From: YaBa Date: Thu, 28 Dec 2023 05:23:12 +0000 Subject: [PATCH 08/29] Added NFC plugin; parser for WashCity cards. (#3319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added NFC plugin; parser for WashCity cards. * Fixed file to the correct path * Added demo file Demo_WC_20E.nfc * Fixed clang format * fixed typo (balance_cents) * Removed demo file as requested. Co-authored-by: あく --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/washcity.c | 200 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/washcity.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 4d8cb86c07..b92d0ebf1a 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -65,6 +65,15 @@ App( sources=["plugins/supported_cards/troika.c"], ) +App( + appid="washcity_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="washcity_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/washcity.c"], +) + App( appid="plantain_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c new file mode 100644 index 0000000000..a0edeef6ad --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -0,0 +1,200 @@ +/* + * Parser for WashCity MarkItaly Card (Europe). + * + * Copyright 2023 Filipe Polido (YaBaPT) + * + * Based on MetroMoney by Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include +#include + +#define TAG "WashCity" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair washcity_1k_keys[] = { + {.a = 0xA0A1A2A3A4A5, .b = 0x010155010100}, // Sector 00 + {.a = 0xC78A3D0E1BCD, .b = 0xFFFFFFFFFFFF}, // Sector 01 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 02 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 03 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 04 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 05 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 06 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 07 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 08 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 09 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 10 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 11 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 12 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 13 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 14 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 15 +}; + +static bool washcity_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 0; + const uint8_t ticket_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + + MfClassicKey key = {0}; + nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool washcity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify key + const uint8_t ticket_sector_number = 1; + const uint8_t ticket_block_number = 0; + + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != washcity_1k_keys[ticket_sector_number].a) break; + + // Parse data + const uint8_t start_block_num = + mf_classic_get_first_block_num_of_sector(ticket_sector_number); + + const uint8_t* block_start_ptr = + &data->block[start_block_num + ticket_block_number].data[0]; + + uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); + + uint32_t balance_eur = balance / 100; + uint8_t balance_cents = balance % 100; + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + + // Card Number is printed in HEX (equal to UID) + char card_number[2 * uid_len + 1]; + + for(size_t i = 0; i < uid_len; ++i) { + card_number[2 * i] = "0123456789ABCDEF"[uid[i] >> 4]; + card_number[2 * i + 1] = "0123456789ABCDEF"[uid[i] & 0xF]; + } + + card_number[2 * uid_len] = '\0'; + + furi_string_printf( + parsed_data, + "\e#WashCity\nCard number: %s\nBalance: %lu.%02u EUR", + card_number, + balance_eur, + balance_cents); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin washcity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = washcity_verify, + .read = washcity_read, + .parse = washcity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor washcity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &washcity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* washcity_plugin_ep() { + return &washcity_plugin_descriptor; +} \ No newline at end of file From 2a0a54a224523d9f11ee22f2832c5d4e345bfba8 Mon Sep 17 00:00:00 2001 From: Arthur Braghetto Date: Thu, 28 Dec 2023 02:35:01 -0300 Subject: [PATCH 09/29] Add Samsung AC remotes DB93 and AR-EH04 (#3301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 201a293057..64d473deff 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -712,3 +712,79 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 8425 4291 542 1401 544 538 542 1404 518 1427 544 534 570 540 516 1434 542 1399 544 539 544 539 541 1403 541 1402 516 570 517 1427 545 566 511 1406 517 1431 542 534 544 1406 543 536 517 567 543 1404 542 1400 595 525 544 1405 542 534 516 1433 516 1428 544 537 544 535 543 1403 542 505 488 21084 8424 4298 566 535 569 1402 567 533 568 537 570 1403 570 1403 565 534 566 537 566 1402 569 1398 569 533 569 538 592 1446 569 535 570 1400 569 567 535 535 568 1437 568 533 568 1398 569 1402 565 533 567 534 569 1403 568 536 569 1402 568 535 567 534 571 1403 568 1415 566 536 571 1362 489 21084 8403 4313 516 1439 517 574 515 1442 515 1441 518 573 516 574 567 1397 514 1440 515 573 516 575 516 1443 515 1439 518 574 516 1440 517 608 535 1396 517 1441 517 579 515 1438 515 576 517 578 568 1390 569 1391 516 575 518 1439 516 573 517 1445 566 1391 516 571 517 572 516 1441 514 543 487 +# +# Model: Samsung DB93 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17837 3089 8903 555 445 578 1411 583 442 554 442 529 468 528 468 554 443 554 442 554 444 552 1441 552 472 523 475 522 1473 522 1473 522 475 547 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 450 547 451 546 474 523 476 521 476 521 476 522 476 521 475 546 451 547 450 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 450 547 451 546 474 523 474 523 476 521 477 520 477 520 477 521 476 521 476 546 450 547 450 547 450 546 450 547 451 546 451 546 1448 546 1472 522 2982 3005 8963 522 1499 495 502 495 502 495 502 496 501 496 501 521 476 522 475 522 475 522 1472 522 475 522 475 522 1473 521 475 522 1473 522 1499 495 1500 494 1500 496 1499 521 1473 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 502 495 503 494 503 495 502 495 501 496 501 521 476 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 493 504 494 502 495 502 495 502 520 477 520 2984 3003 8966 519 1475 520 477 520 477 520 477 520 478 519 477 520 478 519 479 518 504 493 1502 493 504 493 503 494 503 494 1501 519 1476 518 1475 519 478 519 1476 518 1476 519 1477 517 1501 493 1503 491 1503 493 1502 493 1502 518 479 518 479 518 479 518 1476 518 1477 517 1477 517 504 493 504 493 504 493 531 466 507 490 1529 467 506 491 529 468 1527 492 1502 492 505 493 1502 492 1502 492 505 492 504 493 504 492 505 492 506 491 532 465 532 465 531 467 530 467 530 467 1528 467 1527 492 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17829 3090 8902 556 444 579 1410 584 441 529 468 529 468 554 443 554 442 555 443 553 444 551 1442 551 474 522 475 522 1473 522 475 522 475 547 1448 547 1447 547 1447 547 1447 548 1447 547 449 548 450 547 474 523 474 523 476 521 476 521 476 521 477 521 475 522 476 546 450 547 449 548 449 548 450 547 449 548 450 547 450 547 450 547 451 546 474 523 474 523 474 523 479 518 479 518 479 518 501 496 501 496 477 545 475 522 452 545 474 523 474 523 1472 522 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 496 501 522 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 475 522 1473 521 1500 494 1500 495 1499 520 1474 522 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 478 519 478 519 478 519 479 518 503 494 505 492 505 492 1503 493 504 493 504 493 504 518 1477 518 1476 518 1476 518 479 518 1477 517 1477 517 1501 493 1504 490 1528 468 1527 468 1527 493 1501 493 504 493 504 493 504 493 1501 493 1502 492 1502 492 504 493 505 491 532 440 556 466 531 467 530 468 530 467 530 467 1527 492 1503 491 505 492 504 493 504 493 505 491 1503 492 505 467 530 492 506 466 557 464 533 464 532 466 1528 467 1528 467 1528 466 1528 491 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17831 3089 8903 581 420 578 1411 583 442 529 468 528 468 554 443 554 442 554 443 553 445 551 1443 550 475 521 475 522 1473 522 475 547 449 548 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 449 548 450 547 473 524 476 521 475 522 476 520 476 522 475 522 475 547 449 549 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 476 521 476 521 476 520 477 521 476 546 451 547 450 547 450 547 449 548 450 547 1447 547 1448 546 1448 546 1472 523 2957 3029 8967 518 1477 517 502 495 501 496 501 521 475 522 475 522 475 522 475 522 475 522 1472 522 474 523 475 522 1473 521 475 522 1472 522 1499 495 1500 495 1499 520 1474 522 1473 521 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 502 495 502 495 503 494 502 496 501 496 501 521 476 522 476 521 475 522 475 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 2984 3003 8965 520 1474 521 477 520 477 520 477 520 477 520 477 520 478 519 504 493 504 493 1502 494 503 494 502 495 1500 520 1475 519 1475 519 1475 519 478 519 1475 519 1476 518 1501 493 1502 492 1503 493 1501 494 1501 518 1476 518 478 519 478 519 478 519 1476 518 1476 518 1477 517 504 493 506 491 506 491 506 491 506 492 505 492 504 493 504 518 481 516 1501 493 504 493 504 493 480 517 1501 493 504 493 504 493 504 493 505 491 532 466 531 466 532 466 1528 467 1527 468 1527 492 1502 492 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17832 3088 8903 575 448 555 1411 583 441 529 468 529 468 554 443 554 442 554 443 553 444 551 1443 550 474 522 475 522 1473 522 475 522 475 547 1447 548 1446 548 1446 548 1446 548 1447 547 449 548 449 548 474 523 474 523 476 521 476 521 476 521 477 521 475 522 475 547 450 548 449 548 450 547 449 548 449 548 450 547 450 547 449 547 451 546 452 545 474 523 474 523 477 520 478 519 477 519 478 520 477 545 452 545 451 547 451 546 474 523 474 523 1450 544 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 521 476 521 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 476 521 1473 521 1500 494 1500 495 1499 495 1499 521 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 477 520 478 519 503 493 504 493 504 494 503 494 502 519 478 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 477 520 478 519 478 519 478 519 503 494 504 493 505 492 1503 493 504 493 504 493 503 519 478 519 1476 518 1476 518 478 519 1476 518 1477 517 1501 493 1504 490 1504 490 1503 493 1502 493 1502 517 480 517 504 493 480 517 1477 517 1502 492 1502 492 504 493 504 493 504 493 531 466 531 466 1529 467 1527 467 1527 492 504 493 1502 492 505 492 504 493 505 492 1503 492 505 492 505 492 505 467 557 464 532 441 556 466 532 466 1528 466 1528 491 1503 491 1503 466 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 665 17827 3090 8902 556 444 579 1412 583 441 530 467 529 468 554 442 555 442 554 442 554 444 551 1443 550 473 523 475 522 1472 521 475 523 474 523 1471 549 1446 548 1445 549 1446 548 1445 549 448 549 448 549 449 548 473 523 473 524 475 522 475 522 475 523 475 522 474 548 449 548 449 549 448 549 448 549 448 549 448 549 448 549 449 548 449 548 449 548 450 547 473 524 474 523 476 521 476 521 476 521 476 522 475 547 450 547 450 548 449 548 449 548 1447 547 1447 547 1447 547 1448 546 2957 3030 8962 522 1475 519 501 496 501 497 478 519 500 522 475 522 475 523 474 523 474 523 1472 522 474 523 474 523 1472 522 475 522 1472 522 1499 495 1499 496 1499 496 1498 522 1473 522 474 522 475 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 502 495 502 495 502 495 501 496 501 496 501 521 476 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 476 521 502 495 502 495 502 495 503 495 502 495 501 521 476 521 476 521 2983 3004 8964 521 1474 520 476 521 476 521 477 520 476 521 477 520 477 520 503 494 503 494 1501 494 502 495 502 495 502 520 477 521 1474 520 1474 520 477 520 1474 520 1475 519 1475 519 1500 494 1502 492 1502 493 1501 494 1500 519 478 519 477 520 477 520 1475 519 1475 519 1475 519 478 519 478 519 503 494 505 492 505 492 505 492 1503 493 1502 493 1502 517 1476 518 479 518 479 518 479 518 480 517 479 518 1501 493 504 493 504 493 530 467 531 466 531 467 1527 468 1527 491 1502 493 1501 493 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 668 17822 3064 8902 582 442 555 1413 581 442 528 468 529 494 528 469 502 495 527 470 526 471 525 1468 552 446 550 448 549 1446 548 448 549 448 549 1446 548 1448 546 1471 523 1473 521 1473 521 476 521 475 522 475 547 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 474 523 477 520 477 520 477 520 477 521 476 521 476 546 450 548 450 547 474 523 474 523 450 547 474 523 474 523 452 545 474 523 474 523 474 523 1500 494 1499 495 1499 496 1498 522 2955 3032 8963 521 1472 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 1499 495 502 495 502 495 1499 496 501 521 1473 522 1473 522 1472 522 1472 522 1473 521 1473 522 475 522 475 522 502 495 502 495 502 495 503 495 501 496 501 496 501 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 521 476 521 476 521 476 521 477 520 477 520 477 520 476 521 477 520 2984 3003 8967 518 1502 492 504 493 504 494 503 494 503 494 503 519 478 519 478 519 478 519 1476 518 478 518 479 518 478 519 478 519 1501 493 1501 493 506 491 1504 491 1527 468 1503 492 1526 493 1501 493 1501 494 1501 493 1501 493 504 493 504 493 504 493 1528 466 1529 466 1528 467 529 468 529 493 504 493 504 493 504 493 1502 492 1502 492 1502 467 529 493 1502 491 533 465 532 465 532 465 531 467 530 467 1528 467 530 467 530 467 530 467 530 467 530 467 1527 467 1528 466 1528 466 1529 492 +# +# Model: Samsung AR-EH04 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 663 17769 3057 8901 529 492 527 1434 556 465 529 464 529 464 555 439 555 438 556 438 554 440 553 1435 552 444 549 470 524 1465 524 1466 522 473 522 1467 522 1466 549 1440 549 1440 548 1440 548 445 549 445 549 446 548 447 547 471 523 470 524 471 523 472 522 472 521 473 522 472 522 472 523 472 548 446 549 445 548 446 548 447 547 447 547 446 548 447 547 470 524 471 523 471 523 471 523 471 523 498 496 475 519 498 496 497 498 497 522 472 523 471 524 470 523 471 523 1443 546 1442 547 2947 3023 8935 522 1466 522 471 523 472 522 474 520 498 496 498 496 498 497 497 497 497 523 1466 523 471 523 471 523 1466 523 471 523 1466 522 1467 522 1467 522 1493 495 1493 496 1493 497 497 522 472 523 471 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 499 495 499 495 499 495 499 495 499 496 498 522 472 523 472 523 471 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 473 521 499 495 500 494 499 495 499 496 499 521 2947 3024 8937 521 1467 521 473 521 473 521 472 521 473 521 473 521 473 521 473 521 474 520 1469 520 500 494 500 494 1495 495 500 495 499 520 474 521 1468 520 1468 521 1468 521 1468 521 1468 521 1469 519 1470 518 1495 493 1496 493 500 495 499 520 474 521 1468 520 1469 520 1469 520 473 521 474 520 474 520 475 519 476 518 500 494 502 492 502 491 1497 493 1495 495 499 495 499 520 474 520 475 519 475 519 475 519 475 519 475 519 500 494 501 493 501 493 501 493 501 493 1498 491 1497 519 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 665 17760 3083 8875 553 468 527 1432 584 438 555 439 529 464 555 439 555 438 556 438 555 439 553 1435 552 443 550 470 524 1466 523 471 522 473 521 1467 523 1466 549 1439 549 1440 549 1439 549 445 549 445 549 445 549 446 548 470 524 470 524 471 523 472 522 472 522 473 521 472 522 472 547 447 548 445 549 445 549 445 549 445 549 446 548 445 549 445 549 447 547 470 524 471 523 471 523 471 523 473 521 473 521 473 521 473 521 472 523 472 548 446 548 1441 547 1441 548 1441 547 1441 547 2947 3023 8935 522 1466 522 472 522 474 519 475 519 475 519 474 521 473 546 448 547 448 546 1465 523 449 545 448 546 1466 522 471 523 1467 522 1466 522 1493 495 1493 495 1493 496 1493 522 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 499 495 498 495 499 496 498 496 498 496 498 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 500 494 500 495 499 495 499 495 498 522 2947 3023 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 495 500 494 1494 520 1468 521 1467 521 473 521 1468 521 1468 520 1469 519 1469 519 1471 517 1495 494 1495 494 1494 520 474 521 473 521 473 520 1468 521 1468 521 1468 520 474 520 475 519 475 519 500 493 500 494 501 493 501 493 501 493 1495 495 1494 520 474 520 474 520 474 520 475 519 1470 518 475 519 476 518 500 493 501 493 501 493 501 493 1497 492 1497 492 1496 493 1495 518 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 666 17765 3056 8901 529 492 526 1434 556 465 529 464 530 464 555 438 556 438 555 439 554 440 552 1436 551 443 550 470 524 1465 524 471 523 472 522 1467 521 1467 548 1441 549 1440 548 1440 548 445 549 445 549 445 549 446 548 447 547 470 524 471 523 471 523 473 521 472 522 473 521 473 521 473 522 472 548 446 549 446 548 446 548 447 547 447 547 470 524 447 547 471 523 471 523 471 523 471 523 471 523 475 519 498 496 498 496 498 496 497 498 497 523 1466 524 1443 545 1441 548 1442 546 2947 3023 8935 522 1466 522 472 522 472 522 498 496 498 496 498 496 498 496 498 522 472 523 1466 522 471 523 471 523 1466 523 471 523 1466 523 1467 522 1467 521 1493 496 1493 495 1493 497 497 522 472 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 496 498 522 472 522 472 523 472 521 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 474 520 500 494 500 494 500 495 500 495 499 521 2947 3024 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1469 520 500 494 500 493 1496 494 1494 495 1494 520 1468 520 473 521 1469 519 1468 521 1469 519 1470 519 1470 519 1495 493 1496 493 1495 495 499 520 474 520 474 520 1469 520 1469 519 1469 519 474 520 475 519 500 494 500 494 500 494 502 492 502 492 502 493 501 493 1496 494 500 519 475 520 475 518 1470 519 475 518 476 518 475 519 476 494 525 493 501 493 501 493 1498 491 1498 491 1497 493 1496 518 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 664 17763 3082 8874 553 444 552 1433 583 438 529 464 530 464 555 439 555 438 556 438 555 440 553 1435 552 443 550 470 524 1466 523 472 521 472 522 1467 523 1466 549 1439 549 1439 549 1439 550 444 550 445 549 445 549 447 547 470 524 471 523 471 523 472 522 472 522 473 521 472 523 472 522 471 549 446 549 445 549 446 548 446 548 446 548 446 548 446 548 448 546 471 523 471 523 471 523 471 523 475 519 497 497 474 520 475 520 497 497 496 524 471 524 1465 523 1442 547 1442 547 1442 547 2946 3024 8935 522 1466 522 471 523 474 520 474 520 498 496 474 521 497 522 471 523 471 523 1465 523 471 523 471 523 1466 522 471 523 1466 523 1466 523 1493 495 1493 495 1493 496 1492 523 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 496 498 497 498 497 497 523 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 499 495 499 495 499 496 499 496 498 496 498 522 2946 3024 8937 521 1467 521 472 522 472 522 473 521 473 521 473 521 473 521 474 520 474 520 1495 493 500 494 499 495 499 495 499 496 1493 521 1468 520 473 521 1468 520 1468 521 1468 520 1469 519 1470 518 1495 494 1495 494 1494 495 499 520 473 521 473 521 1468 520 1469 520 1468 521 474 520 474 520 475 519 475 519 476 518 1496 492 1496 494 1495 495 499 520 1469 520 474 520 474 520 474 519 1470 520 474 520 476 518 476 519 477 517 500 494 500 494 503 491 1497 492 1496 493 1495 519 1470 519 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 666 17758 3056 8901 528 493 526 1434 580 441 554 439 529 465 554 439 555 439 555 439 554 440 553 1435 552 443 549 470 524 1465 524 472 522 472 521 1467 523 1467 548 1440 549 1440 548 1440 549 445 549 445 549 446 548 446 548 471 523 471 523 471 523 471 523 473 521 473 521 473 521 473 522 472 547 446 549 446 549 445 548 446 548 446 548 446 548 446 548 447 547 471 523 471 523 471 523 471 523 473 521 474 520 473 521 474 521 473 522 473 546 447 548 1441 548 1442 547 1442 547 1442 546 2948 3021 8936 521 1466 522 472 522 498 496 499 494 476 519 476 519 497 497 497 523 472 522 1466 523 471 523 472 522 1466 522 472 522 1466 522 1467 522 1467 522 1493 494 1495 495 1493 522 472 522 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 499 495 499 495 499 496 498 521 473 522 472 523 472 522 472 522 472 522 473 521 472 522 472 522 472 522 473 521 473 521 474 520 473 521 499 495 500 494 500 495 499 495 499 521 2947 3023 8938 520 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 494 500 495 500 494 1494 520 1468 521 474 520 1468 520 1469 520 1468 520 1469 520 1470 518 1496 493 1496 493 1495 495 499 519 475 520 474 520 1468 520 1469 519 1469 519 474 520 475 519 475 519 476 518 500 494 500 494 1497 492 1497 492 1496 493 1495 519 475 519 475 519 475 519 475 519 475 519 1471 518 476 518 500 494 501 493 501 493 501 493 1498 491 1498 491 1496 518 1472 517 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 From 895694c624aac9b63392841e915965a10b5592b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 28 Dec 2023 19:15:07 +0900 Subject: [PATCH 10/29] Scripts: fix incorrect handling of storage stress test count option (#3321) --- scripts/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/storage.py b/scripts/storage.py index e04eaa7e1f..9df2f6c391 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -74,7 +74,7 @@ def init(self): self.parser_list.set_defaults(func=self.list) self.parser_stress = self.subparsers.add_parser("stress", help="Stress test") - self.parser.add_argument( + self.parser_stress.add_argument( "-c", "--count", type=int, default=10, help="Iteration count" ) self.parser_stress.add_argument("flipper_path", help="Flipper path") From a7b60bf2a610e1a364d26a925f3713c08d16d49c Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 29 Dec 2023 07:24:20 +0400 Subject: [PATCH 11/29] MFC emulation fixes (#3324) * mf classic listener: fix write block * nfc: go to idle state instead of sleep * lib nfc: fix documentation --- lib/nfc/nfc.c | 2 +- lib/nfc/protocols/felica/felica_poller.h | 4 ++-- lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h | 4 ++-- lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h | 4 ++-- lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h | 4 ++-- lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h | 4 ++-- lib/nfc/protocols/iso15693_3/iso15693_3_poller.h | 4 ++-- lib/nfc/protocols/mf_classic/mf_classic_listener.c | 6 +++++- lib/nfc/protocols/mf_classic/mf_classic_listener_i.h | 3 +++ 9 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 6475cce435..22a21c9d2f 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -150,7 +150,7 @@ static int32_t nfc_worker_listener(void* context) { } else if(command == NfcCommandReset) { furi_hal_nfc_listener_enable_rx(); } else if(command == NfcCommandSleep) { - furi_hal_nfc_listener_sleep(); + furi_hal_nfc_listener_idle(); } } } diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index 45fd9a9a1f..b0e6778a0f 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -18,8 +18,8 @@ typedef struct FelicaPoller FelicaPoller; * @brief Enumeration of possible Felica poller event types. */ typedef enum { - FelicaPollerEventTypeError, /**< The card was activated by the poller. */ - FelicaPollerEventTypeReady, /**< An error occured during activation procedure. */ + FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ + FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ } FelicaPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h index 42e4b4bf52..c6649152f6 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -18,8 +18,8 @@ typedef struct Iso14443_3aPoller Iso14443_3aPoller; * @brief Enumeration of possible Iso14443_3a poller event types. */ typedef enum { - Iso14443_3aPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_3aPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_3aPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_3aPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_3aPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h index 940903c1dc..cdf8ddf687 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -18,8 +18,8 @@ typedef struct Iso14443_3bPoller Iso14443_3bPoller; * @brief Enumeration of possible Iso14443_3b poller event types. */ typedef enum { - Iso14443_3bPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_3bPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_3bPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_3bPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_3bPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index 73eb6ef74d..fef565e514 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -17,8 +17,8 @@ typedef struct Iso14443_4aPoller Iso14443_4aPoller; * @brief Enumeration of possible Iso14443_4a poller event types. */ typedef enum { - Iso14443_4aPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_4aPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_4aPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_4aPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_4aPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h index 03b288c079..faccf2f3c1 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -17,8 +17,8 @@ typedef struct Iso14443_4bPoller Iso14443_4bPoller; * @brief Enumeration of possible Iso14443_4b poller event types. */ typedef enum { - Iso14443_4bPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_4bPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_4bPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_4bPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_4bPollerEventType; /** diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index a187ceace1..efe8337afa 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -17,8 +17,8 @@ typedef struct Iso15693_3Poller Iso15693_3Poller; * @brief Enumeration of possible Iso15693_3 poller event types. */ typedef enum { - Iso15693_3PollerEventTypeError, /**< The card was activated by the poller. */ - Iso15693_3PollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso15693_3PollerEventTypeError, /**< An error occured during activation procedure. */ + Iso15693_3PollerEventTypeReady, /**< The card was activated by the poller. */ } Iso15693_3PollerEventType; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index bd25aba23e..9f6f1f85c5 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -33,6 +33,7 @@ static void mf_classic_listener_reset_state(MfClassicListener* instance) { instance->state = MfClassicListenerStateIdle; instance->cmd_in_progress = false; instance->current_cmd_handler_idx = 0; + instance->write_block = 0; instance->transfer_value = 0; instance->transfer_valid = false; instance->value_cmd = MfClassicValueCommandInvalid; @@ -240,11 +241,13 @@ static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handl uint8_t block_num = bit_buffer_get_byte(buff, 1); if(block_num >= instance->total_block_num) break; + if(block_num == 0) break; uint8_t sector_num = mf_classic_get_sector_by_block(block_num); uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); if(sector_num != auth_sector_num) break; + instance->write_block = block_num; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; command = MfClassicListenerCommandAck; @@ -265,7 +268,7 @@ static MfClassicListenerCommand mf_classic_listener_write_block_second_part_hand size_t buff_size = bit_buffer_get_size_bytes(buff); if(buff_size != sizeof(MfClassicBlock)) break; - uint8_t block_num = auth_ctx->block_num; + uint8_t block_num = instance->write_block; MfClassicKeyType key_type = auth_ctx->key_type; MfClassicBlock block = instance->data->block[block_num]; @@ -609,6 +612,7 @@ NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { mf_classic_listener_send_short_frame(instance, nack); mf_classic_listener_reset_state(instance); + command = NfcCommandSleep; } else if(mfc_command == MfClassicListenerCommandSilent) { command = NfcCommandReset; } else if(mfc_command == MfClassicListenerCommandSleep) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 52273be9c2..5269743b5c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -40,6 +40,9 @@ struct MfClassicListener { Crypto1* crypto; MfClassicAuthContext auth_context; + // Write block context + uint8_t write_block; + // Value operation data int32_t transfer_value; bool transfer_valid; From 4d5e09643795b338ae86635097dadd00781b28ba Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 30 Dec 2023 00:55:13 +0900 Subject: [PATCH 12/29] parsers cleanup --- applications/main/nfc/plugins/supported_cards/kazan.c | 3 +-- applications/main/nfc/plugins/supported_cards/metromoney.c | 1 - applications/main/nfc/plugins/supported_cards/umarsh.c | 3 +-- .../main/nfc/plugins/supported_cards/zolotaya_korona.c | 2 -- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index e0179be5b8..de47221cec 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -27,8 +27,7 @@ #include #include #include -#include -#include + #include #define TAG "Kazan" diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index b094d055a0..bb34de3309 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -24,7 +24,6 @@ #include #include #include -#include #define TAG "Metromoney" diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index a85c056f6b..bf643d21c1 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -32,8 +32,7 @@ #include #include #include -#include -#include + #include #define TAG "Umarsh" diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index 1c48f967a8..030b21de6a 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -29,8 +29,6 @@ #include #include #include -#include -#include #define TAG "Zolotaya Korona" From b48103196fcab783d236c82b4b6be6cd88d887c2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:43:15 +0300 Subject: [PATCH 13/29] subghz revert previous "fix" and do proper fix and thanks to Willy-JL ! --- .../subghz/scenes/subghz_scene_receiver.c | 3 +- applications/main/subghz/views/receiver.c | 40 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 51063c9999..118a8bc1ce 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -180,7 +180,6 @@ void subghz_scene_receiver_on_enter(void* context) { subghz->idx_menu_chosen = 0; } - subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz_is_locked(subghz)); subghz_view_receiver_set_mode(subghz->subghz_receiver, SubGhzViewReceiverModeLive); // Load history to receiver @@ -224,6 +223,8 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_scene_receiver_update_statusbar(subghz); + subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz_is_locked(subghz)); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); } diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 1eb4d04609..88e3c31e4e 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -101,24 +101,6 @@ void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { true); } -static void subghz_view_receiver_timer_callback(void* context) { - furi_assert(context); - SubGhzViewReceiver* subghz_receiver = context; - with_view_model( - subghz_receiver->view, - SubGhzViewReceiverModel * model, - { model->bar_show = SubGhzViewReceiverBarShowDefault; }, - true); - if(subghz_receiver->lock_count < UNLOCK_CNT) { - subghz_receiver->callback( - SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context); - } else { - subghz_receiver->lock = false; - subghz_receiver->callback(SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); - } - subghz_receiver->lock_count = 0; -} - void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool lock) { furi_assert(subghz_receiver); subghz_receiver->lock_count = 0; @@ -130,7 +112,6 @@ void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, bool loc { model->bar_show = SubGhzViewReceiverBarShowLock; }, true); furi_timer_start(subghz_receiver->timer, 1000); - subghz_view_receiver_timer_callback(subghz_receiver); } else { with_view_model( subghz_receiver->view, @@ -443,6 +424,21 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { } } +static void subghz_view_receiver_timer_callback(void* context) { + furi_assert(context); + SubGhzViewReceiver* subghz_receiver = context; + with_view_model( + subghz_receiver->view, + SubGhzViewReceiverModel * model, + { model->bar_show = SubGhzViewReceiverBarShowDefault; }, + true); + if(subghz_receiver->lock_count < UNLOCK_CNT) { + subghz_receiver->callback( + SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context); + } + subghz_receiver->lock_count = 0; +} + bool subghz_view_receiver_input(InputEvent* event, void* context) { furi_assert(context); SubGhzViewReceiver* subghz_receiver = context; @@ -460,14 +456,14 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { subghz_receiver->lock_count++; } if(subghz_receiver->lock_count >= UNLOCK_CNT) { - // subghz_receiver->callback( - // SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); + subghz_receiver->callback( + SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); with_view_model( subghz_receiver->view, SubGhzViewReceiverModel * model, { model->bar_show = SubGhzViewReceiverBarShowUnlock; }, true); - //subghz_receiver->lock = false; + subghz_receiver->lock = false; furi_timer_start(subghz_receiver->timer, 650); } From 56ad7ece697ff832b8653a95a620b73b2ad807cd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 30 Dec 2023 03:11:55 +0300 Subject: [PATCH 14/29] add new update image for next builds happy new year ! --- assets/slideshow/update_default/frame_00.png | Bin 3266 -> 7088 bytes assets/slideshow/update_default/frame_01.png | Bin 3213 -> 3766 bytes assets/slideshow/update_default/frame_02.png | Bin 3415 -> 3266 bytes assets/slideshow/update_default/frame_03.png | Bin 0 -> 3213 bytes assets/slideshow/update_default/frame_04.png | Bin 0 -> 3415 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/slideshow/update_default/frame_03.png create mode 100644 assets/slideshow/update_default/frame_04.png diff --git a/assets/slideshow/update_default/frame_00.png b/assets/slideshow/update_default/frame_00.png index 801267de010e409524ee592cf3d02a2cb11c1575..d1102b02ab857a1b86be649a45826bb5ef9372eb 100644 GIT binary patch literal 7088 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 diff --git a/assets/slideshow/update_default/frame_01.png b/assets/slideshow/update_default/frame_01.png index ea37077ccd150cf1ac6149567f0cdb3a9ac6eb4b..2f21ef695f7ad96d6613978257b2634c30df0b98 100644 GIT binary patch literal 3766 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 literal 3213 zcmcIn32+nV6_$-LhJp#9q@0aLP!7|T{=E-uV_6chTMHW_8oOYcJ^qzdOIo3oPaMa% zKn*l7NiYfE1PUYsN((p{I^hbYfoTl}Cm}YZ7@8p&a|B4HE;mho*^=Wn4)J8Bnpy4c zzwiIv_ul*7(`rS|?5Bne89T&aFbvDgu;-$4H9FD~V$pZO+n1-J)8IfxzHTs>Cx(xh zve##pptU%UD=(Di%o1eHXBHJrg61+`09hLh$ur9WqFe++MhUt-{uI;A1MixQ9wo)} zH0=b=fDIOUGAe>_ZpCbuTu~(RifLx5F}X}Y06rKJjb%QsUl+8g^*RbLJRrS`Zo;Gr(j32qVjzafYHe?x#iq5WoUh3l1=X5C9|4q%ra_A#XuN z6>{yfBEHaFim5OZ3J4ZUX=$mslr(EWw*}{U-U0{(?V`LJ6~!i2)042H}vRhEC_BO?|!|Ydz#BL0%$! z`vlfq9Ezk>n$V^AX@E%uzLM^1}h1$2o z(et<`nt)&nLNTNTU7F^N7Ahx-Vk8JNFiw5i<5#p&eOj1NWCOH|A(&!9RU0pM0HE8{iFBZdA+eV+sxJ;)I3rL%AejF$))bFg-XBw^Q^@q|A<-|xOnZt6 zkurNciolSJDzcJ@K^1x76pFw^iBm9{APHU~c!J=hE?#KAU6YH$=?m}gmaU@62%{%M z0!5GtK?5FRNu0naoKi8Km5@4dk!4k!5mkwaqGbm?sB=VbZ`3d=1tE$wqQ$Wy24tj3 z2q_lh30%c!o}pEdBuR$kO-4l)R4wQekxo25(G4vDzuRQ|nvG5KYC)%_pq!Jv*E4Ol zoS>$9yy$_R`&7Cy(_v$9o?$S;jE7~9XrOR2JUXgzd8Co%!bOoQugBP>PC=BzO_5@f z!)kzvDYES8BhCE_s2jGl5F*e=sHEFW*VIs{7=%_gD#>@M(DH5Mb+P2p)OnTWc^WVn zOA$DxDwKk8G7m6bCKwi1SU^EF8v6f*dL*rKq3Cx*G+`~K?;lM6XDkJi|(H4ruN;KBqIVG@;t_n6lzC6k>OsKFXaS3q(X3$N$9uo#IZWr~5Qwb8VI3IgOHr(!D35(-oZ0?4qxSdz-p1j+Ci$lfC?QRjj-GmPhNP%d*!`IVy735_&%f zU-rD1pqrjo8|X*f7DO*DsfS{%2EzbLrrqi)tG~XX@x{Lw6Vl|37w&yE^W*v3$s3L5 zh7Pm?d{_R39UCP_jJ07}$HBO&^9M)Wxw$l^e((TpWPD)q(0^o&KC&KElcK?W)Nrcz zgOgYPSpEFk4{nXQwW#RySE(anPHoG7;J9>&JZ`mDlP`T%9oyM?Zbd>str?&8@X+O~ z><1e@{ncbs6wRDFuzCKE9B~|J9p$Rs7ywJ>jL4~9^aLAHym2Qtj?0#7WA;6}CZSLw z)6V6e+g-PC_wdA`(fOmQR|zwf#iq1d*Xs2VuN-|i_g+&?$F*IDYOQT&*DhbbPA!?-s>8_ ze%|t=>fm?IS5sT#E0|K^@| z_wdDI5{JW*=e+GXol`2Sell)o#qXPMWM4`keAj3%xN&jt*|>A7tA9CsLKP@JGT^T( zy<0p}bA^MmNbPpsCyryUmDlZmzm$uee!tasw>9rW{1@>{9_(9_+IGS_`^eR_i_KR$ z+HH$%)a*l^yA_9CJMDIk{@YtW|LFYvEcwcrqboPGzP|TV#iUmg+n2N)9*oavj&a<0 zSas3==hGc8=~c-;zJ0%7oqPNC*w}k}cdbfTR=r^2o_}uHP25h(BbR;p#D-_bwEf`f zbpEnu?)0@+(uxvVD=T*_ozWDN^WwbC1q11~R?^p7mi+eRY&tu6TaJI>in#Koro>t7 zu4Ut2D6F~u_tot;t6Se|U+kN=HUF`Ooj)5gbTaPoM?gwB>7 zUpnv9FI2zWI&Ac@=8Bj9?b1#6>Qj^5pZsp}Z?f74{p;|njZ<87L!xtU;+;TpvtjGF yQ4<#X?(KN^g}K2!*L?l~e?I=ejhIHR%5a!=JyZ0-)}7%$44IDE_CKXRQ}Z9Je{J0W diff --git a/assets/slideshow/update_default/frame_02.png b/assets/slideshow/update_default/frame_02.png index db971092250d8f86551d9914baea868a11d2a082..801267de010e409524ee592cf3d02a2cb11c1575 100644 GIT binary patch delta 1176 zcmcaEbx3l81rx`PjaD5@90r!UhDIR6AM#aOS9BO-PAM_LtP6CQ&U|FOM|pT z!{j8R6pQ4|{mivY_QV*eq)=SqT2^9Jo{^eot7K$gWTXpo<7RKRK*r54*rOQhrTIlY z7#Ns0c)B=-RLprBeY5DO0>@Rq|Np1w%g?cz;(O>q;bTa?d*u7NZi@zvb zVXen_a0d&^V|DtzL{KX)|ZjlR&rOw@4lGYjXq@-lgOpLFWGE_3-pz1r`oqqS17#T-cTa+ zwQ;gFZ|c@>cjsP-4E4UWCAn;-s4R=ZPBGyr7SWm8`S}vkou;duogeCY{neT{fh%>E zUf&Fb4Cn5>%kHrLh}x^TmCM-j{%om#{$>`3!!3EaGc%i8XUt!lQg@qW&851j+q#%v zOj9}deZk_zkG<#f@T5q(zPe#{Je$*@=-f`8MHeg>Pj8(+FQ=m{!BORp#fFbrS0b;l z-P`zAn~C>gVBH7bV@p}?oUN+7bbrIrFU@cJR1`YYPF%M7o$vYb!$JGipPTDEm;wXX z-donY9+H0FAd>iHNq8>Ptm3VAl!A^=uW;<@s$hEY^|Y4vceOx={y4>r?y)^wFV39a zGWG0{FlDVDsmjjBiWU?vHgOQHstj0G>F81TGhggNp6!%A|C>L4Ux?+OvC?CS^e>^3 zIXgEpDr(+qULTe>8-(#qnpS~D(9PYqpJ;c=?eooDHs5 zf)nET7DsL|T=|Pl#%mk5tm-s2vp+s_G}EP1f-)vq{ga$Bt2O1vvb+O%FPy$q*s|D3 zA9}*N)=2#Sr91xhP2Zb(cII1u;eLE(^^NB`_qrkswsg%nf6S)%_i|pg$+khw6AQ#` zkFSb2q*mTwdUqCQQ2{gGleGE9$Mnix@ohM>DO=U}-_=YPDMLR7trLMu7EZ`@=>PD# z|K7gSiT>H*8RZQZ?K}e4rCM(;@4U+~XF;K?ud!WfQ`WYA->*JmiS;6@C)FFORVGMf z#9uaM7uQZ*8+qm$v(Zn5l?kDYL7wtE8JAUU`Zaaxl#4;<8a*0nSFSm_VRdHWfrrAo z`Ic;$vYq=KgN3fe!q2>4Bo>%`Wp=zByyY>&<84i*3bjtN9~j?R{yF~1tj$?>7(di( zTzqNDcYEfwzZ~aE-Pd~I;XXegAb1L!?eeY*-4p){>vcpKKTSC9Q#KgTe~ HDWM4fcbov4 delta 1291 zcmX>kd0lFP1rsOBMyn1c4nqT71IrKtqsgb3lo6b-Od2XFiRMX0W|qdfrYQzyx+aDu z2D(W}rpdY{Mky(2DXFH(iK!-&9htRMEK*IAl1xm@bPY@mQgoBjOw4r?4Gc_mjm(YB z5)F(}43bSvHup2vGT9Sjq>@5$iECMjRe45go~@FRfsv6e%#EA9*#a3=%#)HVO^hs3 zfbL35)=e`sF#)>EFh$oQ$s{Q$)za9)%p_&<0d|eebJ(L8>xEapna;q#{L|CLF{EP7 z+o;>yRx9wZ-2MOmsOx#>>uHzoa0uvqHQ3MOyiT`r~Q|MrMqY zH*(JC@jZ}|!=#ohY#=$i;p7b#pJNcIj|EJ7zHi<=;oxM>5BJv3&pK1`ao4)fHmyvG zEsraVy7wOZrPosbVDI$3@8?*bzg+#lYVR8BZ8A|;d{%g`Z`gJ0TKxN~8G*(-^**j? zeZ^jUfis{<>TuiF#eG*lI6AztE7HqW+cB>>Lg;_ymOrjPlo_h!cg)`7>GX1?=$9<3 zmF;)>tsSq0MfDiJs24oFWdBA611CFHvyE& z$|w3a^t`QNNM>ToKkE93vxL9Iv_7x2`Dx)7E`b2^PbNItYvbnT86MGMkvO;8ghkKMV>j2|WR*2tU#idW4qmef#~;+cQ!lZD=K|Ja{CRx-0>f1P582wm-4%JiYn z?yT!9rP`x&OD&EI?Ob5Rw`_iksfI1*sbvsDYW^;qxH>Yv#;Y;;nqPBM+3oNb%zNIe2|f7c{d41rO2xy8)%%&g zv`2ogZ5Q|w=EM=Hd0A)8dXA+z6DMx_z3-_$|N7TTwY*KdzO$BQ3#Hv;sd%Px=f?X> z*Y!)kd0#GY{Z@TB?SHIp<-YeHpG~Sc@0S$$zTWz?{^NTcf7y<^9{$Ml;heaq05`PTK?`)rZQCqfm^7dz(>DTQGHko>qT@Cv9)vz;plEQ(b;R^u?r_YDH?RUMO zpkR~mF74Wh4HgkQ`8b%KE8IK5mZm362w~ZN!DMfsSHP>x`@Fh1Xf{PE)q`yVKG( zC2_ZNJ9A>*HP3)K+7*G1w#{0!V8zs>u_9%zdE#rsQhLSD2p+YJTh@Q#f8>Fb7%R`q zmQ!VC+iewCx>&EkZ}wxk-YE;*JmOs$=K8iz@nx8>dQ+fq6q7{l-B$`5*73(~>U6R@ z@Jc7DYf+i?TDf04E;Ki=ACR5H9A<3BSp1RyW!~ZX>IW;|G5~?6tDnm{r-UW|5Klw? diff --git a/assets/slideshow/update_default/frame_03.png b/assets/slideshow/update_default/frame_03.png new file mode 100644 index 0000000000000000000000000000000000000000..ea37077ccd150cf1ac6149567f0cdb3a9ac6eb4b GIT binary patch literal 3213 zcmcIn32+nV6_$-LhJp#9q@0aLP!7|T{=E-uV_6chTMHW_8oOYcJ^qzdOIo3oPaMa% zKn*l7NiYfE1PUYsN((p{I^hbYfoTl}Cm}YZ7@8p&a|B4HE;mho*^=Wn4)J8Bnpy4c zzwiIv_ul*7(`rS|?5Bne89T&aFbvDgu;-$4H9FD~V$pZO+n1-J)8IfxzHTs>Cx(xh zve##pptU%UD=(Di%o1eHXBHJrg61+`09hLh$ur9WqFe++MhUt-{uI;A1MixQ9wo)} zH0=b=fDIOUGAe>_ZpCbuTu~(RifLx5F}X}Y06rKJjb%QsUl+8g^*RbLJRrS`Zo;Gr(j32qVjzafYHe?x#iq5WoUh3l1=X5C9|4q%ra_A#XuN z6>{yfBEHaFim5OZ3J4ZUX=$mslr(EWw*}{U-U0{(?V`LJ6~!i2)042H}vRhEC_BO?|!|Ydz#BL0%$! z`vlfq9Ezk>n$V^AX@E%uzLM^1}h1$2o z(et<`nt)&nLNTNTU7F^N7Ahx-Vk8JNFiw5i<5#p&eOj1NWCOH|A(&!9RU0pM0HE8{iFBZdA+eV+sxJ;)I3rL%AejF$))bFg-XBw^Q^@q|A<-|xOnZt6 zkurNciolSJDzcJ@K^1x76pFw^iBm9{APHU~c!J=hE?#KAU6YH$=?m}gmaU@62%{%M z0!5GtK?5FRNu0naoKi8Km5@4dk!4k!5mkwaqGbm?sB=VbZ`3d=1tE$wqQ$Wy24tj3 z2q_lh30%c!o}pEdBuR$kO-4l)R4wQekxo25(G4vDzuRQ|nvG5KYC)%_pq!Jv*E4Ol zoS>$9yy$_R`&7Cy(_v$9o?$S;jE7~9XrOR2JUXgzd8Co%!bOoQugBP>PC=BzO_5@f z!)kzvDYES8BhCE_s2jGl5F*e=sHEFW*VIs{7=%_gD#>@M(DH5Mb+P2p)OnTWc^WVn zOA$DxDwKk8G7m6bCKwi1SU^EF8v6f*dL*rKq3Cx*G+`~K?;lM6XDkJi|(H4ruN;KBqIVG@;t_n6lzC6k>OsKFXaS3q(X3$N$9uo#IZWr~5Qwb8VI3IgOHr(!D35(-oZ0?4qxSdz-p1j+Ci$lfC?QRjj-GmPhNP%d*!`IVy735_&%f zU-rD1pqrjo8|X*f7DO*DsfS{%2EzbLrrqi)tG~XX@x{Lw6Vl|37w&yE^W*v3$s3L5 zh7Pm?d{_R39UCP_jJ07}$HBO&^9M)Wxw$l^e((TpWPD)q(0^o&KC&KElcK?W)Nrcz zgOgYPSpEFk4{nXQwW#RySE(anPHoG7;J9>&JZ`mDlP`T%9oyM?Zbd>str?&8@X+O~ z><1e@{ncbs6wRDFuzCKE9B~|J9p$Rs7ywJ>jL4~9^aLAHym2Qtj?0#7WA;6}CZSLw z)6V6e+g-PC_wdA`(fOmQR|zwf#iq1d*Xs2VuN-|i_g+&?$F*IDYOQT&*DhbbPA!?-s>8_ ze%|t=>fm?IS5sT#E0|K^@| z_wdDI5{JW*=e+GXol`2Sell)o#qXPMWM4`keAj3%xN&jt*|>A7tA9CsLKP@JGT^T( zy<0p}bA^MmNbPpsCyryUmDlZmzm$uee!tasw>9rW{1@>{9_(9_+IGS_`^eR_i_KR$ z+HH$%)a*l^yA_9CJMDIk{@YtW|LFYvEcwcrqboPGzP|TV#iUmg+n2N)9*oavj&a<0 zSas3==hGc8=~c-;zJ0%7oqPNC*w}k}cdbfTR=r^2o_}uHP25h(BbR;p#D-_bwEf`f zbpEnu?)0@+(uxvVD=T*_ozWDN^WwbC1q11~R?^p7mi+eRY&tu6TaJI>in#Koro>t7 zu4Ut2D6F~u_tot;t6Se|U+kN=HUF`Ooj)5gbTaPoM?gwB>7 zUpnv9FI2zWI&Ac@=8Bj9?b1#6>Qj^5pZsp}Z?f74{p;|njZ<87L!xtU;+;TpvtjGF yQ4<#X?(KN^g}K2!*L?l~e?I=ejhIHR%5a!=JyZ0-)}7%$44IDE_CKXRQ}Z9Je{J0W literal 0 HcmV?d00001 diff --git a/assets/slideshow/update_default/frame_04.png b/assets/slideshow/update_default/frame_04.png new file mode 100644 index 0000000000000000000000000000000000000000..db971092250d8f86551d9914baea868a11d2a082 GIT binary patch literal 3415 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 O_0ef#BFZ%jmi`Amz22<= literal 0 HcmV?d00001 From 0049b1999463c4186ece1c83a67aabaef8c091fc Mon Sep 17 00:00:00 2001 From: Eczbek Date: Mon, 1 Jan 2024 21:56:58 -0500 Subject: [PATCH 15/29] Remove weird newline in `applications/ReadMe.md` >:) --- applications/ReadMe.md | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/ReadMe.md b/applications/ReadMe.md index de465832ae..c54c87c556 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -40,7 +40,6 @@ Applications for main Flipper menu. Background services providing system APIs to applications. - `applications.h` - Firmware application list header - - `bt` - BLE service and application - `cli` - Console service and API - `crypto` - Crypto cli tools From d511d76a1ba843982d523a4ae0612c8b42b2d41b Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Tue, 2 Jan 2024 09:43:46 +0300 Subject: [PATCH 16/29] [FL-3678] [FL-3733] [FL-3723] UI refactor (#3323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added new image DolphinSaved_113x58.png for all "saved" pages * New image DolphinDone_80x58.png added * Replaced dolphins on all scenes accroding to new UI specs * New success dolphin image added * Success scene image replaced * Changed image and text for update initial scene * Image and text adjusted for "Original restored" scene * Removed old DolphinNice_96x59.png image * New image for LFRFID scene * Removed unused image * New UI image added to assets * Replaced warning dolphin on mf_classic write initial fail scene * Removed old image * Changed image on scenes to a new one * New dolphin mafia image * Replaced dolphin mafia image to a new one * Removed DolphinMafia_115x62.png * New check symbol on completed state for detect_reader * Adjusted layout elements position * Removed second switching to popup view in order to achieve control in support callbacks In general now we show generic scene and after that in on_enter callback we can redefine it for particular protocol * CardDetected event now also triggers on_event callback * Now on AuthRequest we throw CardDetected custom event * Added callback for read_on_event * Now we show different screen while reading and unlocking * Fixed missing asstes for some scenes * Update DolphinMafia_119x62.png * Adjusted all the scenes with DolphinMafia image * Scenes with save image adjusted * Removed unnecessary assets DolphinMafia_119x62.png and DolphinSaved_113x58.png * All common dolphins moved to Dolphin folder * Moved DolphinReadingSuccess_59x63.png to Dolphin folder * Set proper led color for detect and read scenes * Added new notification sequence for semi_success results * Use new sequence for semi_success nfc reads * Different events are now throwed depending on read result * Added handling of incomplete event for ultralight cards * Replaced image for iButton scene * Updated API for f18 * Fixed issue with unlock retry sequence * Fix after review * Success notification replaced to semi success in case of incomplete mf classic reading * New text for read scene * New read result sound notification logic for mf classic cards Co-authored-by: あく Co-authored-by: gornekich --- .../scenes/ibutton_scene_delete_success.c | 5 +- .../scenes/ibutton_scene_save_success.c | 5 +- .../scenes/ibutton_scene_write_success.c | 2 +- .../scenes/infrared_scene_edit_delete_done.c | 5 +- .../scenes/infrared_scene_edit_rename_done.c | 5 +- .../scenes/infrared_scene_learn_done.c | 6 +- .../scenes/lfrfid_scene_delete_success.c | 4 +- .../lfrfid/scenes/lfrfid_scene_save_success.c | 4 +- .../main/lfrfid/scenes/lfrfid_scene_write.c | 4 +- .../scenes/lfrfid_scene_write_success.c | 4 +- .../mf_ultralight/mf_ultralight.c | 55 +++++++++++++++++- .../protocol_support/nfc_protocol_support.c | 9 ++- applications/main/nfc/nfc_app.c | 4 +- .../nfc/scenes/nfc_scene_delete_success.c | 4 +- .../main/nfc/scenes/nfc_scene_detect.c | 3 +- .../main/nfc/scenes/nfc_scene_exit_confirm.c | 5 +- .../scenes/nfc_scene_mf_classic_dict_attack.c | 16 ++++- ...nfc_scene_mf_classic_keys_warn_duplicate.c | 2 +- ..._scene_mf_classic_update_initial_success.c | 4 +- .../nfc_scene_mf_classic_write_initial_fail.c | 2 +- ...c_scene_mf_classic_write_initial_success.c | 4 +- .../scenes/nfc_scene_mf_classic_wrong_card.c | 2 +- .../nfc_scene_mf_ultralight_unlock_warn.c | 2 +- .../nfc_scene_mf_ultralight_write_fail.c | 2 +- .../nfc_scene_mf_ultralight_write_success.c | 4 +- .../nfc_scene_mf_ultralight_wrong_card.c | 2 +- .../nfc/scenes/nfc_scene_restore_original.c | 4 +- .../main/nfc/scenes/nfc_scene_retry_confirm.c | 11 ++-- .../main/nfc/scenes/nfc_scene_save_success.c | 4 +- applications/main/nfc/views/detect_reader.c | 1 + .../scenes/subghz_scene_delete_success.c | 4 +- .../scenes/subghz_scene_receiver_info.c | 2 +- .../subghz/scenes/subghz_scene_save_success.c | 4 +- .../scenes/subghz_scene_show_error_sub.c | 2 +- .../subghz/scenes/subghz_scene_show_only_rx.c | 2 +- applications/main/subghz/subghz_i.c | 2 +- applications/services/loader/loader.c | 2 +- .../notification/notification_messages.c | 18 ++++++ .../notification/notification_messages.h | 1 + .../bt_settings_scene_forget_dev_success.c | 2 +- .../desktop_settings_scene_pin_disable.c | 4 +- .../scenes/storage_settings_scene_benchmark.c | 2 +- .../storage_settings_scene_format_confirm.c | 2 +- .../storage_settings_scene_formatting.c | 2 +- .../scenes/storage_settings_scene_sd_info.c | 2 +- .../scenes/storage_settings_scene_unmounted.c | 2 +- assets/icons/Dolphin/DolphinCommon_56x48.png | Bin 1416 -> 0 bytes assets/icons/Dolphin/DolphinDone_80x58.png | Bin 0 -> 1664 bytes assets/icons/Dolphin/DolphinMafia_119x62.png | Bin 0 -> 1280 bytes .../DolphinReadingSuccess_59x63.png | Bin assets/icons/Dolphin/DolphinSaved_92x58.png | Bin 0 -> 901 bytes assets/icons/Dolphin/DolphinSuccess_91x55.png | Bin 0 -> 930 bytes .../DolphinWait_61x59.png | Bin .../Dolphin/WarningDolphinFlip_45x42.png | Bin 0 -> 1437 bytes .../WarningDolphin_45x42.png | Bin assets/icons/NFC/check_big_20x17.png | Bin 0 -> 199 bytes .../icons/RFID/RFIDDolphinSuccess_108x57.png | Bin 2681 -> 0 bytes assets/icons/iButton/DolphinMafia_115x62.png | Bin 2504 -> 0 bytes assets/icons/iButton/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../iButtonDolphinVerySuccess_108x52.png | Bin 2157 -> 0 bytes .../iButtonDolphinVerySuccess_92x55.png | Bin 0 -> 967 bytes targets/f18/api_symbols.csv | 1 + targets/f7/api_symbols.csv | 1 + 63 files changed, 162 insertions(+), 77 deletions(-) delete mode 100644 assets/icons/Dolphin/DolphinCommon_56x48.png create mode 100644 assets/icons/Dolphin/DolphinDone_80x58.png create mode 100644 assets/icons/Dolphin/DolphinMafia_119x62.png rename assets/icons/{Infrared => Dolphin}/DolphinReadingSuccess_59x63.png (100%) create mode 100644 assets/icons/Dolphin/DolphinSaved_92x58.png create mode 100644 assets/icons/Dolphin/DolphinSuccess_91x55.png rename assets/icons/{iButton => Dolphin}/DolphinWait_61x59.png (100%) create mode 100644 assets/icons/Dolphin/WarningDolphinFlip_45x42.png rename assets/icons/{Interface => Dolphin}/WarningDolphin_45x42.png (100%) create mode 100644 assets/icons/NFC/check_big_20x17.png delete mode 100644 assets/icons/RFID/RFIDDolphinSuccess_108x57.png delete mode 100644 assets/icons/iButton/DolphinMafia_115x62.png delete mode 100644 assets/icons/iButton/DolphinNice_96x59.png delete mode 100644 assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png create mode 100644 assets/icons/iButton/iButtonDolphinVerySuccess_92x55.png diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c index 9ff165e4a3..6d4ca24c09 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c @@ -9,9 +9,8 @@ void ibutton_scene_delete_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_delete_success_popup_callback); popup_set_context(popup, ibutton); popup_set_timeout(popup, 1500); diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c index 8b16d2929a..6652ff7c5f 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_success.c @@ -9,9 +9,8 @@ void ibutton_scene_save_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); - + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_save_success_popup_callback); popup_set_context(popup, ibutton); popup_set_timeout(popup, 1500); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write_success.c b/applications/main/ibutton/scenes/ibutton_scene_write_success.c index 17cd53d08d..b36bccfbcb 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write_success.c @@ -10,7 +10,7 @@ void ibutton_scene_write_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52); + popup_set_icon(popup, 0, 9, &I_iButtonDolphinVerySuccess_92x55); popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_write_success_popup_callback); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 9205db4c4e..6515834537 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -4,9 +4,8 @@ void infrared_scene_edit_delete_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_callback(popup, infrared_popup_closed_callback); popup_set_context(popup, context); popup_set_timeout(popup, 1500); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c index 35f5159894..d7332c151c 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c @@ -4,9 +4,8 @@ void infrared_scene_edit_rename_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); - + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_callback(popup, infrared_popup_closed_callback); popup_set_context(popup, context); popup_set_timeout(popup, 1500); diff --git a/applications/main/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c index b4eb38331d..a0c605b0b5 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_done.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_done.c @@ -4,12 +4,12 @@ void infrared_scene_learn_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - if(infrared->app_state.is_learning_new_remote) { + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop); } else { - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); } popup_set_callback(popup, infrared_popup_closed_callback); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c index f940b9bd43..abb173a73d 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c @@ -4,8 +4,8 @@ void lfrfid_scene_delete_success_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c index 52aefa8489..2f5d5ae9ff 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c @@ -7,8 +7,8 @@ void lfrfid_scene_save_success_on_enter(void* context) { // Clear state of data enter scene scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write.c b/applications/main/lfrfid/scenes/lfrfid_scene_write.c index b7faed69ff..f6e762e4d8 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write.c @@ -57,7 +57,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess); consumed = true; } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) { - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop); popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); notification_message(app->notifications, &sequence_blink_start_red); @@ -65,7 +65,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { } else if( (event.event == LfRfidEventWriteFobCannotBeWritten) || (event.event == LfRfidEventWriteTooLongToWrite)) { - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c index 52e30d6b66..78ba481370 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c @@ -4,8 +4,8 @@ void lfrfid_scene_write_success_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; - popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3e27fc539e..4a8d4d7447 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -52,9 +52,15 @@ static NfcCommand if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + uint32_t event = (data->pages_read == data->pages_total) ? NfcCustomEventPollerSuccess : + NfcCustomEventPollerIncomplete; + view_dispatcher_send_custom_event(instance->view_dispatcher, event); return NfcCommandStop; } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); const MfUltralightData* data = @@ -90,10 +96,55 @@ static NfcCommand return NfcCommandContinue; } +enum { + NfcSceneMfUltralightReadMenuStateCardSearch, + NfcSceneMfUltralightReadMenuStateCardFound, +}; + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + + uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : + NfcSceneMfUltralightReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } +bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { + if(event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); + } else if((event == NfcCustomEventPollerIncomplete)) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; +} + static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { Submenu* submenu = instance->submenu; @@ -179,7 +230,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .scene_read = { .on_enter = nfc_scene_read_on_enter_mf_ultralight, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_mf_ultralight, }, .scene_read_menu = { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 38ead0ac3f..ad7f5a0d1d 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -146,8 +146,7 @@ static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { // SceneRead static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { - popup_set_header( - instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 12, 23, &A_Loading_24); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); @@ -162,7 +161,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { // Start poller with the appropriate callback nfc_protocol_support[protocol]->scene_read.on_enter(instance); - nfc_blink_detect_start(instance); + nfc_blink_read_start(instance); } static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) { @@ -200,6 +199,10 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana instance->scene_manager, NfcSceneDetect); } consumed = true; + } else if(event.event == NfcCustomEventCardDetected) { + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index bf15161aa0..183f498951 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -223,7 +223,7 @@ void nfc_text_store_clear(NfcApp* nfc) { } void nfc_blink_read_start(NfcApp* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); + notification_message(nfc->notifications, &sequence_blink_start_yellow); } void nfc_blink_emulate_start(NfcApp* nfc) { @@ -231,7 +231,7 @@ void nfc_blink_emulate_start(NfcApp* nfc) { } void nfc_blink_detect_start(NfcApp* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_yellow); + notification_message(nfc->notifications, &sequence_blink_start_cyan); } void nfc_blink_stop(NfcApp* nfc) { diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index f0c22eec4d..73856c292a 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -10,8 +10,8 @@ void nfc_scene_delete_success_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_delete_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 593c67aabe..34c552aba5 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -17,8 +17,9 @@ void nfc_scene_detect_on_enter(void* context) { // Setup view popup_reset(instance->popup); + popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index c024d31295..16593cc89d 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -12,9 +12,8 @@ void nfc_scene_exit_confirm_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Exit"); dialog_ex_set_right_button_text(dialog_ex, "Stay"); - dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 11, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_exit_confirm_dialog_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b6ba1c119f..328e39132f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -175,6 +175,16 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } +static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + bool is_card_fully_read = mf_classic_is_card_read(mfc_data); + if(is_card_fully_read) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } +} + bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { NfcApp* instance = context; bool consumed = false; @@ -196,7 +206,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); consumed = true; } else { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; @@ -225,13 +235,13 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } else { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } consumed = true; } else if(state == DictAttackStateSystemDictInProgress) { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index 991c956c1c..c3fb92bee0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -11,7 +11,7 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { // Setup view Popup* popup = instance->popup; - popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c index 02e307b01b..2e0ada0da8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_classic_update_initial_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Updated", 11, 20, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_update_initial_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c index f85e5a80c3..4d4367ec8f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c @@ -16,7 +16,7 @@ void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c index acb75cd2e9..100c5c4315 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c index 50025048af..a879985bc8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c @@ -16,7 +16,7 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index e3bbfba59a..db5fd945fa 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -40,7 +40,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_center_button_text(dialog_ex, "OK"); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c index dff5f27815..fcfb5f2b0f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c @@ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c index c1fbc35ee5..9726ef283f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c index a225c474db..0ca765db78 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c @@ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original.c b/applications/main/nfc/scenes/nfc_scene_restore_original.c index 612e6041e6..3a47ce8c3a 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original.c @@ -10,8 +10,8 @@ void nfc_scene_restore_original_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Original file\nrestored", 5, 22, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_restore_original_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index b80f1bdcc1..99acbb0b80 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -12,9 +12,8 @@ void nfc_scene_retry_confirm_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "Stay"); - dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback); @@ -29,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { + if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneDetect); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneRead)) { diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 0cb26c0d45..9d2a380137 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -10,8 +10,8 @@ void nfc_scene_save_success_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_save_success_popup_callback); diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index ebcda7caf1..d832d27d65 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -50,6 +50,7 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) { if(m->state == DetectReaderStateDone) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Completed!"); + canvas_draw_icon(canvas, 20, 23, &I_check_big_20x17); } else { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Collecting..."); diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index 4d9f33e37e..d5c2b391ce 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -12,8 +12,8 @@ void subghz_scene_delete_success_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_delete_success_popup_callback); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 7180bb3a40..08d4caecf9 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -93,7 +93,7 @@ void subghz_scene_receiver_info_on_enter(void* context) { subghz); } } else { - widget_add_icon_element(subghz->widget, 37, 15, &I_DolphinCommon_56x48); + widget_add_icon_element(subghz->widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse."); } diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 40ade5a535..9b610b5f5f 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -11,8 +11,8 @@ void subghz_scene_save_success_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_save_success_popup_callback); diff --git a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c index 113e7ae746..0de48c442a 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c @@ -11,7 +11,7 @@ void subghz_scene_show_error_sub_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, furi_string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c index 1907c41926..3522bf8aa6 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c @@ -21,7 +21,7 @@ void subghz_scene_show_only_rx_on_enter(void* context) { popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index c03efe5e56..4358b164da 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -70,7 +70,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); - dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); dialog_message_show(dialogs, message); dialog_message_free(message); diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 29ec86ac66..158b95de64 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -55,7 +55,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); - dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); dialog_message_set_text( message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); dialog_message_show(dialogs, message); diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index 28ec327c6e..8b79162264 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -519,6 +519,24 @@ const NotificationSequence sequence_success = { NULL, }; +const NotificationSequence sequence_semi_success = { + &message_display_backlight_on, + &message_green_255, + &message_vibro_on, + &message_note_c4, + &message_delay_50, + &message_note_e4, + &message_delay_50, + &message_note_g4, + &message_delay_50, + &message_sound_off, + &message_delay_50, + &message_note_c5, + &message_delay_50, + &message_sound_off, + NULL, +}; + const NotificationSequence sequence_error = { &message_display_backlight_on, &message_red_255, diff --git a/applications/services/notification/notification_messages.h b/applications/services/notification/notification_messages.h index d87cf74f4e..873bb37a86 100644 --- a/applications/services/notification/notification_messages.h +++ b/applications/services/notification/notification_messages.h @@ -138,6 +138,7 @@ extern const NotificationSequence sequence_blink_stop; extern const NotificationSequence sequence_single_vibro; extern const NotificationSequence sequence_double_vibro; extern const NotificationSequence sequence_success; +extern const NotificationSequence sequence_semi_success; extern const NotificationSequence sequence_error; extern const NotificationSequence sequence_audiovisual_alert; diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c index 481ba6d5c8..b7ed63f63e 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c @@ -10,7 +10,7 @@ void bt_settings_scene_forget_dev_success_on_enter(void* context) { BtSettingsApp* app = context; Popup* popup = app->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(popup, "Done", 14, 15, AlignLeft, AlignTop); popup_set_timeout(popup, 1500); popup_set_context(popup, app); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index 7fbcc32521..43f05ec9bd 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -24,8 +24,8 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) { popup_set_context(app->popup, app); popup_set_callback(app->popup, pin_disable_back_callback); - popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(app->popup, "PIN\ndeleted!", 95, 9, AlignCenter, AlignCenter); + popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(app->popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(app->popup, 1500); popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index a5bf1b9d37..e734c78e03 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -125,7 +125,7 @@ void storage_settings_scene_benchmark_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); if(sd_status != FSE_OK) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c index 8af065bf8b..862f55a464 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -14,7 +14,7 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { FS_Error sd_status = storage_sd_status(app->fs_api); if(sd_status == FSE_NOT_READY) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c index df5e3cc17d..f107aaceae 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -47,7 +47,7 @@ void storage_settings_scene_formatting_on_enter(void* context) { dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); } dialog_ex_set_center_button_text(dialog_ex, "OK"); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index 81c786d0cb..aa9662a714 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -19,7 +19,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); if(sd_status != FSE_OK) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c index 33bb955229..86398b1c95 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -42,7 +42,7 @@ void storage_settings_scene_unmounted_on_enter(void* context) { } dialog_ex_set_center_button_text(dialog_ex, "OK"); - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback); diff --git a/assets/icons/Dolphin/DolphinCommon_56x48.png b/assets/icons/Dolphin/DolphinCommon_56x48.png deleted file mode 100644 index 089aaed83507431993a76ca25d32fdd9664c1c84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj diff --git a/assets/icons/Dolphin/DolphinDone_80x58.png b/assets/icons/Dolphin/DolphinDone_80x58.png new file mode 100644 index 0000000000000000000000000000000000000000..594d62d5294997399bc3b256fbe7685ce10b146d GIT binary patch literal 1664 zcmbVNc~BE)6wheY2!uKFLY*phgJAoI;~(9b-S2(h_kQpF-Zi@^ zF+PgtHqDL0;qcVaN)5Xvvag&wj{R2ntG{IzUnWw^ETRmI4WkK8n4Z!RfZBv*5gG#1 zJ63#0gm5_H9b}T0(Z(&5iMAyfDpT!HDDqb46vJwW~kNcFY38LI^aOT(OO4TNw@UFO4^9 zTaz3X0@M&zDwoFDnivAcz-<2B?#QLcvXLjyBwHBFsHE^*6Jci5N(G<25$Z|3oFF79 zt{0&K1d|yAVyOrc=nxShkm0BVg>_OwC(@1Cc@tix3X)1BkVqB;1;KD+Br265;Soxe zN-Rdg!lmdKR&BO2m>DO=e3Pv2Q7rOStUQ7yFovR&D9Sk235nShLs_#a3xG(35HLFq z!%4I2WR9y!uYy(*G?_=}RWxM+M$#-N-#`HpAu80Tmd;G97`! zdI?Mr{87CA|E3RQNrA3j`A_eR9kC7R5?@aPyLmlNgqa;8nw^%VblFu7XWV|ZGAzm7 z-ns8C-3V|CWLx`RUVV5?e=~JLXGNd*gA2VSy}M^liFW=t^rC%5`mI^MKJ^z*yC+9$ zK8y8opgw0i#OZbK-@bIOr+sL3X*0ny?F_5$?^u;L5LsJ%K{s=fe|&Jv>M52PH5+{E zciwC+?Q1wXec-Dn?aE=B+ip?UBV`Bm*T(s!wWZ~plEuod;*9$(j?$d6^(EcKbW!X< z&nIP6%$5Z)#Sc{zYqC;eHfJr#r`xJt{34$f<9D)}sVN9J6SzfMQhTpr?kB2_2ge#4 zPMiznH%5_{H{8XZyCE8Z^qt3(nNamZCKd_dTt1 zybD4vFW|RcM-rOAUY>VP`H4R@hUX>mS_}#FURP5QO}9;kzD&7Mf0X!18#*Cy#s=lb zoGCF8teZgFA$ z+RoI3!Q}^!2oJhss<8vOJLqi_j*V;@D?$qOzS!qAdI~fh>f!$Rlk&;`Vf`4<% z;twaDuf~1bzjhOXb@_XjZ49oi7zcZBs XY(V!OcIS4x{t4>Hc;)f%%(edjvT}hx literal 0 HcmV?d00001 diff --git a/assets/icons/Dolphin/DolphinMafia_119x62.png b/assets/icons/Dolphin/DolphinMafia_119x62.png new file mode 100644 index 0000000000000000000000000000000000000000..1bbbec84ad7c1ddc9c58e5bfdbe577a07fed89f2 GIT binary patch literal 1280 zcmV+b1^@bqP)APj_?zW*z8e~zA_NT6>V$2*g88-oxmD+#wr zA7ivDq4zFwXpGUbeff0!oOYnKS3;$v%{~vwJo*XcVs&~5>b;MLU>YUlN<%G%M#T@o z?+6de6p>?$J_+Ei(SAH=?N5Y}-usxZNff+h6FY5r?c5D6aw$T0L{isj3tk`iVa~a* zTD1@w9g3gu{tlONJ;ZW~wJP`t&F?TN*F$J#5k=X}uGFs#bF~~raP$U%+IB_db5`8(yP3LcPk9^xfs%-Mvp&_ngxYe#3)`2Mbhxjqj2IYqqH( z8IFg~=UN|N|31b@CDv=#;s^ZQQ!Qtok+lRufAEHwZQ%V7a;1bybFSAHXva%V7QvYt z=a-AtT8O$hVl5M}5*)QyRG5sg3+-oZnAZNUL&!g+bD;sguOZAb@n~XqZK#)JEUNEn z&0u?Yof`(OgIl@T#go(r>ll);GL)NP7sIVwoNKwFO1rbA4h{DDsgu~ zMTKOqa-~IAL{!*~V|kKxm2>j%<3~$QIdhLF$OoLblfQXKE$vNDkU^} zQkKJgh98n~Uy7XMvol0!&Rspos4R}Ke^gGD^t_S!n|rpQ+MPHOEyHqza;e-3iS*`P$%4b5wc%6S zA8v%mlw$!&F3w$6irYa$EEl4&q9Jw*t66wBhGyQY{7U(Ag5Z~GSxUqTT4Gm| qhU3k4+UUXAi}V+64(i@1<^BaY>0lxz$?l;50000{@-kk{WNZ}Qvi3fB-FVegOqTM2h3rnSez_LYQtkumcF zV0%-qBRq<1FP~o)Mxu~GHo%XdNG!B$C*a>PEA&Y zMsKh8McrsAnI}k@H1T3~YQqcnBA*H?Cy-<;<+BtE;(N7@%3YCeSrObO>+*lfb$blOH6t5nabt&j;5h0ngD$gv=99OG_5*t-XFYTrcR zI~ynD%+5cVCr|F%6TD8=cwo^Uluq%VQbozoB2W z*pYr5QoL6}0OU6{^T`~^-pxI%7#=}77u4#1R#>5UVwd)scB(ZfQpe9D#(ko2Q7@$u z=@o!CcU1O_BXRRev*I$IBefx^h&`KPyKn+vv=PWEUUKYceR@=(eVbxMzDav_RL*!D z2ekWL1fJjJxqj>x#UA;(!Q+DySrsx_;M_Z-~5WQm1Ipq%JSC>|zkbg`f|hWN4GT~TPv4jFXBIb#$=PaODZK2y6bV*YP)6Fh bnmE4!Wn>(u#J%>f00000NkvXXu0mjfecYgf literal 0 HcmV?d00001 diff --git a/assets/icons/Dolphin/DolphinSuccess_91x55.png b/assets/icons/Dolphin/DolphinSuccess_91x55.png new file mode 100644 index 0000000000000000000000000000000000000000..80caeb203c69add5e5786f576daeb3b018b1c859 GIT binary patch literal 930 zcmV;T16}-yP)a80tk`0v%FYt{QS^;XijtOsVUD?#;+`c<*fIT3&TD?M& z)R06pD<=<1yq5Nwf~f2rMN`RW@xLABnmu1zf@(zLuCU@6K;_KkNePA;OOnm`YL5b? zN2t4s&U7U^vAMgIRkqiKc*{aXbz{Ogp;&7-OLmqEf$vo$7-RLCrSa?mNx6`y!;Df( z6!Bb~-MqCLF2$eY9%M_3j&u!?2c+eqEmo5o!--5QA2=SVY);XUHf@(^tE%uA=^F4| zrp1NhD1IK~*dB?Q}$eEWlXi^hqK5WNV0cpw&j+> z2}`joYeqoo2_oW=Wn?b2tL)VhkwI*PJBi9IMcFu3E*WX;%y99JSV~55t#A0nDaB}EWW!Q%O4%8>eTYkN)$EF$q_;L(K*hAa z2hqnE>{mV&?4AV?IYvM6qu{F96*eI9?jk+0;R}QLx7otB*2Mo*dJDlBjSja7cs(mO zYRx|7yE#C=O?`f{(cwHl_R}0xpMA@nWNxI#8b;jNA{Bje*34|Bd+T}@ zRAnDY!qFg5b3OC9n9-_k4Ou^H=NP>axIysFt&S4Pu7oUAw`Qo>^L_aHKYUj9gKVu+ z{C1^dkwxQ=>|32;6>xX8G)K>z>% literal 0 HcmV?d00001 diff --git a/assets/icons/iButton/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png similarity index 100% rename from assets/icons/iButton/DolphinWait_61x59.png rename to assets/icons/Dolphin/DolphinWait_61x59.png diff --git a/assets/icons/Dolphin/WarningDolphinFlip_45x42.png b/assets/icons/Dolphin/WarningDolphinFlip_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba54afce0249303787fb3d94a78cc167a81a253 GIT binary patch literal 1437 zcmeAS@N?(olHy`uVBq!ia0vp^x+tIX_n~5u`@1BDVmjn}NZ`zM>#8IXksPAt^OIGtXA( z{qFrr3YjUkO5vuy2EGN(sTr9bRYj@6RemAKRoTgwDN6Qs3N{s1Km&49OA-|-a&z*E zttxDlz~)*3*&tzkB?YjOl5ATgh@&EW0~DO|i&7QL^$c~B4Gatv%q{g&Qxc7mjMEa6 zbrg&Yj12V+fyi9f(A>(%*vimS0Sc6W78a$XSp~VcL9GMwY?U%fN(!v>^~=l4^~#O) z@{7{-4J|D#^$m>ljf`}GDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaozEwNPs zIu_!K+yY-;xWReF(69oAntnxMfxe-hfqrf-$ZKHL#U(+h2xnkbT^v$bkg6Y)TAW{6 zlnjiLG-a4(VDRC$2&53`8Y`Fl?$S+VZGS)Lx(C|%6&ddXeXo5l)>e$qx%(B!Jx1#)91#s|KWnyuHfvcmlo299R znSrq(*!kwBrk1WoW~Q!gE(Qk1mP$~)DOkJ?)oY1UuRhQ*`k=T)iffn@Wcz` zz>|M!9x%-p0TcJDoOQE-Iq{~ai(^QH`_*ZU>zWmKTucA|KmWe+?mErbXs(XSlV`YU zyj{3y=hyt?pBvV_R?6ew^D+M8uNu3qtmT_y-D0L)TyCc0dsA%UJkv=5Rzh;RJXOUd zb2IbOTqF$GpKcbJdrqLVzGd1T*X2+9*4{lY#C9V4>K2V9OE2i`>{wIr%jf)?BJJZd zuUTJdQogkH&a9}lvA4LA6npx)qIWK?x%%_%t!Ix}6uLep6h2dsS$*~Ev(@QR+>tKg z{Rx5VCNfp6n;I+CU0M0%6tiXxoBrH_wi@r8w!N3b)U#YQ?77X3J+inwuOh!a;>s!M zv#A>UI^rh96)w2m$Hn}af3o_`pcBH`g5Q$P&bQHDxm}W5pZ!?$r-&Js9`F?M>z7Y^ zY$~^~+n{bs1@D!OSqrT8+&A~yr*>6)e(dx~*IxLhbfzrnPjRGZ^@Z|QqP|R zTf{vU*uQ$uw_jB|PcZzAILGiaHBNU)cG13w&3AXCSuJ$kaL2{#H7|p$nykgUeK8Iy zEju&pn3Sd&9GD@pm18L<>(i^uuF=;{N93ug){1WBn;h+#e&AT0TJLc_#>&d$ZvF9E zzJWrXr!3AsoxANwWh&>=#qMTJV%skmUV2;@?^2MR`M%R;)nyx{?bH9g`?l#1bH%|O Uzg^}hzX270p00i_>zopr0LnWHWB>pF literal 0 HcmV?d00001 diff --git a/assets/icons/Interface/WarningDolphin_45x42.png b/assets/icons/Dolphin/WarningDolphin_45x42.png similarity index 100% rename from assets/icons/Interface/WarningDolphin_45x42.png rename to assets/icons/Dolphin/WarningDolphin_45x42.png diff --git a/assets/icons/NFC/check_big_20x17.png b/assets/icons/NFC/check_big_20x17.png new file mode 100644 index 0000000000000000000000000000000000000000..c74e5b1c319b11eba22a03af828c3c8f420d5dc2 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz2!3HGny7cS=Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFKz@v;i(^OysVFTakM@=&i*yNbL<;(xkakDjK(c#1^+$-6*mTaD`o9?_&apC_PRX0}l sT&QeQUT;1nviI)7rW4P9C6@nS*ssp5V7`gf1!xb0r>mdKI;Vst04MZD2mk;8 literal 0 HcmV?d00001 diff --git a/assets/icons/RFID/RFIDDolphinSuccess_108x57.png b/assets/icons/RFID/RFIDDolphinSuccess_108x57.png deleted file mode 100644 index 34199910945376f054daa0c1738d7e64dc410421..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2681 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 diff --git a/assets/icons/iButton/DolphinNice_96x59.png b/assets/icons/iButton/DolphinNice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 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 Date: Tue, 2 Jan 2024 14:10:19 +0300 Subject: [PATCH 17/29] replace icon --- applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c b/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c index 08636e0361..e791e88ba2 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c @@ -55,8 +55,8 @@ void lfrfid_scene_clear_t5577_on_enter(void* context) { lfrfid_clear_t5577_password_and_config_to_EM(app); notification_message(app->notifications, &sequence_success); - popup_set_header(popup, "Done!", 94, 10, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 7, &I_RFIDDolphinSuccess_108x57); + popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); From 7eeb60e17ecd31b964cdc169f60ae314c6e9831d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 3 Jan 2024 22:00:56 +0900 Subject: [PATCH 18/29] Desktop: fix rpc unlock on pin input screen (#3334) --- applications/services/desktop/desktop.c | 2 +- applications/services/desktop/scenes/desktop_scene_locked.c | 1 + applications/services/desktop/scenes/desktop_scene_pin_input.c | 1 + applications/services/desktop/views/desktop_events.h | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 547883e9a7..7a49dd51e3 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -414,7 +414,7 @@ bool desktop_api_is_locked(Desktop* instance) { void desktop_api_unlock(Desktop* instance) { furi_assert(instance); - view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); + view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalApiUnlock); } FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) { diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index 034eedb8ac..c4cd5748f3 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -83,6 +83,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopLockedEventUnlocked: + case DesktopGlobalApiUnlock: desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index 0e248def60..a21c59e380 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -126,6 +126,7 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case DesktopPinInputEventUnlocked: + case DesktopGlobalApiUnlock: desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 5dc51fd85c..bce9c09d1a 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -50,4 +50,5 @@ typedef enum { DesktopGlobalBeforeAppStarted, DesktopGlobalAfterAppFinished, DesktopGlobalAutoLock, + DesktopGlobalApiUnlock, } DesktopEvent; From 941652ec573c21a9e4d914b3c9dbb1372e9ff75d Mon Sep 17 00:00:00 2001 From: Methodius Date: Fri, 5 Jan 2024 21:23:12 +0900 Subject: [PATCH 19/29] NFC App: Generate MF Classic with custom UID added --- applications/main/nfc/scenes/nfc_scene_set_type.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index e336600807..ff82587df3 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -53,6 +53,15 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; + } else if( + (event.event == NfcDataGeneratorTypeMfClassic1k_4b) || + (event.event == NfcDataGeneratorTypeMfClassic1k_7b) || + (event.event == NfcDataGeneratorTypeMfClassic4k_4b) || + (event.event == NfcDataGeneratorTypeMfClassic4k_7b) || + (event.event == NfcDataGeneratorTypeMfClassicMini)) { + nfc_data_generator_fill_data(event.event, instance->nfc_device); + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } else { nfc_data_generator_fill_data(event.event, instance->nfc_device); scene_manager_set_scene_state( From 00d9c605157aabf2bc0d7105777e0036483eae5d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:24:09 +0300 Subject: [PATCH 20/29] subghz use long press to exit transmitter [ci skip] to avoid unwanted 2 buttons hold condition holding arrow button and exit causes default button change, which is stays as hidden feature but this change makes it harder to call it accidentally --- applications/main/subghz/views/transmitter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 16a2ea1103..f75bbc6611 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -127,7 +127,8 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { SubGhzViewTransmitter* subghz_transmitter = context; bool can_be_sent = false; - if(event->key == InputKeyBack && event->type == InputTypeShort) { + if(event->key == InputKeyBack && event->type == InputTypeLong) { + // Reset view model with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, From d1df26cc830c014b7a8dcabe3a98f6ad08e5bc78 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 6 Jan 2024 00:56:50 +0900 Subject: [PATCH 21/29] NFC: Add manually MF Classic UID desync bug fixed --- .../main/nfc/scenes/nfc_scene_set_uid.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index df8a4dc72c..7376ce0bc1 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -2,6 +2,29 @@ #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +// Sync UID from #UID to block 0 data +void mfclassic_sync_uid(NfcDevice* instance) { + size_t uid_len; + const uint8_t* uid = nfc_device_get_uid(instance, &uid_len); + + MfClassicData* mfc_data = (MfClassicData*)nfc_device_get_data(instance, NfcProtocolMfClassic); + uint8_t* block = mfc_data->block[0].data; + + // Sync UID + for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { + block[i] = uid[i]; + } + + if(uid_len == 4) { + // Calculate BCC + block[uid_len] = 0; + + for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { + block[uid_len] ^= block[i]; + } + } +} + static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { NfcApp* instance = context; // Retrieve previously saved UID length @@ -45,6 +68,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { consumed = true; } } else { + if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolMfClassic) + mfclassic_sync_uid(instance->nfc_device); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; } From d2549b3b1a7b81c3c7b56520d4bdd682d9c186b4 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 6 Jan 2024 03:18:32 +0900 Subject: [PATCH 22/29] Code cleanup --- applications/main/nfc/application.fam | 18 ++--- .../nfc/plugins/supported_cards/washcity.c | 17 ++-- .../main/nfc/resources/nfc/Demo_WC_20E.nfc | 77 ------------------- 3 files changed, 14 insertions(+), 98 deletions(-) delete mode 100755 applications/main/nfc/resources/nfc/Demo_WC_20E.nfc diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 871471b69c..fec3b6c768 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -101,15 +101,6 @@ App( sources=["plugins/supported_cards/metromoney.c"], ) -App( - appid="washcity_parser", - apptype=FlipperAppType.PLUGIN, - entry_point="washcity_plugin_ep", - targets=["f7"], - requires=["nfc"], - sources=["plugins/supported_cards/washcity.c"], -) - App( appid="kazan_parser", apptype=FlipperAppType.PLUGIN, @@ -164,6 +155,15 @@ App( sources=["plugins/supported_cards/hid.c"], ) +App( + appid="washcity_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="washcity_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/washcity.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index a0edeef6ad..93b0690931 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -26,7 +26,6 @@ #include #include #include -#include #define TAG "WashCity" @@ -151,27 +150,21 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); - uint32_t balance_eur = balance / 100; + uint32_t balance_usd = balance / 100; uint8_t balance_cents = balance % 100; size_t uid_len = 0; const uint8_t* uid = mf_classic_get_uid(data, &uid_len); // Card Number is printed in HEX (equal to UID) - char card_number[2 * uid_len + 1]; - - for(size_t i = 0; i < uid_len; ++i) { - card_number[2 * i] = "0123456789ABCDEF"[uid[i] >> 4]; - card_number[2 * i + 1] = "0123456789ABCDEF"[uid[i] & 0xF]; - } - - card_number[2 * uid_len] = '\0'; + uint64_t card_number = nfc_util_bytes2num(uid, uid_len); furi_string_printf( parsed_data, - "\e#WashCity\nCard number: %s\nBalance: %lu.%02u EUR", + "\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u USD", + uid_len * 2, card_number, - balance_eur, + balance_usd, balance_cents); parsed = true; } while(false); diff --git a/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc b/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc deleted file mode 100755 index c8c9cd0058..0000000000 --- a/applications/main/nfc/resources/nfc/Demo_WC_20E.nfc +++ /dev/null @@ -1,77 +0,0 @@ -Filetype: Flipper NFC device -Version: 3 -# Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693 -Device type: Mifare Classic -# UID is common for all formats -UID: 96 00 CA FE -# ISO14443 specific fields -ATQA: 00 04 -SAK: 08 -# Mifare Classic specific data -Mifare Classic type: 1K -Data format version: 2 -# Mifare Classic blocks, '??' means unknown data -Block 0: 96 00 CA FE A2 08 04 00 01 B4 B9 86 13 27 F8 1D -Block 1: FF 00 00 02 00 02 00 02 00 02 00 02 00 02 00 02 -Block 2: 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 3: A0 A1 A2 A3 A4 A5 78 77 88 81 01 01 55 01 01 00 -Block 4: 02 E4 07 D0 80 01 00 00 00 00 00 00 00 00 00 01 -Block 5: 00 00 00 00 00 00 00 00 1B 93 CD 00 00 00 00 FF -Block 6: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 7: C7 8A 3D 0E 1B CD FF 07 80 69 FF FF FF FF FF FF -Block 8: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 9: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 10: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 11: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 12: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 14: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 15: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 16: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 17: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 18: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 19: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 20: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 21: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 22: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 23: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 24: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 25: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 26: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 27: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 28: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 29: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 30: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 31: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 32: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -Block 33: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF -Block 34: 00 00 00 00 00 00 00 00 00 00 03 00 00 00 00 01 -Block 35: C7 8A 3D 0E 00 00 FF 07 80 69 FF FF FF FF FF FF -Block 36: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 37: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 38: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 39: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 41: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 42: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 43: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 44: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 45: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 46: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 47: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 48: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 49: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 51: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 52: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 53: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 54: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 55: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 56: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 57: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 58: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 59: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF -Block 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 61: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 62: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -Block 63: 01 01 55 01 01 00 FF 07 80 69 FF FF FF FF FF FF From f6a363e7d217c6ae87e10c7e9ef4a2e2175d09d9 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 6 Jan 2024 04:36:06 +0900 Subject: [PATCH 23/29] Fix MyKey production date parsing by augustozanellato (https://github.com/flipperdevices/flipperzero-firmware/pull/3332/files) --- .../main/nfc/plugins/supported_cards/mykey.c | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/mykey.c b/applications/main/nfc/plugins/supported_cards/mykey.c index a0e206f9c8..340557241a 100644 --- a/applications/main/nfc/plugins/supported_cards/mykey.c +++ b/applications/main/nfc/plugins/supported_cards/mykey.c @@ -16,6 +16,35 @@ static bool mykey_has_lockid(const St25tbData* data) { return (data->blocks[5] & 0xFF) == 0x7F; } +static bool check_invalid_low_nibble(uint8_t value) { + uint8_t value_lo = value & 0xF; + return value_lo >= 0xA; +} + +static bool mykey_get_production_date( + const St25tbData* data, + uint16_t* year_ptr, + uint8_t* month_ptr, + uint8_t* day_ptr) { + uint32_t date_block = data->blocks[8]; + uint8_t year = date_block >> 16 & 0xFF; + uint8_t month = date_block >> 8 & 0xFF; + uint8_t day = date_block & 0xFF; + // dates are coded in a peculiar way, the hexadecimal value should in fact be interpreted as a decimal value + // so anything in range A-F is invalid. + if(day > 0x31 || month > 0x12 || day == 0 || month == 0 || year == 0) { + return false; + } + if(check_invalid_low_nibble(day) || check_invalid_low_nibble(month) || + check_invalid_low_nibble(year) || check_invalid_low_nibble(year >> 4)) { + return false; + } + *year_ptr = year + 0x2000; + *month_ptr = month; + *day_ptr = day; + return true; +} + static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); furi_assert(parsed_data); @@ -34,7 +63,10 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { } } - if((data->blocks[8] >> 16 & 0xFF) > 0x31 || (data->blocks[8] >> 8 & 0xFF) > 0x12) { + uint16_t mfg_year; + uint8_t mfg_month, mfg_day; + + if(!mykey_get_production_date(data, &mfg_year, &mfg_month, &mfg_day)) { FURI_LOG_D(TAG, "bad mfg date"); return false; } @@ -56,13 +88,8 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_cat_printf(parsed_data, "Blank: %s\n", is_blank ? "yes" : "no"); furi_string_cat_printf(parsed_data, "LockID: %s\n", mykey_has_lockid(data) ? "maybe" : "no"); - uint32_t block8 = data->blocks[8]; furi_string_cat_printf( - parsed_data, - "Prod. date: %02lX/%02lX/%04lX", - block8 >> 16 & 0xFF, - block8 >> 8 & 0xFF, - 0x2000 + (block8 & 0xFF)); + parsed_data, "Prod. date: %02X/%02X/%04X", mfg_day, mfg_month, mfg_year); if(!is_blank) { furi_string_cat_printf( @@ -127,4 +154,4 @@ static const FlipperAppPluginDescriptor mykey_plugin_descriptor = { /* Plugin entry point - must return a pointer to const descriptor */ const FlipperAppPluginDescriptor* mykey_plugin_ep() { return &mykey_plugin_descriptor; -} +} \ No newline at end of file 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 24/29] 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 0000000000..c27ad9c816 --- /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 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 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 zcmaJ@c{r49`+jVNvSba(81Yt?8Cx+K+gL`~8rw)>jKN@*W(G4tN=nI=Eo(wa4Q%8vkx{u?zYHw>PBq%Eg0DzDc z(hS9!#kL=Q9?lyh8i4}IOAwh8Gn{vj+=2WcHX}wv6 z{-S5$q3oHN^-t@S6WJ3RZH#u2$UR~zN#ptcfIceP0M?_BV27-0s*2>6L=N(TM8}(7 z`|{NTz#I>Q9zlC#w88a|1aJf7E{y|X4MV@8D(qEU08kPz2o{^z#g&Kx8Z{gnC4k1g zz$1sJ-hx0100c6^Ou@i?Az=E4l_4L{Q=Hr{4fN#iE9M8{xPXj4 zwXcCZrZHH9x3-ik()GEPC3j>M9}pamP82cr1R^s`)mi|M9yfs4FW$-nvgXNycGe6Q zdyu19NG_nZIkh$YM5nd{EA_o>$im#H=N(jFoW#zri2 zzHaq}&H-mLjWbGW3!*m9Vu-<|sQ8IyUQrUuD^Y zZ5kLaP)TNrO{v3TljpVO71A~Zl0$?5=4HED+vhu;fXTOrK ztd-`*>@YLleW2Dr)O5#aVKIBW;(Net{L&fmykHDc=SE~9Xfj6PB)GnjQpjCw>YwC} zR9aA{Na)9%HeO5YYXoUs+qhO~shM)&$w{7%+(E`K?kUJ#dz(k?py`OXN2cWmbjX(N zhetloFX}k)Erp$Yef^5L=T)?gtyCsf!S5mvbxHH}AK>JBc4f+;Vyks@FWBQm zv;|XTR&l>#uJV~bgvC9Qkq3mEZj9OrDk>*xS?#h4K=vWk3mpm#J4Nx?)+$qpgr={f z{7)j8p!B5jM3F?h8|zJPM$08&^)bWN0{I6}g(+gkb#X>xymxMCnP%kOKiOKG`;q^C z4D8k^D?(ndJ;dQkvA9l9rgCeR6r#CMy`bxTCf*mn;s=?eRS0~E+HaozKD{&G+s?^} z$*3P8yM-F2nbx$W4+H`tb7MFv+BM zVyUoH=hTSQiTjRDR41b@#{FH651d3EoN*4nYvJ_Nexz97qtt`0VtJ>R#YalpP$8%U z`}UI_1=Sv#7uT>tPcBDWD+@7pXTL<&4 z%LPNuSvw%8_kEZ?Nj^E_XIr_1-##9k)Bl`(yiKu9sO_9OkGhfi<8J>FpOT1@qrIWM z)xBOblo_d+sa|#vImb9hEoTWvfUN`xR2-=|SrJ{)7u5dU@B?;=F)6V0Zb^9ZONZqW z;YY!e^mleQyF=k9REPgaqD-Ks9(JxJ5&JFRCZ5$XcWLO}o@T#_q&mNX4y%GcSSqtu zd`EQY(uO`v(mpSy&R1N2fC0t}uhmyrS6DwQhyL`(& zG5PLev}0iuT2M=HAh~j?a7gD(ab5A7Nf%!^-`mujMP2E;ClZ^*(u32b9SB9&iio#D zn^VVRXDd3NeOM~UdYRQ<@|p1QOAEX{{K2}7MwVQY`x`jhf8pan$LN{4B@!7wn-ktw}#xeLT_EEzFQ3*fLAL; zbVp=F?A*v*KepDqneek_h_N6wZ_DS&^@?kZtLlR6g{M3LJPN!Symxl$^2PDJ+yU8b zC~3M|K*&{rl1!?VUXWYGYWMr9Wp+ruL) z{+L0_z!;VSUM53&HC*D*VXgZb-%pk~(9Y6U)Vi6YuIs*4@$(7A*Iyj#^M6hW_GS79 zq5`qgS*%Fbebxo~m7nJG>0&hT0|GNwN9%g(;8#be+!KMB+S#L-j%hS(=~#dM3+eI6 zw&vUr16N(w#4x?+n_}rtjK-osruLA%c4I|E8+q}COIgu&=GFOe`6nNjvyL0w7|(G| zUDo?@EF7`sciGM&=&iPZ9ZHpvBy;11(xQ#CS@&0F`{%Qt)%8=dQ?d(CLin^Y)lbm! zgXMNUs;bFCql|IFJGta5?^Z^YR;i19l7Z3I9R+2mQhQ-3YsfuSy4zkiIty8aJoQm~ zz-R0Gs?x5DQejnzkL+2Gp7yZluJeQ78uOP@O0f>oAsU+Qs0wd7ey%gT*{}IY+NS+5 z8s)U$&*)!>M@4nsxr0!>=%SNaoYK@xEd6on1y&N1>g~k#Pw#SbK7Uv`)q_c9-Yfn2 z$bvOK>|*QD6}H46^!9!|UjA-o3OQ9cMP#nH);v63nqiQIhLn4AaU_*dHP zQ2(X)*0R=jtvtFI-5Ix*=ghu^+eZqPLvzl%H#={ZJSeaJtkT5nouAA$Ik-3Fq#d+qrDcp7N)W0{b7<)I1R& zppL}tN5aTsS&^jPteMP^XXI0dgjgRTXXGub%YQ|%HAk>P4Y~;~xp_GU;q$Ab7n4Vdyo+*kY>nU_ zGx`}T)*BfC?kC-=d=c%rM$)ud>vE5krp2!l3GQ>1E|a6_gjoA_Sa-zzYeJtgQrJupeGtwb~ zv)29Yp$YVd8`Zs=-*>Kwd_P~d^%z%682ss3>)HOsRfH`pa3yyu<=2NRL!Fi_mR(8~ zN^uD}3JP*UvQ-P-ZOKDLPm09b-$gk8VoXsVObl!eub*f~Z}iOVT8(Y5DP-hN@s|B!#~QYw=)K*F;Y8Th24v;Z;(DaM z@*d7#r3}p+O>-dm&_Xa29AM&2^1^|v2pC@+3WxD#oNdAx0055)-Vseh+gQV}B!UKJ z+ed>=Aal?FU|>WiW3T}@8psRhizmXt?3XoQ5Z)UOcG0zg+K>@AKRhy&f^!J9b;O1S zVD-JhMus2*I*da=z|k-uIw6oqh0)>QKY3xC^|l!T2L0(m3xI?F5{0(02O&rl9O$Tq zraBf1g@TUiYj|V4Fjy}yHINomOA`XsfoSTeL!mHjeVC38=&Lss+)~Qs;Q6QyD}WhOSPeD*a|K!%?vmJeh_k z5kcFG7%x%~4G!i={VN9o`5#&$_3v}yoEU_TAwx7ZpxZh9cC@ki|6K`$f4r$Q6z;!z z|CN~P$ROh&C>)g(M8R?@=cBY8iVQ=&_Npv z7Ej!^9QqStV*|4yQfU|>7H4G!2Xja?@OW<+RM*_}sEH{;SI0tMQ_~z_s%xQZenj8Y z#MBIGc2r;QH`a`V4IPoKl5_wQQ%!g~LUmcOwk{}T)0h=FX^_W#uSw~5n0+sl7im$Uh&`Ef)}$5S}1 zy?{b{ajwM@~ 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 zcmaJ@c{r4N`+r3CEm@Lu#*i&$%-EXASZ2m<2%{NkG0Yf~#*8sFmJ*e%IwWO{sO(Ec zjfApgNr;l2Y)KB@EO8Rvao*E;e}DXXpX+&^@ArFO_vdqe?&Z0zC-#V=wS?$iQ2+oW zY;CYEyj5iT5$5N;d!4?40YKD}hQS=M#b7{87Q=^jh5`UV0~xMVyz7iSYIS58Z66bU z%bwvPCk%2yUkjH_P}f!wk+zFb$?lhPuG?j4DWKGn6~iAF7k*vNSx5Y;XrIue%DuSD z_hYWUULOm+@Asj4^;7%i(_Yi*;-!r8PN7<1@gy64XTxyu0`&e}A1^mIHjPa}%p*kA zn1Hl!IawueLzNF$3o|h}2(A@+0q_OA6B7n%ap|>s`=Ym`zMxZ&^MzmGt7Rt~vKJ1Q z1Hpin=S1B>;G~d3#L&M|1&Cjf;M%pvu`sfzFj z1F4ToZvY@GL5`R0(ne5+WNAl-Q5;wDlq z3x?A-?;V&I@I5J(b$0cdPnneYQy^<*fUv~eu8n2(jmrN1smaMcyGFDJ={4cPCbj-l zEn(x#pJ66HR#!g07*~scpNOy)So>K2X4xTUU*}DcD_%pN;;nyFh;98)eg|%}^{OOl z%T74U1jJ#}t}nrJz_I9?TCWatZ;{7Gb=LV!M-72Tr%m}n6Lj-Wc=La=*N`T%YsXgs zV6lo(_g+(&Kiv27SSM#|!ED1i>i`h$V|z0I08V1nAo$niX3fF?fX#}~eq^DvT(?K3 zR&Zb4&Y?Q7AD%{6&}xnKXlb-4IeZ_>Q>*wAS~IHsk+QZY^u4*VL9MfIR3cLnQt$Rm z62+AIP7=&h~e|PN>q&#R!EIpQ>n8Nkh!J?YK@U~2HPhX+Q3|{ z;z4dU%8Mx04n*{EtLF)aTLAc_A5qoTuv-yj&Zzg|PcfDG#(S?=-4lCDX2a6r<+IY? zvYzZkT{p^}ep}=#H4tx#Y1XU#yhljC@r)j%sR8}?kd8>AciUrdv3OC_-bY7^`Kw}A zygMIr1Y{yCYekF%IA{=Qzl9Caf#}$0lMmXbX0U5O#8`y?igUdNI5FS;iTd+he>U#% zg2SSTHae;wWa4*2r9)#djmBy+u^6~U<&7P-k00Q>WxB1p{asXNbPCc9Z1$=qwhoZ} z%7hTNbU+7NA}2E@8z%K9l_pgdJw!9S%mW^*xsGePygqHGI3+!0FeOMyfm^uUPjea0 z&&KaEj6a4h$>zE|bdJv7ZE!XX(SBLp);_1?-tBjLeHDCHX%9cMpYIyJz27nUEup(@ z#`<&eXZ~f5xI~oP<>nZwregXYp*>VZ&Yp)U4!Mf&t|>O-^^9S&DbuM^sSG!wHdp(+ zT*7P7+jh6rZ!2j-@dbssg(HPxZcA=$`1pd8t`|zJ-1J>13Pj!~6}c5=9GP`ha-|j= z&W|pn<}>hS55n9xVg=nB92%T351g|epPHy{0*QGmmIvvm_(>E+osBSTRDaywfBu|y zRmz5P)iqRMK{f)TZ>LWvcUijSVnU}m2c6CH{L2Fz~Dc8WE5=J@h zSD2KXL@cr?axSu-tuZQ{%ge~Ev8-}mkC3!zw$nJSVNH$i*qJfy+V47?Cz>aZLm^j6 zA%%W9O4(Id&P)Hi`IO8TC&M!x7M|3%dt1+Mv^dNO;}k)CI;{%zh9(e7 zdLLEfa0*vR3ks&+Oj&m)Oeai?N8lswr`{OXR-YeYsz5~9rFm@&k?U9e#1O9mr++tALh9Be#b={ zZCuFBKN6}9gVkQ?=jcpTUePGHQSBh%Fr1FelutVcqQgRzYnoX&5F5+PDNgr9qOGs;Y5VGk3J=RkIGOom5aSvDm$o< zEO)U_b0}y^DVp*6W$MtaCj~`~mE=yJZl9S?Bf6O$l1YWhpOPj0CHe=RNQ@qRGPm;0 zauAx_t~pqBnTx5s|I*}HH6^dLqy4ZM{sDd&{~d2M-#z@4)Vt>2HLny}{mtNyo8%lTGBfGM2RCkV6K_Jn}0({Rg&9V`MyWF8-;g? z|8Q{DTC(}K7n>Oi99;<`3Af+xG>xk=vB8rwt0JST`z4SA=dOnqj|si|?VK`I8G0I> zwwPv>?wYpl;pOq%>5XaEhc6=`Kdc9Tle%MI;vQ_bgm0w{%v^exNL}o_o^dkea8VKC3fInZ_N%%QeAY<+nccWFk<*HA^9k)mN)4qw>RHERBth zwyJ)P#(YV&Q}wB3^Er!t%y4v%naAc(-@?$v)3uzerLH0CRl&&1otp_O@lu$b@u~4` zQ4&$JnTJdfh;cL4#>|gAOeeWhJyT)x-ey~=f;=>At!K8kqbsE=J9#lV@g@Cy&c>J8 zS;dEgP4!LtU$h44!%i+AU7xGt3~`hf?vF}2O`Zo`)ZFs@^YM!7+r0He#l*xd0sfSw z9}9-JF7f^=71@?VwkyMj%^|TUfCZW1MFH8;NmPmpg+vYxXr-6{0KX;;Ph=Bu4oGhX z9YWgnfdtW+JTw59m<2IO-hLD|$csXy`J=!KRWHFH8W{y97~=GBObo@BW)s4qxQ005 zy+i!G5oEBLDaa%U$s?ds*d$O8{fvJgG6)6!ixsqKLR7APj>= z0U1MJy54$vdLUy2ghD34z4U!Z-Z~(-9vlXR@or;Xm@yKrkAxvWe_vo;Ko;2t>4LTT zI~?zX0{gPrOe7S_;cy@veF%d^g~AXB1XK?Wg~N4u9=d_S{%lf^u79BFPX;U{(3?eL zvS|!|&^9BC+f`KWG(Vj?jt3W?2N;TeoGKMQ%pm%(NP`ZAaxxIP31 z(!`OxY5v<5t-l~R9MaZ5kWKRUrr2UpU>*sCMk6CJ2$%uJ=#V~4&&mJ>v&33pF^AAt zI2vON*M}kC#y_!GhWA-I#h?8XOa3p`;Fs9#fuJ*ak+BpO?Hq+{#bVGwe`SrN{aOp` zmwbO?$-mYD|0Nd669e7u?f>cZPZMu|wzvNbFYoZr_*49OGtc4;_gwK*74O3kIpTn~ zjj{=6BfNKE{3D{aXVoTAUm;Mb1;5j7# 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 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 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 0000000000..bf7c399142 --- /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 0000000000..ccbbb02d72 --- /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 0000000000..961faec529 --- /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 0000000000..1ce0285b2d --- /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 0000000000..7127713643 --- /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 0000000000..7d0e125d77 --- /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 0000000000..84bfed4ce4 --- /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 0000000000..849c511d94 --- /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 0000000000..4aa51dc173 --- /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 0000000000..3ae7c81454 --- /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 0000000000..d9fb2fd88a --- /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 0000000000..d85affc433 --- /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 0000000000..d72847baa7 --- /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 0000000000..09c14c6688 --- /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 0000000000..025a863852 --- /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 0000000000..229f7299a5 --- /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 0000000000..52dedc9886 --- /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 0000000000..bd4788b83a --- /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 0000000000..d9bf54df9e --- /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 0000000000..86e9f766f0 --- /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 0000000000..44883edd2b --- /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 0000000000..d84a394f4e --- /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 0000000000..b273ab74d3 --- /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 0000000000..6965c13318 --- /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 0000000000..5604962ee8 --- /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 0000000000..b05426e9d0 --- /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 0000000000..185f75578c --- /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; +} From 0d68fb409ca71476fc28b8ea974fd6970a1e0c54 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Jan 2024 01:47:05 +0300 Subject: [PATCH 25/29] update changelog --- CHANGELOG.md | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b54a4be4..f7b643ed34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ **This release has some unresolved issues, if any of those affects your daily usage, stay at 065 release or wait for next releases:**
**Issues from this list will be fixed in next releases** ### Known NFC app regressions and issues: -- Mifare Classic with custom UID add manually option was temporarily removed (Unleashed) - Mifare Mini clones reading is broken (original mini working fine) (OFW) - Mifare Classic dict attack fast skip (multiple presses on OK button) causes glitches/incorrect reading (OFW) - EMV simple data parser was removed with protocol with refactoring (OFW) @@ -14,30 +13,23 @@ - Also in app **Enhanced Sub-GHz Chat** - NFC part was temporarily removed to make app usable, NFC part of the app requires remaking it with new nfc stack
**API was updated to v50.x** ## New changes -* IR: Updated infrared assets (by @amec0e | PR #677) -* NFC: Fix Saflok edge case 0.5% of UIDs got wrong result (by @noproto | PR #668) -* NFC: Zolotaya Korona transport card parser added (by @Leptopt1los) -* NFC: Parsers cleanup for new api (by @Leptopt1los) -* SubGHz: Temp fix for subghz keyboard lock display issue (furi_timer is not working properly) -* SubGHz: Added new option to delete old signals on full memory -* SubGHz: Faac rc/xt add manually (unverified) -* SubGHz: Better subghz history element removal (by @Willy-JL) -* SubGHz: Fix key display newline issue in came atomo +* NFC: Added plugin to read WashCity card balance (by @yaba | PR #683) +* NFC: Add manually MF Classic with custom UID (by @Leptopt1los | PR #690) +* NFC: Fix MyKey production date parsing by [@augustozanellato](https://github.com/flipperdevices/flipperzero-firmware/pull/3332/files) +* Apps: Move hid and snake apps into main repo (will be included in `c` builds) +* Docs: Remove weird newline in applications/ReadMe.md (by @Eczbek | PR #688) +* SubGHz: Proper fix for subghz keyboard lock display issue (thanks @Willy-JL) +* SubGHz: Use long press to exit transmitter (to avoid unwanted 2 buttons hold condition, holding arrow button and exit causes default button change, which is stays as hidden feature, but this change makes it harder to call it accidentally) * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) -* OFW: USART Bridge: added support for software control of DE/RE pins -* OFW: ufbt: changed toolchain environment invocation; updated .gitignore for app template -* OFW: Keys Dict: fix PVS warnings -* OFW: NfcDict Refactoring -* OFW: Add AC's Carrier 42QG5A580SC and AUX YKR-H/006E -* OFW: NFC Plugins loading rework -* OFW: MFC emulation fix -* OFW: nfc_util: little endian bytes2num functions added -* OFW: Add MyKey parser -* OFW: Update CLI MOTD -* OFW: NFC NTAG and ISO14443-3b reading fix -* OFW: FuriHal: RTC register reset API. New factory reset routine that wipes all RTC backup registers content. -* OFW: FuriHal: various GPIO improvements -* OFW: SubGhz: changed the name of the button when sending RAW to SubGHz +* OFW: Desktop: fix rpc unlock on pin input screen +* OFW: UI refactor +* OFW: MFC emulation fixes +* OFW: Scripts: fix incorrect handling of storage stress test count option +* OFW: Add Samsung AC remotes DB93 and AR-EH04 +* OFW: Update mf_classic_dict.nfc +* OFW: Nfc: HID MFC Plugin +* OFW: RPC: reverse input +* Update slideshow pictures by @Svaarich ---- From 4e068ba59316201bfaeea29eb708def7905b113f Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 6 Jan 2024 15:38:08 +0900 Subject: [PATCH 26/29] NFC: Set ATQA scene bit numbering changed --- applications/main/nfc/scenes/nfc_scene_set_atqa.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_set_atqa.c b/applications/main/nfc/scenes/nfc_scene_set_atqa.c index 17a07b8b53..f4bac7f1fb 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_atqa.c +++ b/applications/main/nfc/scenes/nfc_scene_set_atqa.c @@ -4,7 +4,8 @@ static void nfc_scene_set_atqa_byte_input_changed_callback(void* context) { NfcApp* instance = context; - iso14443_3a_set_atqa(instance->iso14443_3a_edit_data, instance->byte_input_store); + uint8_t atqa_msb[2] = {instance->byte_input_store[1], instance->byte_input_store[0]}; + iso14443_3a_set_atqa(instance->iso14443_3a_edit_data, atqa_msb); } void nfc_scene_set_atqa_on_enter(void* context) { From 7de861bb4ccec9c016ef996db4753a550835fc90 Mon Sep 17 00:00:00 2001 From: Methodius Date: Sat, 6 Jan 2024 18:47:35 +0900 Subject: [PATCH 27/29] NFC: Skip system dict bug fixed --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 328e39132f..22727af122 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -173,6 +173,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + instance->nfc_dict_context.is_card_present = true; } static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) { From 7af56e871774c3661271d40c2d972b72664eefc8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:39:24 +0300 Subject: [PATCH 28/29] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b643ed34..563e0c51ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ **Issues from this list will be fixed in next releases** ### Known NFC app regressions and issues: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- Mifare Classic dict attack fast skip (multiple presses on OK button) causes glitches/incorrect reading (OFW) - EMV simple data parser was removed with protocol with refactoring (OFW) - Option to unlock Slix-L (NFC V) with preset or custom password was removed with refactoring (OFW) - NFC CLI was removed with refactoring (OFW) @@ -13,6 +12,8 @@ - Also in app **Enhanced Sub-GHz Chat** - NFC part was temporarily removed to make app usable, NFC part of the app requires remaking it with new nfc stack
**API was updated to v50.x** ## New changes +* NFC: Skip system dict bug fixed (by @Leptopt1los) +* NFC: Set ATQA scene bit numbering changed (by @Leptopt1los) * NFC: Added plugin to read WashCity card balance (by @yaba | PR #683) * NFC: Add manually MF Classic with custom UID (by @Leptopt1los | PR #690) * NFC: Fix MyKey production date parsing by [@augustozanellato](https://github.com/flipperdevices/flipperzero-firmware/pull/3332/files) From 4a47644e64c60cbb3cc60c1079795574f81199bf Mon Sep 17 00:00:00 2001 From: Methodius Date: Sun, 7 Jan 2024 03:09:47 +0900 Subject: [PATCH 29/29] update ReadMe.md --- ReadMe.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 18fcc7c060..6b7c8c82a8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -79,7 +79,8 @@ - **NFC/RFID/iButton** * LFRFID/iButton Fuzzer plugins * Extra Mifare Classic keys - * `Add manually` -> Mifare Classic with custom UID + * NFC `Add manually` -> Mifare Classic with custom UID + * NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) * Picopass/iClass plugin (now with emulation support!) included in releases - **Quality of life & other features** - Customizable Flipper name **Update! Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL) @@ -105,20 +106,7 @@ Keeloq [Not ALL systems supported for decode or emulation!] - [Supported manufac Encoders or sending made by @xMasterX: - Nero Radio 57bit (+ 56bit encoder improvements) - CAME 12bit/24bit encoder fixes (Fixes now merged in OFW) -- Keeloq: HCS101 -- Keeloq: AN-Motors -- Keeloq: JCM Tech -- Keeloq: MHouse -- Keeloq: Nice Smilo -- Keeloq: DTM Neo -- Keeloq: FAAC RC,XT -- Keeloq: Mutancode -- Keeloq: Normstahl -- Keeloq: Beninca + Allmatic -- Keeloq: Stilmatic -- Keeloq: CAME Space -- Keeloq: Aprimatic (model TR and similar) -- Keeloq: Centurion Nova (thanks Carlos !) +- Keeloq: HCS101, AN-Motors, JCM Tech, MHouse, Nice Smilo, DTM Neo, FAAC RC,XT, Mutancode, Normstahl, Beninca + Allmatic, Stilmatic, CAME Space, Aprimatic (model TR and similar), Centurion Nova (thanks Carlos !) Encoders or sending made by @Eng1n33r(first implementation in Q2 2022) & @xMasterX (current version): - CAME Atomo -> Update! check out new [instructions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)