From b12d43b76516d061e96f5b43bca4f98f572a3504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sat, 11 May 2024 00:01:33 +0900 Subject: [PATCH 01/48] Desktop: fix crash on autolock after restart in locked state (#3643) * Desktop: fix crash on autolock after restart in locked state * Desktop: switch to clock model to fix condition race in desktop lock. --- applications/services/desktop/desktop.c | 28 +++++++++++++++-------- applications/services/desktop/desktop_i.h | 11 ++++++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index a9560f31de..748f9a5558 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -61,11 +61,11 @@ static void desktop_clock_update(Desktop* desktop) { furi_hal_rtc_get_datetime(&curr_dt); bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; - if(desktop->time_hour != curr_dt.hour || desktop->time_minute != curr_dt.minute || - desktop->time_format_12 != time_format_12) { - desktop->time_format_12 = time_format_12; - desktop->time_hour = curr_dt.hour; - desktop->time_minute = curr_dt.minute; + if(desktop->clock.hour != curr_dt.hour || desktop->clock.minute != curr_dt.minute || + desktop->clock.format_12 != time_format_12) { + desktop->clock.format_12 = time_format_12; + desktop->clock.hour = curr_dt.hour; + desktop->clock.minute = curr_dt.minute; view_port_update(desktop->clock_viewport); } } @@ -92,8 +92,8 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontPrimary); - uint8_t hour = desktop->time_hour; - if(desktop->time_format_12) { + uint8_t hour = desktop->clock.hour; + if(desktop->clock.format_12) { if(hour > 12) { hour -= 12; } @@ -103,11 +103,11 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { } char buffer[20]; - snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); + snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->clock.minute); view_port_set_width( desktop->clock_viewport, - canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); + canvas_string_width(canvas, buffer) - 1 + (desktop->clock.minute % 10 == 1)); canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer); } @@ -139,7 +139,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { desktop_auto_lock_arm(desktop); return true; case DesktopGlobalAutoLock: - if(!loader_is_locked(desktop->loader)) { + if(!loader_is_locked(desktop->loader) && !desktop->locked) { desktop_lock(desktop); } return true; @@ -215,6 +215,8 @@ static void desktop_clock_timer_callback(void* context) { } void desktop_lock(Desktop* desktop) { + furi_assert(!desktop->locked); + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop->settings.pin_code.length) { @@ -230,9 +232,13 @@ void desktop_lock(Desktop* desktop) { DesktopStatus status = {.locked = true}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = true; } void desktop_unlock(Desktop* desktop) { + furi_assert(desktop->locked); + view_port_enabled_set(desktop->lock_icon_viewport, false); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, false); @@ -251,6 +257,8 @@ void desktop_unlock(Desktop* desktop) { DesktopStatus status = {.locked = false}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = false; } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index b694e05f83..634f0ee00d 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -35,6 +35,12 @@ typedef enum { DesktopViewIdTotal, } DesktopViewId; +typedef struct { + uint8_t hour; + uint8_t minute; + bool format_12; // 1 - 12 hour, 0 - 24H +} DesktopClock; + struct Desktop { // Scene FuriThread* scene_thread; @@ -75,11 +81,10 @@ struct Desktop { FuriPubSub* status_pubsub; - uint8_t time_hour; - uint8_t time_minute; - bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H + DesktopClock clock; bool in_transition : 1; + bool locked : 1; FuriSemaphore* animation_semaphore; }; From b60e6042a500efc33de8f0cc7643516133598177 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 13 May 2024 23:47:07 +0900 Subject: [PATCH 02/48] [FL-3813] Add the Akira animation (#3636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add the Akira animation * Dolphin: adjust weight for new animation Co-authored-by: あく --- .../dolphin/external/L1_Akira_128x64/frame_0.png | Bin 0 -> 1832 bytes .../dolphin/external/L1_Akira_128x64/frame_1.png | Bin 0 -> 1888 bytes .../external/L1_Akira_128x64/frame_10.png | Bin 0 -> 1733 bytes .../external/L1_Akira_128x64/frame_11.png | Bin 0 -> 1601 bytes .../external/L1_Akira_128x64/frame_12.png | Bin 0 -> 1385 bytes .../external/L1_Akira_128x64/frame_13.png | Bin 0 -> 1815 bytes .../external/L1_Akira_128x64/frame_14.png | Bin 0 -> 1701 bytes .../external/L1_Akira_128x64/frame_15.png | Bin 0 -> 1409 bytes .../external/L1_Akira_128x64/frame_16.png | Bin 0 -> 1369 bytes .../external/L1_Akira_128x64/frame_17.png | Bin 0 -> 1398 bytes .../external/L1_Akira_128x64/frame_18.png | Bin 0 -> 1360 bytes .../external/L1_Akira_128x64/frame_19.png | Bin 0 -> 1330 bytes .../dolphin/external/L1_Akira_128x64/frame_2.png | Bin 0 -> 1836 bytes .../external/L1_Akira_128x64/frame_20.png | Bin 0 -> 1077 bytes .../external/L1_Akira_128x64/frame_21.png | Bin 0 -> 1276 bytes .../external/L1_Akira_128x64/frame_22.png | Bin 0 -> 1688 bytes .../external/L1_Akira_128x64/frame_23.png | Bin 0 -> 1984 bytes .../external/L1_Akira_128x64/frame_24.png | Bin 0 -> 2015 bytes .../external/L1_Akira_128x64/frame_25.png | Bin 0 -> 1812 bytes .../external/L1_Akira_128x64/frame_26.png | Bin 0 -> 1395 bytes .../external/L1_Akira_128x64/frame_27.png | Bin 0 -> 1426 bytes .../external/L1_Akira_128x64/frame_28.png | Bin 0 -> 2053 bytes .../external/L1_Akira_128x64/frame_29.png | Bin 0 -> 1891 bytes .../dolphin/external/L1_Akira_128x64/frame_3.png | Bin 0 -> 1908 bytes .../external/L1_Akira_128x64/frame_30.png | Bin 0 -> 1951 bytes .../external/L1_Akira_128x64/frame_31.png | Bin 0 -> 1911 bytes .../external/L1_Akira_128x64/frame_32.png | Bin 0 -> 1726 bytes .../external/L1_Akira_128x64/frame_33.png | Bin 0 -> 1923 bytes .../external/L1_Akira_128x64/frame_34.png | Bin 0 -> 1126 bytes .../external/L1_Akira_128x64/frame_35.png | Bin 0 -> 1555 bytes .../dolphin/external/L1_Akira_128x64/frame_4.png | Bin 0 -> 1989 bytes .../dolphin/external/L1_Akira_128x64/frame_5.png | Bin 0 -> 2000 bytes .../dolphin/external/L1_Akira_128x64/frame_6.png | Bin 0 -> 1869 bytes .../dolphin/external/L1_Akira_128x64/frame_7.png | Bin 0 -> 1868 bytes .../dolphin/external/L1_Akira_128x64/frame_8.png | Bin 0 -> 1891 bytes .../dolphin/external/L1_Akira_128x64/frame_9.png | Bin 0 -> 1817 bytes assets/dolphin/external/L1_Akira_128x64/meta.txt | 14 ++++++++++++++ assets/dolphin/external/manifest.txt | 7 +++++++ 38 files changed, 21 insertions(+) create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_13.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_14.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_15.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_16.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_17.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_18.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_19.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_20.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_21.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_22.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_23.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_24.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_25.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_26.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_27.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_28.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_29.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_30.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_31.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_32.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_33.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_34.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_35.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_0.png b/assets/dolphin/external/L1_Akira_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..36c1bbd4994c02762f5b17ea12993524b0f782ee GIT binary patch literal 1832 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_12.png b/assets/dolphin/external/L1_Akira_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..e3bfd179d9025dc19f60dbe1d2fd5d49e4d4c0ec GIT binary patch literal 1385 zcmV-v1(y1WP)0qEpb6U|QI=(v_{ynWWVXuBv0 zS`dIko9NB5@zi(}H%QQu0At7`IGMTgzN?*3MoG|?0Au_c8pb&L*TFmt-N0^{x03yp zq2x(e6PZSspArdnJc87Z_s}H+83#U^YXCGax}?fn>c{>5D-!MJ!GymL4b zx$?Y;4E-XqUKy_EwIr~r;VL2G?YuJF16(1%uJNNcUAK^_-;McQ%>goYeun4p?vfp3 z$a~f;5_+!W0GV#zbHTyoC@dLoW=@m06FEvWzI$+t04Em$S!~VlmM5@o6}7%4Jw8Q( z$hajAf>6u}u6CSce5BOkJ9>4!%*X$9q8kbY?8H@gv10zGOH~i?(+QBB03py4ysUC8+Go~U zY?XNnnX(?hO4_u^ok+k|eOjSnCX$s;K+EL-H2b!oxmi>`L#OCj#4RA9s1yMr?v~1& zx>ZBY5G=WfY!+4|C=miA1ra0idi@5xr-jr2H?DoXNb>*@BWT9+COc7C4|c|pDv6t< zwY)$I0i=TyYl4>HBLiK3Ap90Ih<8JhWe%eWmN<5@bRWm86J zId$Q>Y3=#O%Jg!fqhM*_#LkW6>0}&ST@rbXf4Y>j)`#CucnK?wMP))E05Lvq&wr>w znxZIBcaC@fbgWER>x1a-9gE6@2(S}B(f&OKu@;7YqVAu{@aLXIKXD$R%_%yTn*dpH z-P-vp4 zTDg8}msq(RCt`-T*)>@&`dSc2)#YETxUx2|zbf zOzmgWf3)7IGBEbFTLhksTL{1ldTU(mXVO3FS7jMM*R{ZkK(FQbcbNcYPFidBy?e*0 zWbAmt^~T8k>wQIFM1an{xCD48&^D)a7t9C5Eqq)a20CVCIvaA!N z-&c{J*+bJkvfq_VURFSUj4S0de zf--}Qtjss`5TIfx5kcF~15_$qI*hF8`B4S52gu3-BL)vMan|>|B2{MW3q23vlutnU%+ophuzjMnk&Xw1ZY*enykPV!sh zf6u-`Lbi59j8$vdwKEZ9B9{%a)hx?(mISbybcxw??%fhtAa=YUul7FP%_8b~qKqZ+ zi}om4YzO%RNGg=$kN{Mzy=$(O33UG;*NJWc7C%!0xPpASZUmk3sxkoLTlB6<1ljGw z8|ST0mw>CtpE>=jw$M4z%>)^TBFbjbski<;KwSep(sz?D*N<=o`N%p|@#(#P37zrB znhOiy^oh5CZ2C0$=+&+g)|#2`-mL->!0AR$|42Wgc`K+SKT7@4{#axsMz3p$AngH8 zUGXfZz4uW=gsh=~5;-#mxrxk{3$NsPb>HgULILb1AF0{ic;rhMJqF2!NnEnE}6^wC`SF;DWBl*%d`B{xDv!LX!`iXvb;N1!$ zaZ6sm1aL?4N3+C!p~9Yi4hK-$WD zvi75GT_J+kpJ#i&=8aOH=~R-IC8FonT6!K*22h)sKat3^%`7Gv&{FW~z4x6NAZohM z?i#J{8JLrg_8zN#GBFs>;RX*7!TUt|myXCE{eEvUEgW`kOp0T6#@_7Q>UbI|<^ySMl|fvx3V{0~2D%Pv99+*eU=t7<%*QiJR%7y-t4oSZi2z zqO}20dB!1Uh;S^;uzXTY2q-DEPgx*FEi61_!K;WtcV@On`b)l2{Y002ovPDHLk FV1jFge-!`# literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_14.png b/assets/dolphin/external/L1_Akira_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..b36fd051fc724a7f39c4234778a12c8227b7bc7c GIT binary patch literal 1701 zcmV;W23q-vP)cSp`pI#;wpJf)T;4{r(VIhnHwK%aEl@>U zum7DS&}^UQ*$&I;HwM4={(ZHkFSd52cHdpYY-AzVPp+#)HZK;IKXNQNcT)l07JO@Q zqLk)cHG7B;TK@_q@M2_>=#HF$;Dl_dBfz|JzyffPR#S8mT6U-}_s{!S983a~S*{3_p9rhyp}NE_88h~oJr z!0v@!Yu5(9iUdeNt^Qsgx<>Il-sPD03@FkuS{0e`()P)8=)AxA0{NI5JMY2-C$VtQNyQKnXlKNx?ApZzaprU%9 zk_4|%0D3)D0qB*%RV3I_0VI0wk5+(HgsdRJ)(W6i*iTac`B?^$3dpfWNbm{;;JBLv za{Lkb=a?lygsl~TgD>1uW78YGbGsdw}BGKuADuRNjrC*G@{QU5)fXY z03$nW=lK%6RKV|h*Tc#UZ^iCpCs;?rtP(t00U}xVi2KC(Xu*lvME1O6ARlKN1)u_l zlagO~wL4<_YGOz~WwqYg-}z-9Tp?{O5*$xa04?ZI0rLdEQDn~R{C*T%^eG{=k>fq$ ze+Fq!$s&Jb9}eqD+?~50eN1c}*}_M<&U#3g3UEgnupW^j!MdBcC^CL(3fNlhvjnpS zxS9l3@S{FiU+*Ng{0x=$)^a4s66NblYyZ9^>gwH;4ZbFY9(lDVfJjQwkX@4~*Ju2q zc9|5C^0kQ?;0h8TIa{{1+C(Hk`lCKX?X7;{Ci`t5wXSCk_o(?ndW>k^oJI9{zko z$W;n368w?Hr+)k;+eLGwHpseU3tFpx#_mkqzF!h`^=ZHwCo>7&*HRYE*63n+{VbAL zJ|g2}KSvdf<9iCQj8!UuS0M4vWgp9z=p^Cg>yi1PzF2)`=BL?`bJxa-NcZ7yuBrvJ zLM=OnHc9ELRp8CeM_X7gW%0Gl7Pj1uv?bqjW*(Xz;16r(oLrtAei*ueJEk#v%DE>j(-j?`6m1!PkC{ zY*_udZPXvD{S^f)Cy%QD1Zr$4L3M#hS8Io4+3y7lu_Xgm5qxV*tFBoEhyWb3X~AoU z9D}$I8LV2+5kJt4Lz|%5cfNjU0&oLaML>dY1$Sh(X=7BMWM+IJJK8;215|_jA0hx5 z;D#bHM8Tgq3eCc^S8;04?2V+bYV_X62_Oy8HCr$u1*LX>lmu2k>u6`G(MV8c=CO(d zB?RDz3PuhY{7gm8`)3jPL$6iAM+3e(4Yam^034qDqrs0Bd<6-t!fPh@qrOzsJ|315 vz=O6UkcHy?XTf*82Tu_)kYgeCR+HdACUF5(X_P&+00000NkvXXu0mjf(nlKZ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_15.png b/assets/dolphin/external/L1_Akira_128x64/frame_15.png new file mode 100755 index 0000000000000000000000000000000000000000..33607328a0f29b835feab64cb0b46b83318d3766 GIT binary patch literal 1409 zcmV-{1%CR8P)e98^ajQlW6s&*$uaYq|E2W!qT@9>1$;Cfkpdd$@O^p%pNu(u!S}kZZF^_~ zH~>(*SFzFiBlrU0J?dLPKDFoKQ%o1rP+O9Js6Dl=;YY#I23d|5eGVP*X)<%2v<>6!EC2=D{yRh1*g zs`4qMXb>IS05*X$07?1Sqa(&qkp`LsZh++d7?RSX5h7~@Uw8vO?&BPwAg%`h??shK z*IVKMbI!Rd_r3C%fh3E1x@W!xu0)KWIlu;B#e0>G=dBn3x3&lUG;jm3@@ROMlv+_4 zL<|S8-2hJKt49CQzkP!p42(Req$+tnF`%eipDDlJcdzY$2=sKapp21W+L{Y-G@cIIaD^5sZ|7$fpi35pyg%Bi}8Dw z$8&Nuc+&*e0K)S*GzUQ74i6xaIQpI>!}3xIE7gItM-Jan!S4*u6Fg73M-@%Ozvs-g z=;4JF0r2`A0e;|V-L81=s^Ve2$UKb$NGajt4Z!2|^m7UHpeZ5b?#ZkdL6blPDNkDu zYa(*-HHOs^_R66$qtyi9<01gZ@HORn>uW4WlWQ48H0MW&C6!+q0X*Jx3Q@0zIV;T- zx+aF_cLF@JR;vS;3-dlA0!Vee*GR2H7W3;u+K`YXq;xLGQ#*mt?pb#Jj*%l`WB7aK zxRa;7a92)~$#w7|hN!I(l{E$v0q}uEMrlK<@+wBW2f8oM;Y7kL*WS(Kb}Gxec2xFO z4)ChV&*WFU2InMN6pE}D;T@`+@rGp}H3W=EUd|v=0aPenTSLQJwt_ufF$q=5*NiXh zrA1_wl|A3U0#>}DqWdQ0eM=5kidqI!?fHP0F9W>?j)56?W?k;uqX;cUQCYkDDKEhR zBITPlm&#Zre9|1t7$wrDkwHpoHLOH|M@ImuoQi;(>pjp)SYB;$mdZ^1rxjdXe!(J< zo&o*Rc%xNcUh`bKwpSL0Reeb3LuF`lBT>LpDn9O6fF~?xS+S=kw`5rR(8BvlDH<^X zNd|{U89=sv_wEhL^zJ!IG`YD}x6oQwi=xX-tFd4J*%XlN+-1Wwy(i*rs}X_D%@}kk z9~cj)0we>X_eh^EhDI^;diU&xB*zo zul6Xd!l*KW2)FR;btjG*z}?EnkgAPcMu6u?`_bm^!VwN&<^0{s=ho2_U{xlb3L}C$ z4d4;~CCsb*>O;}k{z}7&lb_ks9k{&}tkwEfXKY7Q;0PPOwiKe(<#)E4J>d3p{8@n3 zBj=P~RTW5kS@FI|3-~U0&*xSH&<={b7f{8jC1_C~qdbtY(+IL>@dyq;af%VcdMoQ% zE!s%)%5yImRupso{xh|sDL@PV0IQUWlv?R1aO@}uqJAR{BZh7=@=p znBfQ&O4p1h@?0Y-$C4?X?{N~i0xe>Aq6&O|H#p;zTGP>LiIYL)bJza>A;Uzi^@JQ@ P00000NkvXXu0mjfC})%l literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_16.png b/assets/dolphin/external/L1_Akira_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..e115834eff3b0d2b3240dbf798a3152ba8995eef GIT binary patch literal 1369 zcmV-f1*ZCmP)sPYz&!?@)fv%Ha7f)sirJ z&4VXHokOyT>nPk>T*}D-{=nPy^&Uz-Kl7t~0%}WC z4sqh+00dt6ECv7FyL1H5JOClniXJF5hpyMZqF(i6Aay%W_z<0~#MgBqa@suYXgU#~ z_{7&a2nqZ})9=zX%?8bm9!Qbox<5PwWRfrG)%0;_J%N_w54Ek753K?s89939djxuG zX`MmOK5AnGAK3?p*5DEHt)76S7q!pQ)mj}?+55gf#{pV)6_tx7Z&BM?$?6s z0DaIM#~yGUR=DSK+C6O@Z+;{qpz@L~I3M_)y9db`WOI1rqv5!ve3P!VJYP%n2{}O6 zqBns>K1xJun3awY$(b_R!yF+U<)IFMzHm#v7m*4egLrEV4UcIASzS9mFY^Uz(AICw z+($X?V;VuxBu$g+A%>p&(Z&tpFe4oI5fK1=;S$+BiRYiTYG8MJD1lG>w zYood-EV8t7AIU>nqk!AzC>uI}R>D#AX2p>bF%r@xIf>wwq&4CMnhuWb9KhQ_y}Bix zXrBOW!&X^1f$*mkfBfsZi5m{|bBtb*g(`0)bI_*d-8~!rr$2cc5Mi9~G zIR=_1j7ls=6l_~W0FV5vjNPj!5GSDWo)cu%@FOerE&2c+`Pn=rDY9GcGd4& zIN6dN({ljye9c^sj@q#!m2DZew&V#>^f=fS5nv>&=8t!#M@jQqw2|f&GFRKxi2iER z@;?a42>)KGWf7^Zo4sRcB&gqe5P4TtayqpNusVL$?$NqxUa#Kk;aj`42S~+$tQpng z>6Dyrc?8hFOEZZ~FX`fQJcpP8E&q^gYOR)#i1_n!*)Rfl4}d0TS>d(?=n;aDXg!A~ b?>YTHjQTa#bKt=W00000NkvXXu0mjfk)(|v literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_17.png b/assets/dolphin/external/L1_Akira_128x64/frame_17.png new file mode 100755 index 0000000000000000000000000000000000000000..8e5fa20d8d9290e70540c179fdc8f96d97588e30 GIT binary patch literal 1398 zcmV-+1&R8JP)}C~+Ah#sF zN-4D*d@LTj1t_JITI(5ijw4t7p>oC-9j|giz(?a2A>d#izIF@nsi+lmyw`P2mE#z| zVl>60ijB^X;?WqKasF6qE!TCG>$;xz-X+b?lL=pRAFw z6j{-|%IC1cLUcR^Fagr(*OV`vXared;3R>`&|E)-ru3+X$S{Hj72=F@a*n1TsitqQ zMU_c;4dno})|wUj-f8TGMu(qt?RCV1a5^*>h~ne`$)JkYl+NeVIXwm-@kVhz4NL~F z1llzl5yPRFZUDRE@m=x&3EJt8;!jK`IR+xp*`xzsBPrc}I|I%|v?s9T~ zRG_EWI{iHfAb;i=V|#x305?N#2;r}56#~CM2CxC6vA<`oW(emCA&Zm0QP(fg;gAf3 zH=&-dmy-je!#dGi;ZfyB+RT=G4JQOlgcgf6ckE$Q`JxQvcmNezhV8A9TD{V#*LX;< zas?!Zkml4{Pa{Yg+eJHhG=^I1Xq=@q=sn*X54=~xFUvDXd@0@I0T@P3ljh!~xhT*u z!Fh8zG@KmZ7wCqe%BKqRPN{~T>OhlGoA(tZ{;r5*&t4)Xm3k2)u)GHf&7iq##{-mj z>4ehkRea+YS!d+{R!I1013*s~q6EItJRy|kXfkAbWy_O%ToQ=JdOAPiA-oT1&~-h@ zp9hKmM|pr~Ohqe#Qn0UQc}5YHFPd0N^iw^6=h}85dg|Mno{E6;r10jVw0f(9wwA7Y zdk-*r>b*_2nk)z}WbQ~GLE=g2nVrCB7j1O?j4=}HwM5RlXjjgoFLenc@@YJxv>N_- z0J;g0Q4^|{Ue<_iMsE^E=Su78o<toUg?kMhqnu*_zaNWZ?g1&p4G48>4mL-van~zLp0a@seEzFd71o^^Jza8+v2FT8$@?pdtLUwIc?dUA1qo0yN{{bF^m`U6h_@ z7-*g_E3ur}s@dKMLN+@q1ZavxjDW89j3BFqU!j_<2H+__`$Sn4MwSsoatlP(+1O?P ztCdfoRU5O6fUeQxvDVJS@*Kd6`LmTT@v#(u^y;N&y-Hkd0Nejd7^?jA?AOTlBel9V zx5tQ=pZ;s{4@0b4-|LLc@Cs7GM(;g^SatczRx<)ko~@q+P&bMwKdUOxWa&A5krwbO zMD}Ny0azO)&27lasx??%U_^P1#!Mr~*o*Bs0LRvh7ARWSy93eyLmhnVB*ND>b zXj=Pw+b@J@usjChRoe5_kc?AnO-HLGi3X=H-Ir3zH^}-5)7n9zod5s;07*qoM6N<$ Ef;@4W3IG5A literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_18.png b/assets/dolphin/external/L1_Akira_128x64/frame_18.png new file mode 100755 index 0000000000000000000000000000000000000000..6a658e0e22b8218d5fb63e3cf0d494e7e32f7e9a GIT binary patch literal 1360 zcmV-W1+V&vP)(MQB7Gv%uQv)%nO@_0n=arTL32Il=_?R1oHBrm5Dm@Q zyl%Ch6vGB^cb?yQ2@^~!Q18t~`!}BVi)&d!xAk!U4 zXT@Mf>~=JBZK78Ed@^BA`m*Fyg&lC2H)QwNC3e`q*Vw z3iyUsNggFoh2h`1&*&MV`$a(S@1u)AZ?4Dly`&D7pCL&Uz;C^2;TLaELYb6ACLk@ zl^=Sb3iprC5=C@x_ZoP(0gNbrB){UlxCp03ipYMWT*adeAgg@5eiV@oV20wgJvcm? z3g&qD{<(!&%GdIJbdOBkV#;g}vVf>p%%*zrT8~Fd?+PUO5iY`u#}Gl@2aYkL>!9l+ z-c3`KBzt9d^j0-Mv?Y_)YBDl;n2Ri$9I5Ejioqyx<;7b>0I8jdfLog_4o6^lwRB%r zw8)-i>j;qP!#&VEm+tMAg`p`+%KM&HFdGH5T4!4W@buxMRk5chw`5r7(87DB8yRIY zDYi5K%Y#N`b27d4MYPc5)>>?#wXa5-hf^bb8^GQCKR5n5=)lc-)=GPN3N+7t(%KP& zp2AhYMiGEhzEv4}<$C2=hT*LfR=(7liLHnr&%>UT0whIxi~z5A#;rAgmGf6CpW8=MfQPH+zRN1{ z3Io{c{{rq%e&1KBF(SRXG&ge}S12FgMg!1#{guj(gz88*)+pTFa2!8>Q@Bk8usHHj zU@sjZbF4jPMu8c?%I+7&EiZ#K?_aea9;J}awQV!b+GVV~=d6Kk69HC2BY&*-1W~0~ z6m5jO^33JD+AF`cw#Vz>8R6e*wKPRa`_A@Q90~gGBZxdJ>G@pV25POJ<~M+InydGo;v^N*Rf?`49B{S( zK!wC(@pXCuiL*!pcsQ;wfLd#|C)<&y{z%F8L)!vL0+F0%={P)>?PPzE5gbfRnAAQv5CA0i^K3&5;~n zBQzpt^^e>YaD62s$U49#^eEr+jw0&4mK(t7xO+?6gOQL#=fwut9)Qb2!0EWZvIXGt z`I)g_nuD9~N(1=I;1uaqK7Oyk(>FQ5#TZdOmBE2CxGV&1f~8dYEvkI4>!-*+2suCo zDY2sjZI|)^k^^*s+iZ)eb4thKr0*8AOu>^uqyYp#l}5jL3OWsMY`jzX+8E0?0MY>5 z&|I^0it#(^#B*{rc*BI0hPLrZ0u6X`c(3v~E)Y>Xo;Ny+da{G?pM+_cX#lGs<@Mf*UWB#xtgUB!4@bDx1FVFk#MuV! z5D~z^L*gZdR-PaJ+JO(pz@*Dmn3G{9_29DJu^K!pEim=kl85cpUT=-B9Fp_e5PgYMH<_M-m z+_<%AM9x!i;>L0KI1eDDQxR}udy6k4uuiqbS*CCk_>e+SPaf`pc5><5URfAc_Q9QO zPa4V9Ge1HLX-0~ zlxT8et!kk)uePrq&vms2h*a)I-8AhJIc>cjfsP&7v)+(+Z5AMT71~Gorpfinvkb$_ z6IKz+DJ1aLLXc;(XN3Ssksc$!<1HhYmBY_e*_8%hDSu~%G0O-d+`_ZgS-8#sb}Ju4 z$~IOR0Ujf*M;ki>*KhzU=C4*hH;<-(p39vPC9X1nYyV$>SNYRjqQdqgxw_Q1$B0)c zAK(%L&~kmLGS;3IUV&Rn#2srT+}m*FU4T|2=R(A+tUy{zkK!UF;9cPM=ePlA8^!f) zn8m6kaA{j)trD7yl|~TRiz`Ba2Rj(Cgtnp4d94y{qxiDiuk! zBI}xIbl4NL-y?|p8($6+983eX)K5ysC_ELy5somW+^QH)z>%07*qoM6N<$g6YzBj{pDw literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_2.png b/assets/dolphin/external/L1_Akira_128x64/frame_2.png new file mode 100755 index 0000000000000000000000000000000000000000..9f4cc1fb9e43970818b801fce43ff942f343544f GIT binary patch literal 1836 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_21.png b/assets/dolphin/external/L1_Akira_128x64/frame_21.png new file mode 100755 index 0000000000000000000000000000000000000000..727511100d4b48408d45459ebde3814370e6dc61 GIT binary patch literal 1276 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_25.png b/assets/dolphin/external/L1_Akira_128x64/frame_25.png new file mode 100755 index 0000000000000000000000000000000000000000..26c944d16e6917c7e73b40e072cb8f87a5ca8afc GIT binary patch literal 1812 zcmV+v2kZEWP)FbI@0dH+}T{W-rmNzk=z*$ggg@GhVeEc5Jx@V~nr0ncrpmwavW$YSH{JuIu{t zS$~(tA^)zBT{$MR$Wva`HKQBS6aD^{yV|;TVSVuE4oBCB+zd1s^S7$2)Rkdc?U}6K z-Vvp&31cSHvd&H_)vzm1>mS)n+A~{5Z0Tw-faWUJc4Z6@p~!51tv$1QrtEG5+uiBf zX6x4bwL?oQ?)!$Hd0&eMvWWUmOXk|wqDyq2)hTja=gQlBp59iUGy}+J)0<#9hDZCF z@~2&d1x^17-=CQ)WF!=GkIgi2y$6K^{NO5Q7i=0;g{Y6;i5WECj@Cr>S^L5?kfqjD zi1v~27GI8wev7(*6NpChECVod(Oe^Zxd3ZWO*zyqs<5?Snq_BptTmBxXKMYtXO{5X zC)$bLPM0#QXN%6!dsT>es8#rSjqGHktT$m=UuM@;P(3q-wWG(t292&L9Q0AB7;RvqqcRuJve2lOf?z zCkxLHM%pv7w4IHDG$}I@LomTK(reY}?M(urX|J8PP_nP*t3AJpCXJzB*`Co^gS9B0 z%;=(KA$595`m-pVg=QELde{1(2|}&`>Ecna#~9W#Sc@-pLC-7Oc^l3f+roX{{%K%F zkQN7IqU!aYw$RdGnV7`dZE_54b6C&7#wil0os@^>RrZm3q3d;B}^|&$_l{SPik;4iEz(ePAI-!pa8N zMal$TD=asJ{HMG>zsq|X8)(d9>5F8u&xKIQ6!KZ#*}*H7Vht*~Q>+Dv#2uD9u*P75 z%IbxUjD20#zr&!23s{Amgq0#``lDSgVJ)s_M3K0n6W*qQ8Av7wT!mJ+0If}Sw|jn= zWf*2ccT;3`?$~+DAQc2g5H*lUABF`ySymvX>4DlupE2upXJ5%USq7kE07P{jZ4^LUVbfqiw3gH_*Q=_Ahz%$2;33uMa8l&qZl(@29jOm#Cx(eL&@lg z0!weKj+((3APtOK^De3ar%2(&+r8StR2nHu;j)i~=z3h&_46>O%hC`|E&god$;;;Q zC$JL;N<*7h+67c1Vq_?iXXVK6UeU)J|MUilAY&kOC9n!?aA&n&Y1k{$SOH@7LT0j$ zlTZWMG5EKFtAVOlOfEm~c?B4hxiD2a4Q#z3f={x_aR#u!rk4x@OlUCxf)*rd3!QNt zn*L9~5e7gDgcx8~hP4ogVIqYcR7A=agMKvwumz8bNywIIQ58@^izhOANBVZh46Xkk zKtpwEb>I4HVU>o4^7LJ!Wn5A>+8KZe>a5?#i231p&vFS7cd@q7>Iu7G*s9Y#90>xg zMQ5UM6eoDQ68UOvTut9>q#ubVES!i79+d@XKn$`Iq(xFZ`MW0{)g+!2`LFnB9blqr zf;6@mgEIs+8(@O6CgwH-@6pvtlG4@}Sh?HTc93~j- zOk2@7l~qO$M;XBKt+GvLUA@9bS7ZXHe`S{8;V1({^IDH?cPej4PIS^U{i?Er3WNXucke&7^$QvNd_u7@cS_fNuf> zy?QnF`FBJoE%no6v;Ee-RSOVdAMIWjQZYbvjXLXttvud~@;#&Lpx|!i%U~-AtT2S# zhh<91NG6-(|7JtjIt@H!sTG-k*WU8i9gN37JL#;(_Hpoz5kzu*b|ThE)B2n^Vr8(C zHdf*zrQpcJM}je}poC;wN5oeDDm=vyL;oK@B9hXreWEz+YOd?&zk>PXfla4eGYI^Y z@7sYF`A5g;4hl~kEScjE#Q?hK>GTe&D!D$%2&&7-UoPM&KB)`4!D`3+uxY3zd$(%% zI+1EjoHVlk&7i$EjTmH}`S@GG-OvpBv9Dt_=oK}B$Gj1=i_RV2IkxpazzG~RfFsA7 z#KF>T7(x04;aV;tBD?d>*!~_MO2o1NhXg={T^G1tR(Bl&xXbEK)_b`;fVx zDgrU`ciu|Mz7vr}cct~QE}$zt-5s;)^pQbE*Jz)T(?E}YPECS!)LRJ@xy!#l>Gs%p zOJ@PrBxGN#^u}Ldy$uu@rwW~iMa)aB8nktgWBdVN8OYy+^XKXS0000jrtF>jn?bq(JPpkdZn>t0fYVOLHz{PUkYoU>Gk!0!jNsN>U`Y;e60%4-&D(jA zVgNUugs0*s(_&9O4J*7rZ!)$E;W+?R?(Pjr_~rsHq~-vpk`M6B1sXyPfGYKtQ}ARw z)&l^nNmh3^dX_bUxY&P#D|~t`kmdn2#g-?9a2wI?1K8UgwAL9xx-~zRHF8+2HDaWPfEF)rI{6l{+^LO%RWW(S_Pmg@ ztO~e+{GG&epsrkV^LTwd>sposp6&rOa#s`2p{;>l@xMpbHC+Zuwf!fO&y{#P5z$ba z_5d8YSV^a6|5Cg>;nZu8^l8O^Iwm{>NZ#O9@^=!?m2&ty&v*`4R;U3;$~w^l>?WT} z=&W@{&JFO|UuqKAa60)M36bPWWaDekd{Md#=>&RC_f6zS5pR(`GtX%0$L0(W{8q3Q z20SwGz0ods2l;ruO!Rt@HfFjE9JRci$RJ;G>ocWe>!Nxv5@u_yF?|5*`x8niOmr0a zlx!?7!l0U*Od}XJ09u|%xxfhtAZ?aJkxyfLRHF#X9(f+nqwiq@;98Q>eIBK(dJ;He zkkW~7a!-PHkKG2KK3f(Pv(p36xkpKpvigknu=WthJKl?K1CWv(>7-kW)}ABGLYuoM zdCe&0R={I0ZUg8!`MXcO1uyxUS38}28d4>=4M6K&X)VoNikdM%i~QT5>0-(QNX9_F zw-)EM)(BXQD$U)mB!4y!;AC+a@&NR7a0>G-ECbRSH1bdMVl&{3hl6PVbRe}%>37aM zc(7X8pGZE3VGrPm^_cYC$zRXue+u~kau{%X0M6|L*!^m$MC9b)Yb0(ZAFodQ znVE;~k?K`70FH>>3>(9nnMRQ>$<@f$!UgxaHttm5{g@sgf_!R$c#I^o*ArM|j*_p< zA?aqPF?jU$j|Y%qpx0Gq#q7v1K;=axniA=|ateujDGZcS{{F9F6p`f5D%@iX)*3zJ zTk=QpAnNflfamaptA<*Iw}K;2i#6Uu%53@Lou_A=nlW%ak_>R+v`0^+2yX>@1Vvf2 zWoTR|(y9n;9tmC}^ThmBjWDi#5$K z0lfY&L?cFmw~?b6fh2df^EP5cQu({f7^31lmPPfF^0$GO;w*A{$8z_MCW@L7tTKj~ zINv=NOn86>k0%&OC`UuVY*LEltTc+WIIrYOe*kG_3K?=Snfd?#002ovPDHLkV1iK$ Bj&uM3 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_27.png b/assets/dolphin/external/L1_Akira_128x64/frame_27.png new file mode 100755 index 0000000000000000000000000000000000000000..397face78dfc1d6e247aaeccdf0171b4699b41a5 GIT binary patch literal 1426 zcmV;D1#S9?P)A4Fbst}zWszvpI{{;@6=y{8Dwk*kKKx(u=kV;!i){)pNUzw`HfJ~NtE`_(PQTg9T+rMHIJxGZjXOyRD*FPDtO8b8zb3CcVeLX`4q$n$aQ)Sw%Ig9>&uA{!mILe{AHWyaXX`sn391Zgc@D7qpd@(6 z|7I-D0aiZA(jZxCgnT^av|J#?4=fA;J7YfpuK-@RPvhoHb|;Px0lTfAgV*v;TR%os zDg4MjKm-xDe=7NvSfT(cT%%VeCj80S{%S1q04v;QgpjD)2YAlY#!M1et^g+;REzxR^iQYisd- zCV@*VcUe8gM**S_6Ds3!Wn!r*Q+I|>J=t#J2aXB>Rc>Fh_;J0{1xuEH3QDSg3gWz$ z&yh$EIqUAT&j7eH+1)~>IHnbxb^lsZM}l{*Cy?*ibE*?ag=LVH7wP1!B4h{oBeZdz zk^cO19~=+@qL#0Pf!TZ(*%9~ONj|q%?_AqMKxI35wZ)%6eg)|m+pB;K`P_Z8P-P7b zk><~8@}ql|b#4AaNQL{yheH)<_M}ge^CL+z z<`k9WclIet{yI2T=K2p2(tGA=;6a+&0dcnjkAoM z$-sN*>KEMiz3=+~nbJ>6&?;l2MP3DQo$j8)mK-2LzI4dg)n`j2k1}P*KdD9B{+~jl z!_UOv?lC{nmYq_m(~2y_`Tkz^1Q+Mab92XmYi+MM9DFUg`P7PT~jJ`v9K0 zzp_J!y%l|*$wQt@K0vbqc-+5d+>jkvi?<2oNpw#qAD}r0h{XO(mr$D%=LuF=iKL`^ zDhn-VsYL5v2V2KVrJhvR&uaO~b>TU+F2hMtkcjisMe zlaI$pl?l!bXs;uRwj|IJ9y}J3RVL5DSrlU?&(j}~NakeXanbWA%K-r9c|a!iOJj#z zs~joGlCEVBcOa55k9A-f+;uA7i?$q~IwnWJ9n^SF50#d08AQm(YoqJ1D6ti73J^Jf z6MaYuxS(6v0B+!c420gJ9 zyI)#%NnV!Z07*qoM6N<$f;o1pI{*Lx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_28.png b/assets/dolphin/external/L1_Akira_128x64/frame_28.png new file mode 100755 index 0000000000000000000000000000000000000000..6ca246f57c4c8e7b8029958e5555b116f27c1536 GIT binary patch literal 2053 zcmV+g2>SPlP) zMJd%;GUr$!1C6m`(LAW_F5~Zz_s#@G5y1EB8ms~og}jbq^rKQ$mpic|(u59>MF8Kg z8UdmOMMkcmBg2mCZ2Fxk;VOO{K*^P>BY>KpqZE|!gs80w)zNntf0t9e$G}7e<-#li zOn7n?^;M9OP}aYKhyy4x|5d!k_*^JD$#c=?d47CGZPr*eGGr0L&ud}z>lok;K7iyf zLYWWS>{gu3L4OF>X$*?Q!9LMn|1EIY+q}D`&)jNW0uIz*``73l$_3V~`%CoX2JNH%0d0+%! zirAk_5k%^uYJ)wkvMUzO){2O%f=GX4Y-CiG0M9-vqd>?3B5y^W$rSG>-O2UJTqG6H$B2?X@ z&Dllt6kRf~=!taBq8ZaEtO6nqpuCS3)P?Fi)d-*%5fNB5TFm|#)IIlYUD-bz06n_| zE>c2azUmz6c$7OkvK8Z_y{hDC6fM>HGd_WSqaoNCq{vFvPIhv+FrXZlqlYzOUo0IOVN$cU+;Mc91ie>_wpuuW8;I4zTl@^{yH zJ@Z$IX9gGnc7=bIpV^5-CzE+>WmB0B8DCKIA|tVt6tjyrA))mgr_vZEBjP7KnEVVB{jeoRj0nAy7Q zoF5zjO=N|l?<_BQg^^J*mY?aAy^EqtG{?7%Z}k{M-xoD<`9tu_0Z<-^LJJzt+OBS# z8kVoj|1IPD)2gIg|DNg3s-p8e$2%i;YKe0tgsWJw`AX%mQ$ErM&CT|`Q#~;p$1&c! z!j6eozv!b=grD#hBXi>r+3=#!1D z8vlynyW^*xsuQROirxb}1uW&*cTvfT=3^x+lf!hWU`6hp^H^O}CtzIZ`14Nt+d3s* zu7<*nDYJTN*66yS$w>v;a+*WO< zVxZm5R^>~bJ@9j*vN3wz>Hy5pcNoIUMBl3hSedRWaupqH**IlDCm1(z0F;02yVXE4 ze4g~OSDZlhE+Vxn%FAvwa7zS;jK2e!jEFVwibN>&t(2EJ8k6&N5_lKkL-K5R^liTH z4*5I4I2Oa#9l%405UWp(ms_bw?z|MMfUV8rF%D44zgKcKGZtdcSB={pk4n=k4i?=7 zJi-C03*32U@QwS3F1k9}F|VNCEx1AoeW_#HpMnnYWjB~R@Q4V&7&7V;ja3c5`qD|2 z`6S1jc*H$`?ZeRd1r4hE6}i5C&kU#bVY+v)>H_Mv6o4))<^s?iwa4reh5aZMSXoFn z&pTn)dn*UZImq>%f_Q5vt>?JU`}k66hD-9nZec zwrrEo4WM_u~$u16-}G*=c4MIsUts-yxn%uN`ZuM3)Z7`llQq z+X&?sA(aiS>b8>LtwN^LSAHG5^Nu~r_mLw!=}K2|=7415g5sv@TH}SZUngEJgnTjk`6B)tZ_pG=o%>+lNm3%7|Qg_h~hU- z@v5}-Qyq*O9iVzuo6U*(XLk%$CqVKPbO>vf&X9iWX~&1WXgn+ebQo~8c8^Z>mDAp3 j?^Q=->8mPZj4}QLk;r=k|9eg|00000NkvXXu0mjffRO9u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_29.png b/assets/dolphin/external/L1_Akira_128x64/frame_29.png new file mode 100755 index 0000000000000000000000000000000000000000..8bcc83a1db2c9656942f74f1f632247e78502c92 GIT binary patch literal 1891 zcmV-p2b}ncP)zkwjS# zQ=msnHLMu}uyXwuWw>&BmJ6%l>zV32qGFN^M8b0Bhz8O|M3X) zjlruot{MR%MMa^$0?)Gp7Bq6~U^Mw3;( zNN*&ad@`ci=s3yK+0BV^YjwU_%`&6Ql>bfM?-~9htJbqn>oMzBr3*kFEWhI>GPzXc z->r;0fNOuIfb75}Dz?9y~c%2(tps**=?zvY1j$M~*FPa{gd(g>!`0b1jV@v3+>2Whb{g?Ck6 z#&;z=ns#p#J zSBp)>5NFZpAdkq%uQq}Q6K)n>3g`>Ms@{%K(@{nFUXG6pbSCsFt!0#UFe2>4qce*7 z)zw9kSA1dKR|=>Mik!^oMvb6igq@FvOKN~j2=9Fqo|pnFg^d^Hqc)Anl0^_ZhOI{l z#VDI6!-ywe8UgH;3BsN!A&fm!YJ`esWDzQpGc#tjXhWk?m7hhrop@LQvZ9R%G}AUB z?8ZfOy%y5r=h2AhBQG>u_PIj$T|j>|Of9}xmFsypR{o5KotH&`NY+Z&MVY-+f=Xte zk*1X)3+Gh>UIl6Zna6v1nN{V{J$igrjC%7K&(WNVvNFSs*00hXk;}@n3V(h#kQFpp z4vd6lpVb9K8@<~rlv+Slenxi$v#G?&{`}oQMx5P`gfWX%d2qG&XY&-}AECQrUIovS zz^hP+03-92_wQJYdDwmqFq11gyn`M4&U-(C$onf$`uw{Pov*CKOA3gD!946KJgbs% ze$*0HFtR~AjKI>Hp{dkZRp5HvKM*k@oPC}NS}mY(EP8)+ld+9u1#ne1&nXo7vLbg^ zbdrvORUxuowz7zwxi34*urgF@2K4+W3VQSVc*M#*d3FI&f2&^a#At}klw+T%wFVlK z*FJkRJ>{?RewOBr*4F%$u+>n{0cLY-HT)~7VI>_N7$goTK5k4*g%z)+D z(Sx2$Syj3t^HjP~vHTSV5EWx8aQw`Pp{1G0IZ6xPd2RGZ89+7vN1=Ab5pU;_RrHLW zBIimP-r31z29TX~@8HF}bEWbvKb9U;IV-Y^&SiUW9LHJ%U`l42GJAld%Ag$DL>Qen z8p@+~afU9!@>pU3o|5e|&jMFMIWgSTTW6=18E{XoIt!RGfK18w%+6bF1ZtR8F{8{X zkE@k#G4=BRnUaS9;f-#!ly#nmRGhPv$?3h-egY?+<0q#`1U1#`T z!`L@HR(rqo5U&`D&fZ5220v%(V&1D5)13t#gO&L>oc|k$){2(ALl_2+C!44A{44rv zrO30gkML0VXIF2I#d)5u2Eb0WcIEw11Mp~NMsQVYhp2WRBiB*9w1KL}c?)2;BJAxh zP8EzOKU1oDRrw*pa{#r_$(X-O{@aztJTFvIQKW2@-VBBhf+|XlP7JTc3M0t8HcCI9 z(ox2%P&R-WC1Q?_@|UH*+Hf*uMVM87|IB2%18x`4+Fn7?3u%Qa6p2W$s#rCJDaDtf d{A~Qy_yvDSf*KIVECK)k002ovPDHLkV1h@ln#BMB literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_3.png b/assets/dolphin/external/L1_Akira_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..7a1d2b36e828c299c37e05c6a3a078abcffc6b5b GIT binary patch literal 1908 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_31.png b/assets/dolphin/external/L1_Akira_128x64/frame_31.png new file mode 100755 index 0000000000000000000000000000000000000000..4f2deb5c58b47c14558a5183d1a7e9b90b16d71c GIT binary patch literal 1911 zcmV--2Z;EIP)^@RCt`_UG0+NI0#fydH+}T{W(pM9%vdN+nH=vZ8?!8K;m1u z-FqCzaXcQ6aU92B&xzwW#u(#mtOBIxy+%tw3qcI7kB6p(o%<`{3x`>YBUS*tZbB??6f{?^>@ zAxd=&kHgIF9u~{B)%9wNGruE0#W;536TH9bh|j26&$@t!`Ftu}0P;Yu$Io-Fs{Ff^ zaR(xYi9RpY08x&|yc{uBc(6;1%y3xv|5ptoLa7U2eP-tAti@A8zc+WvlF@nF@E3=F z3$+G%MM`DG89UTSk#;P5dVEyndwcoU5jhs^0>;oUW|(5Sf>u2LuOLdRci?dx?>`1b z$PU|65WZ8t&#_5|br*`RN9urq8pxgO_D+rWLZ(pem#qg`)bSy637AD&5rOIUnv?=siHR zxDqw6dtjc)=2Z{NGcZ<&cCn&#rLNiyEX?H^AVZ`$MTsmovUQ3)6+SD7XnLVMdm>Gb zHhZ@M=^mg1FRvlL%=lJ31P>Nvs2m_3dbFM?J$uiLoatz#D7T-tMSu$PtmuovzeS}0 zWroL;s~QOwmzeex*|Pnx!ntAu7@b=YKrtG-L3>b9nm~AmQ;7)dV7pbe(poYjT6Ml% z?erMrzX^;Oc$TLM!Xf|@bXTQr_ZbfSDUF_w9T7b>$f z5bgZ6*=+=tUWcYqL#>2;rH%geFN9RV zLRTiV(gC3NR}UGBTU44Ni${~m%#aniyQ5R;)MNDiji4@A#XUyf02HsUhFFYlmF~s6 zH$d7uLcL1oj&M+DdJNG!%Y2pwhmz22k_=Zk`#cx0Foz8I7IFU%BIG-vfL)Y`?D_ zc9z>GO)oOcC%XqZOZhAK6DgpIZf~uo3^pFoU0J8nT`88o$^cj~rUK`yhLF+BDteEeBIiyT)w`4J25?0ohrB%R+^Kxa&r1)goE2GC*Lr8b zOn(Eu_fYTfG&$3-lt$ zZlEJ{C2a196lWKo3jfICKS}vxkODK&Ry z#;d+louK+Vz*WG#%(1sQ$Nz7<_U($hYQ+ec@pyb9CCI;!IX7z2_43)R+$y5$;a~e0 z|Nk0@o4G>yR;cKZy}9$9Y6Q4el{qW&tn4d16#m&WT4TY^^(_LNmG@T-AflBS!Bwpt zqEj!Z&)s;d0jjweaYfiWyEs*_qWny$>Qm*X%KeI7u*EF&t_rskXQi>o3zbyFl&#Y1 z!lRpZ0TrcUL3gJi>@b4NYgzh@+1yC>5p;phJt xg4kWn+V99mDH4%hRk3OcQ;IJ|`PuwW!GA`&=&$EdWJ3S|002ovPDHLkV1m<}sCWPX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_32.png b/assets/dolphin/external/L1_Akira_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..4f59f99555b225edc829f6204cc80a5ba5497da7 GIT binary patch literal 1726 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 JPDHLkV1f?phW-Em literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_34.png b/assets/dolphin/external/L1_Akira_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..5ffc55f75f3451585d426b352cc99df25d496269 GIT binary patch literal 1126 zcmV-s1eyDZP)USFbIIr@%>-f^P@+L6v8DJ z7XaAzeTQXPfW98=`wjqbIp%@3$9+zr_viYD+m-s9lvx14Eh&p|IR#2>MGY(3x8%zT z@D&<;rC?8>{ndi|AoY))lgjtA0enX*eWhS`tAX1%+_iR|6oNET@o_F8KvO`}UH1$D zsr))t{EyWG9B?kU#uZB;$Qq&&KNkbQ0_KEH-yh!McyGx8N=n2AXi;(+LOr2b00tYv zrD3C6w@m3u*Cus{9>5A7wGj`%je2TO>H7Q8!~^^^>Kw^jo7y@;8bcdY51`GxrI9bK z%xUcb{DzN~3q{G54gcergO_4I7uU5{d@Vhm^dr0!tnC?C6&XqX@V#1Noq_xnR{fMR z*p(k)z!I}(8|k?3Z$dQyE@o5rZ5g?xxXuZkGniiM{dhg&ZB%;zfUDcoglJ6$v2pcO z%o|5NrfCjf{oHdOj#f&0yYc~o9>6OaEu|Xyzo)5;N(jrpNGc>6$mp|7S9UpCnM)m??FdOWuxSTCBH}k^MEO zZ0_X80JL2?sa{W_v|oVV&oCNBGlLPVTFjygF0Hb2u8k1_nL+3?v6e-l=oD;!A1J5y3N8g>>yN4iww=Mh+GnRo=2``u{>cg;>V zfbv5c$LE{^z05wsX73*WFZY^$Z`$e{eg8zB(0?hvDy^Gv=Xk&883HU*K76xiLTLFy zt$Bh00PvwUDqrh$7Y{(;lwUjb<5mlkD>ZWV8ql);KSR4|Z3bPse$=MS>|UTp5~{U% zZU%9gf9XO`XG=*NVd99FIx9a%i~TDAR$%tc&C6f#rhd@g1a`tOjW^87&#sR z&ip-k!hLUdNm$FJ;N;6)GFky_KN1yD zgj9oItxc#cu6zsCzY&DdN}9QI*iH!#qFAp_>@+n13(?}yqz}Ul;9z8ha3XV?0dS=9 s$1_buP%8f(rZ9k1{(Veg0Lgv)16R;j8|j|I0ssI207*qoM6N<$f-x2lJpcdz literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_35.png b/assets/dolphin/external/L1_Akira_128x64/frame_35.png new file mode 100755 index 0000000000000000000000000000000000000000..9a101f0aa36d323084e06e505fff0b14cfccf8e7 GIT binary patch literal 1555 zcmV+u2JHEXP)Y+s7={Jr(w`|ES>9e$;2P>k<1c~8P|w*q&88ZnCD1k!a$&Xe(#4v;ne|77@q zuX6yNz7a>z()}kJzO`TdUk)&n5-V_pe5r<8_}>gK>3vvVUWttSh|^>o?9mze1(~m^{Cr5o`PfZ7yF^;x5bj_<$J_?lNJ<;&ly*x5E;|a4i{wl7KA$wlb zKh5y7`*@ybJ1T1Ls*RBhhsuhi{7*~wRRQ$Y1!UnxI~SN?;fSor7`px^Mg5A*x%3=B zQ&@6>iqa5WgBtg#hF@JSkIeaZHO6LV+PaLMJo1rpwt^5>%gNxy0nk?s$W2BvLIm0N z4vl)`0gxqA>n`{>PY-SU49YUVtU}TGyq!e(fh5b?aB4h$rhs7RMdw(0UTOTdE?{K_ zSdrF{&7|_9#u2iy_&#b- zE-O+N?lk^+p7&({YmSsDq)qlBKN@A49+`|#b&gPZHX`li!3SvzRA&IR1W3HYTbItG z*P_U<{xPq@2{OjF_Eid$AFAy5Cn;eq$`zpRwRIymL~jhq8WqOpIz*j(=3Jw-b)6H> z20zrm(oIqW@P3$I87qQKF26=GhD}mmh!D~%;!ta=>8zYia0HD5rbOmAlXJ5cXi}b z+~Ig!wpT*A)AZi|W&n;%`j?1_AW=WYq4(l;0U1c7!(#IiXFz07X!5MN zG&^ODe--)Tk<9hr`k*#VWq=H#>D|i&6k3#_nLyLUo8vh)q64CrMWCf|5;Uh#e-KiQ zs&-GyAks#o^Q7yVF5cW(WQ{+rd%FJ#p0YkA zgIF?J1D*v`LnCq(GA#m_{f_IlI`XCcpw~S1FRg2q&vp?9*wBHbcRDW8fmq{u@45dR zx={}cI?goHz89BrE>u3GrexD(b{%^TOieBgy>53-{K78y47`+IDpm#Xnj3b z%$idLX~Uuf7&(BE){|hflB2}^eBFNJR8QE?h6@dIrPCx6^g zb{qIGXe=Kgew>_-wX71f9pFZ2Z7MaKWjyUHfnU?QkJk3HfLq{2G)nBWt^tudKJN^0 zGkD6@fU^In?Ck(Iz&aVU(!F%(7HKiXHnQy!a2vF`?2V<@ve+EpA(;6+Km~RQxEax> zc&!y5Dz%*f9s%@epf@Ip?Envh_gJ|a+X22Dn*)3~{sFEAmOW?D3%LLQ002ovPDHLk FV1m_$>L>sJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_4.png b/assets/dolphin/external/L1_Akira_128x64/frame_4.png new file mode 100755 index 0000000000000000000000000000000000000000..ea42d75d60c0de48db304dcdf196d5922e68279e GIT binary patch literal 1989 zcmV;$2RitPP)b7pAL76Bzti91_(i|2>na~qEB-G+e+J)(y!FVw_kBOY`642}><5fuClTOX zhVkU+8qSb*yiJTJJAToR0AMouBm#8!RgJpV+kG9_M^1ez+#~uC0EnUkP8@8PN7ZS% zfnD-Dng6%Jy{4rr)dZqLu~UHeO!NFiA7RJ3%08yad#ckdy{d}ZD9@Z9&AXp&AZ@c( zEq@}dbO z-&3fX(5(Qd-UNC|p&*zp4d`07dh4lqdCxvcJN9~Y?zT_e1L#sx&G1NHZ*4?2u$ePY ze5b0~|g1yCfjiljSs)-;X)yUyF4>5-1>Jre}> z+duocr-N;TD|ZS@FTE$ybISQ5f4&RQ8(0MJ1g(n?^mBr&;nDAE=2x=bbF_A9rq)|ItdX;IRW!&Zs%htoh+HC4PR*!uuPS-!vIuqlD44rrZ07ThJ&piYkayS&<~?1aeNm$tHGET) zB}R2(s8D4}(lm+y9z1866m{#?xt*~sa?C{b+r5!4)8y$Dq0G0}@@EW;3}c0{uo*eB zJK|~GvN6w|C4${u=QFaVgJYr*rV&7|@iJ<BBgmajKmJPQt#{ubWuk z?g7@%dLId0#AF8Q86sG&j*nVKgaBIp+JkhoJUcq>P8m~#{JHFOU03hRAS8f=&#E!9 zGO|d&j}X9vTSo6_PX_NN`h$rQ01-fEM#iAh7-f)XqZLIj%5H6B#`zWzqN&5v6_vZJ zZ#qGdt!Y>Idb_4_&PoGlb<6mB0p0BZzD)rvqiI;L>_;fVII~J8 zYIYU`*&4lWt=+Mk$S?vhfx#%vM7e&B_D8`R(aB~lDka?>pp#5m7JUQ+& z`pP;#)5$Y<6~U@|JYCp+3dye6-Lvo8dw?naOvfJ)%dfg7Bjmsizp2skx{K)>LH&tN zH*3V0ac%91rVwU~+#S2v`kkRyw5CdrLV(J6iPkWNL+hfZawpRwIn4Pioj}>Q{J((= zzs1XbhKQL0cGlYUohc`J#*C~P->G1r5P*TJtTfo@_YPC7#nG&f_-AsW&pTObb(<x&7j*nljWe@=>bX#XZ=jrgv^yr`f z6#z)sq9{1*XUI`mHZaCOLE!0?p-d&?QJ^8GdlVs3l~Q4 zbjCi<@tJNFomPOYANEYqI`j+%Kz4ApfzkU;)>N$vV*tjnCz>x5RNBejnzb&ImFbM0 zQzf7~t492y?DwG1qY6w$C2GlD`gzVk=eLezsDe6cz3nGgM!e{cOHp%Hk=W$CT|V#u^X zvU>r$Wgc2(w#KvVMW#k@Y&$<>j&eW>D4> zL}ka8D7&8Nrr8o>pD|5bXH68a*#r?$D*!aT6XMtmi~0;*B2Yh}XYfx^7J!pUR|#}n)6GU~yLwZL1vvn*{V_;@9ZIJ4P4?`KxT_dpFeWsu0Wy_U) z&gy)+;oL3(OZd)SHfz0SEFa4c)o$i2Y(TQ(ai*)4?LBMpzljrf?+Qwa5HdXY>qb9Q z3L+h?Gb-@R=p1$Z*+j$AC9?P5158oYv$za!dI#hLG*RvB(hsFY60`8dRi&@{5 zolzE`J4jjGJ7I5;VgO7|0g;T1&(z_kXd)098&21Mk)Z%s&n6X%GV&>oKSige9YntY X2l-HT4d<&o00000NkvXXu0mjfqrTcj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_5.png b/assets/dolphin/external/L1_Akira_128x64/frame_5.png new file mode 100755 index 0000000000000000000000000000000000000000..c347d3035380bfec476a597479fa0d1a82f5084d GIT binary patch literal 2000 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 Hu0mjfR2qjA literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_7.png b/assets/dolphin/external/L1_Akira_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..275f2a35770c4d3b88e915969cd713f16e6eb690 GIT binary patch literal 1868 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 Hu0mjf3G!+@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/meta.txt b/assets/dolphin/external/L1_Akira_128x64/meta.txt new file mode 100755 index 0000000000..4ac9b38b34 --- /dev/null +++ b/assets/dolphin/external/L1_Akira_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 21 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index c0e1741a0c..42e18ad3f2 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -189,3 +189,10 @@ Max butthurt: 12 Min level: 3 Max level: 3 Weight: 5 + +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 From e1cb69d04692371757e0ceb95d1e763fe2d1813b Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Mon, 13 May 2024 16:11:46 +0100 Subject: [PATCH 03/48] Remove unused DolphinWait_61x59 icon (#3645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- assets/icons/Dolphin/DolphinWait_61x59.png | Bin 2023 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/icons/Dolphin/DolphinWait_61x59.png diff --git a/assets/icons/Dolphin/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png deleted file mode 100644 index 423e079199b00df0d910981caf8944cbaf8ee67e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2023 zcmbVN2~ZPP7!Hc#RVu|&R6N!I%3-nxkdW1AfgnT)&=3`rikr>mC`oqNT?mIZ73u(L zrQQXsTAVP$bgCRqVL%3}R4ZPzma(V>JPRlyt(Mwx+HOKfI~`kFcXs!^eeZkUfB##O zlo0DNW!4lPkLMwelPS4T$~}uGjpN?2soqDpVKNn$%J6tor`sPlUipC;Jl=#i45}11 zMG=qUq)CWrNHrnMF;N_v$6K;2hr;j-f(6us&R~}EhnidYfI%bWuL)N`3M!h=8{+b4 zA~`QXh39495)FUZQea6A$`P0d76WojMl*xvNcj$4l$+a^K|bJsuo+T*q+KA8qDTUw zNtyseLP&r^5CVuLLRb_QCW00L2!uc&6b{0O02ZN87z&F4=f&rw(HbqPlr4A4;=ZJO zJE3n9Bn4xk2i;ixRy=n$^KLBdFw2s6uYSlET-yrfXL z;LoKsnOtawjmhRTa@zJ>G^5I;2vA8dWEPDRG1;6%zcIxqJ;{=cp8N+pT-z>dC^VWT zFqWiMBxxKARMHp=fWSfo2wY<@Ye)+dWS8PRK*%tbkn*{x!2$^3ZWV%{P&f+1AuxnO z&?r>F<$(rcvHu1pH3n_&3!xeu)snOc(Gwi$zvRUzj3KqG1*3^b z9p~;B<{ii>584ZM)DH0PCOY>1Qru&3u4CAzu2#i;xSAbd<~khBwX$#Sj?d@u##!XD zNR@tb=h}6&`}|35K||KN=gwBmj&-Xj(w;uMx-L?`(_*_ZqL8ard_B`EtMjXyZhc;> zZF)8CG-7Mu_=mE#!jz);z6%T5S~mVv@At*=X|?Ol?+v-67}*3~ z(I*yd*!+Hl{6bH0ad%YF(4l>;^=h9m2|jf9+!^TVd3?C0_k*j|7SExD#fc+65!1f++)=)#z9VKCyt!)o1FiS^CqLF68$Nlu@R-*F`KK+GldJrn z-6KW!*!*?i?t>eHLo#YCZFefNm-}Sy+HzW#(kJAQ% zu3Sm`#Ai<6U{7oT)is=0nUQ{99C7-Y$5MCXl>F$xVZhye@tt~FtDW0WISrk6aF6fm zc|x z#r8!%cqXJshqsp~m3WISwlJh}Z|BwFsa3Puno2spY&@ROyQ9E+zRBvfE9A|~$kXqv z6B}Ob_YXK*dq(uIvE9}XXM5*fnIhFsF*9M3Y+v$?f)8s}ZTskiwP8;Zmba>-dq;Q2 swOjp-{igh?qWDbj&x1Zp}@CP3WTf1pBX89+MzD8nvO|4+g31aR2}S From 8ffee678c69495ca5529728938751a84d5c3cb8d Mon Sep 17 00:00:00 2001 From: Samar Sunkaria Date: Mon, 13 May 2024 17:21:28 +0200 Subject: [PATCH 04/48] Add support for R_ARM_REL32 relocations. (#3631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is fairly straightforward to correctly resolve an R_ARM_REL32 relocation as described in in the "Relocation types" section of ARM ELF Specification (https://developer.arm.com/documentation/espc0003/1-0/?lang=en). The documentation provides the following formula: ``` S - P + A ``` where `S` is the value of the symbol (symAddr), `P` is the address of the place being relocated (relAddr), and `A` is the addend (value extracted from the storage unit being relocated, in this case). I encountered the R_ARM_REL32 relocation type as part of my work for building apps written in Swift for the Flipper Zero. I have manually tested that this relocation works correctly by building and running multiple apps that depend on this relocation. Co-authored-by: あく --- lib/flipper_application/elf/elf_file.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 6543168662..398f25209a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -202,6 +202,7 @@ __attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) { STRCASE(R_ARM_NONE) STRCASE(R_ARM_TARGET1) STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_REL32) STRCASE(R_ARM_THM_PC22) STRCASE(R_ARM_THM_JUMP24) default: @@ -329,6 +330,10 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 *((uint32_t*)relAddr) += symAddr; FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; + case R_ARM_REL32: + *((uint32_t*)relAddr) += symAddr - relAddr; + FURI_LOG_D(TAG, " R_ARM_REL32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; case R_ARM_THM_PC22: case R_ARM_CALL: case R_ARM_THM_JUMP24: From 9648da951a090206aa2da127fd1834ed93506664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 13 May 2024 18:32:28 +0100 Subject: [PATCH 05/48] FuriHal: move version init to early stage (#3647) --- targets/f18/furi_hal/furi_hal.c | 2 +- targets/f7/furi_hal/furi_hal.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6cfc939b8c..247c2ee2d5 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_speaker_init(); diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 7f3959bc37..3a58fe196b 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_region_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); From b9966a50d9bcab3996a1f67d619d6cdc10878732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 13 May 2024 18:54:59 +0100 Subject: [PATCH 06/48] Ble: new connection parameters negotiation scheme (#3644) * Ble: new connection parameters negotiation scheme * Ble: finer connection parameters negotiation flow * Ble: naming and grammar * gap: typo fix & field init --------- Co-authored-by: hedger Co-authored-by: hedger --- lib/ble_profile/extra_profiles/hid_profile.c | 2 +- targets/f7/ble_glue/gap.c | 58 +++++++++++++++---- targets/f7/ble_glue/profiles/serial_profile.c | 2 +- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index 7231fdb195..85fb101b8c 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -381,7 +381,7 @@ static GapConfig template_config = { .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 2e4a74a9e6..2235a8da33 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -38,6 +38,8 @@ typedef struct { FuriThread* thread; FuriMessageQueue* command_queue; bool enable_adv; + bool is_secure; + uint8_t negotiation_round; } Gap; typedef enum { @@ -72,17 +74,46 @@ static void gap_verify_connection_parameters(Gap* gap) { // Send connection parameters request update if necessary GapConnectionParamsRequest* params = &gap->config->conn_param; - if(params->conn_int_min > gap->connection_params.conn_interval || - params->conn_int_max < gap->connection_params.conn_interval) { - FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); + + // Desired max connection interval depends on how many negotiation rounds we had in the past + // In the first negotiation round we want connection interval to be minimum + // If platform disagree then we request wider range + uint16_t connection_interval_max = gap->negotiation_round ? params->conn_int_max : + params->conn_int_min; + + // We do care about lower connection interval bound a lot: if it's lower than 30ms 2nd core will not allow us to use flash controller + bool negotiation_failed = params->conn_int_min > gap->connection_params.conn_interval; + + // We don't care about upper bound till connection become secure + if(gap->is_secure) { + negotiation_failed |= connection_interval_max < gap->connection_params.conn_interval; + } + + if(negotiation_failed) { + FURI_LOG_W( + TAG, + "Connection interval doesn't suite us. Trying to negotiate, round %u", + gap->negotiation_round + 1); if(aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, params->conn_int_min, - params->conn_int_max, + connection_interval_max, gap->connection_params.slave_latency, gap->connection_params.supervisor_timeout)) { FURI_LOG_E(TAG, "Failed to request connection parameters update"); + // The other side is not in the mood + // But we are open to try it again + gap->negotiation_round = 0; + } else { + gap->negotiation_round++; } + } else { + FURI_LOG_I( + TAG, + "Connection interval suits us. Spent %u rounds to negotiate", + gap->negotiation_round); + // Looks like the other side is open to negotiation + gap->negotiation_round = 0; } } @@ -97,9 +128,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); - } + furi_check(gap); + furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { hci_disconnection_complete_event_rp0* disconnection_complete_event = @@ -110,6 +141,8 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } + gap->is_secure = false; + gap->negotiation_round = 0; // Enterprise sleep furi_delay_us(666 + 666); if(gap->enable_adv) { @@ -211,6 +244,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE: FURI_LOG_D(TAG, "Slave security initiated"); + gap->is_secure = true; break; case ACI_GAP_BOND_LOST_VSEVT_CODE: @@ -269,9 +303,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { default: break; } - if(gap) { - furi_mutex_release(gap->state_mutex); - } + + furi_mutex_release(gap->state_mutex); + return BleEventFlowEnable; } @@ -512,6 +546,10 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); + // Set initial state + gap->is_secure = false; + gap->negotiation_round = 0; + uint8_t adv_service_uid[2]; gap->service.adv_svc_uuid_len = 1; adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 165b813307..118a76e8c3 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -47,7 +47,7 @@ static GapConfig serial_template_config = { .pairing_method = GapPairingPinCodeShow, .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }}; From 98c51d13a1f679ef455b21fe557f5dc5f7612988 Mon Sep 17 00:00:00 2001 From: fatale69 <156803833+fatale69@users.noreply.github.com> Date: Mon, 13 May 2024 22:33:20 +0300 Subject: [PATCH 07/48] vscode: config fixes (#3587) * Update extensions.json * vscode: fixed extensions config for clangd; updated paths for current toolchain location convention --------- Co-authored-by: hedger Co-authored-by: hedger --- .vscode/example/clangd/extensions.json | 5 +++-- .vscode/example/cpptools/c_cpp_properties.json | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json index daab417cd0..4f24dd7b5d 100644 --- a/.vscode/example/clangd/extensions.json +++ b/.vscode/example/clangd/extensions.json @@ -8,7 +8,8 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ @@ -16,4 +17,4 @@ "ms-vscode.cpptools", "ms-vscode.cmake-tools" ] -} +} \ No newline at end of file diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json index 3f8d15a5d4..245d44ca1c 100644 --- a/.vscode/example/cpptools/c_cpp_properties.json +++ b/.vscode/example/cpptools/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "Win32", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -10,7 +10,7 @@ }, { "name": "Linux", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -18,7 +18,7 @@ }, { "name": "Mac", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", From a7715704f89990963d33b0c1fcb405de384d8400 Mon Sep 17 00:00:00 2001 From: hakuyoku2011 <92091283+hakuyoku2011@users.noreply.github.com> Date: Tue, 14 May 2024 21:15:30 +0900 Subject: [PATCH 08/48] Infrared: Add Toshiba RAS-2518D (#3635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Infrared: Add Toshiba RAS-2518D * Infrared: cleanup AC universal remote Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 4e1735246e..68abaf2592 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -826,3 +826,35 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 +# +# Model: Toshiba RAS-2518D +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520 From 5f9b300ab2a55a1a8424c11078e7899d0e25d912 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 14 May 2024 13:34:27 +0100 Subject: [PATCH 09/48] NFC: Mf Desfire fix reading big files (#3616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mf desfire: fix incorrect long files reading * nfc app: trim record size for mf desfire render * mf desfire: rework reading long record files * mf desfire: more robust size check Co-authored-by: あく --- .../mf_desfire/mf_desfire_render.c | 24 +++--- .../mf_desfire/mf_desfire_poller_i.c | 77 ++++++++++--------- 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index f8eacd51a2..23a1a3b69a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -2,6 +2,8 @@ #include "../iso14443_4a/iso14443_4a_render.h" +#define MF_DESFIRE_RENDER_MAX_RECORD_SIZE (256U) + void nfc_render_mf_desfire_info( const MfDesfireData* data, NfcProtocolFormatType format_type, @@ -212,8 +214,6 @@ void nfc_render_mf_desfire_file_settings_data( uint32_t record_count = 1; uint32_t record_size = 0; - const uint32_t total_size = simple_array_get_count(data->data); - switch(settings->type) { case MfDesfireFileTypeStandard: case MfDesfireFileTypeBackup: @@ -257,17 +257,14 @@ void nfc_render_mf_desfire_file_settings_data( return; } + // Limit record size + bool trim_data = record_size > MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + if(trim_data) { + record_size = MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + } + for(uint32_t rec = 0; rec < record_count; rec++) { - const uint32_t size_offset = rec * record_size; - const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0; - - if(size_remaining < record_size) { - furi_string_cat_printf( - str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size); - record_size = size_remaining; - } else { - furi_string_cat_printf(str, "record %lu\n", rec); - } + furi_string_cat_printf(str, "record %lu\n", rec); for(uint32_t ch = 0; ch < record_size; ch += 4) { furi_string_cat_printf(str, "%03lx|", ch); @@ -296,6 +293,9 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_push_back(str, '\n'); } + if(trim_data) { + furi_string_cat_str(str, "..."); + } furi_string_push_back(str, '\n'); } diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 2b86318491..1dd6c50e10 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -75,10 +75,10 @@ MfDesfireError mf_desfire_send_chunks( const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - if(rx_size <= rx_capacity_remaining) { + if(rx_size <= rx_capacity_remaining + 1) { bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size); + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); } } } while(false); @@ -327,36 +327,63 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } -MfDesfireError mf_desfire_poller_read_file_data( +static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, + uint8_t read_cmd, uint32_t offset, size_t size, MfDesfireFileData* data) { furi_check(instance); furi_check(data); - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + MfDesfireError error = MfDesfireErrorNone; + simple_array_init(data->data, size); - MfDesfireError error; + size_t buffer_capacity = bit_buffer_get_capacity_bytes(instance->result_buffer); + uint32_t current_offset = offset; + uint32_t bytes_read = 0; - do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + while(bytes_read < size) { + size_t bytes_to_read = MIN(buffer_capacity, size - bytes_read); + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, read_cmd); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); + error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); + if(bytes_received != bytes_to_read) { + FURI_LOG_W(TAG, "Read %zu out of %zu bytes", bytes_received, bytes_to_read); error = MfDesfireErrorProtocol; + break; } - } while(false); + + uint8_t* file_data = simple_array_get_data(data->data); + bit_buffer_write_bytes(instance->result_buffer, &file_data[current_offset], bytes_to_read); + bytes_read += bytes_to_read; + current_offset += bytes_to_read; + } + + if(error != MfDesfireErrorNone) { + simple_array_reset(data->data); + } return error; } +MfDesfireError mf_desfire_poller_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + return mf_desfire_poller_read_file(instance, id, MF_DESFIRE_CMD_READ_DATA, offset, size, data); +} + MfDesfireError mf_desfire_poller_read_file_value( MfDesfirePoller* instance, MfDesfireFileId id, @@ -389,28 +416,8 @@ MfDesfireError mf_desfire_poller_read_file_records( uint32_t offset, size_t size, MfDesfireFileData* data) { - furi_check(instance); - furi_check(data); - - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); - - MfDesfireError error; - - do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - - if(error != MfDesfireErrorNone) break; - - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; - } - } while(false); - - return error; + return mf_desfire_poller_read_file( + instance, id, MF_DESFIRE_CMD_READ_RECORDS, offset, size, data); } MfDesfireError mf_desfire_poller_read_file_data_multi( From a86aeface5a33f9ffdbeceb806a8ba01611cd45c Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Tue, 14 May 2024 17:27:35 +0300 Subject: [PATCH 10/48] electra lfrfid protocol implemented (#3640) --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_electra.c | 439 ++++++++++++++++++++++++ lib/lfrfid/protocols/protocol_electra.h | 4 + lib/lfrfid/protocols/protocol_em4100.c | 23 +- 5 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 lib/lfrfid/protocols/protocol_electra.c create mode 100644 lib/lfrfid/protocols/protocol_electra.h diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index a8d0ff2804..175d64c2b2 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -1,5 +1,6 @@ #include "lfrfid_protocols.h" #include "protocol_em4100.h" +#include "protocol_electra.h" #include "protocol_h10301.h" #include "protocol_idteck.h" #include "protocol_indala26.h" @@ -22,6 +23,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolEM410032] = &protocol_em4100_32, [LFRFIDProtocolEM410016] = &protocol_em4100_16, + [LFRFIDProtocolElectra] = &protocol_electra, [LFRFIDProtocolH10301] = &protocol_h10301, [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 64a9fcba2e..89aa51c251 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -11,6 +11,7 @@ typedef enum { LFRFIDProtocolEM4100, LFRFIDProtocolEM410032, LFRFIDProtocolEM410016, + LFRFIDProtocolElectra, LFRFIDProtocolH10301, LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c new file mode 100644 index 0000000000..b94e60fbe3 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -0,0 +1,439 @@ +/* + * Electra intercom rfid protocol (Romania) + * + * Based on EM4100 protocol implementation from https://github.com/flipperdevices/flipperzero-firmware/blob/dev/lib/lfrfid/protocols/protocol_em4100.c + * + * Copyright 2024 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 . + * ------------------------------------------------------------------------------------------------------------------------------ + * PROTOCOL DESCRIPTION: + * ------------------------------------------------------------------------------------------------------------------------------ + * Electra intercom 125 kHz protocol based on 64-bit clock EM4100, but includes some extra data after base EM4100 data (epilogue) + * + * Epilogue size is 64 bits, but only first 16 bits matter. Rest 6 bytes - some filler data, + * that arbitrary change is not validated by the Electra intercoms + * + * There are curently three known types of epilogue: + * - 0x7E71AAAAAAAAAAAA (AA filler) + * - 0x7E71000000000000 (00 filler) + * - 0x0030AAAAAAAAAAAA + * + * First two epilogue bytes may be interpreted as EM4100 data continuation + * Nevertheless, these bytes have correct row parity bits, but have not correct collumn parity + + * For example: 0x7E71AAAAAAAAAAAA epilogue: + * + * In binary: | 0b01111110 | 01110001 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | + * In hex: | 0x7E | 71 | AA | AA | AA | AA | AA | AA | + * + * As EM4100 data: + * 0111 1 // 7 + * 1100 0 // C + * 0111 1 // 7 + * 1101 0 // here epilogue filler starts (from second bit) + * 1010 1 // there is no correct raw parity bits anymore + * 0101 0 + * 1010 1 + * 0101 0 + * 1010 // and no correct column parity + */ + +#include "bit_lib/bit_lib.h" +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define TAG "ELECTRA" + +typedef uint64_t ElectraDecodedData; + +#define EM_HEADER_POS (55) +#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) + +#define EM_FIRST_ROW_POS (50) + +#define EM_ROW_COUNT (10) +#define EM_COLUMN_COUNT (4) +#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) + +#define EM_COLUMN_POS (4) +#define ELECTRA_STOP_POS (0) +#define ELECTRA_STOP_MASK (0x1LLU << ELECTRA_STOP_POS) + +#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | ELECTRA_STOP_MASK) +#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) + +#define ELECTRA_DECODED_BASE_DATA_SIZE (5) +#define ELECTRA_ENCODED_BASE_DATA_SIZE (sizeof(ElectraDecodedData)) + +#define ELECTRA_DECODED_EPILOGUE_SIZE (3) +#define ELECTRA_ENCODED_EPILOGUE_SIZE (sizeof(ElectraDecodedData)) + +#define ELECTRA_DECODED_DATA_SIZE (ELECTRA_DECODED_BASE_DATA_SIZE + ELECTRA_DECODED_EPILOGUE_SIZE) +#define ELECTRA_ENCODED_DATA_SIZE (ELECTRA_ENCODED_BASE_DATA_SIZE + ELECTRA_ENCODED_EPILOGUE_SIZE) + +#define ELECTRA_DECODED_DATA_EPILOGUE_START_POS (ELECTRA_DECODED_BASE_DATA_SIZE) + +#define ELECTRA_CLOCK_PER_BIT (64) + +#define ELECTRA_READ_SHORT_TIME (256) +#define ELECTRA_READ_LONG_TIME (512) +#define ELECTRA_READ_JITTER_TIME (100) + +#define ELECTRA_READ_SHORT_TIME_LOW (ELECTRA_READ_SHORT_TIME - ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_SHORT_TIME_HIGH (ELECTRA_READ_SHORT_TIME + ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_LONG_TIME_LOW (ELECTRA_READ_LONG_TIME - ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_LONG_TIME_HIGH (ELECTRA_READ_LONG_TIME + ELECTRA_READ_JITTER_TIME) + +#define EM_ENCODED_DATA_HEADER (0xFF80000000000000ULL) + +typedef struct { + uint8_t data[ELECTRA_DECODED_DATA_SIZE]; + + ElectraDecodedData encoded_base_data; + ElectraDecodedData encoded_epilogue; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolElectra; + +ProtocolElectra* protocol_electra_alloc(void) { + ProtocolElectra* proto = malloc(sizeof(ProtocolElectra)); + return (void*)proto; +}; + +void protocol_electra_free(ProtocolElectra* proto) { + free(proto); +}; + +uint8_t* protocol_electra_get_data(ProtocolElectra* proto) { + return proto->data; +}; + +static void electra_decode( + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, + const uint8_t* encoded_epilogue, + const uint8_t encoded_epilogue_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) { + furi_check(decoded_data_size >= ELECTRA_DECODED_DATA_SIZE); + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); + furi_check(encoded_epilogue_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); + + uint8_t decoded_data_index = 0; + ElectraDecodedData base_data = *((ElectraDecodedData*)(encoded_base_data)); + //ElectraDecodedData epilogue = *((ElectraDecodedData*)(encoded_epilogue)); + + // clean result + memset(decoded_data, 0, decoded_data_size); + + // header + for(uint8_t i = 0; i < 9; i++) { + base_data = base_data << 1; + } + + // nibbles + uint8_t value = 0; + for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { + uint8_t nibble = 0; + for(uint8_t i = 0; i < 5; i++) { + if(i < 4) nibble = (nibble << 1) | (base_data & (1LLU << 63) ? 1 : 0); + base_data = base_data << 1; + } + value = (value << 4) | nibble; + if(r % 2) { + decoded_data[decoded_data_index] |= value; + decoded_data_index++; + value = 0; + } + } + + // copy first 3 bytes of encoded epilogue to decoded data + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 1]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 2]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 3]; +} + +static bool electra_can_be_decoded( + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, + const uint8_t* encoded_epilogue_data, + const uint8_t encoded_epilogue_data_size) { + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); + furi_check(encoded_epilogue_data_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); + const ElectraDecodedData* base_data = (ElectraDecodedData*)encoded_base_data; + const ElectraDecodedData* epilogue = (ElectraDecodedData*)encoded_epilogue_data; + + // check electra epilogue. if em4100 header - break + if((*epilogue & EM_ENCODED_DATA_HEADER) == EM_ENCODED_DATA_HEADER) return false; + + // check header and stop bit + if((*base_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; + + // check row parity + for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { + parity_sum += (*base_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; + } + + if((parity_sum % 2)) { + return false; + } + } + + // check columns parity + for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { + parity_sum += (*base_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; + } + + if((parity_sum % 2)) { + FURI_LOG_D( + TAG, + "Unexpected column parity found. EM4100 data: %016llX", + bit_lib_bytes_to_num_be(encoded_base_data, encoded_base_data_size)); + return false; + } + } + + // encoded_epilogue_data lsb encoded + uint8_t epilogue_filler = encoded_epilogue_data[(ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2]; + + for(uint8_t i = 0; i < ((ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2); i++) + if(encoded_epilogue_data[i] != epilogue_filler) { + FURI_LOG_D(TAG, "Unexpected epilogue filler found: %016llX", *epilogue); + return false; + } + + return true; +} + +void protocol_electra_decoder_start(ProtocolElectra* proto) { + memset(proto->data, 0, ELECTRA_DECODED_DATA_SIZE); + proto->encoded_base_data = 0; + proto->encoded_epilogue = 0; + + manchester_advance( + proto->decoder_manchester_state, + ManchesterEventReset, + &proto->decoder_manchester_state, + NULL); +}; + +bool protocol_electra_decoder_feed(ProtocolElectra* proto, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > ELECTRA_READ_SHORT_TIME_LOW && duration < ELECTRA_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > ELECTRA_READ_LONG_TIME_LOW && duration < ELECTRA_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); + + if(data_ok) { + /* + EM 4100 BASE DATA (64 bit) ELECTRA EPILOGUE (64 bit) + _________________________________ _________________________________ + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | <- new data bit + --------------------------------- --------------------------------- + <- epilogue msb is carry bit to base data + */ + bool carry = proto->encoded_epilogue >> 63 & 0b1; + + proto->encoded_base_data = (proto->encoded_base_data << 1) | carry; + proto->encoded_epilogue = (proto->encoded_epilogue << 1) | data; + + if(electra_can_be_decoded( + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, + (uint8_t*)&proto->encoded_epilogue, + ELECTRA_ENCODED_EPILOGUE_SIZE)) { + electra_decode( + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, + (uint8_t*)&proto->encoded_epilogue, + ELECTRA_ENCODED_EPILOGUE_SIZE, + proto->data, + ELECTRA_DECODED_DATA_SIZE); + result = true; + } + } + } + + return result; +}; + +static void em_write_nibble(bool low_nibble, uint8_t data, ElectraDecodedData* encoded_base_data) { + uint8_t parity_sum = 0; + uint8_t start = 0; + if(!low_nibble) start = 4; + + for(int8_t i = (start + 3); i >= start; i--) { + parity_sum += (data >> i) & 1; + *encoded_base_data = (*encoded_base_data << 1) | ((data >> i) & 1); + } + + *encoded_base_data = (*encoded_base_data << 1) | ((parity_sum % 2) & 1); +} + +bool protocol_electra_encoder_start(ProtocolElectra* proto) { + // header + proto->encoded_base_data = 0b111111111; + + // data + for(uint8_t i = 0; i < ELECTRA_DECODED_BASE_DATA_SIZE; i++) { + em_write_nibble(false, proto->data[i], &proto->encoded_base_data); + em_write_nibble(true, proto->data[i], &proto->encoded_base_data); + } + + // column parity and stop bit + uint8_t parity_sum; + + for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { + parity_sum = 0; + for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { + uint8_t parity_bit = (proto->encoded_base_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; + parity_sum += parity_bit; + } + proto->encoded_base_data = (proto->encoded_base_data << 1) | ((parity_sum % 2) & 1); + } + + // stop bit + proto->encoded_base_data = (proto->encoded_base_data << 1) | 0; + + proto->encoded_data_index = 0; + proto->encoded_polarity = true; + + // epilogue + proto->encoded_epilogue = (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS]); + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1]); + + //fill bytes 2-7 by epilogue filler + for(uint8_t i = 2; i < ELECTRA_ENCODED_EPILOGUE_SIZE; i++) { + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2]; + } + + return true; +}; + +LevelDuration protocol_electra_encoder_yield(ProtocolElectra* proto) { + bool level; + if(proto->encoded_data_index < 64) + level = (proto->encoded_base_data >> (63 - proto->encoded_data_index)) & 1; + else + level = (proto->encoded_epilogue >> (63 - (proto->encoded_data_index - 64))) & 1; + + uint32_t duration = ELECTRA_CLOCK_PER_BIT / 2; + + if(proto->encoded_polarity) { + proto->encoded_polarity = false; + } else { + level = !level; + + proto->encoded_polarity = true; + proto->encoded_data_index++; + if(proto->encoded_data_index >= 128) { + proto->encoded_data_index = 0; + } + } + + return level_duration_make(level, duration); +}; + +bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + // Correct protocol data by redecoding + protocol_electra_encoder_start(protocol); + electra_decode( + (uint8_t*)&protocol->encoded_base_data, + sizeof(ElectraDecodedData), + (uint8_t*)&protocol->encoded_epilogue, + sizeof(ElectraDecodedData), + protocol->data, + ELECTRA_DECODED_DATA_SIZE); + + protocol_electra_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_64 | + (4 << LFRFID_T5577_MAXBLOCK_SHIFT)); + request->t5577.block[1] = protocol->encoded_base_data >> 32; + request->t5577.block[2] = protocol->encoded_base_data & 0xFFFFFFFF; + request->t5577.block[3] = protocol->encoded_epilogue >> 32; + request->t5577.block[4] = protocol->encoded_epilogue & 0xFFFFFFFF; + request->t5577.blocks_to_write = 5; + result = true; + } + return result; +}; + +void protocol_electra_render_data(ProtocolElectra* protocol, FuriString* result) { + furi_string_printf(result, "Epilogue: %016llX", protocol->encoded_epilogue); +}; + +const ProtocolBase protocol_electra = { + .name = "Electra", + .manufacturer = "Electra Group", + .data_size = ELECTRA_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_electra_alloc, + .free = (ProtocolFree)protocol_electra_free, + .get_data = (ProtocolGetData)protocol_electra_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_electra_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_electra_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_electra_encoder_start, + .yield = (ProtocolEncoderYield)protocol_electra_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_electra_render_data, + .render_brief_data = (ProtocolRenderData)protocol_electra_render_data, + .write_data = (ProtocolWriteData)protocol_electra_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_electra.h b/lib/lfrfid/protocols/protocol_electra.h new file mode 100644 index 0000000000..2c9f79285f --- /dev/null +++ b/lib/lfrfid/protocols/protocol_electra.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_electra; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 055d06d868..a4a403167a 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -4,6 +4,7 @@ #include "lfrfid_protocols.h" typedef uint64_t EM4100DecodedData; +typedef uint64_t EM4100Epilogue; #define EM_HEADER_POS (55) #define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) @@ -28,10 +29,13 @@ typedef uint64_t EM4100DecodedData; #define EM_READ_LONG_TIME_BASE (512) #define EM_READ_JITTER_TIME_BASE (100) +#define EM_ENCODED_DATA_HEADER (0xFF80000000000000ULL) + typedef struct { uint8_t data[EM4100_DECODED_DATA_SIZE]; EM4100DecodedData encoded_data; + EM4100Epilogue encoded_epilogue; uint8_t encoded_data_index; bool encoded_polarity; @@ -147,9 +151,16 @@ static void em4100_decode( } } -static bool em4100_can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { +static bool em4100_can_be_decoded( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + const uint8_t* encoded_epilogue) { furi_check(encoded_data_size >= EM4100_ENCODED_DATA_SIZE); const EM4100DecodedData* card_data = (EM4100DecodedData*)encoded_data; + const EM4100Epilogue* epilogue = (EM4100Epilogue*)encoded_epilogue; + + // check first 9 bytes on epilogue (to prevent conflict with Electra protocol) + if((*epilogue & EM_ENCODED_DATA_HEADER) != EM_ENCODED_DATA_HEADER) return false; // check header and stop bit if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; @@ -221,9 +232,15 @@ bool protocol_em4100_decoder_feed(ProtocolEM4100* proto, bool level, uint32_t du proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); if(data_ok) { - proto->encoded_data = (proto->encoded_data << 1) | data; + bool carry = proto->encoded_epilogue >> 63 & 0b1; + + proto->encoded_data = (proto->encoded_data << 1) | carry; + proto->encoded_epilogue = (proto->encoded_epilogue << 1) | data; - if(em4100_can_be_decoded((uint8_t*)&proto->encoded_data, sizeof(EM4100DecodedData))) { + if(em4100_can_be_decoded( + (uint8_t*)&proto->encoded_data, + sizeof(EM4100DecodedData), + (uint8_t*)&proto->encoded_epilogue)) { em4100_decode( (uint8_t*)&proto->encoded_data, sizeof(EM4100DecodedData), From e3f95a326bab32c9fd3c3dd66a0fe9ec7b6e9105 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 15 May 2024 03:58:09 +0900 Subject: [PATCH 11/48] [FL-3797] Settings menu refactoring (#3632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Settings menu refactoring * Update F18 api * Wording changes * Update certification icon * Desktop: optimize settings save routine, fix navigation lag * Gui: add submenu position getter. Desktop: proper menu index preservation. * Gui: proper index getter for submenu. Desktop: cleaner settings navigation. Co-authored-by: あく --- applications/services/gui/elements.h | 24 ++- applications/services/gui/modules/submenu.c | 39 ++++ applications/services/gui/modules/submenu.h | 20 +- applications/settings/about/about.c | 12 +- .../bt_settings_scene_forget_dev_confirm.c | 6 +- .../scenes/bt_settings_scene_start.c | 2 +- .../desktop_settings/desktop_settings_app.c | 4 + .../desktop_settings/desktop_settings_app.h | 4 +- .../scenes/desktop_settings_scene_config.h | 3 + .../scenes/desktop_settings_scene_favorite.c | 84 ++++++-- .../scenes/desktop_settings_scene_pin_menu.c | 7 +- .../scenes/desktop_settings_scene_pin_setup.c | 1 + .../desktop_settings_scene_pin_setup_done.c | 1 + ...settings_scene_quick_apps_direction_menu.c | 187 ++++++++++++++++++ .../desktop_settings_scene_quick_apps_menu.c | 80 ++++++++ .../scenes/desktop_settings_scene_start.c | 78 +------- .../desktop_settings_view_pin_setup_howto2.c | 4 +- .../power_settings_app/power_settings_app.h | 2 + .../scenes/power_settings_scene_config.h | 1 + .../scenes/power_settings_scene_power_off.c | 10 +- .../scenes/power_settings_scene_reboot.c | 12 +- .../power_settings_scene_reboot_confirm.c | 66 +++++++ .../scenes/storage_settings_scene_benchmark.c | 28 ++- ...storage_settings_scene_benchmark_confirm.c | 70 +++++++ .../scenes/storage_settings_scene_config.h | 1 + .../storage_settings_scene_factory_reset.c | 8 +- .../storage_settings_scene_format_confirm.c | 4 +- .../storage_settings_scene_formatting.c | 15 +- .../storage_settings_scene_internal_info.c | 2 +- .../scenes/storage_settings_scene_sd_info.c | 30 ++- .../scenes/storage_settings_scene_start.c | 2 +- .../About/CertificationChina1_122x47.png | Bin 5215 -> 0 bytes .../About/CertificationChina1_124x47.png | Bin 0 -> 429 bytes ...h_55x52.png => LoadingHourglass_24x24.png} | Bin 3898 -> 3650 bytes assets/icons/Settings/dolph_cry_49x54.png | Bin 0 -> 973 bytes assets/icons/Settings/qr_benchmark_25x25.png | Bin 0 -> 395 bytes targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- 38 files changed, 673 insertions(+), 142 deletions(-) create mode 100644 applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c create mode 100644 applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c create mode 100644 applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c create mode 100644 applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c delete mode 100644 assets/icons/About/CertificationChina1_122x47.png create mode 100644 assets/icons/About/CertificationChina1_124x47.png rename assets/icons/Settings/{Cry_dolph_55x52.png => LoadingHourglass_24x24.png} (70%) create mode 100644 assets/icons/Settings/dolph_cry_49x54.png create mode 100644 assets/icons/Settings/qr_benchmark_25x25.png diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 833d5d6eec..54d78420ea 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -121,7 +121,8 @@ void elements_multiline_text_aligned( /** Draw multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -129,7 +130,8 @@ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* t /** Draw framed multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -137,8 +139,10 @@ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const /** Draw slightly rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_slightly_rounded_frame( Canvas* canvas, @@ -150,8 +154,10 @@ void elements_slightly_rounded_frame( /** Draw slightly rounded box * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of box + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width height of box + * @param height height of box */ void elements_slightly_rounded_box( Canvas* canvas, @@ -163,8 +169,10 @@ void elements_slightly_rounded_box( /** Draw bold rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 35916fe236..5b1bdccda0 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -215,6 +215,26 @@ void submenu_add_item( true); } +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { + furi_check(submenu); + furi_check(label); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + SubmenuItemArray_it_t it; + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + if(index == SubmenuItemArray_cref(it)->index) { + furi_string_set_str(SubmenuItemArray_cref(it)->label, label); + break; + } + } + }, + true); +} + void submenu_reset(Submenu* submenu) { furi_check(submenu); @@ -230,6 +250,25 @@ void submenu_reset(Submenu* submenu) { true); } +uint32_t submenu_get_selected_item(Submenu* submenu) { + furi_check(submenu); + + uint32_t selected_item_index = 0; + + with_view_model( + submenu->view, + SubmenuModel * model, + { + if(model->position < SubmenuItemArray_size(model->items)) { + const SubmenuItem* item = SubmenuItemArray_cget(model->items, model->position); + selected_item_index = item->index; + } + }, + false); + + return selected_item_index; +} + void submenu_set_selected_item(Submenu* submenu, uint32_t index) { furi_check(submenu); with_view_model( diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index 6ae148eb5b..e435f94a29 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -53,16 +53,32 @@ void submenu_add_item( SubmenuItemCallback callback, void* callback_context); +/** Change label of an existing item + * + * @param submenu Submenu instance + * @param index The index of the item + * @param label The new label + */ +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); + /** Remove all items from submenu * * @param submenu Submenu instance */ void submenu_reset(Submenu* submenu); -/** Set submenu item selector +/** Get submenu selected item index + * + * @param submenu Submenu instance + * + * @return Index of the selected item + */ +uint32_t submenu_get_selected_item(Submenu* submenu); + +/** Set submenu selected item by index * * @param submenu Submenu instance - * @param index The index + * @param index The index of the selected item */ void submenu_set_selected_item(Submenu* submenu, uint32_t index); diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 8f0798d9cf..973d1f4814 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -41,7 +41,7 @@ static DialogMessageButton about_screen_product(DialogsApp* dialogs, DialogMessa static DialogMessageButton about_screen_address(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - const char* screen_text = "Flipper Devices Inc\n" + const char* screen_text = "Flipper Devices Inc.\n" "Suite B #551, 2803\n" "Philadelphia Pike, Claymont\n" "DE, USA 19703\n"; @@ -56,7 +56,7 @@ static DialogMessageButton about_screen_compliance(DialogsApp* dialogs, DialogMe DialogMessageButton result; const char* screen_text = "For all compliance\n" - "certificates please visit:\n" + "certificates, please visit:\n" "www.flipp.dev/compliance"; dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); @@ -97,7 +97,7 @@ static DialogMessageButton about_screen_cert_china_0(DialogsApp* dialogs, Dialog 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_icon(message, &I_CertificationChina1_124x47, 3, 3); dialog_message_set_text( message, furi_hal_version_get_srrc_id(), 55, 11, AlignLeft, AlignBottom); result = dialog_message_show(dialogs, message); @@ -227,9 +227,11 @@ int32_t about_settings_app(void* p) { while(1) { if(screen_index >= COUNT_OF(about_screens) - 1) { - dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_buttons(message, "Prev.", NULL, NULL); + } else if(screen_index == 0) { + dialog_message_set_buttons(message, NULL, NULL, "Next"); } else { - dialog_message_set_buttons(message, "Back", NULL, "Next"); + dialog_message_set_buttons(message, "Prev.", NULL, "Next"); } screen_result = about_screens[screen_index](dialogs, message); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 31921b9f33..d72cb95e73 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -10,10 +10,10 @@ void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void bt_settings_scene_forget_dev_confirm_on_enter(void* context) { BtSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog, "Back"); + dialog, "All previous pairings\nwill be lost!", 64, 14, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(dialog, "Cancel"); dialog_ex_set_right_button_text(dialog, "Unpair"); dialog_ex_set_context(dialog, app); dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index c148f09435..1d72a9e6fa 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -53,7 +53,7 @@ void bt_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, BtSettingOff); variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); } - variable_item_list_add(var_item_list, "Forget All Paired Devices", 1, NULL, NULL); + variable_item_list_add(var_item_list, "Unpair All Devices", 1, NULL, NULL); variable_item_list_set_enter_callback( var_item_list, bt_settings_scene_start_var_list_enter_callback, app); } else { diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index b030656f7f..238d866f2e 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -92,6 +92,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); DESKTOP_SETTINGS_LOAD(&app->settings); + if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); } else { @@ -99,6 +100,9 @@ extern int32_t desktop_settings_app(void* p) { } view_dispatcher_run(app->view_dispatcher); + + DESKTOP_SETTINGS_SAVE(&app->settings); desktop_settings_app_free(app); + return 0; } diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 6f97564c94..1a2c733eda 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -40,5 +40,7 @@ typedef struct { PinCode pincode_buffer; bool pincode_buffer_filled; - uint8_t menu_idx; + uint32_t pin_menu_idx; + uint32_t quick_apps_menu_idx; + uint32_t quick_apps_direction_menu_idx; } DesktopSettingsApp; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h index 5bc52172b9..52b8829bee 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -9,3 +9,6 @@ ADD_SCENE(desktop_settings, pin_setup, PinSetup) ADD_SCENE(desktop_settings, pin_setup_howto, PinSetupHowto) ADD_SCENE(desktop_settings, pin_setup_howto2, PinSetupHowto2) ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone) + +ADD_SCENE(desktop_settings, quick_apps_menu, QuickAppsMenu) +ADD_SCENE(desktop_settings, quick_apps_direction_menu, QuickAppsDirectionMenu) \ No newline at end of file diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index db5d231ffa..d77c8adda1 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -9,11 +9,17 @@ #define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT) #define DEFAULT_INDEX (0) -#define EXTERNAL_BROWSER_NAME ("Apps Menu (Default)") -#define PASSPORT_NAME ("Passport (Default)") +#define EXTERNAL_BROWSER_NAME ("( ) Apps Menu (Default)") +#define EXTERNAL_BROWSER_NAME_SELECTED ("(*) Apps Menu (Default)") +#define PASSPORT_NAME ("( ) Passport (Default)") +#define PASSPORT_NAME_SELECTED ("(*) Passport (Default)") + +#define SELECTED_PREFIX ("(*) ") +#define NOT_SELECTED_PREFIX ("( ) ") #define EXTERNAL_APPLICATION_INDEX (1) -#define EXTERNAL_APPLICATION_NAME ("[Select App]") +#define EXTERNAL_APPLICATION_NAME ("( ) [Select App]") +#define EXTERNAL_APPLICATION_NAME_SELECTED ("(*) [Select App]") #define PRESELECTED_SPECIAL 0xffffffff @@ -61,7 +67,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); uint32_t pre_select_item = PRESELECTED_SPECIAL; FavoriteApp* curr_favorite_app = NULL; - bool is_dummy_app = false; bool default_passport = false; if((favorite_id & SCENE_STATE_SET_DUMMY_APP) == 0) { @@ -74,7 +79,6 @@ void desktop_settings_scene_favorite_on_enter(void* context) { favorite_id &= ~(SCENE_STATE_SET_DUMMY_APP); furi_assert(favorite_id < DummyAppNumber); curr_favorite_app = &app->settings.dummy_apps[favorite_id]; - is_dummy_app = true; default_passport = true; } @@ -94,29 +98,76 @@ void desktop_settings_scene_favorite_on_enter(void* context) { desktop_settings_scene_favorite_submenu_callback, app); - if(!is_dummy_app) { - for(size_t i = 0; i < APPS_COUNT; i++) { - const char* name = favorite_fap_get_app_name(i); + FuriString* full_name = furi_string_alloc(); - submenu_add_item( - submenu, name, i + 2, desktop_settings_scene_favorite_submenu_callback, app); + for(size_t i = 0; i < APPS_COUNT; i++) { + const char* name = favorite_fap_get_app_name(i); - // Select favorite item in submenu - if(!strcmp(name, curr_favorite_app->name_or_path)) { - pre_select_item = i + 2; - } + // Add the prefix + furi_string_reset(full_name); + if(!strcmp(name, curr_favorite_app->name_or_path)) { + furi_string_set_str(full_name, SELECTED_PREFIX); + } else { + furi_string_set_str(full_name, NOT_SELECTED_PREFIX); + } + furi_string_cat_str(full_name, name); + + submenu_add_item( + submenu, + furi_string_get_cstr(full_name), + i + 2, + desktop_settings_scene_favorite_submenu_callback, + app); + + // Select favorite item in submenu + if(!strcmp(name, curr_favorite_app->name_or_path)) { + pre_select_item = i + 2; } } if(pre_select_item == PRESELECTED_SPECIAL) { if(curr_favorite_app->name_or_path[0] == '\0') { pre_select_item = DEFAULT_INDEX; + submenu_change_item_label( + submenu, + DEFAULT_INDEX, + default_passport ? (PASSPORT_NAME_SELECTED) : (EXTERNAL_BROWSER_NAME_SELECTED)); } else { pre_select_item = EXTERNAL_APPLICATION_INDEX; + submenu_change_item_label( + submenu, EXTERNAL_APPLICATION_INDEX, EXTERNAL_APPLICATION_NAME_SELECTED); } } - submenu_set_header(submenu, is_dummy_app ? ("Dummy Mode App") : ("Favorite App")); + switch(favorite_id) { + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort: + submenu_set_header(submenu, "Left - Short"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong: + submenu_set_header(submenu, "Left - Long"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort: + submenu_set_header(submenu, "Right - Short"); + break; + case SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong: + submenu_set_header(submenu, "Right - Long"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppLeft: + submenu_set_header(submenu, "Left"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppRight: + submenu_set_header(submenu, "Right"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppDown: + submenu_set_header(submenu, "Down"); + break; + case SCENE_STATE_SET_DUMMY_APP | DummyAppOk: + submenu_set_header(submenu, "Middle"); + break; + default: + break; + } + submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch. view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); @@ -177,6 +228,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e scene_manager_previous_scene(app->scene_manager); }; consumed = true; + + DESKTOP_SETTINGS_SAVE(&app->settings); } furi_string_free(temp_path); @@ -185,6 +238,5 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e void desktop_settings_scene_favorite_on_exit(void* context) { DesktopSettingsApp* app = context; - DESKTOP_SETTINGS_SAVE(&app->settings); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 86e756ede8..9fdd688965 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -44,7 +44,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { } submenu_set_header(app->submenu, "PIN Code Settings"); - submenu_set_selected_item(app->submenu, app->menu_idx); + submenu_set_selected_item(app->submenu, app->pin_menu_idx); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } @@ -76,11 +76,16 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); } + return consumed; } void desktop_settings_scene_pin_menu_on_exit(void* context) { DesktopSettingsApp* app = context; + + app->pin_menu_idx = submenu_get_selected_item(app->submenu); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index 1603aa3372..5b8aa8638a 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -97,6 +97,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent break; } } + return consumed; } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index b829f995bc..4cef4ba986 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -27,6 +27,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) { DESKTOP_SETTINGS_SAVE(&app->settings); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_blink_green_10); furi_record_close(RECORD_NOTIFICATION); desktop_view_pin_input_set_context(app->pin_input_view, app); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c new file mode 100644 index 0000000000..dbc877ede7 --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_direction_menu.c @@ -0,0 +1,187 @@ +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +enum QuickAppsSubmenuIndex { + QuickAppsSubmenuIndexFavoriteLeftClick, + QuickAppsSubmenuIndexFavoriteRightClick, + QuickAppsSubmenuIndexFavoriteLeftHold, + QuickAppsSubmenuIndexFavoriteRightHold, + QuickAppsSubmenuIndexDummyLeftClick, + QuickAppsSubmenuIndexDummyRightClick, + QuickAppsSubmenuIndexDummyDownClick, + QuickAppsSubmenuIndexDummyMiddleClick, +}; + +static void desktop_settings_scene_quick_apps_direction_menu_submenu_callback( + void* context, + uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_quick_apps_direction_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_reset(submenu); + + uint32_t favorite_id = scene_manager_get_scene_state( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + + if(favorite_id == SCENE_STATE_SET_FAVORITE_APP) { + submenu_add_item( + submenu, + "Left - Click", + QuickAppsSubmenuIndexFavoriteLeftClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Click", + QuickAppsSubmenuIndexFavoriteRightClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Left - Hold", + QuickAppsSubmenuIndexFavoriteLeftHold, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Hold", + QuickAppsSubmenuIndexFavoriteRightHold, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Default Mode"); + } else { + submenu_add_item( + submenu, + "Left - Click", + QuickAppsSubmenuIndexDummyLeftClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Right - Click", + QuickAppsSubmenuIndexDummyRightClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Down - Click", + QuickAppsSubmenuIndexDummyDownClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Middle - Click", + QuickAppsSubmenuIndexDummyMiddleClick, + desktop_settings_scene_quick_apps_direction_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Dummy Mode"); + } + + submenu_set_selected_item(app->submenu, app->quick_apps_direction_menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_quick_apps_direction_menu_on_event( + void* context, + SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case QuickAppsSubmenuIndexFavoriteLeftClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteRightClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteLeftHold: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexFavoriteRightHold: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyLeftClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppLeft); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyRightClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppRight); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyDownClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppDown); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + case QuickAppsSubmenuIndexDummyMiddleClick: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneFavorite, + SCENE_STATE_SET_DUMMY_APP | DummyAppOk); + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + consumed = true; + break; + default: + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); + } + + return consumed; +} + +void desktop_settings_scene_quick_apps_direction_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + app->quick_apps_direction_menu_idx = submenu_get_selected_item(app->submenu); + submenu_reset(app->submenu); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c new file mode 100644 index 0000000000..acba4fa255 --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c @@ -0,0 +1,80 @@ +#include +#include + +#include "../desktop_settings_app.h" +#include "desktop_settings_scene.h" +#include "desktop_settings_scene_i.h" + +#define SCENE_EVENT_SET_DEFAULT (0U) +#define SCENE_EVENT_SET_DUMMY (1U) + +static void + desktop_settings_scene_quick_apps_menu_submenu_callback(void* context, uint32_t index) { + DesktopSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void desktop_settings_scene_quick_apps_menu_on_enter(void* context) { + DesktopSettingsApp* app = context; + Submenu* submenu = app->submenu; + submenu_reset(submenu); + + submenu_add_item( + submenu, + "Default Mode", + SCENE_EVENT_SET_DEFAULT, + desktop_settings_scene_quick_apps_menu_submenu_callback, + app); + + submenu_add_item( + submenu, + "Dummy Mode", + SCENE_EVENT_SET_DUMMY, + desktop_settings_scene_quick_apps_menu_submenu_callback, + app); + + submenu_set_header(app->submenu, "Set Quick Access Apps"); + submenu_set_selected_item(app->submenu, app->quick_apps_menu_idx); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); +} + +bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SCENE_EVENT_SET_DEFAULT: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneQuickAppsDirectionMenu, + SCENE_STATE_SET_FAVORITE_APP); + scene_manager_next_scene( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + consumed = true; + break; + case SCENE_EVENT_SET_DUMMY: + scene_manager_set_scene_state( + app->scene_manager, + DesktopSettingsAppSceneQuickAppsDirectionMenu, + SCENE_STATE_SET_DUMMY_APP); + scene_manager_next_scene( + app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); + consumed = true; + break; + default: + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); + } + + return consumed; +} + +void desktop_settings_scene_quick_apps_menu_on_exit(void* context) { + DesktopSettingsApp* app = context; + app->quick_apps_menu_idx = submenu_get_selected_item(app->submenu); + submenu_reset(app->submenu); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 3e77bd8a16..8dec260165 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -9,14 +9,7 @@ typedef enum { DesktopSettingsPinSetup = 0, DesktopSettingsAutoLockDelay, DesktopSettingsClockDisplay, - DesktopSettingsFavoriteLeftShort, - DesktopSettingsFavoriteLeftLong, - DesktopSettingsFavoriteRightShort, - DesktopSettingsFavoriteRightLong, - DesktopSettingsDummyLeft, - DesktopSettingsDummyRight, - DesktopSettingsDummyDown, - DesktopSettingsDummyOk, + DesktopSettingsFavoriteApps, } DesktopSettingsEntry; #define AUTO_LOCK_DELAY_COUNT 6 @@ -93,15 +86,7 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, clock_enable_text[value_index]); - variable_item_list_add(variable_item_list, "Favorite App - Left Short", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Left Long", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Right Short", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Favorite App - Right Long", 1, NULL, NULL); - - variable_item_list_add(variable_item_list, "Dummy Mode App - Left", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Right", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Down", 1, NULL, NULL); - variable_item_list_add(variable_item_list, "Dummy Mode App - Ok", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Set Quick Access Apps", 1, NULL, NULL); variable_item_list_set_enter_callback( variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); @@ -119,62 +104,8 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinMenu); break; - case DesktopSettingsFavoriteLeftShort: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftShort); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteLeftLong: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppLeftLong); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteRightShort: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightShort); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsFavoriteRightLong: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_FAVORITE_APP | FavoriteAppRightLong); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - - case DesktopSettingsDummyLeft: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppLeft); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyRight: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppRight); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyDown: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppDown); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); - break; - case DesktopSettingsDummyOk: - scene_manager_set_scene_state( - app->scene_manager, - DesktopSettingsAppSceneFavorite, - SCENE_STATE_SET_DUMMY_APP | DummyAppOk); - scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); + case DesktopSettingsFavoriteApps: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneQuickAppsMenu); break; default: @@ -188,5 +119,4 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even void desktop_settings_scene_start_on_exit(void* context) { DesktopSettingsApp* app = context; variable_item_list_reset(app->variable_item_list); - DESKTOP_SETTINGS_SAVE(&app->settings); } diff --git a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c index b09b0b95f1..6148747a26 100644 --- a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c @@ -24,9 +24,9 @@ static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* mo elements_multiline_text_aligned( canvas, 64, - 24, - AlignCenter, + 0, AlignCenter, + AlignTop, "Forgotten PIN can only be\n" "reset with entire device.\n" "Read docs How to reset PIN."); diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h index cd05846c05..09cc5af7ae 100644 --- a/applications/settings/power_settings_app/power_settings_app.h +++ b/applications/settings/power_settings_app/power_settings_app.h @@ -30,3 +30,5 @@ typedef enum { PowerSettingsAppViewSubmenu, PowerSettingsAppViewDialog, } PowerSettingsAppView; + +typedef enum { RebootTypeDFU, RebootTypeNormal } RebootType; diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h index cc8656dcfc..f57071b9b0 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h @@ -1,4 +1,5 @@ ADD_SCENE(power_settings, start, Start) ADD_SCENE(power_settings, battery_info, BatteryInfo) ADD_SCENE(power_settings, reboot, Reboot) +ADD_SCENE(power_settings, reboot_confirm, RebootConfirm) ADD_SCENE(power_settings, power_off, PowerOff) diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index c3f5d5ad80..6cd9c5c676 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -10,12 +10,12 @@ void power_settings_scene_power_off_on_enter(void* context) { PowerSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Turn OFF Device?", 64, 2, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52); - dialog_ex_set_left_button_text(dialog, "Back"); - dialog_ex_set_right_button_text(dialog, "OFF"); + dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_context(dialog, app); diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c index 2d5dedfd42..187d969ed9 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c @@ -24,7 +24,7 @@ void power_settings_scene_reboot_on_enter(void* context) { app); submenu_add_item( submenu, - "Flipper OS", + "Reboot Flipper", PowerSettingsRebootSubmenuIndexOs, power_settings_scene_reboot_submenu_callback, app); @@ -33,14 +33,18 @@ void power_settings_scene_reboot_on_enter(void* context) { } bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); + PowerSettingsApp* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == PowerSettingsRebootSubmenuIndexDfu) { - power_reboot(PowerBootModeDfu); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeDFU); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } else if(event.event == PowerSettingsRebootSubmenuIndexOs) { - power_reboot(PowerBootModeNormal); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeNormal); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } consumed = true; } diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c new file mode 100644 index 0000000000..62e06de927 --- /dev/null +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c @@ -0,0 +1,66 @@ +#include "../power_settings_app.h" + +void power_settings_scene_reboot_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + PowerSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void power_settings_scene_reboot_confirm_on_enter(void* context) { + PowerSettingsApp* app = context; + DialogEx* dialog = app->dialog; + + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(reboot_type == RebootTypeDFU) { + dialog_ex_set_header(dialog, "Reboot to DFU Mode?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, + "Needed for device maintenance\nor firmware upgrades", + 64, + 14, + AlignCenter, + AlignTop); + } else if(reboot_type == RebootTypeNormal) { + dialog_ex_set_header(dialog, "Reboot Flipper?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, "May help with some firmware\n issues", 64, 14, AlignCenter, AlignTop); + } else { + furi_crash("Invalid reboot type"); + } + + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Reboot"); + + dialog_ex_set_result_callback(dialog, power_settings_scene_reboot_confirm_dialog_callback); + dialog_ex_set_context(dialog, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog); +} + +bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEvent event) { + PowerSettingsApp* app = context; + bool consumed = false; + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } else if(event.event == DialogExResultRight) { + if(reboot_type == RebootTypeDFU) { + power_reboot(PowerBootModeDfu); + } else { + power_reboot(PowerBootModeNormal); + } + } + consumed = true; + } + return consumed; +} + +void power_settings_scene_reboot_confirm_on_exit(void* context) { + PowerSettingsApp* app = context; + dialog_ex_reset(app->dialog); +} 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 e734c78e03..bfc9ac9c93 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -1,5 +1,7 @@ #include "../storage_settings.h" #include +#include +#include #define BENCH_DATA_SIZE 4096 #define BENCH_COUNT 6 @@ -86,7 +88,8 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; - dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Benchmarking...", 74, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 12, 20, &I_LoadingHourglass_24x24); for(size_t i = 0; i < BENCH_COUNT; i++) { if(!storage_settings_scene_bench_write( app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) @@ -95,6 +98,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { if(i > 0) furi_string_cat_printf(app->text_string, "\n"); furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); @@ -110,6 +114,12 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); } + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); + free(bench_data); } @@ -146,11 +156,17 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DialogExResultCenter: - consumed = scene_manager_previous_scene(app->scene_manager); + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); break; } - } else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) { - consumed = true; + } else if(event.type == SceneManagerEventTypeBack) { + if(sd_status == FSE_OK) { + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); + } else { + consumed = true; + } } return consumed; @@ -160,6 +176,10 @@ void storage_settings_scene_benchmark_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); furi_string_reset(app->text_string); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c new file mode 100644 index 0000000000..2f86447618 --- /dev/null +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c @@ -0,0 +1,70 @@ +#include "../storage_settings.h" + +static void + storage_settings_scene_benchmark_confirm_dialog_callback(DialogExResult result, void* context) { + StorageSettings* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void storage_settings_scene_benchmark_confirm_on_enter(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + FS_Error sd_status = storage_sd_status(app->fs_api); + + if(sd_status == FSE_NOT_READY) { + 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); + dialog_ex_set_center_button_text(dialog_ex, "Ok"); + } else { + dialog_ex_set_header(dialog_ex, "Benchmark SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, + "SD will be tested in SPI\nmode. Learn more:\nr.flipper.net/sd_test", + 0, + 12, + AlignLeft, + AlignTop); + dialog_ex_set_icon(dialog_ex, 103, 12, &I_qr_benchmark_25x25); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Benchmark"); + } + + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback( + dialog_ex, storage_settings_scene_benchmark_confirm_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); +} + +bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManagerEvent event) { + StorageSettings* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DialogExResultLeft: + case DialogExResultCenter: + consumed = scene_manager_previous_scene(app->scene_manager); + break; + case DialogExResultRight: + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void storage_settings_scene_benchmark_confirm_on_exit(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_reset(dialog_ex); +} diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h index 18e7ba5aa3..d6e76894ba 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h @@ -5,5 +5,6 @@ ADD_SCENE(storage_settings, format_confirm, FormatConfirm) ADD_SCENE(storage_settings, formatting, Formatting) ADD_SCENE(storage_settings, sd_info, SDInfo) ADD_SCENE(storage_settings, internal_info, InternalInfo) +ADD_SCENE(storage_settings, benchmark_confirm, BenchmarkConfirm) ADD_SCENE(storage_settings, benchmark, Benchmark) ADD_SCENE(storage_settings, factory_reset, FactoryReset) diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index 5832c65893..2d977176a0 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -21,14 +21,14 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Erase"); - dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Confirm Factory Reset?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, - "Internal storage will be erased\r\nData and settings will be lost!", + "Internal storage will be erased\ndata and settings will be lost!", 64, - 32, + 14, AlignCenter, - AlignCenter); + AlignTop); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); } 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 862f55a464..13b368d3f4 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 @@ -20,8 +20,8 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); dialog_ex_set_center_button_text(dialog_ex, "Ok"); } else { - dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 12, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Format"); } 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 f107aaceae..2fb232f14a 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -1,4 +1,6 @@ #include "../storage_settings.h" +#include +#include static const NotificationMessage message_green_165 = { .type = NotificationMessageTypeLedGreen, @@ -31,7 +33,8 @@ void storage_settings_scene_formatting_on_enter(void* context) { FS_Error error; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Formatting...", 70, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 15, 20, &I_LoadingHourglass_24x24); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); notification_message_block(app->notification, &sequence_set_formatting_leds); @@ -44,11 +47,17 @@ void storage_settings_scene_formatting_on_enter(void* context) { if(error != FSE_OK) { dialog_ex_set_header(dialog_ex, "Cannot Format SD Card", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); } dialog_ex_set_center_button_text(dialog_ex, "OK"); } @@ -75,5 +84,9 @@ void storage_settings_scene_formatting_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index f205efc0ae..b7620b6e82 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -27,7 +27,7 @@ void storage_settings_scene_internal_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: LittleFS\n%lu KiB total\n%lu KiB free", + "Name: %s\nType: LittleFS\nTotal: %lu KiB\nFree: %lu KiB", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); 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 aa9662a714..cad3fbfafa 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 @@ -27,12 +27,31 @@ void storage_settings_scene_sd_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" - "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", + "Label: %s\nType: %s\n", sd_info.label, - sd_api_get_fs_type_text(sd_info.fs_type), - sd_info.kb_total, - sd_info.kb_free, + sd_api_get_fs_type_text(sd_info.fs_type)); + + if(sd_info.kb_total < 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu KiB\n", sd_info.kb_total); + } else if(sd_info.kb_total < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu MiB\n", sd_info.kb_total / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Total: %lu GiB\n", sd_info.kb_total / (1024 * 1024)); + } + + if(sd_info.kb_free < 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu KiB\n", sd_info.kb_free); + } else if(sd_info.kb_free < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu MiB\n", sd_info.kb_free / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Free: %lu GiB\n", sd_info.kb_free / (1024 * 1024)); + } + + furi_string_cat_printf( + app->text_string, + "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", sd_info.manufacturer_id, sd_info.oem_id, sd_info.product_name, @@ -41,6 +60,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { sd_info.product_serial_number, sd_info.manufacturing_month, sd_info.manufacturing_year); + dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index 0e667024f1..e351a2ef71 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -109,7 +109,7 @@ bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent even case StorageSettingsStartSubmenuIndexBenchy: scene_manager_set_scene_state( app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); consumed = true; break; case StorageSettingsStartSubmenuIndexFactoryReset: diff --git a/assets/icons/About/CertificationChina1_122x47.png b/assets/icons/About/CertificationChina1_122x47.png deleted file mode 100644 index c5eebec6776af61659c796fa55741f9a549bedae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5215 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 Xf^!1xOY<4R00000NkvXXu0mjf#QVd4 literal 0 HcmV?d00001 diff --git a/assets/icons/Settings/Cry_dolph_55x52.png b/assets/icons/Settings/LoadingHourglass_24x24.png similarity index 70% rename from assets/icons/Settings/Cry_dolph_55x52.png rename to assets/icons/Settings/LoadingHourglass_24x24.png index 86d9db1b497cd9e49bb62987cb35075b4bc7a410..9c49dcad1cb6060c4057ad5c44d67f440c2a91b1 100644 GIT binary patch delta 422 zcmdlbcSuIDGr-TCmrII^fq{Y7)59eQNK1e)2Q!eozVhs;jf&-5j2e??a`kB#>Lwc) z7$}%q>RBcyC!3_CS{myp7#SED=^Gg98=B}Enphc_TNxQm_T?^_tiz);c^7x~WIkR6 zeq%jj1r6WC(@vVoXfTjNL2^%w0_l%nc1)ogJM_ja}WG%`J=!jVz6g zEtM21atnNYtz7bxOLJ56N<3X`m4G68DVZr&P`wto^iHoSkw4cEn{ pO$PgN{;}_HzIpl9d4&=d35G^5hRi9SpH2l?;pyt^U8eBS^7 delta 696 zcmX>kvrA5~Gr-TCmrII^fq{Y7)59eQNSgz(2?sNfl$;oFYolU07h~DvnOuEp2D-@x z1_lb|mU=0PNr}lxNjeHf21bVZ1}6H3Cb|X&R>qcACMJ{pxl1O$;ZB*nhg;L7z}MHx zzbG?3GcPg6B|o_|H#M)s)5TV)BDX*CnUiJFPK_^myVa^ud6XN>+|9>De^w8xxAobJJ#WAGf)|P3v z`Hm=XxXk?j|D~_l-ED`@Hm1CFp8ZHLP{OpE_li$WhwqX}g~vj^|IU?<-sg5(fvPv+mW0F?cs@P-Z_VT$$NG{hH`nVv zlUXCaFKOP6gLCvACkH&!sy`rF_L%=!(v$w{^*^3oN|~~gdE1i<1+O@bw2yXO77kgf z#DDEo?1Ytqd#-*JoKmzopr06iN2s{jB1 diff --git a/assets/icons/Settings/dolph_cry_49x54.png b/assets/icons/Settings/dolph_cry_49x54.png new file mode 100644 index 0000000000000000000000000000000000000000..351a849b09030fd4628bbbf582503dfb83672c59 GIT binary patch literal 973 zcmV;;12X)HP)^74TPjRsUxkj1>*;PV6t7eFDw(WuIv6mwruS4`RwBiLhH>T z{w$4%m|Dg4qvN>VK1(Upyp@rywexF>C)i^7{(8MCrLVy9Z9!%5NH{TpXdrs6H9UHa z5tNL;i)}WJG~PVpYsanUTl7ke$dvP#V@%tpH;=Rut}S|{M%YEIJL9co;dDw;^F6Cx z$C{R$Km$;4PdV&`Rz~sBj04qO|QCb*yOzu=3VLgIgDU4W=?ZS-np$Do9 zhIg?90##V7#>C3X=H0*(UG*tA$;#mqvEK`;^KJx|I80`*G(HKtxvHb0M2oTx^GQRZ zjXW8l6~GHGuao=%-Y?aPPG+T!&!Mk8ZIfTy+8p>pnN1$ zM{-HmvWmISe<{7O+N*>!( z{e2b19v&n4Ah0~v;G(bJalS_mL-X5?9s$fNN;B$hE5mjbdn8;*TyJgW1^nJ450hI^ zo!E@ATh?HQ*?1H}tuGE3Eu!jT*EN1~XI;q723Df=ndO_!B`g%U7L7XY*jel{Lzy>v z!!x=);vDO+1%M3qrZ$DTrql1s--KqG{?#)hgFUPmL_Q`q<_79m{qTrHKj{%mgL>gvQdo~O z(*W-T_vTa&OKbIFhvCq_Mf4)ulx$Add!@Pg$M`GA@@)IQc1CSRm`6`|Vg#N+4Ab5K v&c;UCdJm6;)7|(ljL0hGuP2^DR(t*eG-K{iyaDCP00000NkvXXu0mjfItI*` literal 0 HcmV?d00001 diff --git a/assets/icons/Settings/qr_benchmark_25x25.png b/assets/icons/Settings/qr_benchmark_25x25.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f9df119582ba1625b5c40d869150d72e49bf37 GIT binary patch literal 395 zcmV;60d)R}P)P000>X1^@s6#OZ}&00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPj=?b%4XtA$ZUrb~kgF?s8V;qp}O#oaJ%lv*EAWmF>5n1cxby zZ@WK8Co29`v?;A*8@aB&yK3c{ZjAdo-yow0t#gmg;IW%dkZh1tUj>h|n{q#{*|F34 z*{Pe2!F^X5K6B(u(0zn1Pbpp zTxys%i6%2vhazbe_HAZVefNc^`O$7OBU_K!K=C@ Date: Wed, 15 May 2024 14:25:02 +0300 Subject: [PATCH 12/48] Add new map analyser (#3648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new map analyser * Fix typos Co-authored-by: hedger Co-authored-by: あく --- .github/workflows/build.yml | 29 +--- scripts/map_analyse_upload.py | 86 +++++++++++ scripts/map_mariadb_insert.py | 139 ----------------- scripts/map_parser.py | 274 ---------------------------------- 4 files changed, 92 insertions(+), 436 deletions(-) create mode 100755 scripts/map_analyse_upload.py delete mode 100755 scripts/map_mariadb_insert.py delete mode 100755 scripts/map_parser.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 252310af6d..2bc2178aea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - target: [f7, f18] + target: [f7, f18] steps: - name: 'Wipe workspace' run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; @@ -103,29 +103,12 @@ jobs: run: | cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf - cp ${{ github.event_path }} map_analyser_files/event.json source scripts/toolchain/fbtenv.sh - get_size() - { - SECTION="$1"; - arm-none-eabi-size \ - -A map_analyser_files/firmware.elf \ - | grep "^$SECTION" | awk '{print $2}' - } - export BSS_SIZE="$(get_size ".bss")" - export TEXT_SIZE="$(get_size ".text")" - export RODATA_SIZE="$(get_size ".rodata")" - export DATA_SIZE="$(get_size ".data")" - export FREE_FLASH_SIZE="$(get_size ".free_flash")" - python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0 - python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all - python3 scripts/map_mariadb_insert.py \ - ${{ secrets.AMAP_MARIADB_USER }} \ - ${{ secrets.AMAP_MARIADB_PASSWORD }} \ - ${{ secrets.AMAP_MARIADB_HOST }} \ - ${{ secrets.AMAP_MARIADB_PORT }} \ - ${{ secrets.AMAP_MARIADB_DATABASE }} \ - map_analyser_files/firmware.elf.map.all + python3 scripts/map_analyse_upload.py \ + "--elf_file=map_analyser_files/firmware.elf" \ + "--map_file=map_analyser_files/firmware.elf.map" \ + "--analyser_url=${{ secrets.ANALYSER_URL }}" \ + "--analyser_token=${{ secrets.ANALYSER_TOKEN }}"; - name: 'Find previous comment' if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} diff --git a/scripts/map_analyse_upload.py b/scripts/map_analyse_upload.py new file mode 100755 index 0000000000..38d9618796 --- /dev/null +++ b/scripts/map_analyse_upload.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import os +import requests +import argparse +import subprocess + +# usage: +# COMMIT_HASH, COMMIT_MSG, BRANCH_NAME, +# PULL_ID(optional), PULL_NAME(optional) must be set as envs +# maybe from sctipts/get_env.py +# other args must be set via command line args + + +class AnalyseRequest: + def __init__(self): + self.commit_hash = os.environ["COMMIT_HASH"] + self.commit_msg = os.environ["COMMIT_MSG"] + self.branch_name = os.environ["BRANCH_NAME"] + self.pull_id = os.getenv("PULL_ID", default=None) + self.pull_name = os.getenv("PULL_NAME", default=None) + + def get_payload(self): + return vars(self) + + +class AnalyseUploader: + def __init__(self): + self.args = self.parse_args() + + @staticmethod + def get_sections_size(elf_file) -> dict: + ret = dict() + all_sizes = subprocess.check_output( + ["arm-none-eabi-size", "-A", elf_file], shell=False + ) + all_sizes = all_sizes.splitlines() + + sections_to_keep = (".text", ".rodata", ".data", ".bss", ".free_flash") + for line in all_sizes: + line = line.decode("utf-8") + parts = line.split() + if len(parts) != 3: + continue + section, size, _ = parts + if section not in sections_to_keep: + continue + section_size_payload_name = ( + section[1:] if section.startswith(".") else section + ) + section_size_payload_name += "_size" + ret[section_size_payload_name] = size + return ret + + @staticmethod + def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--elf_file", help="Firmware ELF file", required=True) + parser.add_argument("--map_file", help="Firmware MAP file", required=True) + parser.add_argument( + "--analyser_token", help="Analyser auth token", required=True + ) + parser.add_argument( + "--analyser_url", help="Analyser analyse url", required=True + ) + args = parser.parse_args() + return args + + def upload_analyse_request(self): + payload = AnalyseRequest().get_payload() | self.get_sections_size( + self.args.elf_file + ) + headers = {"Authorization": f"Bearer {self.args.analyser_token}"} + file = {"map_file": open(self.args.map_file, "rb")} + response = requests.post( + self.args.analyser_url, data=payload, files=file, headers=headers + ) + if not response.ok: + raise Exception( + f"Failed to upload map file, code: {response.status_code}, reason: {response.text}" + ) + + +if __name__ == "__main__": + analyzer = AnalyseUploader() + analyzer.upload_analyse_request() diff --git a/scripts/map_mariadb_insert.py b/scripts/map_mariadb_insert.py deleted file mode 100755 index a4c9ed5c78..0000000000 --- a/scripts/map_mariadb_insert.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# mariadb==1.1.6 - -from datetime import datetime -import argparse -import mariadb -import sys -import os - - -def parseArgs(): - parser = argparse.ArgumentParser() - parser.add_argument("db_user", help="MariaDB user") - parser.add_argument("db_pass", help="MariaDB password") - parser.add_argument("db_host", help="MariaDB hostname") - parser.add_argument("db_port", type=int, help="MariaDB port") - parser.add_argument("db_name", help="MariaDB database") - parser.add_argument("report_file", help="Report file(.map.all)") - args = parser.parse_args() - return args - - -def mariadbConnect(args): - try: - conn = mariadb.connect( - user=args.db_user, - password=args.db_pass, - host=args.db_host, - port=args.db_port, - database=args.db_name, - ) - except mariadb.Error as e: - print(f"Error connecting to MariaDB: {e}") - sys.exit(1) - return conn - - -def parseEnv(): - outArr = [] - outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - outArr.append(os.getenv("COMMIT_HASH", default=None)) - outArr.append(os.getenv("COMMIT_MSG", default=None)) - outArr.append(os.getenv("BRANCH_NAME", default=None)) - outArr.append(os.getenv("BSS_SIZE", default=None)) - outArr.append(os.getenv("TEXT_SIZE", default=None)) - outArr.append(os.getenv("RODATA_SIZE", default=None)) - outArr.append(os.getenv("DATA_SIZE", default=None)) - outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) - outArr.append(os.getenv("PULL_ID", default=None)) - outArr.append(os.getenv("PULL_NAME", default=None)) - return outArr - - -def createTables(cur, conn): - headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `datetime` datetime NOT NULL, \ - `commit` varchar(40) NOT NULL, \ - `commit_msg` text NOT NULL, \ - `branch_name` text NOT NULL, \ - `bss_size` int(10) unsigned NOT NULL, \ - `text_size` int(10) unsigned NOT NULL, \ - `rodata_size` int(10) unsigned NOT NULL, \ - `data_size` int(10) unsigned NOT NULL, \ - `free_flash_size` int(10) unsigned NOT NULL, \ - `pullrequest_id` int(10) unsigned DEFAULT NULL, \ - `pullrequest_name` text DEFAULT NULL, \ - PRIMARY KEY (`id`), \ - KEY `header_id_index` (`id`) )" - dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ - `header_id` int(10) unsigned NOT NULL, \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `section` text NOT NULL, \ - `address` text NOT NULL, \ - `size` int(10) unsigned NOT NULL, \ - `name` text NOT NULL, \ - `lib` text NOT NULL, \ - `obj_name` text NOT NULL, \ - PRIMARY KEY (`id`), \ - KEY `data_id_index` (`id`), \ - KEY `data_header_id_index` (`header_id`), \ - CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" - cur.execute(headerTable) - cur.execute(dataTable) - conn.commit() - - -def insertHeader(data, cur, conn): - query = "INSERT INTO `header` ( \ - datetime, commit, commit_msg, branch_name, bss_size, text_size, \ - rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cur.execute(query, data) - conn.commit() - return cur.lastrowid - - -def parseFile(fileObj, headerID): - arr = [] - fileLines = fileObj.readlines() - for line in fileLines: - lineArr = [] - tempLineArr = line.split("\t") - lineArr.append(headerID) - lineArr.append(tempLineArr[0]) # section - lineArr.append(int(tempLineArr[2], 16)) # address hex - lineArr.append(int(tempLineArr[3])) # size - lineArr.append(tempLineArr[4]) # name - lineArr.append(tempLineArr[5]) # lib - lineArr.append(tempLineArr[6]) # obj_name - arr.append(tuple(lineArr)) - return arr - - -def insertData(data, cur, conn): - query = "INSERT INTO `data` ( \ - header_id, section, address, size, \ - name, lib, obj_name) \ - VALUES (?, ?, ?, ?, ? ,?, ?)" - cur.executemany(query, data) - conn.commit() - - -def main(): - args = parseArgs() - dbConn = mariadbConnect(args) - reportFile = open(args.report_file) - dbCurs = dbConn.cursor() - createTables(dbCurs, dbConn) - headerID = insertHeader(parseEnv(), dbCurs, dbConn) - insertData(parseFile(reportFile, headerID), dbCurs, dbConn) - reportFile.close() - dbCurs.close() - - -if __name__ == "__main__": - main() diff --git a/scripts/map_parser.py b/scripts/map_parser.py deleted file mode 100755 index 1efc4fe82f..0000000000 --- a/scripts/map_parser.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# cxxfilt==0.3.0 - -# Most part of this code written by Lars-Dominik Braun https://github.com/PromyLOPh/linkermapviz -# and distributes under MIT licence - -# Copyright (c) 2017 Lars-Dominik Braun -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -import sys -import re -import os -from typing import TextIO -from cxxfilt import demangle - - -class Objectfile: - def __init__(self, section: str, offset: int, size: int, comment: str): - self.section = section.strip() - self.offset = offset - self.size = size - self.path = (None, None) - self.basepath = None - - if comment: - self.path = re.match(r"^(.+?)(?:\(([^\)]+)\))?$", comment).groups() - self.basepath = os.path.basename(self.path[0]) - - self.children = [] - - def __repr__(self) -> str: - return f"" - - -def update_children_size(children: list[list], subsection_size: int) -> list: - # set subsection size to an only child - if len(children) == 1: - children[0][1] = subsection_size - return children - - rest_size = subsection_size - - for index in range(1, len(children)): - if rest_size > 0: - # current size = current address - previous child address - child_size = children[index][0] - children[index - 1][0] - rest_size -= child_size - children[index - 1][1] = child_size - - # if there is rest size, set it to the last child element - if rest_size > 0: - children[-1][1] = rest_size - - return children - - -def parse_sections(file_name: str) -> list: - """ - Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because - some messages are localized. - """ - - sections = [] - with open(file_name, "r") as file: - # skip until memory map is found - found = False - - while True: - line = file.readline() - if not line: - break - if line.strip() == "Memory Configuration": - found = True - break - - if not found: - raise Exception(f"Memory configuration is not found in the{input_file}") - - # long section names result in a linebreak afterwards - sectionre = re.compile( - "(?P
.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+", - re.I, - ) - subsectionre = re.compile( - "[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+", re.I - ) - s = file.read() - pos = 0 - - while True: - m = sectionre.match(s, pos) - if not m: - # skip that line - try: - nextpos = s.index("\n", pos) + 1 - pos = nextpos - continue - except ValueError: - break - - pos = m.end() - section = m.group("section") - v = m.group("offset") - offset = int(v, 16) if v is not None else None - v = m.group("size") - size = int(v, 16) if v is not None else None - comment = m.group("comment") - - if section != "*default*" and size > 0: - of = Objectfile(section, offset, size, comment) - - if section.startswith(" "): - children = [] - sections[-1].children.append(of) - - while True: - m = subsectionre.match(s, pos) - if not m: - break - pos = m.end() - offset, function = m.groups() - offset = int(offset, 16) - if sections and sections[-1].children: - children.append([offset, 0, function]) - - if children: - children = update_children_size( - children=children, subsection_size=of.size - ) - - sections[-1].children[-1].children.extend(children) - - else: - sections.append(of) - - return sections - - -def get_subsection_name(section_name: str, subsection: Objectfile) -> str: - subsection_split_names = subsection.section.split(".") - if subsection.section.startswith("."): - subsection_split_names = subsection_split_names[1:] - - return ( - f".{subsection_split_names[1]}" - if len(subsection_split_names) > 2 - else section_name - ) - - -def write_subsection( - section_name: str, - subsection_name: str, - address: str, - size: int, - demangled_name: str, - module_name: str, - file_name: str, - mangled_name: str, - write_file_object: TextIO, -) -> None: - write_file_object.write( - f"{section_name}\t" - f"{subsection_name}\t" - f"{address}\t" - f"{size}\t" - f"{demangled_name}\t" - f"{module_name}\t" - f"{file_name}\t" - f"{mangled_name}\n" - ) - - -def save_subsection( - section_name: str, subsection: Objectfile, write_file_object: TextIO -) -> None: - subsection_name = get_subsection_name(section_name, subsection) - module_name = subsection.path[0] - file_name = subsection.path[1] - - if not file_name: - file_name, module_name = module_name, "" - - if not subsection.children: - address = f"{subsection.offset:x}" - size = subsection.size - mangled_name = ( - "" - if subsection.section == section_name - else subsection.section.split(".")[-1] - ) - demangled_name = demangle(mangled_name) if mangled_name else mangled_name - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - return - - for subsection_child in subsection.children: - address = f"{subsection_child[0]:x}" - size = subsection_child[1] - mangled_name = subsection_child[2] - demangled_name = demangle(mangled_name) - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - - -def save_section(section: Objectfile, write_file_object: TextIO) -> None: - section_name = section.section - for subsection in section.children: - save_subsection( - section_name=section_name, - subsection=subsection, - write_file_object=write_file_object, - ) - - -def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None: - with open(output_file_name, "w") as write_file_object: - for section in parsed_data: - if section.children: - save_section(section=section, write_file_object=write_file_object) - - -if __name__ == "__main__": - if len(sys.argv) < 3: - raise Exception(f"Usage: {sys.argv[0]} ") - - input_file = sys.argv[1] - output_file = sys.argv[2] - - parsed_sections = parse_sections(input_file) - - if parsed_sections is None: - raise Exception(f"Memory configuration is not {input_file}") - - save_parsed_data(parsed_sections, output_file) From 1afa3f7ef3ca955ee435c8436de888d4296dda26 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 15 May 2024 18:22:54 +0300 Subject: [PATCH 13/48] Merge remote-tracking branch 'OFW/dev' into dev --- .vscode/example/clangd/extensions.json | 5 +- .../example/cpptools/c_cpp_properties.json | 6 +- .../infrared/resources/infrared/assets/ac.ir | 32 ++ applications/services/gui/elements.h | 24 +- applications/services/gui/modules/submenu.c | 39 +++ applications/services/gui/modules/submenu.h | 20 +- applications/settings/about/about.c | 10 +- .../bt_settings_scene_forget_dev_confirm.c | 6 +- .../scenes/bt_settings_scene_start.c | 2 +- .../desktop_settings/desktop_settings_app.c | 4 + .../desktop_settings/desktop_settings_app.h | 1 + .../scenes/desktop_settings_scene_pin_menu.c | 7 +- .../scenes/desktop_settings_scene_pin_setup.c | 1 + .../desktop_settings_scene_pin_setup_done.c | 1 + .../desktop_settings_view_pin_setup_howto2.c | 4 +- .../power_settings_app/power_settings_app.h | 2 + .../scenes/power_settings_scene_config.h | 1 + .../scenes/power_settings_scene_power_off.c | 10 +- .../scenes/power_settings_scene_reboot.c | 12 +- .../power_settings_scene_reboot_confirm.c | 66 +++++ .../scenes/storage_settings_scene_benchmark.c | 28 +- ...storage_settings_scene_benchmark_confirm.c | 70 +++++ .../scenes/storage_settings_scene_config.h | 1 + .../storage_settings_scene_factory_reset.c | 8 +- .../storage_settings_scene_format_confirm.c | 4 +- .../storage_settings_scene_formatting.c | 15 +- .../storage_settings_scene_internal_info.c | 2 +- .../scenes/storage_settings_scene_sd_info.c | 30 +- .../scenes/storage_settings_scene_start.c | 2 +- .../external/L1_Akira_128x64/frame_0.png | Bin 0 -> 1832 bytes .../external/L1_Akira_128x64/frame_1.png | Bin 0 -> 1888 bytes .../external/L1_Akira_128x64/frame_10.png | Bin 0 -> 1733 bytes .../external/L1_Akira_128x64/frame_11.png | Bin 0 -> 1601 bytes .../external/L1_Akira_128x64/frame_12.png | Bin 0 -> 1385 bytes .../external/L1_Akira_128x64/frame_13.png | Bin 0 -> 1815 bytes .../external/L1_Akira_128x64/frame_14.png | Bin 0 -> 1701 bytes .../external/L1_Akira_128x64/frame_15.png | Bin 0 -> 1409 bytes .../external/L1_Akira_128x64/frame_16.png | Bin 0 -> 1369 bytes .../external/L1_Akira_128x64/frame_17.png | Bin 0 -> 1398 bytes .../external/L1_Akira_128x64/frame_18.png | Bin 0 -> 1360 bytes .../external/L1_Akira_128x64/frame_19.png | Bin 0 -> 1330 bytes .../external/L1_Akira_128x64/frame_2.png | Bin 0 -> 1836 bytes .../external/L1_Akira_128x64/frame_20.png | Bin 0 -> 1077 bytes .../external/L1_Akira_128x64/frame_21.png | Bin 0 -> 1276 bytes .../external/L1_Akira_128x64/frame_22.png | Bin 0 -> 1688 bytes .../external/L1_Akira_128x64/frame_23.png | Bin 0 -> 1984 bytes .../external/L1_Akira_128x64/frame_24.png | Bin 0 -> 2015 bytes .../external/L1_Akira_128x64/frame_25.png | Bin 0 -> 1812 bytes .../external/L1_Akira_128x64/frame_26.png | Bin 0 -> 1395 bytes .../external/L1_Akira_128x64/frame_27.png | Bin 0 -> 1426 bytes .../external/L1_Akira_128x64/frame_28.png | Bin 0 -> 2053 bytes .../external/L1_Akira_128x64/frame_29.png | Bin 0 -> 1891 bytes .../external/L1_Akira_128x64/frame_3.png | Bin 0 -> 1908 bytes .../external/L1_Akira_128x64/frame_30.png | Bin 0 -> 1951 bytes .../external/L1_Akira_128x64/frame_31.png | Bin 0 -> 1911 bytes .../external/L1_Akira_128x64/frame_32.png | Bin 0 -> 1726 bytes .../external/L1_Akira_128x64/frame_33.png | Bin 0 -> 1923 bytes .../external/L1_Akira_128x64/frame_34.png | Bin 0 -> 1126 bytes .../external/L1_Akira_128x64/frame_35.png | Bin 0 -> 1555 bytes .../external/L1_Akira_128x64/frame_4.png | Bin 0 -> 1989 bytes .../external/L1_Akira_128x64/frame_5.png | Bin 0 -> 2000 bytes .../external/L1_Akira_128x64/frame_6.png | Bin 0 -> 1869 bytes .../external/L1_Akira_128x64/frame_7.png | Bin 0 -> 1868 bytes .../external/L1_Akira_128x64/frame_8.png | Bin 0 -> 1891 bytes .../external/L1_Akira_128x64/frame_9.png | Bin 0 -> 1817 bytes .../dolphin/external/L1_Akira_128x64/meta.txt | 14 + assets/dolphin/external/manifest.txt | 7 + assets/icons/Dolphin/DolphinWait_61x59.png | Bin 2023 -> 0 bytes ...h_55x52.png => LoadingHourglass_24x24.png} | Bin 3898 -> 3650 bytes assets/icons/Settings/dolph_cry_49x54.png | Bin 0 -> 973 bytes assets/icons/Settings/qr_benchmark_25x25.png | Bin 0 -> 395 bytes lib/ble_profile/extra_profiles/hid_profile.c | 2 +- lib/flipper_application/elf/elf_file.c | 5 + .../mf_desfire/mf_desfire_poller_i.c | 2 +- scripts/map_analyse_upload.py | 86 ++++++ scripts/map_mariadb_insert.py | 139 --------- scripts/map_parser.py | 274 ------------------ targets/f18/api_symbols.csv | 4 +- targets/f18/furi_hal/furi_hal.c | 2 +- targets/f7/api_symbols.csv | 4 +- targets/f7/ble_glue/gap.c | 58 +++- targets/f7/ble_glue/profiles/serial_profile.c | 2 +- targets/f7/furi_hal/furi_hal.c | 2 +- 83 files changed, 531 insertions(+), 483 deletions(-) create mode 100644 applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c create mode 100644 applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_13.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_14.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_15.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_16.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_17.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_18.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_19.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_20.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_21.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_22.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_23.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_24.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_25.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_26.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_27.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_28.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_29.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_30.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_31.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_32.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_33.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_34.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_35.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Akira_128x64/meta.txt delete mode 100644 assets/icons/Dolphin/DolphinWait_61x59.png rename assets/icons/Settings/{Cry_dolph_55x52.png => LoadingHourglass_24x24.png} (70%) create mode 100644 assets/icons/Settings/dolph_cry_49x54.png create mode 100644 assets/icons/Settings/qr_benchmark_25x25.png create mode 100755 scripts/map_analyse_upload.py delete mode 100755 scripts/map_mariadb_insert.py delete mode 100755 scripts/map_parser.py diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json index daab417cd0..4f24dd7b5d 100644 --- a/.vscode/example/clangd/extensions.json +++ b/.vscode/example/clangd/extensions.json @@ -8,7 +8,8 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ @@ -16,4 +17,4 @@ "ms-vscode.cpptools", "ms-vscode.cmake-tools" ] -} +} \ No newline at end of file diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json index 3f8d15a5d4..245d44ca1c 100644 --- a/.vscode/example/cpptools/c_cpp_properties.json +++ b/.vscode/example/cpptools/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "Win32", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -10,7 +10,7 @@ }, { "name": "Linux", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -18,7 +18,7 @@ }, { "name": "Mac", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 38db599b23..a007b12b7e 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -1021,3 +1021,35 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 +# +# Model: Toshiba RAS-2518D +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520 diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 8cc9123621..8270a93191 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -121,7 +121,8 @@ void elements_multiline_text_aligned( /** Draw multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -129,7 +130,8 @@ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* t /** Draw framed multiline text * * @param canvas Canvas instance - * @param x, y top left corner coordinates + * @param x top left corner coordinates + * @param y top left corner coordinates * @param text string (possible multiline) */ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text); @@ -137,8 +139,10 @@ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const /** Draw slightly rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_slightly_rounded_frame( Canvas* canvas, @@ -150,8 +154,10 @@ void elements_slightly_rounded_frame( /** Draw slightly rounded box * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of box + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width height of box + * @param height height of box */ void elements_slightly_rounded_box( Canvas* canvas, @@ -163,8 +169,10 @@ void elements_slightly_rounded_box( /** Draw bold rounded frame * * @param canvas Canvas instance - * @param x, y top left corner coordinates - * @param width, height size of frame + * @param x top left corner coordinates + * @param y top left corner coordinates + * @param width width of frame + * @param height height of frame */ void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index a4250c2e10..74f93320f9 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -318,6 +318,26 @@ void submenu_add_lockable_item( true); } +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { + furi_check(submenu); + furi_check(label); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + SubmenuItemArray_it_t it; + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + if(index == SubmenuItemArray_cref(it)->index) { + furi_string_set_str(SubmenuItemArray_cref(it)->label, label); + break; + } + } + }, + true); +} + void submenu_reset(Submenu* submenu) { furi_check(submenu); view_set_orientation(submenu->view, ViewOrientationHorizontal); @@ -335,6 +355,25 @@ void submenu_reset(Submenu* submenu) { true); } +uint32_t submenu_get_selected_item(Submenu* submenu) { + furi_check(submenu); + + uint32_t selected_item_index = 0; + + with_view_model( + submenu->view, + SubmenuModel * model, + { + if(model->position < SubmenuItemArray_size(model->items)) { + const SubmenuItem* item = SubmenuItemArray_cget(model->items, model->position); + selected_item_index = item->index; + } + }, + false); + + return selected_item_index; +} + void submenu_set_selected_item(Submenu* submenu, uint32_t index) { furi_check(submenu); with_view_model( diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index 676e99d299..d77f570c26 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -73,16 +73,32 @@ void submenu_add_lockable_item( bool locked, const char* locked_message); +/** Change label of an existing item + * + * @param submenu Submenu instance + * @param index The index of the item + * @param label The new label + */ +void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); + /** Remove all items from submenu * * @param submenu Submenu instance */ void submenu_reset(Submenu* submenu); -/** Set submenu item selector +/** Get submenu selected item index + * + * @param submenu Submenu instance + * + * @return Index of the selected item + */ +uint32_t submenu_get_selected_item(Submenu* submenu); + +/** Set submenu selected item by index * * @param submenu Submenu instance - * @param index The index + * @param index The index of the selected item */ void submenu_set_selected_item(Submenu* submenu, uint32_t index); diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index 6b7c9f976a..1669e335a6 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -43,7 +43,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; - const char* screen_text = "Flipper Devices Inc\n" + const char* screen_text = "Flipper Devices Inc.\n" "Suite B #551, 2803\n" "Philadelphia Pike, Claymont\n" "DE, USA 19703\n"; @@ -59,7 +59,7 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* DialogMessageButton result; const char* screen_text = "For all compliance\n" - "certificates please visit:\n" + "certificates, please visit:\n" "www.flipp.dev/compliance"; dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); @@ -226,9 +226,11 @@ int32_t about_settings_app(void* p) { while(1) { if(screen_index >= COUNT_OF(about_screens) - 1) { - dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_buttons(message, "Prev.", NULL, NULL); + } else if(screen_index == 0) { + dialog_message_set_buttons(message, NULL, NULL, "Next"); } else { - dialog_message_set_buttons(message, "Back", NULL, "Next"); + dialog_message_set_buttons(message, "Prev.", NULL, "Next"); } screen_result = about_screens[screen_index](dialogs, message); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c index 150c3ef9f1..d341595f89 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_confirm.c @@ -11,10 +11,10 @@ void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void bt_settings_scene_forget_dev_confirm_on_enter(void* context) { BtSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); - dialog_ex_set_left_button_text(dialog, "Back"); + dialog, "All previous pairings\nwill be lost!", 64, 14, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(dialog, "Cancel"); dialog_ex_set_right_button_text(dialog, "Unpair"); dialog_ex_set_context(dialog, app); dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback); diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index c148f09435..1d72a9e6fa 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -53,7 +53,7 @@ void bt_settings_scene_start_on_enter(void* context) { variable_item_set_current_value_index(item, BtSettingOff); variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); } - variable_item_list_add(var_item_list, "Forget All Paired Devices", 1, NULL, NULL); + variable_item_list_add(var_item_list, "Unpair All Devices", 1, NULL, NULL); variable_item_list_set_enter_callback( var_item_list, bt_settings_scene_start_var_list_enter_callback, app); } else { diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index 15936bcb80..35ee2a3f1f 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -133,6 +133,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { extern int32_t desktop_settings_app(void* p) { DesktopSettingsApp* app = desktop_settings_app_alloc(); DESKTOP_SETTINGS_LOAD(&app->settings); + if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); } else { @@ -140,6 +141,9 @@ extern int32_t desktop_settings_app(void* p) { } view_dispatcher_run(app->view_dispatcher); + + DESKTOP_SETTINGS_SAVE(&app->settings); desktop_settings_app_free(app); + return 0; } diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 76f23e22f9..48adb9cfb0 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -47,4 +47,5 @@ typedef struct { char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH]; uint8_t menu_idx; + uint32_t pin_menu_idx; } DesktopSettingsApp; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 86e756ede8..9fdd688965 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -44,7 +44,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { } submenu_set_header(app->submenu, "PIN Code Settings"); - submenu_set_selected_item(app->submenu, app->menu_idx); + submenu_set_selected_item(app->submenu, app->pin_menu_idx); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); } @@ -76,11 +76,16 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e consumed = true; break; } + } else if(event.type == SceneManagerEventTypeBack) { + submenu_set_selected_item(app->submenu, 0); } + return consumed; } void desktop_settings_scene_pin_menu_on_exit(void* context) { DesktopSettingsApp* app = context; + + app->pin_menu_idx = submenu_get_selected_item(app->submenu); submenu_reset(app->submenu); } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index 1603aa3372..5b8aa8638a 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -97,6 +97,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent break; } } + return consumed; } diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index b829f995bc..4cef4ba986 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -27,6 +27,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) { DESKTOP_SETTINGS_SAVE(&app->settings); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_blink_green_10); furi_record_close(RECORD_NOTIFICATION); desktop_view_pin_input_set_context(app->pin_input_view, app); diff --git a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c index b09b0b95f1..6148747a26 100644 --- a/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/views/desktop_settings_view_pin_setup_howto2.c @@ -24,9 +24,9 @@ static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* mo elements_multiline_text_aligned( canvas, 64, - 24, - AlignCenter, + 0, AlignCenter, + AlignTop, "Forgotten PIN can only be\n" "reset with entire device.\n" "Read docs How to reset PIN."); diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h index eb9ffcf67d..6ee1a0a086 100644 --- a/applications/settings/power_settings_app/power_settings_app.h +++ b/applications/settings/power_settings_app/power_settings_app.h @@ -30,3 +30,5 @@ typedef enum { PowerSettingsAppViewSubmenu, PowerSettingsAppViewDialog, } PowerSettingsAppView; + +typedef enum { RebootTypeDFU, RebootTypeNormal } RebootType; diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h index cc8656dcfc..f57071b9b0 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_config.h +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_config.h @@ -1,4 +1,5 @@ ADD_SCENE(power_settings, start, Start) ADD_SCENE(power_settings, battery_info, BatteryInfo) ADD_SCENE(power_settings, reboot, Reboot) +ADD_SCENE(power_settings, reboot_confirm, RebootConfirm) ADD_SCENE(power_settings, power_off, PowerOff) diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index c3f5d5ad80..6cd9c5c676 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -10,12 +10,12 @@ void power_settings_scene_power_off_on_enter(void* context) { PowerSettingsApp* app = context; DialogEx* dialog = app->dialog; - dialog_ex_set_header(dialog, "Turn OFF Device?", 64, 2, AlignCenter, AlignTop); + dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( - dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52); - dialog_ex_set_left_button_text(dialog, "Back"); - dialog_ex_set_right_button_text(dialog, "OFF"); + dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_context(dialog, app); diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c index 2d5dedfd42..187d969ed9 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot.c @@ -24,7 +24,7 @@ void power_settings_scene_reboot_on_enter(void* context) { app); submenu_add_item( submenu, - "Flipper OS", + "Reboot Flipper", PowerSettingsRebootSubmenuIndexOs, power_settings_scene_reboot_submenu_callback, app); @@ -33,14 +33,18 @@ void power_settings_scene_reboot_on_enter(void* context) { } bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); + PowerSettingsApp* app = context; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == PowerSettingsRebootSubmenuIndexDfu) { - power_reboot(PowerBootModeDfu); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeDFU); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } else if(event.event == PowerSettingsRebootSubmenuIndexOs) { - power_reboot(PowerBootModeNormal); + scene_manager_set_scene_state( + app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeNormal); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm); } consumed = true; } diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c new file mode 100644 index 0000000000..62e06de927 --- /dev/null +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_reboot_confirm.c @@ -0,0 +1,66 @@ +#include "../power_settings_app.h" + +void power_settings_scene_reboot_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + PowerSettingsApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void power_settings_scene_reboot_confirm_on_enter(void* context) { + PowerSettingsApp* app = context; + DialogEx* dialog = app->dialog; + + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(reboot_type == RebootTypeDFU) { + dialog_ex_set_header(dialog, "Reboot to DFU Mode?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, + "Needed for device maintenance\nor firmware upgrades", + 64, + 14, + AlignCenter, + AlignTop); + } else if(reboot_type == RebootTypeNormal) { + dialog_ex_set_header(dialog, "Reboot Flipper?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog, "May help with some firmware\n issues", 64, 14, AlignCenter, AlignTop); + } else { + furi_crash("Invalid reboot type"); + } + + dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_right_button_text(dialog, "Reboot"); + + dialog_ex_set_result_callback(dialog, power_settings_scene_reboot_confirm_dialog_callback); + dialog_ex_set_context(dialog, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog); +} + +bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEvent event) { + PowerSettingsApp* app = context; + bool consumed = false; + RebootType reboot_type = + scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } else if(event.event == DialogExResultRight) { + if(reboot_type == RebootTypeDFU) { + power_reboot(PowerBootModeDfu); + } else { + power_reboot(PowerBootModeNormal); + } + } + consumed = true; + } + return consumed; +} + +void power_settings_scene_reboot_confirm_on_exit(void* context) { + PowerSettingsApp* app = context; + dialog_ex_reset(app->dialog); +} 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 e734c78e03..bfc9ac9c93 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -1,5 +1,7 @@ #include "../storage_settings.h" #include +#include +#include #define BENCH_DATA_SIZE 4096 #define BENCH_COUNT 6 @@ -86,7 +88,8 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; - dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Benchmarking...", 74, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 12, 20, &I_LoadingHourglass_24x24); for(size_t i = 0; i < BENCH_COUNT; i++) { if(!storage_settings_scene_bench_write( app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) @@ -95,6 +98,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { if(i > 0) furi_string_cat_printf(app->text_string, "\n"); furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); @@ -110,6 +114,12 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); } + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); + free(bench_data); } @@ -146,11 +156,17 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DialogExResultCenter: - consumed = scene_manager_previous_scene(app->scene_manager); + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); break; } - } else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) { - consumed = true; + } else if(event.type == SceneManagerEventTypeBack) { + if(sd_status == FSE_OK) { + consumed = scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, StorageSettingsStart); + } else { + consumed = true; + } } return consumed; @@ -160,6 +176,10 @@ void storage_settings_scene_benchmark_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); furi_string_reset(app->text_string); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c new file mode 100644 index 0000000000..2f86447618 --- /dev/null +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c @@ -0,0 +1,70 @@ +#include "../storage_settings.h" + +static void + storage_settings_scene_benchmark_confirm_dialog_callback(DialogExResult result, void* context) { + StorageSettings* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, result); +} + +void storage_settings_scene_benchmark_confirm_on_enter(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + FS_Error sd_status = storage_sd_status(app->fs_api); + + if(sd_status == FSE_NOT_READY) { + 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); + dialog_ex_set_center_button_text(dialog_ex, "Ok"); + } else { + dialog_ex_set_header(dialog_ex, "Benchmark SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + dialog_ex, + "SD will be tested in SPI\nmode. Learn more:\nr.flipper.net/sd_test", + 0, + 12, + AlignLeft, + AlignTop); + dialog_ex_set_icon(dialog_ex, 103, 12, &I_qr_benchmark_25x25); + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Benchmark"); + } + + dialog_ex_set_context(dialog_ex, app); + dialog_ex_set_result_callback( + dialog_ex, storage_settings_scene_benchmark_confirm_dialog_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); +} + +bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManagerEvent event) { + StorageSettings* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DialogExResultLeft: + case DialogExResultCenter: + consumed = scene_manager_previous_scene(app->scene_manager); + break; + case DialogExResultRight: + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + consumed = true; + break; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = true; + } + + return consumed; +} + +void storage_settings_scene_benchmark_confirm_on_exit(void* context) { + StorageSettings* app = context; + DialogEx* dialog_ex = app->dialog_ex; + + dialog_ex_reset(dialog_ex); +} diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h index 18e7ba5aa3..d6e76894ba 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_config.h +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_config.h @@ -5,5 +5,6 @@ ADD_SCENE(storage_settings, format_confirm, FormatConfirm) ADD_SCENE(storage_settings, formatting, Formatting) ADD_SCENE(storage_settings, sd_info, SDInfo) ADD_SCENE(storage_settings, internal_info, InternalInfo) +ADD_SCENE(storage_settings, benchmark_confirm, BenchmarkConfirm) ADD_SCENE(storage_settings, benchmark, Benchmark) ADD_SCENE(storage_settings, factory_reset, FactoryReset) diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index 5832c65893..2d977176a0 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -21,14 +21,14 @@ void storage_settings_scene_factory_reset_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Erase"); - dialog_ex_set_header(dialog_ex, "Confirm Factory Reset", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Confirm Factory Reset?", 64, 0, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, - "Internal storage will be erased\r\nData and settings will be lost!", + "Internal storage will be erased\ndata and settings will be lost!", 64, - 32, + 14, AlignCenter, - AlignCenter); + AlignTop); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); } 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 862f55a464..13b368d3f4 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 @@ -20,8 +20,8 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); dialog_ex_set_center_button_text(dialog_ex, "Ok"); } else { - dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 10, AlignCenter, AlignCenter); - dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Format SD Card?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All data will be lost!", 64, 12, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Format"); } 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 f107aaceae..2fb232f14a 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -1,4 +1,6 @@ #include "../storage_settings.h" +#include +#include static const NotificationMessage message_green_165 = { .type = NotificationMessageTypeLedGreen, @@ -31,7 +33,8 @@ void storage_settings_scene_formatting_on_enter(void* context) { FS_Error error; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Formatting...", 70, 32, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 15, 20, &I_LoadingHourglass_24x24); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); notification_message_block(app->notification, &sequence_set_formatting_leds); @@ -44,11 +47,17 @@ void storage_settings_scene_formatting_on_enter(void* context) { if(error != FSE_OK) { dialog_ex_set_header(dialog_ex, "Cannot Format SD Card", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_icon(dialog_ex, 0, 0, NULL); dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + notification_message(notification, &sequence_set_green_255); + notification_message(notification, &sequence_success); + furi_record_close(RECORD_NOTIFICATION); } dialog_ex_set_center_button_text(dialog_ex, "OK"); } @@ -75,5 +84,9 @@ void storage_settings_scene_formatting_on_exit(void* context) { StorageSettings* app = context; DialogEx* dialog_ex = app->dialog_ex; + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_green); + furi_record_close(RECORD_NOTIFICATION); + dialog_ex_reset(dialog_ex); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index f205efc0ae..b7620b6e82 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -27,7 +27,7 @@ void storage_settings_scene_internal_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: LittleFS\n%lu KiB total\n%lu KiB free", + "Name: %s\nType: LittleFS\nTotal: %lu KiB\nFree: %lu KiB", furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown", (uint32_t)(total_space / 1024), (uint32_t)(free_space / 1024)); 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 aa9662a714..cad3fbfafa 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 @@ -27,12 +27,31 @@ void storage_settings_scene_sd_info_on_enter(void* context) { } else { furi_string_printf( app->text_string, - "Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n" - "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", + "Label: %s\nType: %s\n", sd_info.label, - sd_api_get_fs_type_text(sd_info.fs_type), - sd_info.kb_total, - sd_info.kb_free, + sd_api_get_fs_type_text(sd_info.fs_type)); + + if(sd_info.kb_total < 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu KiB\n", sd_info.kb_total); + } else if(sd_info.kb_total < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Total: %lu MiB\n", sd_info.kb_total / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Total: %lu GiB\n", sd_info.kb_total / (1024 * 1024)); + } + + if(sd_info.kb_free < 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu KiB\n", sd_info.kb_free); + } else if(sd_info.kb_free < 1024 * 1024) { + furi_string_cat_printf(app->text_string, "Free: %lu MiB\n", sd_info.kb_free / 1024); + } else { + furi_string_cat_printf( + app->text_string, "Free: %lu GiB\n", sd_info.kb_free / (1024 * 1024)); + } + + furi_string_cat_printf( + app->text_string, + "%02X%s %s v%i.%i\nSN:%04lX %02i/%i", sd_info.manufacturer_id, sd_info.oem_id, sd_info.product_name, @@ -41,6 +60,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { sd_info.product_serial_number, sd_info.manufacturing_month, sd_info.manufacturing_year); + dialog_ex_set_text( dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index 0e667024f1..e351a2ef71 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -109,7 +109,7 @@ bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent even case StorageSettingsStartSubmenuIndexBenchy: scene_manager_set_scene_state( app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); + scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); consumed = true; break; case StorageSettingsStartSubmenuIndexFactoryReset: diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_0.png b/assets/dolphin/external/L1_Akira_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..36c1bbd4994c02762f5b17ea12993524b0f782ee GIT binary patch literal 1832 zcmV+@2iN$CP)|yO9djF^|f+kYI#csw5TS1ZE;okz6xm3VeuvYVfWaYxvFs6hLU zcd`Cd*ACJe6GdPap7S12TQ~)I-c2s~f z<+!7V)t5OwlKt-Rc0bXB3_CbL$N6f|bb{>e<-Xc)$^p=Ut*QWLvz~p|GJPfU@0|7Q z*`xuGjWSgQKnG)GURm;4uq5vt9p5?6r@9eOdXV#(;MomN_87fY*1wzM&z|M;Z11%h zv-)r%rztv=l7F{yzbc8A-UC$5n$<&P;8q^%Ol6|EQ`WDpo?CAMk+D$_tZEIN$-vr$ zp1l$~TjsNK_r3Zaz_t*D*?aq@ZHycrxXhlq?NY^+mupY&)avUPeGZ^hgbq669h>#c zH-HqIs!4;9zamhUR)8yJdcPjoh=9I(nH_i(_D$ zmhD+J=-fLfm1X65zD))2U@v2oO!G3>tgmd$f(z+Zb=kEm@fIAwqw|d#rq`}?ex##H zjH|A`6~Hm@)fGUs39XOpVlrDLdyGzGbM)*?Td8cbfB~?(0%Tbh=~8+=D#>TEnt9hd zuuE*`f3r4JNsv8%R*QC307j>ksmjjmRKT)+tIzL^$$lj7IF4{XNJb^k;~NQ#7Ah4W zGNv-1g10ZL0+HR39UZW3VTCS>Y9F1%u=qg&qnEq`kWtZE)-dE)6lj&K$iB1a+yQj6 z-@l9WsN=9l`0e0s1*m#F%1{)9YERwi{LC(kmCny}b(|em%`x(S7f?l*n(e9%)XyW< ze1-EZyl&Rfl~b9FXJlu(CcY-guz#XWD#Xu5ua}usoQk}lgSo=_v-L$S&`&~9_C~gP zJ3C~Loxl*vzYwBK0TWC~k7TR;ELyvZgn2y`59pMAuQ!rk$zTZmxAlk9f-)MFmF)jf z#CFMH6(~ELkFqk$MrMBsqmQa`r}c(9N5KJDq0AJ)27s4&bpL1l`7OW*vg@n*Nll*`|zl;D|3Ekb69?b>_zWqyok}WtpZfeu4JOB!8l)W zNK|d2{Y1KuEc85saz532XipK!dw@zQQvosuM(bH*y|D^{Y!7VaBRjlJ6}rxkWMorD zXy4e(0la~XJkTLss#2E^A#~(M`zUV$D?#c;BEB%c zuE>Z?&YUo!&+Oh==i9Gr`Dd#M0`d{sw>{qomvMlrp*Yj7V;FR_mI;803};}g5PAnD z#UX}dq>vfAu0~bnJu(u-PD&MrP}?Hg?iB9vL&o^93J~>VhCgIv6IBt0JfC%PWiO*I zs|2k8=uQEnD^-5xuW~bw1Ss1`z<6gEKaY4$`i#4qG`WP^;EiC)m%j(G;y$SiMDNM}%(`pkm1OIa0_Pw$k~P&WNu>C3-&Z z+Q5=GYXj0fZJU3(6G3V7xcL+i&FjqC=yR4mXJZzUp}dm;-BBP^wrmA6;1{~h|7 z0O^$GN+&C)a;S=>KPmK;fv^HmtB7C&!8$LhBxf9ks)r&o;u982e^TfxCxS7!(FxsQ z*!Qz45b0wwSZOi-S+%YT2(;!nj(_hrhImJ?moI3Hn(HlTLsj6F34#Lfqkd-)lMQp7 z2igNG?+gL8GSIt`-D-P@*8X!9V4}K2I-9L%#8mCGvJm@c13$8Z8UybJ_An~vs~J{h zc?G3Covj9cbqpZ@m<_&~2u^f^pzC~AZ$$+CSt`VlvH3?8If(FNdt7FC2WNvxW$tI4 zAL(G%A{$m!A*8p(0g$lOIO~Y$0IQL2#YIbX`xmXX`se2zTiCJuFMnVwfQsF>a|)OP zd6&D4i4}p!wC0Z_fe2YpzKi2`0TSM^C;;9qgMPt5`qi=3Ma3`fht}^7_V~^E`{F;b WbGfA4+=u%B0000?80Y!#G91V8`S~~w`m3}dF+W58BIx-tPS%Ep_p*ftKqfH@lnBdSHDNNo zuEbCL?+c)s{3{Y~+3ZaI7-PIzt=$X}eOo}|kK-`ux+7uIMwO`$J_XQSSy^1=fBKO< zoPkWdPROprYv0p$m(Ali{%ZlhjoEpb zoqr|WqZp*0>|Af1ToJg!3Mf!%^Gepq?s`__UD3L-43K%xcPE`pc#FMq%Gr+f?@B(qnqb8Keu zwJmnnd$DHTKT4vv%eyteF`E8ENLB-MT^3oFJXX3s@i#=i~ zYpi}JHh$0kTMt48x*33N0V^6~HTlRE5@;1_W;R6^16Z-WWP;~EYf{7jR)Esx>cU%# z&M-)~fYJqzlF#h^D0VCEm3E@@ah~V*G0>W%sT4i4zGuMDeCDVpZOWg!pl!7)bEnl= zzO!p1Vg5SU+Cl6y`LlPNiR_KxD|`<-laJ7``}N#ucSQrB7(dTOa130nFb3>`F*JL1 z@+-m*fuZ_{UA-ftwH7a{FGy5GnC#oyrpGvDg&K)v)Ur2@#q9M+u=YRW zMu7pCT^%Kx@i7K&0Zj7sF4fR|nMr0AtD8w9!wgJZ;XOb#NH%e3eAb_iU+*4o%={U> zhvyR&VQ1n!2FNDTjGyRzBONej^e4l7t#QcO@_rT(*Rc>5@s6c6yR+Nq$9Ln zE2b44WZ6S=ym-FGoU7-YA3{X)8lEU*nK4WE*4BAf4w1-e0Qot3cg>t( z0a%OUeMNJ%&}8hOIr?Rga@VnF8*={qp6!T?FqSqMs+aRn5i#*ag)i6Wj{?xUM-R3r zSAX@KqChjsKY}ODWK!gqOyV_IRW4)DWAs| zSq;FRhUS=C{<-+f0P-@WvgLZ1YyC0LM!fZC!QTczrV_L+QT zPDZ7_6#7eu7KAxRd$v6aW_{~t-gxVKj{#8ep(6NdpW}uaK*CC{L}o{2E|RPkkx8}} zD>Elz1?H~eIF9=lz!>q4W`$jJhW7kse50bVY()%ajnCkA7eNv9c^aXz`&i%Fk9U`W zVq)$A(7i({=J5aq7y};-GxM@@z44i2UdK7VDm))k0Au8eAQDJbzvy!bMBno>>tf6s zzk){|I$jl!jC*&s3r50OMD{eZwh3cz6Lk6qbu*<;SaJa$KzvJZM8mH9HCv#Q~=H3>B*0*mTJ;bV6$o<_hEitTh-OMMKS06Nr2OnPS946{NA|@ zbdW&$%gDr8fKv-S z4r`@r_jf1&x}Sko4@8T)3<^bpGx4`;y-|_HQP^4+S_C5&7rOcaHD*(MbR_P6r8i`yVyH ziO9(NE)t`0b`*qYtQ|E0_xW|Y{1YPbzk7T_v|7*FzKaC>`Z_uJe1hFUUxAm54mG`t z1TSRdM4XcRUH)iApfz3x3653(LS)i+V}~M{$F;y}Mz*}ZuTztcB=uwkApZzef?9=b zH2Imnucud11lM(aKBp=GeLC$Eq6MfIldn<$i8<%d3eZVNuO8?m!SM=!D(uq~K;G-X zT_pG#1>ks^1akat`LFm@?A#qQu5giskRid* z3V;yV*9ct-&|z;Chy*CPSrYsT1<+P&mkdW@bVo6*z4Dc$&ye7&P63fs*>xn_@pi8Z z)|O-?AI;@`%9}M06{4O#rwStVfJXuz%dhy`z$|{_oH)?jqDMm+o5vNvlXa_rIWfs7 z5nh`rWZp7I!_TYWCtjsp1h@X1K5=7r|J68}iwM->1ipT2u|x<-j2<0m4+ z7;7h;DhakLfR=o(Pu8y|-e;gOx!)0=c96ibAxZGoB&h@0 zsVRX5Z8d7Psg0H_l%VXs@5N#DXqf50O#!qOg4#uE0iy!beGQQD7{2ug=BlXUTkIRZE-NsUdsXS;^a5GqPF> zphB&>pIHIX3cS@iMkAc&WAzw^{LiW|%3HKQ{GGs#ZGZ>C+Jj{~uMK^dpfS2nF_vAV zcAAYyn@BqZl(#4e)n5u-CBd$(B+{>o1l1D(`LQx)rbb%Hm#Y9BMTv#z%94TUI;m8F zP8w0A^6ak2KeA=vpC60vB0*IFBFWcC(7F1h56DL?PAf_fjT@>xw8doEg8yGYM?sh+ z4((e6E$RF@_0#NeI~kv&cC{-aLDq+ea&{=S`X5GAjmwF9eNk|tq}!Bn$Ic$vV(AVB%nZ`iDt(`Bv`EzP@<#Br?!@Yq4`IWPxmT$(MVttptB&P zt5z7vU}aWuW$*LsRSG!SA8ikoDtN1yt{tZ|LRtYj21NnMZXKFUv{ECsBw;Lj9z6fG zx+Y?8B@bv(m;|T}ZavFZw;6Pw0IGI*d?LjhQU>0)a}mn8x&+{zG$*pPfpuiz?JDR{ zDDKayFJuQjLp6ZqM>gIOga{y=sF0y@9P6B@Wv}DZfb3ZWVFSBV&~mEA!}1v<31rMF8r^4MYka9+YTc<^Gq2M*{lnN`58b-Ad$P&Y35G zG+>b+n*6AOWMPrOD!jT@e$*FK>~;7Q!4d(+@XxNjGYY`t>xy^6F4_vTRUK7=j3S5%u$@+9W)ghW?|WTjmG$1y#+e|?<%!c z|E-vcJEH>9tM73FSoxlaCIkv^JG-L`m?v0(dKRemeLySn9o*EhY8SL)>#Us< zI8p;-2h|~qQaqN=DmYmqW3Q0C^j5G2YJk*_C5`j^>;!p#VraENeWb#Q$DftxC{pBE z%>;-h-rKI{VAZ=jNTVP7IJ>%QOW+eUzw4Vf0 zR?{!~9Ic*x^{iUHWkidr=<1?Av_4WX=@~zttL&=h@mO=yP5`aALn|+n6y6q*iTjUk zH#}*PZADWWsYcNJS760ErT|jXvwJ`HQB6jbbkP{S{xwoeU_=2jgO3(F+E$W{2ldtb zMPkfcyP(+{QGg2bGrrLMwQ^*XVu>c7$1=GXym6qpYX288^!pQog z3O^pttA8Hey)B^C<5eWyj@qll!%5It+;yOp0FeYv7Czd_w&cHCdb4BfeiM*vzoGz> zuRfmSTJqmT0!e*tz>cMj02y|p!mw+CyxRbJKH)z@f)j+fLIL)@E_iDJbf5O`z=?PK zES~4U-vw|?$o{rV!AXD;OrP<7n(Yj3+y>TaQUEWjyZ3%tStC2rD&Jr9xip53D!x(+ zOcUmq0$5TQog6Qj(CVkF9;raoht*HB_cs0qF(NJUujKEu00000NkvXXu0mjf`tt#g literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_12.png b/assets/dolphin/external/L1_Akira_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..e3bfd179d9025dc19f60dbe1d2fd5d49e4d4c0ec GIT binary patch literal 1385 zcmV-v1(y1WP)0qEpb6U|QI=(v_{ynWWVXuBv0 zS`dIko9NB5@zi(}H%QQu0At7`IGMTgzN?*3MoG|?0Au_c8pb&L*TFmt-N0^{x03yp zq2x(e6PZSspArdnJc87Z_s}H+83#U^YXCGax}?fn>c{>5D-!MJ!GymL4b zx$?Y;4E-XqUKy_EwIr~r;VL2G?YuJF16(1%uJNNcUAK^_-;McQ%>goYeun4p?vfp3 z$a~f;5_+!W0GV#zbHTyoC@dLoW=@m06FEvWzI$+t04Em$S!~VlmM5@o6}7%4Jw8Q( z$hajAf>6u}u6CSce5BOkJ9>4!%*X$9q8kbY?8H@gv10zGOH~i?(+QBB03py4ysUC8+Go~U zY?XNnnX(?hO4_u^ok+k|eOjSnCX$s;K+EL-H2b!oxmi>`L#OCj#4RA9s1yMr?v~1& zx>ZBY5G=WfY!+4|C=miA1ra0idi@5xr-jr2H?DoXNb>*@BWT9+COc7C4|c|pDv6t< zwY)$I0i=TyYl4>HBLiK3Ap90Ih<8JhWe%eWmN<5@bRWm86J zId$Q>Y3=#O%Jg!fqhM*_#LkW6>0}&ST@rbXf4Y>j)`#CucnK?wMP))E05Lvq&wr>w znxZIBcaC@fbgWER>x1a-9gE6@2(S}B(f&OKu@;7YqVAu{@aLXIKXD$R%_%yTn*dpH z-P-vp4 zTDg8}msq(RCt`-T*)>@&`dSc2)#YETxUx2|zbf zOzmgWf3)7IGBEbFTLhksTL{1ldTU(mXVO3FS7jMM*R{ZkK(FQbcbNcYPFidBy?e*0 zWbAmt^~T8k>wQIFM1an{xCD48&^D)a7t9C5Eqq)a20CVCIvaA!N z-&c{J*+bJkvfq_VURFSUj4S0de zf--}Qtjss`5TIfx5kcF~15_$qI*hF8`B4S52gu3-BL)vMan|>|B2{MW3q23vlutnU%+ophuzjMnk&Xw1ZY*enykPV!sh zf6u-`Lbi59j8$vdwKEZ9B9{%a)hx?(mISbybcxw??%fhtAa=YUul7FP%_8b~qKqZ+ zi}om4YzO%RNGg=$kN{Mzy=$(O33UG;*NJWc7C%!0xPpASZUmk3sxkoLTlB6<1ljGw z8|ST0mw>CtpE>=jw$M4z%>)^TBFbjbski<;KwSep(sz?D*N<=o`N%p|@#(#P37zrB znhOiy^oh5CZ2C0$=+&+g)|#2`-mL->!0AR$|42Wgc`K+SKT7@4{#axsMz3p$AngH8 zUGXfZz4uW=gsh=~5;-#mxrxk{3$NsPb>HgULILb1AF0{ic;rhMJqF2!NnEnE}6^wC`SF;DWBl*%d`B{xDv!LX!`iXvb;N1!$ zaZ6sm1aL?4N3+C!p~9Yi4hK-$WD zvi75GT_J+kpJ#i&=8aOH=~R-IC8FonT6!K*22h)sKat3^%`7Gv&{FW~z4x6NAZohM z?i#J{8JLrg_8zN#GBFs>;RX*7!TUt|myXCE{eEvUEgW`kOp0T6#@_7Q>UbI|<^ySMl|fvx3V{0~2D%Pv99+*eU=t7<%*QiJR%7y-t4oSZi2z zqO}20dB!1Uh;S^;uzXTY2q-DEPgx*FEi61_!K;WtcV@On`b)l2{Y002ovPDHLk FV1jFge-!`# literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_14.png b/assets/dolphin/external/L1_Akira_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..b36fd051fc724a7f39c4234778a12c8227b7bc7c GIT binary patch literal 1701 zcmV;W23q-vP)cSp`pI#;wpJf)T;4{r(VIhnHwK%aEl@>U zum7DS&}^UQ*$&I;HwM4={(ZHkFSd52cHdpYY-AzVPp+#)HZK;IKXNQNcT)l07JO@Q zqLk)cHG7B;TK@_q@M2_>=#HF$;Dl_dBfz|JzyffPR#S8mT6U-}_s{!S983a~S*{3_p9rhyp}NE_88h~oJr z!0v@!Yu5(9iUdeNt^Qsgx<>Il-sPD03@FkuS{0e`()P)8=)AxA0{NI5JMY2-C$VtQNyQKnXlKNx?ApZzaprU%9 zk_4|%0D3)D0qB*%RV3I_0VI0wk5+(HgsdRJ)(W6i*iTac`B?^$3dpfWNbm{;;JBLv za{Lkb=a?lygsl~TgD>1uW78YGbGsdw}BGKuADuRNjrC*G@{QU5)fXY z03$nW=lK%6RKV|h*Tc#UZ^iCpCs;?rtP(t00U}xVi2KC(Xu*lvME1O6ARlKN1)u_l zlagO~wL4<_YGOz~WwqYg-}z-9Tp?{O5*$xa04?ZI0rLdEQDn~R{C*T%^eG{=k>fq$ ze+Fq!$s&Jb9}eqD+?~50eN1c}*}_M<&U#3g3UEgnupW^j!MdBcC^CL(3fNlhvjnpS zxS9l3@S{FiU+*Ng{0x=$)^a4s66NblYyZ9^>gwH;4ZbFY9(lDVfJjQwkX@4~*Ju2q zc9|5C^0kQ?;0h8TIa{{1+C(Hk`lCKX?X7;{Ci`t5wXSCk_o(?ndW>k^oJI9{zko z$W;n368w?Hr+)k;+eLGwHpseU3tFpx#_mkqzF!h`^=ZHwCo>7&*HRYE*63n+{VbAL zJ|g2}KSvdf<9iCQj8!UuS0M4vWgp9z=p^Cg>yi1PzF2)`=BL?`bJxa-NcZ7yuBrvJ zLM=OnHc9ELRp8CeM_X7gW%0Gl7Pj1uv?bqjW*(Xz;16r(oLrtAei*ueJEk#v%DE>j(-j?`6m1!PkC{ zY*_udZPXvD{S^f)Cy%QD1Zr$4L3M#hS8Io4+3y7lu_Xgm5qxV*tFBoEhyWb3X~AoU z9D}$I8LV2+5kJt4Lz|%5cfNjU0&oLaML>dY1$Sh(X=7BMWM+IJJK8;215|_jA0hx5 z;D#bHM8Tgq3eCc^S8;04?2V+bYV_X62_Oy8HCr$u1*LX>lmu2k>u6`G(MV8c=CO(d zB?RDz3PuhY{7gm8`)3jPL$6iAM+3e(4Yam^034qDqrs0Bd<6-t!fPh@qrOzsJ|315 vz=O6UkcHy?XTf*82Tu_)kYgeCR+HdACUF5(X_P&+00000NkvXXu0mjf(nlKZ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_15.png b/assets/dolphin/external/L1_Akira_128x64/frame_15.png new file mode 100755 index 0000000000000000000000000000000000000000..33607328a0f29b835feab64cb0b46b83318d3766 GIT binary patch literal 1409 zcmV-{1%CR8P)e98^ajQlW6s&*$uaYq|E2W!qT@9>1$;Cfkpdd$@O^p%pNu(u!S}kZZF^_~ zH~>(*SFzFiBlrU0J?dLPKDFoKQ%o1rP+O9Js6Dl=;YY#I23d|5eGVP*X)<%2v<>6!EC2=D{yRh1*g zs`4qMXb>IS05*X$07?1Sqa(&qkp`LsZh++d7?RSX5h7~@Uw8vO?&BPwAg%`h??shK z*IVKMbI!Rd_r3C%fh3E1x@W!xu0)KWIlu;B#e0>G=dBn3x3&lUG;jm3@@ROMlv+_4 zL<|S8-2hJKt49CQzkP!p42(Req$+tnF`%eipDDlJcdzY$2=sKapp21W+L{Y-G@cIIaD^5sZ|7$fpi35pyg%Bi}8Dw z$8&Nuc+&*e0K)S*GzUQ74i6xaIQpI>!}3xIE7gItM-Jan!S4*u6Fg73M-@%Ozvs-g z=;4JF0r2`A0e;|V-L81=s^Ve2$UKb$NGajt4Z!2|^m7UHpeZ5b?#ZkdL6blPDNkDu zYa(*-HHOs^_R66$qtyi9<01gZ@HORn>uW4WlWQ48H0MW&C6!+q0X*Jx3Q@0zIV;T- zx+aF_cLF@JR;vS;3-dlA0!Vee*GR2H7W3;u+K`YXq;xLGQ#*mt?pb#Jj*%l`WB7aK zxRa;7a92)~$#w7|hN!I(l{E$v0q}uEMrlK<@+wBW2f8oM;Y7kL*WS(Kb}Gxec2xFO z4)ChV&*WFU2InMN6pE}D;T@`+@rGp}H3W=EUd|v=0aPenTSLQJwt_ufF$q=5*NiXh zrA1_wl|A3U0#>}DqWdQ0eM=5kidqI!?fHP0F9W>?j)56?W?k;uqX;cUQCYkDDKEhR zBITPlm&#Zre9|1t7$wrDkwHpoHLOH|M@ImuoQi;(>pjp)SYB;$mdZ^1rxjdXe!(J< zo&o*Rc%xNcUh`bKwpSL0Reeb3LuF`lBT>LpDn9O6fF~?xS+S=kw`5rR(8BvlDH<^X zNd|{U89=sv_wEhL^zJ!IG`YD}x6oQwi=xX-tFd4J*%XlN+-1Wwy(i*rs}X_D%@}kk z9~cj)0we>X_eh^EhDI^;diU&xB*zo zul6Xd!l*KW2)FR;btjG*z}?EnkgAPcMu6u?`_bm^!VwN&<^0{s=ho2_U{xlb3L}C$ z4d4;~CCsb*>O;}k{z}7&lb_ks9k{&}tkwEfXKY7Q;0PPOwiKe(<#)E4J>d3p{8@n3 zBj=P~RTW5kS@FI|3-~U0&*xSH&<={b7f{8jC1_C~qdbtY(+IL>@dyq;af%VcdMoQ% zE!s%)%5yImRupso{xh|sDL@PV0IQUWlv?R1aO@}uqJAR{BZh7=@=p znBfQ&O4p1h@?0Y-$C4?X?{N~i0xe>Aq6&O|H#p;zTGP>LiIYL)bJza>A;Uzi^@JQ@ P00000NkvXXu0mjfC})%l literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_16.png b/assets/dolphin/external/L1_Akira_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..e115834eff3b0d2b3240dbf798a3152ba8995eef GIT binary patch literal 1369 zcmV-f1*ZCmP)sPYz&!?@)fv%Ha7f)sirJ z&4VXHokOyT>nPk>T*}D-{=nPy^&Uz-Kl7t~0%}WC z4sqh+00dt6ECv7FyL1H5JOClniXJF5hpyMZqF(i6Aay%W_z<0~#MgBqa@suYXgU#~ z_{7&a2nqZ})9=zX%?8bm9!Qbox<5PwWRfrG)%0;_J%N_w54Ek753K?s89939djxuG zX`MmOK5AnGAK3?p*5DEHt)76S7q!pQ)mj}?+55gf#{pV)6_tx7Z&BM?$?6s z0DaIM#~yGUR=DSK+C6O@Z+;{qpz@L~I3M_)y9db`WOI1rqv5!ve3P!VJYP%n2{}O6 zqBns>K1xJun3awY$(b_R!yF+U<)IFMzHm#v7m*4egLrEV4UcIASzS9mFY^Uz(AICw z+($X?V;VuxBu$g+A%>p&(Z&tpFe4oI5fK1=;S$+BiRYiTYG8MJD1lG>w zYood-EV8t7AIU>nqk!AzC>uI}R>D#AX2p>bF%r@xIf>wwq&4CMnhuWb9KhQ_y}Bix zXrBOW!&X^1f$*mkfBfsZi5m{|bBtb*g(`0)bI_*d-8~!rr$2cc5Mi9~G zIR=_1j7ls=6l_~W0FV5vjNPj!5GSDWo)cu%@FOerE&2c+`Pn=rDY9GcGd4& zIN6dN({ljye9c^sj@q#!m2DZew&V#>^f=fS5nv>&=8t!#M@jQqw2|f&GFRKxi2iER z@;?a42>)KGWf7^Zo4sRcB&gqe5P4TtayqpNusVL$?$NqxUa#Kk;aj`42S~+$tQpng z>6Dyrc?8hFOEZZ~FX`fQJcpP8E&q^gYOR)#i1_n!*)Rfl4}d0TS>d(?=n;aDXg!A~ b?>YTHjQTa#bKt=W00000NkvXXu0mjfk)(|v literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_17.png b/assets/dolphin/external/L1_Akira_128x64/frame_17.png new file mode 100755 index 0000000000000000000000000000000000000000..8e5fa20d8d9290e70540c179fdc8f96d97588e30 GIT binary patch literal 1398 zcmV-+1&R8JP)}C~+Ah#sF zN-4D*d@LTj1t_JITI(5ijw4t7p>oC-9j|giz(?a2A>d#izIF@nsi+lmyw`P2mE#z| zVl>60ijB^X;?WqKasF6qE!TCG>$;xz-X+b?lL=pRAFw z6j{-|%IC1cLUcR^Fagr(*OV`vXared;3R>`&|E)-ru3+X$S{Hj72=F@a*n1TsitqQ zMU_c;4dno})|wUj-f8TGMu(qt?RCV1a5^*>h~ne`$)JkYl+NeVIXwm-@kVhz4NL~F z1llzl5yPRFZUDRE@m=x&3EJt8;!jK`IR+xp*`xzsBPrc}I|I%|v?s9T~ zRG_EWI{iHfAb;i=V|#x305?N#2;r}56#~CM2CxC6vA<`oW(emCA&Zm0QP(fg;gAf3 zH=&-dmy-je!#dGi;ZfyB+RT=G4JQOlgcgf6ckE$Q`JxQvcmNezhV8A9TD{V#*LX;< zas?!Zkml4{Pa{Yg+eJHhG=^I1Xq=@q=sn*X54=~xFUvDXd@0@I0T@P3ljh!~xhT*u z!Fh8zG@KmZ7wCqe%BKqRPN{~T>OhlGoA(tZ{;r5*&t4)Xm3k2)u)GHf&7iq##{-mj z>4ehkRea+YS!d+{R!I1013*s~q6EItJRy|kXfkAbWy_O%ToQ=JdOAPiA-oT1&~-h@ zp9hKmM|pr~Ohqe#Qn0UQc}5YHFPd0N^iw^6=h}85dg|Mno{E6;r10jVw0f(9wwA7Y zdk-*r>b*_2nk)z}WbQ~GLE=g2nVrCB7j1O?j4=}HwM5RlXjjgoFLenc@@YJxv>N_- z0J;g0Q4^|{Ue<_iMsE^E=Su78o<toUg?kMhqnu*_zaNWZ?g1&p4G48>4mL-van~zLp0a@seEzFd71o^^Jza8+v2FT8$@?pdtLUwIc?dUA1qo0yN{{bF^m`U6h_@ z7-*g_E3ur}s@dKMLN+@q1ZavxjDW89j3BFqU!j_<2H+__`$Sn4MwSsoatlP(+1O?P ztCdfoRU5O6fUeQxvDVJS@*Kd6`LmTT@v#(u^y;N&y-Hkd0Nejd7^?jA?AOTlBel9V zx5tQ=pZ;s{4@0b4-|LLc@Cs7GM(;g^SatczRx<)ko~@q+P&bMwKdUOxWa&A5krwbO zMD}Ny0azO)&27lasx??%U_^P1#!Mr~*o*Bs0LRvh7ARWSy93eyLmhnVB*ND>b zXj=Pw+b@J@usjChRoe5_kc?AnO-HLGi3X=H-Ir3zH^}-5)7n9zod5s;07*qoM6N<$ Ef;@4W3IG5A literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_18.png b/assets/dolphin/external/L1_Akira_128x64/frame_18.png new file mode 100755 index 0000000000000000000000000000000000000000..6a658e0e22b8218d5fb63e3cf0d494e7e32f7e9a GIT binary patch literal 1360 zcmV-W1+V&vP)(MQB7Gv%uQv)%nO@_0n=arTL32Il=_?R1oHBrm5Dm@Q zyl%Ch6vGB^cb?yQ2@^~!Q18t~`!}BVi)&d!xAk!U4 zXT@Mf>~=JBZK78Ed@^BA`m*Fyg&lC2H)QwNC3e`q*Vw z3iyUsNggFoh2h`1&*&MV`$a(S@1u)AZ?4Dly`&D7pCL&Uz;C^2;TLaELYb6ACLk@ zl^=Sb3iprC5=C@x_ZoP(0gNbrB){UlxCp03ipYMWT*adeAgg@5eiV@oV20wgJvcm? z3g&qD{<(!&%GdIJbdOBkV#;g}vVf>p%%*zrT8~Fd?+PUO5iY`u#}Gl@2aYkL>!9l+ z-c3`KBzt9d^j0-Mv?Y_)YBDl;n2Ri$9I5Ejioqyx<;7b>0I8jdfLog_4o6^lwRB%r zw8)-i>j;qP!#&VEm+tMAg`p`+%KM&HFdGH5T4!4W@buxMRk5chw`5r7(87DB8yRIY zDYi5K%Y#N`b27d4MYPc5)>>?#wXa5-hf^bb8^GQCKR5n5=)lc-)=GPN3N+7t(%KP& zp2AhYMiGEhzEv4}<$C2=hT*LfR=(7liLHnr&%>UT0whIxi~z5A#;rAgmGf6CpW8=MfQPH+zRN1{ z3Io{c{{rq%e&1KBF(SRXG&ge}S12FgMg!1#{guj(gz88*)+pTFa2!8>Q@Bk8usHHj zU@sjZbF4jPMu8c?%I+7&EiZ#K?_aea9;J}awQV!b+GVV~=d6Kk69HC2BY&*-1W~0~ z6m5jO^33JD+AF`cw#Vz>8R6e*wKPRa`_A@Q90~gGBZxdJ>G@pV25POJ<~M+InydGo;v^N*Rf?`49B{S( zK!wC(@pXCuiL*!pcsQ;wfLd#|C)<&y{z%F8L)!vL0+F0%={P)>?PPzE5gbfRnAAQv5CA0i^K3&5;~n zBQzpt^^e>YaD62s$U49#^eEr+jw0&4mK(t7xO+?6gOQL#=fwut9)Qb2!0EWZvIXGt z`I)g_nuD9~N(1=I;1uaqK7Oyk(>FQ5#TZdOmBE2CxGV&1f~8dYEvkI4>!-*+2suCo zDY2sjZI|)^k^^*s+iZ)eb4thKr0*8AOu>^uqyYp#l}5jL3OWsMY`jzX+8E0?0MY>5 z&|I^0it#(^#B*{rc*BI0hPLrZ0u6X`c(3v~E)Y>Xo;Ny+da{G?pM+_cX#lGs<@Mf*UWB#xtgUB!4@bDx1FVFk#MuV! z5D~z^L*gZdR-PaJ+JO(pz@*Dmn3G{9_29DJu^K!pEim=kl85cpUT=-B9Fp_e5PgYMH<_M-m z+_<%AM9x!i;>L0KI1eDDQxR}udy6k4uuiqbS*CCk_>e+SPaf`pc5><5URfAc_Q9QO zPa4V9Ge1HLX-0~ zlxT8et!kk)uePrq&vms2h*a)I-8AhJIc>cjfsP&7v)+(+Z5AMT71~Gorpfinvkb$_ z6IKz+DJ1aLLXc;(XN3Ssksc$!<1HhYmBY_e*_8%hDSu~%G0O-d+`_ZgS-8#sb}Ju4 z$~IOR0Ujf*M;ki>*KhzU=C4*hH;<-(p39vPC9X1nYyV$>SNYRjqQdqgxw_Q1$B0)c zAK(%L&~kmLGS;3IUV&Rn#2srT+}m*FU4T|2=R(A+tUy{zkK!UF;9cPM=ePlA8^!f) zn8m6kaA{j)trD7yl|~TRiz`Ba2Rj(Cgtnp4d94y{qxiDiuk! zBI}xIbl4NL-y?|p8($6+983eX)K5ysC_ELy5somW+^QH)z>%07*qoM6N<$g6YzBj{pDw literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_2.png b/assets/dolphin/external/L1_Akira_128x64/frame_2.png new file mode 100755 index 0000000000000000000000000000000000000000..9f4cc1fb9e43970818b801fce43ff942f343544f GIT binary patch literal 1836 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_21.png b/assets/dolphin/external/L1_Akira_128x64/frame_21.png new file mode 100755 index 0000000000000000000000000000000000000000..727511100d4b48408d45459ebde3814370e6dc61 GIT binary patch literal 1276 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_25.png b/assets/dolphin/external/L1_Akira_128x64/frame_25.png new file mode 100755 index 0000000000000000000000000000000000000000..26c944d16e6917c7e73b40e072cb8f87a5ca8afc GIT binary patch literal 1812 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 Bj&uM3 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_27.png b/assets/dolphin/external/L1_Akira_128x64/frame_27.png new file mode 100755 index 0000000000000000000000000000000000000000..397face78dfc1d6e247aaeccdf0171b4699b41a5 GIT binary patch literal 1426 zcmV;D1#S9?P)A4Fbst}zWszvpI{{;@6=y{8Dwk*kKKx(u=kV;!i){)pNUzw`HfJ~NtE`_(PQTg9T+rMHIJxGZjXOyRD*FPDtO8b8zb3CcVeLX`4q$n$aQ)Sw%Ig9>&uA{!mILe{AHWyaXX`sn391Zgc@D7qpd@(6 z|7I-D0aiZA(jZxCgnT^av|J#?4=fA;J7YfpuK-@RPvhoHb|;Px0lTfAgV*v;TR%os zDg4MjKm-xDe=7NvSfT(cT%%VeCj80S{%S1q04v;QgpjD)2YAlY#!M1et^g+;REzxR^iQYisd- zCV@*VcUe8gM**S_6Ds3!Wn!r*Q+I|>J=t#J2aXB>Rc>Fh_;J0{1xuEH3QDSg3gWz$ z&yh$EIqUAT&j7eH+1)~>IHnbxb^lsZM}l{*Cy?*ibE*?ag=LVH7wP1!B4h{oBeZdz zk^cO19~=+@qL#0Pf!TZ(*%9~ONj|q%?_AqMKxI35wZ)%6eg)|m+pB;K`P_Z8P-P7b zk><~8@}ql|b#4AaNQL{yheH)<_M}ge^CL+z z<`k9WclIet{yI2T=K2p2(tGA=;6a+&0dcnjkAoM z$-sN*>KEMiz3=+~nbJ>6&?;l2MP3DQo$j8)mK-2LzI4dg)n`j2k1}P*KdD9B{+~jl z!_UOv?lC{nmYq_m(~2y_`Tkz^1Q+Mab92XmYi+MM9DFUg`P7PT~jJ`v9K0 zzp_J!y%l|*$wQt@K0vbqc-+5d+>jkvi?<2oNpw#qAD}r0h{XO(mr$D%=LuF=iKL`^ zDhn-VsYL5v2V2KVrJhvR&uaO~b>TU+F2hMtkcjisMe zlaI$pl?l!bXs;uRwj|IJ9y}J3RVL5DSrlU?&(j}~NakeXanbWA%K-r9c|a!iOJj#z zs~joGlCEVBcOa55k9A-f+;uA7i?$q~IwnWJ9n^SF50#d08AQm(YoqJ1D6ti73J^Jf z6MaYuxS(6v0B+!c420gJ9 zyI)#%NnV!Z07*qoM6N<$f;o1pI{*Lx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_28.png b/assets/dolphin/external/L1_Akira_128x64/frame_28.png new file mode 100755 index 0000000000000000000000000000000000000000..6ca246f57c4c8e7b8029958e5555b116f27c1536 GIT binary patch literal 2053 zcmV+g2>SPlP) zMJd%;GUr$!1C6m`(LAW_F5~Zz_s#@G5y1EB8ms~og}jbq^rKQ$mpic|(u59>MF8Kg z8UdmOMMkcmBg2mCZ2Fxk;VOO{K*^P>BY>KpqZE|!gs80w)zNntf0t9e$G}7e<-#li zOn7n?^;M9OP}aYKhyy4x|5d!k_*^JD$#c=?d47CGZPr*eGGr0L&ud}z>lok;K7iyf zLYWWS>{gu3L4OF>X$*?Q!9LMn|1EIY+q}D`&)jNW0uIz*``73l$_3V~`%CoX2JNH%0d0+%! zirAk_5k%^uYJ)wkvMUzO){2O%f=GX4Y-CiG0M9-vqd>?3B5y^W$rSG>-O2UJTqG6H$B2?X@ z&Dllt6kRf~=!taBq8ZaEtO6nqpuCS3)P?Fi)d-*%5fNB5TFm|#)IIlYUD-bz06n_| zE>c2azUmz6c$7OkvK8Z_y{hDC6fM>HGd_WSqaoNCq{vFvPIhv+FrXZlqlYzOUo0IOVN$cU+;Mc91ie>_wpuuW8;I4zTl@^{yH zJ@Z$IX9gGnc7=bIpV^5-CzE+>WmB0B8DCKIA|tVt6tjyrA))mgr_vZEBjP7KnEVVB{jeoRj0nAy7Q zoF5zjO=N|l?<_BQg^^J*mY?aAy^EqtG{?7%Z}k{M-xoD<`9tu_0Z<-^LJJzt+OBS# z8kVoj|1IPD)2gIg|DNg3s-p8e$2%i;YKe0tgsWJw`AX%mQ$ErM&CT|`Q#~;p$1&c! z!j6eozv!b=grD#hBXi>r+3=#!1D z8vlynyW^*xsuQROirxb}1uW&*cTvfT=3^x+lf!hWU`6hp^H^O}CtzIZ`14Nt+d3s* zu7<*nDYJTN*66yS$w>v;a+*WO< zVxZm5R^>~bJ@9j*vN3wz>Hy5pcNoIUMBl3hSedRWaupqH**IlDCm1(z0F;02yVXE4 ze4g~OSDZlhE+Vxn%FAvwa7zS;jK2e!jEFVwibN>&t(2EJ8k6&N5_lKkL-K5R^liTH z4*5I4I2Oa#9l%405UWp(ms_bw?z|MMfUV8rF%D44zgKcKGZtdcSB={pk4n=k4i?=7 zJi-C03*32U@QwS3F1k9}F|VNCEx1AoeW_#HpMnnYWjB~R@Q4V&7&7V;ja3c5`qD|2 z`6S1jc*H$`?ZeRd1r4hE6}i5C&kU#bVY+v)>H_Mv6o4))<^s?iwa4reh5aZMSXoFn z&pTn)dn*UZImq>%f_Q5vt>?JU`}k66hD-9nZec zwrrEo4WM_u~$u16-}G*=c4MIsUts-yxn%uN`ZuM3)Z7`llQq z+X&?sA(aiS>b8>LtwN^LSAHG5^Nu~r_mLw!=}K2|=7415g5sv@TH}SZUngEJgnTjk`6B)tZ_pG=o%>+lNm3%7|Qg_h~hU- z@v5}-Qyq*O9iVzuo6U*(XLk%$CqVKPbO>vf&X9iWX~&1WXgn+ebQo~8c8^Z>mDAp3 j?^Q=->8mPZj4}QLk;r=k|9eg|00000NkvXXu0mjffRO9u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_29.png b/assets/dolphin/external/L1_Akira_128x64/frame_29.png new file mode 100755 index 0000000000000000000000000000000000000000..8bcc83a1db2c9656942f74f1f632247e78502c92 GIT binary patch literal 1891 zcmV-p2b}ncP)zkwjS# zQ=msnHLMu}uyXwuWw>&BmJ6%l>zV32qGFN^M8b0Bhz8O|M3X) zjlruot{MR%MMa^$0?)Gp7Bq6~U^Mw3;( zNN*&ad@`ci=s3yK+0BV^YjwU_%`&6Ql>bfM?-~9htJbqn>oMzBr3*kFEWhI>GPzXc z->r;0fNOuIfb75}Dz?9y~c%2(tps**=?zvY1j$M~*FPa{gd(g>!`0b1jV@v3+>2Whb{g?Ck6 z#&;z=ns#p#J zSBp)>5NFZpAdkq%uQq}Q6K)n>3g`>Ms@{%K(@{nFUXG6pbSCsFt!0#UFe2>4qce*7 z)zw9kSA1dKR|=>Mik!^oMvb6igq@FvOKN~j2=9Fqo|pnFg^d^Hqc)Anl0^_ZhOI{l z#VDI6!-ywe8UgH;3BsN!A&fm!YJ`esWDzQpGc#tjXhWk?m7hhrop@LQvZ9R%G}AUB z?8ZfOy%y5r=h2AhBQG>u_PIj$T|j>|Of9}xmFsypR{o5KotH&`NY+Z&MVY-+f=Xte zk*1X)3+Gh>UIl6Zna6v1nN{V{J$igrjC%7K&(WNVvNFSs*00hXk;}@n3V(h#kQFpp z4vd6lpVb9K8@<~rlv+Slenxi$v#G?&{`}oQMx5P`gfWX%d2qG&XY&-}AECQrUIovS zz^hP+03-92_wQJYdDwmqFq11gyn`M4&U-(C$onf$`uw{Pov*CKOA3gD!946KJgbs% ze$*0HFtR~AjKI>Hp{dkZRp5HvKM*k@oPC}NS}mY(EP8)+ld+9u1#ne1&nXo7vLbg^ zbdrvORUxuowz7zwxi34*urgF@2K4+W3VQSVc*M#*d3FI&f2&^a#At}klw+T%wFVlK z*FJkRJ>{?RewOBr*4F%$u+>n{0cLY-HT)~7VI>_N7$goTK5k4*g%z)+D z(Sx2$Syj3t^HjP~vHTSV5EWx8aQw`Pp{1G0IZ6xPd2RGZ89+7vN1=Ab5pU;_RrHLW zBIimP-r31z29TX~@8HF}bEWbvKb9U;IV-Y^&SiUW9LHJ%U`l42GJAld%Ag$DL>Qen z8p@+~afU9!@>pU3o|5e|&jMFMIWgSTTW6=18E{XoIt!RGfK18w%+6bF1ZtR8F{8{X zkE@k#G4=BRnUaS9;f-#!ly#nmRGhPv$?3h-egY?+<0q#`1U1#`T z!`L@HR(rqo5U&`D&fZ5220v%(V&1D5)13t#gO&L>oc|k$){2(ALl_2+C!44A{44rv zrO30gkML0VXIF2I#d)5u2Eb0WcIEw11Mp~NMsQVYhp2WRBiB*9w1KL}c?)2;BJAxh zP8EzOKU1oDRrw*pa{#r_$(X-O{@aztJTFvIQKW2@-VBBhf+|XlP7JTc3M0t8HcCI9 z(ox2%P&R-WC1Q?_@|UH*+Hf*uMVM87|IB2%18x`4+Fn7?3u%Qa6p2W$s#rCJDaDtf d{A~Qy_yvDSf*KIVECK)k002ovPDHLkV1h@ln#BMB literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_3.png b/assets/dolphin/external/L1_Akira_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..7a1d2b36e828c299c37e05c6a3a078abcffc6b5b GIT binary patch literal 1908 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_31.png b/assets/dolphin/external/L1_Akira_128x64/frame_31.png new file mode 100755 index 0000000000000000000000000000000000000000..4f2deb5c58b47c14558a5183d1a7e9b90b16d71c GIT binary patch literal 1911 zcmV--2Z;EIP)^@RCt`_UG0+NI0#fydH+}T{W(pM9%vdN+nH=vZ8?!8K;m1u z-FqCzaXcQ6aU92B&xzwW#u(#mtOBIxy+%tw3qcI7kB6p(o%<`{3x`>YBUS*tZbB??6f{?^>@ zAxd=&kHgIF9u~{B)%9wNGruE0#W;536TH9bh|j26&$@t!`Ftu}0P;Yu$Io-Fs{Ff^ zaR(xYi9RpY08x&|yc{uBc(6;1%y3xv|5ptoLa7U2eP-tAti@A8zc+WvlF@nF@E3=F z3$+G%MM`DG89UTSk#;P5dVEyndwcoU5jhs^0>;oUW|(5Sf>u2LuOLdRci?dx?>`1b z$PU|65WZ8t&#_5|br*`RN9urq8pxgO_D+rWLZ(pem#qg`)bSy637AD&5rOIUnv?=siHR zxDqw6dtjc)=2Z{NGcZ<&cCn&#rLNiyEX?H^AVZ`$MTsmovUQ3)6+SD7XnLVMdm>Gb zHhZ@M=^mg1FRvlL%=lJ31P>Nvs2m_3dbFM?J$uiLoatz#D7T-tMSu$PtmuovzeS}0 zWroL;s~QOwmzeex*|Pnx!ntAu7@b=YKrtG-L3>b9nm~AmQ;7)dV7pbe(poYjT6Ml% z?erMrzX^;Oc$TLM!Xf|@bXTQr_ZbfSDUF_w9T7b>$f z5bgZ6*=+=tUWcYqL#>2;rH%geFN9RV zLRTiV(gC3NR}UGBTU44Ni${~m%#aniyQ5R;)MNDiji4@A#XUyf02HsUhFFYlmF~s6 zH$d7uLcL1oj&M+DdJNG!%Y2pwhmz22k_=Zk`#cx0Foz8I7IFU%BIG-vfL)Y`?D_ zc9z>GO)oOcC%XqZOZhAK6DgpIZf~uo3^pFoU0J8nT`88o$^cj~rUK`yhLF+BDteEeBIiyT)w`4J25?0ohrB%R+^Kxa&r1)goE2GC*Lr8b zOn(Eu_fYTfG&$3-lt$ zZlEJ{C2a196lWKo3jfICKS}vxkODK&Ry z#;d+louK+Vz*WG#%(1sQ$Nz7<_U($hYQ+ec@pyb9CCI;!IX7z2_43)R+$y5$;a~e0 z|Nk0@o4G>yR;cKZy}9$9Y6Q4el{qW&tn4d16#m&WT4TY^^(_LNmG@T-AflBS!Bwpt zqEj!Z&)s;d0jjweaYfiWyEs*_qWny$>Qm*X%KeI7u*EF&t_rskXQi>o3zbyFl&#Y1 z!lRpZ0TrcUL3gJi>@b4NYgzh@+1yC>5p;phJt xg4kWn+V99mDH4%hRk3OcQ;IJ|`PuwW!GA`&=&$EdWJ3S|002ovPDHLkV1m<}sCWPX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_32.png b/assets/dolphin/external/L1_Akira_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..4f59f99555b225edc829f6204cc80a5ba5497da7 GIT binary patch literal 1726 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 JPDHLkV1f?phW-Em literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_34.png b/assets/dolphin/external/L1_Akira_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..5ffc55f75f3451585d426b352cc99df25d496269 GIT binary patch literal 1126 zcmV-s1eyDZP)USFbIIr@%>-f^P@+L6v8DJ z7XaAzeTQXPfW98=`wjqbIp%@3$9+zr_viYD+m-s9lvx14Eh&p|IR#2>MGY(3x8%zT z@D&<;rC?8>{ndi|AoY))lgjtA0enX*eWhS`tAX1%+_iR|6oNET@o_F8KvO`}UH1$D zsr))t{EyWG9B?kU#uZB;$Qq&&KNkbQ0_KEH-yh!McyGx8N=n2AXi;(+LOr2b00tYv zrD3C6w@m3u*Cus{9>5A7wGj`%je2TO>H7Q8!~^^^>Kw^jo7y@;8bcdY51`GxrI9bK z%xUcb{DzN~3q{G54gcergO_4I7uU5{d@Vhm^dr0!tnC?C6&XqX@V#1Noq_xnR{fMR z*p(k)z!I}(8|k?3Z$dQyE@o5rZ5g?xxXuZkGniiM{dhg&ZB%;zfUDcoglJ6$v2pcO z%o|5NrfCjf{oHdOj#f&0yYc~o9>6OaEu|Xyzo)5;N(jrpNGc>6$mp|7S9UpCnM)m??FdOWuxSTCBH}k^MEO zZ0_X80JL2?sa{W_v|oVV&oCNBGlLPVTFjygF0Hb2u8k1_nL+3?v6e-l=oD;!A1J5y3N8g>>yN4iww=Mh+GnRo=2``u{>cg;>V zfbv5c$LE{^z05wsX73*WFZY^$Z`$e{eg8zB(0?hvDy^Gv=Xk&883HU*K76xiLTLFy zt$Bh00PvwUDqrh$7Y{(;lwUjb<5mlkD>ZWV8ql);KSR4|Z3bPse$=MS>|UTp5~{U% zZU%9gf9XO`XG=*NVd99FIx9a%i~TDAR$%tc&C6f#rhd@g1a`tOjW^87&#sR z&ip-k!hLUdNm$FJ;N;6)GFky_KN1yD zgj9oItxc#cu6zsCzY&DdN}9QI*iH!#qFAp_>@+n13(?}yqz}Ul;9z8ha3XV?0dS=9 s$1_buP%8f(rZ9k1{(Veg0Lgv)16R;j8|j|I0ssI207*qoM6N<$f-x2lJpcdz literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_35.png b/assets/dolphin/external/L1_Akira_128x64/frame_35.png new file mode 100755 index 0000000000000000000000000000000000000000..9a101f0aa36d323084e06e505fff0b14cfccf8e7 GIT binary patch literal 1555 zcmV+u2JHEXP)Y+s7={Jr(w`|ES>9e$;2P>k<1c~8P|w*q&88ZnCD1k!a$&Xe(#4v;ne|77@q zuX6yNz7a>z()}kJzO`TdUk)&n5-V_pe5r<8_}>gK>3vvVUWttSh|^>o?9mze1(~m^{Cr5o`PfZ7yF^;x5bj_<$J_?lNJ<;&ly*x5E;|a4i{wl7KA$wlb zKh5y7`*@ybJ1T1Ls*RBhhsuhi{7*~wRRQ$Y1!UnxI~SN?;fSor7`px^Mg5A*x%3=B zQ&@6>iqa5WgBtg#hF@JSkIeaZHO6LV+PaLMJo1rpwt^5>%gNxy0nk?s$W2BvLIm0N z4vl)`0gxqA>n`{>PY-SU49YUVtU}TGyq!e(fh5b?aB4h$rhs7RMdw(0UTOTdE?{K_ zSdrF{&7|_9#u2iy_&#b- zE-O+N?lk^+p7&({YmSsDq)qlBKN@A49+`|#b&gPZHX`li!3SvzRA&IR1W3HYTbItG z*P_U<{xPq@2{OjF_Eid$AFAy5Cn;eq$`zpRwRIymL~jhq8WqOpIz*j(=3Jw-b)6H> z20zrm(oIqW@P3$I87qQKF26=GhD}mmh!D~%;!ta=>8zYia0HD5rbOmAlXJ5cXi}b z+~Ig!wpT*A)AZi|W&n;%`j?1_AW=WYq4(l;0U1c7!(#IiXFz07X!5MN zG&^ODe--)Tk<9hr`k*#VWq=H#>D|i&6k3#_nLyLUo8vh)q64CrMWCf|5;Uh#e-KiQ zs&-GyAks#o^Q7yVF5cW(WQ{+rd%FJ#p0YkA zgIF?J1D*v`LnCq(GA#m_{f_IlI`XCcpw~S1FRg2q&vp?9*wBHbcRDW8fmq{u@45dR zx={}cI?goHz89BrE>u3GrexD(b{%^TOieBgy>53-{K78y47`+IDpm#Xnj3b z%$idLX~Uuf7&(BE){|hflB2}^eBFNJR8QE?h6@dIrPCx6^g zb{qIGXe=Kgew>_-wX71f9pFZ2Z7MaKWjyUHfnU?QkJk3HfLq{2G)nBWt^tudKJN^0 zGkD6@fU^In?Ck(Iz&aVU(!F%(7HKiXHnQy!a2vF`?2V<@ve+EpA(;6+Km~RQxEax> zc&!y5Dz%*f9s%@epf@Ip?Envh_gJ|a+X22Dn*)3~{sFEAmOW?D3%LLQ002ovPDHLk FV1m_$>L>sJ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_4.png b/assets/dolphin/external/L1_Akira_128x64/frame_4.png new file mode 100755 index 0000000000000000000000000000000000000000..ea42d75d60c0de48db304dcdf196d5922e68279e GIT binary patch literal 1989 zcmV;$2RitPP)b7pAL76Bzti91_(i|2>na~qEB-G+e+J)(y!FVw_kBOY`642}><5fuClTOX zhVkU+8qSb*yiJTJJAToR0AMouBm#8!RgJpV+kG9_M^1ez+#~uC0EnUkP8@8PN7ZS% zfnD-Dng6%Jy{4rr)dZqLu~UHeO!NFiA7RJ3%08yad#ckdy{d}ZD9@Z9&AXp&AZ@c( zEq@}dbO z-&3fX(5(Qd-UNC|p&*zp4d`07dh4lqdCxvcJN9~Y?zT_e1L#sx&G1NHZ*4?2u$ePY ze5b0~|g1yCfjiljSs)-;X)yUyF4>5-1>Jre}> z+duocr-N;TD|ZS@FTE$ybISQ5f4&RQ8(0MJ1g(n?^mBr&;nDAE=2x=bbF_A9rq)|ItdX;IRW!&Zs%htoh+HC4PR*!uuPS-!vIuqlD44rrZ07ThJ&piYkayS&<~?1aeNm$tHGET) zB}R2(s8D4}(lm+y9z1866m{#?xt*~sa?C{b+r5!4)8y$Dq0G0}@@EW;3}c0{uo*eB zJK|~GvN6w|C4${u=QFaVgJYr*rV&7|@iJ<BBgmajKmJPQt#{ubWuk z?g7@%dLId0#AF8Q86sG&j*nVKgaBIp+JkhoJUcq>P8m~#{JHFOU03hRAS8f=&#E!9 zGO|d&j}X9vTSo6_PX_NN`h$rQ01-fEM#iAh7-f)XqZLIj%5H6B#`zWzqN&5v6_vZJ zZ#qGdt!Y>Idb_4_&PoGlb<6mB0p0BZzD)rvqiI;L>_;fVII~J8 zYIYU`*&4lWt=+Mk$S?vhfx#%vM7e&B_D8`R(aB~lDka?>pp#5m7JUQ+& z`pP;#)5$Y<6~U@|JYCp+3dye6-Lvo8dw?naOvfJ)%dfg7Bjmsizp2skx{K)>LH&tN zH*3V0ac%91rVwU~+#S2v`kkRyw5CdrLV(J6iPkWNL+hfZawpRwIn4Pioj}>Q{J((= zzs1XbhKQL0cGlYUohc`J#*C~P->G1r5P*TJtTfo@_YPC7#nG&f_-AsW&pTObb(<x&7j*nljWe@=>bX#XZ=jrgv^yr`f z6#z)sq9{1*XUI`mHZaCOLE!0?p-d&?QJ^8GdlVs3l~Q4 zbjCi<@tJNFomPOYANEYqI`j+%Kz4ApfzkU;)>N$vV*tjnCz>x5RNBejnzb&ImFbM0 zQzf7~t492y?DwG1qY6w$C2GlD`gzVk=eLezsDe6cz3nGgM!e{cOHp%Hk=W$CT|V#u^X zvU>r$Wgc2(w#KvVMW#k@Y&$<>j&eW>D4> zL}ka8D7&8Nrr8o>pD|5bXH68a*#r?$D*!aT6XMtmi~0;*B2Yh}XYfx^7J!pUR|#}n)6GU~yLwZL1vvn*{V_;@9ZIJ4P4?`KxT_dpFeWsu0Wy_U) z&gy)+;oL3(OZd)SHfz0SEFa4c)o$i2Y(TQ(ai*)4?LBMpzljrf?+Qwa5HdXY>qb9Q z3L+h?Gb-@R=p1$Z*+j$AC9?P5158oYv$za!dI#hLG*RvB(hsFY60`8dRi&@{5 zolzE`J4jjGJ7I5;VgO7|0g;T1&(z_kXd)098&21Mk)Z%s&n6X%GV&>oKSige9YntY X2l-HT4d<&o00000NkvXXu0mjfqrTcj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_5.png b/assets/dolphin/external/L1_Akira_128x64/frame_5.png new file mode 100755 index 0000000000000000000000000000000000000000..c347d3035380bfec476a597479fa0d1a82f5084d GIT binary patch literal 2000 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 Hu0mjfR2qjA literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_7.png b/assets/dolphin/external/L1_Akira_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..275f2a35770c4d3b88e915969cd713f16e6eb690 GIT binary patch literal 1868 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 Hu0mjf3G!+@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Akira_128x64/meta.txt b/assets/dolphin/external/L1_Akira_128x64/meta.txt new file mode 100755 index 0000000000..4ac9b38b34 --- /dev/null +++ b/assets/dolphin/external/L1_Akira_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 21 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index d9488cbba5..70b2b7159e 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -190,6 +190,13 @@ Min level: 3 Max level: 3 Weight: 5 +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 + Name: L3_Fireplace_128x64 Min butthurt: 0 Max butthurt: 13 diff --git a/assets/icons/Dolphin/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png deleted file mode 100644 index 423e079199b00df0d910981caf8944cbaf8ee67e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2023 zcmbVN2~ZPP7!Hc#RVu|&R6N!I%3-nxkdW1AfgnT)&=3`rikr>mC`oqNT?mIZ73u(L zrQQXsTAVP$bgCRqVL%3}R4ZPzma(V>JPRlyt(Mwx+HOKfI~`kFcXs!^eeZkUfB##O zlo0DNW!4lPkLMwelPS4T$~}uGjpN?2soqDpVKNn$%J6tor`sPlUipC;Jl=#i45}11 zMG=qUq)CWrNHrnMF;N_v$6K;2hr;j-f(6us&R~}EhnidYfI%bWuL)N`3M!h=8{+b4 zA~`QXh39495)FUZQea6A$`P0d76WojMl*xvNcj$4l$+a^K|bJsuo+T*q+KA8qDTUw zNtyseLP&r^5CVuLLRb_QCW00L2!uc&6b{0O02ZN87z&F4=f&rw(HbqPlr4A4;=ZJO zJE3n9Bn4xk2i;ixRy=n$^KLBdFw2s6uYSlET-yrfXL z;LoKsnOtawjmhRTa@zJ>G^5I;2vA8dWEPDRG1;6%zcIxqJ;{=cp8N+pT-z>dC^VWT zFqWiMBxxKARMHp=fWSfo2wY<@Ye)+dWS8PRK*%tbkn*{x!2$^3ZWV%{P&f+1AuxnO z&?r>F<$(rcvHu1pH3n_&3!xeu)snOc(Gwi$zvRUzj3KqG1*3^b z9p~;B<{ii>584ZM)DH0PCOY>1Qru&3u4CAzu2#i;xSAbd<~khBwX$#Sj?d@u##!XD zNR@tb=h}6&`}|35K||KN=gwBmj&-Xj(w;uMx-L?`(_*_ZqL8ard_B`EtMjXyZhc;> zZF)8CG-7Mu_=mE#!jz);z6%T5S~mVv@At*=X|?Ol?+v-67}*3~ z(I*yd*!+Hl{6bH0ad%YF(4l>;^=h9m2|jf9+!^TVd3?C0_k*j|7SExD#fc+65!1f++)=)#z9VKCyt!)o1FiS^CqLF68$Nlu@R-*F`KK+GldJrn z-6KW!*!*?i?t>eHLo#YCZFefNm-}Sy+HzW#(kJAQ% zu3Sm`#Ai<6U{7oT)is=0nUQ{99C7-Y$5MCXl>F$xVZhye@tt~FtDW0WISrk6aF6fm zc|x z#r8!%cqXJshqsp~m3WISwlJh}Z|BwFsa3Puno2spY&@ROyQ9E+zRBvfE9A|~$kXqv z6B}Ob_YXK*dq(uIvE9}XXM5*fnIhFsF*9M3Y+v$?f)8s}ZTskiwP8;Zmba>-dq;Q2 swOjp-{igh?qWDbj&x1Zp}@CP3WTf1pBX89+MzD8nvO|4+g31aR2}S diff --git a/assets/icons/Settings/Cry_dolph_55x52.png b/assets/icons/Settings/LoadingHourglass_24x24.png similarity index 70% rename from assets/icons/Settings/Cry_dolph_55x52.png rename to assets/icons/Settings/LoadingHourglass_24x24.png index 86d9db1b497cd9e49bb62987cb35075b4bc7a410..9c49dcad1cb6060c4057ad5c44d67f440c2a91b1 100644 GIT binary patch delta 422 zcmdlbcSuIDGr-TCmrII^fq{Y7)59eQNK1e)2Q!eozVhs;jf&-5j2e??a`kB#>Lwc) z7$}%q>RBcyC!3_CS{myp7#SED=^Gg98=B}Enphc_TNxQm_T?^_tiz);c^7x~WIkR6 zeq%jj1r6WC(@vVoXfTjNL2^%w0_l%nc1)ogJM_ja}WG%`J=!jVz6g zEtM21atnNYtz7bxOLJ56N<3X`m4G68DVZr&P`wto^iHoSkw4cEn{ pO$PgN{;}_HzIpl9d4&=d35G^5hRi9SpH2l?;pyt^U8eBS^7 delta 696 zcmX>kvrA5~Gr-TCmrII^fq{Y7)59eQNSgz(2?sNfl$;oFYolU07h~DvnOuEp2D-@x z1_lb|mU=0PNr}lxNjeHf21bVZ1}6H3Cb|X&R>qcACMJ{pxl1O$;ZB*nhg;L7z}MHx zzbG?3GcPg6B|o_|H#M)s)5TV)BDX*CnUiJFPK_^myVa^ud6XN>+|9>De^w8xxAobJJ#WAGf)|P3v z`Hm=XxXk?j|D~_l-ED`@Hm1CFp8ZHLP{OpE_li$WhwqX}g~vj^|IU?<-sg5(fvPv+mW0F?cs@P-Z_VT$$NG{hH`nVv zlUXCaFKOP6gLCvACkH&!sy`rF_L%=!(v$w{^*^3oN|~~gdE1i<1+O@bw2yXO77kgf z#DDEo?1Ytqd#-*JoKmzopr06iN2s{jB1 diff --git a/assets/icons/Settings/dolph_cry_49x54.png b/assets/icons/Settings/dolph_cry_49x54.png new file mode 100644 index 0000000000000000000000000000000000000000..351a849b09030fd4628bbbf582503dfb83672c59 GIT binary patch literal 973 zcmV;;12X)HP)^74TPjRsUxkj1>*;PV6t7eFDw(WuIv6mwruS4`RwBiLhH>T z{w$4%m|Dg4qvN>VK1(Upyp@rywexF>C)i^7{(8MCrLVy9Z9!%5NH{TpXdrs6H9UHa z5tNL;i)}WJG~PVpYsanUTl7ke$dvP#V@%tpH;=Rut}S|{M%YEIJL9co;dDw;^F6Cx z$C{R$Km$;4PdV&`Rz~sBj04qO|QCb*yOzu=3VLgIgDU4W=?ZS-np$Do9 zhIg?90##V7#>C3X=H0*(UG*tA$;#mqvEK`;^KJx|I80`*G(HKtxvHb0M2oTx^GQRZ zjXW8l6~GHGuao=%-Y?aPPG+T!&!Mk8ZIfTy+8p>pnN1$ zM{-HmvWmISe<{7O+N*>!( z{e2b19v&n4Ah0~v;G(bJalS_mL-X5?9s$fNN;B$hE5mjbdn8;*TyJgW1^nJ450hI^ zo!E@ATh?HQ*?1H}tuGE3Eu!jT*EN1~XI;q723Df=ndO_!B`g%U7L7XY*jel{Lzy>v z!!x=);vDO+1%M3qrZ$DTrql1s--KqG{?#)hgFUPmL_Q`q<_79m{qTrHKj{%mgL>gvQdo~O z(*W-T_vTa&OKbIFhvCq_Mf4)ulx$Add!@Pg$M`GA@@)IQc1CSRm`6`|Vg#N+4Ab5K v&c;UCdJm6;)7|(ljL0hGuP2^DR(t*eG-K{iyaDCP00000NkvXXu0mjfItI*` literal 0 HcmV?d00001 diff --git a/assets/icons/Settings/qr_benchmark_25x25.png b/assets/icons/Settings/qr_benchmark_25x25.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f9df119582ba1625b5c40d869150d72e49bf37 GIT binary patch literal 395 zcmV;60d)R}P)P000>X1^@s6#OZ}&00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPj=?b%4XtA$ZUrb~kgF?s8V;qp}O#oaJ%lv*EAWmF>5n1cxby zZ@WK8Co29`v?;A*8@aB&yK3c{ZjAdo-yow0t#gmg;IW%dkZh1tUj>h|n{q#{*|F34 z*{Pe2!F^X5K6B(u(0zn1Pbpp zTxys%i6%2vhazbe_HAZVefNc^`O$7OBU_K!K=C@rx_buffer, sizeof(uint8_t)); } else { FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); diff --git a/scripts/map_analyse_upload.py b/scripts/map_analyse_upload.py new file mode 100755 index 0000000000..38d9618796 --- /dev/null +++ b/scripts/map_analyse_upload.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import os +import requests +import argparse +import subprocess + +# usage: +# COMMIT_HASH, COMMIT_MSG, BRANCH_NAME, +# PULL_ID(optional), PULL_NAME(optional) must be set as envs +# maybe from sctipts/get_env.py +# other args must be set via command line args + + +class AnalyseRequest: + def __init__(self): + self.commit_hash = os.environ["COMMIT_HASH"] + self.commit_msg = os.environ["COMMIT_MSG"] + self.branch_name = os.environ["BRANCH_NAME"] + self.pull_id = os.getenv("PULL_ID", default=None) + self.pull_name = os.getenv("PULL_NAME", default=None) + + def get_payload(self): + return vars(self) + + +class AnalyseUploader: + def __init__(self): + self.args = self.parse_args() + + @staticmethod + def get_sections_size(elf_file) -> dict: + ret = dict() + all_sizes = subprocess.check_output( + ["arm-none-eabi-size", "-A", elf_file], shell=False + ) + all_sizes = all_sizes.splitlines() + + sections_to_keep = (".text", ".rodata", ".data", ".bss", ".free_flash") + for line in all_sizes: + line = line.decode("utf-8") + parts = line.split() + if len(parts) != 3: + continue + section, size, _ = parts + if section not in sections_to_keep: + continue + section_size_payload_name = ( + section[1:] if section.startswith(".") else section + ) + section_size_payload_name += "_size" + ret[section_size_payload_name] = size + return ret + + @staticmethod + def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--elf_file", help="Firmware ELF file", required=True) + parser.add_argument("--map_file", help="Firmware MAP file", required=True) + parser.add_argument( + "--analyser_token", help="Analyser auth token", required=True + ) + parser.add_argument( + "--analyser_url", help="Analyser analyse url", required=True + ) + args = parser.parse_args() + return args + + def upload_analyse_request(self): + payload = AnalyseRequest().get_payload() | self.get_sections_size( + self.args.elf_file + ) + headers = {"Authorization": f"Bearer {self.args.analyser_token}"} + file = {"map_file": open(self.args.map_file, "rb")} + response = requests.post( + self.args.analyser_url, data=payload, files=file, headers=headers + ) + if not response.ok: + raise Exception( + f"Failed to upload map file, code: {response.status_code}, reason: {response.text}" + ) + + +if __name__ == "__main__": + analyzer = AnalyseUploader() + analyzer.upload_analyse_request() diff --git a/scripts/map_mariadb_insert.py b/scripts/map_mariadb_insert.py deleted file mode 100755 index a4c9ed5c78..0000000000 --- a/scripts/map_mariadb_insert.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# mariadb==1.1.6 - -from datetime import datetime -import argparse -import mariadb -import sys -import os - - -def parseArgs(): - parser = argparse.ArgumentParser() - parser.add_argument("db_user", help="MariaDB user") - parser.add_argument("db_pass", help="MariaDB password") - parser.add_argument("db_host", help="MariaDB hostname") - parser.add_argument("db_port", type=int, help="MariaDB port") - parser.add_argument("db_name", help="MariaDB database") - parser.add_argument("report_file", help="Report file(.map.all)") - args = parser.parse_args() - return args - - -def mariadbConnect(args): - try: - conn = mariadb.connect( - user=args.db_user, - password=args.db_pass, - host=args.db_host, - port=args.db_port, - database=args.db_name, - ) - except mariadb.Error as e: - print(f"Error connecting to MariaDB: {e}") - sys.exit(1) - return conn - - -def parseEnv(): - outArr = [] - outArr.append(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - outArr.append(os.getenv("COMMIT_HASH", default=None)) - outArr.append(os.getenv("COMMIT_MSG", default=None)) - outArr.append(os.getenv("BRANCH_NAME", default=None)) - outArr.append(os.getenv("BSS_SIZE", default=None)) - outArr.append(os.getenv("TEXT_SIZE", default=None)) - outArr.append(os.getenv("RODATA_SIZE", default=None)) - outArr.append(os.getenv("DATA_SIZE", default=None)) - outArr.append(os.getenv("FREE_FLASH_SIZE", default=None)) - outArr.append(os.getenv("PULL_ID", default=None)) - outArr.append(os.getenv("PULL_NAME", default=None)) - return outArr - - -def createTables(cur, conn): - headerTable = "CREATE TABLE IF NOT EXISTS `header` ( \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `datetime` datetime NOT NULL, \ - `commit` varchar(40) NOT NULL, \ - `commit_msg` text NOT NULL, \ - `branch_name` text NOT NULL, \ - `bss_size` int(10) unsigned NOT NULL, \ - `text_size` int(10) unsigned NOT NULL, \ - `rodata_size` int(10) unsigned NOT NULL, \ - `data_size` int(10) unsigned NOT NULL, \ - `free_flash_size` int(10) unsigned NOT NULL, \ - `pullrequest_id` int(10) unsigned DEFAULT NULL, \ - `pullrequest_name` text DEFAULT NULL, \ - PRIMARY KEY (`id`), \ - KEY `header_id_index` (`id`) )" - dataTable = "CREATE TABLE IF NOT EXISTS `data` ( \ - `header_id` int(10) unsigned NOT NULL, \ - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, \ - `section` text NOT NULL, \ - `address` text NOT NULL, \ - `size` int(10) unsigned NOT NULL, \ - `name` text NOT NULL, \ - `lib` text NOT NULL, \ - `obj_name` text NOT NULL, \ - PRIMARY KEY (`id`), \ - KEY `data_id_index` (`id`), \ - KEY `data_header_id_index` (`header_id`), \ - CONSTRAINT `data_header_id_foreign` FOREIGN KEY (`header_id`) REFERENCES `header` (`id`) )" - cur.execute(headerTable) - cur.execute(dataTable) - conn.commit() - - -def insertHeader(data, cur, conn): - query = "INSERT INTO `header` ( \ - datetime, commit, commit_msg, branch_name, bss_size, text_size, \ - rodata_size, data_size, free_flash_size, pullrequest_id, pullrequest_name) \ - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cur.execute(query, data) - conn.commit() - return cur.lastrowid - - -def parseFile(fileObj, headerID): - arr = [] - fileLines = fileObj.readlines() - for line in fileLines: - lineArr = [] - tempLineArr = line.split("\t") - lineArr.append(headerID) - lineArr.append(tempLineArr[0]) # section - lineArr.append(int(tempLineArr[2], 16)) # address hex - lineArr.append(int(tempLineArr[3])) # size - lineArr.append(tempLineArr[4]) # name - lineArr.append(tempLineArr[5]) # lib - lineArr.append(tempLineArr[6]) # obj_name - arr.append(tuple(lineArr)) - return arr - - -def insertData(data, cur, conn): - query = "INSERT INTO `data` ( \ - header_id, section, address, size, \ - name, lib, obj_name) \ - VALUES (?, ?, ?, ?, ? ,?, ?)" - cur.executemany(query, data) - conn.commit() - - -def main(): - args = parseArgs() - dbConn = mariadbConnect(args) - reportFile = open(args.report_file) - dbCurs = dbConn.cursor() - createTables(dbCurs, dbConn) - headerID = insertHeader(parseEnv(), dbCurs, dbConn) - insertData(parseFile(reportFile, headerID), dbCurs, dbConn) - reportFile.close() - dbCurs.close() - - -if __name__ == "__main__": - main() diff --git a/scripts/map_parser.py b/scripts/map_parser.py deleted file mode 100755 index 1efc4fe82f..0000000000 --- a/scripts/map_parser.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python3 - -# Requiremets: -# cxxfilt==0.3.0 - -# Most part of this code written by Lars-Dominik Braun https://github.com/PromyLOPh/linkermapviz -# and distributes under MIT licence - -# Copyright (c) 2017 Lars-Dominik Braun -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -import sys -import re -import os -from typing import TextIO -from cxxfilt import demangle - - -class Objectfile: - def __init__(self, section: str, offset: int, size: int, comment: str): - self.section = section.strip() - self.offset = offset - self.size = size - self.path = (None, None) - self.basepath = None - - if comment: - self.path = re.match(r"^(.+?)(?:\(([^\)]+)\))?$", comment).groups() - self.basepath = os.path.basename(self.path[0]) - - self.children = [] - - def __repr__(self) -> str: - return f"" - - -def update_children_size(children: list[list], subsection_size: int) -> list: - # set subsection size to an only child - if len(children) == 1: - children[0][1] = subsection_size - return children - - rest_size = subsection_size - - for index in range(1, len(children)): - if rest_size > 0: - # current size = current address - previous child address - child_size = children[index][0] - children[index - 1][0] - rest_size -= child_size - children[index - 1][1] = child_size - - # if there is rest size, set it to the last child element - if rest_size > 0: - children[-1][1] = rest_size - - return children - - -def parse_sections(file_name: str) -> list: - """ - Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because - some messages are localized. - """ - - sections = [] - with open(file_name, "r") as file: - # skip until memory map is found - found = False - - while True: - line = file.readline() - if not line: - break - if line.strip() == "Memory Configuration": - found = True - break - - if not found: - raise Exception(f"Memory configuration is not found in the{input_file}") - - # long section names result in a linebreak afterwards - sectionre = re.compile( - "(?P
.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+", - re.I, - ) - subsectionre = re.compile( - "[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+", re.I - ) - s = file.read() - pos = 0 - - while True: - m = sectionre.match(s, pos) - if not m: - # skip that line - try: - nextpos = s.index("\n", pos) + 1 - pos = nextpos - continue - except ValueError: - break - - pos = m.end() - section = m.group("section") - v = m.group("offset") - offset = int(v, 16) if v is not None else None - v = m.group("size") - size = int(v, 16) if v is not None else None - comment = m.group("comment") - - if section != "*default*" and size > 0: - of = Objectfile(section, offset, size, comment) - - if section.startswith(" "): - children = [] - sections[-1].children.append(of) - - while True: - m = subsectionre.match(s, pos) - if not m: - break - pos = m.end() - offset, function = m.groups() - offset = int(offset, 16) - if sections and sections[-1].children: - children.append([offset, 0, function]) - - if children: - children = update_children_size( - children=children, subsection_size=of.size - ) - - sections[-1].children[-1].children.extend(children) - - else: - sections.append(of) - - return sections - - -def get_subsection_name(section_name: str, subsection: Objectfile) -> str: - subsection_split_names = subsection.section.split(".") - if subsection.section.startswith("."): - subsection_split_names = subsection_split_names[1:] - - return ( - f".{subsection_split_names[1]}" - if len(subsection_split_names) > 2 - else section_name - ) - - -def write_subsection( - section_name: str, - subsection_name: str, - address: str, - size: int, - demangled_name: str, - module_name: str, - file_name: str, - mangled_name: str, - write_file_object: TextIO, -) -> None: - write_file_object.write( - f"{section_name}\t" - f"{subsection_name}\t" - f"{address}\t" - f"{size}\t" - f"{demangled_name}\t" - f"{module_name}\t" - f"{file_name}\t" - f"{mangled_name}\n" - ) - - -def save_subsection( - section_name: str, subsection: Objectfile, write_file_object: TextIO -) -> None: - subsection_name = get_subsection_name(section_name, subsection) - module_name = subsection.path[0] - file_name = subsection.path[1] - - if not file_name: - file_name, module_name = module_name, "" - - if not subsection.children: - address = f"{subsection.offset:x}" - size = subsection.size - mangled_name = ( - "" - if subsection.section == section_name - else subsection.section.split(".")[-1] - ) - demangled_name = demangle(mangled_name) if mangled_name else mangled_name - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - return - - for subsection_child in subsection.children: - address = f"{subsection_child[0]:x}" - size = subsection_child[1] - mangled_name = subsection_child[2] - demangled_name = demangle(mangled_name) - - write_subsection( - section_name=section_name, - subsection_name=subsection_name, - address=address, - size=size, - demangled_name=demangled_name, - module_name=module_name, - file_name=file_name, - mangled_name=mangled_name, - write_file_object=write_file_object, - ) - - -def save_section(section: Objectfile, write_file_object: TextIO) -> None: - section_name = section.section - for subsection in section.children: - save_subsection( - section_name=section_name, - subsection=subsection, - write_file_object=write_file_object, - ) - - -def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None: - with open(output_file_name, "w") as write_file_object: - for section in parsed_data: - if section.children: - save_section(section=section, write_file_object=write_file_object) - - -if __name__ == "__main__": - if len(sys.argv) < 3: - raise Exception(f"Usage: {sys.argv[0]} ") - - input_file = sys.argv[1] - output_file = sys.argv[2] - - parsed_sections = parse_sections(input_file) - - if parsed_sections is None: - raise Exception(f"Memory configuration is not {input_file}") - - save_parsed_data(parsed_sections, output_file) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 3cee044aa7..492539d465 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,61.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2554,7 +2554,9 @@ Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_alloc,Submenu*, +Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6cfc939b8c..247c2ee2d5 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_speaker_init(); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2181661002..08f700bd2a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.2,, +Version,+,61.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -3426,7 +3426,9 @@ Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" Function,+,submenu_add_lockable_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*, _Bool, const char*" Function,+,submenu_alloc,Submenu*, +Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* +Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 5a95cff263..bdef259e33 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -40,6 +40,8 @@ typedef struct { FuriThread* thread; FuriMessageQueue* command_queue; bool enable_adv; + bool is_secure; + uint8_t negotiation_round; } Gap; typedef enum { @@ -87,17 +89,46 @@ static void gap_verify_connection_parameters(Gap* gap) { // Send connection parameters request update if necessary GapConnectionParamsRequest* params = &gap->config->conn_param; - if(params->conn_int_min > gap->connection_params.conn_interval || - params->conn_int_max < gap->connection_params.conn_interval) { - FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); + + // Desired max connection interval depends on how many negotiation rounds we had in the past + // In the first negotiation round we want connection interval to be minimum + // If platform disagree then we request wider range + uint16_t connection_interval_max = gap->negotiation_round ? params->conn_int_max : + params->conn_int_min; + + // We do care about lower connection interval bound a lot: if it's lower than 30ms 2nd core will not allow us to use flash controller + bool negotiation_failed = params->conn_int_min > gap->connection_params.conn_interval; + + // We don't care about upper bound till connection become secure + if(gap->is_secure) { + negotiation_failed |= connection_interval_max < gap->connection_params.conn_interval; + } + + if(negotiation_failed) { + FURI_LOG_W( + TAG, + "Connection interval doesn't suite us. Trying to negotiate, round %u", + gap->negotiation_round + 1); if(aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, params->conn_int_min, - params->conn_int_max, + connection_interval_max, gap->connection_params.slave_latency, gap->connection_params.supervisor_timeout)) { FURI_LOG_E(TAG, "Failed to request connection parameters update"); + // The other side is not in the mood + // But we are open to try it again + gap->negotiation_round = 0; + } else { + gap->negotiation_round++; } + } else { + FURI_LOG_I( + TAG, + "Connection interval suits us. Spent %u rounds to negotiate", + gap->negotiation_round); + // Looks like the other side is open to negotiation + gap->negotiation_round = 0; } } @@ -112,9 +143,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); - } + furi_check(gap); + furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { hci_disconnection_complete_event_rp0* disconnection_complete_event = @@ -125,6 +156,8 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } + gap->is_secure = false; + gap->negotiation_round = 0; // Enterprise sleep furi_delay_us(666 + 666); if(gap->enable_adv) { @@ -232,6 +265,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE: FURI_LOG_D(TAG, "Slave security initiated"); + gap->is_secure = true; break; case ACI_GAP_BOND_LOST_VSEVT_CODE: @@ -293,9 +327,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { default: break; } - if(gap) { - furi_mutex_release(gap->state_mutex); - } + + furi_mutex_release(gap->state_mutex); + return BleEventFlowEnable; } @@ -539,6 +573,10 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); + // Set initial state + gap->is_secure = false; + gap->negotiation_round = 0; + uint8_t adv_service_uid[2]; gap->service.adv_svc_uuid_len = 1; adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 165b813307..118a76e8c3 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -47,7 +47,7 @@ static GapConfig serial_template_config = { .pairing_method = GapPairingPinCodeShow, .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }}; diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 3686658f7e..7e713a08d1 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_ibutton_init(); From 5efbd36bb8aa1ee3e76f0119701aef5f3d6a4beb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 15 May 2024 18:23:19 +0300 Subject: [PATCH 14/48] sync anims --- .ci_files/anims_ofw.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt index c0e1741a0c..42e18ad3f2 100644 --- a/.ci_files/anims_ofw.txt +++ b/.ci_files/anims_ofw.txt @@ -189,3 +189,10 @@ Max butthurt: 12 Min level: 3 Max level: 3 Weight: 5 + +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 From 1d17206e2358583a7feac2e142218f5b4ca38148 Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Thu, 16 May 2024 01:47:21 +1000 Subject: [PATCH 15/48] TLSF memory allocator. Less free flash, moar free ram. (#3572) * add tlsf as submodule * libs: tlsf * Furi: tlsf as allocator * Furi: heap walker * shmal fixshesh * f18: tlsf * PVS: ignore tlsf * I like to moving * merge upcoming changes * memmgr: alloc aligned, realloc * Furi: distinct name for auxiliary memory pool * Furi: put idle and timer thread to mem2 * Furi: fix smal things in allocator * Furi: remove aligned_free. Use free instead. * aligned_malloc -> aligned_alloc * aligned_alloc, parameters order * aligned_alloc: check that alignment is correct * unit test: malloc * unit tests: realloc and test with memory fragmentation * unit tests: aligned_alloc * update api * updater: properly read large update file Co-authored-by: Aleksandr Kutuzov --- .gitmodules | 3 + .pvsoptions | 2 +- .../debug/unit_tests/furi/furi_memmgr_test.c | 262 +++++- .../debug/unit_tests/furi/furi_test.c | 2 + applications/services/cli/cli_commands.c | 48 +- furi/core/memmgr.c | 39 +- furi/core/memmgr.h | 25 +- furi/core/memmgr_heap.c | 746 +++++------------- furi/core/memmgr_heap.h | 12 +- furi/core/thread.c | 4 +- furi/flipper.c | 13 +- lib/SConscript | 1 + lib/flipper_application/elf/elf_file.c | 8 +- lib/tlsf | 1 + lib/tlsf.scons | 21 + targets/f18/api_symbols.csv | 12 +- targets/f18/target.json | 3 +- targets/f7/api_symbols.csv | 12 +- targets/f7/fatfs/sector_cache.c | 2 +- targets/f7/src/update.c | 14 +- targets/f7/target.json | 3 +- 21 files changed, 608 insertions(+), 625 deletions(-) create mode 160000 lib/tlsf create mode 100644 lib/tlsf.scons diff --git a/.gitmodules b/.gitmodules index c4c68a6a77..a8d12803cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,3 +41,6 @@ [submodule "documentation/doxygen/doxygen-awesome-css"] path = documentation/doxygen/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git +[submodule "lib/tlsf"] + path = lib/tlsf + url = https://github.com/espressif/tlsf diff --git a/.pvsoptions b/.pvsoptions index 8606eef154..590a34de8b 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 01e2c17f66..399e2d4188 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,8 +1,5 @@ #include "../minunit.h" -#include -#include -#include -#include +#include void test_furi_memmgr(void) { void* ptr; @@ -37,3 +34,260 @@ void test_furi_memmgr(void) { } free(ptr); } + +static void test_memmgr_malloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = malloc(allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "malloc failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after malloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (malloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_realloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = realloc(ptr, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "realloc(NULL) failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after realloc(NULL)"; + break; + } + } + + memset(ptr, 0x55, allocation_size); + + ptr = realloc(ptr, allocation_size * 2); + + // test that we can reallocate memory + if(ptr == NULL) { + error_message = "realloc failed"; + } + + // test that memory content is preserved + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0x55) { + error_message = "memory is not reallocated after realloc"; + break; + } + } + + // test that remaining memory is zero-initialized + size_t non_zero_count = 0; + for(size_t i = allocation_size; i < allocation_size * 2; i++) { + if(ptr[i] != 0) { + non_zero_count += 1; + } + } + + // check that at most of memory is zero-initialized + // we know that allocator not always can restore content size from a pointer + // so we check against small threshold + if(non_zero_count > 4) { + error_message = "seems that memory is not zero-initialized after realloc"; + } + + uint8_t* null_ptr = realloc(ptr, 0); + + // test that we can free memory + if(null_ptr != NULL) { + error_message = "realloc(0) failed"; + } + + // test that memory is zero-initialized after realloc(0) + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after realloc(0)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = aligned_alloc(alignment, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "aligned_alloc failed"; + } + + // test that memory is aligned + if(((uintptr_t)ptr % alignment) != 0) { + error_message = "memory is not aligned after aligned_alloc"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after aligned_alloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +void test_furi_memmgr_advanced(void) { + const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; + const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); + const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; + const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); + + // do test without memory fragmentation + { + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + } + + // do test with memory fragmentation + { + void* blocks[sizes_count]; + void* guards[sizes_count - 1]; + + // setup guards + for(size_t i = 0; i < sizes_count; i++) { + blocks[i] = malloc(sizes[i]); + if(i < sizes_count - 1) { + guards[i] = malloc(sizes[i]); + } + } + + for(size_t i = 0; i < sizes_count; i++) { + free(blocks[i]); + } + + // do test + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + + // cleanup guards + for(size_t i = 0; i < sizes_count - 1; i++) { + free(guards[i]); + } + } +} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e287f9927f..e0b5916d55 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,6 +9,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); +void test_furi_memmgr_advanced(void); static int foo = 0; @@ -37,6 +38,7 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); + test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 43f1c01c4d..56d05785fa 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -425,8 +425,34 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Pool free: %zu\r\n", memmgr_pool_get_free()); - printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); + printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); + printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); +} + +typedef struct { + void* addr; + size_t size; +} FreeBlockInfo; + +#define FREE_BLOCK_INFO_MAX 128 + +typedef struct { + FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; + size_t free_blocks_count; +} FreeBlockContext; + +static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { + FreeBlockContext* free_blocks = (FreeBlockContext*)context; + if(!used) { + if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { + free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; + free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; + free_blocks->free_blocks_count++; + } else { + return false; + } + } + return true; } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { @@ -434,7 +460,23 @@ void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - memmgr_heap_printf_free_blocks(); + FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); + free_blocks->free_blocks_count = 0; + + memmgr_heap_walk_blocks(free_block_walker, free_blocks); + + for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { + printf( + "A %p S %zu\r\n", + (void*)free_blocks->free_blocks[i].addr, + free_blocks->free_blocks[i].size); + } + + if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { + printf("... and more\r\n"); + } + + free(free_blocks); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768adc05df..768d448904 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,6 +4,8 @@ #include extern void* pvPortMalloc(size_t xSize); +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); +extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -18,18 +20,7 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - if(size == 0) { - vPortFree(ptr); - return NULL; - } - - void* p = pvPortMalloc(size); - if(ptr != NULL) { - memcpy(p, ptr, size); - vPortFree(ptr); - } - - return p; + return pvPortRealloc(ptr, size); } void* calloc(size_t count, size_t size) { @@ -47,6 +38,10 @@ char* strdup(const char* s) { return y; } +void* aligned_alloc(size_t alignment, size_t size) { + return pvPortAllocAligned(size, alignment); +} + size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -79,33 +74,17 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_alloc_from_pool(size_t size) { +void* memmgr_aux_pool_alloc(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_pool_get_free(void) { +size_t memmgr_aux_pool_get_free(void) { return furi_hal_memory_get_free(); } size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); -} - -void* aligned_malloc(size_t size, size_t alignment) { - void* p1; // original block - void** p2; // aligned block - int offset = alignment - 1 + sizeof(void*); - if((p1 = (void*)malloc(size + offset)) == NULL) { - return NULL; - } - p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); - p2[-1] = p1; - return p2; -} - -void aligned_free(void* p) { - free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index bc0c35faa7..796a1f5378 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,37 +36,22 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * An aligned version of malloc, used when you need to get the aligned space on the heap - * Freeing the received address is performed ONLY through the aligned_free function - * @param size - * @param alignment - * @return void* - */ -void* aligned_malloc(size_t size, size_t alignment); - -/** - * Freed space obtained through the aligned_malloc function - * @param p pointer to result of aligned_malloc - */ -void aligned_free(void* p); - -/** - * @brief Allocate memory from separate memory pool. That memory can't be freed. + * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_alloc_from_pool(size_t size); +void* memmgr_aux_pool_alloc(size_t size); /** - * @brief Get free memory pool size + * @brief Get the auxiliary pool free memory size * * @return size_t */ -size_t memmgr_pool_get_free(void); +size_t memmgr_aux_pool_get_free(void); /** - * @brief Get max free block size from memory pool + * @brief Get max free block size from the auxiliary memory pool * * @return size_t */ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3f62b518c2..3dfc7f5acb 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,124 +1,18 @@ -/* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -/* - * A sample implementation of pvPortMalloc() and vPortFree() that combines - * (coalescences) adjacent memory blocks as they are freed, and in so doing - * limits memory fragmentation. - * - * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. - */ - -#include "memmgr_heap.h" -#include "check.h" -#include -#include -#include -#include -#include - -/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ -#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE - +#include +#include +#include #include #include +#include -#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE - -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT -#endif - -#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) -#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 -#endif - -/* Block sizes must not get too small. */ -#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) - -/* Assumes 8bit bytes! */ -#define heapBITS_PER_BYTE ((size_t)8) - -/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; -uint8_t* ucHeap = (uint8_t*)&__heap_start__; - -/* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ -typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ -} BlockLink_t; - -/*-----------------------------------------------------------*/ - -/* - * Inserts a block of memory that is being freed into the correct position in - * the list of free memory blocks. The block being freed will be merged with - * the block in front it and/or the block behind it if the memory blocks are - * adjacent to each other. - */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); - -/* - * Called automatically to setup the required heap structures the first time - * pvPortMalloc() is called. - */ -static void prvHeapInit(void); - -/*-----------------------------------------------------------*/ - -/* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ -static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & - ~((size_t)portBYTE_ALIGNMENT_MASK); - -/* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; - -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; - -/* Furi heap extension */ -#include -/* Allocation tracking types */ +static tlsf_t tlsf = NULL; +static size_t heap_used = 0; +static size_t heap_max_used = 0; + +// Allocation tracking types DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 DICT_DEF2( //-V1048 @@ -128,17 +22,35 @@ DICT_DEF2( //-V1048 MemmgrHeapAllocDict_t, DICT_OPLIST(MemmgrHeapAllocDict)) -/* Thread allocation tracing storage */ +// Thread allocation tracing storage static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; static volatile uint32_t memmgr_heap_thread_trace_depth = 0; -/* Initialize tracing storage on start */ -void memmgr_heap_init(void) { +static inline void memmgr_lock(void) { + vTaskSuspendAll(); +} + +static inline void memmgr_unlock(void) { + xTaskResumeAll(); +} + +static inline size_t memmgr_get_heap_size(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; +} + +// Initialize tracing storage +static void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } +__attribute__((constructor)) static void memmgr_init(void) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); @@ -148,517 +60,289 @@ void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { MemmgrHeapAllocDict_clear(alloc_dict); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } -size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - vTaskSuspendAll(); - { +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - uint8_t* puc = (uint8_t*)data->key; - puc -= xHeapStructSize; - BlockLink_t* pxLink = (void*)puc; - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && - pxLink->pxNextFreeBlock == NULL) { - leftovers += data->value; - } - } - } + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); } memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); - return leftovers; } -#undef traceMALLOC -static inline void traceMALLOC(void* pointer, size_t size) { +static inline void memmgr_heap_trace_free(void* pointer) { FuriThreadId thread_id = furi_thread_get_current_id(); if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } } -#undef traceFREE -static inline void traceFREE(void* pointer, size_t size) { - UNUSED(size); - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + memmgr_lock(); + { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + block_header_t* block = block_from_ptr((uint8_t*)data->key); + if(!block_is_free(block)) { + leftovers += data->value; + } + } + } } memmgr_heap_thread_trace_depth--; } + memmgr_unlock(); + return leftovers; } -size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); +static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { + UNUSED(ptr); - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; + bool free = !used; + size_t* max_free_block_size = (size_t*)user; + if(free && size > *max_free_block_size) { + *max_free_block_size = size; } - xTaskResumeAll(); - return max_free_size; + return true; } -void memmgr_heap_printf_free_blocks(void) { - BlockLink_t* pxBlock; - //TODO enable when we can do printf with a locked scheduler - //vTaskSuspendAll(); +size_t memmgr_heap_get_max_free_block(void) { + size_t max_free_block_size = 0; - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; - } + memmgr_lock(); - //xTaskResumeAll(); -} + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. + memmgr_unlock(); - return str; + return max_free_block_size; } -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} +typedef struct { + BlockWalker walker; + void* context; +} BlockWalkerWrapper; -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); +static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { + BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; + return wrapper->walker(ptr, size, used, wrapper->context); } -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } +void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { + memmgr_lock(); - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif -/*-----------------------------------------------------------*/ + BlockWalkerWrapper wrapper = {walker, context}; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); -void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; - void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + memmgr_unlock(); +} +void* pvPortMalloc(size_t xSize) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif - - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif - - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + memmgr_lock(); + + // allocate block + void* data = tlsf_malloc(tlsf, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc(0)"); + } else { + furi_crash("out of memory"); } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); } - vTaskSuspendAll(); - { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } - if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { - /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ - pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { - pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; - } + // trace allocation + memmgr_heap_trace_malloc(data, xSize); - /* If the end marker was reached then a block of adequate size - was not found. */ - if(pxBlock != pxEnd) { - /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); - - /* This block is being returned for use so must be taken out - of the list of free blocks. */ - pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; - - /* If the block is larger than required it can be split into - two. */ - if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { - /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ - pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); - configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); - - /* Calculate the sizes of two blocks split from the - single block. */ - pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; - pxBlock->xBlockSize = xWantedSize; - - /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); - } else { - mtCOVERAGE_TEST_MARKER(); - } + memmgr_unlock(); - xFreeBytesRemaining -= pxBlock->xBlockSize; + // clear block content + memset(data, 0, xSize); - if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { - xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; - } else { - mtCOVERAGE_TEST_MARKER(); - } + return data; +} - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; +void vPortFree(void* pv) { + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // ignore NULL pointer + if(pv != NULL) { + memmgr_lock(); - traceMALLOC(pvReturn, xWantedSize); - } - (void)xTaskResumeAll(); + // get block size + size_t block_size = tlsf_block_size(pv); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif + // clear block content + memset(pv, 0, block_size); -#if(configUSE_MALLOC_FAILED_HOOK == 1) - { - if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); - vApplicationMallocFailedHook(); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } -#endif + // update heap usage + heap_used -= block_size; + heap_used -= tlsf_alloc_overhead(); - configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); + // free + tlsf_free(tlsf, pv); - furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); - return pvReturn; -} -/*-----------------------------------------------------------*/ + // trace free + memmgr_heap_trace_free(pv); -void vPortFree(void* pv) { - uint8_t* puc = (uint8_t*)pv; - BlockLink_t* pxLink; + memmgr_unlock(); + } +} +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - if(pv != NULL) { - /* The memory being freed will have an BlockLink_t structure immediately - before it. */ - puc -= xHeapStructSize; - - /* This casting is to keep the compiler from issuing warnings. */ - pxLink = (void*)puc; - - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { - /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); -#endif - - vTaskSuspendAll(); - { - furi_assert((size_t)pv >= SRAM_BASE); - furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); - furi_assert(pxLink->xBlockSize >= xHeapStructSize); - furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); - - /* Add this block to the list of free blocks. */ - xFreeBytesRemaining += pxLink->xBlockSize; - traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); - } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); - } + // alignment must be power of 2 + if((xAlignment & (xAlignment - 1)) != 0) { + furi_crash("invalid alignment"); + } + + memmgr_lock(); + + // allocate block + void* data = tlsf_memalign(tlsf, xAlignment, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc_aligned(0)"); } else { - mtCOVERAGE_TEST_MARKER(); + furi_crash("out of memory"); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } -} -/*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } -size_t xPortGetFreeHeapSize(void) { - return xFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + // trace allocation + memmgr_heap_trace_malloc(data, xSize); -size_t xPortGetMinimumEverFreeHeapSize(void) { - return xMinimumEverFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + memmgr_unlock(); + + // clear block content + memset(data, 0, xSize); -void vPortInitialiseBlocks(void) { - /* This just exists to keep the linker quiet. */ + return data; } -/*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +extern void* pvPortRealloc(void* pv, size_t xSize) { + // realloc(ptr, 0) is equivalent to free(ptr) + if(xSize == 0) { + vPortFree(pv); + return NULL; + } + + // realloc(NULL, size) is equivalent to malloc(size) + if(pv == NULL) { + return pvPortMalloc(xSize); + } - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + /* realloc things */ - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); } - pucAlignedHeap = (uint8_t*)uxAddress; - - /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; - xStart.xBlockSize = (size_t)0; - - /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; - pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; - - /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; - - /* Only one block exists - and it covers the entire usable heap space. */ - xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); -} -/*-----------------------------------------------------------*/ + memmgr_lock(); -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { - BlockLink_t* pxIterator; - uint8_t* puc; + // trace old block as free + size_t old_size = tlsf_block_size(pv); - /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { - /* Nothing to do here, just iterate to the right position. */ - } + // trace free + memmgr_heap_trace_free(pv); - /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ - puc = (uint8_t*)pxIterator; - if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { - pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; - pxBlockToInsert = pxIterator; - } else { - mtCOVERAGE_TEST_MARKER(); + // reallocate block + void* data = tlsf_realloc(tlsf, pv, xSize); + if(data == NULL) { + furi_crash("out of memory"); } - /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ - puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { - /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; - } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; - } - } else { - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; + // update heap usage + heap_used -= old_size; + heap_used += tlsf_block_size(data); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ - if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; - } else { - mtCOVERAGE_TEST_MARKER(); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + memmgr_unlock(); + + // clear remain block content, if the new size is bigger + // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize + if(xSize > old_size) { + memset((uint8_t*)data + old_size, 0, xSize - old_size); } + + return data; } + +size_t xPortGetFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); +} + +size_t xPortGetTotalHeapSize(void) { + return memmgr_get_heap_size(); +} + +size_t xPortGetMinimumEverFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); +} \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 7d889f1520..2f61deb642 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,9 +40,17 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -/** Print the address and size of all free blocks to stdout +typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); + +/** + * @brief Walk through all heap blocks + * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. + * Also, printf and furi_log contains malloc calls, so do not use them. + * + * @param walker + * @param context */ -void memmgr_heap_printf_free_blocks(void); +void memmgr_heap_walk_blocks(BlockWalker walker, void* context); #ifdef __cplusplus } diff --git a/furi/core/thread.c b/furi/core/thread.c index f9f73b4f75..c9bf79d32a 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -276,8 +276,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_alloc_from_pool(sizeof(StackType_t) * stack), - memmgr_alloc_from_pool(sizeof(StaticTask_t))); + memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), + memmgr_aux_pool_alloc(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/furi/flipper.c b/furi/flipper.c index c7ba3b4fb1..6c7b9831a4 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -51,12 +51,17 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; + void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *tcb_ptr = &idle_task_tcb; + *stack_ptr = idle_task_stack; *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -64,7 +69,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); + *tcb_ptr = &timer_task_tcb; + *stack_ptr = timer_task_stack; *stack_size = configTIMER_TASK_STACK_DEPTH; } \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index 8125739325..29c48de6d9 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -13,6 +13,7 @@ env.Append( libs = env.BuildModules( [ + "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 398f25209a..3e10ae3fe3 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -466,7 +466,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); + section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { @@ -718,7 +718,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - aligned_free(s->fast_rel->data); + free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -785,10 +785,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - aligned_free(itref->value.data); + free(itref->value.data); } if(itref->value.fast_rel) { - aligned_free(itref->value.fast_rel->data); + free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/lib/tlsf b/lib/tlsf new file mode 160000 index 0000000000..8fc595fe22 --- /dev/null +++ b/lib/tlsf @@ -0,0 +1 @@ +Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 diff --git a/lib/tlsf.scons b/lib/tlsf.scons new file mode 100644 index 0000000000..0a8419dbdc --- /dev/null +++ b/lib/tlsf.scons @@ -0,0 +1,21 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/tlsf", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="tlsf") +libenv.ApplyLibFlags() + +libenv.Append( + CPPDEFINES=[], +) + +sources = [File("tlsf/tlsf.c")] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 492539d465..ef2d2fcb6a 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,62.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -515,9 +515,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1984,7 +1982,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1992,8 +1991,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd4..a61c1373e1 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,6 +13,7 @@ "print", "flipper18", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -68,4 +69,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e209023b5c..1f959f0c70 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,62.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -586,9 +586,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -2394,7 +2392,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2402,8 +2401,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index 319dd21732..df86cb7f15 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_alloc_from_pool(sizeof(SectorCache)); + cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); } if(cache != NULL) { diff --git a/targets/f7/src/update.c b/targets/f7/src/update.c index e6cb4aabee..261adb5caf 100644 --- a/targets/f7/src/update.c +++ b/targets/f7/src/update.c @@ -78,21 +78,21 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest furi_string_free(loader_img_path); void* img = malloc(stat.fsize); - uint32_t bytes_read = 0; + uint32_t read_total = 0; + uint16_t read_current = 0; const uint16_t MAX_READ = 0xFFFF; uint32_t crc = 0; do { - uint16_t size_read = 0; - if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 + if(f_read(&file, img + read_total, MAX_READ, &read_current) != FR_OK) { //-V769 break; } - crc = crc32_calc_buffer(crc, img + bytes_read, size_read); - bytes_read += size_read; - } while(bytes_read == MAX_READ); + crc = crc32_calc_buffer(crc, img + read_total, read_current); + read_total += read_current; + } while(read_current == MAX_READ); do { - if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { + if((read_total != stat.fsize) || (crc != manifest->staged_loader_crc)) { break; } diff --git a/targets/f7/target.json b/targets/f7/target.json index 25872198bf..caa3f58eec 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,6 +22,7 @@ "print", "flipper7", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -55,4 +56,4 @@ "bit_lib", "datetime" ] -} +} \ No newline at end of file From fb8f59fa1d8a4f0232a68cdf17cd53882c00b8d0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 15 May 2024 19:04:37 +0300 Subject: [PATCH 16/48] fix merge issues --- applications/services/gui/modules/submenu.c | 20 -------------------- applications/services/gui/modules/submenu.h | 8 -------- 2 files changed, 28 deletions(-) diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 941784b006..74f93320f9 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -338,26 +338,6 @@ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* lab true); } -void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) { - furi_check(submenu); - furi_check(label); - - with_view_model( - submenu->view, - SubmenuModel * model, - { - SubmenuItemArray_it_t it; - for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); - SubmenuItemArray_next(it)) { - if(index == SubmenuItemArray_cref(it)->index) { - furi_string_set_str(SubmenuItemArray_cref(it)->label, label); - break; - } - } - }, - true); -} - void submenu_reset(Submenu* submenu) { furi_check(submenu); view_set_orientation(submenu->view, ViewOrientationHorizontal); diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index c6aa49331d..d77f570c26 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -81,14 +81,6 @@ void submenu_add_lockable_item( */ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); -/** Change label of an existing item - * - * @param submenu Submenu instance - * @param index The index of the item - * @param label The new label - */ -void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); - /** Remove all items from submenu * * @param submenu Submenu instance From 3005f6cd9a333cf6d16f2df873ad0f86f174b45f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 15 May 2024 19:08:10 +0300 Subject: [PATCH 17/48] Hid app improvements - Move new mouse jiggler into mouse jiggler stealth - Set stealth jiggler max time default value to 4 min and min value to 1 min - Various UI fixes - Merge changes from OFW commit 11d7f5385432912bac77c485c7a028761ff1353c ([FL-3770, FL-3680] HID App improvements and little extra (3518)) --- applications/system/hid_app/hid.c | 91 +++---- applications/system/hid_app/hid.h | 12 +- .../system/hid_app/scenes/hid_scene_config.h | 3 +- .../hid_app/scenes/hid_scene_exit_confirm.c | 45 ---- .../system/hid_app/scenes/hid_scene_main.c | 3 +- applications/system/hid_app/views.h | 1 + .../system/hid_app/views/hid_keyboard.c | 15 +- .../system/hid_app/views/hid_keynote.c | 46 ++-- applications/system/hid_app/views/hid_media.c | 42 ++-- applications/system/hid_app/views/hid_mouse.c | 18 +- .../system/hid_app/views/hid_mouse_clicker.c | 22 +- .../system/hid_app/views/hid_mouse_jiggler.c | 143 ++++------- .../system/hid_app/views/hid_mouse_jiggler.h | 2 + .../hid_app/views/hid_mouse_jiggler_stealth.c | 224 ++++++++++++++++++ .../hid_app/views/hid_mouse_jiggler_stealth.h | 16 ++ applications/system/hid_app/views/hid_movie.c | 16 +- .../system/hid_app/views/hid_music_macos.c | 19 +- .../system/hid_app/views/hid_numpad.c | 35 ++- applications/system/hid_app/views/hid_ptt.c | 14 +- .../system/hid_app/views/hid_tiktok.c | 16 +- 20 files changed, 429 insertions(+), 354 deletions(-) delete mode 100644 applications/system/hid_app/scenes/hid_scene_exit_confirm.c create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler_stealth.c create mode 100644 applications/system/hid_app/views/hid_mouse_jiggler_stealth.h diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 1b0667bbff..a4502c0169 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -20,6 +20,7 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexMouse, HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, + HidSubmenuIndexMouseJigglerStealth, HidSubmenuIndexPushToTalk, HidSubmenuIndexRemovePairing, }; @@ -34,7 +35,6 @@ bool hid_back_event_callback(void* context) { furi_assert(context); Hid* app = context; FURI_LOG_D("HID", "Back event"); - app->view_id = HidViewSubmenu; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); return true; } @@ -57,42 +57,32 @@ 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 == HidSubmenuIndexMusicMacOs) { - app->view_id = HidViewMusicMacOs; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMusicMacOs); } 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 == HidSubmenuIndexTikTok) { - app->view_id = BtHidViewTikTok; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); } 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 == HidSubmenuIndexMouseJigglerStealth) { + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJigglerStealth); } else if(index == HidSubmenuIndexPushToTalk) { - app->view_id = HidViewPushToTalkMenu; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu); } else if(index == HidSubmenuIndexRemovePairing) { scene_manager_next_scene(app->scene_manager, HidSceneUnpair); @@ -119,6 +109,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con 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_mouse_jiggler_stealth_set_connected_status(hid->hid_mouse_jiggler_stealth, connected); hid_ptt_set_connected_status(hid->hid_ptt, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } @@ -156,67 +147,41 @@ Hid* hid_alloc(void) { app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); // Device Type Submenu view - app->device_type_submenu = submenu_alloc(); + app->submenu = submenu_alloc(); + submenu_add_item(app->submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); - submenu_add_item( - app->device_type_submenu, + app->submenu, "Keynote Vertical", HidSubmenuIndexKeynoteVertical, hid_submenu_callback, app); + submenu_add_item(app->submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); + submenu_add_item(app->submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app); + submenu_add_item(app->submenu, "Media", HidSubmenuIndexMedia, 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); + app->submenu, "Apple Music macOS", HidSubmenuIndexMusicMacOs, hid_submenu_callback, app); + submenu_add_item(app->submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app); + submenu_add_item(app->submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, - "Apple Music macOS", - HidSubmenuIndexMusicMacOs, - hid_submenu_callback, - app); + app->submenu, "TikTok / YT Shorts", HidSubmenuIndexTikTok, 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); - submenu_add_item( - app->device_type_submenu, - "TikTok / YT Shorts", - HidSubmenuIndexTikTok, - hid_submenu_callback, - app); + app->submenu, "Mouse Clicker", HidSubmenuIndexMouseClicker, hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, - "Mouse Clicker", - HidSubmenuIndexMouseClicker, - hid_submenu_callback, - app); + app->submenu, "Mouse Jiggler", HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, - "Mouse Jiggler", - HidSubmenuIndexMouseJiggler, + app->submenu, + "Mouse Jiggler Stealth", + HidSubmenuIndexMouseJigglerStealth, hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, - "PushToTalk", - HidSubmenuIndexPushToTalk, - hid_submenu_callback, - app); + app->submenu, "PushToTalk", HidSubmenuIndexPushToTalk, hid_submenu_callback, app); #ifdef HID_TRANSPORT_BLE submenu_add_item( - app->device_type_submenu, - "Remove Pairing", - HidSubmenuIndexRemovePairing, - hid_submenu_callback, - app); + app->submenu, "Remove Pairing", HidSubmenuIndexRemovePairing, hid_submenu_callback, app); #endif - 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_set_previous_callback(submenu_get_view(app->submenu), hid_exit); + view_dispatcher_add_view(app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->submenu)); return app; } @@ -285,6 +250,12 @@ Hid* hid_app_alloc_view(void* context) { app->view_dispatcher, HidViewMouseJiggler, hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + // Mouse jiggler stealth view + app->hid_mouse_jiggler_stealth = hid_mouse_jiggler_stealth_alloc(app); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJigglerStealth, + hid_mouse_jiggler_stealth_get_view(app->hid_mouse_jiggler_stealth)); // PushToTalk view app->hid_ptt_menu = hid_ptt_menu_alloc(app); @@ -307,7 +278,7 @@ void hid_free(Hid* app) { #endif // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); - submenu_free(app->device_type_submenu); + submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); @@ -330,6 +301,8 @@ void hid_free(Hid* app) { hid_mouse_clicker_free(app->hid_mouse_clicker); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJigglerStealth); + hid_mouse_jiggler_stealth_free(app->hid_mouse_jiggler_stealth); view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalkMenu); hid_ptt_menu_free(app->hid_ptt_menu); view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalk); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index 70a73e2ec7..b15ab58ff7 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -27,6 +27,7 @@ #include "views/hid_mouse.h" #include "views/hid_mouse_clicker.h" #include "views/hid_mouse_jiggler.h" +#include "views/hid_mouse_jiggler_stealth.h" #include "views/hid_tiktok.h" #include "views/hid_ptt.h" #include "views/hid_ptt_menu.h" @@ -35,11 +36,6 @@ #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" -typedef enum { - HidTransportUsb, - HidTransportBle, -} HidTransport; - typedef struct Hid Hid; struct Hid { @@ -49,7 +45,7 @@ struct Hid { NotificationApp* notifications; ViewDispatcher* view_dispatcher; SceneManager* scene_manager; - Submenu* device_type_submenu; + Submenu* submenu; DialogEx* dialog; Popup* popup; HidKeynote* hid_keynote; @@ -61,12 +57,10 @@ struct Hid { HidMouse* hid_mouse; HidMouseClicker* hid_mouse_clicker; HidMouseJiggler* hid_mouse_jiggler; + HidMouseJigglerStealth* hid_mouse_jiggler_stealth; HidTikTok* hid_tiktok; HidPushToTalk* hid_ptt; HidPushToTalkMenu* hid_ptt_menu; - - HidTransport transport; - uint32_t view_id; }; void bt_hid_remove_pairing(Hid* app); diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h index 8f3a788d1f..1228ead4f0 100644 --- a/applications/system/hid_app/scenes/hid_scene_config.h +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -1,3 +1,2 @@ ADD_SCENE(hid, main, Main) -ADD_SCENE(hid, unpair, Unpair) -ADD_SCENE(hid, exit_confirm, ExitConfirm) \ No newline at end of file +ADD_SCENE(hid, unpair, Unpair) \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c deleted file mode 100644 index 94e783e939..0000000000 --- a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "../hid.h" -#include "../views.h" - -static void hid_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - scene_manager_previous_scene(app->scene_manager); - } else if(result == DialogExResultCenter) { - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, HidSceneMain); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -void hid_scene_exit_confirm_on_enter(void* context) { - Hid* app = context; - - // Exit dialog view - dialog_ex_reset(app->dialog); - dialog_ex_set_result_callback(app->dialog, hid_scene_exit_confirm_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); -} - -bool hid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - Hid* app = context; - bool consumed = false; - UNUSED(app); - UNUSED(event); - - return consumed; -} - -void hid_scene_exit_confirm_on_exit(void* context) { - Hid* app = context; - - dialog_ex_reset(app->dialog); -} diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c index 6c4a116820..cd1051ac7c 100644 --- a/applications/system/hid_app/scenes/hid_scene_main.c +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -4,7 +4,8 @@ void hid_scene_main_on_enter(void* context) { Hid* app = context; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + view_dispatcher_switch_to_view( + app->view_dispatcher, scene_manager_get_scene_state(app->scene_manager, HidSceneMain)); } bool hid_scene_main_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 6fc8531ae1..71cd30e5dd 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -9,6 +9,7 @@ typedef enum { HidViewMouse, HidViewMouseClicker, HidViewMouseJiggler, + HidViewMouseJigglerStealth, BtHidViewTikTok, HidViewPushToTalk, HidViewPushToTalkMenu, diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 1ee2c01c9c..9c62650f34 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -23,7 +23,6 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; - HidTransport transport; } HidKeyboardModel; typedef struct { @@ -229,8 +228,9 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidKeyboardModel* model = context; - // Header - if((!model->connected) && (model->transport == HidTransportBle)) { +// Header +#ifdef HID_TRANSPORT_BLE + if((!model->connected)) { 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"); @@ -243,6 +243,7 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); return; // Dont render the keyboard if we are not yet connected } +#endif canvas_set_font(canvas, FontKeyboard); // Start shifting the all keys up if on the next row (Scrolling) @@ -400,13 +401,7 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { 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); + hid_keyboard->view, HidKeyboardModel * model, { model->y = 1; }, true); return hid_keyboard; } diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c index 543363bf67..3c966e20c3 100644 --- a/applications/system/hid_app/views/hid_keynote.c +++ b/applications/system/hid_app/views/hid_keynote.c @@ -19,7 +19,6 @@ typedef struct { 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) { @@ -39,14 +38,14 @@ 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); - } +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); @@ -92,12 +91,12 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { canvas_set_color(canvas, ColorBlack); // Ok - canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + canvas_draw_icon(canvas, 63, 24, &I_Space_65x18); if(model->ok_pressed) { - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + elements_slightly_rounded_box(canvas, 66, 26, 60, 13); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + canvas_draw_icon(canvas, 74, 28, &I_Ok_btn_9x9); elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); canvas_set_color(canvas, ColorBlack); @@ -115,19 +114,19 @@ static void hid_keynote_draw_vertical_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, 20, 3, AlignLeft, AlignTop, "Keynote"); +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); +#else + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); +#endif canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8); canvas_set_font(canvas, FontSecondary); @@ -275,9 +274,6 @@ HidKeynote* hid_keynote_alloc(Hid* hid) { 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; } diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index af213eb03c..104e8a6c68 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -22,7 +22,6 @@ typedef struct { 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) { @@ -42,14 +41,14 @@ 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); - } +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); @@ -60,9 +59,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -70,9 +69,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -80,9 +79,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); @@ -92,9 +91,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -104,9 +103,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_media_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -116,9 +115,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); @@ -210,9 +209,6 @@ HidMedia* hid_media_alloc(Hid* hid) { 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; } diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c index 3ae7c81454..1816a48b4e 100644 --- a/applications/system/hid_app/views/hid_mouse.c +++ b/applications/system/hid_app/views/hid_mouse.c @@ -21,21 +21,20 @@ typedef struct { 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); - } +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); @@ -219,9 +218,6 @@ HidMouse* hid_mouse_alloc(Hid* hid) { 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; } diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index d85affc433..79922d05fb 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -18,7 +18,6 @@ typedef struct { bool connected; bool running; int rate; - HidTransport transport; } HidMouseClickerModel; static void hid_mouse_clicker_start_or_restart_timer(void* context) { @@ -43,17 +42,17 @@ 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); - } +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); + elements_multiline_text_aligned(canvas, 27, 3, AlignLeft, AlignTop, "Mouse Clicker"); // Ok canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); @@ -179,10 +178,7 @@ HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) { with_view_model( hid_mouse_clicker->view, HidMouseClickerModel * model, - { - model->transport = hid->transport; - model->rate = DEFAULT_CLICK_RATE; - }, + { model->rate = DEFAULT_CLICK_RATE; }, true); return hid_mouse_clicker; diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c index e36fa09666..3040753b66 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.c +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -15,44 +15,41 @@ struct HidMouseJiggler { typedef struct { bool connected; bool running; - int min_interval; // Minimum interval for random range - int max_interval; // Maximum interval for random range - HidTransport transport; + int interval_idx; + uint8_t counter; } 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); - } +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif - // Title "Mouse Jiggler" canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 17, 2, AlignLeft, AlignTop, "Mouse Jiggler"); - - // Display the current min interval in minutes - canvas_set_font(canvas, FontSecondary); // Assuming there's a smaller font available - FuriString* min_interval_str = furi_string_alloc_printf("Min: %d min", model->min_interval); - elements_multiline_text_aligned( - canvas, 0, 16, AlignLeft, AlignTop, furi_string_get_cstr(min_interval_str)); - furi_string_free(min_interval_str); - - // Display the current max interval in minutes - FuriString* max_interval_str = furi_string_alloc_printf("Max: %d min", model->max_interval); - elements_multiline_text_aligned( - canvas, 0, 28, AlignLeft, AlignTop, furi_string_get_cstr(max_interval_str)); - furi_string_free(max_interval_str); - - // "Press Start to jiggle" + elements_multiline_text_aligned(canvas, 27, 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, 50, "Press Start\nto jiggle"); + 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); @@ -81,20 +78,11 @@ static void hid_mouse_jiggler_timer_callback(void* context) { HidMouseJigglerModel * model, { if(model->running) { - // Generate a random interval in minutes and convert to milliseconds - int randomIntervalMinutes = - model->min_interval + rand() % (model->max_interval - model->min_interval + 1); - - // Randomize the mouse movement distance and direction - int move_x = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 - int move_y = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 - - // Perform the mouse move with the randomized values - hid_hal_mouse_move(hid_mouse_jiggler->hid, move_x, move_y); - - // Restart timer with the new random interval - furi_timer_stop(hid_mouse_jiggler->timer); - furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + model->counter++; + hid_hal_mouse_move( + hid_mouse_jiggler->hid, + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + 0); } }, false); @@ -116,51 +104,23 @@ static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { hid_mouse_jiggler->view, HidMouseJigglerModel * model, { - if(event->type == InputTypePress) { - switch(event->key) { - case InputKeyOk: - model->running = !model->running; - if(model->running) { - furi_timer_stop(hid_mouse_jiggler->timer); - int randomIntervalMinutes = - model->min_interval + - rand() % (model->max_interval - model->min_interval + 1); - furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); - } - consumed = true; - break; - - case InputKeyUp: - if(!model->running && model->min_interval < model->max_interval) { - model->min_interval++; // Increment min interval by 1 minute - } - consumed = true; - break; - - case InputKeyDown: - if(!model->running && model->min_interval > 1) { // Minimum 1 minute - model->min_interval--; // Decrement min interval by 1 minute - } - consumed = true; - break; - - case InputKeyRight: - if(!model->running && model->max_interval < 30) { // Maximum 30 minutes - model->max_interval++; // Increment max interval by 1 minute - } - consumed = true; - break; - - case InputKeyLeft: - if(!model->running && model->max_interval > model->min_interval + 1) { - model->max_interval--; // Decrement max interval by 1 minute - } - consumed = true; - break; - - default: - break; - } + 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); @@ -185,14 +145,7 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); with_view_model( - hid_mouse_jiggler->view, - HidMouseJigglerModel * model, - { - // Initialize the min and max interval values - model->min_interval = 2; // 2 minutes - model->max_interval = 15; // 15 minutes - }, - true); + hid_mouse_jiggler->view, HidMouseJigglerModel * model, { model->interval_idx = 2; }, true); return hid_mouse_jiggler; } diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.h b/applications/system/hid_app/views/hid_mouse_jiggler.h index 4361e0bde6..025a863852 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.h +++ b/applications/system/hid_app/views/hid_mouse_jiggler.h @@ -2,6 +2,8 @@ #include +#define MOUSE_MOVE_SHORT 5 + typedef struct Hid Hid; typedef struct HidMouseJiggler HidMouseJiggler; diff --git a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c new file mode 100644 index 0000000000..736cfbac00 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c @@ -0,0 +1,224 @@ +#include "hid_mouse_jiggler_stealth.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseJigglerStealth" + +struct HidMouseJigglerStealth { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + int min_interval; // Minimum interval for random range + int max_interval; // Maximum interval for random range +} HidMouseJigglerStealthModel; + +static void hid_mouse_jiggler_stealth_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerStealthModel* model = context; + +// Header +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } +#endif + + // Title "Mouse Jiggler" + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 10, 2, AlignLeft, AlignTop, "Mouse Jiggler Stealth"); + + // Display the current min interval in minutes + canvas_set_font(canvas, FontSecondary); // Assuming there's a smaller font available + FuriString* min_interval_str = furi_string_alloc_printf("Min: %d min", model->min_interval); + elements_multiline_text_aligned( + canvas, 0, 16, AlignLeft, AlignTop, furi_string_get_cstr(min_interval_str)); + furi_string_free(min_interval_str); + + // Display the current max interval in minutes + FuriString* max_interval_str = furi_string_alloc_printf("Max: %d min", model->max_interval); + elements_multiline_text_aligned( + canvas, 0, 28, AlignLeft, AlignTop, furi_string_get_cstr(max_interval_str)); + furi_string_free(max_interval_str); + + // "Press Start to jiggle" + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 50, "Press Start\nto jiggle"); + + // 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_stealth_timer_callback(void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + if(model->running) { + // Generate a random interval in minutes and convert to milliseconds + int randomIntervalMinutes = + model->min_interval + rand() % (model->max_interval - model->min_interval + 1); + + // Randomize the mouse movement distance and direction + int move_x = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 + int move_y = (rand() % 2001) - 1000; // Randomly between -1000 and 1000 + + // Perform the mouse move with the randomized values + hid_hal_mouse_move(hid_mouse_jiggler->hid, move_x, move_y); + + // Restart timer with the new random interval + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + } + }, + false); +} + +static void hid_mouse_jiggler_stealth_exit_callback(void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_stealth_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJigglerStealth* hid_mouse_jiggler = context; + + bool consumed = false; + + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + if(event->type == InputTypePress) { + switch(event->key) { + case InputKeyOk: + model->running = !model->running; + if(model->running) { + furi_timer_stop(hid_mouse_jiggler->timer); + int randomIntervalMinutes = + model->min_interval + + rand() % (model->max_interval - model->min_interval + 1); + furi_timer_start(hid_mouse_jiggler->timer, randomIntervalMinutes * 60000); + } + consumed = true; + break; + + case InputKeyUp: + if(!model->running && model->min_interval < model->max_interval) { + model->min_interval++; // Increment min interval by 1 minute + } + consumed = true; + break; + + case InputKeyDown: + if(!model->running && model->min_interval > 1) { // Minimum 1 minute + model->min_interval--; // Decrement min interval by 1 minute + } + consumed = true; + break; + + case InputKeyRight: + if(!model->running && model->max_interval < 30) { // Maximum 30 minutes + model->max_interval++; // Increment max interval by 1 minute + } + consumed = true; + break; + + case InputKeyLeft: + if(!model->running && model->max_interval > model->min_interval + 1) { + model->max_interval--; // Decrement max interval by 1 minute + } + consumed = true; + break; + + default: + break; + } + } + }, + true); + + return consumed; +} + +HidMouseJigglerStealth* hid_mouse_jiggler_stealth_alloc(Hid* hid) { + HidMouseJigglerStealth* hid_mouse_jiggler = malloc(sizeof(HidMouseJigglerStealth)); + + 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(HidMouseJigglerStealthModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_input_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_stealth_exit_callback); + + hid_mouse_jiggler->hid = hid; + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_stealth_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { + // Initialize the min and max interval values + model->min_interval = 1; // 1 minutes + model->max_interval = 4; // 4 minutes + }, + true); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_stealth_free(HidMouseJigglerStealth* 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_stealth_get_view(HidMouseJigglerStealth* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} + +void hid_mouse_jiggler_stealth_set_connected_status( + HidMouseJigglerStealth* hid_mouse_jiggler, + bool connected) { + furi_assert(hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerStealthModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h new file mode 100644 index 0000000000..61b52b3cb7 --- /dev/null +++ b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidMouseJigglerStealth HidMouseJigglerStealth; + +HidMouseJigglerStealth* hid_mouse_jiggler_stealth_alloc(Hid* bt_hid); + +void hid_mouse_jiggler_stealth_free(HidMouseJigglerStealth* hid_mouse_jiggler); + +View* hid_mouse_jiggler_stealth_get_view(HidMouseJigglerStealth* hid_mouse_jiggler); + +void hid_mouse_jiggler_stealth_set_connected_status( + HidMouseJigglerStealth* 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 index 0a91b7f3be..90af5d6906 100644 --- a/applications/system/hid_app/views/hid_movie.c +++ b/applications/system/hid_app/views/hid_movie.c @@ -22,7 +22,6 @@ typedef struct { 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) { @@ -43,13 +42,13 @@ static void hid_movie_draw_callback(Canvas* canvas, void* 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); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie"); @@ -211,9 +210,6 @@ HidMovie* hid_movie_alloc(Hid* hid) { 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; } diff --git a/applications/system/hid_app/views/hid_music_macos.c b/applications/system/hid_app/views/hid_music_macos.c index 68d738fd73..637e6850be 100644 --- a/applications/system/hid_app/views/hid_music_macos.c +++ b/applications/system/hid_app/views/hid_music_macos.c @@ -22,7 +22,6 @@ typedef struct { bool ok_pressed; bool connected; bool back_pressed; - HidTransport transport; } HidMusicMacosModel; static void hid_music_macos_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -43,13 +42,13 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { HidMusicMacosModel* 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); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Music"); @@ -215,12 +214,6 @@ HidMusicMacos* hid_music_macos_alloc(Hid* hid) { view_set_draw_callback(hid_music_macos->view, hid_music_macos_draw_callback); view_set_input_callback(hid_music_macos->view, hid_music_macos_input_callback); - with_view_model( - hid_music_macos->view, - HidMusicMacosModel * model, - { model->transport = hid->transport; }, - true); - return hid_music_macos; } diff --git a/applications/system/hid_app/views/hid_numpad.c b/applications/system/hid_app/views/hid_numpad.c index bd4788b83a..7e528e3fbd 100644 --- a/applications/system/hid_app/views/hid_numpad.c +++ b/applications/system/hid_app/views/hid_numpad.c @@ -23,7 +23,6 @@ typedef struct { bool back_pressed; bool connected; char key_string[5]; - HidTransport transport; } HidNumpadModel; typedef struct { @@ -135,27 +134,29 @@ static void hid_numpad_draw_callback(Canvas* canvas, void* 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"); - +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad"); + 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"); +#endif 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)) { +#ifdef HID_TRANSPORT_BLE + if(!model->connected) { return; } +#endif canvas_set_font(canvas, FontKeyboard); uint8_t initY = 0; // = model->y == 0 ? 0 : 1; @@ -289,13 +290,7 @@ HidNumpad* hid_numpad_alloc(Hid* bt_hid) { 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); + hid_numpad->view, HidNumpadModel * model, { model->y = 0; }, true); return hid_numpad; } diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 3b7031a36f..59643abac1 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -32,7 +32,6 @@ typedef struct { size_t osIndex; size_t appIndex; size_t window_position; - HidTransport transport; PushToTalkActionCallback callback_trigger_mute; PushToTalkActionCallback callback_trigger_camera; PushToTalkActionCallback callback_trigger_hand; @@ -575,13 +574,13 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* 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); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif // OS and App labels canvas_set_font(canvas, FontSecondary); @@ -816,7 +815,6 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { 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(); diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index e9198791d8..5f4e8057db 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -20,7 +20,6 @@ typedef struct { bool connected; bool is_cursor_set; bool back_mouse_pressed; - HidTransport transport; } HidTikTokModel; static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { @@ -28,13 +27,13 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { HidTikTokModel* 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); - } +#ifdef HID_TRANSPORT_BLE + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } +#endif canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok /"); @@ -234,9 +233,6 @@ HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); - with_view_model( - hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); - return hid_tiktok; } From 50bee67748f139fc52561e9adb4e06816a05f088 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 15 May 2024 11:09:08 -0700 Subject: [PATCH 18/48] Skylanders plugin (#3315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PoC skylanders plugin * based on https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/db0c7e4fad5c214c36aa8753d27befe49676a985 * More figures * Fix util methods Co-authored-by: あく Co-authored-by: gornekich --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/skylanders.c | 873 ++++++++++++++++++ 2 files changed, 882 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/skylanders.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index b6a0f09d37..898434bb78 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -182,6 +182,15 @@ App( sources=["plugins/supported_cards/itso.c"], ) +App( + appid="skylanders_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="skylanders_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/skylanders.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c new file mode 100644 index 0000000000..a60a5dba38 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -0,0 +1,873 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Skylanders" + +static const uint64_t skylanders_key = 0x4b0b20107ccb; + +bool skylanders_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 0; + 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 = {}; + bit_lib_num_to_bytes_be(skylanders_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 skylanders_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++) { + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(skylanders_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 = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t fill_name(const uint16_t id, FuriString* name) { + // USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1 + // AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1 + switch(id) { + case 0x0000: + furi_string_cat_printf(name, "Whirlwind"); + break; + case 0x0001: + furi_string_cat_printf(name, "Sonic Boom"); + break; + case 0x0002: + furi_string_cat_printf(name, "Warnado"); + break; + case 0x0003: + furi_string_cat_printf(name, "Lightning Rod"); + break; + case 0x0004: + furi_string_cat_printf(name, "Bash"); + break; + case 0x0005: + furi_string_cat_printf(name, "Terrafin"); + break; + case 0x0006: + furi_string_cat_printf(name, "Dino-Rang"); + break; + case 0x0007: + furi_string_cat_printf(name, "Prism Break"); + break; + case 0x0008: + furi_string_cat_printf(name, "Sunburn"); + break; + case 0x0009: + furi_string_cat_printf(name, "Eruptor"); + break; + case 0x000A: + furi_string_cat_printf(name, "Ignitor"); + break; + case 0x000B: + furi_string_cat_printf(name, "Flameslinger"); + break; + case 0x000C: + furi_string_cat_printf(name, "Zap"); + break; + case 0x000D: + furi_string_cat_printf(name, "Wham-Shell"); + break; + case 0x000E: + furi_string_cat_printf(name, "Gill Grunt"); + break; + case 0x000F: + furi_string_cat_printf(name, "Slam Bam"); + break; + case 0x0010: + furi_string_cat_printf(name, "Spyro"); + break; + case 0x0011: + furi_string_cat_printf(name, "Voodood"); + break; + case 0x0012: + furi_string_cat_printf(name, "Double Trouble"); + break; + case 0x0013: + furi_string_cat_printf(name, "Trigger Happy"); + break; + case 0x0014: + furi_string_cat_printf(name, "Drobot"); + break; + case 0x0015: + furi_string_cat_printf(name, "Drill Sergeant"); + break; + case 0x0016: + furi_string_cat_printf(name, "Boomer"); + break; + case 0x0017: + furi_string_cat_printf(name, "Wrecking Ball"); + break; + case 0x0018: + furi_string_cat_printf(name, "Camo"); + break; + case 0x0019: + furi_string_cat_printf(name, "Zook"); + break; + case 0x001A: + furi_string_cat_printf(name, "Stealth Elf"); + break; + case 0x001B: + furi_string_cat_printf(name, "Stump Smash"); + break; + case 0x001C: + furi_string_cat_printf(name, "Dark Spyro"); + break; + case 0x001D: + furi_string_cat_printf(name, "Hex"); + break; + case 0x001E: + furi_string_cat_printf(name, "Chop Chop"); + break; + case 0x001F: + furi_string_cat_printf(name, "Ghost Roaster"); + break; + case 0x0020: + furi_string_cat_printf(name, "Cynder"); + break; + case 0x0064: + furi_string_cat_printf(name, "Jet Vac"); + break; + case 0x0065: + furi_string_cat_printf(name, "Swarm"); + break; + case 0x0066: + furi_string_cat_printf(name, "Crusher"); + break; + case 0x0067: + furi_string_cat_printf(name, "Flashwing"); + break; + case 0x0068: + furi_string_cat_printf(name, "Hot Head"); + break; + case 0x0069: + furi_string_cat_printf(name, "Hot Dog"); + break; + case 0x006A: + furi_string_cat_printf(name, "Chill"); + break; + case 0x006B: + furi_string_cat_printf(name, "Thumpback"); + break; + case 0x006C: + furi_string_cat_printf(name, "Pop Fizz"); + break; + case 0x006D: + furi_string_cat_printf(name, "Ninjini"); + break; + case 0x006E: + furi_string_cat_printf(name, "Bouncer"); + break; + case 0x006F: + furi_string_cat_printf(name, "Sprocket"); + break; + case 0x0070: + furi_string_cat_printf(name, "Tree Rex"); + break; + case 0x0071: + furi_string_cat_printf(name, "Shroomboom"); + break; + case 0x0072: + furi_string_cat_printf(name, "Eye-Brawl"); + break; + case 0x0073: + furi_string_cat_printf(name, "Fright Rider"); + break; + case 0x00C8: + furi_string_cat_printf(name, "Anvil Rain"); + break; + case 0x00C9: + furi_string_cat_printf(name, "Treasure Chest"); + break; + case 0x00CA: + furi_string_cat_printf(name, "Healing Elixer"); + break; + case 0x00CB: + furi_string_cat_printf(name, "Ghost Swords"); + break; + case 0x00CC: + furi_string_cat_printf(name, "Time Twister"); + break; + case 0x00CD: + furi_string_cat_printf(name, "Sky-Iron Shield"); + break; + case 0x00CE: + furi_string_cat_printf(name, "Winged Boots"); + break; + case 0x00CF: + furi_string_cat_printf(name, "Sparx Dragonfly"); + break; + case 0x00D0: + furi_string_cat_printf(name, "Dragonfire Cannon"); + break; + case 0x00D1: + furi_string_cat_printf(name, "Scorpion Striker Catapult"); + break; + case 0x00D2: + furi_string_cat_printf(name, "Trap - Magic"); + break; + case 0x00D3: + furi_string_cat_printf(name, "Trap - Water"); + break; + case 0x00D4: + furi_string_cat_printf(name, "Trap - Air"); + break; + case 0x00D5: + furi_string_cat_printf(name, "Trap - Undead"); + break; + case 0x00D6: + furi_string_cat_printf(name, "Trap - Tech"); + break; + case 0x00D7: + furi_string_cat_printf(name, "Trap - Fire"); + break; + case 0x00D8: + furi_string_cat_printf(name, "Trap - Earth"); + break; + case 0x00D9: + furi_string_cat_printf(name, "Trap - Life"); + break; + case 0x00DA: + furi_string_cat_printf(name, "Trap - Light"); + break; + case 0x00DB: + furi_string_cat_printf(name, "Trap - Dark"); + break; + case 0x00DC: + furi_string_cat_printf(name, "Trap - Kaos"); + break; + case 0x00E6: + furi_string_cat_printf(name, "Hand Of Fate"); + break; + case 0x00E7: + furi_string_cat_printf(name, "Piggy Bank"); + break; + case 0x00E8: + furi_string_cat_printf(name, "Rocket Ram"); + break; + case 0x00E9: + furi_string_cat_printf(name, "Tiki Speaky"); + break; + case 0x00EB: + furi_string_cat_printf(name, "Imaginite Mystery Chest"); + break; + case 0x012C: + furi_string_cat_printf(name, "Dragons Peak"); + break; + case 0x012D: + furi_string_cat_printf(name, "Empire of Ice"); + break; + case 0x012E: + furi_string_cat_printf(name, "Pirate Seas"); + break; + case 0x012F: + furi_string_cat_printf(name, "Darklight Crypt"); + break; + case 0x0130: + furi_string_cat_printf(name, "Volcanic Vault"); + break; + case 0x0131: + furi_string_cat_printf(name, "Mirror Of Mystery"); + break; + case 0x0132: + furi_string_cat_printf(name, "Nightmare Express"); + break; + case 0x0133: + furi_string_cat_printf(name, "Sunscraper Spire"); + break; + case 0x0134: + furi_string_cat_printf(name, "Midnight Museum"); + break; + case 0x0194: + furi_string_cat_printf(name, "Bash"); + break; + case 0x01A0: + furi_string_cat_printf(name, "Spyro"); + break; + case 0x01A3: + furi_string_cat_printf(name, "Trigger Happy"); + break; + case 0x01AE: + furi_string_cat_printf(name, "Chop Chop"); + break; + case 0x01C2: + furi_string_cat_printf(name, "Gusto"); + break; + case 0x01C3: + furi_string_cat_printf(name, "Thunderbolt"); + break; + case 0x01C4: + furi_string_cat_printf(name, "Fling Kong"); + break; + case 0x01C5: + furi_string_cat_printf(name, "Blades"); + break; + case 0x01C6: + furi_string_cat_printf(name, "Wallop"); + break; + case 0x01C7: + furi_string_cat_printf(name, "Head Rush"); + break; + case 0x01C8: + furi_string_cat_printf(name, "Fist Bump"); + break; + case 0x01C9: + furi_string_cat_printf(name, "Rocky Roll"); + break; + case 0x01CA: + furi_string_cat_printf(name, "Wildfire"); + break; + case 0x01CB: + furi_string_cat_printf(name, "Ka Boom"); + break; + case 0x01CC: + furi_string_cat_printf(name, "Trail Blazer"); + break; + case 0x01CD: + furi_string_cat_printf(name, "Torch"); + break; + case 0x01CE: + furi_string_cat_printf(name, "Snap Shot"); + break; + case 0x01CF: + furi_string_cat_printf(name, "Lob Star"); + break; + case 0x01D0: + furi_string_cat_printf(name, "Flip Wreck"); + break; + case 0x01D1: + furi_string_cat_printf(name, "Echo"); + break; + case 0x01D2: + furi_string_cat_printf(name, "Blastermind"); + break; + case 0x01D3: + furi_string_cat_printf(name, "Enigma"); + break; + case 0x01D4: + furi_string_cat_printf(name, "Deja Vu"); + break; + case 0x01D5: + furi_string_cat_printf(name, "Cobra Cadabra"); + break; + case 0x01D6: + furi_string_cat_printf(name, "Jawbreaker"); + break; + case 0x01D7: + furi_string_cat_printf(name, "Gearshift"); + break; + case 0x01D8: + furi_string_cat_printf(name, "Chopper"); + break; + case 0x01D9: + furi_string_cat_printf(name, "Tread Head"); + break; + case 0x01DA: + furi_string_cat_printf(name, "Bushwhack"); + break; + case 0x01DB: + furi_string_cat_printf(name, "Tuff Luck"); + break; + case 0x01DC: + furi_string_cat_printf(name, "Food Fight"); + break; + case 0x01DD: + furi_string_cat_printf(name, "High Five"); + break; + case 0x01DE: + furi_string_cat_printf(name, "Krypt King"); + break; + case 0x01DF: + furi_string_cat_printf(name, "Short Cut"); + break; + case 0x01E0: + furi_string_cat_printf(name, "Bat Spin"); + break; + case 0x01E1: + furi_string_cat_printf(name, "Funny Bone"); + break; + case 0x01E2: + furi_string_cat_printf(name, "Knight light"); + break; + case 0x01E3: + furi_string_cat_printf(name, "Spotlight"); + break; + case 0x01E4: + furi_string_cat_printf(name, "Knight Mare"); + break; + case 0x01E5: + furi_string_cat_printf(name, "Blackout"); + break; + case 0x01F6: + furi_string_cat_printf(name, "Bop"); + break; + case 0x01F7: + furi_string_cat_printf(name, "Spry"); + break; + case 0x01F8: + furi_string_cat_printf(name, "Hijinx"); + break; + case 0x01F9: + furi_string_cat_printf(name, "Terrabite"); + break; + case 0x01FA: + furi_string_cat_printf(name, "Breeze"); + break; + case 0x01FB: + furi_string_cat_printf(name, "Weeruptor"); + break; + case 0x01FC: + furi_string_cat_printf(name, "Pet Vac"); + break; + case 0x01FD: + furi_string_cat_printf(name, "Small Fry"); + break; + case 0x01FE: + furi_string_cat_printf(name, "Drobit"); + break; + case 0x0202: + furi_string_cat_printf(name, "Gill Runt"); + break; + case 0x0207: + furi_string_cat_printf(name, "Trigger Snappy"); + break; + case 0x020E: + furi_string_cat_printf(name, "Whisper Elf"); + break; + case 0x021C: + furi_string_cat_printf(name, "Barkley"); + break; + case 0x021D: + furi_string_cat_printf(name, "Thumpling"); + break; + case 0x021E: + furi_string_cat_printf(name, "Mini Jini"); + break; + case 0x021F: + furi_string_cat_printf(name, "Eye Small"); + break; + case 0x0259: + furi_string_cat_printf(name, "King Pen"); + break; + case 0x0265: + furi_string_cat_printf(name, "Golden Queen"); + break; + case 0x02AD: + furi_string_cat_printf(name, "Fire Acorn"); + break; + case 0x03E8: + furi_string_cat_printf(name, "(Boom) Jet"); + break; + case 0x03E9: + furi_string_cat_printf(name, "(Free) Ranger"); + break; + case 0x03EA: + furi_string_cat_printf(name, "(Rubble) Rouser"); + break; + case 0x03EB: + furi_string_cat_printf(name, "(Doom) Stone"); + break; + case 0x03EC: + furi_string_cat_printf(name, "Blast Zone"); + break; + case 0x03ED: + furi_string_cat_printf(name, "(Fire) Kraken"); + break; + case 0x03EE: + furi_string_cat_printf(name, "(Stink) Bomb"); + break; + case 0x03EF: + furi_string_cat_printf(name, "(Grilla) Drilla"); + break; + case 0x03F0: + furi_string_cat_printf(name, "(Hoot) Loop"); + break; + case 0x03F1: + furi_string_cat_printf(name, "(Trap) Shadow"); + break; + case 0x03F2: + furi_string_cat_printf(name, "(Magna) Charge"); + break; + case 0x03F3: + furi_string_cat_printf(name, "(Spy) Rise"); + break; + case 0x03F4: + furi_string_cat_printf(name, "(Night) Shift"); + break; + case 0x03F5: + furi_string_cat_printf(name, "(Rattle) Shake"); + break; + case 0x03F6: + furi_string_cat_printf(name, "(Freeze) Blade"); + break; + case 0x03F7: + furi_string_cat_printf(name, "Wash Buckler"); + break; + case 0x07D0: + furi_string_cat_printf(name, "Boom (Jet)"); + break; + case 0x07D1: + furi_string_cat_printf(name, "Free (Ranger)"); + break; + case 0x07D2: + furi_string_cat_printf(name, "Rubble (Rouser)"); + break; + case 0x07D3: + furi_string_cat_printf(name, "Doom (Stone)"); + break; + case 0x07D4: + furi_string_cat_printf(name, "Blast Zone (Head)"); + break; + case 0x07D5: + furi_string_cat_printf(name, "Fire (Kraken)"); + break; + case 0x07D6: + furi_string_cat_printf(name, "Stink (Bomb)"); + break; + case 0x07D7: + furi_string_cat_printf(name, "Grilla (Drilla)"); + break; + case 0x07D8: + furi_string_cat_printf(name, "Hoot (Loop)"); + break; + case 0x07D9: + furi_string_cat_printf(name, "Trap (Shadow)"); + break; + case 0x07DA: + furi_string_cat_printf(name, "Magna (Charge)"); + break; + case 0x07DB: + furi_string_cat_printf(name, "Spy (Rise)"); + break; + case 0x07DC: + furi_string_cat_printf(name, "Night (Shift)"); + break; + case 0x07DD: + furi_string_cat_printf(name, "Rattle (Shake)"); + break; + case 0x07DE: + furi_string_cat_printf(name, "Freeze (Blade)"); + break; + case 0x07DF: + furi_string_cat_printf(name, "Wash Buckler (Head)"); + break; + case 0x0BB8: + furi_string_cat_printf(name, "Scratch"); + break; + case 0x0BB9: + furi_string_cat_printf(name, "Pop Thorn"); + break; + case 0x0BBA: + furi_string_cat_printf(name, "Slobber Tooth"); + break; + case 0x0BBB: + furi_string_cat_printf(name, "Scorp"); + break; + case 0x0BBC: + furi_string_cat_printf(name, "Fryno"); + break; + case 0x0BBD: + furi_string_cat_printf(name, "Smolderdash"); + break; + case 0x0BBE: + furi_string_cat_printf(name, "Bumble Blast"); + break; + case 0x0BBF: + furi_string_cat_printf(name, "Zoo Lou"); + break; + case 0x0BC0: + furi_string_cat_printf(name, "Dune Bug"); + break; + case 0x0BC1: + furi_string_cat_printf(name, "Star Strike"); + break; + case 0x0BC2: + furi_string_cat_printf(name, "Countdown"); + break; + case 0x0BC3: + furi_string_cat_printf(name, "Wind Up"); + break; + case 0x0BC4: + furi_string_cat_printf(name, "Roller Brawl"); + break; + case 0x0BC5: + furi_string_cat_printf(name, "Grim Creeper"); + break; + case 0x0BC6: + furi_string_cat_printf(name, "Rip Tide"); + break; + case 0x0BC7: + furi_string_cat_printf(name, "Punk Shock"); + break; + case 0x0C80: + furi_string_cat_printf(name, "Battle Hammer"); + break; + case 0x0C81: + furi_string_cat_printf(name, "Sky Diamond"); + break; + case 0x0C82: + furi_string_cat_printf(name, "Platinum Sheep"); + break; + case 0x0C83: + furi_string_cat_printf(name, "Groove Machine"); + break; + case 0x0C84: + furi_string_cat_printf(name, "UFO Hat"); + break; + case 0x0C94: + furi_string_cat_printf(name, "Jet Stream"); + break; + case 0x0C95: + furi_string_cat_printf(name, "Tomb Buggy"); + break; + case 0x0C96: + furi_string_cat_printf(name, "Reef Ripper"); + break; + case 0x0C97: + furi_string_cat_printf(name, "Burn Cycle"); + break; + case 0x0C98: + furi_string_cat_printf(name, "Hot Streak"); + break; + case 0x0C99: + furi_string_cat_printf(name, "Shark Tank"); + break; + case 0x0C9A: + furi_string_cat_printf(name, "Thump Truck"); + break; + case 0x0C9B: + furi_string_cat_printf(name, "Crypt Crusher"); + break; + case 0x0C9C: + furi_string_cat_printf(name, "Stealth Stinger"); + break; + case 0x0C9F: + furi_string_cat_printf(name, "Dive Bomber"); + break; + case 0x0CA0: + furi_string_cat_printf(name, "Sky Slicer"); + break; + case 0x0CA1: + furi_string_cat_printf(name, "Clown Cruiser"); + break; + case 0x0CA2: + furi_string_cat_printf(name, "Gold Rusher"); + break; + case 0x0CA3: + furi_string_cat_printf(name, "Shield Striker"); + break; + case 0x0CA4: + furi_string_cat_printf(name, "Sun Runner"); + break; + case 0x0CA5: + furi_string_cat_printf(name, "Sea Shadow"); + break; + case 0x0CA6: + furi_string_cat_printf(name, "Splatter Splasher"); + break; + case 0x0CA7: + furi_string_cat_printf(name, "Soda Skimmer"); + break; + case 0x0CA8: + furi_string_cat_printf(name, "Barrel Blaster"); + break; + case 0x0CA9: + furi_string_cat_printf(name, "Buzz Wing"); + break; + case 0x0CE4: + furi_string_cat_printf(name, "Sheep Wreck Island"); + break; + case 0x0CE5: + furi_string_cat_printf(name, "Tower of Time"); + break; + case 0x0CE6: + furi_string_cat_printf(name, "Fiery Forge"); + break; + case 0x0CE7: + furi_string_cat_printf(name, "Arkeyan Crossbow"); + break; + case 0x0D48: + furi_string_cat_printf(name, "Fiesta"); + break; + case 0x0D49: + furi_string_cat_printf(name, "High Volt"); + break; + case 0x0D4A: + furi_string_cat_printf(name, "Splat"); + break; + case 0x0D4E: + furi_string_cat_printf(name, "Stormblade"); + break; + case 0x0D53: + furi_string_cat_printf(name, "Smash It"); + break; + case 0x0D54: + furi_string_cat_printf(name, "Spitfire"); + break; + case 0x0D55: + furi_string_cat_printf(name, "Hurricane Jet-Vac"); + break; + case 0x0D56: + furi_string_cat_printf(name, "Double Dare Trigger Happy"); + break; + case 0x0D57: + furi_string_cat_printf(name, "Super Shot Stealth Elf"); + break; + case 0x0D58: + furi_string_cat_printf(name, "Shark Shooter Terrafin"); + break; + case 0x0D59: + furi_string_cat_printf(name, "Bone Bash Roller Brawl"); + break; + case 0x0D5C: + furi_string_cat_printf(name, "Big Bubble Pop Fizz"); + break; + case 0x0D5D: + furi_string_cat_printf(name, "Lava Lance Eruptor"); + break; + case 0x0D5E: + furi_string_cat_printf(name, "Deep Dive Gill Grunt"); + break; + case 0x0D5F: + furi_string_cat_printf(name, "Turbo Charge Donkey Kong"); + break; + case 0x0D60: + furi_string_cat_printf(name, "Hammer Slam Bowser"); + break; + case 0x0D61: + furi_string_cat_printf(name, "Dive-Clops"); + break; + case 0x0D62: + furi_string_cat_printf(name, "Astroblast"); + break; + case 0x0D63: + furi_string_cat_printf(name, "Nightfall"); + break; + case 0x0D64: + furi_string_cat_printf(name, "Thrillipede"); + break; + case 0x0DAC: + furi_string_cat_printf(name, "Sky Trophy"); + break; + case 0x0DAD: + furi_string_cat_printf(name, "Land Trophy"); + break; + case 0x0DAE: + furi_string_cat_printf(name, "Sea Trophy"); + break; + case 0x0DAF: + furi_string_cat_printf(name, "Kaos Trophy"); + break; + default: + furi_string_cat_printf(name, "Unknown"); + break; + } + + return true; +} + +static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + FuriString* name = furi_string_alloc(); + + do { + // verify key + const uint8_t verify_sector = 0; + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + if(key != skylanders_key) break; + + const uint16_t id = (uint16_t)*data->block[1].data; + if(id == 0) break; + + bool success = fill_name(id, name); + if(!success) break; + + furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name)); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin skylanders_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = skylanders_verify, + .read = skylanders_read, + .parse = skylanders_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor skylanders_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &skylanders_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* skylanders_plugin_ep(void) { + return &skylanders_plugin_descriptor; +} From 6d1a5c71e6ec23a93e6c9f701566af524b1611f7 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Wed, 15 May 2024 11:09:08 -0700 Subject: [PATCH 19/48] Skylanders plugin (#3315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PoC skylanders plugin * based on https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/db0c7e4fad5c214c36aa8753d27befe49676a985 * More figures * Fix util methods Co-authored-by: あく Co-authored-by: gornekich --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/skylanders.c | 873 ++++++++++++++++++ 2 files changed, 882 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/skylanders.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 2b4b906d6d..4c347b2d85 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -263,6 +263,15 @@ App( sources=["plugins/supported_cards/itso.c"], ) +App( + appid="skylanders_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="skylanders_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/skylanders.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c new file mode 100644 index 0000000000..a60a5dba38 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -0,0 +1,873 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "Skylanders" + +static const uint64_t skylanders_key = 0x4b0b20107ccb; + +bool skylanders_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 0; + 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 = {}; + bit_lib_num_to_bytes_be(skylanders_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 skylanders_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++) { + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(skylanders_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 = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t fill_name(const uint16_t id, FuriString* name) { + // USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1 + // AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1 + switch(id) { + case 0x0000: + furi_string_cat_printf(name, "Whirlwind"); + break; + case 0x0001: + furi_string_cat_printf(name, "Sonic Boom"); + break; + case 0x0002: + furi_string_cat_printf(name, "Warnado"); + break; + case 0x0003: + furi_string_cat_printf(name, "Lightning Rod"); + break; + case 0x0004: + furi_string_cat_printf(name, "Bash"); + break; + case 0x0005: + furi_string_cat_printf(name, "Terrafin"); + break; + case 0x0006: + furi_string_cat_printf(name, "Dino-Rang"); + break; + case 0x0007: + furi_string_cat_printf(name, "Prism Break"); + break; + case 0x0008: + furi_string_cat_printf(name, "Sunburn"); + break; + case 0x0009: + furi_string_cat_printf(name, "Eruptor"); + break; + case 0x000A: + furi_string_cat_printf(name, "Ignitor"); + break; + case 0x000B: + furi_string_cat_printf(name, "Flameslinger"); + break; + case 0x000C: + furi_string_cat_printf(name, "Zap"); + break; + case 0x000D: + furi_string_cat_printf(name, "Wham-Shell"); + break; + case 0x000E: + furi_string_cat_printf(name, "Gill Grunt"); + break; + case 0x000F: + furi_string_cat_printf(name, "Slam Bam"); + break; + case 0x0010: + furi_string_cat_printf(name, "Spyro"); + break; + case 0x0011: + furi_string_cat_printf(name, "Voodood"); + break; + case 0x0012: + furi_string_cat_printf(name, "Double Trouble"); + break; + case 0x0013: + furi_string_cat_printf(name, "Trigger Happy"); + break; + case 0x0014: + furi_string_cat_printf(name, "Drobot"); + break; + case 0x0015: + furi_string_cat_printf(name, "Drill Sergeant"); + break; + case 0x0016: + furi_string_cat_printf(name, "Boomer"); + break; + case 0x0017: + furi_string_cat_printf(name, "Wrecking Ball"); + break; + case 0x0018: + furi_string_cat_printf(name, "Camo"); + break; + case 0x0019: + furi_string_cat_printf(name, "Zook"); + break; + case 0x001A: + furi_string_cat_printf(name, "Stealth Elf"); + break; + case 0x001B: + furi_string_cat_printf(name, "Stump Smash"); + break; + case 0x001C: + furi_string_cat_printf(name, "Dark Spyro"); + break; + case 0x001D: + furi_string_cat_printf(name, "Hex"); + break; + case 0x001E: + furi_string_cat_printf(name, "Chop Chop"); + break; + case 0x001F: + furi_string_cat_printf(name, "Ghost Roaster"); + break; + case 0x0020: + furi_string_cat_printf(name, "Cynder"); + break; + case 0x0064: + furi_string_cat_printf(name, "Jet Vac"); + break; + case 0x0065: + furi_string_cat_printf(name, "Swarm"); + break; + case 0x0066: + furi_string_cat_printf(name, "Crusher"); + break; + case 0x0067: + furi_string_cat_printf(name, "Flashwing"); + break; + case 0x0068: + furi_string_cat_printf(name, "Hot Head"); + break; + case 0x0069: + furi_string_cat_printf(name, "Hot Dog"); + break; + case 0x006A: + furi_string_cat_printf(name, "Chill"); + break; + case 0x006B: + furi_string_cat_printf(name, "Thumpback"); + break; + case 0x006C: + furi_string_cat_printf(name, "Pop Fizz"); + break; + case 0x006D: + furi_string_cat_printf(name, "Ninjini"); + break; + case 0x006E: + furi_string_cat_printf(name, "Bouncer"); + break; + case 0x006F: + furi_string_cat_printf(name, "Sprocket"); + break; + case 0x0070: + furi_string_cat_printf(name, "Tree Rex"); + break; + case 0x0071: + furi_string_cat_printf(name, "Shroomboom"); + break; + case 0x0072: + furi_string_cat_printf(name, "Eye-Brawl"); + break; + case 0x0073: + furi_string_cat_printf(name, "Fright Rider"); + break; + case 0x00C8: + furi_string_cat_printf(name, "Anvil Rain"); + break; + case 0x00C9: + furi_string_cat_printf(name, "Treasure Chest"); + break; + case 0x00CA: + furi_string_cat_printf(name, "Healing Elixer"); + break; + case 0x00CB: + furi_string_cat_printf(name, "Ghost Swords"); + break; + case 0x00CC: + furi_string_cat_printf(name, "Time Twister"); + break; + case 0x00CD: + furi_string_cat_printf(name, "Sky-Iron Shield"); + break; + case 0x00CE: + furi_string_cat_printf(name, "Winged Boots"); + break; + case 0x00CF: + furi_string_cat_printf(name, "Sparx Dragonfly"); + break; + case 0x00D0: + furi_string_cat_printf(name, "Dragonfire Cannon"); + break; + case 0x00D1: + furi_string_cat_printf(name, "Scorpion Striker Catapult"); + break; + case 0x00D2: + furi_string_cat_printf(name, "Trap - Magic"); + break; + case 0x00D3: + furi_string_cat_printf(name, "Trap - Water"); + break; + case 0x00D4: + furi_string_cat_printf(name, "Trap - Air"); + break; + case 0x00D5: + furi_string_cat_printf(name, "Trap - Undead"); + break; + case 0x00D6: + furi_string_cat_printf(name, "Trap - Tech"); + break; + case 0x00D7: + furi_string_cat_printf(name, "Trap - Fire"); + break; + case 0x00D8: + furi_string_cat_printf(name, "Trap - Earth"); + break; + case 0x00D9: + furi_string_cat_printf(name, "Trap - Life"); + break; + case 0x00DA: + furi_string_cat_printf(name, "Trap - Light"); + break; + case 0x00DB: + furi_string_cat_printf(name, "Trap - Dark"); + break; + case 0x00DC: + furi_string_cat_printf(name, "Trap - Kaos"); + break; + case 0x00E6: + furi_string_cat_printf(name, "Hand Of Fate"); + break; + case 0x00E7: + furi_string_cat_printf(name, "Piggy Bank"); + break; + case 0x00E8: + furi_string_cat_printf(name, "Rocket Ram"); + break; + case 0x00E9: + furi_string_cat_printf(name, "Tiki Speaky"); + break; + case 0x00EB: + furi_string_cat_printf(name, "Imaginite Mystery Chest"); + break; + case 0x012C: + furi_string_cat_printf(name, "Dragons Peak"); + break; + case 0x012D: + furi_string_cat_printf(name, "Empire of Ice"); + break; + case 0x012E: + furi_string_cat_printf(name, "Pirate Seas"); + break; + case 0x012F: + furi_string_cat_printf(name, "Darklight Crypt"); + break; + case 0x0130: + furi_string_cat_printf(name, "Volcanic Vault"); + break; + case 0x0131: + furi_string_cat_printf(name, "Mirror Of Mystery"); + break; + case 0x0132: + furi_string_cat_printf(name, "Nightmare Express"); + break; + case 0x0133: + furi_string_cat_printf(name, "Sunscraper Spire"); + break; + case 0x0134: + furi_string_cat_printf(name, "Midnight Museum"); + break; + case 0x0194: + furi_string_cat_printf(name, "Bash"); + break; + case 0x01A0: + furi_string_cat_printf(name, "Spyro"); + break; + case 0x01A3: + furi_string_cat_printf(name, "Trigger Happy"); + break; + case 0x01AE: + furi_string_cat_printf(name, "Chop Chop"); + break; + case 0x01C2: + furi_string_cat_printf(name, "Gusto"); + break; + case 0x01C3: + furi_string_cat_printf(name, "Thunderbolt"); + break; + case 0x01C4: + furi_string_cat_printf(name, "Fling Kong"); + break; + case 0x01C5: + furi_string_cat_printf(name, "Blades"); + break; + case 0x01C6: + furi_string_cat_printf(name, "Wallop"); + break; + case 0x01C7: + furi_string_cat_printf(name, "Head Rush"); + break; + case 0x01C8: + furi_string_cat_printf(name, "Fist Bump"); + break; + case 0x01C9: + furi_string_cat_printf(name, "Rocky Roll"); + break; + case 0x01CA: + furi_string_cat_printf(name, "Wildfire"); + break; + case 0x01CB: + furi_string_cat_printf(name, "Ka Boom"); + break; + case 0x01CC: + furi_string_cat_printf(name, "Trail Blazer"); + break; + case 0x01CD: + furi_string_cat_printf(name, "Torch"); + break; + case 0x01CE: + furi_string_cat_printf(name, "Snap Shot"); + break; + case 0x01CF: + furi_string_cat_printf(name, "Lob Star"); + break; + case 0x01D0: + furi_string_cat_printf(name, "Flip Wreck"); + break; + case 0x01D1: + furi_string_cat_printf(name, "Echo"); + break; + case 0x01D2: + furi_string_cat_printf(name, "Blastermind"); + break; + case 0x01D3: + furi_string_cat_printf(name, "Enigma"); + break; + case 0x01D4: + furi_string_cat_printf(name, "Deja Vu"); + break; + case 0x01D5: + furi_string_cat_printf(name, "Cobra Cadabra"); + break; + case 0x01D6: + furi_string_cat_printf(name, "Jawbreaker"); + break; + case 0x01D7: + furi_string_cat_printf(name, "Gearshift"); + break; + case 0x01D8: + furi_string_cat_printf(name, "Chopper"); + break; + case 0x01D9: + furi_string_cat_printf(name, "Tread Head"); + break; + case 0x01DA: + furi_string_cat_printf(name, "Bushwhack"); + break; + case 0x01DB: + furi_string_cat_printf(name, "Tuff Luck"); + break; + case 0x01DC: + furi_string_cat_printf(name, "Food Fight"); + break; + case 0x01DD: + furi_string_cat_printf(name, "High Five"); + break; + case 0x01DE: + furi_string_cat_printf(name, "Krypt King"); + break; + case 0x01DF: + furi_string_cat_printf(name, "Short Cut"); + break; + case 0x01E0: + furi_string_cat_printf(name, "Bat Spin"); + break; + case 0x01E1: + furi_string_cat_printf(name, "Funny Bone"); + break; + case 0x01E2: + furi_string_cat_printf(name, "Knight light"); + break; + case 0x01E3: + furi_string_cat_printf(name, "Spotlight"); + break; + case 0x01E4: + furi_string_cat_printf(name, "Knight Mare"); + break; + case 0x01E5: + furi_string_cat_printf(name, "Blackout"); + break; + case 0x01F6: + furi_string_cat_printf(name, "Bop"); + break; + case 0x01F7: + furi_string_cat_printf(name, "Spry"); + break; + case 0x01F8: + furi_string_cat_printf(name, "Hijinx"); + break; + case 0x01F9: + furi_string_cat_printf(name, "Terrabite"); + break; + case 0x01FA: + furi_string_cat_printf(name, "Breeze"); + break; + case 0x01FB: + furi_string_cat_printf(name, "Weeruptor"); + break; + case 0x01FC: + furi_string_cat_printf(name, "Pet Vac"); + break; + case 0x01FD: + furi_string_cat_printf(name, "Small Fry"); + break; + case 0x01FE: + furi_string_cat_printf(name, "Drobit"); + break; + case 0x0202: + furi_string_cat_printf(name, "Gill Runt"); + break; + case 0x0207: + furi_string_cat_printf(name, "Trigger Snappy"); + break; + case 0x020E: + furi_string_cat_printf(name, "Whisper Elf"); + break; + case 0x021C: + furi_string_cat_printf(name, "Barkley"); + break; + case 0x021D: + furi_string_cat_printf(name, "Thumpling"); + break; + case 0x021E: + furi_string_cat_printf(name, "Mini Jini"); + break; + case 0x021F: + furi_string_cat_printf(name, "Eye Small"); + break; + case 0x0259: + furi_string_cat_printf(name, "King Pen"); + break; + case 0x0265: + furi_string_cat_printf(name, "Golden Queen"); + break; + case 0x02AD: + furi_string_cat_printf(name, "Fire Acorn"); + break; + case 0x03E8: + furi_string_cat_printf(name, "(Boom) Jet"); + break; + case 0x03E9: + furi_string_cat_printf(name, "(Free) Ranger"); + break; + case 0x03EA: + furi_string_cat_printf(name, "(Rubble) Rouser"); + break; + case 0x03EB: + furi_string_cat_printf(name, "(Doom) Stone"); + break; + case 0x03EC: + furi_string_cat_printf(name, "Blast Zone"); + break; + case 0x03ED: + furi_string_cat_printf(name, "(Fire) Kraken"); + break; + case 0x03EE: + furi_string_cat_printf(name, "(Stink) Bomb"); + break; + case 0x03EF: + furi_string_cat_printf(name, "(Grilla) Drilla"); + break; + case 0x03F0: + furi_string_cat_printf(name, "(Hoot) Loop"); + break; + case 0x03F1: + furi_string_cat_printf(name, "(Trap) Shadow"); + break; + case 0x03F2: + furi_string_cat_printf(name, "(Magna) Charge"); + break; + case 0x03F3: + furi_string_cat_printf(name, "(Spy) Rise"); + break; + case 0x03F4: + furi_string_cat_printf(name, "(Night) Shift"); + break; + case 0x03F5: + furi_string_cat_printf(name, "(Rattle) Shake"); + break; + case 0x03F6: + furi_string_cat_printf(name, "(Freeze) Blade"); + break; + case 0x03F7: + furi_string_cat_printf(name, "Wash Buckler"); + break; + case 0x07D0: + furi_string_cat_printf(name, "Boom (Jet)"); + break; + case 0x07D1: + furi_string_cat_printf(name, "Free (Ranger)"); + break; + case 0x07D2: + furi_string_cat_printf(name, "Rubble (Rouser)"); + break; + case 0x07D3: + furi_string_cat_printf(name, "Doom (Stone)"); + break; + case 0x07D4: + furi_string_cat_printf(name, "Blast Zone (Head)"); + break; + case 0x07D5: + furi_string_cat_printf(name, "Fire (Kraken)"); + break; + case 0x07D6: + furi_string_cat_printf(name, "Stink (Bomb)"); + break; + case 0x07D7: + furi_string_cat_printf(name, "Grilla (Drilla)"); + break; + case 0x07D8: + furi_string_cat_printf(name, "Hoot (Loop)"); + break; + case 0x07D9: + furi_string_cat_printf(name, "Trap (Shadow)"); + break; + case 0x07DA: + furi_string_cat_printf(name, "Magna (Charge)"); + break; + case 0x07DB: + furi_string_cat_printf(name, "Spy (Rise)"); + break; + case 0x07DC: + furi_string_cat_printf(name, "Night (Shift)"); + break; + case 0x07DD: + furi_string_cat_printf(name, "Rattle (Shake)"); + break; + case 0x07DE: + furi_string_cat_printf(name, "Freeze (Blade)"); + break; + case 0x07DF: + furi_string_cat_printf(name, "Wash Buckler (Head)"); + break; + case 0x0BB8: + furi_string_cat_printf(name, "Scratch"); + break; + case 0x0BB9: + furi_string_cat_printf(name, "Pop Thorn"); + break; + case 0x0BBA: + furi_string_cat_printf(name, "Slobber Tooth"); + break; + case 0x0BBB: + furi_string_cat_printf(name, "Scorp"); + break; + case 0x0BBC: + furi_string_cat_printf(name, "Fryno"); + break; + case 0x0BBD: + furi_string_cat_printf(name, "Smolderdash"); + break; + case 0x0BBE: + furi_string_cat_printf(name, "Bumble Blast"); + break; + case 0x0BBF: + furi_string_cat_printf(name, "Zoo Lou"); + break; + case 0x0BC0: + furi_string_cat_printf(name, "Dune Bug"); + break; + case 0x0BC1: + furi_string_cat_printf(name, "Star Strike"); + break; + case 0x0BC2: + furi_string_cat_printf(name, "Countdown"); + break; + case 0x0BC3: + furi_string_cat_printf(name, "Wind Up"); + break; + case 0x0BC4: + furi_string_cat_printf(name, "Roller Brawl"); + break; + case 0x0BC5: + furi_string_cat_printf(name, "Grim Creeper"); + break; + case 0x0BC6: + furi_string_cat_printf(name, "Rip Tide"); + break; + case 0x0BC7: + furi_string_cat_printf(name, "Punk Shock"); + break; + case 0x0C80: + furi_string_cat_printf(name, "Battle Hammer"); + break; + case 0x0C81: + furi_string_cat_printf(name, "Sky Diamond"); + break; + case 0x0C82: + furi_string_cat_printf(name, "Platinum Sheep"); + break; + case 0x0C83: + furi_string_cat_printf(name, "Groove Machine"); + break; + case 0x0C84: + furi_string_cat_printf(name, "UFO Hat"); + break; + case 0x0C94: + furi_string_cat_printf(name, "Jet Stream"); + break; + case 0x0C95: + furi_string_cat_printf(name, "Tomb Buggy"); + break; + case 0x0C96: + furi_string_cat_printf(name, "Reef Ripper"); + break; + case 0x0C97: + furi_string_cat_printf(name, "Burn Cycle"); + break; + case 0x0C98: + furi_string_cat_printf(name, "Hot Streak"); + break; + case 0x0C99: + furi_string_cat_printf(name, "Shark Tank"); + break; + case 0x0C9A: + furi_string_cat_printf(name, "Thump Truck"); + break; + case 0x0C9B: + furi_string_cat_printf(name, "Crypt Crusher"); + break; + case 0x0C9C: + furi_string_cat_printf(name, "Stealth Stinger"); + break; + case 0x0C9F: + furi_string_cat_printf(name, "Dive Bomber"); + break; + case 0x0CA0: + furi_string_cat_printf(name, "Sky Slicer"); + break; + case 0x0CA1: + furi_string_cat_printf(name, "Clown Cruiser"); + break; + case 0x0CA2: + furi_string_cat_printf(name, "Gold Rusher"); + break; + case 0x0CA3: + furi_string_cat_printf(name, "Shield Striker"); + break; + case 0x0CA4: + furi_string_cat_printf(name, "Sun Runner"); + break; + case 0x0CA5: + furi_string_cat_printf(name, "Sea Shadow"); + break; + case 0x0CA6: + furi_string_cat_printf(name, "Splatter Splasher"); + break; + case 0x0CA7: + furi_string_cat_printf(name, "Soda Skimmer"); + break; + case 0x0CA8: + furi_string_cat_printf(name, "Barrel Blaster"); + break; + case 0x0CA9: + furi_string_cat_printf(name, "Buzz Wing"); + break; + case 0x0CE4: + furi_string_cat_printf(name, "Sheep Wreck Island"); + break; + case 0x0CE5: + furi_string_cat_printf(name, "Tower of Time"); + break; + case 0x0CE6: + furi_string_cat_printf(name, "Fiery Forge"); + break; + case 0x0CE7: + furi_string_cat_printf(name, "Arkeyan Crossbow"); + break; + case 0x0D48: + furi_string_cat_printf(name, "Fiesta"); + break; + case 0x0D49: + furi_string_cat_printf(name, "High Volt"); + break; + case 0x0D4A: + furi_string_cat_printf(name, "Splat"); + break; + case 0x0D4E: + furi_string_cat_printf(name, "Stormblade"); + break; + case 0x0D53: + furi_string_cat_printf(name, "Smash It"); + break; + case 0x0D54: + furi_string_cat_printf(name, "Spitfire"); + break; + case 0x0D55: + furi_string_cat_printf(name, "Hurricane Jet-Vac"); + break; + case 0x0D56: + furi_string_cat_printf(name, "Double Dare Trigger Happy"); + break; + case 0x0D57: + furi_string_cat_printf(name, "Super Shot Stealth Elf"); + break; + case 0x0D58: + furi_string_cat_printf(name, "Shark Shooter Terrafin"); + break; + case 0x0D59: + furi_string_cat_printf(name, "Bone Bash Roller Brawl"); + break; + case 0x0D5C: + furi_string_cat_printf(name, "Big Bubble Pop Fizz"); + break; + case 0x0D5D: + furi_string_cat_printf(name, "Lava Lance Eruptor"); + break; + case 0x0D5E: + furi_string_cat_printf(name, "Deep Dive Gill Grunt"); + break; + case 0x0D5F: + furi_string_cat_printf(name, "Turbo Charge Donkey Kong"); + break; + case 0x0D60: + furi_string_cat_printf(name, "Hammer Slam Bowser"); + break; + case 0x0D61: + furi_string_cat_printf(name, "Dive-Clops"); + break; + case 0x0D62: + furi_string_cat_printf(name, "Astroblast"); + break; + case 0x0D63: + furi_string_cat_printf(name, "Nightfall"); + break; + case 0x0D64: + furi_string_cat_printf(name, "Thrillipede"); + break; + case 0x0DAC: + furi_string_cat_printf(name, "Sky Trophy"); + break; + case 0x0DAD: + furi_string_cat_printf(name, "Land Trophy"); + break; + case 0x0DAE: + furi_string_cat_printf(name, "Sea Trophy"); + break; + case 0x0DAF: + furi_string_cat_printf(name, "Kaos Trophy"); + break; + default: + furi_string_cat_printf(name, "Unknown"); + break; + } + + return true; +} + +static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + FuriString* name = furi_string_alloc(); + + do { + // verify key + const uint8_t verify_sector = 0; + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + if(key != skylanders_key) break; + + const uint16_t id = (uint16_t)*data->block[1].data; + if(id == 0) break; + + bool success = fill_name(id, name); + if(!success) break; + + furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name)); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin skylanders_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = skylanders_verify, + .read = skylanders_read, + .parse = skylanders_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor skylanders_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &skylanders_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* skylanders_plugin_ep(void) { + return &skylanders_plugin_descriptor; +} From 5dddb075ac42170ee99e67f34db85e0f2d52c647 Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Thu, 16 May 2024 01:47:21 +1000 Subject: [PATCH 20/48] TLSF memory allocator. Less free flash, moar free ram. (#3572) * add tlsf as submodule * libs: tlsf * Furi: tlsf as allocator * Furi: heap walker * shmal fixshesh * f18: tlsf * PVS: ignore tlsf * I like to moving * merge upcoming changes * memmgr: alloc aligned, realloc * Furi: distinct name for auxiliary memory pool * Furi: put idle and timer thread to mem2 * Furi: fix smal things in allocator * Furi: remove aligned_free. Use free instead. * aligned_malloc -> aligned_alloc * aligned_alloc, parameters order * aligned_alloc: check that alignment is correct * unit test: malloc * unit tests: realloc and test with memory fragmentation * unit tests: aligned_alloc * update api * updater: properly read large update file Co-authored-by: Aleksandr Kutuzov --- .gitmodules | 3 + .pvsoptions | 2 +- .../debug/unit_tests/furi/furi_memmgr_test.c | 262 +++++- .../debug/unit_tests/furi/furi_test.c | 2 + applications/services/cli/cli_commands.c | 48 +- furi/core/memmgr.c | 39 +- furi/core/memmgr.h | 25 +- furi/core/memmgr_heap.c | 746 +++++------------- furi/core/memmgr_heap.h | 12 +- furi/core/thread.c | 4 +- furi/flipper.c | 13 +- lib/SConscript | 1 + lib/flipper_application/elf/elf_file.c | 8 +- lib/tlsf | 1 + lib/tlsf.scons | 21 + targets/f18/api_symbols.csv | 12 +- targets/f18/target.json | 3 +- targets/f7/api_symbols.csv | 12 +- targets/f7/fatfs/sector_cache.c | 2 +- targets/f7/src/update.c | 14 +- targets/f7/target.json | 3 +- 21 files changed, 608 insertions(+), 625 deletions(-) create mode 160000 lib/tlsf create mode 100644 lib/tlsf.scons diff --git a/.gitmodules b/.gitmodules index 6f73e125ac..a42e32d0d3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,3 +42,6 @@ path = applications/main/subghz_remote url = https://github.com/DarkFlippers/SubGHz_Remote.git branch = ufw_main_app +[submodule "lib/tlsf"] + path = lib/tlsf + url = https://github.com/espressif/tlsf diff --git a/.pvsoptions b/.pvsoptions index 8606eef154..590a34de8b 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 01e2c17f66..399e2d4188 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,8 +1,5 @@ #include "../minunit.h" -#include -#include -#include -#include +#include void test_furi_memmgr(void) { void* ptr; @@ -37,3 +34,260 @@ void test_furi_memmgr(void) { } free(ptr); } + +static void test_memmgr_malloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = malloc(allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "malloc failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after malloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (malloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_realloc(const size_t allocation_size) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = realloc(ptr, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "realloc(NULL) failed"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after realloc(NULL)"; + break; + } + } + + memset(ptr, 0x55, allocation_size); + + ptr = realloc(ptr, allocation_size * 2); + + // test that we can reallocate memory + if(ptr == NULL) { + error_message = "realloc failed"; + } + + // test that memory content is preserved + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0x55) { + error_message = "memory is not reallocated after realloc"; + break; + } + } + + // test that remaining memory is zero-initialized + size_t non_zero_count = 0; + for(size_t i = allocation_size; i < allocation_size * 2; i++) { + if(ptr[i] != 0) { + non_zero_count += 1; + } + } + + // check that at most of memory is zero-initialized + // we know that allocator not always can restore content size from a pointer + // so we check against small threshold + if(non_zero_count > 4) { + error_message = "seems that memory is not zero-initialized after realloc"; + } + + uint8_t* null_ptr = realloc(ptr, 0); + + // test that we can free memory + if(null_ptr != NULL) { + error_message = "realloc(0) failed"; + } + + // test that memory is zero-initialized after realloc(0) + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after realloc(0)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { + uint8_t* ptr = NULL; + const char* error_message = NULL; + + FURI_CRITICAL_ENTER(); + + ptr = aligned_alloc(alignment, allocation_size); + + // test that we can allocate memory + if(ptr == NULL) { + error_message = "aligned_alloc failed"; + } + + // test that memory is aligned + if(((uintptr_t)ptr % alignment) != 0) { + error_message = "memory is not aligned after aligned_alloc"; + } + + // test that memory is zero-initialized after allocation + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] != 0) { + error_message = "memory is not zero-initialized after aligned_alloc"; + break; + } + } + memset(ptr, 0x55, allocation_size); + free(ptr); + + // test that memory is zero-initialized after free + // we know that allocator can use this memory for inner purposes + // so we check that memory at least partially zero-initialized + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" + + size_t zero_count = 0; + for(size_t i = 0; i < allocation_size; i++) { + if(ptr[i] == 0) { + zero_count++; + } + } + +#pragma GCC diagnostic pop + + // check that at least 75% of memory is zero-initialized + if(zero_count < (allocation_size * 0.75)) { + error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; + } + + FURI_CRITICAL_EXIT(); + + if(error_message != NULL) { + mu_fail(error_message); + } +} + +void test_furi_memmgr_advanced(void) { + const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; + const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); + const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; + const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); + + // do test without memory fragmentation + { + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + } + + // do test with memory fragmentation + { + void* blocks[sizes_count]; + void* guards[sizes_count - 1]; + + // setup guards + for(size_t i = 0; i < sizes_count; i++) { + blocks[i] = malloc(sizes[i]); + if(i < sizes_count - 1) { + guards[i] = malloc(sizes[i]); + } + } + + for(size_t i = 0; i < sizes_count; i++) { + free(blocks[i]); + } + + // do test + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_malloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + test_memmgr_realloc(sizes[i]); + } + + for(size_t i = 0; i < sizes_count; i++) { + for(size_t j = 0; j < alignments_count; j++) { + test_memmgr_alloc_aligned(sizes[i], alignments[j]); + } + } + + // cleanup guards + for(size_t i = 0; i < sizes_count - 1; i++) { + free(guards[i]); + } + } +} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e287f9927f..e0b5916d55 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,6 +9,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); +void test_furi_memmgr_advanced(void); static int foo = 0; @@ -37,6 +38,7 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); + test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index b1dfa3b73a..d22ee4eba9 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -435,8 +435,34 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Pool free: %zu\r\n", memmgr_pool_get_free()); - printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); + printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); + printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); +} + +typedef struct { + void* addr; + size_t size; +} FreeBlockInfo; + +#define FREE_BLOCK_INFO_MAX 128 + +typedef struct { + FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; + size_t free_blocks_count; +} FreeBlockContext; + +static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { + FreeBlockContext* free_blocks = (FreeBlockContext*)context; + if(!used) { + if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { + free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; + free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; + free_blocks->free_blocks_count++; + } else { + return false; + } + } + return true; } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { @@ -444,7 +470,23 @@ void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - memmgr_heap_printf_free_blocks(); + FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); + free_blocks->free_blocks_count = 0; + + memmgr_heap_walk_blocks(free_block_walker, free_blocks); + + for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { + printf( + "A %p S %zu\r\n", + (void*)free_blocks->free_blocks[i].addr, + free_blocks->free_blocks[i].size); + } + + if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { + printf("... and more\r\n"); + } + + free(free_blocks); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768adc05df..768d448904 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,6 +4,8 @@ #include extern void* pvPortMalloc(size_t xSize); +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); +extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -18,18 +20,7 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - if(size == 0) { - vPortFree(ptr); - return NULL; - } - - void* p = pvPortMalloc(size); - if(ptr != NULL) { - memcpy(p, ptr, size); - vPortFree(ptr); - } - - return p; + return pvPortRealloc(ptr, size); } void* calloc(size_t count, size_t size) { @@ -47,6 +38,10 @@ char* strdup(const char* s) { return y; } +void* aligned_alloc(size_t alignment, size_t size) { + return pvPortAllocAligned(size, alignment); +} + size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -79,33 +74,17 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_alloc_from_pool(size_t size) { +void* memmgr_aux_pool_alloc(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_pool_get_free(void) { +size_t memmgr_aux_pool_get_free(void) { return furi_hal_memory_get_free(); } size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); -} - -void* aligned_malloc(size_t size, size_t alignment) { - void* p1; // original block - void** p2; // aligned block - int offset = alignment - 1 + sizeof(void*); - if((p1 = (void*)malloc(size + offset)) == NULL) { - return NULL; - } - p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); - p2[-1] = p1; - return p2; -} - -void aligned_free(void* p) { - free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index bc0c35faa7..796a1f5378 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,37 +36,22 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * An aligned version of malloc, used when you need to get the aligned space on the heap - * Freeing the received address is performed ONLY through the aligned_free function - * @param size - * @param alignment - * @return void* - */ -void* aligned_malloc(size_t size, size_t alignment); - -/** - * Freed space obtained through the aligned_malloc function - * @param p pointer to result of aligned_malloc - */ -void aligned_free(void* p); - -/** - * @brief Allocate memory from separate memory pool. That memory can't be freed. + * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_alloc_from_pool(size_t size); +void* memmgr_aux_pool_alloc(size_t size); /** - * @brief Get free memory pool size + * @brief Get the auxiliary pool free memory size * * @return size_t */ -size_t memmgr_pool_get_free(void); +size_t memmgr_aux_pool_get_free(void); /** - * @brief Get max free block size from memory pool + * @brief Get max free block size from the auxiliary memory pool * * @return size_t */ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3f62b518c2..3dfc7f5acb 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,124 +1,18 @@ -/* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -/* - * A sample implementation of pvPortMalloc() and vPortFree() that combines - * (coalescences) adjacent memory blocks as they are freed, and in so doing - * limits memory fragmentation. - * - * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. - */ - -#include "memmgr_heap.h" -#include "check.h" -#include -#include -#include -#include -#include - -/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ -#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE - +#include +#include +#include #include #include +#include -#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE - -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT -#endif - -#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) -#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 -#endif - -/* Block sizes must not get too small. */ -#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) - -/* Assumes 8bit bytes! */ -#define heapBITS_PER_BYTE ((size_t)8) - -/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; -uint8_t* ucHeap = (uint8_t*)&__heap_start__; - -/* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ -typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ -} BlockLink_t; - -/*-----------------------------------------------------------*/ - -/* - * Inserts a block of memory that is being freed into the correct position in - * the list of free memory blocks. The block being freed will be merged with - * the block in front it and/or the block behind it if the memory blocks are - * adjacent to each other. - */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); - -/* - * Called automatically to setup the required heap structures the first time - * pvPortMalloc() is called. - */ -static void prvHeapInit(void); - -/*-----------------------------------------------------------*/ - -/* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ -static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & - ~((size_t)portBYTE_ALIGNMENT_MASK); - -/* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; - -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; - -/* Furi heap extension */ -#include -/* Allocation tracking types */ +static tlsf_t tlsf = NULL; +static size_t heap_used = 0; +static size_t heap_max_used = 0; + +// Allocation tracking types DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 DICT_DEF2( //-V1048 @@ -128,17 +22,35 @@ DICT_DEF2( //-V1048 MemmgrHeapAllocDict_t, DICT_OPLIST(MemmgrHeapAllocDict)) -/* Thread allocation tracing storage */ +// Thread allocation tracing storage static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; static volatile uint32_t memmgr_heap_thread_trace_depth = 0; -/* Initialize tracing storage on start */ -void memmgr_heap_init(void) { +static inline void memmgr_lock(void) { + vTaskSuspendAll(); +} + +static inline void memmgr_unlock(void) { + xTaskResumeAll(); +} + +static inline size_t memmgr_get_heap_size(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; +} + +// Initialize tracing storage +static void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } +__attribute__((constructor)) static void memmgr_init(void) { + size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; + tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); + memmgr_heap_init(); +} + void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); @@ -148,517 +60,289 @@ void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { MemmgrHeapAllocDict_clear(alloc_dict); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - vTaskSuspendAll(); + memmgr_lock(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); + memmgr_unlock(); } -size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - vTaskSuspendAll(); - { +static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - uint8_t* puc = (uint8_t*)data->key; - puc -= xHeapStructSize; - BlockLink_t* pxLink = (void*)puc; - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && - pxLink->pxNextFreeBlock == NULL) { - leftovers += data->value; - } - } - } + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); } memmgr_heap_thread_trace_depth--; } - (void)xTaskResumeAll(); - return leftovers; } -#undef traceMALLOC -static inline void traceMALLOC(void* pointer, size_t size) { +static inline void memmgr_heap_trace_free(void* pointer) { FuriThreadId thread_id = furi_thread_get_current_id(); if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } } -#undef traceFREE -static inline void traceFREE(void* pointer, size_t size) { - UNUSED(size); - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + memmgr_lock(); + { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + block_header_t* block = block_from_ptr((uint8_t*)data->key); + if(!block_is_free(block)) { + leftovers += data->value; + } + } + } } memmgr_heap_thread_trace_depth--; } + memmgr_unlock(); + return leftovers; } -size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); +static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { + UNUSED(ptr); - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; + bool free = !used; + size_t* max_free_block_size = (size_t*)user; + if(free && size > *max_free_block_size) { + *max_free_block_size = size; } - xTaskResumeAll(); - return max_free_size; + return true; } -void memmgr_heap_printf_free_blocks(void) { - BlockLink_t* pxBlock; - //TODO enable when we can do printf with a locked scheduler - //vTaskSuspendAll(); +size_t memmgr_heap_get_max_free_block(void) { + size_t max_free_block_size = 0; - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; - } + memmgr_lock(); - //xTaskResumeAll(); -} + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. + memmgr_unlock(); - return str; + return max_free_block_size; } -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} +typedef struct { + BlockWalker walker; + void* context; +} BlockWalkerWrapper; -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); +static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { + BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; + return wrapper->walker(ptr, size, used, wrapper->context); } -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } +void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { + memmgr_lock(); - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif -/*-----------------------------------------------------------*/ + BlockWalkerWrapper wrapper = {walker, context}; + pool_t pool = tlsf_get_pool(tlsf); + tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); -void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; - void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + memmgr_unlock(); +} +void* pvPortMalloc(size_t xSize) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif - - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif - - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + memmgr_lock(); + + // allocate block + void* data = tlsf_malloc(tlsf, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc(0)"); + } else { + furi_crash("out of memory"); } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); } - vTaskSuspendAll(); - { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } - if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { - /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ - pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { - pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; - } + // trace allocation + memmgr_heap_trace_malloc(data, xSize); - /* If the end marker was reached then a block of adequate size - was not found. */ - if(pxBlock != pxEnd) { - /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); - - /* This block is being returned for use so must be taken out - of the list of free blocks. */ - pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; - - /* If the block is larger than required it can be split into - two. */ - if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { - /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ - pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); - configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); - - /* Calculate the sizes of two blocks split from the - single block. */ - pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; - pxBlock->xBlockSize = xWantedSize; - - /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); - } else { - mtCOVERAGE_TEST_MARKER(); - } + memmgr_unlock(); - xFreeBytesRemaining -= pxBlock->xBlockSize; + // clear block content + memset(data, 0, xSize); - if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { - xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; - } else { - mtCOVERAGE_TEST_MARKER(); - } + return data; +} - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; +void vPortFree(void* pv) { + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); + } -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + // ignore NULL pointer + if(pv != NULL) { + memmgr_lock(); - traceMALLOC(pvReturn, xWantedSize); - } - (void)xTaskResumeAll(); + // get block size + size_t block_size = tlsf_block_size(pv); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif + // clear block content + memset(pv, 0, block_size); -#if(configUSE_MALLOC_FAILED_HOOK == 1) - { - if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); - vApplicationMallocFailedHook(); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } -#endif + // update heap usage + heap_used -= block_size; + heap_used -= tlsf_alloc_overhead(); - configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); + // free + tlsf_free(tlsf, pv); - furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); - return pvReturn; -} -/*-----------------------------------------------------------*/ + // trace free + memmgr_heap_trace_free(pv); -void vPortFree(void* pv) { - uint8_t* puc = (uint8_t*)pv; - BlockLink_t* pxLink; + memmgr_unlock(); + } +} +extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { + // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - if(pv != NULL) { - /* The memory being freed will have an BlockLink_t structure immediately - before it. */ - puc -= xHeapStructSize; - - /* This casting is to keep the compiler from issuing warnings. */ - pxLink = (void*)puc; - - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); - - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { - /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); -#endif - - vTaskSuspendAll(); - { - furi_assert((size_t)pv >= SRAM_BASE); - furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); - furi_assert(pxLink->xBlockSize >= xHeapStructSize); - furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); - - /* Add this block to the list of free blocks. */ - xFreeBytesRemaining += pxLink->xBlockSize; - traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); - } - (void)xTaskResumeAll(); - } else { - mtCOVERAGE_TEST_MARKER(); - } + // alignment must be power of 2 + if((xAlignment & (xAlignment - 1)) != 0) { + furi_crash("invalid alignment"); + } + + memmgr_lock(); + + // allocate block + void* data = tlsf_memalign(tlsf, xAlignment, xSize); + if(data == NULL) { + if(xSize == 0) { + furi_crash("malloc_aligned(0)"); } else { - mtCOVERAGE_TEST_MARKER(); + furi_crash("out of memory"); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } -} -/*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ + // update heap usage + heap_used += tlsf_block_size(data); + heap_used += tlsf_alloc_overhead(); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; + } -size_t xPortGetFreeHeapSize(void) { - return xFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + // trace allocation + memmgr_heap_trace_malloc(data, xSize); -size_t xPortGetMinimumEverFreeHeapSize(void) { - return xMinimumEverFreeBytesRemaining; -} -/*-----------------------------------------------------------*/ + memmgr_unlock(); + + // clear block content + memset(data, 0, xSize); -void vPortInitialiseBlocks(void) { - /* This just exists to keep the linker quiet. */ + return data; } -/*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +extern void* pvPortRealloc(void* pv, size_t xSize) { + // realloc(ptr, 0) is equivalent to free(ptr) + if(xSize == 0) { + vPortFree(pv); + return NULL; + } + + // realloc(NULL, size) is equivalent to malloc(size) + if(pv == NULL) { + return pvPortMalloc(xSize); + } - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + /* realloc things */ - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + // memory management in ISR is not allowed + if(FURI_IS_IRQ_MODE()) { + furi_crash("memmgt in ISR"); } - pucAlignedHeap = (uint8_t*)uxAddress; - - /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; - xStart.xBlockSize = (size_t)0; - - /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; - pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; - - /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; - - /* Only one block exists - and it covers the entire usable heap space. */ - xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); -} -/*-----------------------------------------------------------*/ + memmgr_lock(); -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { - BlockLink_t* pxIterator; - uint8_t* puc; + // trace old block as free + size_t old_size = tlsf_block_size(pv); - /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { - /* Nothing to do here, just iterate to the right position. */ - } + // trace free + memmgr_heap_trace_free(pv); - /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ - puc = (uint8_t*)pxIterator; - if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { - pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; - pxBlockToInsert = pxIterator; - } else { - mtCOVERAGE_TEST_MARKER(); + // reallocate block + void* data = tlsf_realloc(tlsf, pv, xSize); + if(data == NULL) { + furi_crash("out of memory"); } - /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ - puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { - /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; - } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; - } - } else { - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; + // update heap usage + heap_used -= old_size; + heap_used += tlsf_block_size(data); + if(heap_used > heap_max_used) { + heap_max_used = heap_used; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ - if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; - } else { - mtCOVERAGE_TEST_MARKER(); + // trace allocation + memmgr_heap_trace_malloc(data, xSize); + + memmgr_unlock(); + + // clear remain block content, if the new size is bigger + // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize + if(xSize > old_size) { + memset((uint8_t*)data + old_size, 0, xSize - old_size); } + + return data; } + +size_t xPortGetFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); +} + +size_t xPortGetTotalHeapSize(void) { + return memmgr_get_heap_size(); +} + +size_t xPortGetMinimumEverFreeHeapSize(void) { + return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); +} \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 7d889f1520..2f61deb642 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,9 +40,17 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -/** Print the address and size of all free blocks to stdout +typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); + +/** + * @brief Walk through all heap blocks + * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. + * Also, printf and furi_log contains malloc calls, so do not use them. + * + * @param walker + * @param context */ -void memmgr_heap_printf_free_blocks(void); +void memmgr_heap_walk_blocks(BlockWalker walker, void* context); #ifdef __cplusplus } diff --git a/furi/core/thread.c b/furi/core/thread.c index f9f73b4f75..c9bf79d32a 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -276,8 +276,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_alloc_from_pool(sizeof(StackType_t) * stack), - memmgr_alloc_from_pool(sizeof(StaticTask_t))); + memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), + memmgr_aux_pool_alloc(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/furi/flipper.c b/furi/flipper.c index c7ba3b4fb1..6c7b9831a4 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -51,12 +51,17 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; +PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; +PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; + void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); + *tcb_ptr = &idle_task_tcb; + *stack_ptr = idle_task_stack; *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -64,7 +69,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); - *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); + *tcb_ptr = &timer_task_tcb; + *stack_ptr = timer_task_stack; *stack_size = configTIMER_TASK_STACK_DEPTH; } \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index 8125739325..29c48de6d9 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -13,6 +13,7 @@ env.Append( libs = env.BuildModules( [ + "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 398f25209a..3e10ae3fe3 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -466,7 +466,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); + section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { @@ -718,7 +718,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - aligned_free(s->fast_rel->data); + free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -785,10 +785,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - aligned_free(itref->value.data); + free(itref->value.data); } if(itref->value.fast_rel) { - aligned_free(itref->value.fast_rel->data); + free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/lib/tlsf b/lib/tlsf new file mode 160000 index 0000000000..8fc595fe22 --- /dev/null +++ b/lib/tlsf @@ -0,0 +1 @@ +Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 diff --git a/lib/tlsf.scons b/lib/tlsf.scons new file mode 100644 index 0000000000..0a8419dbdc --- /dev/null +++ b/lib/tlsf.scons @@ -0,0 +1,21 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/tlsf", + ], +) + + +libenv = env.Clone(FW_LIB_NAME="tlsf") +libenv.ApplyLibFlags() + +libenv.Append( + CPPDEFINES=[], +) + +sources = [File("tlsf/tlsf.c")] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 492539d465..ef2d2fcb6a 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,62.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -515,9 +515,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1984,7 +1982,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1992,8 +1991,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd4..a61c1373e1 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,6 +13,7 @@ "print", "flipper18", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -68,4 +69,4 @@ "ibutton", "infrared" ] -} +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 08f700bd2a..5674cebc6a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,62.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -590,9 +590,7 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,-,aligned_alloc,void*,"size_t, size_t" -Function,+,aligned_free,void,void* -Function,+,aligned_malloc,void*,"size_t, size_t" +Function,+,aligned_alloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -2438,7 +2436,8 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,-,memmgr_alloc_from_pool,void*,size_t +Function,+,memmgr_aux_pool_alloc,void*,size_t +Function,+,memmgr_aux_pool_get_free,size_t, Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2446,8 +2445,7 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_printf_free_blocks,void, -Function,-,memmgr_pool_get_free,size_t, +Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index 319dd21732..df86cb7f15 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_alloc_from_pool(sizeof(SectorCache)); + cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); } if(cache != NULL) { diff --git a/targets/f7/src/update.c b/targets/f7/src/update.c index e6cb4aabee..261adb5caf 100644 --- a/targets/f7/src/update.c +++ b/targets/f7/src/update.c @@ -78,21 +78,21 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest furi_string_free(loader_img_path); void* img = malloc(stat.fsize); - uint32_t bytes_read = 0; + uint32_t read_total = 0; + uint16_t read_current = 0; const uint16_t MAX_READ = 0xFFFF; uint32_t crc = 0; do { - uint16_t size_read = 0; - if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 + if(f_read(&file, img + read_total, MAX_READ, &read_current) != FR_OK) { //-V769 break; } - crc = crc32_calc_buffer(crc, img + bytes_read, size_read); - bytes_read += size_read; - } while(bytes_read == MAX_READ); + crc = crc32_calc_buffer(crc, img + read_total, read_current); + read_total += read_current; + } while(read_current == MAX_READ); do { - if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { + if((read_total != stat.fsize) || (crc != manifest->staged_loader_crc)) { break; } diff --git a/targets/f7/target.json b/targets/f7/target.json index 25872198bf..caa3f58eec 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,6 +22,7 @@ "print", "flipper7", "furi", + "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -55,4 +56,4 @@ "bit_lib", "datetime" ] -} +} \ No newline at end of file From 97d5b8b6f604475ffc39e08ff4168dd81e0c40d4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 16 May 2024 03:54:20 +0300 Subject: [PATCH 21/48] subghz plugin ext only load --- lib/subghz/devices/registry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/devices/registry.c b/lib/subghz/devices/registry.c index 779ba81d7a..cf044c98a3 100644 --- a/lib/subghz/devices/registry.c +++ b/lib/subghz/devices/registry.c @@ -23,8 +23,8 @@ void subghz_device_registry_init(void) { firmware_api_interface); //TODO FL-3556: fix path to plugins - if(plugin_manager_load_all(subghz_device->manager, "/any/apps_data/subghz/plugins") != - //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + //if(plugin_manager_load_all(subghz_device->manager, APP_DATA_PATH("plugins")) != + if(plugin_manager_load_all(subghz_device->manager, "/ext/apps_data/subghz/plugins") != PluginManagerErrorNone) { FURI_LOG_E(TAG, "Failed to load all libs"); } From 0ae78dfcddffe1ccbb98ca6e6f1395b25e4dbf45 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 16 May 2024 05:12:51 +0300 Subject: [PATCH 22/48] Revert TLSF This reverts commit 5dddb075ac42170ee99e67f34db85e0f2d52c647 --- .gitmodules | 4 - .pvsoptions | 2 +- .../debug/unit_tests/furi/furi_memmgr_test.c | 262 +----- .../debug/unit_tests/furi/furi_test.c | 2 - applications/services/cli/cli_commands.c | 48 +- furi/core/memmgr.c | 39 +- furi/core/memmgr.h | 25 +- furi/core/memmgr_heap.c | 746 +++++++++++++----- furi/core/memmgr_heap.h | 12 +- furi/core/thread.c | 4 +- furi/flipper.c | 13 +- lib/SConscript | 1 - lib/flipper_application/elf/elf_file.c | 8 +- lib/tlsf | 1 - lib/tlsf.scons | 21 - targets/f18/api_symbols.csv | 12 +- targets/f18/target.json | 3 +- targets/f7/api_symbols.csv | 12 +- targets/f7/fatfs/sector_cache.c | 2 +- targets/f7/src/update.c | 14 +- targets/f7/target.json | 3 +- 21 files changed, 625 insertions(+), 609 deletions(-) delete mode 160000 lib/tlsf delete mode 100644 lib/tlsf.scons diff --git a/.gitmodules b/.gitmodules index 6a9fc0ba5d..6f73e125ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,7 +42,3 @@ path = applications/main/subghz_remote url = https://github.com/DarkFlippers/SubGHz_Remote.git branch = ufw_main_app -[submodule "lib/tlsf"] - path = lib/tlsf - url = https://github.com/espressif/tlsf - diff --git a/.pvsoptions b/.pvsoptions index 590a34de8b..8606eef154 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 399e2d4188..01e2c17f66 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,5 +1,8 @@ #include "../minunit.h" -#include +#include +#include +#include +#include void test_furi_memmgr(void) { void* ptr; @@ -34,260 +37,3 @@ void test_furi_memmgr(void) { } free(ptr); } - -static void test_memmgr_malloc(const size_t allocation_size) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = malloc(allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "malloc failed"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after malloc"; - break; - } - } - memset(ptr, 0x55, allocation_size); - free(ptr); - - // test that memory is zero-initialized after free - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after free (malloc)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -static void test_memmgr_realloc(const size_t allocation_size) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = realloc(ptr, allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "realloc(NULL) failed"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after realloc(NULL)"; - break; - } - } - - memset(ptr, 0x55, allocation_size); - - ptr = realloc(ptr, allocation_size * 2); - - // test that we can reallocate memory - if(ptr == NULL) { - error_message = "realloc failed"; - } - - // test that memory content is preserved - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0x55) { - error_message = "memory is not reallocated after realloc"; - break; - } - } - - // test that remaining memory is zero-initialized - size_t non_zero_count = 0; - for(size_t i = allocation_size; i < allocation_size * 2; i++) { - if(ptr[i] != 0) { - non_zero_count += 1; - } - } - - // check that at most of memory is zero-initialized - // we know that allocator not always can restore content size from a pointer - // so we check against small threshold - if(non_zero_count > 4) { - error_message = "seems that memory is not zero-initialized after realloc"; - } - - uint8_t* null_ptr = realloc(ptr, 0); - - // test that we can free memory - if(null_ptr != NULL) { - error_message = "realloc(0) failed"; - } - - // test that memory is zero-initialized after realloc(0) - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after realloc(0)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = aligned_alloc(alignment, allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "aligned_alloc failed"; - } - - // test that memory is aligned - if(((uintptr_t)ptr % alignment) != 0) { - error_message = "memory is not aligned after aligned_alloc"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after aligned_alloc"; - break; - } - } - memset(ptr, 0x55, allocation_size); - free(ptr); - - // test that memory is zero-initialized after free - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -void test_furi_memmgr_advanced(void) { - const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; - const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); - const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; - const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); - - // do test without memory fragmentation - { - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_malloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_realloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - for(size_t j = 0; j < alignments_count; j++) { - test_memmgr_alloc_aligned(sizes[i], alignments[j]); - } - } - } - - // do test with memory fragmentation - { - void* blocks[sizes_count]; - void* guards[sizes_count - 1]; - - // setup guards - for(size_t i = 0; i < sizes_count; i++) { - blocks[i] = malloc(sizes[i]); - if(i < sizes_count - 1) { - guards[i] = malloc(sizes[i]); - } - } - - for(size_t i = 0; i < sizes_count; i++) { - free(blocks[i]); - } - - // do test - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_malloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_realloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - for(size_t j = 0; j < alignments_count; j++) { - test_memmgr_alloc_aligned(sizes[i], alignments[j]); - } - } - - // cleanup guards - for(size_t i = 0; i < sizes_count - 1; i++) { - free(guards[i]); - } - } -} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e0b5916d55..e287f9927f 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,7 +9,6 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); -void test_furi_memmgr_advanced(void); static int foo = 0; @@ -38,7 +37,6 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); - test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index d22ee4eba9..b1dfa3b73a 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -435,34 +435,8 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); - printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); -} - -typedef struct { - void* addr; - size_t size; -} FreeBlockInfo; - -#define FREE_BLOCK_INFO_MAX 128 - -typedef struct { - FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; - size_t free_blocks_count; -} FreeBlockContext; - -static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { - FreeBlockContext* free_blocks = (FreeBlockContext*)context; - if(!used) { - if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { - free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; - free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; - free_blocks->free_blocks_count++; - } else { - return false; - } - } - return true; + printf("Pool free: %zu\r\n", memmgr_pool_get_free()); + printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { @@ -470,23 +444,7 @@ void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); - free_blocks->free_blocks_count = 0; - - memmgr_heap_walk_blocks(free_block_walker, free_blocks); - - for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { - printf( - "A %p S %zu\r\n", - (void*)free_blocks->free_blocks[i].addr, - free_blocks->free_blocks[i].size); - } - - if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { - printf("... and more\r\n"); - } - - free(free_blocks); + memmgr_heap_printf_free_blocks(); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768d448904..768adc05df 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,8 +4,6 @@ #include extern void* pvPortMalloc(size_t xSize); -extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); -extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -20,7 +18,18 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - return pvPortRealloc(ptr, size); + if(size == 0) { + vPortFree(ptr); + return NULL; + } + + void* p = pvPortMalloc(size); + if(ptr != NULL) { + memcpy(p, ptr, size); + vPortFree(ptr); + } + + return p; } void* calloc(size_t count, size_t size) { @@ -38,10 +47,6 @@ char* strdup(const char* s) { return y; } -void* aligned_alloc(size_t alignment, size_t size) { - return pvPortAllocAligned(size, alignment); -} - size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -74,17 +79,33 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_aux_pool_alloc(size_t size) { +void* memmgr_alloc_from_pool(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_aux_pool_get_free(void) { +size_t memmgr_pool_get_free(void) { return furi_hal_memory_get_free(); } size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); +} + +void* aligned_malloc(size_t size, size_t alignment) { + void* p1; // original block + void** p2; // aligned block + int offset = alignment - 1 + sizeof(void*); + if((p1 = (void*)malloc(size + offset)) == NULL) { + return NULL; + } + p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); + p2[-1] = p1; + return p2; +} + +void aligned_free(void* p) { + free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index 796a1f5378..bc0c35faa7 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,22 +36,37 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. + * An aligned version of malloc, used when you need to get the aligned space on the heap + * Freeing the received address is performed ONLY through the aligned_free function + * @param size + * @param alignment + * @return void* + */ +void* aligned_malloc(size_t size, size_t alignment); + +/** + * Freed space obtained through the aligned_malloc function + * @param p pointer to result of aligned_malloc + */ +void aligned_free(void* p); + +/** + * @brief Allocate memory from separate memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_aux_pool_alloc(size_t size); +void* memmgr_alloc_from_pool(size_t size); /** - * @brief Get the auxiliary pool free memory size + * @brief Get free memory pool size * * @return size_t */ -size_t memmgr_aux_pool_get_free(void); +size_t memmgr_pool_get_free(void); /** - * @brief Get max free block size from the auxiliary memory pool + * @brief Get max free block size from memory pool * * @return size_t */ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3dfc7f5acb..3f62b518c2 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,18 +1,124 @@ -#include -#include -#include +/* + * FreeRTOS Kernel V10.2.1 + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +/* + * A sample implementation of pvPortMalloc() and vPortFree() that combines + * (coalescences) adjacent memory blocks as they are freed, and in so doing + * limits memory fragmentation. + * + * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the + * memory management pages of http://www.FreeRTOS.org for more information. + */ + +#include "memmgr_heap.h" +#include "check.h" +#include +#include +#include +#include +#include + +/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining +all the API functions to use the MPU wrappers. That should only be done when +task.h is included from an application file. */ +#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE + #include #include -#include +#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE + +#ifdef HEAP_PRINT_DEBUG +#error This feature is broken, logging transport must be replaced with RTT +#endif + +#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) +#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 +#endif + +/* Block sizes must not get too small. */ +#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) + +/* Assumes 8bit bytes! */ +#define heapBITS_PER_BYTE ((size_t)8) + +/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; +uint8_t* ucHeap = (uint8_t*)&__heap_start__; + +/* Define the linked list structure. This is used to link free blocks in order +of their memory address. */ +typedef struct A_BLOCK_LINK { + struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ + size_t xBlockSize; /*<< The size of the free block. */ +} BlockLink_t; + +/*-----------------------------------------------------------*/ + +/* + * Inserts a block of memory that is being freed into the correct position in + * the list of free memory blocks. The block being freed will be merged with + * the block in front it and/or the block behind it if the memory blocks are + * adjacent to each other. + */ +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); + +/* + * Called automatically to setup the required heap structures the first time + * pvPortMalloc() is called. + */ +static void prvHeapInit(void); + +/*-----------------------------------------------------------*/ + +/* The size of the structure placed at the beginning of each allocated memory +block must by correctly byte aligned. */ +static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & + ~((size_t)portBYTE_ALIGNMENT_MASK); + +/* Create a couple of list links to mark the start and end of the list. */ +static BlockLink_t xStart, *pxEnd = NULL; + +/* Keeps track of the number of free bytes remaining, but says nothing about +fragmentation. */ +static size_t xFreeBytesRemaining = 0U; +static size_t xMinimumEverFreeBytesRemaining = 0U; + +/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize +member of an BlockLink_t structure is set then the block belongs to the +application. When the bit is free the block is still part of the free heap +space. */ +static size_t xBlockAllocatedBit = 0; + +/* Furi heap extension */ +#include -static tlsf_t tlsf = NULL; -static size_t heap_used = 0; -static size_t heap_max_used = 0; - -// Allocation tracking types +/* Allocation tracking types */ DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 DICT_DEF2( //-V1048 @@ -22,35 +128,17 @@ DICT_DEF2( //-V1048 MemmgrHeapAllocDict_t, DICT_OPLIST(MemmgrHeapAllocDict)) -// Thread allocation tracing storage +/* Thread allocation tracing storage */ static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; static volatile uint32_t memmgr_heap_thread_trace_depth = 0; -static inline void memmgr_lock(void) { - vTaskSuspendAll(); -} - -static inline void memmgr_unlock(void) { - xTaskResumeAll(); -} - -static inline size_t memmgr_get_heap_size(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} - -// Initialize tracing storage -static void memmgr_heap_init(void) { +/* Initialize tracing storage on start */ +void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } -__attribute__((constructor)) static void memmgr_init(void) { - size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; - tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); - memmgr_heap_init(); -} - void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - memmgr_lock(); + vTaskSuspendAll(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); @@ -60,289 +148,517 @@ void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { MemmgrHeapAllocDict_clear(alloc_dict); memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); + (void)xTaskResumeAll(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - memmgr_lock(); + vTaskSuspendAll(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); + (void)xTaskResumeAll(); } -static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + vTaskSuspendAll(); + { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + uint8_t* puc = (uint8_t*)data->key; + puc -= xHeapStructSize; + BlockLink_t* pxLink = (void*)puc; + + if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && + pxLink->pxNextFreeBlock == NULL) { + leftovers += data->value; + } + } + } } memmgr_heap_thread_trace_depth--; } + (void)xTaskResumeAll(); + return leftovers; } -static inline void memmgr_heap_trace_free(void* pointer) { +#undef traceMALLOC +static inline void traceMALLOC(void* pointer, size_t size) { FuriThreadId thread_id = furi_thread_get_current_id(); if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); } memmgr_heap_thread_trace_depth--; } } -size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - memmgr_lock(); - { +#undef traceFREE +static inline void traceFREE(void* pointer, size_t size) { + UNUSED(size); + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - block_header_t* block = block_from_ptr((uint8_t*)data->key); - if(!block_is_free(block)) { - leftovers += data->value; - } - } - } + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); - return leftovers; } -static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { - UNUSED(ptr); +size_t memmgr_heap_get_max_free_block(void) { + size_t max_free_size = 0; + BlockLink_t* pxBlock; + vTaskSuspendAll(); - bool free = !used; - size_t* max_free_block_size = (size_t*)user; - if(free && size > *max_free_block_size) { - *max_free_block_size = size; + pxBlock = xStart.pxNextFreeBlock; + while(pxBlock->pxNextFreeBlock != NULL) { + if(pxBlock->xBlockSize > max_free_size) { + max_free_size = pxBlock->xBlockSize; + } + pxBlock = pxBlock->pxNextFreeBlock; } - return true; + xTaskResumeAll(); + return max_free_size; } -size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_block_size = 0; +void memmgr_heap_printf_free_blocks(void) { + BlockLink_t* pxBlock; + //TODO enable when we can do printf with a locked scheduler + //vTaskSuspendAll(); - memmgr_lock(); + pxBlock = xStart.pxNextFreeBlock; + while(pxBlock->pxNextFreeBlock != NULL) { + printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); + pxBlock = pxBlock->pxNextFreeBlock; + } - pool_t pool = tlsf_get_pool(tlsf); - tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); + //xTaskResumeAll(); +} - memmgr_unlock(); +#ifdef HEAP_PRINT_DEBUG +char* ultoa(unsigned long num, char* str, int radix) { + char temp[33]; // at radix 2 the string is at most 32 + 1 null long. + int temp_loc = 0; + int digit; + int str_loc = 0; + + //construct a backward string of the number. + do { + digit = (unsigned long)num % ((unsigned long)radix); + if(digit < 10) + temp[temp_loc++] = digit + '0'; + else + temp[temp_loc++] = digit - 10 + 'A'; + num = ((unsigned long)num) / ((unsigned long)radix); + } while((unsigned long)num > 0); + + temp_loc--; + + //now reverse the string. + while(temp_loc >= 0) { // while there are still chars + str[str_loc++] = temp[temp_loc--]; + } + str[str_loc] = 0; // add null termination. - return max_free_block_size; + return str; } -typedef struct { - BlockWalker walker; - void* context; -} BlockWalkerWrapper; - -static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { - BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; - return wrapper->walker(ptr, size, used, wrapper->context); +static void print_heap_init(void) { + char tmp_str[33]; + size_t heap_start = (size_t)&__heap_start__; + size_t heap_end = (size_t)&__heap_end__; + + // {PHStart|heap_start|heap_end} + FURI_CRITICAL_ENTER(); + furi_log_puts("{PHStart|"); + ultoa(heap_start, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("|"); + ultoa(heap_end, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); } -void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { - memmgr_lock(); +static void print_heap_malloc(void* ptr, size_t size) { + char tmp_str[33]; + const char* name = furi_thread_get_name(furi_thread_get_current_id()); + if(!name) { + name = ""; + } - BlockWalkerWrapper wrapper = {walker, context}; - pool_t pool = tlsf_get_pool(tlsf); - tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); + // {thread name|m|address|size} + FURI_CRITICAL_ENTER(); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|m|0x"); + ultoa((unsigned long)ptr, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("|"); + utoa(size, tmp_str, 10); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); +} - memmgr_unlock(); +static void print_heap_free(void* ptr) { + char tmp_str[33]; + const char* name = furi_thread_get_name(furi_thread_get_current_id()); + if(!name) { + name = ""; + } + + // {thread name|f|address} + FURI_CRITICAL_ENTER(); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|f|0x"); + ultoa((unsigned long)ptr, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); } +#endif +/*-----------------------------------------------------------*/ + +void* pvPortMalloc(size_t xWantedSize) { + BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; + void* pvReturn = NULL; + size_t to_wipe = xWantedSize; -void* pvPortMalloc(size_t xSize) { - // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - memmgr_lock(); - - // allocate block - void* data = tlsf_malloc(tlsf, xSize); - if(data == NULL) { - if(xSize == 0) { - furi_crash("malloc(0)"); - } else { - furi_crash("out of memory"); +#ifdef HEAP_PRINT_DEBUG + BlockLink_t* print_heap_block = NULL; +#endif + + /* If this is the first call to malloc then the heap will require + initialisation to setup the list of free blocks. */ + if(pxEnd == NULL) { +#ifdef HEAP_PRINT_DEBUG + print_heap_init(); +#endif + + vTaskSuspendAll(); + { + prvHeapInit(); + memmgr_heap_init(); } + (void)xTaskResumeAll(); + } else { + mtCOVERAGE_TEST_MARKER(); } - // update heap usage - heap_used += tlsf_block_size(data); - heap_used += tlsf_alloc_overhead(); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; - } - - // trace allocation - memmgr_heap_trace_malloc(data, xSize); + vTaskSuspendAll(); + { + /* Check the requested block size is not so large that the top bit is + set. The top bit of the block size member of the BlockLink_t structure + is used to determine who owns the block - the application or the + kernel, so it must be free. */ + if((xWantedSize & xBlockAllocatedBit) == 0) { + /* The wanted size is increased so it can contain a BlockLink_t + structure in addition to the requested amount of bytes. */ + if(xWantedSize > 0) { + xWantedSize += xHeapStructSize; + + /* Ensure that blocks are always aligned to the required number + of bytes. */ + if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { + /* Byte alignment required. */ + xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); + configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } - memmgr_unlock(); + if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { + /* Traverse the list from the start (lowest address) block until + one of adequate size is found. */ + pxPreviousBlock = &xStart; + pxBlock = xStart.pxNextFreeBlock; + while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { + pxPreviousBlock = pxBlock; + pxBlock = pxBlock->pxNextFreeBlock; + } - // clear block content - memset(data, 0, xSize); + /* If the end marker was reached then a block of adequate size + was not found. */ + if(pxBlock != pxEnd) { + /* Return the memory space pointed to - jumping over the + BlockLink_t structure at its start. */ + pvReturn = + (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); + + /* This block is being returned for use so must be taken out + of the list of free blocks. */ + pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; + + /* If the block is larger than required it can be split into + two. */ + if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { + /* This block is to be split into two. Create a new + block following the number of bytes requested. The void + cast is used to prevent byte alignment warnings from the + compiler. */ + pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); + configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); + + /* Calculate the sizes of two blocks split from the + single block. */ + pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; + pxBlock->xBlockSize = xWantedSize; + + /* Insert the new block into the list of free blocks. */ + prvInsertBlockIntoFreeList(pxNewBlockLink); + } else { + mtCOVERAGE_TEST_MARKER(); + } - return data; -} + xFreeBytesRemaining -= pxBlock->xBlockSize; -void vPortFree(void* pv) { - // memory management in ISR is not allowed - if(FURI_IS_IRQ_MODE()) { - furi_crash("memmgt in ISR"); - } + if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { + xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; + } else { + mtCOVERAGE_TEST_MARKER(); + } - // ignore NULL pointer - if(pv != NULL) { - memmgr_lock(); + /* The block is being returned - it is allocated and owned + by the application and has no "next" block. */ + pxBlock->xBlockSize |= xBlockAllocatedBit; + pxBlock->pxNextFreeBlock = NULL; - // get block size - size_t block_size = tlsf_block_size(pv); +#ifdef HEAP_PRINT_DEBUG + print_heap_block = pxBlock; +#endif + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } - // clear block content - memset(pv, 0, block_size); + traceMALLOC(pvReturn, xWantedSize); + } + (void)xTaskResumeAll(); - // update heap usage - heap_used -= block_size; - heap_used -= tlsf_alloc_overhead(); +#ifdef HEAP_PRINT_DEBUG + print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); +#endif - // free - tlsf_free(tlsf, pv); +#if(configUSE_MALLOC_FAILED_HOOK == 1) + { + if(pvReturn == NULL) { + extern void vApplicationMallocFailedHook(void); + vApplicationMallocFailedHook(); + } else { + mtCOVERAGE_TEST_MARKER(); + } + } +#endif - // trace free - memmgr_heap_trace_free(pv); + configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); - memmgr_unlock(); - } + furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); + pvReturn = memset(pvReturn, 0, to_wipe); + return pvReturn; } +/*-----------------------------------------------------------*/ + +void vPortFree(void* pv) { + uint8_t* puc = (uint8_t*)pv; + BlockLink_t* pxLink; -extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { - // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - // alignment must be power of 2 - if((xAlignment & (xAlignment - 1)) != 0) { - furi_crash("invalid alignment"); - } - - memmgr_lock(); - - // allocate block - void* data = tlsf_memalign(tlsf, xAlignment, xSize); - if(data == NULL) { - if(xSize == 0) { - furi_crash("malloc_aligned(0)"); + if(pv != NULL) { + /* The memory being freed will have an BlockLink_t structure immediately + before it. */ + puc -= xHeapStructSize; + + /* This casting is to keep the compiler from issuing warnings. */ + pxLink = (void*)puc; + + /* Check the block is actually allocated. */ + configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); + configASSERT(pxLink->pxNextFreeBlock == NULL); + + if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { + if(pxLink->pxNextFreeBlock == NULL) { + /* The block is being returned to the heap - it is no longer + allocated. */ + pxLink->xBlockSize &= ~xBlockAllocatedBit; + +#ifdef HEAP_PRINT_DEBUG + print_heap_free(pxLink); +#endif + + vTaskSuspendAll(); + { + furi_assert((size_t)pv >= SRAM_BASE); + furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); + furi_assert(pxLink->xBlockSize >= xHeapStructSize); + furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); + + /* Add this block to the list of free blocks. */ + xFreeBytesRemaining += pxLink->xBlockSize; + traceFREE(pv, pxLink->xBlockSize); + memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); + prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); + } + (void)xTaskResumeAll(); + } else { + mtCOVERAGE_TEST_MARKER(); + } } else { - furi_crash("out of memory"); + mtCOVERAGE_TEST_MARKER(); } + } else { +#ifdef HEAP_PRINT_DEBUG + print_heap_free(pv); +#endif } +} +/*-----------------------------------------------------------*/ - // update heap usage - heap_used += tlsf_block_size(data); - heap_used += tlsf_alloc_overhead(); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; - } - - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - - memmgr_unlock(); +size_t xPortGetTotalHeapSize(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; +} +/*-----------------------------------------------------------*/ - // clear block content - memset(data, 0, xSize); +size_t xPortGetFreeHeapSize(void) { + return xFreeBytesRemaining; +} +/*-----------------------------------------------------------*/ - return data; +size_t xPortGetMinimumEverFreeHeapSize(void) { + return xMinimumEverFreeBytesRemaining; } +/*-----------------------------------------------------------*/ -extern void* pvPortRealloc(void* pv, size_t xSize) { - // realloc(ptr, 0) is equivalent to free(ptr) - if(xSize == 0) { - vPortFree(pv); - return NULL; - } +void vPortInitialiseBlocks(void) { + /* This just exists to keep the linker quiet. */ +} +/*-----------------------------------------------------------*/ - // realloc(NULL, size) is equivalent to malloc(size) - if(pv == NULL) { - return pvPortMalloc(xSize); - } +static void prvHeapInit(void) { + BlockLink_t* pxFirstFreeBlock; + uint8_t* pucAlignedHeap; + size_t uxAddress; + size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; - /* realloc things */ + /* Ensure the heap starts on a correctly aligned boundary. */ + uxAddress = (size_t)ucHeap; - // memory management in ISR is not allowed - if(FURI_IS_IRQ_MODE()) { - furi_crash("memmgt in ISR"); + if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { + uxAddress += (portBYTE_ALIGNMENT - 1); + uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); + xTotalHeapSize -= uxAddress - (size_t)ucHeap; } - memmgr_lock(); - - // trace old block as free - size_t old_size = tlsf_block_size(pv); + pucAlignedHeap = (uint8_t*)uxAddress; + + /* xStart is used to hold a pointer to the first item in the list of free + blocks. The void cast is used to prevent compiler warnings. */ + xStart.pxNextFreeBlock = (void*)pucAlignedHeap; + xStart.xBlockSize = (size_t)0; + + /* pxEnd is used to mark the end of the list of free blocks and is inserted + at the end of the heap space. */ + uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; + uxAddress -= xHeapStructSize; + uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); + pxEnd = (void*)uxAddress; + pxEnd->xBlockSize = 0; + pxEnd->pxNextFreeBlock = NULL; + + /* To start with there is a single free block that is sized to take up the + entire heap space, minus the space taken by pxEnd. */ + pxFirstFreeBlock = (void*)pucAlignedHeap; + pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; + pxFirstFreeBlock->pxNextFreeBlock = pxEnd; + + /* Only one block exists - and it covers the entire usable heap space. */ + xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; + xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; + + /* Work out the position of the top bit in a size_t variable. */ + xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); +} +/*-----------------------------------------------------------*/ - // trace free - memmgr_heap_trace_free(pv); +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { + BlockLink_t* pxIterator; + uint8_t* puc; - // reallocate block - void* data = tlsf_realloc(tlsf, pv, xSize); - if(data == NULL) { - furi_crash("out of memory"); + /* Iterate through the list until a block is found that has a higher address + than the block being inserted. */ + for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; + pxIterator = pxIterator->pxNextFreeBlock) { + /* Nothing to do here, just iterate to the right position. */ } - // update heap usage - heap_used -= old_size; - heap_used += tlsf_block_size(data); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; + /* Do the block being inserted, and the block it is being inserted after + make a contiguous block of memory? */ + puc = (uint8_t*)pxIterator; + if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { + pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; + pxBlockToInsert = pxIterator; + } else { + mtCOVERAGE_TEST_MARKER(); } - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - - memmgr_unlock(); - - // clear remain block content, if the new size is bigger - // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize - if(xSize > old_size) { - memset((uint8_t*)data + old_size, 0, xSize - old_size); + /* Do the block being inserted, and the block it is being inserted before + make a contiguous block of memory? */ + puc = (uint8_t*)pxBlockToInsert; + if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { + if(pxIterator->pxNextFreeBlock != pxEnd) { + /* Form one big block from the two blocks. */ + pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; + pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; + } else { + pxBlockToInsert->pxNextFreeBlock = pxEnd; + } + } else { + pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } - return data; -} - -size_t xPortGetFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); -} - -size_t xPortGetTotalHeapSize(void) { - return memmgr_get_heap_size(); + /* If the block being inserted plugged a gab, so was merged with the block + before and the block after, then it's pxNextFreeBlock pointer will have + already been set, and should not be set here as that would make it point + to itself. */ + if(pxIterator != pxBlockToInsert) { + pxIterator->pxNextFreeBlock = pxBlockToInsert; + } else { + mtCOVERAGE_TEST_MARKER(); + } } - -size_t xPortGetMinimumEverFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); -} \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 2f61deb642..7d889f1520 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,17 +40,9 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); - -/** - * @brief Walk through all heap blocks - * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. - * Also, printf and furi_log contains malloc calls, so do not use them. - * - * @param walker - * @param context +/** Print the address and size of all free blocks to stdout */ -void memmgr_heap_walk_blocks(BlockWalker walker, void* context); +void memmgr_heap_printf_free_blocks(void); #ifdef __cplusplus } diff --git a/furi/core/thread.c b/furi/core/thread.c index c9bf79d32a..f9f73b4f75 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -276,8 +276,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), - memmgr_aux_pool_alloc(sizeof(StaticTask_t))); + memmgr_alloc_from_pool(sizeof(StackType_t) * stack), + memmgr_alloc_from_pool(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/furi/flipper.c b/furi/flipper.c index 6c7b9831a4..c7ba3b4fb1 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -51,17 +51,12 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } -PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; -PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; -PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; -PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; - void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = &idle_task_tcb; - *stack_ptr = idle_task_stack; + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -69,7 +64,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = &timer_task_tcb; - *stack_ptr = timer_task_stack; + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; } \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index 29c48de6d9..8125739325 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -13,7 +13,6 @@ env.Append( libs = env.BuildModules( [ - "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 3e10ae3fe3..398f25209a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -466,7 +466,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); + section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { @@ -718,7 +718,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - free(s->fast_rel->data); + aligned_free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -785,10 +785,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - free(itref->value.data); + aligned_free(itref->value.data); } if(itref->value.fast_rel) { - free(itref->value.fast_rel->data); + aligned_free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/lib/tlsf b/lib/tlsf deleted file mode 160000 index 8fc595fe22..0000000000 --- a/lib/tlsf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 diff --git a/lib/tlsf.scons b/lib/tlsf.scons deleted file mode 100644 index 0a8419dbdc..0000000000 --- a/lib/tlsf.scons +++ /dev/null @@ -1,21 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/tlsf", - ], -) - - -libenv = env.Clone(FW_LIB_NAME="tlsf") -libenv.ApplyLibFlags() - -libenv.Append( - CPPDEFINES=[], -) - -sources = [File("tlsf/tlsf.c")] - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ef2d2fcb6a..492539d465 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,62.0,, +Version,+,61.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -515,7 +515,9 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,+,aligned_alloc,void*,"size_t, size_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1982,8 +1984,7 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,+,memmgr_aux_pool_alloc,void*,size_t -Function,+,memmgr_aux_pool_get_free,size_t, +Function,-,memmgr_alloc_from_pool,void*,size_t Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1991,7 +1992,8 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f18/target.json b/targets/f18/target.json index a61c1373e1..43e9254cd4 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,7 +13,6 @@ "print", "flipper18", "furi", - "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -69,4 +68,4 @@ "ibutton", "infrared" ] -} \ No newline at end of file +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5674cebc6a..08f700bd2a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,62.0,, +Version,+,61.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -590,7 +590,9 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,+,aligned_alloc,void*,"size_t, size_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -2436,8 +2438,7 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,+,memmgr_aux_pool_alloc,void*,size_t -Function,+,memmgr_aux_pool_get_free,size_t, +Function,-,memmgr_alloc_from_pool,void*,size_t Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2445,7 +2446,8 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index df86cb7f15..319dd21732 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); + cache = memmgr_alloc_from_pool(sizeof(SectorCache)); } if(cache != NULL) { diff --git a/targets/f7/src/update.c b/targets/f7/src/update.c index 261adb5caf..e6cb4aabee 100644 --- a/targets/f7/src/update.c +++ b/targets/f7/src/update.c @@ -78,21 +78,21 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest furi_string_free(loader_img_path); void* img = malloc(stat.fsize); - uint32_t read_total = 0; - uint16_t read_current = 0; + uint32_t bytes_read = 0; const uint16_t MAX_READ = 0xFFFF; uint32_t crc = 0; do { - if(f_read(&file, img + read_total, MAX_READ, &read_current) != FR_OK) { //-V769 + uint16_t size_read = 0; + if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 break; } - crc = crc32_calc_buffer(crc, img + read_total, read_current); - read_total += read_current; - } while(read_current == MAX_READ); + crc = crc32_calc_buffer(crc, img + bytes_read, size_read); + bytes_read += size_read; + } while(bytes_read == MAX_READ); do { - if((read_total != stat.fsize) || (crc != manifest->staged_loader_crc)) { + if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { break; } diff --git a/targets/f7/target.json b/targets/f7/target.json index caa3f58eec..25872198bf 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,7 +22,6 @@ "print", "flipper7", "furi", - "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -56,4 +55,4 @@ "bit_lib", "datetime" ] -} \ No newline at end of file +} From 7c63bf7574c4616ccff023c97dc8507e210ba59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 16 May 2024 15:43:27 +0100 Subject: [PATCH 23/48] Revert "TLSF memory allocator. Less free flash, moar free ram. (#3572)" (#3651) * Revert "TLSF memory allocator. Less free flash, moar free ram. (#3572)" This reverts commit 1d17206e2358583a7feac2e142218f5b4ca38148. * Fix PVS warnings * github: logging for ticket number checks to stdout * memgr: removed offending todo --------- Co-authored-by: hedger --- .../workflows/lint_and_submodule_check.yml | 2 + .gitmodules | 3 - .pvsoptions | 2 +- .../debug/unit_tests/furi/furi_memmgr_test.c | 262 +----- .../debug/unit_tests/furi/furi_test.c | 2 - .../nfc/plugins/supported_cards/skylanders.c | 16 +- applications/services/cli/cli_commands.c | 48 +- furi/core/memmgr.c | 39 +- furi/core/memmgr.h | 25 +- furi/core/memmgr_heap.c | 746 +++++++++++++----- furi/core/memmgr_heap.h | 12 +- furi/core/thread.c | 4 +- furi/flipper.c | 13 +- lib/SConscript | 1 - lib/flipper_application/elf/elf_file.c | 8 +- lib/tlsf | 1 - lib/tlsf.scons | 21 - targets/f18/api_symbols.csv | 12 +- targets/f18/target.json | 3 +- targets/f7/api_symbols.csv | 12 +- targets/f7/fatfs/sector_cache.c | 2 +- targets/f7/src/update.c | 14 +- targets/f7/target.json | 3 +- 23 files changed, 631 insertions(+), 620 deletions(-) delete mode 160000 lib/tlsf delete mode 100644 lib/tlsf.scons diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml index 3f4d8c79bd..3063d943d6 100644 --- a/.github/workflows/lint_and_submodule_check.yml +++ b/.github/workflows/lint_and_submodule_check.yml @@ -54,6 +54,8 @@ jobs: echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY; echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; + echo "Error: Missing issue number in comment(s):"; + echo "$MISSING_TICKETS"; exit 1; else echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY; diff --git a/.gitmodules b/.gitmodules index a8d12803cb..c4c68a6a77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,6 +41,3 @@ [submodule "documentation/doxygen/doxygen-awesome-css"] path = documentation/doxygen/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git -[submodule "lib/tlsf"] - path = lib/tlsf - url = https://github.com/espressif/tlsf diff --git a/.pvsoptions b/.pvsoptions index 590a34de8b..8606eef154 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/tlsf -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 399e2d4188..01e2c17f66 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -1,5 +1,8 @@ #include "../minunit.h" -#include +#include +#include +#include +#include void test_furi_memmgr(void) { void* ptr; @@ -34,260 +37,3 @@ void test_furi_memmgr(void) { } free(ptr); } - -static void test_memmgr_malloc(const size_t allocation_size) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = malloc(allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "malloc failed"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after malloc"; - break; - } - } - memset(ptr, 0x55, allocation_size); - free(ptr); - - // test that memory is zero-initialized after free - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after free (malloc)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -static void test_memmgr_realloc(const size_t allocation_size) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = realloc(ptr, allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "realloc(NULL) failed"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after realloc(NULL)"; - break; - } - } - - memset(ptr, 0x55, allocation_size); - - ptr = realloc(ptr, allocation_size * 2); - - // test that we can reallocate memory - if(ptr == NULL) { - error_message = "realloc failed"; - } - - // test that memory content is preserved - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0x55) { - error_message = "memory is not reallocated after realloc"; - break; - } - } - - // test that remaining memory is zero-initialized - size_t non_zero_count = 0; - for(size_t i = allocation_size; i < allocation_size * 2; i++) { - if(ptr[i] != 0) { - non_zero_count += 1; - } - } - - // check that at most of memory is zero-initialized - // we know that allocator not always can restore content size from a pointer - // so we check against small threshold - if(non_zero_count > 4) { - error_message = "seems that memory is not zero-initialized after realloc"; - } - - uint8_t* null_ptr = realloc(ptr, 0); - - // test that we can free memory - if(null_ptr != NULL) { - error_message = "realloc(0) failed"; - } - - // test that memory is zero-initialized after realloc(0) - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after realloc(0)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -static void test_memmgr_alloc_aligned(const size_t allocation_size, const size_t alignment) { - uint8_t* ptr = NULL; - const char* error_message = NULL; - - FURI_CRITICAL_ENTER(); - - ptr = aligned_alloc(alignment, allocation_size); - - // test that we can allocate memory - if(ptr == NULL) { - error_message = "aligned_alloc failed"; - } - - // test that memory is aligned - if(((uintptr_t)ptr % alignment) != 0) { - error_message = "memory is not aligned after aligned_alloc"; - } - - // test that memory is zero-initialized after allocation - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] != 0) { - error_message = "memory is not zero-initialized after aligned_alloc"; - break; - } - } - memset(ptr, 0x55, allocation_size); - free(ptr); - - // test that memory is zero-initialized after free - // we know that allocator can use this memory for inner purposes - // so we check that memory at least partially zero-initialized - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" - - size_t zero_count = 0; - for(size_t i = 0; i < allocation_size; i++) { - if(ptr[i] == 0) { - zero_count++; - } - } - -#pragma GCC diagnostic pop - - // check that at least 75% of memory is zero-initialized - if(zero_count < (allocation_size * 0.75)) { - error_message = "seems that memory is not zero-initialized after free (aligned_alloc)"; - } - - FURI_CRITICAL_EXIT(); - - if(error_message != NULL) { - mu_fail(error_message); - } -} - -void test_furi_memmgr_advanced(void) { - const size_t sizes[] = {50, 100, 500, 1000, 5000, 10000}; - const size_t sizes_count = sizeof(sizes) / sizeof(sizes[0]); - const size_t alignments[] = {4, 8, 16, 32, 64, 128, 256, 512, 1024}; - const size_t alignments_count = sizeof(alignments) / sizeof(alignments[0]); - - // do test without memory fragmentation - { - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_malloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_realloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - for(size_t j = 0; j < alignments_count; j++) { - test_memmgr_alloc_aligned(sizes[i], alignments[j]); - } - } - } - - // do test with memory fragmentation - { - void* blocks[sizes_count]; - void* guards[sizes_count - 1]; - - // setup guards - for(size_t i = 0; i < sizes_count; i++) { - blocks[i] = malloc(sizes[i]); - if(i < sizes_count - 1) { - guards[i] = malloc(sizes[i]); - } - } - - for(size_t i = 0; i < sizes_count; i++) { - free(blocks[i]); - } - - // do test - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_malloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - test_memmgr_realloc(sizes[i]); - } - - for(size_t i = 0; i < sizes_count; i++) { - for(size_t j = 0; j < alignments_count; j++) { - test_memmgr_alloc_aligned(sizes[i], alignments[j]); - } - } - - // cleanup guards - for(size_t i = 0; i < sizes_count - 1; i++) { - free(guards[i]); - } - } -} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/furi/furi_test.c index e0b5916d55..e287f9927f 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/furi/furi_test.c @@ -9,7 +9,6 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); -void test_furi_memmgr_advanced(void); static int foo = 0; @@ -38,7 +37,6 @@ MU_TEST(mu_test_furi_memmgr) { // this test is not accurate, but gives a basic understanding // that memory management is working fine test_furi_memmgr(); - test_furi_memmgr_advanced(); } MU_TEST_SUITE(test_suite) { diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c index a60a5dba38..fca1c3185f 100644 --- a/applications/main/nfc/plugins/supported_cards/skylanders.c +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -92,6 +92,7 @@ static uint8_t fill_name(const uint16_t id, FuriString* name) { furi_string_cat_printf(name, "Lightning Rod"); break; case 0x0004: + case 0x0194: furi_string_cat_printf(name, "Bash"); break; case 0x0005: @@ -128,6 +129,7 @@ static uint8_t fill_name(const uint16_t id, FuriString* name) { furi_string_cat_printf(name, "Slam Bam"); break; case 0x0010: + case 0x01A0: furi_string_cat_printf(name, "Spyro"); break; case 0x0011: @@ -137,6 +139,7 @@ static uint8_t fill_name(const uint16_t id, FuriString* name) { furi_string_cat_printf(name, "Double Trouble"); break; case 0x0013: + case 0x01A3: furi_string_cat_printf(name, "Trigger Happy"); break; case 0x0014: @@ -170,6 +173,7 @@ static uint8_t fill_name(const uint16_t id, FuriString* name) { furi_string_cat_printf(name, "Hex"); break; case 0x001E: + case 0x01AE: furi_string_cat_printf(name, "Chop Chop"); break; case 0x001F: @@ -331,18 +335,6 @@ static uint8_t fill_name(const uint16_t id, FuriString* name) { case 0x0134: furi_string_cat_printf(name, "Midnight Museum"); break; - case 0x0194: - furi_string_cat_printf(name, "Bash"); - break; - case 0x01A0: - furi_string_cat_printf(name, "Spyro"); - break; - case 0x01A3: - furi_string_cat_printf(name, "Trigger Happy"); - break; - case 0x01AE: - furi_string_cat_printf(name, "Chop Chop"); - break; case 0x01C2: furi_string_cat_printf(name, "Gusto"); break; diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 56d05785fa..43f1c01c4d 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -425,34 +425,8 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Minimum heap size: %zu\r\n", memmgr_get_minimum_free_heap()); printf("Maximum heap block: %zu\r\n", memmgr_heap_get_max_free_block()); - printf("Aux pool total free: %zu\r\n", memmgr_aux_pool_get_free()); - printf("Aux pool max free block: %zu\r\n", memmgr_pool_get_max_block()); -} - -typedef struct { - void* addr; - size_t size; -} FreeBlockInfo; - -#define FREE_BLOCK_INFO_MAX 128 - -typedef struct { - FreeBlockInfo free_blocks[FREE_BLOCK_INFO_MAX]; - size_t free_blocks_count; -} FreeBlockContext; - -static bool free_block_walker(void* pointer, size_t size, bool used, void* context) { - FreeBlockContext* free_blocks = (FreeBlockContext*)context; - if(!used) { - if(free_blocks->free_blocks_count < FREE_BLOCK_INFO_MAX) { - free_blocks->free_blocks[free_blocks->free_blocks_count].addr = pointer; - free_blocks->free_blocks[free_blocks->free_blocks_count].size = size; - free_blocks->free_blocks_count++; - } else { - return false; - } - } - return true; + printf("Pool free: %zu\r\n", memmgr_pool_get_free()); + printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { @@ -460,23 +434,7 @@ void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { UNUSED(args); UNUSED(context); - FreeBlockContext* free_blocks = malloc(sizeof(FreeBlockContext)); - free_blocks->free_blocks_count = 0; - - memmgr_heap_walk_blocks(free_block_walker, free_blocks); - - for(size_t i = 0; i < free_blocks->free_blocks_count; i++) { - printf( - "A %p S %zu\r\n", - (void*)free_blocks->free_blocks[i].addr, - free_blocks->free_blocks[i].size); - } - - if(free_blocks->free_blocks_count == FREE_BLOCK_INFO_MAX) { - printf("... and more\r\n"); - } - - free(free_blocks); + memmgr_heap_printf_free_blocks(); } void cli_command_i2c(Cli* cli, FuriString* args, void* context) { diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index 768d448904..768adc05df 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -4,8 +4,6 @@ #include extern void* pvPortMalloc(size_t xSize); -extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment); -extern void* pvPortRealloc(void* pv, size_t xSize); extern void vPortFree(void* pv); extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetTotalHeapSize(void); @@ -20,7 +18,18 @@ void free(void* ptr) { } void* realloc(void* ptr, size_t size) { - return pvPortRealloc(ptr, size); + if(size == 0) { + vPortFree(ptr); + return NULL; + } + + void* p = pvPortMalloc(size); + if(ptr != NULL) { + memcpy(p, ptr, size); + vPortFree(ptr); + } + + return p; } void* calloc(size_t count, size_t size) { @@ -38,10 +47,6 @@ char* strdup(const char* s) { return y; } -void* aligned_alloc(size_t alignment, size_t size) { - return pvPortAllocAligned(size, alignment); -} - size_t memmgr_get_free_heap(void) { return xPortGetFreeHeapSize(); } @@ -74,17 +79,33 @@ void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) { return realloc(ptr, size); } -void* memmgr_aux_pool_alloc(size_t size) { +void* memmgr_alloc_from_pool(size_t size) { void* p = furi_hal_memory_alloc(size); if(p == NULL) p = malloc(size); return p; } -size_t memmgr_aux_pool_get_free(void) { +size_t memmgr_pool_get_free(void) { return furi_hal_memory_get_free(); } size_t memmgr_pool_get_max_block(void) { return furi_hal_memory_max_pool_block(); +} + +void* aligned_malloc(size_t size, size_t alignment) { + void* p1; // original block + void** p2; // aligned block + int offset = alignment - 1 + sizeof(void*); + if((p1 = (void*)malloc(size + offset)) == NULL) { + return NULL; + } + p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1)); + p2[-1] = p1; + return p2; +} + +void aligned_free(void* p) { + free(((void**)p)[-1]); } \ No newline at end of file diff --git a/furi/core/memmgr.h b/furi/core/memmgr.h index 796a1f5378..bc0c35faa7 100644 --- a/furi/core/memmgr.h +++ b/furi/core/memmgr.h @@ -36,22 +36,37 @@ size_t memmgr_get_total_heap(void); size_t memmgr_get_minimum_free_heap(void); /** - * @brief Allocate memory from the auxiliary memory pool. That memory can't be freed. + * An aligned version of malloc, used when you need to get the aligned space on the heap + * Freeing the received address is performed ONLY through the aligned_free function + * @param size + * @param alignment + * @return void* + */ +void* aligned_malloc(size_t size, size_t alignment); + +/** + * Freed space obtained through the aligned_malloc function + * @param p pointer to result of aligned_malloc + */ +void aligned_free(void* p); + +/** + * @brief Allocate memory from separate memory pool. That memory can't be freed. * * @param size * @return void* */ -void* memmgr_aux_pool_alloc(size_t size); +void* memmgr_alloc_from_pool(size_t size); /** - * @brief Get the auxiliary pool free memory size + * @brief Get free memory pool size * * @return size_t */ -size_t memmgr_aux_pool_get_free(void); +size_t memmgr_pool_get_free(void); /** - * @brief Get max free block size from the auxiliary memory pool + * @brief Get max free block size from memory pool * * @return size_t */ diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3dfc7f5acb..3cee0d3779 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,18 +1,124 @@ -#include -#include -#include +/* + * FreeRTOS Kernel V10.2.1 + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +/* + * A sample implementation of pvPortMalloc() and vPortFree() that combines + * (coalescences) adjacent memory blocks as they are freed, and in so doing + * limits memory fragmentation. + * + * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the + * memory management pages of http://www.FreeRTOS.org for more information. + */ + +#include "memmgr_heap.h" +#include "check.h" +#include +#include +#include +#include +#include + +/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining +all the API functions to use the MPU wrappers. That should only be done when +task.h is included from an application file. */ +#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE + #include #include -#include +#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE + +#ifdef HEAP_PRINT_DEBUG +#error This feature is broken, logging transport must be replaced with RTT +#endif + +#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) +#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 +#endif + +/* Block sizes must not get too small. */ +#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) + +/* Assumes 8bit bytes! */ +#define heapBITS_PER_BYTE ((size_t)8) + +/* Heap start end symbols provided by linker */ extern const void __heap_start__; extern const void __heap_end__; +uint8_t* ucHeap = (uint8_t*)&__heap_start__; + +/* Define the linked list structure. This is used to link free blocks in order +of their memory address. */ +typedef struct A_BLOCK_LINK { + struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ + size_t xBlockSize; /*<< The size of the free block. */ +} BlockLink_t; + +/*-----------------------------------------------------------*/ + +/* + * Inserts a block of memory that is being freed into the correct position in + * the list of free memory blocks. The block being freed will be merged with + * the block in front it and/or the block behind it if the memory blocks are + * adjacent to each other. + */ +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); + +/* + * Called automatically to setup the required heap structures the first time + * pvPortMalloc() is called. + */ +static void prvHeapInit(void); + +/*-----------------------------------------------------------*/ + +/* The size of the structure placed at the beginning of each allocated memory +block must by correctly byte aligned. */ +static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & + ~((size_t)portBYTE_ALIGNMENT_MASK); + +/* Create a couple of list links to mark the start and end of the list. */ +static BlockLink_t xStart, *pxEnd = NULL; + +/* Keeps track of the number of free bytes remaining, but says nothing about +fragmentation. */ +static size_t xFreeBytesRemaining = 0U; +static size_t xMinimumEverFreeBytesRemaining = 0U; + +/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize +member of an BlockLink_t structure is set then the block belongs to the +application. When the bit is free the block is still part of the free heap +space. */ +static size_t xBlockAllocatedBit = 0; + +/* Furi heap extension */ +#include -static tlsf_t tlsf = NULL; -static size_t heap_used = 0; -static size_t heap_max_used = 0; - -// Allocation tracking types +/* Allocation tracking types */ DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t) //-V1048 DICT_DEF2( //-V1048 @@ -22,35 +128,17 @@ DICT_DEF2( //-V1048 MemmgrHeapAllocDict_t, DICT_OPLIST(MemmgrHeapAllocDict)) -// Thread allocation tracing storage +/* Thread allocation tracing storage */ static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0}; static volatile uint32_t memmgr_heap_thread_trace_depth = 0; -static inline void memmgr_lock(void) { - vTaskSuspendAll(); -} - -static inline void memmgr_unlock(void) { - xTaskResumeAll(); -} - -static inline size_t memmgr_get_heap_size(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} - -// Initialize tracing storage -static void memmgr_heap_init(void) { +/* Initialize tracing storage on start */ +void memmgr_heap_init(void) { MemmgrHeapThreadDict_init(memmgr_heap_thread_dict); } -__attribute__((constructor)) static void memmgr_init(void) { - size_t pool_size = (size_t)&__heap_end__ - (size_t)&__heap_start__; - tlsf = tlsf_create_with_pool((void*)&__heap_start__, pool_size, pool_size); - memmgr_heap_init(); -} - void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { - memmgr_lock(); + vTaskSuspendAll(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL); @@ -60,289 +148,517 @@ void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) { MemmgrHeapAllocDict_clear(alloc_dict); memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); + (void)xTaskResumeAll(); } void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) { - memmgr_lock(); + vTaskSuspendAll(); { memmgr_heap_thread_trace_depth++; furi_check(MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id)); memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); + (void)xTaskResumeAll(); } -static inline void memmgr_heap_trace_malloc(void* pointer, size_t size) { - FuriThreadId thread_id = furi_thread_get_current_id(); - if(thread_id && memmgr_heap_thread_trace_depth == 0) { +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { + size_t leftovers = MEMMGR_HEAP_UNKNOWN; + vTaskSuspendAll(); + { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); + leftovers = 0; + MemmgrHeapAllocDict_it_t alloc_dict_it; + for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); + !MemmgrHeapAllocDict_end_p(alloc_dict_it); + MemmgrHeapAllocDict_next(alloc_dict_it)) { + MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); + if(data->key != 0) { + uint8_t* puc = (uint8_t*)data->key; + puc -= xHeapStructSize; + BlockLink_t* pxLink = (void*)puc; + + if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && + pxLink->pxNextFreeBlock == NULL) { + leftovers += data->value; + } + } + } } memmgr_heap_thread_trace_depth--; } + (void)xTaskResumeAll(); + return leftovers; } -static inline void memmgr_heap_trace_free(void* pointer) { +#undef traceMALLOC +static inline void traceMALLOC(void* pointer, size_t size) { FuriThreadId thread_id = furi_thread_get_current_id(); if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - // In some cases thread may want to release memory that was not allocated by it - const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); - UNUSED(res); + MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size); } memmgr_heap_thread_trace_depth--; } } -size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { - size_t leftovers = MEMMGR_HEAP_UNKNOWN; - memmgr_lock(); - { +#undef traceFREE +static inline void traceFREE(void* pointer, size_t size) { + UNUSED(size); + FuriThreadId thread_id = furi_thread_get_current_id(); + if(thread_id && memmgr_heap_thread_trace_depth == 0) { memmgr_heap_thread_trace_depth++; MemmgrHeapAllocDict_t* alloc_dict = MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id); if(alloc_dict) { - leftovers = 0; - MemmgrHeapAllocDict_it_t alloc_dict_it; - for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict); - !MemmgrHeapAllocDict_end_p(alloc_dict_it); - MemmgrHeapAllocDict_next(alloc_dict_it)) { - MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it); - if(data->key != 0) { - block_header_t* block = block_from_ptr((uint8_t*)data->key); - if(!block_is_free(block)) { - leftovers += data->value; - } - } - } + // In some cases thread may want to release memory that was not allocated by it + const bool res = MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer); + UNUSED(res); } memmgr_heap_thread_trace_depth--; } - memmgr_unlock(); - return leftovers; } -static bool tlsf_walker_max_free(void* ptr, size_t size, int used, void* user) { - UNUSED(ptr); +size_t memmgr_heap_get_max_free_block(void) { + size_t max_free_size = 0; + BlockLink_t* pxBlock; + vTaskSuspendAll(); - bool free = !used; - size_t* max_free_block_size = (size_t*)user; - if(free && size > *max_free_block_size) { - *max_free_block_size = size; + pxBlock = xStart.pxNextFreeBlock; + while(pxBlock->pxNextFreeBlock != NULL) { + if(pxBlock->xBlockSize > max_free_size) { + max_free_size = pxBlock->xBlockSize; + } + pxBlock = pxBlock->pxNextFreeBlock; } - return true; + xTaskResumeAll(); + return max_free_size; } -size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_block_size = 0; +void memmgr_heap_printf_free_blocks(void) { + BlockLink_t* pxBlock; + //can be enabled once we can do printf with a locked scheduler + //vTaskSuspendAll(); - memmgr_lock(); + pxBlock = xStart.pxNextFreeBlock; + while(pxBlock->pxNextFreeBlock != NULL) { + printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); + pxBlock = pxBlock->pxNextFreeBlock; + } - pool_t pool = tlsf_get_pool(tlsf); - tlsf_walk_pool(pool, tlsf_walker_max_free, &max_free_block_size); + //xTaskResumeAll(); +} - memmgr_unlock(); +#ifdef HEAP_PRINT_DEBUG +char* ultoa(unsigned long num, char* str, int radix) { + char temp[33]; // at radix 2 the string is at most 32 + 1 null long. + int temp_loc = 0; + int digit; + int str_loc = 0; + + //construct a backward string of the number. + do { + digit = (unsigned long)num % ((unsigned long)radix); + if(digit < 10) + temp[temp_loc++] = digit + '0'; + else + temp[temp_loc++] = digit - 10 + 'A'; + num = ((unsigned long)num) / ((unsigned long)radix); + } while((unsigned long)num > 0); + + temp_loc--; + + //now reverse the string. + while(temp_loc >= 0) { // while there are still chars + str[str_loc++] = temp[temp_loc--]; + } + str[str_loc] = 0; // add null termination. - return max_free_block_size; + return str; } -typedef struct { - BlockWalker walker; - void* context; -} BlockWalkerWrapper; - -static bool tlsf_walker_wrapper(void* ptr, size_t size, int used, void* user) { - BlockWalkerWrapper* wrapper = (BlockWalkerWrapper*)user; - return wrapper->walker(ptr, size, used, wrapper->context); +static void print_heap_init(void) { + char tmp_str[33]; + size_t heap_start = (size_t)&__heap_start__; + size_t heap_end = (size_t)&__heap_end__; + + // {PHStart|heap_start|heap_end} + FURI_CRITICAL_ENTER(); + furi_log_puts("{PHStart|"); + ultoa(heap_start, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("|"); + ultoa(heap_end, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); } -void memmgr_heap_walk_blocks(BlockWalker walker, void* context) { - memmgr_lock(); +static void print_heap_malloc(void* ptr, size_t size) { + char tmp_str[33]; + const char* name = furi_thread_get_name(furi_thread_get_current_id()); + if(!name) { + name = ""; + } - BlockWalkerWrapper wrapper = {walker, context}; - pool_t pool = tlsf_get_pool(tlsf); - tlsf_walk_pool(pool, tlsf_walker_wrapper, &wrapper); + // {thread name|m|address|size} + FURI_CRITICAL_ENTER(); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|m|0x"); + ultoa((unsigned long)ptr, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("|"); + utoa(size, tmp_str, 10); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); +} - memmgr_unlock(); +static void print_heap_free(void* ptr) { + char tmp_str[33]; + const char* name = furi_thread_get_name(furi_thread_get_current_id()); + if(!name) { + name = ""; + } + + // {thread name|f|address} + FURI_CRITICAL_ENTER(); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|f|0x"); + ultoa((unsigned long)ptr, tmp_str, 16); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); + FURI_CRITICAL_EXIT(); } +#endif +/*-----------------------------------------------------------*/ + +void* pvPortMalloc(size_t xWantedSize) { + BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; + void* pvReturn = NULL; + size_t to_wipe = xWantedSize; -void* pvPortMalloc(size_t xSize) { - // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - memmgr_lock(); - - // allocate block - void* data = tlsf_malloc(tlsf, xSize); - if(data == NULL) { - if(xSize == 0) { - furi_crash("malloc(0)"); - } else { - furi_crash("out of memory"); +#ifdef HEAP_PRINT_DEBUG + BlockLink_t* print_heap_block = NULL; +#endif + + /* If this is the first call to malloc then the heap will require + initialisation to setup the list of free blocks. */ + if(pxEnd == NULL) { +#ifdef HEAP_PRINT_DEBUG + print_heap_init(); +#endif + + vTaskSuspendAll(); + { + prvHeapInit(); + memmgr_heap_init(); } + (void)xTaskResumeAll(); + } else { + mtCOVERAGE_TEST_MARKER(); } - // update heap usage - heap_used += tlsf_block_size(data); - heap_used += tlsf_alloc_overhead(); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; - } - - // trace allocation - memmgr_heap_trace_malloc(data, xSize); + vTaskSuspendAll(); + { + /* Check the requested block size is not so large that the top bit is + set. The top bit of the block size member of the BlockLink_t structure + is used to determine who owns the block - the application or the + kernel, so it must be free. */ + if((xWantedSize & xBlockAllocatedBit) == 0) { + /* The wanted size is increased so it can contain a BlockLink_t + structure in addition to the requested amount of bytes. */ + if(xWantedSize > 0) { + xWantedSize += xHeapStructSize; + + /* Ensure that blocks are always aligned to the required number + of bytes. */ + if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { + /* Byte alignment required. */ + xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); + configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } - memmgr_unlock(); + if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { + /* Traverse the list from the start (lowest address) block until + one of adequate size is found. */ + pxPreviousBlock = &xStart; + pxBlock = xStart.pxNextFreeBlock; + while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { + pxPreviousBlock = pxBlock; + pxBlock = pxBlock->pxNextFreeBlock; + } - // clear block content - memset(data, 0, xSize); + /* If the end marker was reached then a block of adequate size + was not found. */ + if(pxBlock != pxEnd) { + /* Return the memory space pointed to - jumping over the + BlockLink_t structure at its start. */ + pvReturn = + (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); + + /* This block is being returned for use so must be taken out + of the list of free blocks. */ + pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; + + /* If the block is larger than required it can be split into + two. */ + if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { + /* This block is to be split into two. Create a new + block following the number of bytes requested. The void + cast is used to prevent byte alignment warnings from the + compiler. */ + pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); + configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); + + /* Calculate the sizes of two blocks split from the + single block. */ + pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; + pxBlock->xBlockSize = xWantedSize; + + /* Insert the new block into the list of free blocks. */ + prvInsertBlockIntoFreeList(pxNewBlockLink); + } else { + mtCOVERAGE_TEST_MARKER(); + } - return data; -} + xFreeBytesRemaining -= pxBlock->xBlockSize; -void vPortFree(void* pv) { - // memory management in ISR is not allowed - if(FURI_IS_IRQ_MODE()) { - furi_crash("memmgt in ISR"); - } + if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) { + xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; + } else { + mtCOVERAGE_TEST_MARKER(); + } - // ignore NULL pointer - if(pv != NULL) { - memmgr_lock(); + /* The block is being returned - it is allocated and owned + by the application and has no "next" block. */ + pxBlock->xBlockSize |= xBlockAllocatedBit; + pxBlock->pxNextFreeBlock = NULL; - // get block size - size_t block_size = tlsf_block_size(pv); +#ifdef HEAP_PRINT_DEBUG + print_heap_block = pxBlock; +#endif + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + mtCOVERAGE_TEST_MARKER(); + } - // clear block content - memset(pv, 0, block_size); + traceMALLOC(pvReturn, xWantedSize); + } + (void)xTaskResumeAll(); - // update heap usage - heap_used -= block_size; - heap_used -= tlsf_alloc_overhead(); +#ifdef HEAP_PRINT_DEBUG + print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); +#endif - // free - tlsf_free(tlsf, pv); +#if(configUSE_MALLOC_FAILED_HOOK == 1) + { + if(pvReturn == NULL) { + extern void vApplicationMallocFailedHook(void); + vApplicationMallocFailedHook(); + } else { + mtCOVERAGE_TEST_MARKER(); + } + } +#endif - // trace free - memmgr_heap_trace_free(pv); + configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); - memmgr_unlock(); - } + furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); + pvReturn = memset(pvReturn, 0, to_wipe); + return pvReturn; } +/*-----------------------------------------------------------*/ + +void vPortFree(void* pv) { + uint8_t* puc = (uint8_t*)pv; + BlockLink_t* pxLink; -extern void* pvPortAllocAligned(size_t xSize, size_t xAlignment) { - // memory management in ISR is not allowed if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } - // alignment must be power of 2 - if((xAlignment & (xAlignment - 1)) != 0) { - furi_crash("invalid alignment"); - } - - memmgr_lock(); - - // allocate block - void* data = tlsf_memalign(tlsf, xAlignment, xSize); - if(data == NULL) { - if(xSize == 0) { - furi_crash("malloc_aligned(0)"); + if(pv != NULL) { + /* The memory being freed will have an BlockLink_t structure immediately + before it. */ + puc -= xHeapStructSize; + + /* This casting is to keep the compiler from issuing warnings. */ + pxLink = (void*)puc; + + /* Check the block is actually allocated. */ + configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); + configASSERT(pxLink->pxNextFreeBlock == NULL); + + if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { + if(pxLink->pxNextFreeBlock == NULL) { + /* The block is being returned to the heap - it is no longer + allocated. */ + pxLink->xBlockSize &= ~xBlockAllocatedBit; + +#ifdef HEAP_PRINT_DEBUG + print_heap_free(pxLink); +#endif + + vTaskSuspendAll(); + { + furi_assert((size_t)pv >= SRAM_BASE); + furi_assert((size_t)pv < SRAM_BASE + 1024 * 256); + furi_assert(pxLink->xBlockSize >= xHeapStructSize); + furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256); + + /* Add this block to the list of free blocks. */ + xFreeBytesRemaining += pxLink->xBlockSize; + traceFREE(pv, pxLink->xBlockSize); + memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); + prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); + } + (void)xTaskResumeAll(); + } else { + mtCOVERAGE_TEST_MARKER(); + } } else { - furi_crash("out of memory"); + mtCOVERAGE_TEST_MARKER(); } + } else { +#ifdef HEAP_PRINT_DEBUG + print_heap_free(pv); +#endif } +} +/*-----------------------------------------------------------*/ - // update heap usage - heap_used += tlsf_block_size(data); - heap_used += tlsf_alloc_overhead(); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; - } - - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - - memmgr_unlock(); +size_t xPortGetTotalHeapSize(void) { + return (size_t)&__heap_end__ - (size_t)&__heap_start__; +} +/*-----------------------------------------------------------*/ - // clear block content - memset(data, 0, xSize); +size_t xPortGetFreeHeapSize(void) { + return xFreeBytesRemaining; +} +/*-----------------------------------------------------------*/ - return data; +size_t xPortGetMinimumEverFreeHeapSize(void) { + return xMinimumEverFreeBytesRemaining; } +/*-----------------------------------------------------------*/ -extern void* pvPortRealloc(void* pv, size_t xSize) { - // realloc(ptr, 0) is equivalent to free(ptr) - if(xSize == 0) { - vPortFree(pv); - return NULL; - } +void vPortInitialiseBlocks(void) { + /* This just exists to keep the linker quiet. */ +} +/*-----------------------------------------------------------*/ - // realloc(NULL, size) is equivalent to malloc(size) - if(pv == NULL) { - return pvPortMalloc(xSize); - } +static void prvHeapInit(void) { + BlockLink_t* pxFirstFreeBlock; + uint8_t* pucAlignedHeap; + size_t uxAddress; + size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; - /* realloc things */ + /* Ensure the heap starts on a correctly aligned boundary. */ + uxAddress = (size_t)ucHeap; - // memory management in ISR is not allowed - if(FURI_IS_IRQ_MODE()) { - furi_crash("memmgt in ISR"); + if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { + uxAddress += (portBYTE_ALIGNMENT - 1); + uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); + xTotalHeapSize -= uxAddress - (size_t)ucHeap; } - memmgr_lock(); - - // trace old block as free - size_t old_size = tlsf_block_size(pv); + pucAlignedHeap = (uint8_t*)uxAddress; + + /* xStart is used to hold a pointer to the first item in the list of free + blocks. The void cast is used to prevent compiler warnings. */ + xStart.pxNextFreeBlock = (void*)pucAlignedHeap; + xStart.xBlockSize = (size_t)0; + + /* pxEnd is used to mark the end of the list of free blocks and is inserted + at the end of the heap space. */ + uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; + uxAddress -= xHeapStructSize; + uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); + pxEnd = (void*)uxAddress; + pxEnd->xBlockSize = 0; + pxEnd->pxNextFreeBlock = NULL; + + /* To start with there is a single free block that is sized to take up the + entire heap space, minus the space taken by pxEnd. */ + pxFirstFreeBlock = (void*)pucAlignedHeap; + pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; + pxFirstFreeBlock->pxNextFreeBlock = pxEnd; + + /* Only one block exists - and it covers the entire usable heap space. */ + xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; + xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; + + /* Work out the position of the top bit in a size_t variable. */ + xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); +} +/*-----------------------------------------------------------*/ - // trace free - memmgr_heap_trace_free(pv); +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { + BlockLink_t* pxIterator; + uint8_t* puc; - // reallocate block - void* data = tlsf_realloc(tlsf, pv, xSize); - if(data == NULL) { - furi_crash("out of memory"); + /* Iterate through the list until a block is found that has a higher address + than the block being inserted. */ + for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; + pxIterator = pxIterator->pxNextFreeBlock) { + /* Nothing to do here, just iterate to the right position. */ } - // update heap usage - heap_used -= old_size; - heap_used += tlsf_block_size(data); - if(heap_used > heap_max_used) { - heap_max_used = heap_used; + /* Do the block being inserted, and the block it is being inserted after + make a contiguous block of memory? */ + puc = (uint8_t*)pxIterator; + if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { + pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; + pxBlockToInsert = pxIterator; + } else { + mtCOVERAGE_TEST_MARKER(); } - // trace allocation - memmgr_heap_trace_malloc(data, xSize); - - memmgr_unlock(); - - // clear remain block content, if the new size is bigger - // can't guarantee that all data will be zeroed, cos tlsf_block_size is not always the same as xSize - if(xSize > old_size) { - memset((uint8_t*)data + old_size, 0, xSize - old_size); + /* Do the block being inserted, and the block it is being inserted before + make a contiguous block of memory? */ + puc = (uint8_t*)pxBlockToInsert; + if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { + if(pxIterator->pxNextFreeBlock != pxEnd) { + /* Form one big block from the two blocks. */ + pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; + pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; + } else { + pxBlockToInsert->pxNextFreeBlock = pxEnd; + } + } else { + pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } - return data; -} - -size_t xPortGetFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_used - tlsf_size(tlsf); -} - -size_t xPortGetTotalHeapSize(void) { - return memmgr_get_heap_size(); + /* If the block being inserted plugged a gab, so was merged with the block + before and the block after, then it's pxNextFreeBlock pointer will have + already been set, and should not be set here as that would make it point + to itself. */ + if(pxIterator != pxBlockToInsert) { + pxIterator->pxNextFreeBlock = pxBlockToInsert; + } else { + mtCOVERAGE_TEST_MARKER(); + } } - -size_t xPortGetMinimumEverFreeHeapSize(void) { - return memmgr_get_heap_size() - heap_max_used - tlsf_size(tlsf); -} \ No newline at end of file diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 2f61deb642..7d889f1520 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -40,17 +40,9 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); */ size_t memmgr_heap_get_max_free_block(void); -typedef bool (*BlockWalker)(void* pointer, size_t size, bool used, void* context); - -/** - * @brief Walk through all heap blocks - * @warning This function will lock memory manager and may cause deadlocks if any malloc/free is called inside the callback. - * Also, printf and furi_log contains malloc calls, so do not use them. - * - * @param walker - * @param context +/** Print the address and size of all free blocks to stdout */ -void memmgr_heap_walk_blocks(BlockWalker walker, void* context); +void memmgr_heap_printf_free_blocks(void); #ifdef __cplusplus } diff --git a/furi/core/thread.c b/furi/core/thread.c index c9bf79d32a..f9f73b4f75 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -276,8 +276,8 @@ void furi_thread_start(FuriThread* thread) { stack, thread, priority, - memmgr_aux_pool_alloc(sizeof(StackType_t) * stack), - memmgr_aux_pool_alloc(sizeof(StaticTask_t))); + memmgr_alloc_from_pool(sizeof(StackType_t) * stack), + memmgr_alloc_from_pool(sizeof(StaticTask_t))); } else { BaseType_t ret = xTaskCreate( furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); diff --git a/furi/flipper.c b/furi/flipper.c index 6c7b9831a4..c7ba3b4fb1 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -51,17 +51,12 @@ void flipper_init(void) { FURI_LOG_I(TAG, "Startup complete"); } -PLACE_IN_SECTION("MB_MEM2") static StaticTask_t idle_task_tcb; -PLACE_IN_SECTION("MB_MEM2") static StackType_t idle_task_stack[configIDLE_TASK_STACK_DEPTH]; -PLACE_IN_SECTION("MB_MEM2") static StaticTask_t timer_task_tcb; -PLACE_IN_SECTION("MB_MEM2") static StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; - void vApplicationGetIdleTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = &idle_task_tcb; - *stack_ptr = idle_task_stack; + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configIDLE_TASK_STACK_DEPTH); *stack_size = configIDLE_TASK_STACK_DEPTH; } @@ -69,7 +64,7 @@ void vApplicationGetTimerTaskMemory( StaticTask_t** tcb_ptr, StackType_t** stack_ptr, uint32_t* stack_size) { - *tcb_ptr = &timer_task_tcb; - *stack_ptr = timer_task_stack; + *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); + *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; } \ No newline at end of file diff --git a/lib/SConscript b/lib/SConscript index 29c48de6d9..8125739325 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -13,7 +13,6 @@ env.Append( libs = env.BuildModules( [ - "tlsf", "mlib", "stm32wb", "freertos", diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 3e10ae3fe3..398f25209a 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -466,7 +466,7 @@ static bool elf_load_section_data(ELFFile* elf, ELFSection* section, Elf32_Shdr* return true; } - section->data = aligned_alloc(section_header->sh_addralign, section_header->sh_size); + section->data = aligned_malloc(section_header->sh_size, section_header->sh_addralign); section->size = section_header->sh_size; if(section_header->sh_type == SHT_NOBITS) { @@ -718,7 +718,7 @@ static bool elf_relocate_fast(ELFFile* elf, ELFSection* s) { } } - free(s->fast_rel->data); + aligned_free(s->fast_rel->data); free(s->fast_rel); s->fast_rel = NULL; @@ -785,10 +785,10 @@ void elf_file_free(ELFFile* elf) { ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); if(itref->value.data) { - free(itref->value.data); + aligned_free(itref->value.data); } if(itref->value.fast_rel) { - free(itref->value.fast_rel->data); + aligned_free(itref->value.fast_rel->data); free(itref->value.fast_rel); } free((void*)itref->key); diff --git a/lib/tlsf b/lib/tlsf deleted file mode 160000 index 8fc595fe22..0000000000 --- a/lib/tlsf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8fc595fe223cd0b3b5d7b29eb86825e4bd38e6e8 diff --git a/lib/tlsf.scons b/lib/tlsf.scons deleted file mode 100644 index 0a8419dbdc..0000000000 --- a/lib/tlsf.scons +++ /dev/null @@ -1,21 +0,0 @@ -Import("env") - -env.Append( - CPPPATH=[ - "#/lib/tlsf", - ], -) - - -libenv = env.Clone(FW_LIB_NAME="tlsf") -libenv.ApplyLibFlags() - -libenv.Append( - CPPDEFINES=[], -) - -sources = [File("tlsf/tlsf.c")] - -lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) -libenv.Install("${LIB_DIST_DIR}", lib) -Return("lib") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ef2d2fcb6a..492539d465 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,62.0,, +Version,+,61.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -515,7 +515,9 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,+,aligned_alloc,void*,"size_t, size_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -1982,8 +1984,7 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,+,memmgr_aux_pool_alloc,void*,size_t -Function,+,memmgr_aux_pool_get_free,size_t, +Function,-,memmgr_alloc_from_pool,void*,size_t Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -1991,7 +1992,8 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f18/target.json b/targets/f18/target.json index a61c1373e1..43e9254cd4 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -13,7 +13,6 @@ "print", "flipper18", "furi", - "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -69,4 +68,4 @@ "ibutton", "infrared" ] -} \ No newline at end of file +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1f959f0c70..e209023b5c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,62.0,, +Version,+,61.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -586,7 +586,9 @@ Function,-,acosh,double,double Function,-,acoshf,float,float Function,-,acoshl,long double,long double Function,-,acosl,long double,long double -Function,+,aligned_alloc,void*,"size_t, size_t" +Function,-,aligned_alloc,void*,"size_t, size_t" +Function,+,aligned_free,void,void* +Function,+,aligned_malloc,void*,"size_t, size_t" Function,-,arc4random,__uint32_t, Function,-,arc4random_buf,void,"void*, size_t" Function,-,arc4random_uniform,__uint32_t,__uint32_t @@ -2392,8 +2394,7 @@ Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" Function,+,memcpy,void*,"void*, const void*, size_t" Function,-,memmem,void*,"const void*, size_t, const void*, size_t" -Function,+,memmgr_aux_pool_alloc,void*,size_t -Function,+,memmgr_aux_pool_get_free,size_t, +Function,-,memmgr_alloc_from_pool,void*,size_t Function,+,memmgr_get_free_heap,size_t, Function,+,memmgr_get_minimum_free_heap,size_t, Function,+,memmgr_get_total_heap,size_t, @@ -2401,7 +2402,8 @@ Function,+,memmgr_heap_disable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_enable_thread_trace,void,FuriThreadId Function,+,memmgr_heap_get_max_free_block,size_t, Function,+,memmgr_heap_get_thread_memory,size_t,FuriThreadId -Function,+,memmgr_heap_walk_blocks,void,"BlockWalker, void*" +Function,+,memmgr_heap_printf_free_blocks,void, +Function,-,memmgr_pool_get_free,size_t, Function,-,memmgr_pool_get_max_block,size_t, Function,+,memmove,void*,"void*, const void*, size_t" Function,-,mempcpy,void*,"void*, const void*, size_t" diff --git a/targets/f7/fatfs/sector_cache.c b/targets/f7/fatfs/sector_cache.c index df86cb7f15..319dd21732 100644 --- a/targets/f7/fatfs/sector_cache.c +++ b/targets/f7/fatfs/sector_cache.c @@ -19,7 +19,7 @@ static SectorCache* cache = NULL; void sector_cache_init(void) { if(cache == NULL) { - cache = memmgr_aux_pool_alloc(sizeof(SectorCache)); + cache = memmgr_alloc_from_pool(sizeof(SectorCache)); } if(cache != NULL) { diff --git a/targets/f7/src/update.c b/targets/f7/src/update.c index 261adb5caf..e6cb4aabee 100644 --- a/targets/f7/src/update.c +++ b/targets/f7/src/update.c @@ -78,21 +78,21 @@ static bool flipper_update_load_stage(const FuriString* work_dir, UpdateManifest furi_string_free(loader_img_path); void* img = malloc(stat.fsize); - uint32_t read_total = 0; - uint16_t read_current = 0; + uint32_t bytes_read = 0; const uint16_t MAX_READ = 0xFFFF; uint32_t crc = 0; do { - if(f_read(&file, img + read_total, MAX_READ, &read_current) != FR_OK) { //-V769 + uint16_t size_read = 0; + if(f_read(&file, img + bytes_read, MAX_READ, &size_read) != FR_OK) { //-V769 break; } - crc = crc32_calc_buffer(crc, img + read_total, read_current); - read_total += read_current; - } while(read_current == MAX_READ); + crc = crc32_calc_buffer(crc, img + bytes_read, size_read); + bytes_read += size_read; + } while(bytes_read == MAX_READ); do { - if((read_total != stat.fsize) || (crc != manifest->staged_loader_crc)) { + if((bytes_read != stat.fsize) || (crc != manifest->staged_loader_crc)) { break; } diff --git a/targets/f7/target.json b/targets/f7/target.json index caa3f58eec..25872198bf 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -22,7 +22,6 @@ "print", "flipper7", "furi", - "tlsf", "freertos", "stm32wb", "hwdrivers", @@ -56,4 +55,4 @@ "bit_lib", "datetime" ] -} \ No newline at end of file +} From 63403bbae2f46e41d9d3b136e228e0b602e1461e Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 16 May 2024 18:55:08 +0100 Subject: [PATCH 24/48] JS: Add submenu module (#3601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JS: Add submenu module * Using view_holder instead of view_dispatcher Co-authored-by: nminaylov Co-authored-by: あく --- applications/services/gui/application.fam | 1 + applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/submenu.js | 11 ++ .../system/js_app/modules/js_submenu.c | 148 ++++++++++++++++++ targets/f18/api_symbols.csv | 13 +- targets/f7/api_symbols.csv | 13 +- 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/submenu.js create mode 100644 applications/system/js_app/modules/js_submenu.c diff --git a/applications/services/gui/application.fam b/applications/services/gui/application.fam index 869d964dd0..b7dd18baa1 100644 --- a/applications/services/gui/application.fam +++ b/applications/services/gui/application.fam @@ -16,6 +16,7 @@ App( "elements.h", "view_dispatcher.h", "view_stack.h", + "view_holder.h", "modules/button_menu.h", "modules/byte_input.h", "modules/popup.h", diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 114bec55f8..a955ef355e 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -46,3 +46,11 @@ App( requires=["js_app"], sources=["modules/js_serial.c"], ) + +App( + appid="js_submenu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_submenu_ep", + requires=["js_app"], + sources=["modules/js_submenu.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js new file mode 100644 index 0000000000..2455513093 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/submenu.js @@ -0,0 +1,11 @@ +let submenu = require("submenu"); + +submenu.addItem("Item 1", 0); +submenu.addItem("Item 2", 1); +submenu.addItem("Item 3", 2); + +submenu.setHeader("Select an option:"); + +let result = submenu.show(); +// Returns undefined when pressing back +print("Result:", result); diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c new file mode 100644 index 0000000000..058b32fd09 --- /dev/null +++ b/applications/system/js_app/modules/js_submenu.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include "../js_modules.h" + +typedef struct { + Submenu* submenu; + ViewHolder* view_holder; + FuriApiLock lock; + uint32_t result; + bool accepted; +} JsSubmenuInst; + +static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst); + furi_assert(submenu); + return submenu; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void submenu_callback(void* context, uint32_t id) { + JsSubmenuInst* submenu = context; + submenu->result = id; + submenu->accepted = true; + api_lock_unlock(submenu->lock); +} + +static void submenu_exit(void* context) { + JsSubmenuInst* submenu = context; + submenu->result = 0; + submenu->accepted = false; + api_lock_unlock(submenu->lock); +} + +static void js_submenu_add_item(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + mjs_val_t label_arg = mjs_arg(mjs, 0); + const char* label = mjs_get_string(mjs, &label_arg, NULL); + if(!label) { + ret_bad_args(mjs, "Label must be a string"); + return; + } + + mjs_val_t id_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(id_arg)) { + ret_bad_args(mjs, "Id must be a number"); + return; + } + int32_t id = mjs_get_int32(mjs, id_arg); + + submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_set_header(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t header_arg = mjs_arg(mjs, 0); + const char* header = mjs_get_string(mjs, &header_arg, NULL); + if(!header) { + ret_bad_args(mjs, "Header must be a string"); + return; + } + + submenu_set_header(submenu->submenu, header); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_show(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + submenu->lock = api_lock_alloc_locked(); + Gui* gui = furi_record_open(RECORD_GUI); + submenu->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(submenu->view_holder, gui); + view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); + + view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); + view_holder_start(submenu->view_holder); + api_lock_wait_unlock(submenu->lock); + + view_holder_stop(submenu->view_holder); + view_holder_free(submenu->view_holder); + furi_record_close(RECORD_GUI); + api_lock_free(submenu->lock); + + submenu_reset(submenu->submenu); + if(submenu->accepted) { + mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } +} + +static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { + JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); + mjs_val_t submenu_obj = mjs_mk_object(mjs); + mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); + mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); + mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); + mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); + submenu->submenu = submenu_alloc(); + *object = submenu_obj; + return submenu; +} + +static void js_submenu_destroy(void* inst) { + JsSubmenuInst* submenu = inst; + submenu_free(submenu->submenu); + free(submenu); +} + +static const JsModuleDescriptor js_submenu_desc = { + "submenu", + js_submenu_create, + js_submenu_destroy, +}; + +static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_submenu_desc, +}; + +const FlipperAppPluginDescriptor* js_submenu_ep(void) { + return &submenu_plugin_descriptor; +} diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 492539d465..ce7ad25369 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,61.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -27,6 +27,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,, Header,+,applications/services/gui/modules/widget.h,, Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_holder.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/firmware_api/firmware_api.h,, @@ -2683,6 +2684,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" Function,+,view_free,void,View* Function,+,view_free_model,void,View* Function,+,view_get_model,void*,View* +Function,+,view_holder_alloc,ViewHolder*, +Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*" +Function,+,view_holder_free,void,ViewHolder* +Function,+,view_holder_get_free_context,void*,ViewHolder* +Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*" +Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*" +Function,+,view_holder_set_view,void,"ViewHolder*, View*" +Function,+,view_holder_start,void,ViewHolder* +Function,+,view_holder_stop,void,ViewHolder* +Function,+,view_holder_update,void,"View*, void*" Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e209023b5c..5a44dbc6fd 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.3,, +Version,+,61.4,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -28,6 +28,7 @@ Header,+,applications/services/gui/modules/variable_item_list.h,, Header,+,applications/services/gui/modules/widget.h,, Header,+,applications/services/gui/modules/widget_elements/widget_element.h,, Header,+,applications/services/gui/view_dispatcher.h,, +Header,+,applications/services/gui/view_holder.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/firmware_api/firmware_api.h,, @@ -3483,6 +3484,16 @@ Function,+,view_dispatcher_switch_to_view,void,"ViewDispatcher*, uint32_t" Function,+,view_free,void,View* Function,+,view_free_model,void,View* Function,+,view_get_model,void*,View* +Function,+,view_holder_alloc,ViewHolder*, +Function,+,view_holder_attach_to_gui,void,"ViewHolder*, Gui*" +Function,+,view_holder_free,void,ViewHolder* +Function,+,view_holder_get_free_context,void*,ViewHolder* +Function,+,view_holder_set_back_callback,void,"ViewHolder*, BackCallback, void*" +Function,+,view_holder_set_free_callback,void,"ViewHolder*, FreeCallback, void*" +Function,+,view_holder_set_view,void,"ViewHolder*, View*" +Function,+,view_holder_start,void,ViewHolder* +Function,+,view_holder_stop,void,ViewHolder* +Function,+,view_holder_update,void,"View*, void*" Function,+,view_port_alloc,ViewPort*, Function,+,view_port_draw_callback_set,void,"ViewPort*, ViewPortDrawCallback, void*" Function,+,view_port_enabled_set,void,"ViewPort*, _Bool" From 603a86dbe6b59fd475863cf40b96e981fdfd535b Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 17 May 2024 20:52:42 +0900 Subject: [PATCH 25/48] [FL-3797] Settings refactor fixes (#3654) --- .../scenes/desktop_settings_scene_pin_disable.c | 2 +- .../scenes/desktop_settings_scene_pin_menu.c | 2 +- .../scenes/storage_settings_scene_formatting.c | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) 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 c3e22805d6..2f21fd999f 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 @@ -25,7 +25,7 @@ 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_119x62); - popup_set_header(app->popup, "PIN\nDeleted!", 100, 0, AlignCenter, AlignTop); + popup_set_header(app->popup, "Removed", 100, 10, AlignCenter, AlignTop); 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/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 9fdd688965..950be4c5a3 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -37,7 +37,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, - "Disable", + "Remove PIN", SCENE_EVENT_DISABLE_PIN, desktop_settings_scene_pin_menu_submenu_callback, app); 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 2fb232f14a..6a958610e5 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -51,15 +51,15 @@ 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, 83, 22, &I_WarningDolphinFlip_45x42); - dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog_ex, 48, 6, &I_DolphinDone_80x58); + dialog_ex_set_header(dialog_ex, "Formatted", 5, 10, AlignLeft, AlignTop); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); notification_message(notification, &sequence_set_green_255); notification_message(notification, &sequence_success); furi_record_close(RECORD_NOTIFICATION); } - dialog_ex_set_center_button_text(dialog_ex, "OK"); + dialog_ex_set_left_button_text(dialog_ex, "Finish"); } bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { @@ -68,7 +68,7 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultCenter: + case DialogExResultLeft: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; From 217bfac2fc8d281f6ba496e6a42786fef38ec606 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 17 May 2024 12:58:32 +0100 Subject: [PATCH 26/48] NFC: add Slix capabilities (#3652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * iso15693 listener: fix inventory cmd and buffer overflow * iso15 listener: fix read multiple blocks command * slix: print password * slix: add capabilities field * slix listener: skip password validation for special capability * slix: fix capability name * slix: add capabilities handler to verify and reset * nfc test: introduce slix tests * fbt: change toolchain back to 33 version * slix: fix saving capablities comment * unit tests: add slix files to resources * slix: fix set passwrd signature * nfc tests: add set correct password test * nfc test: complete slix password tests * nfc test: add slix file test * nfc test: handle errors in worker callback * iso15693_3: code clean up * iso15693_listener: fix incorrect afi handling * slix: chage capabilities format to one word camel case * unit tests: update nfc files with new slix format Co-authored-by: あく --- applications/debug/unit_tests/nfc/nfc_test.c | 145 ++++++++++++++++++ .../debug/unit_tests/nfc/nfc_transport.c | 5 +- .../nfc/Slix_cap_accept_all_pass.nfc | 41 +++++ .../unit_tests/nfc/Slix_cap_default.nfc | 41 +++++ .../unit_tests/nfc/Slix_cap_missed.nfc | 39 +++++ .../iso15693_3/iso15693_3_listener.c | 3 +- .../iso15693_3/iso15693_3_listener_i.c | 17 +- lib/nfc/protocols/slix/slix.c | 64 +++++++- lib/nfc/protocols/slix/slix.h | 8 + lib/nfc/protocols/slix/slix_listener_i.c | 7 + lib/nfc/protocols/slix/slix_poller.c | 6 +- lib/nfc/protocols/slix/slix_poller.h | 12 +- lib/nfc/protocols/slix/slix_poller_i.c | 11 +- 13 files changed, 375 insertions(+), 24 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc create mode 100644 applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc create mode 100644 applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index c6304d53ca..8bb88df9ab 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -13,6 +13,12 @@ #include #include #include +#include +#include +#include +#include +#include + #include #include @@ -42,6 +48,19 @@ typedef struct { FuriThreadId thread_id; } NfcTestMfClassicSendFrameTest; +typedef enum { + NfcTestSlixPollerSetPasswordStateGetRandomNumber, + NfcTestSlixPollerSetPasswordStateSetPassword, +} NfcTestSlixPollerSetPasswordState; + +typedef struct { + FuriThreadId thread_id; + NfcTestSlixPollerSetPasswordState state; + SlixRandomNumber random_number; + SlixPassword password; + SlixError error; +} NfcTestSlixPollerSetPasswordContext; + typedef struct { Storage* storage; } NfcTest; @@ -627,6 +646,127 @@ MU_TEST(mf_classic_dict_test) { "Remove test dict failed"); } +MU_TEST(slix_file_with_capabilities_test) { + NfcDevice* nfc_device_missed_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_missed_cap, EXT_PATH("unit_tests/nfc/Slix_cap_missed.nfc")), + "nfc_device_load() failed\r\n"); + + NfcDevice* nfc_device_default_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_default_cap, EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc")), + "nfc_device_load() failed\r\n"); + + mu_assert( + nfc_device_is_equal(nfc_device_missed_cap, nfc_device_default_cap), + "nfc_device_is_equal() failed\r\n"); + + nfc_device_free(nfc_device_default_cap); + nfc_device_free(nfc_device_missed_cap); +} + +NfcCommand slix_poller_set_password_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + Iso15693_3PollerEvent* iso15_event = event.parent_event_data; + SlixPoller* poller = event.poller; + NfcTestSlixPollerSetPasswordContext* slix_ctx = context; + + if(iso15_event->type == Iso15693_3PollerEventTypeReady) { + iso15693_3_copy( + poller->data->iso15693_3_data, iso15693_3_poller_get_data(poller->iso15693_3_poller)); + + if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateGetRandomNumber) { + slix_ctx->error = slix_poller_get_random_number(poller, &slix_ctx->random_number); + if(slix_ctx->error != SlixErrorNone) { + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } else { + slix_ctx->state = NfcTestSlixPollerSetPasswordStateSetPassword; + } + } else if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateSetPassword) { + slix_ctx->error = slix_poller_set_password( + poller, SlixPasswordTypeRead, slix_ctx->password, slix_ctx->random_number); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + } else { + slix_ctx->error = slix_process_iso15693_3_error(iso15_event->data->error); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + + return command; +} + +static void slix_set_password_test(const char* file_path, SlixPassword pass, bool correct_pass) { + FURI_LOG_I(TAG, "Testing file: %s", file_path); + + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + mu_assert(nfc_device_load(nfc_device, file_path), "nfc_device_load() failed\r\n"); + + const SlixData* slix_data = nfc_device_get_data(nfc_device, NfcProtocolSlix); + NfcListener* slix_listener = nfc_listener_alloc(listener, NfcProtocolSlix, slix_data); + nfc_listener_start(slix_listener, NULL, NULL); + + SlixCapabilities slix_capabilities = slix_data->capabilities; + + NfcPoller* slix_poller = nfc_poller_alloc(poller, NfcProtocolSlix); + + NfcTestSlixPollerSetPasswordContext slix_poller_context = { + .thread_id = furi_thread_get_current_id(), + .state = NfcTestSlixPollerSetPasswordStateGetRandomNumber, + .password = pass, + .error = SlixErrorNone, + }; + + nfc_poller_start_ex(slix_poller, slix_poller_set_password_callback, &slix_poller_context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag\r\n"); + + nfc_poller_stop(slix_poller); + nfc_poller_free(slix_poller); + nfc_listener_stop(slix_listener); + nfc_listener_free(slix_listener); + + mu_assert( + slix_poller_context.state == NfcTestSlixPollerSetPasswordStateSetPassword, + "Poller failed before setting password\r\n"); + + if((slix_capabilities == SlixCapabilitiesAcceptAllPasswords) || (correct_pass)) { + mu_assert(slix_poller_context.error == SlixErrorNone, "Failed to set password\r\n"); + } else { + mu_assert( + slix_poller_context.error == SlixErrorTimeout, + "Must have received SlixErrorTimeout\r\n"); + } + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + +MU_TEST(slix_set_password_default_cap_correct_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x00000000, true); +} + +MU_TEST(slix_set_password_default_cap_incorrect_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x12341234, false); +} + +MU_TEST(slix_set_password_access_all_passwords_cap) { + slix_set_password_test( + EXT_PATH("unit_tests/nfc/Slix_cap_accept_all_pass.nfc"), 0x12341234, false); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); @@ -668,6 +808,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); + MU_RUN_TEST(slix_file_with_capabilities_test); + MU_RUN_TEST(slix_set_password_default_cap_correct_pass); + MU_RUN_TEST(slix_set_password_default_cap_incorrect_pass); + MU_RUN_TEST(slix_set_password_access_all_passwords_cap); + nfc_test_free(); } diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index 6886ef66da..df0e009e79 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -55,6 +55,7 @@ struct Nfc { Iso14443_3aColResStatus col_res_status; Iso14443_3aColResData col_res_data; + bool software_col_res_required; NfcEventCallback callback; void* context; @@ -170,6 +171,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data( furi_check(atqa); nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); + instance->software_col_res_required = true; return NfcErrorNone; } @@ -275,7 +277,8 @@ static int32_t nfc_worker_listener(void* context) { } else if(message.type == NfcMessageTypeTx) { nfc_test_print( NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits); - if(instance->col_res_status != Iso14443_3aColResStatusDone) { + if(instance->software_col_res_required && + (instance->col_res_status != Iso14443_3aColResStatusDone)) { nfc_worker_listener_pass_col_res( instance, message.data.data, message.data.data_bits); } else { diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc new file mode 100644 index 0000000000..0a8db9ae97 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: AcceptAllPasswords +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc new file mode 100644 index 0000000000..d1af957dc3 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: Default +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc new file mode 100644 index 0000000000..35650b0e82 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc @@ -0,0 +1,39 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c index 84e7508585..151e4ae4a6 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -8,7 +8,7 @@ #define TAG "Iso15693_3Listener" -#define ISO15693_3_LISTENER_BUFFER_SIZE (64U) +#define ISO15693_3_LISTENER_BUFFER_SIZE (256U) Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { furi_assert(nfc); @@ -67,6 +67,7 @@ NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypeRxEnd) { BitBuffer* rx_buffer = nfc_event->data.buffer; + bit_buffer_reset(instance->tx_buffer); if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { iso13239_crc_trim(rx_buffer); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c index a8dec7ae33..6132fbf47f 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -64,7 +64,9 @@ static Iso15693_3Error iso15693_3_listener_inventory_handler( if(afi_flag) { const uint8_t afi = *data++; // When AFI flag is set, ignore non-matching requests - if(afi != instance->data->system_info.afi) break; + if(afi != 0) { + if(afi != instance->data->system_info.afi) break; + } } const uint8_t mask_len = *data++; @@ -260,16 +262,9 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( } const uint32_t block_index_start = request->first_block_num; - const uint32_t block_index_end = block_index_start + request->block_count; - - const uint32_t block_count = request->block_count + 1; - const uint32_t block_count_max = instance->data->system_info.block_count; - const uint32_t block_count_available = block_count_max - block_index_start; - - if(block_count > block_count_available) { - error = Iso15693_3ErrorInternal; - break; - } + const uint32_t block_index_end = + MIN((block_index_start + request->block_count + 1), + ((uint32_t)instance->data->system_info.block_count - 1)); error = iso15693_3_listener_extension_handler( instance, diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 533ecff741..f6ce885d4b 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -14,6 +14,7 @@ #define SLIX_TYPE_INDICATOR_SLIX (0x02U) #define SLIX_TYPE_INDICATOR_SLIX2 (0x01U) +#define SLIX_CAPABILITIES_KEY "Capabilities" #define SLIX_PASSWORD_READ_KEY "Password Read" #define SLIX_PASSWORD_WRITE_KEY "Password Write" #define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy" @@ -69,6 +70,11 @@ static const SlixTypeFeatures slix_type_features[] = { [SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2, }; +static const char* slix_capabilities_names[SlixCapabilitiesCount] = { + [SlixCapabilitiesDefault] = "Default", + [SlixCapabilitiesAcceptAllPasswords] = "AcceptAllPasswords", +}; + typedef struct { const char* key; SlixTypeFeatures feature_flag; @@ -110,6 +116,7 @@ void slix_reset(SlixData* data) { furi_check(data); iso15693_3_reset(data->iso15693_3_data); + data->capabilities = SlixCapabilitiesDefault; slix_password_set_defaults(data->passwords); memset(&data->system_info, 0, sizeof(SlixSystemInfo)); @@ -123,6 +130,7 @@ void slix_copy(SlixData* data, const SlixData* other) { furi_check(other); iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data); + data->capabilities = other->capabilities; memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount); memcpy(data->signature, other->signature, sizeof(SlixSignature)); @@ -138,6 +146,30 @@ bool slix_verify(SlixData* data, const FuriString* device_type) { return false; } +static bool slix_load_capabilities(SlixData* data, FlipperFormat* ff) { + bool capabilities_loaded = false; + FuriString* capabilities_str = furi_string_alloc(); + + if(!flipper_format_read_string(ff, SLIX_CAPABILITIES_KEY, capabilities_str)) { + if(flipper_format_rewind(ff)) { + data->capabilities = SlixCapabilitiesDefault; + capabilities_loaded = true; + } + } else { + for(size_t i = 0; i < COUNT_OF(slix_capabilities_names); i++) { + if(furi_string_cmp_str(capabilities_str, slix_capabilities_names[i]) == 0) { + data->capabilities = i; + capabilities_loaded = true; + break; + } + } + } + + furi_string_free(capabilities_str); + + return capabilities_loaded; +} + static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -164,13 +196,14 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { furi_check(ff); bool loaded = false; - do { if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break; const SlixType slix_type = slix_get_type(data); if(slix_type >= SlixTypeCount) break; + if(!slix_load_capabilities(data, ff)) break; + if(!slix_load_passwords(data->passwords, slix_type, ff)) break; if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { @@ -220,6 +253,33 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { return loaded; } +static bool slix_save_capabilities(const SlixData* data, FlipperFormat* ff) { + bool save_success = false; + + FuriString* tmp_str = furi_string_alloc(); + do { + furi_string_set_str( + tmp_str, "SLIX capabilities field affects emulation modes. Possible options: "); + for(size_t i = 0; i < SlixCapabilitiesCount; i++) { + furi_string_cat_str(tmp_str, slix_capabilities_names[i]); + if(i < SlixCapabilitiesCount - 1) { + furi_string_cat(tmp_str, ", "); + } + } + if(!flipper_format_write_comment_cstr(ff, furi_string_get_cstr(tmp_str))) break; + + if(!flipper_format_write_string_cstr( + ff, SLIX_CAPABILITIES_KEY, slix_capabilities_names[data->capabilities])) + break; + + save_success = true; + } while(false); + + furi_string_free(tmp_str); + + return save_success; +} + static bool slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -251,6 +311,8 @@ bool slix_save(const SlixData* data, FlipperFormat* ff) { if(!iso15693_3_save(data->iso15693_3_data, ff)) break; if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break; + if(!slix_save_capabilities(data, ff)) break; + if(!flipper_format_write_comment_cstr( ff, "Passwords are optional. If a password is omitted, a default value will be used")) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index 2de26847a0..cc2390c6ee 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -91,12 +91,20 @@ typedef struct { SlixLockBits lock_bits; } SlixSystemInfo; +typedef enum { + SlixCapabilitiesDefault, + SlixCapabilitiesAcceptAllPasswords, + + SlixCapabilitiesCount, +} SlixCapabilities; + typedef struct { Iso15693_3Data* iso15693_3_data; SlixSystemInfo system_info; SlixSignature signature; SlixPassword passwords[SlixPasswordTypeCount]; SlixPrivacy privacy; + SlixCapabilities capabilities; } SlixData; SlixData* slix_alloc(void); diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index 15ab2cd3c8..66c4241cb8 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -54,6 +54,13 @@ static SlixError slix_listener_set_password( } SlixListenerSessionState* session_state = &instance->session_state; + + // With AcceptAllPassword capability set skip password validation + if(instance->data->capabilities == SlixCapabilitiesAcceptAllPasswords) { + session_state->password_match[password_type] = true; + break; + } + session_state->password_match[password_type] = (password == slix_get_password(slix_data, password_type)); diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 3c9a7cce4b..aeb1180cfd 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -114,7 +114,8 @@ static NfcCommand slix_poller_handler_check_privacy_password(SlixPoller* instanc break; } - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; @@ -145,7 +146,8 @@ static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { instance->error = slix_poller_get_random_number(instance, &instance->random_number); if(instance->error != SlixErrorNone) break; - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 4ea7f880d7..e78f7882a8 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -107,12 +107,16 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* * Must ONLY be used inside the callback function. * * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[out] type SlixPasswordType instance. - * @param[out] password SlixPassword instance. + * @param[in] type SlixPasswordType instance. + * @param[in] password SlixPassword instance. + * @param[in] random_number SlixRandomNumber instance. * @return SlixErrorNone on success, an error code on failure. */ -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 9b0b5ec553..ee6912cc4a 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -92,8 +92,11 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* return error; } -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number) { furi_assert(instance); bool skip_uid = (type == SlixPasswordTypePrivacy); @@ -102,8 +105,8 @@ SlixError uint8_t password_type = (0x01 << type); bit_buffer_append_byte(instance->tx_buffer, password_type); - uint8_t rn_l = instance->random_number >> 8; - uint8_t rn_h = instance->random_number; + uint8_t rn_l = random_number >> 8; + uint8_t rn_h = random_number; uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l; uint32_t xored_password = double_rand_num ^ password; uint8_t xored_password_arr[4] = {}; From c673b53e21ea6e8ac57e6cf9823929521d0ddba7 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 17 May 2024 17:45:40 +0100 Subject: [PATCH 27/48] JS: Add math module (#3598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JS: Add math module * Double constants * Error on argument type mismatch * Fix missing returns * Using sin, exp from c library * asin, acos, pow, sqrt too * Js: tests for math module and various fixes. Co-authored-by: あく Co-authored-by: nminaylov --- applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/math.js | 69 ++++ applications/system/js_app/js_thread.c | 2 +- applications/system/js_app/modules/js_math.c | 354 ++++++++++++++++++ lib/mjs/mjs_core.c | 2 +- 5 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/math.js create mode 100644 applications/system/js_app/modules/js_math.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index a955ef355e..920e888cc2 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -54,3 +54,11 @@ App( requires=["js_app"], sources=["modules/js_submenu.c"], ) + +App( + appid="js_math", + apptype=FlipperAppType.PLUGIN, + entry_point="js_math_ep", + requires=["js_app"], + sources=["modules/js_math.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js new file mode 100644 index 0000000000..c5a0bf18d0 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -0,0 +1,69 @@ +let math = require("math"); + +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); + +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 759d63b0e3..78b6f6ff47 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -285,7 +285,7 @@ static int32_t js_thread(void* arg) { } const char* stack_trace = mjs_get_stack_trace(mjs); if(stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); + FURI_LOG_E(TAG, "Stack trace:\r\n%s", stack_trace); if(worker->app_callback) { worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); } diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c new file mode 100644 index 0000000000..7661568186 --- /dev/null +++ b/applications/system/js_app/modules/js_math.c @@ -0,0 +1,354 @@ +#include "../js_modules.h" +#include "furi_hal_random.h" +#include + +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_args(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } + return true; +} + +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; + } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + +void js_math_abs(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); +} + +void js_math_acos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); +} + +void js_math_acosh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); +} + +void js_math_asin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); +} + +void js_math_asinh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); +} + +void js_math_atan(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); +} + +void js_math_atan2(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); +} + +void js_math_atanh(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); +} + +void js_math_cbrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); +} + +void js_math_ceil(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); +} + +void js_math_clz32(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); + int count = 0; + while(x) { + x >>= 1; + count++; + } + + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); +} + +void js_math_cos(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); +} + +void js_math_exp(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); +} + +void js_math_floor(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); +} + +void js_math_log(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x <= 0) { + ret_bad_args(mjs, "Invalid input value for math.log"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); +} + +void js_math_max(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); +} + +void js_math_min(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); +} + +void js_math_pow(struct mjs* mjs) { + if(!check_args(mjs, 2)) { + return; + } + + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); +} + +void js_math_random(struct mjs* mjs) { + if(!check_args(mjs, 0)) { + return; + } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe + const uint32_t random_val = furi_hal_random_get(); + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + + mjs_return(mjs, mjs_mk_number(mjs, rnd)); +} + +void js_math_sign(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return( + mjs, + mjs_mk_number(mjs, x == (double)0. ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); +} + +void js_math_sin(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); +} + +void js_math_sqrt(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; + } + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); +} + +void js_math_trunc(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); +} + +static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); + mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); + mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); + mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); + mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin)); + mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh)); + mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan)); + mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2)); + mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh)); + mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt)); + mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil)); + mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32)); + mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos)); + mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp)); + mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor)); + mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log)); + mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max)); + mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min)); + mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow)); + mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random)); + mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign)); + mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin)); + mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt)); + mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); + mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); + mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); + *object = math_obj; + return (void*)1; +} + +static const JsModuleDescriptor js_math_desc = { + "math", + js_math_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_math_desc, +}; + +const FlipperAppPluginDescriptor* js_math_ep(void) { + return &plugin_descriptor; +} diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index aae196599f..bcdcb364ab 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -280,7 +280,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); int line_no = mjs_get_lineno_by_offset(mjs, offset); char* new_line = NULL; - const char* fmt = "at %s:%d\n"; + const char* fmt = "\tat %s:%d\r\n"; if(filename == NULL) { // fprintf( // stderr, From 0d456aa5505ca7361aba50011db30ffaf10633e2 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 17 May 2024 18:43:52 +0100 Subject: [PATCH 28/48] JS: Add textbox module (#3597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JS: Add textbox module * Using view_holder instead of view_dispatcher, more checks in js_textbox_show * API version sync * Rename emptyText() to clearText() * Keeping view_holder allocated for thread sefety * Js: proper comparision with 0 in js_math_sign * Js: add comments and fix condition race in textbox Co-authored-by: あく Co-authored-by: nminaylov --- applications/system/js_app/application.fam | 8 + .../js_app/examples/apps/Scripts/textbox.js | 30 +++ applications/system/js_app/modules/js_math.c | 3 +- .../system/js_app/modules/js_textbox.c | 220 ++++++++++++++++++ 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/textbox.js create mode 100644 applications/system/js_app/modules/js_textbox.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 920e888cc2..a7ae5c7c75 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -62,3 +62,11 @@ App( requires=["js_app"], sources=["modules/js_math.c"], ) + +App( + appid="js_textbox", + apptype=FlipperAppType.PLUGIN, + entry_point="js_textbox_ep", + requires=["js_app"], + sources=["modules/js_textbox.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js new file mode 100644 index 0000000000..6caf372347 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -0,0 +1,30 @@ +let textbox = require("textbox"); + +// You should set config before adding text +// Focus (start / end), Font (text / hex) +textbox.setConfig("end", "text"); + +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); + +// Add default text +textbox.addText("Example dynamic updating textbox\n"); + +// Non-blocking, can keep updating text after, can close in JS or in GUI +textbox.show(); + +let i = 0; +while (textbox.isOpen() && i < 20) { + print("console", i++); + + // Add text to textbox buffer + textbox.addText("textbox " + to_string(i) + "\n"); + + delay(500); +} + +// If not closed by user (instead i < 20 is false above), close forcefully +if (textbox.isOpen()) { + textbox.close(); +} diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index 7661568186..b4c1cdca23 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -267,7 +267,8 @@ void js_math_sign(struct mjs* mjs) { mjs_return( mjs, - mjs_mk_number(mjs, x == (double)0. ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); } void js_math_sin(struct mjs* mjs) { diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c new file mode 100644 index 0000000000..d15cd2779d --- /dev/null +++ b/applications/system/js_app/modules/js_textbox.c @@ -0,0 +1,220 @@ +#include +#include +#include "../js_modules.h" + +typedef struct { + TextBox* text_box; + ViewHolder* view_holder; + FuriString* text; + bool is_shown; +} JsTextboxInst; + +static JsTextboxInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); + furi_assert(textbox); + return textbox; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_textbox_set_config(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + TextBoxFocus set_focus = TextBoxFocusStart; + mjs_val_t focus_arg = mjs_arg(mjs, 0); + const char* focus = mjs_get_string(mjs, &focus_arg, NULL); + if(!focus) { + ret_bad_args(mjs, "Focus must be a string"); + return; + } else { + if(!strncmp(focus, "start", strlen("start"))) { + set_focus = TextBoxFocusStart; + } else if(!strncmp(focus, "end", strlen("end"))) { + set_focus = TextBoxFocusEnd; + } else { + ret_bad_args(mjs, "Bad focus value"); + return; + } + } + + TextBoxFont set_font = TextBoxFontText; + mjs_val_t font_arg = mjs_arg(mjs, 1); + const char* font = mjs_get_string(mjs, &font_arg, NULL); + if(!font) { + ret_bad_args(mjs, "Font must be a string"); + return; + } else { + if(!strncmp(font, "text", strlen("text"))) { + set_font = TextBoxFontText; + } else if(!strncmp(font, "hex", strlen("hex"))) { + set_font = TextBoxFontHex; + } else { + ret_bad_args(mjs, "Bad font value"); + return; + } + } + + text_box_set_focus(textbox->text_box, set_focus); + text_box_set_font(textbox->text_box, set_font); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_add_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t text_arg = mjs_arg(mjs, 0); + size_t text_len = 0; + const char* text = mjs_get_string(mjs, &text_arg, &text_len); + if(!text) { + ret_bad_args(mjs, "Text must be a string"); + return; + } + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + size_t new_len = furi_string_size(textbox->text) + text_len; + if(new_len >= 4096) { + furi_string_right(textbox->text, new_len / 2); + } + + furi_string_cat(textbox->text, text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_clear_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + + furi_string_reset(textbox->text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_is_open(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); +} + +static void textbox_callback(void* context, uint32_t arg) { + UNUSED(arg); + JsTextboxInst* textbox = context; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; +} + +static void textbox_exit(void* context) { + JsTextboxInst* textbox = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(textbox_callback, textbox, 0); +} + +static void js_textbox_show(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + view_holder_start(textbox->view_holder); + textbox->is_shown = true; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_close(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { + JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + + mjs_val_t textbox_obj = mjs_mk_object(mjs); + mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); + mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); + mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); + mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); + mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); + mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); + + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + + *object = textbox_obj; + return textbox; +} + +static void js_textbox_destroy(void* inst) { + JsTextboxInst* textbox = inst; + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + + text_box_free(textbox->text_box); + furi_string_free(textbox->text); + free(textbox); +} + +static const JsModuleDescriptor js_textbox_desc = { + "textbox", + js_textbox_create, + js_textbox_destroy, +}; + +static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_textbox_desc, +}; + +const FlipperAppPluginDescriptor* js_textbox_ep(void) { + return &textbox_plugin_descriptor; +} \ No newline at end of file From 276feac73f4fff01a961af4fd54ca864adbc987b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 08:24:29 +0100 Subject: [PATCH 29/48] HID App: Update to scene based startscreen --- applications/system/hid_app/hid.c | 127 ++----------- .../system/hid_app/scenes/hid_scene_config.h | 3 +- .../system/hid_app/scenes/hid_scene_start.c | 167 ++++++++++++++++++ 3 files changed, 180 insertions(+), 117 deletions(-) create mode 100644 applications/system/hid_app/scenes/hid_scene_start.c diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index a4502c0169..55c27bf3c3 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -8,23 +8,6 @@ #define TAG "HidApp" -enum HidDebugSubmenuIndex { - HidSubmenuIndexKeynote, - HidSubmenuIndexKeynoteVertical, - HidSubmenuIndexKeyboard, - HidSubmenuIndexNumpad, - HidSubmenuIndexMedia, - HidSubmenuIndexMusicMacOs, - HidSubmenuIndexMovie, - HidSubmenuIndexTikTok, - HidSubmenuIndexMouse, - HidSubmenuIndexMouseClicker, - HidSubmenuIndexMouseJiggler, - HidSubmenuIndexMouseJigglerStealth, - HidSubmenuIndexPushToTalk, - HidSubmenuIndexRemovePairing, -}; - bool hid_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Hid* app = context; @@ -34,9 +17,7 @@ bool hid_custom_event_callback(void* context, uint32_t event) { bool hid_back_event_callback(void* context) { furi_assert(context); Hid* app = context; - FURI_LOG_D("HID", "Back event"); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - return true; + return scene_manager_handle_back_event(app->scene_manager); } void bt_hid_remove_pairing(Hid* app) { @@ -53,53 +34,12 @@ void bt_hid_remove_pairing(Hid* app) { furi_hal_bt_start_advertising(); } -static void hid_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - Hid* app = context; - if(index == HidSubmenuIndexKeynote) { - hid_keynote_set_orientation(app->hid_keynote, false); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeynoteVertical) { - hid_keynote_set_orientation(app->hid_keynote, true); - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); - } else if(index == HidSubmenuIndexKeyboard) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); - } else if(index == HidSubmenuIndexNumpad) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewNumpad); - } else if(index == HidSubmenuIndexMedia) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); - } else if(index == HidSubmenuIndexMusicMacOs) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMusicMacOs); - } else if(index == HidSubmenuIndexMovie) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie); - } else if(index == HidSubmenuIndexMouse) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == HidSubmenuIndexTikTok) { - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); - } else if(index == HidSubmenuIndexMouseClicker) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); - } else if(index == HidSubmenuIndexMouseJiggler) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); - } else if(index == HidSubmenuIndexMouseJigglerStealth) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJigglerStealth); - } else if(index == HidSubmenuIndexPushToTalk) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu); - } else if(index == HidSubmenuIndexRemovePairing) { - scene_manager_next_scene(app->scene_manager, HidSceneUnpair); - } -} - static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { furi_assert(context); Hid* hid = context; - bool connected = (status == BtStatusConnected); -#ifdef HID_TRANSPORT_BLE - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); - } -#endif + const bool connected = (status == BtStatusConnected); + notification_internal_message( + hid->notifications, connected ? &sequence_set_blue_255 : &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); @@ -114,11 +54,6 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } -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; @@ -139,55 +74,18 @@ Hid* hid_alloc(void) { // 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); - view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, hid_custom_event_callback); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // Scene Manager app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); // Device Type Submenu view app->submenu = submenu_alloc(); - submenu_add_item(app->submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); - submenu_add_item( - app->submenu, - "Keynote Vertical", - HidSubmenuIndexKeynoteVertical, - hid_submenu_callback, - app); - submenu_add_item(app->submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); - submenu_add_item(app->submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app); - submenu_add_item(app->submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); - submenu_add_item( - app->submenu, "Apple Music macOS", HidSubmenuIndexMusicMacOs, hid_submenu_callback, app); - submenu_add_item(app->submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app); - submenu_add_item(app->submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); - submenu_add_item( - app->submenu, "TikTok / YT Shorts", HidSubmenuIndexTikTok, hid_submenu_callback, app); - submenu_add_item( - app->submenu, "Mouse Clicker", HidSubmenuIndexMouseClicker, hid_submenu_callback, app); - submenu_add_item( - app->submenu, "Mouse Jiggler", HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); - submenu_add_item( - app->submenu, - "Mouse Jiggler Stealth", - HidSubmenuIndexMouseJigglerStealth, - hid_submenu_callback, - app); - submenu_add_item( - app->submenu, "PushToTalk", HidSubmenuIndexPushToTalk, hid_submenu_callback, app); -#ifdef HID_TRANSPORT_BLE - submenu_add_item( - app->submenu, "Remove Pairing", HidSubmenuIndexRemovePairing, hid_submenu_callback, app); -#endif - view_set_previous_callback(submenu_get_view(app->submenu), hid_exit); - view_dispatcher_add_view(app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->submenu)); - return app; -} -Hid* hid_app_alloc_view(void* context) { - furi_assert(context); - Hid* app = context; + view_dispatcher_add_view(app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->submenu)); // Dialog view app->dialog = dialog_ex_alloc(); @@ -327,18 +225,16 @@ void hid_free(Hid* app) { int32_t hid_usb_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); - app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as USB 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); - scene_manager_next_scene(app->scene_manager, HidSceneMain); + scene_manager_next_scene(app->scene_manager, HidSceneStart); view_dispatcher_run(app->view_dispatcher); @@ -352,7 +248,6 @@ int32_t hid_usb_app(void* p) { int32_t hid_ble_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); - app = hid_app_alloc_view(app); FURI_LOG_D("HID", "Starting as BLE app"); @@ -382,7 +277,7 @@ int32_t hid_ble_app(void* p) { dolphin_deed(DolphinDeedPluginStart); - scene_manager_next_scene(app->scene_manager, HidSceneMain); + scene_manager_next_scene(app->scene_manager, HidSceneStart); view_dispatcher_run(app->view_dispatcher); diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h index 1228ead4f0..d18b155582 100644 --- a/applications/system/hid_app/scenes/hid_scene_config.h +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -1,2 +1,3 @@ +ADD_SCENE(hid, start, Start) ADD_SCENE(hid, main, Main) -ADD_SCENE(hid, unpair, Unpair) \ No newline at end of file +ADD_SCENE(hid, unpair, Unpair) diff --git a/applications/system/hid_app/scenes/hid_scene_start.c b/applications/system/hid_app/scenes/hid_scene_start.c new file mode 100644 index 0000000000..61d340eec7 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_start.c @@ -0,0 +1,167 @@ +#include "../hid.h" +#include "../views.h" + +enum HidSubmenuIndex { + HidSubmenuIndexKeynote, + HidSubmenuIndexKeynoteVertical, + HidSubmenuIndexKeyboard, + HidSubmenuIndexNumpad, + HidSubmenuIndexMedia, + HidSubmenuIndexMusicMacOs, + HidSubmenuIndexMovie, + HidSubmenuIndexTikTok, + HidSubmenuIndexMouse, + HidSubmenuIndexMouseClicker, + HidSubmenuIndexMouseJiggler, + HidSubmenuIndexMouseJigglerStealth, + HidSubmenuIndexPushToTalk, + HidSubmenuIndexRemovePairing, +}; + +static void hid_scene_start_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Hid* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void hid_scene_start_on_enter(void* context) { + Hid* app = context; + submenu_add_item( + app->submenu, "Keynote", HidSubmenuIndexKeynote, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "Keynote Vertical", + HidSubmenuIndexKeynoteVertical, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Numpad", HidSubmenuIndexNumpad, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Media", HidSubmenuIndexMedia, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "Apple Music macOS", + HidSubmenuIndexMusicMacOs, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, "Movie", HidSubmenuIndexMovie, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, "Mouse", HidSubmenuIndexMouse, hid_scene_start_submenu_callback, app); + submenu_add_item( + app->submenu, + "TikTok / YT Shorts", + HidSubmenuIndexTikTok, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Clicker", + HidSubmenuIndexMouseClicker, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "Mouse Jiggler Stealth", + HidSubmenuIndexMouseJigglerStealth, + hid_scene_start_submenu_callback, + app); + submenu_add_item( + app->submenu, + "PushToTalk", + HidSubmenuIndexPushToTalk, + hid_scene_start_submenu_callback, + app); +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->submenu, + "Bluetooth Unpairing", + HidSubmenuIndexRemovePairing, + hid_scene_start_submenu_callback, + app); +#endif + + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, HidSceneStart)); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); +} + +bool hid_scene_start_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == HidSubmenuIndexRemovePairing) { + scene_manager_next_scene(app->scene_manager, HidSceneUnpair); + } else { + HidView view_id; + + switch(event.event) { + case HidSubmenuIndexKeynote: + view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, false); + break; + case HidSubmenuIndexKeynoteVertical: + view_id = HidViewKeynote; + hid_keynote_set_orientation(app->hid_keynote, true); + break; + case HidSubmenuIndexKeyboard: + view_id = HidViewKeyboard; + break; + case HidSubmenuIndexNumpad: + view_id = HidViewNumpad; + break; + case HidSubmenuIndexMedia: + view_id = HidViewMedia; + break; + case HidSubmenuIndexMusicMacOs: + view_id = HidViewMusicMacOs; + break; + case HidSubmenuIndexMovie: + view_id = HidViewMovie; + break; + case HidSubmenuIndexTikTok: + view_id = BtHidViewTikTok; + break; + case HidSubmenuIndexMouse: + view_id = HidViewMouse; + break; + case HidSubmenuIndexMouseClicker: + view_id = HidViewMouseClicker; + break; + case HidSubmenuIndexMouseJiggler: + view_id = HidViewMouseJiggler; + break; + case HidSubmenuIndexMouseJigglerStealth: + view_id = HidViewMouseJigglerStealth; + break; + case HidSubmenuIndexPushToTalk: + view_id = HidViewPushToTalkMenu; + break; + default: + furi_crash(); + } + + scene_manager_set_scene_state(app->scene_manager, HidSceneMain, view_id); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + } + + scene_manager_set_scene_state(app->scene_manager, HidSceneStart, event.event); + consumed = true; + } + + return consumed; +} + +void hid_scene_start_on_exit(void* context) { + Hid* app = context; + submenu_reset(app->submenu); +} From c4709a5b6b5c2ea867bc628bd90ff6960b6a853c Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 08:26:36 +0100 Subject: [PATCH 30/48] HID App: Update icons --- .../system/hid_app/assets/Circles_47x47.png | Bin 3712 -> 0 bytes .../system/hid_app/assets/DolphinDone_80x58.png | Bin 0 -> 1664 bytes .../system/hid_app/assets/DolphinNice_96x59.png | Bin 2459 -> 0 bytes .../hid_app/assets/Ok_btn_pressed_13x13.png | Bin 3625 -> 0 bytes .../system/hid_app/scenes/hid_scene_unpair.c | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 applications/system/hid_app/assets/Circles_47x47.png create mode 100644 applications/system/hid_app/assets/DolphinDone_80x58.png delete mode 100644 applications/system/hid_app/assets/DolphinNice_96x59.png delete mode 100644 applications/system/hid_app/assets/Ok_btn_pressed_13x13.png diff --git a/applications/system/hid_app/assets/Circles_47x47.png b/applications/system/hid_app/assets/Circles_47x47.png deleted file mode 100644 index 6a16ebf7bbe999fa143c683bef713a6e2f466cbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| diff --git a/applications/system/hid_app/assets/DolphinDone_80x58.png b/applications/system/hid_app/assets/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/applications/system/hid_app/assets/DolphinNice_96x59.png b/applications/system/hid_app/assets/DolphinNice_96x59.png deleted file mode 100644 index a299d3630239b4486e249cc501872bed5996df3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q{Vn_(lP!vI=DFY(X1wo}3 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 diff --git a/applications/system/hid_app/scenes/hid_scene_unpair.c b/applications/system/hid_app/scenes/hid_scene_unpair.c index 7b0bbd9e62..0f9387fcf3 100644 --- a/applications/system/hid_app/scenes/hid_scene_unpair.c +++ b/applications/system/hid_app/scenes/hid_scene_unpair.c @@ -36,7 +36,7 @@ void hid_scene_unpair_on_enter(void* context) { dialog_ex_set_right_button_text(app->dialog, "Unpair"); // Un-pair success popup view - popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_icon(app->popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); popup_set_timeout(app->popup, 1500); popup_set_context(app->popup, app); From 23ca7e7b48dc12ef2fa2e08ab96593364aa4b7e9 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 08:28:45 +0100 Subject: [PATCH 31/48] Some formatting to match upstream --- applications/system/hid_app/hid.c | 3 +-- applications/system/hid_app/hid.h | 3 ++- .../system/hid_app/scenes/hid_scene_main.c | 1 - .../system/hid_app/views/hid_keyboard.c | 4 +-- .../system/hid_app/views/hid_keynote.c | 5 ++-- applications/system/hid_app/views/hid_media.c | 4 +-- applications/system/hid_app/views/hid_media.h | 1 - applications/system/hid_app/views/hid_mouse.c | 27 +++++++++---------- .../system/hid_app/views/hid_mouse_clicker.c | 2 +- .../system/hid_app/views/hid_mouse_jiggler.c | 2 +- applications/system/hid_app/views/hid_movie.c | 24 ++++++++--------- .../system/hid_app/views/hid_music_macos.c | 24 ++++++++--------- .../system/hid_app/views/hid_tiktok.c | 24 ++++++++--------- 13 files changed, 60 insertions(+), 64 deletions(-) diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 55c27bf3c3..586d198a9b 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -4,7 +4,6 @@ #include "views.h" #include #include -#include "hid_icons.h" #define TAG "HidApp" @@ -59,7 +58,7 @@ static uint32_t hid_ptt_menu_view(void* context) { return HidViewPushToTalkMenu; } -Hid* hid_alloc(void) { +Hid* hid_alloc() { Hid* app = malloc(sizeof(Hid)); // Gui diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index b15ab58ff7..ac565217a8 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -62,6 +62,7 @@ struct Hid { HidPushToTalk* hid_ptt; HidPushToTalkMenu* hid_ptt_menu; }; + void bt_hid_remove_pairing(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); @@ -76,4 +77,4 @@ 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); \ No newline at end of file +void hid_hal_mouse_release_all(Hid* instance); diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c index cd1051ac7c..1d0ca5ef4b 100644 --- a/applications/system/hid_app/scenes/hid_scene_main.c +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -3,7 +3,6 @@ void hid_scene_main_on_enter(void* context) { Hid* app = context; - view_dispatcher_switch_to_view( app->view_dispatcher, scene_manager_get_scene_state(app->scene_manager, HidSceneMain)); } diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 9c62650f34..9dd2ad6552 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -228,9 +228,9 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidKeyboardModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE - if((!model->connected)) { + if(!model->connected) { 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"); diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c index 3c966e20c3..793f6e5d35 100644 --- a/applications/system/hid_app/views/hid_keynote.c +++ b/applications/system/hid_app/views/hid_keynote.c @@ -38,7 +38,7 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidKeynoteModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); @@ -114,7 +114,7 @@ static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { furi_assert(context); HidKeynoteModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); @@ -273,7 +273,6 @@ HidKeynote* hid_keynote_alloc(Hid* hid) { 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); - return hid_keynote; } diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index 104e8a6c68..10109b3a36 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -41,7 +41,7 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidMediaModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); @@ -197,6 +197,7 @@ static bool hid_media_input_callback(InputEvent* event, void* context) { hid_media_process_release(hid_media, event); } } + return consumed; } @@ -208,7 +209,6 @@ HidMedia* hid_media_alloc(Hid* hid) { 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); - return hid_media; } diff --git a/applications/system/hid_app/views/hid_media.h b/applications/system/hid_app/views/hid_media.h index 2356734b3e..faf88301f4 100644 --- a/applications/system/hid_app/views/hid_media.h +++ b/applications/system/hid_app/views/hid_media.h @@ -3,7 +3,6 @@ #include typedef struct Hid Hid; - typedef struct HidMedia HidMedia; HidMedia* hid_media_alloc(Hid* hid); diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c index 1816a48b4e..c4024f10e3 100644 --- a/applications/system/hid_app/views/hid_mouse.c +++ b/applications/system/hid_app/views/hid_mouse.c @@ -27,7 +27,7 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidMouseModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); @@ -53,9 +53,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 8, &I_Pin_arrow_up_7x9); @@ -63,9 +63,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 40, &I_Pin_arrow_down_7x9); @@ -73,9 +73,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 63, 25, &I_Pin_arrow_left_9x7); @@ -83,9 +83,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 95, 25, &I_Pin_arrow_right_9x7); @@ -93,9 +93,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Ok if(model->left_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 24, &I_Left_mouse_icon_9x9); @@ -103,9 +103,9 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { // Back if(model->right_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 112, 38, &I_Right_mouse_icon_9x9); @@ -217,7 +217,6 @@ HidMouse* hid_mouse_alloc(Hid* hid) { 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); - return hid_mouse; } diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index 79922d05fb..bc0abe56f3 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -42,7 +42,7 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidMouseClickerModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); diff --git a/applications/system/hid_app/views/hid_mouse_jiggler.c b/applications/system/hid_app/views/hid_mouse_jiggler.c index 3040753b66..41743b0432 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler.c +++ b/applications/system/hid_app/views/hid_mouse_jiggler.c @@ -25,7 +25,7 @@ static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidMouseJigglerModel* model = context; -// Header + // Header #ifdef HID_TRANSPORT_BLE if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); diff --git a/applications/system/hid_app/views/hid_movie.c b/applications/system/hid_app/views/hid_movie.c index 90af5d6906..2ee7b08388 100644 --- a/applications/system/hid_app/views/hid_movie.c +++ b/applications/system/hid_app/views/hid_movie.c @@ -59,9 +59,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -69,9 +69,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -79,9 +79,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft); @@ -90,9 +90,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -101,9 +101,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -113,9 +113,9 @@ static void hid_movie_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); diff --git a/applications/system/hid_app/views/hid_music_macos.c b/applications/system/hid_app/views/hid_music_macos.c index 637e6850be..1675b6dd52 100644 --- a/applications/system/hid_app/views/hid_music_macos.c +++ b/applications/system/hid_app/views/hid_music_macos.c @@ -59,9 +59,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); @@ -69,9 +69,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); @@ -79,9 +79,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); @@ -91,9 +91,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); @@ -103,9 +103,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } hid_music_macos_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); @@ -115,9 +115,9 @@ static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { // Exit if(model->back_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index 5f4e8057db..7e8e287226 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -45,9 +45,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Pause if(model->back_mouse_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9); @@ -55,9 +55,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Up if(model->up_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9); @@ -65,9 +65,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Down if(model->down_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9); @@ -75,9 +75,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Left if(model->left_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6); @@ -85,9 +85,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Right if(model->right_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6); @@ -95,9 +95,9 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { // Ok if(model->ok_pressed) { - canvas_set_bitmap_mode(canvas, 1); + canvas_set_bitmap_mode(canvas, true); canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); - canvas_set_bitmap_mode(canvas, 0); + canvas_set_bitmap_mode(canvas, false); canvas_set_color(canvas, ColorWhite); } canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9); From 7c013c0534381989d3220e2b0f6efca217357734 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 08:31:36 +0100 Subject: [PATCH 32/48] HID App: Add few missing things from last merge --- applications/system/hid_app/views/hid_mouse.c | 9 ++++ .../system/hid_app/views/hid_mouse_clicker.c | 43 ++++++++++--------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/applications/system/hid_app/views/hid_mouse.c b/applications/system/hid_app/views/hid_mouse.c index c4024f10e3..4f568b26c7 100644 --- a/applications/system/hid_app/views/hid_mouse.c +++ b/applications/system/hid_app/views/hid_mouse.c @@ -201,6 +201,15 @@ static bool hid_mouse_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeLong && event->key == InputKeyBack) { hid_hal_mouse_release_all(hid_mouse->hid); + + with_view_model( + hid_mouse->view, + HidMouseModel * model, + { + model->left_mouse_held = false; + model->left_mouse_pressed = false; + }, + false); } else { hid_mouse_process(hid_mouse, event); consumed = true; diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index bc0abe56f3..3a2a064cbb 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -52,37 +52,37 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { #endif canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 27, 3, AlignLeft, AlignTop, "Mouse Clicker"); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker"); + canvas_set_font(canvas, FontSecondary); // 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); + canvas_draw_icon(canvas, 58, 25, &I_Space_65x18); - elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + if(model->running) { + elements_slightly_rounded_box(canvas, 61, 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); + + canvas_draw_icon(canvas, 69, 29, &I_Ok_btn_9x9); + if(model->running) { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + elements_multiline_text_aligned(canvas, 86, 37, AlignLeft, AlignBottom, "Stop"); } else { - elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + elements_multiline_text_aligned(canvas, 86, 37, AlignLeft, AlignBottom, "Start"); } canvas_set_color(canvas, ColorBlack); + // Clicks/s + char label[20]; + snprintf(label, sizeof(label), "%d clicks/s", model->rate); + elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label); + + canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4); + canvas_draw_icon(canvas, 25, 44, &I_ButtonDown_7x4); + // Back - canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); - elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Exit"); } static void hid_mouse_clicker_timer_callback(void* context) { @@ -144,6 +144,9 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { rate_changed = true; consumed = true; break; + case InputKeyBack: + model->running = false; + break; default: consumed = true; break; From de4b086083b8f9914631969c236505ac8c83ee5d Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 18 May 2024 19:40:01 +0100 Subject: [PATCH 33/48] Fix calling both `view_free_model()` and `view_free()` (#3655) --- applications/services/gui/view.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/view.c b/applications/services/gui/view.c index 8fc5c26997..a35c2fa388 100644 --- a/applications/services/gui/view.c +++ b/applications/services/gui/view.c @@ -97,10 +97,11 @@ void view_free_model(View* view) { furi_mutex_free(model->mutex); free(model->data); free(model); - view->model = NULL; } else { furi_crash(); } + view->model = NULL; + view->model_type = ViewModelTypeNone; } void* view_get_model(View* view) { From 21abcb56fdb96a4fea568a0e326d7641787cd5b6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 19 May 2024 03:54:21 +0300 Subject: [PATCH 34/48] merge ofw dev missing parts --- .../js_app/examples/apps/Scripts/math.js | 110 ++++--- .../js_app/examples/apps/Scripts/textbox.js | 6 +- applications/system/js_app/modules/js_math.c | 268 ++++++++++-------- .../system/js_app/modules/js_submenu.c | 38 +-- .../system/js_app/modules/js_textbox.c | 102 ++++--- 5 files changed, 293 insertions(+), 231 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js index 49212f904a..c5a0bf18d0 100644 --- a/applications/system/js_app/examples/apps/Scripts/math.js +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -1,47 +1,69 @@ let math = require("math"); -let absResult = math.abs(-5); -let acosResult = math.acos(0.5); -let acoshResult = math.acosh(2); -let asinResult = math.asin(0.5); -let asinhResult = math.asinh(2); -let atanResult = math.atan(1); -let atan2Result = math.atan2(1, 1); -let atanhResult = math.atanh(0.5); -let cbrtResult = math.cbrt(27); -let ceilResult = math.ceil(5.3); -let clz32Result = math.clz32(1); -let cosResult = math.cos(math.PI); -let expResult = math.exp(1); -let floorResult = math.floor(5.7); -let maxResult = math.max(3, 5); -let minResult = math.min(3, 5); -let powResult = math.pow(2, 3); -let randomResult = math.random(); -let signResult = math.sign(-5); -let sinResult = math.sin(math.PI / 2); -let sqrtResult = math.sqrt(25); -let truncResult = math.trunc(5.7); +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); -print("math.abs(-5):", absResult); -print("math.acos(0.5):", acosResult); -print("math.acosh(2):", acoshResult); -print("math.asin(0.5):", asinResult); -print("math.asinh(2):", asinhResult); -print("math.atan(1):", atanResult); -print("math.atan2(1, 1):", atan2Result); -print("math.atanh(0.5):", atanhResult); -print("math.cbrt(27):", cbrtResult); -print("math.ceil(5.3):", ceilResult); -print("math.clz32(1):", clz32Result); -print("math.cos(math.PI):", cosResult); -print("math.exp(1):", expResult); -print("math.floor(5.7):", floorResult); -print("math.max(3, 5):", maxResult); -print("math.min(3, 5):", minResult); -print("math.pow(2, 3):", powResult); -print("math.random():", randomResult); -print("math.sign(-5):", signResult); -print("math.sin(math.PI/2):", sinResult); -print("math.sqrt(25):", sqrtResult); -print("math.trunc(5.7):", truncResult); \ No newline at end of file +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js index bb6c4fc23a..6caf372347 100644 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -4,9 +4,9 @@ let textbox = require("textbox"); // Focus (start / end), Font (text / hex) textbox.setConfig("end", "text"); -// Can make sure it's empty before showing, in case of reusing in same script -// (Closing textbox already empties the text, but maybe you added more in a loop for example) -textbox.emptyText(); +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); // Add default text textbox.addText("Example dynamic updating textbox\n"); diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index e7daae41b8..b4c1cdca23 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -1,268 +1,313 @@ #include "../js_modules.h" #include "furi_hal_random.h" +#include -#define JS_MATH_PI (double)3.14159265358979323846 -#define JS_MATH_E (double)2.7182818284590452354 +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" static void ret_bad_args(struct mjs* mjs, const char* error) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, mjs_mk_undefined()); + mjs_return(mjs, MJS_UNDEFINED); } -static bool check_arg_count(struct mjs* mjs, size_t count) { +static bool check_args(struct mjs* mjs, size_t count) { size_t num_args = mjs_nargs(mjs); if(num_args != count) { ret_bad_args(mjs, "Wrong argument count"); return false; } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } return true; } +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; + } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + void js_math_abs(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); } void js_math_acos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < -1 || x > 1) { - ret_bad_args(mjs, "Invalid input value for Math.acos"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); } void js_math_acosh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 1) { - ret_bad_args(mjs, "Invalid input value for Math.acosh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); } void js_math_asin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); } void js_math_asinh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); } void js_math_atan(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); } void js_math_atan2(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); } void js_math_atanh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x <= -1 || x >= 1) { - ret_bad_args(mjs, "Invalid input value for Math.atanh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log((1 + x) / (1 - x)))); + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); } void js_math_cbrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0))); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); } void js_math_ceil(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5))); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); } void js_math_clz32(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); int count = 0; while(x) { x >>= 1; count++; } + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); } void js_math_cos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); } void js_math_exp(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = 1; - double term = 1; - for(int i = 1; i < 100; i++) { - term *= x / i; - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); } void js_math_floor(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)x)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); } void js_math_log(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); if(x <= 0) { - ret_bad_args(mjs, "Invalid input value for Math.log"); - mjs_return(mjs, MJS_UNDEFINED); - } - double result = 0; - while(x >= JS_MATH_E) { - x /= JS_MATH_E; - result++; + ret_bad_args(mjs, "Invalid input value for math.log"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, result + log(x))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); } void js_math_max(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); } void js_math_min(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); } void js_math_pow(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); - double result = 1; - for(int i = 0; i < exponent; i++) { - result *= base; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); } void js_math_random(struct mjs* mjs) { - if(!check_arg_count(mjs, 0)) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 0)) { + return; } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe const uint32_t random_val = furi_hal_random_get(); - double rnd = (double)random_val / FURI_HAL_RANDOM_MAX; + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + mjs_return(mjs, mjs_mk_number(mjs, rnd)); } void js_math_sign(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1))); + + mjs_return( + mjs, + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); } void js_math_sin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = x; - double term = x; - for(int i = 1; i < 10; i++) { - term *= -x * x / ((2 * i) * (2 * i + 1)); - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); } void js_math_sqrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 0) { - ret_bad_args(mjs, "Invalid input value for Math.sqrt"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; } - double result = 1; - while(result * result < x) { - result += (double)0.001; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); } void js_math_trunc(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x))); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); @@ -288,6 +333,7 @@ static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); *object = math_obj; return (void*)1; } @@ -306,4 +352,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* js_math_ep(void) { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c index d66a7d24dc..058b32fd09 100644 --- a/applications/system/js_app/modules/js_submenu.c +++ b/applications/system/js_app/modules/js_submenu.c @@ -1,11 +1,13 @@ #include -#include +#include #include +#include #include "../js_modules.h" typedef struct { Submenu* submenu; - ViewDispatcher* view_dispatcher; + ViewHolder* view_holder; + FuriApiLock lock; uint32_t result; bool accepted; } JsSubmenuInst; @@ -35,15 +37,14 @@ static void submenu_callback(void* context, uint32_t id) { JsSubmenuInst* submenu = context; submenu->result = id; submenu->accepted = true; - view_dispatcher_stop(submenu->view_dispatcher); + api_lock_unlock(submenu->lock); } -static bool submenu_exit(void* context) { +static void submenu_exit(void* context) { JsSubmenuInst* submenu = context; submenu->result = 0; submenu->accepted = false; - view_dispatcher_stop(submenu->view_dispatcher); - return true; + api_lock_unlock(submenu->lock); } static void js_submenu_add_item(struct mjs* mjs) { @@ -89,21 +90,20 @@ static void js_submenu_show(struct mjs* mjs) { JsSubmenuInst* submenu = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; + submenu->lock = api_lock_alloc_locked(); Gui* gui = furi_record_open(RECORD_GUI); - submenu->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(submenu->view_dispatcher); - view_dispatcher_add_view(submenu->view_dispatcher, 0, submenu_get_view(submenu->submenu)); - view_dispatcher_set_event_callback_context(submenu->view_dispatcher, submenu); - view_dispatcher_set_navigation_event_callback(submenu->view_dispatcher, submenu_exit); - view_dispatcher_attach_to_gui(submenu->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(submenu->view_dispatcher, 0); - - view_dispatcher_run(submenu->view_dispatcher); - - view_dispatcher_remove_view(submenu->view_dispatcher, 0); - view_dispatcher_free(submenu->view_dispatcher); - submenu->view_dispatcher = NULL; + submenu->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(submenu->view_holder, gui); + view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); + + view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); + view_holder_start(submenu->view_holder); + api_lock_wait_unlock(submenu->lock); + + view_holder_stop(submenu->view_holder); + view_holder_free(submenu->view_holder); furi_record_close(RECORD_GUI); + api_lock_free(submenu->lock); submenu_reset(submenu->submenu); if(submenu->accepted) { diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c index cf4e8dbbfc..d15cd2779d 100644 --- a/applications/system/js_app/modules/js_textbox.c +++ b/applications/system/js_app/modules/js_textbox.c @@ -1,13 +1,12 @@ #include -#include -#include +#include #include "../js_modules.h" typedef struct { TextBox* text_box; - ViewDispatcher* view_dispatcher; - FuriThread* thread; + ViewHolder* view_holder; FuriString* text; + bool is_shown; } JsTextboxInst; static JsTextboxInst* get_this_ctx(struct mjs* mjs) { @@ -87,6 +86,9 @@ static void js_textbox_add_text(struct mjs* mjs) { return; } + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + size_t new_len = furi_string_size(textbox->text) + text_len; if(new_len >= 4096) { furi_string_right(textbox->text, new_len / 2); @@ -99,10 +101,13 @@ static void js_textbox_add_text(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void js_textbox_empty_text(struct mjs* mjs) { +static void js_textbox_clear_text(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + furi_string_reset(textbox->text); text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); @@ -114,58 +119,34 @@ static void js_textbox_is_open(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - mjs_return(mjs, mjs_mk_boolean(mjs, !!textbox->thread)); -} - -static void textbox_deinit(void* context) { - JsTextboxInst* textbox = context; - furi_thread_join(textbox->thread); - furi_thread_free(textbox->thread); - textbox->thread = NULL; - - view_dispatcher_remove_view(textbox->view_dispatcher, 0); - view_dispatcher_free(textbox->view_dispatcher); - textbox->view_dispatcher = NULL; - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); } static void textbox_callback(void* context, uint32_t arg) { UNUSED(arg); - textbox_deinit(context); + JsTextboxInst* textbox = context; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; } -static bool textbox_exit(void* context) { +static void textbox_exit(void* context) { JsTextboxInst* textbox = context; - view_dispatcher_stop(textbox->view_dispatcher); + // Using timer to schedule view_holder stop, will not work under high CPU load furi_timer_pending_callback(textbox_callback, textbox, 0); - return true; -} - -static int32_t textbox_thread(void* context) { - ViewDispatcher* view_dispatcher = context; - view_dispatcher_run(view_dispatcher); - return 0; } static void js_textbox_show(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(textbox->view_dispatcher); - view_dispatcher_add_view(textbox->view_dispatcher, 0, text_box_get_view(textbox->text_box)); - view_dispatcher_set_event_callback_context(textbox->view_dispatcher, textbox); - view_dispatcher_set_navigation_event_callback(textbox->view_dispatcher, textbox_exit); - view_dispatcher_attach_to_gui(textbox->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(textbox->view_dispatcher, 0); - - textbox->thread = - furi_thread_alloc_ex("JsTextbox", 1024, textbox_thread, textbox->view_dispatcher); - furi_thread_start(textbox->thread); + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + view_holder_start(textbox->view_holder); + textbox->is_shown = true; mjs_return(mjs, MJS_UNDEFINED); } @@ -174,36 +155,49 @@ static void js_textbox_close(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; mjs_return(mjs, MJS_UNDEFINED); } static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + mjs_val_t textbox_obj = mjs_mk_object(mjs); mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "emptyText", ~0, MJS_MK_FN(js_textbox_empty_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - textbox->text_box = text_box_alloc(); + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + *object = textbox_obj; return textbox; } static void js_textbox_destroy(void* inst) { JsTextboxInst* textbox = inst; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + text_box_free(textbox->text_box); furi_string_free(textbox->text); free(textbox); @@ -223,4 +217,4 @@ static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { const FlipperAppPluginDescriptor* js_textbox_ep(void) { return &textbox_plugin_descriptor; -} +} \ No newline at end of file From 4f6c98dc0dc433db5a82c143a62227ab7c60884c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 19 May 2024 03:56:22 +0300 Subject: [PATCH 35/48] move js examples to examples subfolder --- .../apps/Scripts/{ => examples}/array_buf_test.js | 0 .../apps/Scripts/{ => examples}/bad_uart.js | 0 .../apps/Scripts/{ => examples}/badusb_demo.js | 0 .../apps/Scripts/{ => examples}/blebeacon.js | 0 .../examples/apps/Scripts/{ => examples}/console.js | 0 .../examples/apps/Scripts/{ => examples}/delay.js | 0 .../examples/apps/Scripts/{ => examples}/dialog.js | 0 .../examples/apps/Scripts/{ => examples}/gpio.js | 0 .../apps/Scripts/{ => examples}/keyboard.js | 0 .../examples/apps/Scripts/{ => examples}/load.js | 0 .../apps/Scripts/{ => examples}/load_api.js | 0 .../examples/apps/Scripts/{ => examples}/math.js | 0 .../examples/apps/Scripts/{ => examples}/notify.js | 0 .../examples/apps/Scripts/{ => examples}/path.js | 0 .../apps/Scripts/{ => examples}/stringutils.js | 0 .../examples/apps/Scripts/{ => examples}/subghz.js | 0 .../examples/apps/Scripts/{ => examples}/submenu.js | 0 .../examples/apps/Scripts/{ => examples}/textbox.js | 0 .../apps/Scripts/{ => examples}/uart_echo.js | 0 .../examples/apps/Scripts/{ => examples}/usbdisk.js | 0 .../apps/Scripts/{ => examples}/widget-js.fxbm | Bin .../examples/apps/Scripts/{ => examples}/widget.js | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/array_buf_test.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/bad_uart.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/badusb_demo.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/blebeacon.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/console.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/delay.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/dialog.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/gpio.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/keyboard.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/load.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/load_api.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/math.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/notify.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/path.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/stringutils.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/subghz.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/submenu.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/textbox.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/uart_echo.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/usbdisk.js (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/widget-js.fxbm (100%) rename applications/system/js_app/examples/apps/Scripts/{ => examples}/widget.js (100%) diff --git a/applications/system/js_app/examples/apps/Scripts/array_buf_test.js b/applications/system/js_app/examples/apps/Scripts/examples/array_buf_test.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/array_buf_test.js rename to applications/system/js_app/examples/apps/Scripts/examples/array_buf_test.js diff --git a/applications/system/js_app/examples/apps/Scripts/bad_uart.js b/applications/system/js_app/examples/apps/Scripts/examples/bad_uart.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/bad_uart.js rename to applications/system/js_app/examples/apps/Scripts/examples/bad_uart.js diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/examples/badusb_demo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/badusb_demo.js rename to applications/system/js_app/examples/apps/Scripts/examples/badusb_demo.js diff --git a/applications/system/js_app/examples/apps/Scripts/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/examples/blebeacon.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/blebeacon.js rename to applications/system/js_app/examples/apps/Scripts/examples/blebeacon.js diff --git a/applications/system/js_app/examples/apps/Scripts/console.js b/applications/system/js_app/examples/apps/Scripts/examples/console.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/console.js rename to applications/system/js_app/examples/apps/Scripts/examples/console.js diff --git a/applications/system/js_app/examples/apps/Scripts/delay.js b/applications/system/js_app/examples/apps/Scripts/examples/delay.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/delay.js rename to applications/system/js_app/examples/apps/Scripts/examples/delay.js diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/examples/dialog.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/dialog.js rename to applications/system/js_app/examples/apps/Scripts/examples/dialog.js diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/examples/gpio.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/gpio.js rename to applications/system/js_app/examples/apps/Scripts/examples/gpio.js diff --git a/applications/system/js_app/examples/apps/Scripts/keyboard.js b/applications/system/js_app/examples/apps/Scripts/examples/keyboard.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/keyboard.js rename to applications/system/js_app/examples/apps/Scripts/examples/keyboard.js diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/examples/load.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/load.js rename to applications/system/js_app/examples/apps/Scripts/examples/load.js diff --git a/applications/system/js_app/examples/apps/Scripts/load_api.js b/applications/system/js_app/examples/apps/Scripts/examples/load_api.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/load_api.js rename to applications/system/js_app/examples/apps/Scripts/examples/load_api.js diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/examples/math.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/math.js rename to applications/system/js_app/examples/apps/Scripts/examples/math.js diff --git a/applications/system/js_app/examples/apps/Scripts/notify.js b/applications/system/js_app/examples/apps/Scripts/examples/notify.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/notify.js rename to applications/system/js_app/examples/apps/Scripts/examples/notify.js diff --git a/applications/system/js_app/examples/apps/Scripts/path.js b/applications/system/js_app/examples/apps/Scripts/examples/path.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/path.js rename to applications/system/js_app/examples/apps/Scripts/examples/path.js diff --git a/applications/system/js_app/examples/apps/Scripts/stringutils.js b/applications/system/js_app/examples/apps/Scripts/examples/stringutils.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/stringutils.js rename to applications/system/js_app/examples/apps/Scripts/examples/stringutils.js diff --git a/applications/system/js_app/examples/apps/Scripts/subghz.js b/applications/system/js_app/examples/apps/Scripts/examples/subghz.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/subghz.js rename to applications/system/js_app/examples/apps/Scripts/examples/subghz.js diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/examples/submenu.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/submenu.js rename to applications/system/js_app/examples/apps/Scripts/examples/submenu.js diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/examples/textbox.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/textbox.js rename to applications/system/js_app/examples/apps/Scripts/examples/textbox.js diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/examples/uart_echo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/uart_echo.js rename to applications/system/js_app/examples/apps/Scripts/examples/uart_echo.js diff --git a/applications/system/js_app/examples/apps/Scripts/usbdisk.js b/applications/system/js_app/examples/apps/Scripts/examples/usbdisk.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/usbdisk.js rename to applications/system/js_app/examples/apps/Scripts/examples/usbdisk.js diff --git a/applications/system/js_app/examples/apps/Scripts/widget-js.fxbm b/applications/system/js_app/examples/apps/Scripts/examples/widget-js.fxbm similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/widget-js.fxbm rename to applications/system/js_app/examples/apps/Scripts/examples/widget-js.fxbm diff --git a/applications/system/js_app/examples/apps/Scripts/widget.js b/applications/system/js_app/examples/apps/Scripts/examples/widget.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/widget.js rename to applications/system/js_app/examples/apps/Scripts/examples/widget.js From 8275140fe6f95ed62ecb457a58956661740ffdd7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 19 May 2024 03:57:19 +0300 Subject: [PATCH 36/48] rename --- .../{examples => js_examples}/array_buf_test.js | 0 .../Scripts/{examples => js_examples}/bad_uart.js | 0 .../{examples => js_examples}/badusb_demo.js | 0 .../Scripts/{examples => js_examples}/blebeacon.js | 0 .../Scripts/{examples => js_examples}/console.js | 0 .../apps/Scripts/{examples => js_examples}/delay.js | 0 .../Scripts/{examples => js_examples}/dialog.js | 0 .../apps/Scripts/{examples => js_examples}/gpio.js | 0 .../Scripts/{examples => js_examples}/keyboard.js | 0 .../apps/Scripts/{examples => js_examples}/load.js | 0 .../Scripts/{examples => js_examples}/load_api.js | 0 .../apps/Scripts/{examples => js_examples}/math.js | 0 .../Scripts/{examples => js_examples}/notify.js | 0 .../apps/Scripts/{examples => js_examples}/path.js | 0 .../{examples => js_examples}/stringutils.js | 0 .../Scripts/{examples => js_examples}/subghz.js | 0 .../Scripts/{examples => js_examples}/submenu.js | 0 .../Scripts/{examples => js_examples}/textbox.js | 0 .../Scripts/{examples => js_examples}/uart_echo.js | 0 .../Scripts/{examples => js_examples}/usbdisk.js | 0 .../{examples => js_examples}/widget-js.fxbm | Bin .../Scripts/{examples => js_examples}/widget.js | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/array_buf_test.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/bad_uart.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/badusb_demo.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/blebeacon.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/console.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/delay.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/dialog.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/gpio.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/keyboard.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/load.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/load_api.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/math.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/notify.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/path.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/stringutils.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/subghz.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/submenu.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/textbox.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/uart_echo.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/usbdisk.js (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/widget-js.fxbm (100%) rename applications/system/js_app/examples/apps/Scripts/{examples => js_examples}/widget.js (100%) diff --git a/applications/system/js_app/examples/apps/Scripts/examples/array_buf_test.js b/applications/system/js_app/examples/apps/Scripts/js_examples/array_buf_test.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/array_buf_test.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/array_buf_test.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/bad_uart.js b/applications/system/js_app/examples/apps/Scripts/js_examples/bad_uart.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/bad_uart.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/bad_uart.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/js_examples/badusb_demo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/badusb_demo.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/badusb_demo.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/js_examples/blebeacon.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/blebeacon.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/blebeacon.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/console.js b/applications/system/js_app/examples/apps/Scripts/js_examples/console.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/console.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/console.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/delay.js b/applications/system/js_app/examples/apps/Scripts/js_examples/delay.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/delay.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/delay.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/dialog.js b/applications/system/js_app/examples/apps/Scripts/js_examples/dialog.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/dialog.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/dialog.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/gpio.js b/applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/gpio.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/gpio.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/keyboard.js b/applications/system/js_app/examples/apps/Scripts/js_examples/keyboard.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/keyboard.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/keyboard.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/load.js b/applications/system/js_app/examples/apps/Scripts/js_examples/load.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/load.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/load.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/load_api.js b/applications/system/js_app/examples/apps/Scripts/js_examples/load_api.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/load_api.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/load_api.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/math.js b/applications/system/js_app/examples/apps/Scripts/js_examples/math.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/math.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/math.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/notify.js b/applications/system/js_app/examples/apps/Scripts/js_examples/notify.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/notify.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/notify.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/path.js b/applications/system/js_app/examples/apps/Scripts/js_examples/path.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/path.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/path.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/stringutils.js b/applications/system/js_app/examples/apps/Scripts/js_examples/stringutils.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/stringutils.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/stringutils.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/subghz.js b/applications/system/js_app/examples/apps/Scripts/js_examples/subghz.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/subghz.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/subghz.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/submenu.js b/applications/system/js_app/examples/apps/Scripts/js_examples/submenu.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/submenu.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/submenu.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/textbox.js b/applications/system/js_app/examples/apps/Scripts/js_examples/textbox.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/textbox.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/textbox.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/js_examples/uart_echo.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/uart_echo.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/uart_echo.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/usbdisk.js b/applications/system/js_app/examples/apps/Scripts/js_examples/usbdisk.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/usbdisk.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/usbdisk.js diff --git a/applications/system/js_app/examples/apps/Scripts/examples/widget-js.fxbm b/applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/widget-js.fxbm rename to applications/system/js_app/examples/apps/Scripts/js_examples/widget-js.fxbm diff --git a/applications/system/js_app/examples/apps/Scripts/examples/widget.js b/applications/system/js_app/examples/apps/Scripts/js_examples/widget.js similarity index 100% rename from applications/system/js_app/examples/apps/Scripts/examples/widget.js rename to applications/system/js_app/examples/apps/Scripts/js_examples/widget.js From e909818f659fc698f232289a93f30e517c49a74d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 19 May 2024 04:53:30 +0300 Subject: [PATCH 37/48] fix stealth jiggler icon with ble --- applications/system/hid_app/views/hid_mouse_jiggler_stealth.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c index 736cfbac00..d031b4c5de 100644 --- a/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c +++ b/applications/system/hid_app/views/hid_mouse_jiggler_stealth.c @@ -34,7 +34,11 @@ static void hid_mouse_jiggler_stealth_draw_callback(Canvas* canvas, void* contex // Title "Mouse Jiggler" canvas_set_font(canvas, FontPrimary); +#ifdef HID_TRANSPORT_BLE + elements_multiline_text_aligned(canvas, 17, 4, AlignLeft, AlignTop, "Mouse Jiggler Stealth"); +#else elements_multiline_text_aligned(canvas, 10, 2, AlignLeft, AlignTop, "Mouse Jiggler Stealth"); +#endif // Display the current min interval in minutes canvas_set_font(canvas, FontSecondary); // Assuming there's a smaller font available From d5339f8270336e362a0dd9af69380845fa4bb95d Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 20 May 2024 16:13:20 +0100 Subject: [PATCH 38/48] [FL-3816] Text Box rework (#3642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * text box: rework text box to use less memory * text box: optimize lines iteration * text box: fix back scrolling * text box: add end focus support * text box: allocate index buffer on stack instead of heap * text box: fix index decrement * apps: add text box debug app * debug: rework text box view app with view stack * debug app: rename text_box_test app to text_box_element_test * text box: rework text box module * debug: update text box view test * text box: fix end focus initial offset * debug: fix memory leak in text box view test app Co-authored-by: あく Co-authored-by: hedger --- .../text_box_element_test/application.fam | 10 + .../text_box_element_test.c} | 2 +- .../application.fam | 6 +- .../text_box_view_test/text_box_view_test.c | 133 ++++++++ applications/services/gui/modules/text_box.c | 313 ++++++++++++------ 5 files changed, 359 insertions(+), 105 deletions(-) create mode 100644 applications/debug/text_box_element_test/application.fam rename applications/debug/{text_box_test/text_box_test.c => text_box_element_test/text_box_element_test.c} (98%) rename applications/debug/{text_box_test => text_box_view_test}/application.fam (55%) create mode 100644 applications/debug/text_box_view_test/text_box_view_test.c diff --git a/applications/debug/text_box_element_test/application.fam b/applications/debug/text_box_element_test/application.fam new file mode 100644 index 0000000000..5e1abcddc8 --- /dev/null +++ b/applications/debug/text_box_element_test/application.fam @@ -0,0 +1,10 @@ +App( + appid="text_box_element_test", + name="Text Box Element Test", + apptype=FlipperAppType.DEBUG, + entry_point="text_box_element_test_app", + requires=["gui"], + stack_size=1 * 1024, + order=140, + fap_category="Debug", +) diff --git a/applications/debug/text_box_test/text_box_test.c b/applications/debug/text_box_element_test/text_box_element_test.c similarity index 98% rename from applications/debug/text_box_test/text_box_test.c rename to applications/debug/text_box_element_test/text_box_element_test.c index b980f686e1..2b9475d2b2 100644 --- a/applications/debug/text_box_test/text_box_test.c +++ b/applications/debug/text_box_element_test/text_box_element_test.c @@ -71,7 +71,7 @@ static void text_box_test_input_callback(InputEvent* input_event, void* ctx) { furi_message_queue_put(event_queue, input_event, FuriWaitForever); } -int32_t text_box_test_app(void* p) { +int32_t text_box_element_test_app(void* p) { UNUSED(p); FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); furi_check(event_queue); diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_view_test/application.fam similarity index 55% rename from applications/debug/text_box_test/application.fam rename to applications/debug/text_box_view_test/application.fam index 823c21d068..e356a278ea 100644 --- a/applications/debug/text_box_test/application.fam +++ b/applications/debug/text_box_view_test/application.fam @@ -1,8 +1,8 @@ App( - appid="text_box_test", - name="Text Box Test", + appid="text_box_view_test", + name="Text Box View Test", apptype=FlipperAppType.DEBUG, - entry_point="text_box_test_app", + entry_point="text_box_view_test_app", requires=["gui"], stack_size=1 * 1024, order=140, diff --git a/applications/debug/text_box_view_test/text_box_view_test.c b/applications/debug/text_box_view_test/text_box_view_test.c new file mode 100644 index 0000000000..4414835ecc --- /dev/null +++ b/applications/debug/text_box_view_test/text_box_view_test.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#define TAG "TextBoxViewTest" + +typedef struct { + TextBoxFont font; + TextBoxFocus focus; + const char* text; +} TextBoxViewTestContent; + +static const TextBoxViewTestContent text_box_view_test_content_arr[] = { + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = "Hello, let's test text box. Press Right and Left to switch content", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "Verify that symbols don't overlap borders: llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllend", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "\n\n\n Start from several newline chars. Verify that scrolling doesn't break.\n\n\n\n\nThe end", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusStart, + .text = + "Let's test big text.\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "The same but with EndFocus\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.", + }, + { + .font = TextBoxFontHex, + .focus = TextBoxFocusEnd, + .text = + "0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999", + }, +}; + +typedef struct { + TextBox* text_box; + ViewDispatcher* view_dispatcher; + size_t current_content_i; +} TextBoxViewTest; + +static void text_box_update_view(TextBoxViewTest* instance) { + text_box_reset(instance->text_box); + + const TextBoxViewTestContent* content = + &text_box_view_test_content_arr[instance->current_content_i]; + text_box_set_font(instance->text_box, content->font); + text_box_set_focus(instance->text_box, content->focus); + text_box_set_text(instance->text_box, content->text); +} + +static bool text_box_switch_view_input_callback(InputEvent* event, void* context) { + bool consumed = false; + TextBoxViewTest* instance = context; + size_t contents_cnt = COUNT_OF(text_box_view_test_content_arr); + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + if(instance->current_content_i < contents_cnt - 1) { + instance->current_content_i++; + text_box_update_view(instance); + consumed = true; + } + } else if(event->key == InputKeyLeft) { + if(instance->current_content_i > 0) { + instance->current_content_i--; + text_box_update_view(instance); + consumed = true; + } + } else if(event->key == InputKeyBack) { + view_dispatcher_stop(instance->view_dispatcher); + } + } + + return consumed; +} + +int32_t text_box_view_test_app(void* p) { + UNUSED(p); + + Gui* gui = furi_record_open(RECORD_GUI); + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_enable_queue(view_dispatcher); + + TextBoxViewTest instance = { + .text_box = text_box_alloc(), + .current_content_i = 0, + .view_dispatcher = view_dispatcher, + }; + + text_box_update_view(&instance); + + View* text_box_switch_view = view_alloc(); + view_set_input_callback(text_box_switch_view, text_box_switch_view_input_callback); + view_set_context(text_box_switch_view, &instance); + + ViewStack* view_stack = view_stack_alloc(); + view_stack_add_view(view_stack, text_box_switch_view); + view_stack_add_view(view_stack, text_box_get_view(instance.text_box)); + + view_dispatcher_add_view(view_dispatcher, 0, view_stack_get_view(view_stack)); + view_dispatcher_switch_to_view(view_dispatcher, 0); + + view_dispatcher_run(view_dispatcher); + + view_dispatcher_remove_view(view_dispatcher, 0); + view_dispatcher_free(view_dispatcher); + view_stack_free(view_stack); + view_free(text_box_switch_view); + text_box_free(instance.text_box); + + furi_record_close(RECORD_GUI); + + return 0; +} diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 9c2959b301..954847c650 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -4,8 +4,13 @@ #include #include -#define TEXT_BOX_MAX_SYMBOL_WIDTH (10) -#define TEXT_BOX_LINE_WIDTH (120) +#define TEXT_BOX_TEXT_WIDTH (120) +#define TEXT_BOX_TEXT_HEIGHT (56) +#define TEXT_BOX_MAX_LINES_PER_SCREEN (10) + +#define TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM (3) +#define TEXT_BOX_LINES_SCROLL_SPEED_FAST (5) +#define TEXT_BOX_LINES_SCROLL_SPEED_SATURATION (9) struct TextBox { View* view; @@ -14,13 +19,19 @@ struct TextBox { }; typedef struct { + TextBoxFont font; + TextBoxFocus focus; const char* text; - char* text_pos; - FuriString* text_formatted; + int32_t scroll_pos; int32_t scroll_num; - TextBoxFont font; - TextBoxFocus focus; + int32_t lines_on_screen; + + int32_t line_offset; + int32_t text_offset; + FuriString* text_on_screen; + FuriString* text_line; + bool formatted; } TextBoxModel; @@ -29,20 +40,11 @@ static void text_box_process_down(TextBox* text_box, uint8_t lines) { text_box->view, TextBoxModel * model, { - if(model->scroll_pos < model->scroll_num - lines) { + if(model->scroll_pos + lines < model->scroll_num) { model->scroll_pos += lines; - for(uint8_t i = 0; i < lines; i++) { - // Search next line start - while(*model->text_pos++ != '\n') - ; - } - } else if(lines > 1) { - lines = model->scroll_num - model->scroll_pos - 1; - model->scroll_pos = model->scroll_num - 1; - for(uint8_t i = 0; i < lines; i++) { - // Search next line start - while(*model->text_pos++ != '\n') - ; + } else { + if(model->scroll_num > 0) { + model->scroll_pos = model->scroll_num - 1; } } }, @@ -54,67 +56,194 @@ static void text_box_process_up(TextBox* text_box, uint8_t lines) { text_box->view, TextBoxModel * model, { - if(model->scroll_pos > lines - 1) { + if(model->scroll_pos - lines > 0) { model->scroll_pos -= lines; - for(uint8_t i = 0; i < lines; i++) { - // Reach last symbol of previous line - model->text_pos--; - // Search previous line start - while((model->text_pos != model->text) && (*(--model->text_pos) != '\n')) - ; - if(*model->text_pos == '\n') { - model->text_pos++; - } - } - } else if(lines > 1) { - lines = model->scroll_pos; + } else { model->scroll_pos = 0; - model->text_pos = (char*)model->text; } }, true); } -static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { - size_t i = 0; +static bool text_box_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + + TextBox* text_box = context; + bool consumed = false; + + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + int32_t scroll_speed = 1; + if(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_FAST) { + if(text_box->button_held_for_ticks % 2) { + scroll_speed = 0; + } else { + scroll_speed = + (text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_SATURATION) ? + TEXT_BOX_LINES_SCROLL_SPEED_FAST : + TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM; + } + } + + if(event->key == InputKeyDown) { + text_box_process_down(text_box, scroll_speed); + consumed = true; + } else if(event->key == InputKeyUp) { + text_box_process_up(text_box, scroll_speed); + consumed = true; + } + + text_box->button_held_for_ticks++; + } else if(event->type == InputTypeRelease) { + text_box->button_held_for_ticks = 0; + consumed = true; + } + + return consumed; +} + +static bool text_box_end_of_text_reached(TextBoxModel* model) { + return model->text[model->text_offset] == '\0'; +} + +static bool text_box_start_of_text_reached(TextBoxModel* model) { + return model->text_offset == 0; +} + +static void text_box_seek_next_line(Canvas* canvas, TextBoxModel* model) { size_t line_width = 0; - const char* str = model->text; - size_t line_num = 0; - while(str[i] != '\0') { - char symb = str[i++]; - if(symb != '\n') { + while(!text_box_end_of_text_reached(model)) { + char symb = model->text[model->text_offset]; + if(symb == '\n') { + model->text_offset++; + break; + } else { size_t glyph_width = canvas_glyph_width(canvas, symb); - if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) { - line_num++; - line_width = 0; - furi_string_push_back(model->text_formatted, '\n'); + if(line_width + glyph_width > TEXT_BOX_TEXT_WIDTH) { + break; } line_width += glyph_width; - } else { - line_num++; - line_width = 0; + model->text_offset++; } - furi_string_push_back(model->text_formatted, symb); } - line_num++; - model->text = furi_string_get_cstr(model->text_formatted); - model->text_pos = (char*)model->text; - size_t lines_on_screen = 56 / canvas_current_font_height(canvas); - if(model->focus == TextBoxFocusEnd && line_num > lines_on_screen) { - // Set text position to 5th line from the end - const char* end = model->text + furi_string_size(model->text_formatted); - for(size_t i = 0; i < line_num - lines_on_screen; i++) { - while(model->text_pos < end) { - if(*model->text_pos++ == '\n') break; - } +} + +static void text_box_seek_end_of_prev_line(TextBoxModel* model) { + do { + if(text_box_start_of_text_reached(model)) break; + model->text_offset--; + if(text_box_start_of_text_reached(model)) break; + if(model->text[model->text_offset] == '\n') { + model->text_offset--; + } + } while(false); +} + +static void text_box_seek_prev_paragraph(TextBoxModel* model) { + while(!text_box_start_of_text_reached(model)) { + if(model->text[model->text_offset] == '\n') { + model->text_offset++; + break; + } + model->text_offset--; + } +} + +static void text_box_seek_prev_line(Canvas* canvas, TextBoxModel* model) { + int32_t start_text_offset = model->text_offset; + + text_box_seek_end_of_prev_line(model); + text_box_seek_prev_paragraph(model); + + int32_t current_text_offset = model->text_offset; + while(true) { + text_box_seek_next_line(canvas, model); + if(model->text_offset == start_text_offset) { + break; + } + current_text_offset = model->text_offset; + } + model->text_offset = current_text_offset; +} + +static void text_box_move_line_offset(Canvas* canvas, TextBoxModel* model, int32_t line_offset) { + if(line_offset >= 0) { + for(int32_t i = 0; i < line_offset; i++) { + text_box_seek_next_line(canvas, model); + } + } else { + for(int32_t i = 0; i < (-line_offset); i++) { + text_box_seek_prev_line(canvas, model); + } + } +} + +static void text_box_update_screen_text(Canvas* canvas, TextBoxModel* model) { + furi_string_reset(model->text_on_screen); + furi_string_reset(model->text_line); + + int32_t start_text_offset = model->text_offset; + + for(int32_t i = 0; i < model->lines_on_screen; i++) { + int32_t current_line_text_offset = model->text_offset; + text_box_seek_next_line(canvas, model); + int32_t next_line_text_offset = model->text_offset; + furi_string_set_strn( + model->text_line, + &model->text[current_line_text_offset], + next_line_text_offset - current_line_text_offset); + size_t str_len = furi_string_size(model->text_line); + if(furi_string_get_char(model->text_line, str_len - 1) != '\n') { + furi_string_push_back(model->text_line, '\n'); + } + furi_string_cat(model->text_on_screen, model->text_line); + + if(text_box_end_of_text_reached(model)) break; + current_line_text_offset = next_line_text_offset; + } + + model->text_offset = start_text_offset; +} + +static void text_box_update_text_on_screen(Canvas* canvas, TextBoxModel* model) { + int32_t line_offset = model->scroll_pos - model->line_offset; + text_box_move_line_offset(canvas, model, line_offset); + text_box_update_screen_text(canvas, model); + model->line_offset = model->scroll_pos; +} + +static void text_box_prepare_model(Canvas* canvas, TextBoxModel* model) { + int32_t lines_num = 0; + model->text_offset = 0; + model->scroll_num = 0; + model->scroll_pos = 0; + model->line_offset = 0; + model->lines_on_screen = TEXT_BOX_TEXT_HEIGHT / canvas_current_font_height(canvas); + + // Cache text offset to quick final text offset update if TextBoxFocusEnd is set + int32_t window_offset[TEXT_BOX_MAX_LINES_PER_SCREEN] = {}; + do { + window_offset[lines_num % model->lines_on_screen] = model->text_offset; + text_box_seek_next_line(canvas, model); + lines_num++; + } while(!text_box_end_of_text_reached(model)); + lines_num++; + + if(model->focus == TextBoxFocusEnd) { + if(lines_num > model->lines_on_screen) { + model->text_offset = window_offset[(lines_num - 1) % model->lines_on_screen]; } - model->scroll_num = line_num - (lines_on_screen - 1); - model->scroll_pos = line_num - lines_on_screen; } else { - model->scroll_num = MAX(line_num - (lines_on_screen - 1), 0u); - model->scroll_pos = 0; + model->text_offset = 0; + } + + if(lines_num > model->lines_on_screen) { + model->scroll_num = lines_num - model->lines_on_screen; + model->scroll_pos = (model->focus == TextBoxFocusEnd) ? model->scroll_num - 1 : 0; } + + text_box_update_screen_text(canvas, model); + model->line_offset = model->scroll_pos; } static void text_box_view_draw_callback(Canvas* canvas, void* _model) { @@ -132,44 +261,17 @@ static void text_box_view_draw_callback(Canvas* canvas, void* _model) { } if(!model->formatted) { - text_box_insert_endline(canvas, model); + text_box_prepare_model(canvas, model); model->formatted = true; } elements_slightly_rounded_frame(canvas, 0, 0, 124, 64); - elements_multiline_text(canvas, 3, 11, model->text_pos); elements_scrollbar(canvas, model->scroll_pos, model->scroll_num); -} -static bool text_box_view_input_callback(InputEvent* event, void* context) { - furi_assert(context); - - TextBox* text_box = context; - bool consumed = false; - if(event->type == InputTypeShort || event->type == InputTypeRepeat) { - int32_t scroll_speed = 1; - if(text_box->button_held_for_ticks > 5) { - if(text_box->button_held_for_ticks % 2) { - scroll_speed = 0; - } else { - scroll_speed = text_box->button_held_for_ticks > 9 ? 5 : 3; - } - } - - if(event->key == InputKeyDown) { - text_box_process_down(text_box, scroll_speed); - consumed = true; - } else if(event->key == InputKeyUp) { - text_box_process_up(text_box, scroll_speed); - consumed = true; - } - - text_box->button_held_for_ticks++; - } else if(event->type == InputTypeRelease) { - text_box->button_held_for_ticks = 0; - consumed = true; + if(model->line_offset != model->scroll_pos) { + text_box_update_text_on_screen(canvas, model); } - return consumed; + elements_multiline_text(canvas, 3, 11, furi_string_get_cstr(model->text_on_screen)); } TextBox* text_box_alloc(void) { @@ -185,7 +287,8 @@ TextBox* text_box_alloc(void) { TextBoxModel * model, { model->text = NULL; - model->text_formatted = furi_string_alloc_set(""); + model->text_on_screen = furi_string_alloc(); + model->text_line = furi_string_alloc(); model->formatted = false; model->font = TextBoxFontText; }, @@ -198,7 +301,13 @@ void text_box_free(TextBox* text_box) { furi_check(text_box); with_view_model( - text_box->view, TextBoxModel * model, { furi_string_free(model->text_formatted); }, true); + text_box->view, + TextBoxModel * model, + { + furi_string_free(model->text_on_screen); + furi_string_free(model->text_line); + }, + true); view_free(text_box->view); free(text_box); } @@ -216,9 +325,15 @@ void text_box_reset(TextBox* text_box) { TextBoxModel * model, { model->text = NULL; - furi_string_set(model->text_formatted, ""); model->font = TextBoxFontText; model->focus = TextBoxFocusStart; + furi_string_reset(model->text_line); + furi_string_reset(model->text_on_screen); + model->line_offset = 0; + model->text_offset = 0; + model->lines_on_screen = 0; + model->scroll_num = 0; + model->scroll_pos = 0; model->formatted = false; }, true); @@ -227,16 +342,12 @@ void text_box_reset(TextBox* text_box) { void text_box_set_text(TextBox* text_box, const char* text) { furi_check(text_box); furi_check(text); - size_t str_length = strlen(text); - size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH; with_view_model( text_box->view, TextBoxModel * model, { model->text = text; - furi_string_reset(model->text_formatted); - furi_string_reserve(model->text_formatted, str_length + formating_margin); model->formatted = false; }, true); From 9e42e00eadd79ab8414ec31b3af1ccb5a14c48d0 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 20 May 2024 21:23:47 +0400 Subject: [PATCH 39/48] Icons: compression fixes & larger dimension support (#3564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * toolbox, gui: fixes for compressed icon handling * ufbt: fixes for generated vscode project * scripts: increased max dimensions for image converter * icon type changes * linter fixes; api sync * gui: docs fix * toolbox: fixed potential decoder buffer overflow * minor cleanup * fbt: sdk: suppressed deprecation warnings in API table * toolbox: compress: added unit tests vscode: now installs resources for unit_tests unit_tests: now loads subghz region data * toolbox: compress: review fixes, pt 1 * compress: now passes decoder buffer size as constructor argument; auto-resize decoder buffer; crash on failed icon decompression * PVS fixes * pvs fixes, pt2 * doxygen fixes * investigating unit test failures * investigating unit test failures * investigating unit test failures * investigating unit test failures * investigating unit test failures * UnitTests: move all tests into plugins, brakes testing * UnitTests: add plugin API and update plugin entrypoints * UniTests: Test runner that works with plugins * fbt: extra filtering for extapps to include in build * UnitTests: filter tests by name * loader: restored API table for unit_test build config * Add various missing symbols to API table * UnitTest: fail on plugin load error * UnitTests: cleanup plugin api and reporting * unit_tests: composite resolver * UnitTests: remove unused declaration * unit_tests, nfc: moved mock nfc implementation to libnfc * unit_tests: api: removed redundant #define * toolbox: compress: removed size_hint for icons; triggering furi_check on oversized icons * gui: icon, icon_animation: removed size hit APIs * Format Sources. Cleanup code. * loader: refuse to start .fal as app * toolbox: compress: fixed memory corruption in operations with small destination buffer; added unit tests for that case * unit_tests: proper test skipping; better selective test interface * unit_tests: moved 'loading' logging to proper location Co-authored-by: あく --- .vscode/example/tasks.json | 2 +- applications/debug/unit_tests/application.fam | 204 ++++++++++++++++- .../unit_tests/compress/compressed.bin | Bin 0 -> 135 bytes .../unit_tests/compress/uncompressed.bin | Bin 0 -> 640 bytes applications/debug/unit_tests/test_index.c | 168 -------------- applications/debug/unit_tests/test_runner.c | 216 ++++++++++++++++++ applications/debug/unit_tests/test_runner.h | 12 + .../{ => tests}/bit_lib/bit_lib_test.c | 6 +- .../debug/unit_tests/{ => tests}/bt/bt_test.c | 4 +- .../debug/unit_tests/tests/common/common.c | 42 ++++ .../unit_tests/tests/compress/compress_test.c | 159 +++++++++++++ .../datetime}/datetimelib_test.c | 6 +- .../dialogs_file_browser_options.c | 4 +- .../{storage => tests/dirwalk}/dirwalk_test.c | 6 +- .../{ => tests}/expansion/expansion_test.c | 4 +- .../flipper_format/flipper_format_test.c | 4 +- .../flipper_format_string_test.c | 4 +- .../float_tools/float_tools_test.c | 4 +- .../{ => tests}/furi/furi_memmgr_test.c | 2 +- .../{ => tests}/furi/furi_pubsub_test.c | 2 +- .../{ => tests}/furi/furi_record_test.c | 2 +- .../unit_tests/{ => tests}/furi/furi_test.c | 4 +- .../{ => tests}/furi_hal/furi_hal_tests.c | 4 +- .../furi_hal_crypto}/furi_hal_crypto_tests.c | 4 +- .../furi_string}/furi_string_test.c | 6 +- .../{ => tests}/infrared/infrared_test.c | 4 +- .../{ => tests}/lfrfid/lfrfid_protocols.c | 6 +- .../{ => tests}/manifest/manifest.c | 6 +- .../debug/unit_tests/{ => tests}/minunit.h | 0 .../unit_tests/{ => tests}/minunit_vars.h | 0 .../unit_tests/{ => tests}/minunit_vars_ex.h | 0 .../unit_tests/{ => tests}/nfc/nfc_test.c | 4 +- .../unit_tests/{ => tests}/power/power_test.c | 4 +- .../protocol_dict/protocol_dict_test.c | 6 +- .../unit_tests/{ => tests}/rpc/rpc_test.c | 4 +- .../{ => tests}/storage/storage_test.c | 4 +- .../{ => tests}/stream/stream_test.c | 4 +- .../{ => tests}/subghz/subghz_test.c | 4 +- applications/debug/unit_tests/tests/test.h | 12 + .../debug/unit_tests/tests/test_api.h | 29 +++ .../{ => tests}/varint/varint_test.c | 8 +- .../debug/unit_tests/unit_test_api_table.cpp | 19 ++ .../debug/unit_tests/unit_test_api_table_i.h | 29 +++ applications/debug/unit_tests/unit_tests.c | 21 ++ applications/services/bt/application.fam | 2 +- .../services/bt/bt_service/bt_keys_storage.h | 8 + .../services/desktop/helpers/slideshow.c | 1 - applications/services/gui/canvas.c | 6 +- applications/services/gui/canvas_i.h | 2 + applications/services/gui/icon.c | 18 +- applications/services/gui/icon.h | 28 ++- applications/services/gui/icon_i.h | 6 +- .../loader/firmware_api/firmware_api.cpp | 14 -- applications/services/loader/loader.c | 6 + .../services/loader/loader_applications.c | 2 +- applications/services/rpc/rpc_i.h | 8 + lib/nfc/SConscript | 1 + lib/nfc/nfc.c | 2 +- .../nfc/nfc_transport.c => lib/nfc/nfc_mock.c | 0 lib/subghz/SConscript | 1 + lib/subghz/subghz_file_encoder_worker.h | 8 + lib/toolbox/SConscript | 3 + lib/toolbox/compress.c | 171 ++++++++------ lib/toolbox/compress.h | 18 +- scripts/assets.py | 4 +- scripts/fbt/appmanifest.py | 28 ++- scripts/fbt_tools/fbt_extapps.py | 3 +- scripts/fbt_tools/fbt_sdk.py | 1 + targets/f18/api_symbols.csv | 36 ++- targets/f18/target.json | 2 +- targets/f7/api_symbols.csv | 50 +++- targets/f7/target.json | 2 +- 72 files changed, 1127 insertions(+), 337 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin create mode 100644 applications/debug/unit_tests/resources/unit_tests/compress/uncompressed.bin delete mode 100644 applications/debug/unit_tests/test_index.c create mode 100644 applications/debug/unit_tests/test_runner.c create mode 100644 applications/debug/unit_tests/test_runner.h rename applications/debug/unit_tests/{ => tests}/bit_lib/bit_lib_test.c (99%) rename applications/debug/unit_tests/{ => tests}/bt/bt_test.c (98%) create mode 100644 applications/debug/unit_tests/tests/common/common.c create mode 100644 applications/debug/unit_tests/tests/compress/compress_test.c rename applications/debug/unit_tests/{datetimelib => tests/datetime}/datetimelib_test.c (98%) rename applications/debug/unit_tests/{dialogs => tests/dialogs_file_browser_options}/dialogs_file_browser_options.c (93%) rename applications/debug/unit_tests/{storage => tests/dirwalk}/dirwalk_test.c (99%) rename applications/debug/unit_tests/{ => tests}/expansion/expansion_test.c (99%) rename applications/debug/unit_tests/{ => tests}/flipper_format/flipper_format_test.c (99%) rename applications/debug/unit_tests/{flipper_format => tests/flipper_format_string}/flipper_format_string_test.c (99%) rename applications/debug/unit_tests/{ => tests}/float_tools/float_tools_test.c (97%) rename applications/debug/unit_tests/{ => tests}/furi/furi_memmgr_test.c (97%) rename applications/debug/unit_tests/{ => tests}/furi/furi_pubsub_test.c (98%) rename applications/debug/unit_tests/{ => tests}/furi/furi_record_test.c (96%) rename applications/debug/unit_tests/{ => tests}/furi/furi_test.c (94%) rename applications/debug/unit_tests/{ => tests}/furi_hal/furi_hal_tests.c (99%) rename applications/debug/unit_tests/{furi_hal => tests/furi_hal_crypto}/furi_hal_crypto_tests.c (99%) rename applications/debug/unit_tests/{furi => tests/furi_string}/furi_string_test.c (99%) rename applications/debug/unit_tests/{ => tests}/infrared/infrared_test.c (99%) rename applications/debug/unit_tests/{ => tests}/lfrfid/lfrfid_protocols.c (99%) rename applications/debug/unit_tests/{ => tests}/manifest/manifest.c (97%) rename applications/debug/unit_tests/{ => tests}/minunit.h (100%) rename applications/debug/unit_tests/{ => tests}/minunit_vars.h (100%) rename applications/debug/unit_tests/{ => tests}/minunit_vars_ex.h (100%) rename applications/debug/unit_tests/{ => tests}/nfc/nfc_test.c (99%) rename applications/debug/unit_tests/{ => tests}/power/power_test.c (98%) rename applications/debug/unit_tests/{ => tests}/protocol_dict/protocol_dict_test.c (98%) rename applications/debug/unit_tests/{ => tests}/rpc/rpc_test.c (99%) rename applications/debug/unit_tests/{ => tests}/storage/storage_test.c (99%) rename applications/debug/unit_tests/{ => tests}/stream/stream_test.c (99%) rename applications/debug/unit_tests/{ => tests}/subghz/subghz_test.c (99%) create mode 100644 applications/debug/unit_tests/tests/test.h create mode 100644 applications/debug/unit_tests/tests/test_api.h rename applications/debug/unit_tests/{ => tests}/varint/varint_test.c (97%) create mode 100644 applications/debug/unit_tests/unit_test_api_table.cpp create mode 100644 applications/debug/unit_tests/unit_test_api_table_i.h create mode 100644 applications/debug/unit_tests/unit_tests.c rename applications/debug/unit_tests/nfc/nfc_transport.c => lib/nfc/nfc_mock.c (100%) diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index e36a1fe47a..1bc6d9ee75 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -79,7 +79,7 @@ "label": "[Debug:unit_tests] Flash (USB)", "group": "build", "type": "shell", - "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb" + "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full" }, { "label": "[Debug] Flash (USB, with resources)", diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index aa25dab279..f5f84ead79 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -2,8 +2,9 @@ App( appid="unit_tests", apptype=FlipperAppType.STARTUP, entry_point="unit_tests_on_system_start", + sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"], cdefines=["APP_UNIT_TESTS"], - requires=["system_settings"], + requires=["system_settings", "subghz_start"], provides=["delay_test"], resources="resources", order=100, @@ -12,9 +13,210 @@ App( App( appid="delay_test", name="Delay Test", + sources=["tests/common/*.c", "tests/rpc/*.c"], apptype=FlipperAppType.SYSTEM, entry_point="delay_test_app", stack_size=1 * 1024, requires=["unit_tests"], order=110, ) + +App( + appid="test_varint", + sources=["tests/common/*.c", "tests/varint/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi", + sources=["tests/common/*.c", "tests/furi/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_hal", + sources=["tests/common/*.c", "tests/furi_hal/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_hal_crypto", + sources=["tests/common/*.c", "tests/furi_hal_crypto/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_furi_string", + sources=["tests/common/*.c", "tests/furi_string/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_storage", + sources=["tests/common/*.c", "tests/storage/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_stream", + sources=["tests/common/*.c", "tests/stream/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_dirwalk", + sources=["tests/common/*.c", "tests/dirwalk/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_manifest", + sources=["tests/common/*.c", "tests/manifest/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_flipper_format", + sources=["tests/common/*.c", "tests/flipper_format/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_flipper_format_string", + sources=["tests/common/*.c", "tests/flipper_format_string/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_rpc", + sources=["tests/common/*.c", "tests/rpc/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_subghz", + sources=["tests/common/*.c", "tests/subghz/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_infrared", + sources=["tests/common/*.c", "tests/infrared/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_nfc", + sources=["tests/common/*.c", "tests/nfc/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_power", + sources=["tests/common/*.c", "tests/power/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_protocol_dict", + sources=["tests/common/*.c", "tests/protocol_dict/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_lfrfid", + sources=["tests/common/*.c", "tests/lfrfid/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_bit_lib", + sources=["tests/common/*.c", "tests/bit_lib/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_datetime", + sources=["tests/common/*.c", "tests/datetime/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_float_tools", + sources=["tests/common/*.c", "tests/float_tools/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_bt", + sources=["tests/common/*.c", "tests/bt/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_dialogs_file_browser_options", + sources=["tests/common/*.c", "tests/dialogs_file_browser_options/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_expansion", + sources=["tests/common/*.c", "tests/expansion/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) + +App( + appid="test_compress", + sources=["tests/common/*.c", "tests/compress/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin b/applications/debug/unit_tests/resources/unit_tests/compress/compressed.bin new file mode 100644 index 0000000000000000000000000000000000000000..95d729295a76c87fa0b6058cef17939599df1731 GIT binary patch literal 135 zcmZQ%Xl7_|NC>b{5#wcVaY_u>kzyvx-{F!JXt5+rSG>n9IdI35GF$lx9w|YVD${(` yr+B3X?M&Ggt3SghE!c9&wOsQ#e(AwGpM0yem;dqq_&@uP|NH;cKmJb==K}yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH bG%_|ZH8Z!cw6eCbwX*jN>NV1 literal 0 HcmV?d00001 diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c deleted file mode 100644 index 5d0282bd77..0000000000 --- a/applications/debug/unit_tests/test_index.c +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include -#include -#include "minunit_vars.h" -#include -#include -#include - -#define TAG "UnitTests" - -int run_minunit_test_furi(void); -int run_minunit_test_furi_hal(void); -int run_minunit_test_furi_hal_crypto(void); -int run_minunit_test_furi_string(void); -int run_minunit_test_infrared(void); -int run_minunit_test_rpc(void); -int run_minunit_test_manifest(void); -int run_minunit_test_flipper_format(void); -int run_minunit_test_flipper_format_string(void); -int run_minunit_test_stream(void); -int run_minunit_test_storage(void); -int run_minunit_test_subghz(void); -int run_minunit_test_dirwalk(void); -int run_minunit_test_power(void); -int run_minunit_test_protocol_dict(void); -int run_minunit_test_lfrfid_protocols(void); -int run_minunit_test_nfc(void); -int run_minunit_test_bit_lib(void); -int run_minunit_test_datetime(void); -int run_minunit_test_float_tools(void); -int run_minunit_test_bt(void); -int run_minunit_test_dialogs_file_browser_options(void); -int run_minunit_test_expansion(void); - -typedef int (*UnitTestEntry)(void); - -typedef struct { - const char* name; - const UnitTestEntry entry; -} UnitTest; - -const UnitTest unit_tests[] = { - {.name = "furi", .entry = run_minunit_test_furi}, - {.name = "furi_hal", .entry = run_minunit_test_furi_hal}, - {.name = "furi_hal_crypto", .entry = run_minunit_test_furi_hal_crypto}, - {.name = "furi_string", .entry = run_minunit_test_furi_string}, - {.name = "storage", .entry = run_minunit_test_storage}, - {.name = "stream", .entry = run_minunit_test_stream}, - {.name = "dirwalk", .entry = run_minunit_test_dirwalk}, - {.name = "manifest", .entry = run_minunit_test_manifest}, - {.name = "flipper_format", .entry = run_minunit_test_flipper_format}, - {.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string}, - {.name = "rpc", .entry = run_minunit_test_rpc}, - {.name = "subghz", .entry = run_minunit_test_subghz}, - {.name = "infrared", .entry = run_minunit_test_infrared}, - {.name = "nfc", .entry = run_minunit_test_nfc}, - {.name = "power", .entry = run_minunit_test_power}, - {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, - {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, - {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, - {.name = "datetime", .entry = run_minunit_test_datetime}, - {.name = "float_tools", .entry = run_minunit_test_float_tools}, - {.name = "bt", .entry = run_minunit_test_bt}, - {.name = "dialogs_file_browser_options", - .entry = run_minunit_test_dialogs_file_browser_options}, - {.name = "expansion", .entry = run_minunit_test_expansion}, -}; - -void minunit_print_progress(void) { - static const char progress[] = {'\\', '|', '/', '-'}; - static uint8_t progress_counter = 0; - static uint32_t last_tick = 0; - uint32_t current_tick = furi_get_tick(); - if(current_tick - last_tick > 20) { - last_tick = current_tick; - printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); - fflush(stdout); - } -} - -void minunit_print_fail(const char* str) { - printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); -} - -void minunit_printf_warning(const char* format, ...) { - FuriString* str = furi_string_alloc(); - va_list args; - va_start(args, format); - furi_string_vprintf(str, format, args); - va_end(args); - printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str)); - furi_string_free(str); -} - -void unit_tests_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); - UNUSED(args); - UNUSED(context); - minunit_run = 0; - minunit_assert = 0; - minunit_fail = 0; - minunit_status = 0; - - Loader* loader = furi_record_open(RECORD_LOADER); - NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - - // TODO FL-3491: lock device while test running - if(loader_is_locked(loader)) { - printf("RPC: stop all applications to run tests\r\n"); - notification_message(notification, &sequence_blink_magenta_100); - } else { - notification_message_block(notification, &sequence_set_only_blue_255); - - uint32_t heap_before = memmgr_get_free_heap(); - uint32_t cycle_counter = furi_get_tick(); - - for(size_t i = 0; i < COUNT_OF(unit_tests); i++) { - if(cli_cmd_interrupt_received(cli)) { - break; - } - - if(furi_string_size(args)) { - if(furi_string_cmp_str(args, unit_tests[i].name) == 0) { - unit_tests[i].entry(); - } else { - printf("Skipping %s\r\n", unit_tests[i].name); - } - } else { - unit_tests[i].entry(); - } - } - - if(minunit_run != 0) { - printf("\r\nFailed tests: %u\r\n", minunit_fail); - - // Time report - cycle_counter = (furi_get_tick() - cycle_counter); - printf("Consumed: %lu ms\r\n", cycle_counter); - - // Wait for tested services and apps to deallocate memory - furi_delay_ms(200); - uint32_t heap_after = memmgr_get_free_heap(); - printf("Leaked: %ld\r\n", heap_before - heap_after); - - // Final Report - if(minunit_fail == 0) { - notification_message(notification, &sequence_success); - printf("Status: PASSED\r\n"); - } else { - notification_message(notification, &sequence_error); - printf("Status: FAILED\r\n"); - } - } - } - - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_LOADER); -} - -void unit_tests_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - // We need to launch apps from tests, so we cannot lock loader - cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); - furi_record_close(RECORD_CLI); -#endif -} diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c new file mode 100644 index 0000000000..6af807086e --- /dev/null +++ b/applications/debug/unit_tests/test_runner.c @@ -0,0 +1,216 @@ +#include "test_runner.h" + +#include "tests/test_api.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern const ElfApiInterface* const unit_tests_api_interface; + +#define TAG "TestRunner" + +#define PLUGINS_PATH "/ext/apps_data/unit_tests/plugins" + +struct TestRunner { + Storage* storage; + Loader* loader; + NotificationApp* notification; + + // Temporary used things + Cli* cli; + FuriString* args; + + // ELF related stuff + CompositeApiResolver* composite_resolver; + + // Report data + int minunit_run; + int minunit_assert; + int minunit_fail; + int minunit_status; +}; + +TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { + TestRunner* instance = malloc(sizeof(TestRunner)); + + instance->storage = furi_record_open(RECORD_STORAGE); + instance->loader = furi_record_open(RECORD_LOADER); + instance->notification = furi_record_open(RECORD_NOTIFICATION); + + instance->cli = cli; + instance->args = args; + + instance->composite_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->composite_resolver, firmware_api_interface); + composite_api_resolver_add(instance->composite_resolver, unit_tests_api_interface); + + return instance; +} + +void test_runner_free(TestRunner* instance) { + furi_assert(instance); + + composite_api_resolver_free(instance->composite_resolver); + + furi_record_close(RECORD_NOTIFICATION); + instance->notification = NULL; + + furi_record_close(RECORD_LOADER); + instance->loader = NULL; + + furi_record_close(RECORD_STORAGE); + instance->storage = NULL; + + free(instance); +} + +static bool test_runner_run_plugin(TestRunner* instance, const char* path) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Loading %s", path); + FlipperApplication* lib = flipper_application_alloc( + instance->storage, composite_api_resolver_get(instance->composite_resolver)); + + bool result = false; + instance->minunit_fail = -1; + do { + FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path); + + if(preload_res != FlipperApplicationPreloadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to preload %s, %d", path, preload_res); + break; + } + + if(!flipper_application_is_plugin(lib)) { + FURI_LOG_E(TAG, "Not a plugin %s", path); + break; + } + + FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib); + if(load_status != FlipperApplicationLoadStatusSuccess) { + FURI_LOG_E(TAG, "Failed to load %s", path); + break; + } + + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(lib); + + const TestApi* test = app_descriptor->entry_point; + + instance->minunit_fail = test->run(); + + instance->minunit_run += test->get_minunit_run(); + instance->minunit_assert += test->get_minunit_assert(); + instance->minunit_status += test->get_minunit_status(); + + result = (instance->minunit_fail == 0); + } while(false); + + flipper_application_free(lib); + + return result; +} + +static void test_runner_run_internal(TestRunner* instance) { + furi_assert(instance); + + char file_name_buffer[256]; + FuriString* file_name = furi_string_alloc(); + FuriString* file_basename = furi_string_alloc(); + File* directory = storage_file_alloc(instance->storage); + + do { + if(!storage_dir_open(directory, PLUGINS_PATH)) { + FURI_LOG_E(TAG, "Failed to open directory %s", PLUGINS_PATH); + break; + } + + while(true) { + if(cli_cmd_interrupt_received(instance->cli)) { + break; + } + + if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) { + break; + } + + furi_string_set(file_name, file_name_buffer); + if(!furi_string_end_with_str(file_name, ".fal")) { + continue; + } + + path_concat(PLUGINS_PATH, file_name_buffer, file_name); + + path_extract_filename(file_name, file_basename, true); + const char* file_basename_cstr = furi_string_get_cstr(file_basename); + + bool result = true; + if(furi_string_size(instance->args)) { + if(furi_string_cmp_str(instance->args, file_basename_cstr) == 0) { + result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name)); + } else { + printf("Skipping %s\r\n", file_basename_cstr); + } + } else { + result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name)); + } + + if(!result) { + printf("Failed to execute test: %s\r\n", file_basename_cstr); + break; + } + } + } while(false); + + storage_dir_close(directory); + storage_file_free(directory); + furi_string_free(file_name); + furi_string_free(file_basename); +} + +void test_runner_run(TestRunner* instance) { + furi_assert(instance); + + // TODO FL-3491: lock device while test running + if(loader_is_locked(instance->loader)) { + printf("RPC: stop all applications to run tests\r\n"); + notification_message(instance->notification, &sequence_blink_magenta_100); + } else { + notification_message_block(instance->notification, &sequence_set_only_blue_255); + + uint32_t heap_before = memmgr_get_free_heap(); + uint32_t cycle_counter = furi_get_tick(); + + test_runner_run_internal(instance); + + if(instance->minunit_run != 0) { + printf("\r\nFailed tests: %d\r\n", instance->minunit_fail); + + // Time report + cycle_counter = (furi_get_tick() - cycle_counter); + printf("Consumed: %lu ms\r\n", cycle_counter); + + // Wait for tested services and apps to deallocate memory + furi_delay_ms(200); + uint32_t heap_after = memmgr_get_free_heap(); + printf("Leaked: %ld\r\n", heap_before - heap_after); + + // Final Report + if(instance->minunit_fail == 0) { + notification_message(instance->notification, &sequence_success); + printf("Status: PASSED\r\n"); + } else { + notification_message(instance->notification, &sequence_error); + printf("Status: FAILED\r\n"); + } + } + } +} diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h new file mode 100644 index 0000000000..024799ebe5 --- /dev/null +++ b/applications/debug/unit_tests/test_runner.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +typedef struct TestRunner TestRunner; +typedef struct Cli Cli; + +TestRunner* test_runner_alloc(Cli* cli, FuriString* args); + +void test_runner_free(TestRunner* isntance); + +void test_runner_run(TestRunner* isntance); \ No newline at end of file diff --git a/applications/debug/unit_tests/bit_lib/bit_lib_test.c b/applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c similarity index 99% rename from applications/debug/unit_tests/bit_lib/bit_lib_test.c rename to applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c index 239a3b562a..d12aeb2aa1 100644 --- a/applications/debug/unit_tests/bit_lib/bit_lib_test.c +++ b/applications/debug/unit_tests/tests/bit_lib/bit_lib_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include MU_TEST(test_bit_lib_increment_index) { @@ -737,4 +737,6 @@ MU_TEST_SUITE(test_bit_lib) { int run_minunit_test_bit_lib(void) { MU_RUN_SUITE(test_bit_lib); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_bit_lib) diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/tests/bt/bt_test.c similarity index 98% rename from applications/debug/unit_tests/bt/bt_test.c rename to applications/debug/unit_tests/tests/bt/bt_test.c index a09b9894ba..e7704381d0 100644 --- a/applications/debug/unit_tests/bt/bt_test.c +++ b/applications/debug/unit_tests/tests/bt/bt_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include @@ -108,3 +108,5 @@ int run_minunit_test_bt(void) { MU_RUN_SUITE(test_bt); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_bt) diff --git a/applications/debug/unit_tests/tests/common/common.c b/applications/debug/unit_tests/tests/common/common.c new file mode 100644 index 0000000000..aef6aa1a4b --- /dev/null +++ b/applications/debug/unit_tests/tests/common/common.c @@ -0,0 +1,42 @@ +#include "../test.h" +#include "../minunit_vars.h" + +#include + +void minunit_print_progress(void) { + static const char progress[] = {'\\', '|', '/', '-'}; + static uint8_t progress_counter = 0; + static uint32_t last_tick = 0; + uint32_t current_tick = furi_get_tick(); + if(current_tick - last_tick > 20) { + last_tick = current_tick; + printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); + fflush(stdout); + } +} + +void minunit_print_fail(const char* str) { + printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); +} + +void minunit_printf_warning(const char* format, ...) { + FuriString* str = furi_string_alloc(); + va_list args; + va_start(args, format); + furi_string_vprintf(str, format, args); + va_end(args); + printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str)); + furi_string_free(str); +} + +int get_minunit_run(void) { + return minunit_run; +} + +int get_minunit_assert(void) { + return minunit_assert; +} + +int get_minunit_status(void) { + return minunit_status; +} diff --git a/applications/debug/unit_tests/tests/compress/compress_test.c b/applications/debug/unit_tests/tests/compress/compress_test.c new file mode 100644 index 0000000000..8f61532971 --- /dev/null +++ b/applications/debug/unit_tests/tests/compress/compress_test.c @@ -0,0 +1,159 @@ +#include "../test.h" + +#include + +#include +#include +#include + +#include + +#include + +#define COMPRESS_UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/compress/" path) + +static void compress_test_reference_comp_decomp() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* compressed_file = storage_file_alloc(storage); + File* decompressed_file = storage_file_alloc(storage); + + mu_assert( + storage_file_open( + compressed_file, + COMPRESS_UNIT_TESTS_PATH("compressed.bin"), + FSAM_READ, + FSOM_OPEN_EXISTING), + "Failed to open compressed file"); + mu_assert( + storage_file_open( + decompressed_file, + COMPRESS_UNIT_TESTS_PATH("uncompressed.bin"), + FSAM_READ, + FSOM_OPEN_EXISTING), + "Failed to open decompressed file"); + + uint64_t compressed_ref_size = storage_file_size(compressed_file); + uint64_t decompressed_ref_size = storage_file_size(decompressed_file); + + mu_assert(compressed_ref_size > 0 && decompressed_ref_size > 0, "Invalid file sizes"); + + uint8_t* compressed_ref_buff = malloc(compressed_ref_size); + uint8_t* decompressed_ref_buff = malloc(decompressed_ref_size); + + mu_assert( + storage_file_read(compressed_file, compressed_ref_buff, compressed_ref_size) == + compressed_ref_size, + "Failed to read compressed file"); + + mu_assert( + storage_file_read(decompressed_file, decompressed_ref_buff, decompressed_ref_size) == + decompressed_ref_size, + "Failed to read decompressed file"); + + storage_file_free(compressed_file); + storage_file_free(decompressed_file); + furi_record_close(RECORD_STORAGE); + + uint8_t* temp_buffer = malloc(1024); + Compress* comp = compress_alloc(1024); + + size_t encoded_size = 0; + mu_assert( + compress_encode( + comp, decompressed_ref_buff, decompressed_ref_size, temp_buffer, 1024, &encoded_size), + "Compress failed"); + + mu_assert(encoded_size == compressed_ref_size, "Encoded size is not equal to reference size"); + + mu_assert( + memcmp(temp_buffer, compressed_ref_buff, compressed_ref_size) == 0, + "Encoded buffer is not equal to reference"); + + size_t decoded_size = 0; + mu_assert( + compress_decode( + comp, compressed_ref_buff, compressed_ref_size, temp_buffer, 1024, &decoded_size), + "Decompress failed"); + + mu_assert( + decoded_size == decompressed_ref_size, "Decoded size is not equal to reference size"); + + mu_assert( + memcmp(temp_buffer, decompressed_ref_buff, decompressed_ref_size) == 0, + "Decoded buffer is not equal to reference"); + + compress_free(comp); + + free(temp_buffer); + free(compressed_ref_buff); + free(decompressed_ref_buff); +} + +static void compress_test_random_comp_decomp() { + static const size_t src_buffer_size = 1024; + static const size_t encoded_buffer_size = 1024; + static const size_t small_buffer_size = src_buffer_size / 32; + + // We only fill half of the buffer with random data, so if anything goes wrong, there's no overflow + static const size_t src_data_size = src_buffer_size / 2; + + Compress* comp = compress_alloc(src_buffer_size); + uint8_t* src_buff = malloc(src_buffer_size); + uint8_t* encoded_buff = malloc(encoded_buffer_size); + uint8_t* decoded_buff = malloc(src_buffer_size); + uint8_t* small_buff = malloc(small_buffer_size); + + furi_hal_random_fill_buf(src_buff, src_data_size); + + size_t encoded_size = 0; + + mu_assert( + compress_encode( + comp, src_buff, src_data_size, encoded_buff, encoded_buffer_size, &encoded_size), + "Compress failed"); + + mu_assert(encoded_size > 0, "Encoded size is zero"); + + size_t small_enc_dec_size = 0; + mu_assert( + compress_encode( + comp, src_buff, src_data_size, small_buff, small_buffer_size, &small_enc_dec_size) == + false, + "Compress to small buffer failed"); + + size_t decoded_size = 0; + mu_assert( + compress_decode( + comp, encoded_buff, encoded_size, decoded_buff, src_buffer_size, &decoded_size), + "Decompress failed"); + mu_assert(decoded_size == src_data_size, "Decoded size is not equal to source size"); + + mu_assert( + memcmp(src_buff, decoded_buff, src_data_size) == 0, + "Decoded buffer is not equal to source"); + + mu_assert( + compress_decode( + comp, encoded_buff, encoded_size, small_buff, small_buffer_size, &small_enc_dec_size) == + false, + "Decompress to small buffer failed"); + + free(small_buff); + free(src_buff); + free(encoded_buff); + free(decoded_buff); + compress_free(comp); +} + +MU_TEST_SUITE(test_compress) { + MU_RUN_TEST(compress_test_random_comp_decomp); + MU_RUN_TEST(compress_test_reference_comp_decomp); +} + +int run_minunit_test_compress(void) { + MU_RUN_SUITE(test_compress); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_compress) diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/tests/datetime/datetimelib_test.c similarity index 98% rename from applications/debug/unit_tests/datetimelib/datetimelib_test.c rename to applications/debug/unit_tests/tests/datetime/datetimelib_test.c index c171a4413b..7d90f18de1 100644 --- a/applications/debug/unit_tests/datetimelib/datetimelib_test.c +++ b/applications/debug/unit_tests/tests/datetime/datetimelib_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include @@ -188,4 +188,6 @@ int run_minunit_test_datetime(void) { MU_RUN_SUITE(test_datetime_validate_datetime); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_datetime) diff --git a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c b/applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c similarity index 93% rename from applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c rename to applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c index 657b933dfc..e42e00f9a7 100644 --- a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c +++ b/applications/debug/unit_tests/tests/dialogs_file_browser_options/dialogs_file_browser_options.c @@ -1,6 +1,6 @@ #include -#include "../minunit.h" +#include "../test.h" MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) { mu_assert( @@ -30,3 +30,5 @@ int run_minunit_test_dialogs_file_browser_options(void) { return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_dialogs_file_browser_options) diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c similarity index 99% rename from applications/debug/unit_tests/storage/dirwalk_test.c rename to applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c index 415ec7dd43..c2474e7554 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/tests/dirwalk/dirwalk_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -269,4 +269,6 @@ MU_TEST_SUITE(test_dirwalk_suite) { int run_minunit_test_dirwalk(void) { MU_RUN_SUITE(test_dirwalk_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_dirwalk) diff --git a/applications/debug/unit_tests/expansion/expansion_test.c b/applications/debug/unit_tests/tests/expansion/expansion_test.c similarity index 99% rename from applications/debug/unit_tests/expansion/expansion_test.c rename to applications/debug/unit_tests/tests/expansion/expansion_test.c index c6a143ed34..79d9b0a57c 100644 --- a/applications/debug/unit_tests/expansion/expansion_test.c +++ b/applications/debug/unit_tests/tests/expansion/expansion_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include @@ -198,3 +198,5 @@ int run_minunit_test_expansion(void) { MU_RUN_SUITE(test_expansion_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_expansion) diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c similarity index 99% rename from applications/debug/unit_tests/flipper_format/flipper_format_test.c rename to applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c index 471055fb0c..1a2eaf222d 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c @@ -2,7 +2,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define TEST_DIR TEST_DIR_NAME "/" #define TEST_DIR_NAME EXT_PATH("unit_tests_tmp") @@ -549,3 +549,5 @@ int run_minunit_test_flipper_format(void) { MU_RUN_SUITE(flipper_format); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_flipper_format) diff --git a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c b/applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c similarity index 99% rename from applications/debug/unit_tests/flipper_format/flipper_format_string_test.c rename to applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c index 99bb5dda59..821b1ba22a 100644 --- a/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c +++ b/applications/debug/unit_tests/tests/flipper_format_string/flipper_format_string_test.c @@ -3,7 +3,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const char* test_filetype = "Flipper Format test"; static const uint32_t test_version = 666; @@ -335,3 +335,5 @@ int run_minunit_test_flipper_format_string(void) { MU_RUN_SUITE(flipper_format_string_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_flipper_format_string) diff --git a/applications/debug/unit_tests/float_tools/float_tools_test.c b/applications/debug/unit_tests/tests/float_tools/float_tools_test.c similarity index 97% rename from applications/debug/unit_tests/float_tools/float_tools_test.c rename to applications/debug/unit_tests/tests/float_tools/float_tools_test.c index 91ac469378..d016ca4a2d 100644 --- a/applications/debug/unit_tests/float_tools/float_tools_test.c +++ b/applications/debug/unit_tests/tests/float_tools/float_tools_test.c @@ -1,7 +1,7 @@ #include #include -#include "../minunit.h" +#include "../test.h" MU_TEST(float_tools_equal_test) { mu_check(float_is_equal(FLT_MAX, FLT_MAX)); @@ -58,3 +58,5 @@ int run_minunit_test_float_tools(void) { MU_RUN_SUITE(float_tools_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_float_tools) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/tests/furi/furi_memmgr_test.c similarity index 97% rename from applications/debug/unit_tests/furi/furi_memmgr_test.c rename to applications/debug/unit_tests/tests/furi/furi_memmgr_test.c index 01e2c17f66..837fd60859 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_memmgr_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include #include diff --git a/applications/debug/unit_tests/furi/furi_pubsub_test.c b/applications/debug/unit_tests/tests/furi/furi_pubsub_test.c similarity index 98% rename from applications/debug/unit_tests/furi/furi_pubsub_test.c rename to applications/debug/unit_tests/tests/furi/furi_pubsub_test.c index 8925ff42ec..fdf15e6f58 100644 --- a/applications/debug/unit_tests/furi/furi_pubsub_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_pubsub_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" const uint32_t context_value = 0xdeadbeef; const uint32_t notify_value_0 = 0x12345678; diff --git a/applications/debug/unit_tests/furi/furi_record_test.c b/applications/debug/unit_tests/tests/furi/furi_record_test.c similarity index 96% rename from applications/debug/unit_tests/furi/furi_record_test.c rename to applications/debug/unit_tests/tests/furi/furi_record_test.c index 10a5a83933..6a2b5a3d26 100644 --- a/applications/debug/unit_tests/furi/furi_record_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_record_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define TEST_RECORD_NAME "test/holding" diff --git a/applications/debug/unit_tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c similarity index 94% rename from applications/debug/unit_tests/furi/furi_test.c rename to applications/debug/unit_tests/tests/furi/furi_test.c index e287f9927f..eab9a917d8 100644 --- a/applications/debug/unit_tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" // v2 tests void test_furi_create_open(void); @@ -55,3 +55,5 @@ int run_minunit_test_furi(void) { return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c similarity index 99% rename from applications/debug/unit_tests/furi_hal/furi_hal_tests.c rename to applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c index 0c5cec8a61..985daa03d7 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/tests/furi_hal/furi_hal_tests.c @@ -4,7 +4,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #include #define DATA_SIZE 4 @@ -232,3 +232,5 @@ int run_minunit_test_furi_hal(void) { MU_RUN_SUITE(furi_hal_i2c_ext_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi_hal) diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c b/applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c similarity index 99% rename from applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c rename to applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c index c2bd6c5f81..0a264a18aa 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_crypto_tests.c +++ b/applications/debug/unit_tests/tests/furi_hal_crypto/furi_hal_crypto_tests.c @@ -1,7 +1,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const uint8_t key_ctr_1[32] = { 0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C, @@ -600,3 +600,5 @@ int run_minunit_test_furi_hal_crypto(void) { MU_RUN_SUITE(furi_hal_crypto_gcm_test); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_furi_hal_crypto) diff --git a/applications/debug/unit_tests/furi/furi_string_test.c b/applications/debug/unit_tests/tests/furi_string/furi_string_test.c similarity index 99% rename from applications/debug/unit_tests/furi/furi_string_test.c rename to applications/debug/unit_tests/tests/furi_string/furi_string_test.c index 853076b67b..949d8c0488 100644 --- a/applications/debug/unit_tests/furi/furi_string_test.c +++ b/applications/debug/unit_tests/tests/furi_string/furi_string_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" static void test_setup(void) { } @@ -466,4 +466,6 @@ int run_minunit_test_furi_string(void) { MU_RUN_SUITE(test_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_furi_string) diff --git a/applications/debug/unit_tests/infrared/infrared_test.c b/applications/debug/unit_tests/tests/infrared/infrared_test.c similarity index 99% rename from applications/debug/unit_tests/infrared/infrared_test.c rename to applications/debug/unit_tests/tests/infrared/infrared_test.c index 4442fedb30..922577aa86 100644 --- a/applications/debug/unit_tests/infrared/infrared_test.c +++ b/applications/debug/unit_tests/tests/infrared/infrared_test.c @@ -2,7 +2,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" #define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/") #define IR_TEST_FILE_PREFIX "test_" @@ -549,3 +549,5 @@ int run_minunit_test_infrared(void) { MU_RUN_SUITE(infrared_test); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_infrared) diff --git a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c similarity index 99% rename from applications/debug/unit_tests/lfrfid/lfrfid_protocols.c rename to applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c index 6a7dbab9cb..fc380ecc49 100644 --- a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c +++ b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -550,4 +550,6 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) { int run_minunit_test_lfrfid_protocols(void) { MU_RUN_SUITE(test_lfrfid_protocols_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_lfrfid_protocols) diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/tests/manifest/manifest.c similarity index 97% rename from applications/debug/unit_tests/manifest/manifest.c rename to applications/debug/unit_tests/tests/manifest/manifest.c index e8ac93d7c0..fcbab0ae8a 100644 --- a/applications/debug/unit_tests/manifest/manifest.c +++ b/applications/debug/unit_tests/tests/manifest/manifest.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include #define TAG "Manifest" @@ -72,4 +72,6 @@ MU_TEST_SUITE(manifest_suite) { int run_minunit_test_manifest(void) { MU_RUN_SUITE(manifest_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_manifest) diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h similarity index 100% rename from applications/debug/unit_tests/minunit.h rename to applications/debug/unit_tests/tests/minunit.h diff --git a/applications/debug/unit_tests/minunit_vars.h b/applications/debug/unit_tests/tests/minunit_vars.h similarity index 100% rename from applications/debug/unit_tests/minunit_vars.h rename to applications/debug/unit_tests/tests/minunit_vars.h diff --git a/applications/debug/unit_tests/minunit_vars_ex.h b/applications/debug/unit_tests/tests/minunit_vars_ex.h similarity index 100% rename from applications/debug/unit_tests/minunit_vars_ex.h rename to applications/debug/unit_tests/tests/minunit_vars_ex.h diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c similarity index 99% rename from applications/debug/unit_tests/nfc/nfc_test.c rename to applications/debug/unit_tests/tests/nfc/nfc_test.c index 8bb88df9ab..4de364a09d 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -24,7 +24,7 @@ #include #include -#include "../minunit.h" +#include "../test.h" #define TAG "NfcTest" @@ -820,3 +820,5 @@ int run_minunit_test_nfc(void) { MU_RUN_SUITE(nfc); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_nfc) diff --git a/applications/debug/unit_tests/power/power_test.c b/applications/debug/unit_tests/tests/power/power_test.c similarity index 98% rename from applications/debug/unit_tests/power/power_test.c rename to applications/debug/unit_tests/tests/power/power_test.c index 03e0ad269f..d121acba64 100644 --- a/applications/debug/unit_tests/power/power_test.c +++ b/applications/debug/unit_tests/tests/power/power_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" static void power_test_deinit(void) { // Try to reset to default charge voltage limit @@ -67,3 +67,5 @@ int run_minunit_test_power(void) { MU_RUN_SUITE(test_power_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_power) diff --git a/applications/debug/unit_tests/protocol_dict/protocol_dict_test.c b/applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c similarity index 98% rename from applications/debug/unit_tests/protocol_dict/protocol_dict_test.c rename to applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c index 6ecaa1a52f..9ced106abd 100644 --- a/applications/debug/unit_tests/protocol_dict/protocol_dict_test.c +++ b/applications/debug/unit_tests/tests/protocol_dict/protocol_dict_test.c @@ -1,5 +1,5 @@ #include -#include "../minunit.h" +#include "../test.h" #include typedef enum { @@ -219,4 +219,6 @@ MU_TEST_SUITE(test_protocol_dict_suite) { int run_minunit_test_protocol_dict(void) { MU_RUN_SUITE(test_protocol_dict_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_protocol_dict) diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c similarity index 99% rename from applications/debug/unit_tests/rpc/rpc_test.c rename to applications/debug/unit_tests/tests/rpc/rpc_test.c index cd692d69de..be7b331f96 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c @@ -17,7 +17,7 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include @@ -1864,3 +1864,5 @@ int32_t delay_test_app(void* p) { return 0; } + +TEST_API_DEFINE(run_minunit_test_rpc) diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c similarity index 99% rename from applications/debug/unit_tests/storage/storage_test.c rename to applications/debug/unit_tests/tests/storage/storage_test.c index e4361f06c3..1d887ced06 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -1,4 +1,4 @@ -#include "../minunit.h" +#include "../test.h" #include #include @@ -712,3 +712,5 @@ int run_minunit_test_storage(void) { MU_RUN_SUITE(test_md5_calc_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_storage) diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/tests/stream/stream_test.c similarity index 99% rename from applications/debug/unit_tests/stream/stream_test.c rename to applications/debug/unit_tests/tests/stream/stream_test.c index 3e31773b4f..a9c22c7232 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/tests/stream/stream_test.c @@ -4,7 +4,7 @@ #include #include #include -#include "../minunit.h" +#include "../test.h" static const char* stream_test_data = "I write differently from what I speak, " "I speak differently from what I think, " @@ -530,3 +530,5 @@ int run_minunit_test_stream(void) { MU_RUN_SUITE(stream_suite); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_stream) diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c similarity index 99% rename from applications/debug/unit_tests/subghz/subghz_test.c rename to applications/debug/unit_tests/tests/subghz/subghz_test.c index a2f00885b4..030a4223fd 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -1,6 +1,6 @@ #include #include -#include "../minunit.h" +#include "../test.h" #include #include #include @@ -908,3 +908,5 @@ int run_minunit_test_subghz(void) { MU_RUN_SUITE(subghz); return MU_EXIT_CODE; } + +TEST_API_DEFINE(run_minunit_test_subghz) diff --git a/applications/debug/unit_tests/tests/test.h b/applications/debug/unit_tests/tests/test.h new file mode 100644 index 0000000000..bd7acc00e7 --- /dev/null +++ b/applications/debug/unit_tests/tests/test.h @@ -0,0 +1,12 @@ +#pragma once + +// Framework +#include "minunit.h" + +#include "test_api.h" + +int get_minunit_run(void); + +int get_minunit_assert(void); + +int get_minunit_status(void); diff --git a/applications/debug/unit_tests/tests/test_api.h b/applications/debug/unit_tests/tests/test_api.h new file mode 100644 index 0000000000..55b9f7885b --- /dev/null +++ b/applications/debug/unit_tests/tests/test_api.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#define APPID "UnitTest" +#define API_VERSION (0u) + +typedef struct { + int (*run)(void); + int (*get_minunit_run)(void); + int (*get_minunit_assert)(void); + int (*get_minunit_status)(void); +} TestApi; + +#define TEST_API_DEFINE(entrypoint) \ + const TestApi test_api = { \ + .run = entrypoint, \ + .get_minunit_run = get_minunit_run, \ + .get_minunit_assert = get_minunit_assert, \ + .get_minunit_status = get_minunit_status, \ + }; \ + const FlipperAppPluginDescriptor app_descriptor = { \ + .appid = APPID, \ + .ep_api_version = API_VERSION, \ + .entry_point = &test_api, \ + }; \ + const FlipperAppPluginDescriptor* get_api(void) { \ + return &app_descriptor; \ + } diff --git a/applications/debug/unit_tests/varint/varint_test.c b/applications/debug/unit_tests/tests/varint/varint_test.c similarity index 97% rename from applications/debug/unit_tests/varint/varint_test.c rename to applications/debug/unit_tests/tests/varint/varint_test.c index ac444013d3..3fdf5115ab 100644 --- a/applications/debug/unit_tests/varint/varint_test.c +++ b/applications/debug/unit_tests/tests/varint/varint_test.c @@ -1,6 +1,8 @@ #include #include -#include "../minunit.h" + +#include "../test.h" + #include #include @@ -85,4 +87,6 @@ MU_TEST_SUITE(test_varint_suite) { int run_minunit_test_varint(void) { MU_RUN_SUITE(test_varint_suite); return MU_EXIT_CODE; -} \ No newline at end of file +} + +TEST_API_DEFINE(run_minunit_test_varint) diff --git a/applications/debug/unit_tests/unit_test_api_table.cpp b/applications/debug/unit_tests/unit_test_api_table.cpp new file mode 100644 index 0000000000..8205d43722 --- /dev/null +++ b/applications/debug/unit_tests/unit_test_api_table.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "unit_test_api_table_i.h" + +static_assert(!has_hash_collisions(unit_tests_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface unit_tests_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + unit_tests_api_table.cbegin(), + unit_tests_api_table.cend(), +}; + +extern "C" const ElfApiInterface* const unit_tests_api_interface = + &unit_tests_hashtable_api_interface; diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h new file mode 100644 index 0000000000..8c2fa4687f --- /dev/null +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +#include +#include + +static constexpr auto unit_tests_api_table = sort(create_array_t( + API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), + API_METHOD(resource_manifest_reader_free, void, (ResourceManifestReader*)), + API_METHOD(resource_manifest_reader_open, bool, (ResourceManifestReader*, const char* filename)), + API_METHOD(resource_manifest_reader_next, ResourceManifestEntry*, (ResourceManifestReader*)), + API_METHOD(resource_manifest_reader_previous, ResourceManifestEntry*, (ResourceManifestReader*)), + API_METHOD(slix_process_iso15693_3_error, SlixError, (Iso15693_3Error)), + API_METHOD(iso15693_3_poller_get_data, const Iso15693_3Data*, (Iso15693_3Poller*)), + API_METHOD(rpc_system_storage_get_error, PB_CommandStatus, (FS_Error)), + API_METHOD(xQueueSemaphoreTake, BaseType_t, (QueueHandle_t, TickType_t)), + API_METHOD(vQueueDelete, void, (QueueHandle_t)), + API_METHOD( + xQueueGenericCreate, + QueueHandle_t, + (const UBaseType_t, const UBaseType_t, const uint8_t)), + API_METHOD( + xQueueGenericSend, + BaseType_t, + (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), + API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c new file mode 100644 index 0000000000..237cb9080e --- /dev/null +++ b/applications/debug/unit_tests/unit_tests.c @@ -0,0 +1,21 @@ +#include +#include + +#include "test_runner.h" + +void unit_tests_cli(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(context); + + TestRunner* test_runner = test_runner_alloc(cli, args); + test_runner_run(test_runner); + test_runner_free(test_runner); +} + +void unit_tests_on_system_start(void) { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); + furi_record_close(RECORD_CLI); +#endif +} diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 2e97dc1d65..2d2840e3a5 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -14,7 +14,7 @@ App( ], stack_size=1 * 1024, order=20, - sdk_headers=["bt_service/bt.h"], + sdk_headers=["bt_service/bt.h", "bt_service/bt_keys_storage.h"], ) App( diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index cb808ca304..587dd570dd 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -3,6 +3,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct BtKeysStorage BtKeysStorage; BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); @@ -18,3 +22,7 @@ bool bt_keys_storage_load(BtKeysStorage* instance); bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); bool bt_keys_storage_delete(BtKeysStorage* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/desktop/helpers/slideshow.c b/applications/services/desktop/helpers/slideshow.c index efd02c4bc8..ec02f27a1d 100644 --- a/applications/services/desktop/helpers/slideshow.c +++ b/applications/services/desktop/helpers/slideshow.c @@ -1,6 +1,5 @@ #include "slideshow.h" -#include #include #include #include diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index c293165879..de09305aab 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -15,7 +15,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = { Canvas* canvas_init(void) { Canvas* canvas = malloc(sizeof(Canvas)); - canvas->compress_icon = compress_icon_alloc(); + canvas->compress_icon = compress_icon_alloc(ICON_DECOMPRESSOR_BUFFER_SIZE); // Initialize mutex canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -390,7 +390,7 @@ void canvas_draw_icon_ex( x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data); canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); } @@ -402,7 +402,7 @@ void canvas_draw_icon(Canvas* canvas, int32_t x, int32_t y, const Icon* icon) { x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data); canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); } diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index c204b970fb..449db71dbc 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -12,6 +12,8 @@ #include #include +#define ICON_DECOMPRESSOR_BUFFER_SIZE (128u * 64 / 8) + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/icon.c b/applications/services/gui/icon.c index e216b92551..6e015ed75a 100644 --- a/applications/services/gui/icon.c +++ b/applications/services/gui/icon.c @@ -1,13 +1,16 @@ +#include "icon.h" #include "icon_i.h" #include -uint8_t icon_get_width(const Icon* instance) { +#include + +uint16_t icon_get_width(const Icon* instance) { furi_check(instance); return instance->width; } -uint8_t icon_get_height(const Icon* instance) { +uint16_t icon_get_height(const Icon* instance) { furi_check(instance); return instance->height; @@ -16,5 +19,14 @@ uint8_t icon_get_height(const Icon* instance) { const uint8_t* icon_get_data(const Icon* instance) { furi_check(instance); - return instance->frames[0]; + return icon_get_frame_data(instance, 0); +} + +uint32_t icon_get_frame_count(const Icon* instance) { + return instance->frame_count; +} + +const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame) { + furi_check(frame < instance->frame_count); + return instance->frames[frame]; } diff --git a/applications/services/gui/icon.h b/applications/services/gui/icon.h index e33c4d34d2..fc19c0a7df 100644 --- a/applications/services/gui/icon.h +++ b/applications/services/gui/icon.h @@ -6,6 +6,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -19,7 +20,7 @@ typedef struct Icon Icon; * * @return width in pixels */ -uint8_t icon_get_width(const Icon* instance); +uint16_t icon_get_width(const Icon* instance); /** Get icon height * @@ -27,15 +28,32 @@ uint8_t icon_get_width(const Icon* instance); * * @return height in pixels */ -uint8_t icon_get_height(const Icon* instance); +uint16_t icon_get_height(const Icon* instance); -/** Get Icon XBM bitmap data +/** Get Icon XBM bitmap data for the first frame * * @param[in] instance pointer to Icon data * - * @return pointer to XBM bitmap data + * @return pointer to compressed XBM bitmap data */ -const uint8_t* icon_get_data(const Icon* instance); +FURI_DEPRECATED const uint8_t* icon_get_data(const Icon* instance); + +/** Get Icon frame count + * + * @param[in] instance pointer to Icon data + * + * @return frame count + */ +uint32_t icon_get_frame_count(const Icon* instance); + +/** Get Icon XBM bitmap data for a particular frame + * + * @param[in] instance pointer to Icon data + * @param[in] frame frame index + * + * @return pointer to compressed XBM bitmap data + */ +const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame); #ifdef __cplusplus } diff --git a/applications/services/gui/icon_i.h b/applications/services/gui/icon_i.h index 58cac8b412..e33cdbf236 100644 --- a/applications/services/gui/icon_i.h +++ b/applications/services/gui/icon_i.h @@ -4,11 +4,11 @@ */ #pragma once -#include "icon.h" +#include struct Icon { - const uint8_t width; - const uint8_t height; + const uint16_t width; + const uint16_t height; const uint8_t frame_count; const uint8_t frame_rate; const uint8_t* const* frames; diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 833f99abe4..45953eddf4 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -10,19 +10,6 @@ static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); -#ifdef APP_UNIT_TESTS -constexpr HashtableApiInterface mock_elf_api_interface{ - { - .api_version_major = 0, - .api_version_minor = 0, - .resolver_callback = &elf_resolve_from_hashtable, - }, - nullptr, - nullptr, -}; - -const ElfApiInterface* const firmware_api_interface = &mock_elf_api_interface; -#else constexpr HashtableApiInterface elf_api_interface{ { .api_version_major = (elf_api_version >> 16), @@ -33,7 +20,6 @@ constexpr HashtableApiInterface elf_api_interface{ elf_api_table.cend(), }; const ElfApiInterface* const firmware_api_interface = &elf_api_interface; -#endif extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { *major = firmware_api_interface->api_version_major; diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index cedc2e83ed..c02eefd044 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -353,6 +353,12 @@ static LoaderStatus loader_start_external_app( FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); + if(flipper_application_is_plugin(loader->app.fap)) { + status = loader_make_status_error( + LoaderStatusErrorInternal, error_message, "Plugin %s is not runnable", path); + break; + } + loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); FuriString* app_name = furi_string_alloc(); path_extract_filename_no_ext(path, app_name); diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index d52aec097d..0c5d93cc8e 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -92,7 +92,7 @@ static bool loader_applications_item_callback( path, loader_applications_app->storage, icon_ptr, item_name); } else { path_extract_filename(path, item_name, false); - memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE); + memcpy(*icon_ptr, icon_get_frame_data(&I_js_script_10px, 0), FAP_MANIFEST_MAX_ICON_SIZE); return true; } } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index ffca50231c..20baca7b12 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -7,6 +7,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void* (*RpcSystemAlloc)(RpcSession* session); typedef void (*RpcSystemFree)(void* context); typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context); @@ -45,3 +49,7 @@ void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index f047101e6c..c6caffdb08 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -33,6 +33,7 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), + File("protocols/slix/slix_poller.h"), File("protocols/st25tb/st25tb_poller.h"), File("protocols/felica/felica_poller.h"), # Listeners diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 59d254592c..d0d342a6bc 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -660,4 +660,4 @@ NfcError nfc_felica_listener_set_sensf_res_data( return nfc_process_hal_error(error); } -#endif // APP_UNIT_TESTS +#endif // FW_CFG_unit_tests diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/lib/nfc/nfc_mock.c similarity index 100% rename from applications/debug/unit_tests/nfc/nfc_transport.c rename to lib/nfc/nfc_mock.c diff --git a/lib/subghz/SConscript b/lib/subghz/SConscript index d0bc2a2543..933cd8f999 100644 --- a/lib/subghz/SConscript +++ b/lib/subghz/SConscript @@ -25,6 +25,7 @@ env.Append( File("subghz_protocol_registry.h"), File("devices/cc1101_configs.h"), File("devices/cc1101_int/cc1101_int_interconnect.h"), + File("subghz_file_encoder_worker.h"), ], ) diff --git a/lib/subghz/subghz_file_encoder_worker.h b/lib/subghz/subghz_file_encoder_worker.h index ae2f4a2a09..a6a24dc74f 100644 --- a/lib/subghz/subghz_file_encoder_worker.h +++ b/lib/subghz/subghz_file_encoder_worker.h @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef void (*SubGhzFileEncoderWorkerCallbackEnd)(void* context); typedef struct SubGhzFileEncoderWorker SubGhzFileEncoderWorker; @@ -59,3 +63,7 @@ void subghz_file_encoder_worker_stop(SubGhzFileEncoderWorker* instance); * @return bool - true if running */ bool subghz_file_encoder_worker_is_running(SubGhzFileEncoderWorker* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 121362424e..11e01a8c9d 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -35,6 +35,9 @@ env.Append( File("simple_array.h"), File("bit_buffer.h"), File("keys_dict.h"), + File("pulse_protocols/pulse_glue.h"), + File("md5_calc.h"), + File("varint.h"), ], ) diff --git a/lib/toolbox/compress.c b/lib/toolbox/compress.c index 70db479680..780bea27ab 100644 --- a/lib/toolbox/compress.c +++ b/lib/toolbox/compress.c @@ -3,6 +3,9 @@ #include #include #include +#include + +#define TAG "Compress" /** Defines encoder and decoder window size */ #define COMPRESS_EXP_BUFF_SIZE_LOG (8u) @@ -10,9 +13,16 @@ /** Defines encoder and decoder lookahead buffer size */ #define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u) -/** Buffer sizes for input and output data */ -#define COMPRESS_ICON_ENCODED_BUFF_SIZE (1024u) -#define COMPRESS_ICON_DECODED_BUFF_SIZE (1024u) +/** Buffer size for input data */ +#define COMPRESS_ICON_ENCODED_BUFF_SIZE (256u) + +static bool compress_decode_internal( + heatshrink_decoder* decoder, + const uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size); typedef struct { uint8_t is_compressed; @@ -24,55 +34,51 @@ _Static_assert(sizeof(CompressHeader) == 4, "Incorrect CompressHeader size"); struct CompressIcon { heatshrink_decoder* decoder; - uint8_t decoded_buff[COMPRESS_ICON_DECODED_BUFF_SIZE]; + uint8_t* buffer; + size_t buffer_size; }; -CompressIcon* compress_icon_alloc(void) { +CompressIcon* compress_icon_alloc(size_t decode_buf_size) { CompressIcon* instance = malloc(sizeof(CompressIcon)); instance->decoder = heatshrink_decoder_alloc( COMPRESS_ICON_ENCODED_BUFF_SIZE, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG); heatshrink_decoder_reset(instance->decoder); - memset(instance->decoded_buff, 0, sizeof(instance->decoded_buff)); + + instance->buffer_size = decode_buf_size + 4; /* To account for heatshrink's poller quirks */ + instance->buffer = malloc(instance->buffer_size); return instance; } void compress_icon_free(CompressIcon* instance) { furi_check(instance); + free(instance->buffer); heatshrink_decoder_free(instance->decoder); free(instance); } -void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff) { +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output) { furi_check(instance); furi_check(icon_data); - furi_check(decoded_buff); + furi_check(output); CompressHeader* header = (CompressHeader*)icon_data; if(header->is_compressed) { - size_t data_processed = 0; - heatshrink_decoder_sink( + size_t decoded_size = 0; + /* If decompression fails - check that decode_buf_size is large enough */ + furi_check(compress_decode_internal( instance->decoder, - (uint8_t*)&icon_data[sizeof(CompressHeader)], - header->compressed_buff_size, - &data_processed); - while(1) { - HSD_poll_res res = heatshrink_decoder_poll( - instance->decoder, - instance->decoded_buff, - sizeof(instance->decoded_buff), - &data_processed); - furi_check((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE)); - if(res != HSDR_POLL_MORE) { - break; - } - } - heatshrink_decoder_reset(instance->decoder); - *decoded_buff = instance->decoded_buff; + icon_data, + /* Decoder will check/process headers again - need to pass them */ + sizeof(CompressHeader) + header->compressed_buff_size, + instance->buffer, + instance->buffer_size, + &decoded_size)); + *output = instance->buffer; } else { - *decoded_buff = (uint8_t*)&icon_data[1]; + *output = (uint8_t*)&icon_data[1]; } } @@ -81,12 +87,6 @@ struct Compress { heatshrink_decoder* decoder; }; -static void compress_reset(Compress* compress) { - furi_assert(compress); - heatshrink_encoder_reset(compress->encoder); - heatshrink_decoder_reset(compress->decoder); -} - Compress* compress_alloc(uint16_t compress_buff_size) { Compress* compress = malloc(sizeof(Compress)); compress->encoder = @@ -105,16 +105,16 @@ void compress_free(Compress* compress) { free(compress); } -bool compress_encode( - Compress* compress, +static bool compress_encode_internal( + heatshrink_encoder* encoder, uint8_t* data_in, size_t data_in_size, uint8_t* data_out, size_t data_out_size, size_t* data_res_size) { - furi_assert(compress); - furi_assert(data_in); - furi_assert(data_in_size); + furi_check(encoder); + furi_check(data_in); + furi_check(data_in_size); size_t sink_size = 0; size_t poll_size = 0; @@ -125,10 +125,10 @@ bool compress_encode( size_t sunk = 0; size_t res_buff_size = sizeof(CompressHeader); - // Sink data to encoding buffer + /* Sink data to encoding buffer */ while((sunk < data_in_size) && !encode_failed) { - sink_res = heatshrink_encoder_sink( - compress->encoder, &data_in[sunk], data_in_size - sunk, &sink_size); + sink_res = + heatshrink_encoder_sink(encoder, &data_in[sunk], data_in_size - sunk, &sink_size); if(sink_res != HSER_SINK_OK) { encode_failed = true; break; @@ -136,10 +136,7 @@ bool compress_encode( sunk += sink_size; do { poll_res = heatshrink_encoder_poll( - compress->encoder, - &data_out[res_buff_size], - data_out_size - res_buff_size, - &poll_size); + encoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); if(poll_res < 0) { encode_failed = true; break; @@ -148,31 +145,30 @@ bool compress_encode( } while(poll_res == HSER_POLL_MORE); } - // Notify sinking complete and poll encoded data - finish_res = heatshrink_encoder_finish(compress->encoder); + /* Notify sinking complete and poll encoded data */ + finish_res = heatshrink_encoder_finish(encoder); if(finish_res < 0) { encode_failed = true; } else { do { poll_res = heatshrink_encoder_poll( - compress->encoder, - &data_out[res_buff_size], - data_out_size - 4 - res_buff_size, - &poll_size); + encoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); if(poll_res < 0) { encode_failed = true; break; } res_buff_size += poll_size; - finish_res = heatshrink_encoder_finish(compress->encoder); + finish_res = heatshrink_encoder_finish(encoder); } while(finish_res != HSER_FINISH_DONE); } bool result = true; - // Write encoded data to output buffer if compression is efficient. Else - write header and original data + /* Write encoded data to output buffer if compression is efficient. Otherwise, write header and original data */ if(!encode_failed && (res_buff_size < data_in_size + 1)) { CompressHeader header = { - .is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size}; + .is_compressed = 0x01, + .reserved = 0x00, + .compressed_buff_size = res_buff_size - sizeof(CompressHeader)}; memcpy(data_out, &header, sizeof(header)); *data_res_size = res_buff_size; } else if(data_out_size > data_in_size) { @@ -183,22 +179,21 @@ bool compress_encode( *data_res_size = 0; result = false; } - compress_reset(compress); - + heatshrink_encoder_reset(encoder); return result; } -bool compress_decode( - Compress* compress, - uint8_t* data_in, +static bool compress_decode_internal( + heatshrink_decoder* decoder, + const uint8_t* data_in, size_t data_in_size, uint8_t* data_out, size_t data_out_size, size_t* data_res_size) { - furi_assert(compress); - furi_assert(data_in); - furi_assert(data_out); - furi_assert(data_res_size); + furi_check(decoder); + furi_check(data_in); + furi_check(data_out); + furi_check(data_res_size); bool result = false; bool decode_failed = false; @@ -211,12 +206,15 @@ bool compress_decode( CompressHeader* header = (CompressHeader*)data_in; if(header->is_compressed) { - // Sink data to decoding buffer + /* Sink data to decoding buffer */ size_t compressed_size = header->compressed_buff_size; - size_t sunk = sizeof(CompressHeader); + size_t sunk = 0; while(sunk < compressed_size && !decode_failed) { sink_res = heatshrink_decoder_sink( - compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size); + decoder, + (uint8_t*)&data_in[sizeof(CompressHeader) + sunk], + compressed_size - sunk, + &sink_size); if(sink_res < 0) { decode_failed = true; break; @@ -224,25 +222,28 @@ bool compress_decode( sunk += sink_size; do { poll_res = heatshrink_decoder_poll( - compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size); - if(poll_res < 0) { + decoder, &data_out[res_buff_size], data_out_size - res_buff_size, &poll_size); + if((poll_res < 0) || ((data_out_size - res_buff_size) == 0)) { decode_failed = true; break; } res_buff_size += poll_size; } while(poll_res == HSDR_POLL_MORE); } - // Notify sinking complete and poll decoded data + /* Notify sinking complete and poll decoded data */ if(!decode_failed) { - finish_res = heatshrink_decoder_finish(compress->decoder); + finish_res = heatshrink_decoder_finish(decoder); if(finish_res < 0) { decode_failed = true; } else { do { poll_res = heatshrink_decoder_poll( - compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size); + decoder, + &data_out[res_buff_size], + data_out_size - res_buff_size, + &poll_size); res_buff_size += poll_size; - finish_res = heatshrink_decoder_finish(compress->decoder); + finish_res = heatshrink_decoder_finish(decoder); } while(finish_res != HSDR_FINISH_DONE); } } @@ -253,9 +254,31 @@ bool compress_decode( *data_res_size = data_in_size - 1; result = true; } else { + /* Not enough space in output buffer */ result = false; } - compress_reset(compress); - + heatshrink_decoder_reset(decoder); return result; } + +bool compress_encode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size) { + return compress_encode_internal( + compress->encoder, data_in, data_in_size, data_out, data_out_size, data_res_size); +} + +bool compress_decode( + Compress* compress, + uint8_t* data_in, + size_t data_in_size, + uint8_t* data_out, + size_t data_out_size, + size_t* data_res_size) { + return compress_decode_internal( + compress->decoder, data_in, data_in_size, data_out, data_out_size, data_res_size); +} diff --git a/lib/toolbox/compress.h b/lib/toolbox/compress.h index f844802ec5..f08e175840 100644 --- a/lib/toolbox/compress.h +++ b/lib/toolbox/compress.h @@ -16,10 +16,14 @@ extern "C" { typedef struct CompressIcon CompressIcon; /** Initialize icon compressor + * + * @param[in] decode_buf_size The icon buffer size for decoding. Ensure that + * it's big enough for any icons that you are + * planning to decode with it. * * @return Compress Icon instance */ -CompressIcon* compress_icon_alloc(void); +CompressIcon* compress_icon_alloc(size_t decode_buf_size); /** Free icon compressor * @@ -29,14 +33,16 @@ void compress_icon_free(CompressIcon* instance); /** Decompress icon * - * @warning decoded_buff pointer set by this function is valid till next + * @warning output pointer set by this function is valid till next * `compress_icon_decode` or `compress_icon_free` call * - * @param instance The Compress Icon instance - * @param icon_data pointer to icon data - * @param[in] decoded_buff pointer to decoded buffer pointer + * @param instance The Compress Icon instance + * @param icon_data pointer to icon data. + * @param[in] output pointer to decoded buffer pointer. Data in buffer is + * valid till next call. If icon data was not compressed, + * pointer within icon_data is returned */ -void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff); +void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** output); /** Compress control structure */ typedef struct Compress Compress; diff --git a/scripts/assets.py b/scripts/assets.py index 711c1b440b..c3d6081c88 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -24,8 +24,8 @@ ICONS_TEMPLATE_C_DATA = "const uint8_t* const {name}[] = {data};\n" ICONS_TEMPLATE_C_ICONS = "const Icon {name} = {{.width={width},.height={height},.frame_count={frame_count},.frame_rate={frame_rate},.frames=_{name}}};\n" -MAX_IMAGE_WIDTH = 128 -MAX_IMAGE_HEIGHT = 64 +MAX_IMAGE_WIDTH = 2**16 - 1 +MAX_IMAGE_HEIGHT = 2**16 - 1 class Main(App): diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index d32869b106..b5b5d6e121 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -351,10 +351,10 @@ def _process_ext_apps(self): ).append(app) def get_ext_apps(self): - return self.extapps + return list(self.extapps) def get_incompatible_ext_apps(self): - return self.incompatible_extapps + return list(self.incompatible_extapps) def _check_conflicts(self): conflicts = [] @@ -399,14 +399,30 @@ def _check_target_match(self): def _group_plugins(self): known_extensions = self.get_apps_of_type(FlipperAppType.PLUGIN, all_known=True) for extension_app in known_extensions: + keep_app = False for parent_app_id in extension_app.requires: try: parent_app = self.appmgr.get(parent_app_id) parent_app._plugins.append(extension_app) + + if ( + parent_app.apptype in self.BUILTIN_APP_TYPES + and parent_app_id in self.appnames + ) or parent_app.apptype not in self.BUILTIN_APP_TYPES: + keep_app |= True + except FlipperManifestException: self._writer( f"Module {extension_app.appid} has unknown parent {parent_app_id}" ) + keep_app = True + # Debug output for plugin parentage + # print( + # f"Module {extension_app.appid} has parents {extension_app.requires} keep={keep_app}" + # ) + if not keep_app and extension_app in self.extapps: + # print(f"Excluding plugin {extension_app.appid}") + self.extapps.remove(extension_app) def get_apps_cdefs(self): cdefs = set() @@ -432,9 +448,11 @@ def get_apps_of_type(self, apptype: FlipperAppType, all_known: bool = False): return sorted( filter( lambda app: app.apptype == apptype, - self.appmgr.known_apps.values() - if all_known - else map(self.appmgr.get, self.appnames), + ( + self.appmgr.known_apps.values() + if all_known + else map(self.appmgr.get, self.appnames) + ), ), key=lambda app: app.order, ) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index a7914c4f87..540510b0d4 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -461,7 +461,8 @@ def _add_host_app_to_targets(host_app): else: # host app is a built-in app components.add_app(artifacts_app_to_run) - components.extra_launch_args = f"-a {host_app.name}" + if host_app.name: + components.extra_launch_args = f"-a {host_app.name}" else: raise UserError("Host app is unknown") else: diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index a3f7faa577..ef2aa91461 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -198,6 +198,7 @@ def gen_sdk_data(sdk_cache: SdkCache): api_def.extend( (f"#include <{h.name}>" for h in sdk_cache.get_headers()), ) + api_def.append('#pragma GCC diagnostic ignored "-Wdeprecated-declarations"') api_def.append(f"const int elf_api_version = {sdk_cache.version.as_int()};") diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ce7ad25369..04ceb928cb 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,6 +1,7 @@ entry,status,name,type,params -Version,+,61.4,, +Version,+,62.3,, Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, @@ -156,10 +157,12 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/keys_dict.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, +Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -168,6 +171,7 @@ Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, +Header,+,lib/toolbox/varint.h,, Header,+,lib/toolbox/version.h,, Header,+,targets/f18/furi_hal/furi_hal_resources.h,, Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, @@ -680,8 +684,15 @@ Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* +Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* +Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_file_path,void,"BtKeysStorage*, const char*" +Function,+,bt_keys_storage_set_ram_params,void,"BtKeysStorage*, uint8_t*, uint16_t" Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_update,_Bool,"BtKeysStorage*, uint8_t*, uint32_t" Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" @@ -773,7 +784,7 @@ Function,+,compress_alloc,Compress*,uint16_t Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_free,void,Compress* -Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_alloc,CompressIcon*,size_t Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" @@ -1490,7 +1501,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pubsub_alloc,FuriPubSub*, -Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" @@ -1675,8 +1686,10 @@ Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimatio Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* -Function,+,icon_get_height,uint8_t,const Icon* -Function,+,icon_get_width,uint8_t,const Icon* +Function,+,icon_get_frame_count,uint32_t,const Icon* +Function,+,icon_get_frame_data,const uint8_t*,"const Icon*, uint32_t" +Function,+,icon_get_height,uint16_t,const Icon* +Function,+,icon_get_width,uint16_t,const Icon* Function,-,ilogb,int,double Function,-,ilogbf,int,float Function,-,ilogbl,int,long double @@ -1980,6 +1993,8 @@ Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned ch Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5_calc_file,_Bool,"File*, const char*, unsigned char[16], FS_Error*" +Function,+,md5_string_calc_file,_Bool,"File*, const char*, FuriString*, FS_Error*" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2254,6 +2269,11 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" +Function,+,pulse_glue_alloc,PulseGlue*, +Function,+,pulse_glue_free,void,PulseGlue* +Function,+,pulse_glue_pop,void,"PulseGlue*, uint32_t*, uint32_t*" +Function,+,pulse_glue_push,_Bool,"PulseGlue*, _Bool, uint32_t" +Function,+,pulse_glue_reset,void,PulseGlue* Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -2642,6 +2662,12 @@ Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" +Function,+,varint_int32_length,size_t,int32_t +Function,+,varint_int32_pack,size_t,"int32_t, uint8_t*" +Function,+,varint_int32_unpack,size_t,"int32_t*, const uint8_t*, size_t" +Function,+,varint_uint32_length,size_t,uint32_t +Function,+,varint_uint32_pack,size_t,"uint32_t, uint8_t*" +Function,+,varint_uint32_unpack,size_t,"uint32_t*, const uint8_t*, size_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" diff --git a/targets/f18/target.json b/targets/f18/target.json index 43e9254cd4..229ec0a7ad 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -30,7 +30,6 @@ "mjs", "mbedtls", "flipper_application", - "toolbox", "u8g2", "nanopb", "update_util", @@ -38,6 +37,7 @@ "flipperformat", "flipper18", "bit_lib", + "toolbox", "datetime" ], "excluded_sources": [ diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5a44dbc6fd..e7a97aff95 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,7 +1,8 @@ entry,status,name,type,params -Version,+,61.4,, +Version,+,62.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, +Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, @@ -158,6 +159,7 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/slix/slix_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, @@ -206,6 +208,7 @@ Header,+,lib/subghz/protocols/public_api.h,, Header,+,lib/subghz/protocols/raw.h,, Header,+,lib/subghz/receiver.h,, Header,+,lib/subghz/registry.h,, +Header,+,lib/subghz/subghz_file_encoder_worker.h,, Header,+,lib/subghz/subghz_protocol_registry.h,, Header,+,lib/subghz/subghz_setting.h,, Header,+,lib/subghz/subghz_tx_rx_worker.h,, @@ -222,10 +225,12 @@ Header,+,lib/toolbox/hex.h,, Header,+,lib/toolbox/keys_dict.h,, Header,+,lib/toolbox/manchester_decoder.h,, Header,+,lib/toolbox/manchester_encoder.h,, +Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, +Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, Header,+,lib/toolbox/saved_struct.h,, Header,+,lib/toolbox/simple_array.h,, Header,+,lib/toolbox/stream/buffered_file_stream.h,, @@ -234,6 +239,7 @@ Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, +Header,+,lib/toolbox/varint.h,, Header,+,lib/toolbox/version.h,, Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, @@ -751,8 +757,15 @@ Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char* +Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage* +Function,+,bt_keys_storage_free,void,BtKeysStorage* +Function,+,bt_keys_storage_load,_Bool,BtKeysStorage* Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_file_path,void,"BtKeysStorage*, const char*" +Function,+,bt_keys_storage_set_ram_params,void,"BtKeysStorage*, uint8_t*, uint16_t" Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_update,_Bool,"BtKeysStorage*, uint8_t*, uint32_t" Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" @@ -844,7 +857,7 @@ Function,+,compress_alloc,Compress*,uint16_t Function,+,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" Function,+,compress_free,void,Compress* -Function,+,compress_icon_alloc,CompressIcon*, +Function,+,compress_icon_alloc,CompressIcon*,size_t Function,+,compress_icon_decode,void,"CompressIcon*, const uint8_t*, uint8_t**" Function,+,compress_icon_free,void,CompressIcon* Function,-,copysign,double,"double, double" @@ -1696,7 +1709,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pubsub_alloc,FuriPubSub*, -Function,-,furi_pubsub_free,void,FuriPubSub* +Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" Function,+,furi_pubsub_subscribe,FuriPubSubSubscription*,"FuriPubSub*, FuriPubSubCallback, void*" Function,+,furi_pubsub_unsubscribe,void,"FuriPubSub*, FuriPubSubSubscription*" @@ -1920,8 +1933,10 @@ Function,+,icon_animation_set_update_callback,void,"IconAnimation*, IconAnimatio Function,+,icon_animation_start,void,IconAnimation* Function,+,icon_animation_stop,void,IconAnimation* Function,+,icon_get_data,const uint8_t*,const Icon* -Function,+,icon_get_height,uint8_t,const Icon* -Function,+,icon_get_width,uint8_t,const Icon* +Function,+,icon_get_frame_count,uint32_t,const Icon* +Function,+,icon_get_frame_data,const uint8_t*,"const Icon*, uint32_t" +Function,+,icon_get_height,uint16_t,const Icon* +Function,+,icon_get_width,uint16_t,const Icon* Function,-,ilogb,int,double Function,-,ilogbf,int,float Function,-,ilogbl,int,long double @@ -2390,6 +2405,8 @@ Function,-,mbedtls_sha256_update,int,"mbedtls_sha256_context*, const unsigned ch Function,-,mblen,int,"const char*, size_t" Function,-,mbstowcs,size_t,"wchar_t*, const char*, size_t" Function,-,mbtowc,int,"wchar_t*, const char*, size_t" +Function,+,md5_calc_file,_Bool,"File*, const char*, unsigned char[16], FS_Error*" +Function,+,md5_string_calc_file,_Bool,"File*, const char*, FuriString*, FS_Error*" Function,-,memccpy,void*,"void*, const void*, int, size_t" Function,+,memchr,void*,"const void*, int, size_t" Function,+,memcmp,int,"const void*, const void*, size_t" @@ -2852,6 +2869,11 @@ Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, siz Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" +Function,+,pulse_glue_alloc,PulseGlue*, +Function,+,pulse_glue_free,void,PulseGlue* +Function,+,pulse_glue_pop,void,"PulseGlue*, uint32_t*, uint32_t*" +Function,+,pulse_glue_push,_Bool,"PulseGlue*, _Bool, uint32_t" +Function,+,pulse_glue_reset,void,PulseGlue* Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -2992,6 +3014,11 @@ Function,+,slix_is_counter_increment_protected,_Bool,const SlixData* Function,+,slix_is_equal,_Bool,"const SlixData*, const SlixData*" Function,+,slix_is_privacy_mode,_Bool,const SlixData* Function,+,slix_load,_Bool,"SlixData*, FlipperFormat*, uint32_t" +Function,+,slix_poller_get_nxp_system_info,SlixError,"SlixPoller*, SlixSystemInfo*" +Function,+,slix_poller_get_random_number,SlixError,"SlixPoller*, SlixRandomNumber*" +Function,+,slix_poller_read_signature,SlixError,"SlixPoller*, SlixSignature*" +Function,+,slix_poller_send_frame,SlixError,"SlixPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,slix_poller_set_password,SlixError,"SlixPoller*, SlixPasswordType, SlixPassword, SlixRandomNumber" Function,+,slix_reset,void,SlixData* Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*" Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t" @@ -3244,6 +3271,13 @@ Function,+,subghz_environment_set_alutech_at_4n_rainbow_table_file_name,void,"Su Function,+,subghz_environment_set_came_atomo_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_nice_flor_s_rainbow_table_file_name,void,"SubGhzEnvironment*, const char*" Function,+,subghz_environment_set_protocol_registry,void,"SubGhzEnvironment*, const SubGhzProtocolRegistry*" +Function,+,subghz_file_encoder_worker_alloc,SubGhzFileEncoderWorker*, +Function,+,subghz_file_encoder_worker_callback_end,void,"SubGhzFileEncoderWorker*, SubGhzFileEncoderWorkerCallbackEnd, void*" +Function,+,subghz_file_encoder_worker_free,void,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* +Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* +Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" +Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* Function,-,subghz_keystore_alloc,SubGhzKeystore*, Function,-,subghz_keystore_free,void,SubGhzKeystore* Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* @@ -3442,6 +3476,12 @@ Function,+,variable_item_list_set_selected_item,void,"VariableItemList*, uint8_t Function,+,variable_item_set_current_value_index,void,"VariableItem*, uint8_t" Function,+,variable_item_set_current_value_text,void,"VariableItem*, const char*" Function,+,variable_item_set_values_count,void,"VariableItem*, uint8_t" +Function,+,varint_int32_length,size_t,int32_t +Function,+,varint_int32_pack,size_t,"int32_t, uint8_t*" +Function,+,varint_int32_unpack,size_t,"int32_t*, const uint8_t*, size_t" +Function,+,varint_uint32_length,size_t,uint32_t +Function,+,varint_uint32_pack,size_t,"uint32_t, uint8_t*" +Function,+,varint_uint32_unpack,size_t,"uint32_t*, const uint8_t*, size_t" Function,-,vasiprintf,int,"char**, const char*, __gnuc_va_list" Function,-,vasniprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" Function,-,vasnprintf,char*,"char*, size_t*, const char*, __gnuc_va_list" diff --git a/targets/f7/target.json b/targets/f7/target.json index 25872198bf..eae92a5cd6 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -45,7 +45,6 @@ "mbedtls", "lfrfid", "flipper_application", - "toolbox", "u8g2", "nanopb", "update_util", @@ -53,6 +52,7 @@ "flipperformat", "flipper7", "bit_lib", + "toolbox", "datetime" ] } From e0797131eca42411e6c31d799b9f7c407f50a79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 20 May 2024 19:17:01 +0100 Subject: [PATCH 40/48] FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock (#3657) * FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock * hal: flash: added FLASH_OP_DEBUG to enable latency measurement Co-authored-by: hedger Co-authored-by: hedger --- targets/f7/furi_hal/furi_hal_clock.c | 20 +++++++++++--------- targets/f7/furi_hal/furi_hal_flash.c | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index 9184fa7157..ca4c7ad4d7 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -133,7 +133,7 @@ void furi_hal_clock_switch_hse2hsi(void) { ; LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI); - furi_assert(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI); + furi_check(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI); while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) ; @@ -170,7 +170,7 @@ void furi_hal_clock_switch_hsi2hse(void) { } bool furi_hal_clock_switch_hse2pll(void) { - furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); LL_RCC_PLL_Enable(); LL_RCC_PLLSAI1_Enable(); @@ -180,12 +180,13 @@ bool furi_hal_clock_switch_hse2pll(void) { while(!LL_RCC_PLLSAI1_IsReady()) ; - if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL) != SHCI_Success) { + // This API returns garbage if stack version < 1.20.0 + SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL); + // So we'll check results by asking hardware directly + if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { return false; } - furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); - LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ); SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); @@ -193,18 +194,19 @@ bool furi_hal_clock_switch_hse2pll(void) { } bool furi_hal_clock_switch_pll2hse(void) { - furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); + furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL); LL_RCC_HSE_Enable(); while(!LL_RCC_HSE_IsReady()) ; - if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE) != SHCI_Success) { + // This API returns garbage if stack version < 1.20.0 + SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE); + // So we'll check results by asking hardware directly + if(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE) { return false; } - furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE); - LL_SetSystemCoreClock(CPU_CLOCK_HSE_HZ); SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL); diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 138e07eab7..43f80c61eb 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -16,6 +16,12 @@ #define TAG "FuriHalFlash" +#ifdef FLASH_OP_DEBUG +#undef FURI_LOG_T +#define FURI_LOG_T(...) +#else +#endif + #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" #define FURI_HAL_FLASH_READ_BLOCK (8U) #define FURI_HAL_FLASH_WRITE_BLOCK (8U) @@ -291,6 +297,7 @@ bool furi_hal_flash_wait_last_operation(uint32_t timeout) { } void furi_hal_flash_erase(uint8_t page) { + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(true); /* Ensure that controller state is valid */ @@ -313,6 +320,12 @@ void furi_hal_flash_erase(uint8_t page) { furi_hal_flush_cache(); furi_hal_flash_end(true); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "erase took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) { @@ -335,6 +348,7 @@ static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* } void furi_hal_flash_write_dword(size_t address, uint64_t data) { + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(false); /* Ensure that controller state is valid */ @@ -357,6 +371,12 @@ void furi_hal_flash_write_dword(size_t address, uint64_t data) { /* Wait for last operation to be completed */ furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "write_dword took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } static size_t furi_hal_flash_get_page_address(uint8_t page) { @@ -369,6 +389,7 @@ void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 furi_hal_flash_erase(page); + uint32_t op_stat = DWT->CYCCNT; furi_hal_flash_begin(false); furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT)); @@ -428,6 +449,12 @@ void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16 CLEAR_BIT(FLASH->CR, FLASH_CR_PG); furi_hal_flash_end(false); + op_stat = DWT->CYCCNT - op_stat; + FURI_LOG_T( + TAG, + "program_page took %lu clocks or %fus", + op_stat, + (double)((float)op_stat / (float)furi_hal_cortex_instructions_per_microsecond())); } int16_t furi_hal_flash_get_page_number(size_t address) { From 11070c9e5e4340fb4bab43644cecbc287c88d17d Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 22 May 2024 18:14:33 +0100 Subject: [PATCH 41/48] Text Box: fix displaying text with end text focus (#3658) * text box: fix reset text offset state after initial text iteration * debug: add tests for text box view debug app * hal: flash: removed redundant #else Co-authored-by: hedger --- .../text_box_view_test/text_box_view_test.c | 30 ++++++++++++++++++- applications/services/gui/modules/text_box.c | 3 +- targets/f7/furi_hal/furi_hal_flash.c | 1 - 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/applications/debug/text_box_view_test/text_box_view_test.c b/applications/debug/text_box_view_test/text_box_view_test.c index 4414835ecc..7bbcb285b8 100644 --- a/applications/debug/text_box_view_test/text_box_view_test.c +++ b/applications/debug/text_box_view_test/text_box_view_test.c @@ -18,6 +18,33 @@ static const TextBoxViewTestContent text_box_view_test_content_arr[] = { .focus = TextBoxFocusStart, .text = "Hello, let's test text box. Press Right and Left to switch content", }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3", + }, + { + .font = TextBoxFontText, + .focus = TextBoxFocusEnd, + .text = + "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3\nLine 4", + }, { .font = TextBoxFontText, .focus = TextBoxFocusStart, @@ -57,7 +84,8 @@ typedef struct { } TextBoxViewTest; static void text_box_update_view(TextBoxViewTest* instance) { - text_box_reset(instance->text_box); + // Intentional incorrect way to reset text box to verify that state resets if text changes + text_box_set_text(instance->text_box, ""); const TextBoxViewTestContent* content = &text_box_view_test_content_arr[instance->current_content_i]; diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 954847c650..c3bff00d08 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -227,14 +227,13 @@ static void text_box_prepare_model(Canvas* canvas, TextBoxModel* model) { text_box_seek_next_line(canvas, model); lines_num++; } while(!text_box_end_of_text_reached(model)); + model->text_offset = 0; lines_num++; if(model->focus == TextBoxFocusEnd) { if(lines_num > model->lines_on_screen) { model->text_offset = window_offset[(lines_num - 1) % model->lines_on_screen]; } - } else { - model->text_offset = 0; } if(lines_num > model->lines_on_screen) { diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 43f80c61eb..322a2e9054 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -19,7 +19,6 @@ #ifdef FLASH_OP_DEBUG #undef FURI_LOG_T #define FURI_LOG_T(...) -#else #endif #define FURI_HAL_CRITICAL_MSG "Critical flash operation fail" From 8ba938cec13946080bf4283e8f8a58d2e12d191c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 22 May 2024 18:35:59 +0100 Subject: [PATCH 42/48] [FL-3775] Archive: fix condition race on exit (#3659) * Archive: fix condition race on exit * Format sources and remove debug logging --------- Co-authored-by: hedger --- applications/main/archive/archive.c | 3 +++ applications/main/archive/archive_i.h | 1 + .../archive/scenes/archive_scene_browser.c | 24 +++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c index 8dd934cc7b..5db650445d 100644 --- a/applications/main/archive/archive.c +++ b/applications/main/archive/archive.c @@ -16,6 +16,7 @@ ArchiveApp* archive_alloc(void) { ArchiveApp* archive = malloc(sizeof(ArchiveApp)); archive->gui = furi_record_open(RECORD_GUI); + archive->loader = furi_record_open(RECORD_LOADER); archive->text_input = text_input_alloc(); archive->fav_move_str = furi_string_alloc(); @@ -61,6 +62,8 @@ void archive_free(ArchiveApp* archive) { text_input_free(archive->text_input); + furi_record_close(RECORD_LOADER); + archive->loader = NULL; furi_record_close(RECORD_GUI); archive->gui = NULL; diff --git a/applications/main/archive/archive_i.h b/applications/main/archive/archive_i.h index d444aef8fc..13b975a44d 100644 --- a/applications/main/archive/archive_i.h +++ b/applications/main/archive/archive_i.h @@ -22,6 +22,7 @@ typedef enum { struct ArchiveApp { Gui* gui; + Loader* loader; ViewDispatcher* view_dispatcher; SceneManager* scene_manager; ArchiveBrowserView* browser; diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 284e534aba..ba928c4993 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -86,10 +86,8 @@ void archive_scene_browser_on_enter(void* context) { archive_update_focus(browser, archive->text_store); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); - Loader* loader = furi_record_open(RECORD_LOADER); - archive->loader_stop_subscription = - furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive); - furi_record_close(RECORD_LOADER); + archive->loader_stop_subscription = furi_pubsub_subscribe( + loader_get_pubsub(archive->loader), archive_loader_callback, archive); uint32_t state = scene_manager_get_scene_state(archive->scene_manager, ArchiveAppSceneBrowser); @@ -213,10 +211,11 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { if(!archive_is_home(browser)) { archive_leave_dir(browser); } else { - Loader* loader = furi_record_open(RECORD_LOADER); - furi_pubsub_unsubscribe( - loader_get_pubsub(loader), archive->loader_stop_subscription); - furi_record_close(RECORD_LOADER); + if(archive->loader_stop_subscription) { + furi_pubsub_unsubscribe( + loader_get_pubsub(archive->loader), archive->loader_stop_subscription); + archive->loader_stop_subscription = NULL; + } view_dispatcher_stop(archive->view_dispatcher); } @@ -232,8 +231,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { void archive_scene_browser_on_exit(void* context) { ArchiveApp* archive = (ArchiveApp*)context; - - Loader* loader = furi_record_open(RECORD_LOADER); - furi_pubsub_unsubscribe(loader_get_pubsub(loader), archive->loader_stop_subscription); - furi_record_close(RECORD_LOADER); + if(archive->loader_stop_subscription) { + furi_pubsub_unsubscribe( + loader_get_pubsub(archive->loader), archive->loader_stop_subscription); + archive->loader_stop_subscription = NULL; + } } From 807bec14b16e849825e1b0a0609fa5614fdc1c84 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 22 May 2024 22:13:28 +0400 Subject: [PATCH 43/48] Replaced obsolete-format delay (#3660) --- targets/f7/furi_hal/furi_hal_flash.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 322a2e9054..9cf64acc58 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -149,9 +149,8 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { /* Erase activity notification */ if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON); - /* 64mHz 5us core2 flag protection */ - for(volatile uint32_t i = 0; i < 35; i++) - ; + /* 5us core2 flag protection */ + furi_delay_us(5); FuriHalCortexTimer timer = furi_hal_cortex_timer_get(FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS * 1000); while(true) { From ab2fcaf4abe4d1f235ad6fda7216373cf22f20ba Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 23 May 2024 13:46:00 +0100 Subject: [PATCH 44/48] [FL-3829] NFC App: fix changing UID (#3663) * nfc app: fix changing uid on each byte change * nfc app: remove unused code --- applications/main/nfc/scenes/nfc_scene_set_uid.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index f09d0a4303..f702fa05b3 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -2,22 +2,12 @@ #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" -static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { - NfcApp* instance = context; - // Retrieve previously saved UID length - const size_t uid_len = scene_manager_get_scene_state(instance->scene_manager, NfcSceneSetUid); - nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); -} - void nfc_scene_set_uid_on_enter(void* context) { NfcApp* instance = context; size_t uid_len; const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); - memcpy(instance->byte_input_store, uid, uid_len); - // Save UID length for use in callback - scene_manager_set_scene_state(instance->scene_manager, NfcSceneSetUid, uid_len); // Setup view ByteInput* byte_input = instance->byte_input; @@ -25,7 +15,7 @@ void nfc_scene_set_uid_on_enter(void* context) { byte_input_set_result_callback( byte_input, nfc_protocol_support_common_byte_input_done_callback, - nfc_scene_set_uid_byte_input_changed_callback, + NULL, instance, instance->byte_input_store, uid_len); @@ -39,6 +29,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventByteInputDone) { + size_t uid_len = 0; + nfc_device_get_uid(instance->nfc_device, &uid_len); + nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len); if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) { if(nfc_save(instance)) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); From ba8dd58dee985dde52c7081a6edbfe0463d1a8ef Mon Sep 17 00:00:00 2001 From: Methodius Date: Fri, 24 May 2024 20:33:34 +0300 Subject: [PATCH 45/48] electra non-initialized encoded epilogue on render fix --- lib/lfrfid/protocols/protocol_electra.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index fabf7ce6bb..29223a43d6 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -411,6 +411,7 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { }; void protocol_electra_render_data(ProtocolElectra* protocol, FuriString* result) { + protocol_electra_encoder_start(protocol); furi_string_printf(result, "Epilogue: %016llX", protocol->encoded_epilogue); }; From e31fbb061abeb361eb338fceb8690a20ac7ddb62 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 16 May 2024 19:53:40 +0100 Subject: [PATCH 46/48] SubGHz: fix memory corrupt in read raw view [ci skip] --- applications/main/subghz/views/subghz_read_raw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index f2a0160457..1b9e1d3163 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -88,7 +88,7 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool tra model->rssi_history[model->ind_write] = u_rssi; } - if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { + if(model->ind_write >= SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { model->rssi_history_end = true; model->ind_write = 0; } From 3ab0a26e1dd5a45a7a3b12941885133c21783576 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 25 May 2024 00:03:56 +0300 Subject: [PATCH 47/48] update changelog --- CHANGELOG.md | 101 +++++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b83ae12b..add610f7b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,75 +1,42 @@ ## New changes -* LFRFID: **Electra intercom protocol support** (Romania) (by @Leptopt1los | PR #750) -* NFC: Temp fix for `iso14443_4_layer_decode_block` crash -* NFC: CharlieCard parser (by @zacharyweiss) -* SubGHz: FAAC RC XT - add 0xB button code on arrow buttons for programming mode -* SubGHz: Add Manually - Sommer FM fixes -* SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**) -* SubGHz: **Status output !TX/RX on the GDO2 CC1101 pin** (by @quen0n | PR #742) -* SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL) -* Desktop: Fixes for animation unload (by @Willy-JL) -* iButton: Updated DS1420 for latest ibutton changes -* Misc: Allow no prefix usage of name_generator_make_detailed_datetime -* Misc: Allow setting view dispatcher callbacks to NULL -* Misc: Added `void` due to `-Wstrict-prototypes` -* Misc: Some code cleanup and proper log levels in nfc parsers -* Infrared: Allow external apps to use infrared settings (by @Willy-JL) -* JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) -* JS: **BadUSB layout support** (by @Willy-JL) -* JS: New Modules `widget`, `vgm` and path globals (by @jamisonderek) -* Apps: Enhance Random Interval and Movement Functionality in HID Mouse Jiggler for Improved Stealth and Human-Like Behavior (by @gushmazuko | PR #746) -* Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra) -* Apps: MFKey - **fixed crashes**, add more free ram (by @noproto & @Willy-JL) -* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) -* OFW PR 3616: NFC: Mf Desfire fix reading big files (by gornekich) -* OFW: iButton: fix crash when deleting some keys -* OFW: Desktop: cleanup error popups -* OFW: Troika parser visual fixes -* OFW: Fix the retry/exit confirmation prompts in iButton -* OFW: nfc app: add legacy keys for plantain cards -* OFW: GUI: Fix array out of bounds in menu exit -* OFW: add support for S(WTX) request in iso14443_4a_poller -* OFW: Mosgortrans parser output fixes -* OFW: BLE: Add GapPairingNone support -* OFW: iButton new UI -* OFW: FuriHal: add ADC API -* OFW: Mf Desfire multiple file rights support -* OFW: **Felica poller** (NFC-F) -* OFW: Desktop/Loader: Unload animations before loading FAPs -* OFW: JS Documentation -* OFW: **Update radio stack to v1.19.0** -* OFW: **Move crypto1 to helpers, add it to the public API** -* OFW: Explain RNG differences, add FURI_HAL_RANDOM_MAX -* OFW: Furi: Add "out of memory" and "malloc(0)" crash messages -* OFW: IR: Fix crash on duty_cycle=1 -* OFW: **Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes)** -* OFW: Hide unlock with reader for MFU-C -* OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env -* OFW: fbt: added -Wstrict-prototypes for main firmware -* OFW: Mifare Ultralight naming fix -* OFW: IR: Remember OTG state -* OFW: Bad USB: fix crash when selecting a keyboard layout -* OFW: L1_Mods animation update : adding VGM visual -* OFW: RFID Improvements -* OFW: Fixed plugins and UI -* OFW: **NFC: Fix mf desfire detect** -* OFW: infrared_transmit.h was missing `#pragma once` -* OFW: Show the wrong PIN Attempt count on the login screen -* OFW: SavedStruct: Introduce saved_struct_get_metadata -* OFW: JS CLI command -* OFW: Add ChromeOS Bad USB demo -* OFW: **Configurable Infrared TX output** (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings) -* OFW: BadUSB: BLE, media keys, Fn/Globe key commands -* OFW: NFC: Slix privacy password reveal ->(was included in previous UL release) and **Desfire detect fix** -* OFW: github: additional pre-upload checks for doxygen workflow -* OFW: NFC UI fixes -* OFW: Gui: unicode support, new canvas API -* OFW: **Api Symbols: replace asserts with checks** +* Apps: **Mifare Nested - ported to latest API** using old nfc lib (by @xMasterX) (+ mem management fix by @Willy-JL) +* LFRFID: Electra fix non-initialized encoded epilogue on render (by @Leptopt1los) +* JS: Move examples to subfolder `js_examples` +* Apps: HID app improvements and fixes
+`- Move new mouse jiggler into mouse jiggler stealth and bring back previous version of mouse jiggler too`
+`- Set stealth jiggler max time default value to 4 min and min value to 1 min`
+`- Merge OFW changes`
+`- More OFW merge fixes` (by @Willy-JL | PR #753)
+* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW (TLSF branch): SubGHz: fix memory corrupt in read raw view +* OFW: NFC App: fix changing UID +* OFW: Replaced obsolete-format delay +* OFW: Archive: fix condition race on exit +* OFW: Text Box: fix displaying text with end text focus +* OFW: FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock +* OFW: Icons: compression fixes & larger dimension support +* OFW: Text Box rework +* OFW: Fix calling both `view_free_model()` and `view_free()` +* OFW: JS: Add textbox module +* OFW: JS: Add math module +* OFW: NFC: add Slix capabilities +* OFW: Settings refactor fixes +* OFW: JS: Add submenu module +* OFW: Skylanders plugin +* OFW: Settings menu refactoring +* OFW: NFC: Mf Desfire fix reading big files +* OFW: Infrared: Add Toshiba RAS-2518D +* OFW: vscode: config fixes +* OFW: Ble: new connection parameters negotiation scheme +* OFW: FuriHal: move version init to early stage +* OFW: Add support for R_ARM_REL32 relocations. +* OFW: Remove unused DolphinWait_61x59 icon +* OFW: Add the Akira animation +* OFW: Desktop: fix crash on autolock after restart in locked state

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) - NFC CLI was removed with refactoring (OFW) (will be back soon) -- Mifare Nested not ported to latest API yet, `unlshd-065` is the latest version on old NFC API that works with "nested app" ---- From 83d85ec0e28eb7cbeb06bfe479aa9c5a176ec236 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 25 May 2024 00:24:44 +0300 Subject: [PATCH 48/48] upd changelog --- CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index add610f7b3..fd085a6efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## New changes * Apps: **Mifare Nested - ported to latest API** using old nfc lib (by @xMasterX) (+ mem management fix by @Willy-JL) -* LFRFID: Electra fix non-initialized encoded epilogue on render (by @Leptopt1los) +* LFRFID: **Electra fix** non-initialized encoded epilogue on render (by @Leptopt1los) * JS: Move examples to subfolder `js_examples` * Apps: HID app improvements and fixes
`- Move new mouse jiggler into mouse jiggler stealth and bring back previous version of mouse jiggler too`
@@ -9,30 +9,30 @@ `- More OFW merge fixes` (by @Willy-JL | PR #753)
* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) * OFW (TLSF branch): SubGHz: fix memory corrupt in read raw view -* OFW: NFC App: fix changing UID +* OFW: **NFC App: fix changing UID** * OFW: Replaced obsolete-format delay -* OFW: Archive: fix condition race on exit +* OFW: **Archive: fix condition race on exit** * OFW: Text Box: fix displaying text with end text focus * OFW: FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock * OFW: Icons: compression fixes & larger dimension support -* OFW: Text Box rework +* OFW: **Text Box rework** * OFW: Fix calling both `view_free_model()` and `view_free()` * OFW: JS: Add textbox module * OFW: JS: Add math module -* OFW: NFC: add Slix capabilities +* OFW: **NFC: add Slix capabilities** * OFW: Settings refactor fixes * OFW: JS: Add submenu module -* OFW: Skylanders plugin +* OFW: **Skylanders plugin** * OFW: Settings menu refactoring * OFW: NFC: Mf Desfire fix reading big files * OFW: Infrared: Add Toshiba RAS-2518D -* OFW: vscode: config fixes +* OFW: **vscode: config fixes** * OFW: Ble: new connection parameters negotiation scheme * OFW: FuriHal: move version init to early stage * OFW: Add support for R_ARM_REL32 relocations. * OFW: Remove unused DolphinWait_61x59 icon * OFW: Add the Akira animation -* OFW: Desktop: fix crash on autolock after restart in locked state +* OFW: **Desktop: fix crash on autolock after restart in locked state**

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) @@ -62,7 +62,7 @@ |TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: -ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... +@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... and all other great people who supported our project and me (xMasterX), thanks to you all!