From 702db8925f86ecc9acc76f14c9a18140e599861c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 12 Aug 2024 09:51:43 +0900 Subject: [PATCH 01/29] Desktop: seaprate callbacks for dolphin and storage subscriptions (#3836) * Desktop: seaprate callbacks for dolphin and storage subscriptions * dolphin: renamed local variable to prevent unintended overrides Co-authored-by: hedger --- .../desktop/animations/animation_manager.c | 22 ++++++++++++++++--- applications/services/dolphin/dolphin.c | 4 ++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index dd2ae76a1c5..858efb9fe7a 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -104,7 +104,7 @@ void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, } } -static void animation_manager_check_blocking_callback(const void* message, void* context) { +static void animation_manager_storage_callback(const void* message, void* context) { const StorageEvent* storage_event = message; switch(storage_event->type) { @@ -123,6 +123,22 @@ static void animation_manager_check_blocking_callback(const void* message, void* } } +static void animation_manager_dolphin_callback(const void* message, void* context) { + const DolphinPubsubEvent* dolphin_event = message; + + switch(*dolphin_event) { + case DolphinPubsubEventUpdate: + furi_assert(context); + AnimationManager* animation_manager = context; + if(animation_manager->check_blocking_callback) { + animation_manager->check_blocking_callback(animation_manager->context); + } + break; + default: + break; + } +} + static void animation_manager_timer_callback(void* context) { furi_assert(context); AnimationManager* animation_manager = context; @@ -300,12 +316,12 @@ AnimationManager* animation_manager_alloc(void) { Storage* storage = furi_record_open(RECORD_STORAGE); animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe( - storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager); + storage_get_pubsub(storage), animation_manager_storage_callback, animation_manager); furi_record_close(RECORD_STORAGE); Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe( - dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager); + dolphin_get_pubsub(dolphin), animation_manager_dolphin_callback, animation_manager); furi_record_close(RECORD_DOLPHIN); animation_manager->blocking_shown_sd_ok = true; diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index dd2ecd2ba15..501e37c3c8c 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -204,8 +204,8 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { if(event.type == DolphinEventTypeDeed) { dolphin_state_on_deed(dolphin->state, event.deed); - DolphinPubsubEvent event = DolphinPubsubEventUpdate; - furi_pubsub_publish(dolphin->pubsub, &event); + DolphinPubsubEvent pubsub_event = DolphinPubsubEventUpdate; + furi_pubsub_publish(dolphin->pubsub, &pubsub_event); furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS); From 99655c15e454ffdbd4bada76bd45fa41fd28f7c1 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 12 Aug 2024 04:09:41 +0300 Subject: [PATCH 02/29] scripts: improved size validator for updater image (#3834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * scripts: update.py: reduced reservation size for flash memory; improved error messages; added checks for updater images * scripts: update: fixed imports Co-authored-by: あく --- scripts/imglint.py | 2 +- scripts/update.py | 44 ++++++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/scripts/imglint.py b/scripts/imglint.py index fc63f33555a..f25fea4f5e7 100644 --- a/scripts/imglint.py +++ b/scripts/imglint.py @@ -4,7 +4,7 @@ from pathlib import Path from flipper.app import App -from PIL import Image, ImageOps +from PIL import Image _logger = logging.getLogger(__name__) diff --git a/scripts/update.py b/scripts/update.py index e880bced844..47a5eeb27b0 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import io import math import os import shutil @@ -8,7 +7,6 @@ import zlib from os.path import exists, join -import heatshrink2 from flipper.app import App from flipper.assets.coprobin import CoproBinary, get_stack_type from flipper.assets.heatshrink_stream import HeatshrinkDataStreamHeader @@ -35,7 +33,12 @@ class Main(App): ) FLASH_BASE = 0x8000000 - MIN_LFS_PAGES = 6 + FLASH_PAGE_SIZE = 4 * 1024 + MIN_GAP_PAGES = 2 + + # Update stage file larger than that is not loadable without fix + # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 + UPDATER_SIZE_THRESHOLD = 128 * 1024 HEATSHRINK_WINDOW_SIZE = 13 HEATSHRINK_LOOKAHEAD_SIZE = 6 @@ -117,7 +120,7 @@ def generate(self): self.logger.error( f"You are trying to bundle a non-standard stack type '{self.args.radiotype}'." ) - self.disclaimer() + self.show_disclaimer() return 1 if radio_addr == 0: @@ -130,7 +133,9 @@ def generate(self): if not exists(self.args.directory): os.makedirs(self.args.directory) + updater_stage_size = os.stat(self.args.stage).st_size shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) + dfu_size = 0 if self.args.dfu: dfu_size = os.stat(self.args.dfu).st_size @@ -146,10 +151,10 @@ def generate(self): ): return 3 - if not self.layout_check(dfu_size, radio_addr): + if not self.layout_check(updater_stage_size, dfu_size, radio_addr): self.logger.warn("Memory layout looks suspicious") - if not self.args.disclaimer == "yes": - self.disclaimer() + if self.args.disclaimer != "yes": + self.show_disclaimer() return 2 if self.args.splash: @@ -198,22 +203,33 @@ def generate(self): return 0 - def layout_check(self, fw_size, radio_addr): + def layout_check(self, stage_size, fw_size, radio_addr): + if stage_size > self.UPDATER_SIZE_THRESHOLD: + self.logger.warn( + f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!" + ) + if fw_size == 0 or radio_addr == 0: self.logger.info("Cannot validate layout for partial package") return True - lfs_span = radio_addr - self.FLASH_BASE - fw_size - self.logger.debug(f"Expected LFS size: {lfs_span}") - lfs_span_pages = lfs_span / (4 * 1024) - if lfs_span_pages < self.MIN_LFS_PAGES: + fw2stack_gap = radio_addr - self.FLASH_BASE - fw_size + self.logger.debug(f"Expected reserved space size: {fw2stack_gap}") + fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE + if fw2stack_gap_pages < 0: + self.logger.warn( + f"Firmware image overlaps C2 region and is not programmable!" + ) + return False + + elif fw2stack_gap_pages < self.MIN_GAP_PAGES: self.logger.warn( - f"Expected LFS size is too small (~{int(lfs_span_pages)} pages)" + f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))" ) return False return True - def disclaimer(self): + def show_disclaimer(self): self.logger.error( "You might brick your device into a state in which you'd need an SWD programmer to fix it." ) From 7c88a4a8f1062063b74277c03617fb9e083e538b Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:21:25 +0900 Subject: [PATCH 03/29] [FL-3899] Add the Procrastination animation (#3860) * Add the Procrastination animation: sorry been putting off doing this for a while * Reformat images --- .../L1_Procrastinating_128x64/frame_0.png | Bin 0 -> 836 bytes .../L1_Procrastinating_128x64/frame_1.png | Bin 0 -> 837 bytes .../L1_Procrastinating_128x64/frame_10.png | Bin 0 -> 604 bytes .../L1_Procrastinating_128x64/frame_11.png | Bin 0 -> 552 bytes .../L1_Procrastinating_128x64/frame_12.png | Bin 0 -> 569 bytes .../L1_Procrastinating_128x64/frame_13.png | Bin 0 -> 609 bytes .../L1_Procrastinating_128x64/frame_14.png | Bin 0 -> 562 bytes .../L1_Procrastinating_128x64/frame_15.png | Bin 0 -> 655 bytes .../L1_Procrastinating_128x64/frame_16.png | Bin 0 -> 636 bytes .../L1_Procrastinating_128x64/frame_17.png | Bin 0 -> 683 bytes .../L1_Procrastinating_128x64/frame_18.png | Bin 0 -> 738 bytes .../L1_Procrastinating_128x64/frame_19.png | Bin 0 -> 769 bytes .../L1_Procrastinating_128x64/frame_2.png | Bin 0 -> 829 bytes .../L1_Procrastinating_128x64/frame_20.png | Bin 0 -> 738 bytes .../L1_Procrastinating_128x64/frame_21.png | Bin 0 -> 684 bytes .../L1_Procrastinating_128x64/frame_22.png | Bin 0 -> 658 bytes .../L1_Procrastinating_128x64/frame_23.png | Bin 0 -> 409 bytes .../L1_Procrastinating_128x64/frame_24.png | Bin 0 -> 513 bytes .../L1_Procrastinating_128x64/frame_25.png | Bin 0 -> 746 bytes .../L1_Procrastinating_128x64/frame_26.png | Bin 0 -> 671 bytes .../L1_Procrastinating_128x64/frame_27.png | Bin 0 -> 569 bytes .../L1_Procrastinating_128x64/frame_28.png | Bin 0 -> 610 bytes .../L1_Procrastinating_128x64/frame_29.png | Bin 0 -> 784 bytes .../L1_Procrastinating_128x64/frame_3.png | Bin 0 -> 830 bytes .../L1_Procrastinating_128x64/frame_30.png | Bin 0 -> 829 bytes .../L1_Procrastinating_128x64/frame_31.png | Bin 0 -> 846 bytes .../L1_Procrastinating_128x64/frame_32.png | Bin 0 -> 842 bytes .../L1_Procrastinating_128x64/frame_33.png | Bin 0 -> 853 bytes .../L1_Procrastinating_128x64/frame_34.png | Bin 0 -> 855 bytes .../L1_Procrastinating_128x64/frame_35.png | Bin 0 -> 796 bytes .../L1_Procrastinating_128x64/frame_36.png | Bin 0 -> 817 bytes .../L1_Procrastinating_128x64/frame_37.png | Bin 0 -> 805 bytes .../L1_Procrastinating_128x64/frame_38.png | Bin 0 -> 831 bytes .../L1_Procrastinating_128x64/frame_39.png | Bin 0 -> 774 bytes .../L1_Procrastinating_128x64/frame_4.png | Bin 0 -> 836 bytes .../L1_Procrastinating_128x64/frame_40.png | Bin 0 -> 744 bytes .../L1_Procrastinating_128x64/frame_5.png | Bin 0 -> 838 bytes .../L1_Procrastinating_128x64/frame_6.png | Bin 0 -> 828 bytes .../L1_Procrastinating_128x64/frame_7.png | Bin 0 -> 828 bytes .../L1_Procrastinating_128x64/frame_8.png | Bin 0 -> 587 bytes .../L1_Procrastinating_128x64/frame_9.png | Bin 0 -> 569 bytes .../L1_Procrastinating_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 9 ++++++- 43 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_13.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_14.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_15.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_18.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_21.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_22.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_24.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_25.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_35.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_36.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_38.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_39.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_40.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Procrastinating_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..9dcf6883973321da68a6894ca994f1ed562e6563 GIT binary patch literal 836 zcmV-K1H1f*P)L|!tlF+P=}yFh6f3P36{WM&w9;a_Gn1BNXOo$E9%h&Nj_(ezOg93?Mh*A z_>zELxFJ!{NMUTqaqS0)VQiEcLFnjpwLp--ntW?=luB$@J%#IwVnW?D2kK6!1_LU>vbDA-~%>T z?sn_*m$MS7NBaB=3Kf=r`h99wouf^%A0B-SF8>Kr4G~{m{`=%jfCr-GDi`nsT%JACx>X)RuHOUtDPl9z9c-IDt zP}!23E$MA!f$saDN%fw4AW#b7%H%N(rJZDle>^3XHHI#$*9qpCi7y-pZh znApxXTWcaBdVQ}%el5-{q8LC|QtkQcb|uI20d)2Vj$4a?M)#;v%DQ-bV^MzFmBQfg zB>}x~Ln6PK!q|}G+Vc^^*eJO;F1LY37l3EVoiLDJ9G+{f^+ZH}mPq6?p~Q@g%+{XI zgCECuz5(>X7{?_uHjOg|V32jfKn5o`-TJpDB6?^^qU@^8FbTdR}mwC9%r z3vf1n?X~4hqkG;3V$}&lk>$$RsH770VJ30Mb5ynGK~0Z$nT>HSfXc_ae7n&J2T5~~ zZ?@K&BBI5X$d~yOnkY7aE+yJKmpt`{jE&`eIA8JhlCnp}V%7=0*40e%(LE0DlL-0o z&P_{Q?KvO0D1D$Xh8E89gi%I0g(1?*gJ-YjVHg&MDt&U$o4Y|2MX@NQl;oev^#1$& zclUta3rdDn7HWn=kj~A4rFMM5)~hfuWamD76OB^sCx9r5;^Z%|zE9qI-3AVLfDM+j z-TwULtVHUPKL3J3h2@`qpW4;nXp8KJM<0XBf5KE##8a35K6w-1foS=Pi1brRCsWY! z^Tl`e1FNNW0(ZY?ac_I+(&`LP+NbcbY)jQ*c08Oiymr3&CFyQWvc>(AVBH1owZS4( zw&Z48`Wu&r1Hun9J7JPL4!d}lVbo4x_G>@K^1r?!7}P; z{`sK40=tV^GcZt3MOIGLgA}w;J(2nA4388Idgr=NNLcE%*;y*ddLj#qeqTf+ zuZimyMv=Z94__J?McWMT)qU=SlDKr$tp_Ps0uYN`pLG9+MzNBJdXU0@#pkxTh4q9< P00000NkvXXu0mjfNO_!^ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..33c4c4214719026fe93417e9cd104cdc64abf00d GIT binary patch literal 604 zcmV-i0;BzjP)X^}#_ zD3RTat9CbDqY)`1vfjI$% z0O%Cgo4R{Ol4d)`m#P7y&cBm!nR^Fdid9_xUg~N@03f$1Zz=)JTf)-|MMimWmL`wn z7s$?&x4r(ti-45g&(Fz>F~&SP0jU7yK37%kIuOtRt^la_fo<>Sv#aYSxDToloU@i2 z2*Aq%>GXPiEa34;Cqw*veog>{Bm{hydz4fN;99wSw`%+1lSEXGkh>7Z| zotD||!xTXPEo$JH3JmOR47bDSq3X#`Emd>bF|q={-6oW13@AqfLJW|KDVkwnO^XT= qQ9vso{_~rQiU8tBiv`duf#^4^#}vfU>rOEM00000UCM|DAbtaQgggku8-k!il$3ddloUudhzCfEgp@@>$`ui~ zK)Jox>!GlHB-b)kw6>INz>A2ES z;SirxL_`OJkrWibhHJ;lyH%`1X&x3(&%Yo1E1JQQB^v9{KXLG!uStQ@1MK+qk-hp! zw#ZK;m1Z4cZF;MhWszC{@egT>4_b$c4uB#OUk3#%1_F-_t%|OV_)T{fC!&vnkBF?45lcTU_41eY3a z<0#BPQb!xpWYz=e|F+)9^eD7(Lj?5Zx(=0$85OwcG&}ruH8?5Fty*GzJ7xqFVjVUr zK-&lefb|*=d_)8_KZ0K%KKunHF#&9lIdS*^0000=G`P)d1q(FzrvH+`315-|l>^vxcc=F1 z7n50WE~zr>5R2)p&Z{y-0C7)<<$Lu_O$R_32yYq%D+dC5hSp^l1MXg(*}3RWdt_s1 zl~VhgLPSKQ0Br!;RGOLDJIKH~h_3;72ArkTE^qJp1g)8NF^0fubPV0x-kuWf4IxV{ zt1uR3AgLpUw6)s<>1XQ!EC(D4G2nrlzaXHuzkO2+@MF_K4OyN~j=o=;Rb^#XS+ zzXF__K!ZZ?4PXUR0F41?05^LI2M;UNk^TT=e!;g{0NC4sDY}1rT-Pkdf2q{4z-GPuOlNJfd z7eKO$2(LsGuYI>S6h0HhJ=5(syYv6EvkL(Cb5*Na>AnirB4Y&@w*vaBJNTayy8b-2o*v3aO#u#ZE48&3Zv`r^WT`~>6%+*03{O0$atD-#^Ge&|h7e^9!$d@QU z?f}-@Y)_y5qSwz3Ow3gqjMnjm409W60OM{_+c)CNf(8H^2+v9ZwJm`SMGD*00ryXx z{(+HO)j*dad5qOLnK9c$~pbmH_NV?vDT%P-~ zFmN=RF{XedO?!iHqh>O)gAbZ(GOY=I4yiAWUOy-i2)+c96?^F&fazp1%-@gnWH9`r zdUExFb~67GAn{{P`vH`DQ`khdc6W<{@ma?7xdUXF=X-o+d z6hJKHqPtCl0O~yII3giM3TIJA7d=K$A{V_y8t57$1OA6B8~82~!X= z1z|5cbLUvhEVA2sy7QfT{?7$~ovB%2*4UvbOOmq!#IJzK>I?oS$QNqB8GxMv&|#~E zmCG^YO|b$-t65%mC|oF34x~lF63GuBq9R)=BZjnJhdNRZtPn6WGb{R;inYsGI<9n4 z;UM2sL_~)MBPl3=4Q9v8yIHKm)I3a}-u%7&FK7l86&mZ%yExeAM^a$w0oMK9fxW#X zo8(85re+;tZTg_+(;~G1;vdl#pR^7o9RNinz7Gmk3_?nC zaH(M)JAme-HjvcO#x!~gF^)&l@3sb@9xEP&Huj`qO~BpWP@U}nWbX2~1UDXMyT7i? z?6~>7J2f|JiS--+y*LOc#5(lbGrteoMj$LUwY_vwiN~OI2xVV76|#wL?d_E%6~Gt7 zV!Z07*qoM6N<$f}Z6CsKn04zlOhe>&@OOm8UxoJlfjeO^zFyBvvr~&}z z1Gp+FuQN%KHe_m}D4GBO%b5FSJrQE3Nf=?RkuS$!Nk%~wxj!5NM42%t6M1wn)pnkuxix8sV*>CXgKScW!`7(*{2|S(suC%L&q5*vmpAUwGZIK8O`44>i;%5s6 zjePkWpt|u<<_)4KUV9yc-OZVAF<3~1Ujy*v8o=ojd6o3&PP_NRGf5i;L=hfBbq|oy z`ZD22uHFRB+V;}jla2U-;{F=Y80Vy~m4KTbINsAfl!*tAS1v?gp-1axw;CX<04$hW z)IO0kzA`MG2TE;jb-Hh^j}3~U#!rO-5Sn_d8}q=tXXarCW^D*g6mwYzWX?k(Yf7yG z^z5^3+%0YPE;9fS<+608JvgY_Wl2iC`2eJZwZri#l76h}QCgQIrLISGrCaR243hLy zUN37rNYY;R-~U?U>J{%tlHO?dh=V`g8^&O3dZpSJ9a&L%WF;{TdS`$ala2^F(*Y+S z{mJ0%+*B-lJl%uCauT?OavlvQ9WNtq!zB0xZckL#S4nYZs=6D_AL|S~050JE+MbG4 pHWdq^m;$E~VKAApS0&|L$~(}$q*QEprcwX^002ovPDHLkV1k~eB|88B literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..9e6b02100b7b3c243f67ef03509e34167bfb2927 GIT binary patch literal 636 zcmV-?0)zdDP)*i3?491_>*n#^%<$%Y47A#>)ZLOK<^9S^h0KY+0BB}Tzfw1M zy_F+S8uQ?Nzzl9szRjY zkRU9%aoLH*5SRrz-Q2aQq9aM`Zln|<<=Y~_ewoqEoJh3+YPH;ML0T&C@htq%5th z%b*bH%cx~8Edfo~>lTkMVHZx#k^}=vh3Ahc@`r#7sNF~g_kfO3h0Ni+c(epK9EIFh z9#)&rBx&6^AuUUXLb3o*G_?(xlR9Sr+D)otYOTacYtJm1(KZD|tm~HfNg$i=j%>E1 zg|$-#p{A5ppT`>yMv?$bhxVSe8mMd@5)n(SE&}XoCIOtccXrdRPuIUMUG-l-|5P1G za!Vcn!W-56@Vt_)4u1Fjavd21I9WEbWTuV`lJulsH;^O)u&j!s2pK>-9WVima{F(} zcdcup2W;$+5GN)1N^gfm?2tI{c?@VC105S@6!0S*1MEnh;jyv&&L)Aq(`d{f@4uI_ ziPgWrUB($JDjGEdNzxT{8q<@jpV)ppK4k%o{?xFk!j3&e+8zfE`hSZgNgs08(4)V2 WQmEnTsQ`rl0000)8Z literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png new file mode 100755 index 0000000000000000000000000000000000000000..c0c09bc6a86ed7a99e4b89ad1d5b4436564b0f29 GIT binary patch literal 683 zcmV;c0#yBpP)53r7QnYTL}^<%F#)t0 zD@4{OJFdHubbqGCW%M<=jQcqz4Az`T{Ud)sa13ab^Wb%UhOns-kXGDu`jd-5lF{Xr zL;2hMS76=YEYywWcdkw(6}9oM^ci5MGJrJF0xF0ftxSQnYD5nG0bcxN0M-rQ{Mw?dS^%=X2X<*-^njH* z&cPKx_ILQMtN>W+@#q`*Xh;D#@+G0{6{|hNsyBbw8g?1nM`wD<3X)WRerHGhJn0$Q zJa)kB0UtsSJVtMC4V9vB5P2_I7(DHt%m6|r@MY66u)7T4 zP^ITU_!<;u7{^Di*FL@d2mL6p*^`$TcASw>9z z#uu4Ly z-E$xeI!YmXk;%UeAGn%PT=hpZCwB?j#G=blSb3AB&kWVv@u$R5f2UK^8mAa=# z5R!ER*qeu7X-t0*H2}f_o8Foi^@v&=LWV1yO=p3S_zw7s&q{0h*vv-`N&sO2aJ9-D zGG?$PFk1%g0FY#JFv7FCL3Xd`$(j-puGwuX}%U$$yPSlG5M0ADiM{y{Oc!<$)%=+#g_C#eLmA`dNBMYP*;#)OCQ@ z>FNVq7@`5)T79nv%~Z`VPY^(p$izK^#J!u%D|1hgyZUka^nn(iL7qFev#kvH4}m}Z U_obYN_W%F@07*qoM6N<$g3y*^I{*Lx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png new file mode 100755 index 0000000000000000000000000000000000000000..e9589f6366d44d02b0c6d158045f6365edaa997f GIT binary patch literal 769 zcmV+c1OEJpP) zF>f4I6otQeJBbaASHcpZ@`^uz8zf6u-i@#b9X}!bgG5kZMB;e}kSUN-Lgz@4Xz3D5 zB$`1X;3{#qHpI*{IFWF?qr}O)^}P2E#TbiQo_o%{=ScU`Dw_o5O%z`!n*^GuvQ;(- zwKk&4meY3G-jEB*8&gLLC#tq)W`)9G-|RHcB#om=qg_#X*#n?2Dr09m&0SzVQb5w} z8A=LGlu>Y^@)`k&6OdhQl})@P>2fO==1o*Nl_cd&G*mcIod7U!GKN7NDUJc?*U@|f zm6xpnpz`Jt6i(~{Fk3kM!e1&|U6CZcQfaOsDzB>m^r*}hopk_G3Kkc}QDs|7@(Ur# zfD@GmXttA04D=&~s{lXGLs(oWo2W9EREGE@8afTYUZ1Bj1Y1W6fXUz5iLeAsR4(Zw zAM(aoZUVx>_FHS<%v=FPKx@5=Ho2Mttok0|y@R0s%L+;Qc@ma--|K~* zXSo3|bsSv#fyZBdK<9K&`Ez!WXPRe#B<*LD^UI|^^{=uLNjlQw>HR!DYM*CET{y<) zmZ3iU1D!IiXl)p>a#{Z%NzHnxncsiOR=k=HxSq#sy6mSxO*bPJ?dR|Rs3T8`B(=BI zY&@_}$`@)=02&(ia!ExHjP+AL$Duuwtm3wAPL~G%oAK-3(??}@ z-CezQ<9PMn=#23;w=N{7UnNP}*Gj2jAxZxN8b2jSKa6%G00000NkvXXu0mjf#)4fy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png new file mode 100755 index 0000000000000000000000000000000000000000..9f1b98c92c47239b9a0015833f62e0ca2e2afb47 GIT binary patch literal 829 zcmV-D1H$}?P)sSp zr{TT+ZevSTRkn|69MzJ(L!1CeIg>EjcFQ?o1R(u~@q=0d)SFAj7-#L=&a7U0D1+|& zFA6gAx5m+U1_MLSNf@D)fl&(Z18xKLCIC+kEqB`bqt8=~t(L0FN2$gU!$$4C(RU?` zIPyV47+FAGN$>-z)}?;Q0(AS!opxG#kBg1Xma0n6(Kx!gF|C6fwOqnzu4>I72jH&; zWD3{ggCK)lu`*lW&Vr~jFsc~KQEpY;}(XLnmTLs8#L|C0m%GujkEK?ojWZx zC0rS{W=WOAY#8Kk_I}y!R|7hpEHA=NtjJH(t4=s_Pl0&oU6bvUc)? z4cI6xrf~Oj4%NleYY%2T?p`3maUD}jI5Yyin?F=OAsZ9Yf4OG{oVy^n*_{+lavG1F9Fk9^WraCcu9F97r) z0Ca#>q_zH+s&nfzUi)(p3kf-T$^{++0gJhh99#T2DGsmhPyy}-$C@Yj^UV4xj1_z1 zZ3|wex_ZHDXCT(JRHtvzTPPZ2blXC&&gq~iIOe6vNy_PVsx$1rzN%_o(;zC0qI^5) zJ-=@hZ4dC47jb79O-eTcubqLT0ExQZNbo;2PK?HQ?F{|{G^wS8$G2eH00000NkvXX Hu0mjf;z)sk literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png new file mode 100755 index 0000000000000000000000000000000000000000..367ac6697ff80161ebf21b267800e59db0613371 GIT binary patch literal 738 zcmV<80v-K{P)?0J=?a`%+=_rD1L?r(K&|`^?0hsDqN!{< zNwR2i>Kaic1IYG-%kEB%qDcU38n88wrm~hKmC+m(ji~5PBj=a|qX+}73TWv{_E>yaGwDO#9i^?6+uM5-%gYAON{F$k2XOha8PUN5wRRX96 znQ$;RiU#2EgcBT) zJam(0pale5(^vg=VL+D+57U~h0iJQ?vNyD#u{7!*sK9pA&dZC%=}fa%U)+$(Mz~3p zVhQIr?KTd8qDgPT)qO7EJh0)zh=S$6v87do_t$+G&3OM6fqf4?MN_f2+2=j&7CmfwJUd{1-F#!@IOd=lFm4nvaS z9X$`XevVI{EB1s)`Q>ZJVX+Lw_&5ChUR``gLXz4idXg@5%W}0nu^`2k_1|}wq1{t` zT>qkwN!{{Vc=Q>st+NUpQ1x&tL|4@* zdn8G}tLkV5714EDk`7fwYn?KQJ%AIGiTov3)oDYL+*O(OMBQr+5He~MJ7rBTNz$$| z2+>vjw*dEDrXh*C1K`pbVd2w{oH7Aum^iReMAxs9G-x-vQIkXsaB!ItENx#X9Xy>6!0^oZ>ne0`Rx1IoQ0Igj_)IESM!@RZ~j>fnHl$@L1cU6sY zbyI0y21(Sd(!86&DGb(~5`BB`U=Gj1xoH($RrmO_JxS3GNTTdtc>e*gCS%B3rzBkc z5y0Vfh+f5R=eDFF8t+IKMB?Q;KuG8x>4UCm;CQGq0Vd^mi#>r+V@kfhyzhwE?ph9vciITDZZF~k7u38&xl zIe;0!tmbYzDuRIhZ#@3+;KUL%&)+P(``nH)0Iqz!5(?YU`dCj={N&F=sgoov6xuWB zS1f+(BBUi)d+Vz+(5@)r))v&u&|zPdlu42n;Grsw2Qh|HBl07Q|D{5Y`xQk`Nm`u) zVC!%R!k3`(?_-Mjcp@X%gabB@XY|A57^tuJ>j10)@Cwb7KW57=dZ?gs(dB=Wj>rFd SMjMg<0000>yM*j}LzkZd{^0d2sfmczCdu7 z!hd!kmBCs;sis zWHyr|RW-9l(<-YhB}sKFt(tnyf4B1umSo>ud67hq@%n<3*O#}A;{IB$LipJW4n z{RaDgCvzQK6nmQ7fJYy1kcN9IYkimV8|ACp-BFrU7l zxGx)eA2r=8yMXYie_{d5x&t!LV)0MwDoGEkd_2_|wZ8?3T64rj$K$eYx`k;wHhr4! z$0>S(P9sSNDrb_ag(*JFJIv`eLO7LF_cWEXy^sjt*exU(3(pr|^`CjFw7r53`)&bd zMj3s+OiJR=uN30&3XMHA&9m~o^iIp@XJKnu;!gOtzu?f<+PimtsAcqby|A++`4acl s%CM0Qd9{E~G{1*)}i!2Wa`SP+Fbi(f|Me07*qoM6N<$f{p1oWB>pF literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png new file mode 100755 index 0000000000000000000000000000000000000000..a44d3665461feefb64b4e89989138e3d920344c7 GIT binary patch literal 409 zcmV;K0cQS*P);NCI09iutolg-zjveeH#&2?)C>mV}ySNyu)>Ibn!z z)4=KFv06HfvozS{`dBS@NwWNxK$7~a`4k{IUoQ!@bElG|Z06KgK-P!H-$M|80&42C52sh{#2A7SYTwPo)j%zf&a zNqmH!(3qgwMYylkN$QaXb!t5!Ar0l1ls(lyg6r~d=Y`HISzC6JWZwLPwOQ&ehbdI` z7m_p+w3DPr{MO1&e|_A6g;VZ@HUd@;dm35bT4;K)naJ3LHi|mw@ZQ{GUmHZ=Vt0q1F~$8p7-plC6h#261+H zxJ%(0%*J~jJcKyYV!Z>QQzScZ1>%g;ZH^>&AGx~^Pr&rTqFGV(cVo@QkhgtUfH=$U z+tzg0QF!yndGM=Peold^tE~rwaa`LFuVpK_z`AGoFH$w)YQ_NMXEG21mQ<4n$?P_{2089kK z)-DNL74Fd#P{W_dfdJ2~+y7xPH1zS(1~(i?YRXqyOOn*|4B*|GZvW9cM_T8f-18Gj zLfVX4`CQ6{%8lBnq;E3y)WCbd5{exkO_kCQMEBL^3@}eauJN>QGbHMb)5kcJFh~dvb1aWeJkk?9=(_{N-dF za1rF2W&77?{ATL{ zjjBJdo_GCcS03yDJ6rti;+u1i0CL3V$>rkT_lX3JnX(;~(9?SMW6+qkwad1M^h89& z!IzCAJlQy=YanWiEPA(VVZ~*w@hPSK)Abw^+^A`eWQ)eBU+hVufg^pDK zy~W}wj8`843tKCgRY+!44q|3j*XWq-6j3vSli*Wt=>#CJ7ZX4aHx&SScH@Ndf8BW; z{P8$QG(fCmM9a*eWg1}OD42){w5c6&%$rTiI>WEL%q5!9Z5YMhFv`hhx|Nh04 z7pwt_0=M4DZ0v#qIqVVzyz*%|c%B@Pp$6a=zuEoccpj8a1Aqa0x;*fpTvcHENRIZs z$uOjK&H>AdL&Q6C?)@9s4+OMOhqUH~(@ z4X|vsaQK7S&U*pew(6lygYQfDvCdjaNvU2DQ_W-Z^ cEWpqC2bO?MXIRbj0RR9107*qoM6N<$g5D2gasU7T literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png new file mode 100755 index 0000000000000000000000000000000000000000..4c5fbaed1072c5fad12fd41302d8eec27d3378cf GIT binary patch literal 671 zcmV;Q0$}}#P)>1wgk350(9nYT2PuPgM226$51@zqMMNtSsx??^(*J5{Gidoxh#4YZ<=h3}lo^X2T%FW&fe)=YgC z?%i@os(7=ciiICkz2^do`sr>S6;*&dL+LxxUdbgWO2Hl<(4jqE)84@fG%iXT9tp+Fu*MV z6ahFjGgrMPBAqxuY?HNGfWf8WfcdkHd_cSadn2W(U0vxkYX6c>C(GqwyAqX-ZNt&9 zdva=5lKXnq5o}-@MaBiJQAp8}isIM>;`& z?}lBe4Tt@=N0m+z*xQa^`L!G0wKX{0=`tMs)RKbZ^R#?mfIM4t9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png new file mode 100755 index 0000000000000000000000000000000000000000..0db3adebf9f623b873113015413743737a081b3d GIT binary patch literal 569 zcmV-90>=G`P)Rv=L01qbP_E7l;?q`m=1=#m?BgRqkm&Z1xp^n<@wV$&&a4fJX}lJXtu~fX7QWg{Mo$df~7)^)w&we%jUXc`(x~`0Zv~N!%PcP4y^}@|*Dh!J&~-@& zx-QaP*9EpZF1gfoS%K}ie3#Z8ms>EuTmXEjYG#cmUF>$;Er6z+rHmLvjt1McQ^NjUl>sW{qC`ix~~M)m#nE|zaj1Bfe4 zUP@ZO&7NK9QXVqq6+k{n4wC_v_V4&QOXy)K3CNA*IRSWBmh|5hTAPev4Kfb+feEk7 z5g0s@;qDw@B*280nW^7qW{fcg%SaW>hKwXh?aR4~cE&#ee3XYdT1vVZ00000NkvXX Hu0mjflOp|V literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png new file mode 100755 index 0000000000000000000000000000000000000000..635f647f475cbd5006ad2db1214cb75a8643cc25 GIT binary patch literal 610 zcmV-o0-gPdP)tgK!Tbd0RMtAE78y(Ks2C; zcq5v0v6aBCJ$vV)7-#di%}7V{IQs4#o=TEb-3g%@OJmQXnkKC+sB7xIYxG+%nNUki zvYB(vIoBJMWm!P(R|SA|3#zx^*-o!~ig)8*Nya#V4SlggNzpq8fVduW!1*2k9k?*J zF5s6dfErva0C$iq2Cz8-aAN?9?7gc>%MY zfXflUN1*PVB;p$Y2awkQzCI3wd>_D1T0q%)d{5xS@kZ<|(pizO$Dx w1nTsS9v$sI&j6)90NF+XlF6P)l6Io>AE<6nC~JC@)c^nh07*qoM6N<$g7$O_cK`qY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png new file mode 100755 index 0000000000000000000000000000000000000000..3ba774243ef5815ab8019973d04fa0007fca4158 GIT binary patch literal 784 zcmV+r1MmEaP)A$ln~A$(%x+My#+HmCc#uK`L6B+}dr?&MmV!d}B!U+!cv8Ak@F3WOc=Mne z5urCP!Kks>u}F^!9z3)pGvc4crS41!_M6Sje7_#HqPe|1yuaV`zD98`>b65`%zQB3 z944kQhl#1|VPaf(n3%kMnAoDMt0zr2$%?-gycFB4C^husILS`wsFxUw(hQ8(yVBTI z4H}HDOHJb|X_mXnDo(B&bEnr0SSZSx#_RDP&b{TTpCp}1bE?K~gfpAFp`_6$wY8V& zbEYWQfhfsrS$nN>DJyz_;)FDJ8=rlz!&hbUj!tZWZ2myv=-k1c%i{3s}n!Fe^ZU* zU4_@H`To^|Yfng$&z)&Zx?;Zx9^QQx(63$V1^xvr0Az)&J^k`4;^@D_bY}0WIXT~m z6ViO=tn-^|V@c9^g;~_;o0Zci?*%{#lV0a?dW5Iv5iek=dYxA$k|Y7rwE7d?7)X)^ zvcMlJtFm(~gg{68@bYb7f#!o3QTKl!c>6PBVt{5I*;`)t<-VT*g1P0Tza#EWB`F^X zSlXnex+6(xOb9$D_AbsVdsr|6-ToGk@=PURE=g%F3vHQ>su&;x3+BN=2|WPXgb)Z> z^W(oA1O20n=`@#ALtsWdckoCbZhi#6AEhCcduMBFokt}NC52sh=AIMBb|!NPIPLE) zO{ddbss(|m!h@Ud(_CE5sGAlIjcWHT!PZc6V@dV`_Py55wTB`oOTc~~n=8kcmp@OS z=mQK#`qcF}FKJhj?1_lSE=Z?=6o7HA@u=h}NtY$bF1A^W7PJj)1Ka}49PB%2cq{n; O0000P)pknF5GJI}*pssH2S&*%N#`~PV+>z($` zqy54DY)e&Dc1~&(R^ow06az>xl_1=)iy1)(AcIHo{7MW|8!N^bYyINhyxx2yh3?|7 z3NrV%Mqw?5p`jNg2vL`zQSkA6c7bXGfG0=lowoku@ziXqsjBjEqEX1GQTMhq}?WFQPS7x`HswzE8qwvP&j1F^DG6}+kvTKGp0B_AV zlS;MW0>q}?X{-KO92&(`qW}&k{xC;Lf)G@O##D@Vy*F9ThO>ORSMPMYA6&@xXInK@ z6}2_mP9|;&QS1UzNF?}-#dmdRte(dEW%+^_J2#}z(0NzCMU(CVfGnQYD81z0-D#mA z!Rn}MR%ALidKTUl2QXfqL$#T;&fds6oldK%ss@=hubw)(u>slENf7PVp58tanoB_t zs;XM_U*^o)kb0!20LvWtguj5U%ixFi-oV0`rK>;`ktM%_^>y;*YYYlZ2Y?OYHJ5$9 zgVggHJY$UMjC}uF`6Hi|Sq9%c_BdA5e-@L$^wB>ujse^st=>`9jg-RYDTrQQKl{Qa zY!;Rixc51W^78rhhf_}2mkF_K%T!{Hj6v`AkEKsZCxql*?wbSa9`J8>=LHNvc*q)8 zHv=J+$wLqtgK}%^oIqh5A0H5|ExjntHGO9Ubw^y=;vX7fW+*|QY|1TR@4qgN59nb4 z=m5=7D}%387dGac_7^3@g_sOIX9LGUz{Na3hMWI5$#bpkQ34)Jpt<#gZq3wy38$R` zwHDx{s%w{>b_(K}n(E9QdP{j{8Q*oGSLM8)7cBGA)D*>JC($_$USCx;tEeC5Mqb{H zd(R&j`L&05%L%!AjK+m6-)W~{DL|~YGv@yfjbft_PCJGF03I=@6jul&D*ylh07*qo IM6N<$f}ApofB*mh literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png new file mode 100755 index 0000000000000000000000000000000000000000..3206c9dfd1dfd5193650ce4cb012c13a800a2c60 GIT binary patch literal 829 zcmV-D1H$}?P)LCh^wyZ zP77`%g5XM2lDt7Eag~6Gftbuo3?z^=^U|cgH_n^;xtNK&!^iJC=i{7X<~p+{pFMp# zI$t#LNZ=-I#OJ6&l!-W(ORVD!%%oi6d!hvZ5$Bp3K0}0_#|5^dsaCh4zrFB3;{uy{ zH9J*hU0E4)@JuG+cErGStRHhOSV}xm3(Sa!2NZ?5Nxc>%Ep!9jGu5?XcW=Nfj>9R> zoFqwabyStKpZ0SQ=c)k%nf=fhV@#=*380XeUmmTP*xp&X^2iZD4NEA$sBz-FTLvP_ z0Dt=OO`;b^{;P+;K?!{f&=+k-#`h!fOZF(ol8`O7+>X$ zkK4yLvH+_{D-b97udm*HV%dV@Dlm85X>as*$BLhJ2g~2hc7OUlD{B9J%Y|tjeduw~ zn6epz&g%B-5uEa*?*av}()=T;UfcgMDJ=ghscqrm^Ao0&HSgygz*@yaTkn-flHz3m zteL(PPexQCqJIDw*?#&q&()ojZcEB+26$mc%0o$=fKb3}1qumZ zE%}p|0fPQbEw=XN6}HrNJt1KI7O!w%3gts01~_MU`%u_3#u#VZv7X0P&iJjh?VYvA zQ~*rCNf^ZtbE)Uqg@UB!X79DUHB!!+%gGEVYZXOc7 zW~ugih0Q?5m>WD$>k*qVroNiaWE6Tt5YFsg9KLb3uAv@rH(gDE@Q3~wYs}bStG>GN z7`6TxK22vz7l*%iuy(kh3&GjnUIHKYb8I$YqE%m=_)L-_*l*3Iv*}Fht941fD%xqY z9s#4{lG+k^Cnl7nB}u7-|5(Z()Q+TugupaVC^9UNe69ZgSgFfw37u<%00000NkvXX Hu0mjf?HQ5D literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png new file mode 100755 index 0000000000000000000000000000000000000000..f5d88201799284f3abb8b5e4d08244cc56fd6356 GIT binary patch literal 846 zcmV-U1F`&xP)W@j*M=8lyn_%UZc?vZNxQ>PC#KOY4v!0_Jr7k+OOm8_ z2Wm(H)QImk419-XBl1?*e z5K04LUR2+Hy zmmZ`*`3KF85M$;h`)db(JZIv%KBH*ZbE3bQCVbIAo)`N Y1BsRN1c2~bWB>pF07*qoM6N<$g6~e3hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..b86058e75cca3a98ae3454f8363203e13cdb7ea6 GIT binary patch literal 842 zcmV-Q1GW5#P)ih|TyMN}*$o76)Q4|-CtO2C_^;7JK}M_Q$Lu?G(x z#IByii>()-nB5Un>M8N07?YhX(iB^FhbDDqo!Rg4F!B2EywCf4|CnChJ^0k2L(wx; z6VH3$ijBB{0-{Xxr}NZVA|QJs>mmNqU$6pu7H-u?u|E^r;Ns~lkKU#B-W^RB+GNjpmU zG3Wo$`rn{Q9_dQwr5CJbYM6TMDbk~Hz~ z1$2YBQJ&DFc;wiZK=4ERVu@gB(3#FT9>4Wr30`>jRygY$1etW^c#&?}M2mGo#h*KOf%5u;%@At+q7#VZHVL3KzY@Kc4~@?^|~IaB{S|eewfIiePut zO}pvZ=(FpR+=3XV{nZF)9+xzhDDoIzl75qvO1QI92EJ}fT2JuI_sAC+7Dz7XKO}$V U(`lmKQ~&?~07*qoM6N<$f|y;SZ~y=R literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png new file mode 100755 index 0000000000000000000000000000000000000000..02902e4be537c9ba6a7aafb4a843856c0ac42c67 GIT binary patch literal 853 zcmV-b1FHOqP)U~D>!yW5GG8{*t`ekRls0pxmL}LZbJJAsozA`A>tf>e@Vtlja30eht{r{(*fDo{ z!bGz{xMCe&pb9P(QKm4qA#Y<=6vlouv;ZKYOwH+MaOf4nzz)^y=j-~@3xAa`u!&b^ zSXG+K%Af}?r6L+Q#y`ROxiAd2Vvp;CnHABvTq!e&*M_8xJkWhp?M~$T<7RUnW;|_4 zl9q?6N;*gmdk|%+0pqFt*cf9>v6c#;5Zm*5s7`l_FEc2JoU!$xruE)j^T25Umg~hUPwN9~4WO;J=m-oQ4ANvfd0aNb&i;yX9 zP4>2tW$e?rn~?gi(qR#LTs@b!e@%}rKkS@JvK{RNTC&94xRF)DqM&Ghs89?|k<`~P z3@SDZIs4jeh;wK$c_&c)y3?(Q=qb3gn7nR{>v2X~xUoNQrPBA@)LQu9_qW!x{EbSH z?pnj19(kfJ$`|;0f+-6xIE?cwzE2&-xTSlpdS-aVGy9R>(7?i@rxz^gP=!EXEfjY@ zEE0RJE&jU+mcMGzildS;Q-ILcOqf<3lvSfZF9LYF-Ifm0 z%Nklrl60{GOio?FTC(Tg1n7noh97|C-P${KKxHr|f!7y$d6xPpVm?n+i%ZjA9yz=|U@{ fN+QJq@pt+UcO~|lNBc~l00000NkvXXu0mjf2X&QK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..46e0b3c1f31fa15f6d6e4f2e1784dfec636d9b4d GIT binary patch literal 855 zcmV-d1E~CoP)aL<< zY!^P`!ns>14g!AqF!D@Gp*B-o@Obp%{Ca`S%<0{YjYg$bp2}8}h6OHuN~Gm&=+9d$ zppm0!^veJf7D6_EQ`O!5{D=05%J_Xb=jq1G#j{VJ$-nq(ulrxo%v*Dr*;mX=qVVS) z;H>KXnfLwiPVnrJ-X7%Ed{bSX3;jna7AAmMTU%^L%jVKG@Z%P&+wj;gH%veUJl3Gf zP6Y7Mw^Ht&YN+8cCSc_#6wJogwX!8Y`P%+EwGn{D*50VY{W9<>V0KIiSQ(j^7%bfq z0bDH|4+eFXbz_YA&MTAvh;4bZ*V?*s?r+`%;0T9L_F7;wpgbeoPO z1!^`2f30olmyl|aKX_!s^VRAPxz)z$0Wj7KJv7|oh0@T04@r57_l_C(@N5dIV3z3a zkGLIS&P$W7R+XfTJDndp4e8c`w8bE9|lxLDn8hQXTnV8=|z&_xqE-_`<(MUhu8J88)u(gT#U|F zUA*9h>lX1LS|&td`IY6QmDVB z{GG8(4xOA!ML(wWWdYg!Q2FV8rVEmGl7hIRXfAh2kx2S$F!prhntRppVI}pUwqK(s zNzz)@mo(}nQy;{643whOE;#3$^IIu^F|;d^q#?sdh9fpMd;so7Sr!sS9*s5ty>?NF zSbN>W4*;}2O#L=ZW>XZ;{~I(ocnpeBn5VEHV|x=IPw)Kp`#ipkODRCw%O3`i7RFV- z>s)IXw12JC2y(ucz&`E0d9OV_Rx+?J02ESL{^K&^+R(xcA6!U|gN=u=(P%>vl2JtC z{F%t`gN_uzN1|H2ivc&C3)T7=M!AOpOAuI{K3ZYUT6b-evKM)}&WIkOtb4 zu&BY&8@E{jpUx%AD;v}QX6TyYIBw%K!z!xr!{z=70} z?(h6K`2{`WD$cn!t25<+Bx(GMmLy4Awmjkc+ybC(z}Pvj;HlPB0wqbi9qmZcJ#$jd zUg%mh04l~f0JY*^B6yM{OLCxdJn?a0u6(MhNWrP8e-cS*Hj}B3gn?9`TNl-x059EJ zb{BbWM6U<|_-Al^a83E{`#(sMGBdyKzd~uX;$FhoF-&hT1U>EW%X6{(hK$3bgyS${opP0Rfy5?@8Ifc`n a9X$edhrb|aZBhgP0000x|AgEh61(gos!i9@01k)yDzA;k~DI(a7 zApZCVT#MSwOS`a(Ky)c3GcUB72r)BFi|IVCc8teNZLzs;A3z{sXRfV=;jc}9UuG%(DTnvEkDf2956V#$1{{|5}0%K7L{*b zVGF{gef;k(s&Vx07BvX?4d#QXkjZZ#1=Hu#K03D0%|gljaEsX!PXTntgX1>4W(;P5 z*8vWtrnK|x-Qo+nj%%1%qxz(0pJRrk(H*VlGm^HMEPzdACP}daMrMI1f%Tsb`3a^shFQvf=U4Pz>K vQ%WSMt-v)u*5Je~qD^O;sPgFKR89Q{vd^|Np1I#900000NkvXXu0mjfNCTFW literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png new file mode 100755 index 0000000000000000000000000000000000000000..b9b37b7319b255146380268ef491dce07f54a3d7 GIT binary patch literal 805 zcmV+=1KRwFP)07KVRPWJs!_&6`OOv$7$RW9{4v;Z(Zdm{7#F0h5xhLva03;!K1u$fm}Z0nCoRtBA3 z$VF1#;296aL#J{kF0^(c`S(1@q@reNGOu4HeOwXe^wNeOR{B!XZo!r$NtX*nEF5+ST&_uX1;Q49kgg&ul=MF%rM^qv7EZ1^?T2rf1ZtlX^!Y=jWI{w1llSDI&sgI?7(N zz_u|?j^D_e&Ce~cC?V5tQ$ET=%g&wC<2^i2Pr?*WS+3>grlsWNw*e*sKK;5id7fOa zxBoQ9^OKf$zXeDOHKrTnSYY({kd9|(Wv~Z!d7b9H7ulX?exi*k8!hqp$}jZxpqt*O zVP`{T?p^?(rS>g?xC?F~77ipyRUG@CBvs7RAZlt?u~b>0GA1CWTlK*(fQF>FkQ$P_HB!le!$4WPR4veQ z(r{zjP{)<&UW(|9fti?|lGNgragMCsX{M1R$=e)cj9F)58NhIqU{?3Djhu!g$p+1h z!OHAd#hpYQPz5}prcrx6~HnWZ4-QOkoo)~5EY6L7UNg7F%MM6`O9!Sb0{BlDnq*}NlHi#KDc-cOP00TXeMQH+%s63O8^AFEghHu z&r)ju$osW<#M1w15yuoYXggZz9X~U4UGlXM;Y(JeDM-ALrp*Z_v}!rp2X>^;Am&E@B!Y=ON;*MLj@ zlN$M0TU;3 zOOk8`GRCY?k1_z8?VHMSH(eaK-Y$T3+>QR?g|*pb*a6^a+D!rX8^3Hxk~QX)dc52{ zc(w6t#3NowH`0xG<&P$kB!{=0*`KaL8COO=dLb;t%iV?BuA~4?;>~n3-H7l1E~#vZ zK^iUxz;sj6Kq4PTxRSIhDU~Q!qzqggN%|$hFOPK5pA=Rq;+Q5+4*{yU2hNH=lkn@cx`LA`SKg{^YNu=OXi|* z%VR!6i^RD|3Wd}I?_;+V(y{^_07O!#+j^B4y-A3CsBV9>tv^dq{zQm8E7cpem5<~y z^z`*yB!if#+jw~{gy>;f68FL8L^7qgQCL=LSJLh%(uuYAr$@(A_FxHKDcO=F>Do{i zC7ovD9wdb>0aLkuWM*ddPObu|rM@ajN=Zin@WffGk2S3(5IalP0f@iWcVMwoZvrUn z6t4kck|KSTr||#tDfK*$fVQ)<4p2Z^2jr~e068h!z>#yee#fknmd|=Cz#KnI;NNVW z{;>JCq`rS!g_7q=Id0-%tqvAQ<1!h%c#v03%RrJAapTE_rc-ZrrI@YTLB|Wf2M4yz zf>!rG>^9N}2Nop6cGq)eg0r*!^sfh$WH*7DoR!?}IK}A~9e|qi<<;8M8S>t(v*QED?l>H+q_jrku8^7`vM2~MmEo#-7y$1rj_}kb6 z@I|Qg{{WxdlnDGP;Oww(h6Z7>SfRbe9<}CJ%w5dvx^ZKyJ(47idfl>nU((1ID||mT zO8{e5D!}4}>L-;PK}}Nca16Lf1^a=Qn52xFpiW|whS@MHfUepT$#XN)lA5ef7?oM3 z<7)66WM*r;(5V=Q3AVhKZRFG{M%36iy3$%ZyR0)6<4Lxc0gdm+F>ZElroX&*aGuJ; z?UQVyex>#KY-e(yx1+PayaQ!2?s3?I%l+lO%b!Y845$6gY%|;F-+LsfY>PqGTaJN+ zB}oH`Vwlj7q=%9+iSlD9hlUDle>^3XHHI#$*9qpCi7y-pZh znApxXTWcaBdVQ}%el5-{q8LC|QtkQcb|uI20d)2Vj$4a?M)#;v%DQ-bV^MzFmBQfg zB>}x~Ln6PK!q|}G+Vc^^*eJO;F1LY37l3EVoiLDJ9G+{f^+ZH}mPq6?p~Q@g%+{XI zgCECuz5(>X7{?_uHjOg|V32jfKn5o`-TJpDB6?^^qU@^8FbTdR}mwC9%r z3vf1n?X~4hqkG;3V$}&lk>$$RsH770VJ30Mb5ynGK~0Z$nT>HSfXc_ae7n&J2T5~~ zZ?@K&BBI5X$d~yOnkY7aE+yJKmpt`{jE&`eIA8JhlCnp}V%7=0*40e%(LE0DlL-0o z&P_{Q?KvO0D1D$Xh8E89gi%I0g(1?*gJ-YjVHg&MDt&U$o4Y|2MX@NQl;oev^#1$& zclUta3rdDn7HWn=kj~A4rFMM5)~hfuWamD76OB^sCx9r5;^Z%|zE9qI-3AVLfDM+j z-TwULtVHUPKL3J3h2@`qpW4;nXp8KJM<0XBf5KE##8a35K6w-1foS=Pi1brRCsWY! z^Tl`e1FNNW0(ZY?ac_I+(&`LP+NbcbY)jQ*c08Oiymr3&CFyQWvc>(AVBH1owZS4( zw&Z48`Wu&r1HunD*8VqWh90O?3#p9I?s=z~F8TB;( ze9&Kk-9@b#7^tTrE2ru~3fici$ozGNM~Vi$bKStO!IE1fEcM##ER|$Ekp)J-FCvoH z#PtiKNZ*c!FO7_%ZHD*iK6gS%T)OJkgA^P)FyOZ4slZ2WC|VBEx&{bJ`;jrmm~dPI;BtFSl9ZE|06}Wm34M;Y5L+&901)Q@HnM~R07brd zwCEzn0EUI&wC^7*cXt8mHb<6^OMtTRg0ox?w~ZO+a5e&7=6eb3&FO#B?Gs72?N@En zd|tc(eg)d(3bT3dcTA;uGEkNG!Ikg4OPVX_=9s>~C@ecjHHSS!A z0hrnNGN52cB8lw^JjGRt+0 zF&l(Dj*!$O)gr=MVAl2L+yad;0rJ8=51}I|(Yhq*giMmsNg}^&g{2vQ#%cTKfem%P zF{`atoV8|YxhH9G?x67f3tyvhwgeetcG{U5PEyR~adA)%C8-jBX9w9qwt4(v;xl^C#071FB#4G=-ynSaY zg=QK4%?^vhM7MAKF6mE+vm)7yhl+=NNqZ7?mD0~NuP2fw5^hT>dvYB~wE(Tj_1Xzn arSv~*!o3_6aULfC0000L|!tlF+P=}yFh6f3P36{WM&w9;a_Gn1BNXOo$E9%h&Nj_(ezOg93?Mh*A z_>zELxFJ!{NMUTqaqS0)VQiEcLFnjpwLp--ntW?=luB$@J%#IwVnW?D2kK6!1_LU>vbDA-~%>T z?sn_*m$MS7NBaB=3Kf=r`h99wouf^%A0B-SF8>Kr4G~{m{`=%jfCr-GDi`nsT%JACx>X)RuHOUtDPl9z9c-IDt zP}!23E$MA!f$saDN%fw4AW#b7%H%N(rJZV!Z07*qoM6N<$f*-w;z5oCK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png new file mode 100755 index 0000000000000000000000000000000000000000..8ec45c9c8566315cd9a37ac79f8ebd53d6c661be GIT binary patch literal 828 zcmV-C1H=4@P)i3o~RYpi$>oxDwol`Ok6u_Qa|&d%duveftZ@ID`Y@AJI0T8(b! z=kdXCzup!R(YwbbvXyw~5XAtxnCj5(y2Xsp0_fqx_(3HGs?AlUl(TVuZ$YjZi$HgIFZOQuEZP|Lsy5E zV;{zZ)&TmI7(bwFT&kB1KyTRSc9P2bT&nN1L`3%;iR`tlIT_`sWID8qWurzp0DnDD z(?Ydr0AkhXc0~Rxj*Ma|Q3!{VV3ebzLkpru#!QTVwLe|XMsN9YuhH%GJ~)@{*V{D_ z(c((9lT2OLM6m&MA<^MyEWIluW9=mVFDvJ?a&to$nzCTzTO{c%0_dfa5~UY{J2Ndb zb+|Te)T*A%jlP3_$pe(1=MZUS?bFw?ZnxWRiHJhas#i}O-Q0w1`#5MB)SlisWz~f+ zv?3x7gO@q=Hl#l33BXZDKH)E*XBhtQ-Wxb5wR{mCL z?;!R4CeJ9Px?|t}R{khpwa)OHM;^tA2G620m_7PO#xa0 z1zUxc1nz#$p}cZ-ghvZl|r~NaYjR7l7IlvTv>imdspP0F~p1+*Wn+UTFp^{0ojZx;qJe#y#SEI z0MG?mRw~14#az0Mb)4a8)~85ejAG#J!AZP;j}mY{(6Ud+_FN5^@;WI{ ziwt<|CvTHtCCkoCEXmHgv-5bEEcHD;yw8W<`#dkc--^0F zj}M0X&5nqO-aRg{sU<^;I04Y*OowLIF6V>>Ko1|r3u*~aZ?7t)tc~+~3v&IT40=ny z3h4R2B{q!=Muwc%p+Q_mMk&AxxDC|X06aO?in{WX$5YLYFCzNmRAR%p68FFu+B!5G z`!FFiE}&mY@B*r?P5rV9&>Oa*Zd!YvOU)f$M0DSh*j(G1lTnUZu0yj}b=4>b;H?L0 zTBx^OfJC*TuE?L|kx|Yh4&iVbjB-?TXh8JHm`U)i_NS})=q+FFwW40{gLCC|;ioVb84r8@kKrFUgytewRBW#yb!c46pJTNYgT7D;=H0D9@9#My=5&P+>f z9j=YLYE{n`M&H7_ka=V$081VDguj5E%kYQy-oQes<;y@Elc&Ff^>zB@YYa=whJX#)^H&1C zgUs{VJfoC~#=ifp`cc4Yli@dyJc<<$o<-#_d-RW-V*vNYYqvyXGo$o*23oFeoPJ>o zwn{50-2I$Ib>-~FgBg$87cp42rD_RBCZK=w$I7QVAE&idc)f^QVkk7ak?ETlZ6994; z03yIQQX76HvbZ_#bib&eT}a50GdA!TXmC;Yk>eIWP8ZGVdsKk?Q}E3bvOU)TrkrjD zG-ANXMAk1l-3+vgFEV$F{&LZ*CljDw=WI|EEcMdN4CQn;m3a=fFCvoHBrt_hly4{f z=MRkH+5^1h815V;NohN9x*1pkkcjO}1ph1gh6otRJ9dfwJ+mNWvl^LtbyVB3H~R@u9g*E0Qv$z zLhHuLyLY(P`5Nf1tGr4W{4`Xyq`^RuShOHu&TX0N(#n2neZ&<|?m&{HeD$YJ&0Uqm ziCsBt>$5rMoV5=O#L58FZhPj1pNG1dsDL5VgX8U+OZzZmhK0IXwRqU&TQp!&0Ng7s z_jK@^{xBQ6IFS-gO?+q5Ngf*jSKOz`KUiIrBmnY2cv~4LZwYJ)EA!L@ifeU-V`m%7 zk(9cUB%8#zJSB6kbM9OQRsk$yuJ8AAy$26U(7fS%obd?Qk8wG_xRgedLoFpsO%nnO zlrbOyW3!DvXMdG6?I=n^_K{(}x|o)og1Z(R2z544OdQx^`d} zr*&02fEl}#;CsFP=*Ni`+SFVV`FTUA7eL&@Q(+M53Sj;c1FQ)^1ssE8`&OiCa0OUh zRV9H{sNdf?2Omc} z`{~!A((@B32HDtI=eomr{1gyJ+wQ0VD9jbBus|Q&0VY5d@B$z!?Y1OrVY(XplO9l4 Z{{U*)6`@>Qr6B+S002ovPDHLkV1mfu1sMPU literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png new file mode 100755 index 0000000000000000000000000000000000000000..071f7421a1194ede557bb6bf4dacec5d38d6c39e GIT binary patch literal 569 zcmV-90>=G`P)*cZ6#naky0Sl^&HviU8s-6N|Un)D;~7MIgMa6|5KtTr;#Px)|^m>STMO zSK9|RFD+AQkI6+uL~_s?piPCDnY{)F)J{4s>O0 zZJhwX*Dnq?CA{yNgXky)iIa}(!XufuUsVeD8$f{1dqX;o-jS!0#Mm`vx&Rz z7~Q^iGKMgoy$`rqBsAy!p!aXRLm@Qv5QXLnXbeC->_QF}BegF%u9hm0J29+pBj500000NkvXX Hu0mjf;c@o| literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt b/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt new file mode 100755 index 00000000000..5017e506c6a --- /dev/null +++ b/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 26 +Active frames: 23 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 9 11 12 13 12 14 15 16 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 37 38 37 38 37 39 40 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 2 +Y: 36 +Text: I'll just get\nback to work +AlignH: Right +AlignV: Center +StartFrame: 42 +EndFrame: 47 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 5ab996f5918..94183bd9ae4 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -188,7 +188,7 @@ Min butthurt: 0 Max butthurt: 12 Min level: 3 Max level: 3 -Weight: 4 +Weight: 3 Name: L1_Akira_128x64 Min butthurt: 0 @@ -202,4 +202,11 @@ Min butthurt: 0 Max butthurt: 12 Min level: 3 Max level: 3 +Weight: 4 + +Name: L1_Procrastinating_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 Weight: 6 From 5272eb75500bca6927947f15f6d2aa828a6ab3b2 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Mon, 2 Sep 2024 05:03:56 -0400 Subject: [PATCH 04/29] Publishing T5577 page 1 block count macro (#3864) * publishing t5577 page 1 block count macro Co-authored-by: Aleksandr Kutuzov --- lib/lfrfid/tools/t5577.c | 3 --- lib/lfrfid/tools/t5577.h | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c index 59ec7d27226..ce97cb32261 100644 --- a/lib/lfrfid/tools/t5577.c +++ b/lib/lfrfid/tools/t5577.c @@ -13,9 +13,6 @@ #define T5577_OPCODE_PAGE_1 0b11 #define T5577_OPCODE_RESET 0b00 -#define T5577_BLOCKS_IN_PAGE_0 8 -#define T5577_BLOCKS_IN_PAGE_1 4 - static void t5577_start(void) { furi_hal_rfid_tim_read_start(125000, 0.5); diff --git a/lib/lfrfid/tools/t5577.h b/lib/lfrfid/tools/t5577.h index 026e7290bb0..49c0b571447 100644 --- a/lib/lfrfid/tools/t5577.h +++ b/lib/lfrfid/tools/t5577.h @@ -7,6 +7,8 @@ extern "C" { #endif #define LFRFID_T5577_BLOCK_COUNT 8 +#define T5577_BLOCKS_IN_PAGE_0 8 +#define T5577_BLOCKS_IN_PAGE_1 4 // T5577 block 0 definitions, thanks proxmark3! #define LFRFID_T5577_POR_DELAY 0x00000001 From f353e5708de6d509e1a9245d4c3e23fc9f5ffafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 5 Sep 2024 22:32:48 +0900 Subject: [PATCH 05/29] Gui: change dialog_ex text ownership model (#3831) * Gui: change dialog_ex text ownership model * Gui: change text ownership model part 2 * Examples: fix DialogEx usage in number input * Gui: fix nullptr dereference in DialogEx, proper reset procedure Co-authored-by: hedger --- .../example_number_input_scene_show_number.c | 3 +- applications/services/gui/modules/dialog_ex.c | 119 ++++++++++-------- 2 files changed, 69 insertions(+), 53 deletions(-) diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c index 2afdaf5c10c..d10d97c2404 100644 --- a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c +++ b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c @@ -13,8 +13,7 @@ static void example_number_input_scene_update_view(void* context) { dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop); - static char buffer[12]; //needs static for extended lifetime - + char buffer[12] = {}; snprintf(buffer, sizeof(buffer), "%ld", app->current_number); dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter); diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index 7171f6892b8..75209a40804 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -10,7 +10,7 @@ struct DialogEx { }; typedef struct { - const char* text; + FuriString* text; uint8_t x; uint8_t y; Align horizontal; @@ -28,16 +28,15 @@ typedef struct { TextElement text; IconElement icon; - const char* left_text; - const char* center_text; - const char* right_text; + FuriString* left_text; + FuriString* center_text; + FuriString* right_text; } DialogExModel; static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { DialogExModel* model = _model; // Prepare canvas - canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); if(model->icon.icon != NULL) { @@ -46,94 +45,94 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { // Draw header canvas_set_font(canvas, FontPrimary); - if(model->header.text != NULL) { + if(furi_string_size(model->header.text)) { elements_multiline_text_aligned( canvas, model->header.x, model->header.y, model->header.horizontal, model->header.vertical, - model->header.text); + furi_string_get_cstr(model->header.text)); } // Draw text canvas_set_font(canvas, FontSecondary); - if(model->text.text != NULL) { + if(furi_string_size(model->text.text)) { elements_multiline_text_aligned( canvas, model->text.x, model->text.y, model->text.horizontal, model->text.vertical, - model->text.text); + furi_string_get_cstr(model->text.text)); } // Draw buttons - if(model->left_text != NULL) { - elements_button_left(canvas, model->left_text); + if(furi_string_size(model->left_text)) { + elements_button_left(canvas, furi_string_get_cstr(model->left_text)); } - if(model->center_text != NULL) { - elements_button_center(canvas, model->center_text); + if(furi_string_size(model->center_text)) { + elements_button_center(canvas, furi_string_get_cstr(model->center_text)); } - if(model->right_text != NULL) { - elements_button_right(canvas, model->right_text); + if(furi_string_size(model->right_text)) { + elements_button_right(canvas, furi_string_get_cstr(model->right_text)); } } static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { DialogEx* dialog_ex = context; bool consumed = false; - const char* left_text = NULL; - const char* center_text = NULL; - const char* right_text = NULL; + bool left_text_present = false; + bool center_text_present = false; + bool right_text_present = false; with_view_model( dialog_ex->view, DialogExModel * model, { - left_text = model->left_text; - center_text = model->center_text; - right_text = model->right_text; + left_text_present = furi_string_size(model->left_text); + center_text_present = furi_string_size(model->center_text); + right_text_present = furi_string_size(model->right_text); }, - true); + false); if(dialog_ex->callback) { if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExResultLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExResultCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExResultRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypePress && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExPressLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExPressCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExPressRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypeRelease && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExReleaseLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExReleaseCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExReleaseRight, dialog_ex->context); consumed = true; } @@ -154,13 +153,13 @@ DialogEx* dialog_ex_alloc(void) { dialog_ex->view, DialogExModel * model, { - model->header.text = NULL; + model->header.text = furi_string_alloc(); model->header.x = 0; model->header.y = 0; model->header.horizontal = AlignLeft; model->header.vertical = AlignBottom; - model->text.text = NULL; + model->text.text = furi_string_alloc(); model->text.x = 0; model->text.y = 0; model->text.horizontal = AlignLeft; @@ -170,17 +169,28 @@ DialogEx* dialog_ex_alloc(void) { model->icon.y = 0; model->icon.icon = NULL; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->left_text = furi_string_alloc(); + model->center_text = furi_string_alloc(); + model->right_text = furi_string_alloc(); }, - true); + false); dialog_ex->enable_extended_events = false; return dialog_ex; } void dialog_ex_free(DialogEx* dialog_ex) { furi_check(dialog_ex); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { + furi_string_free(model->header.text); + furi_string_free(model->text.text); + furi_string_free(model->left_text); + furi_string_free(model->center_text); + furi_string_free(model->right_text); + }, + false); view_free(dialog_ex->view); free(dialog_ex); } @@ -212,7 +222,7 @@ void dialog_ex_set_header( dialog_ex->view, DialogExModel * model, { - model->header.text = text; + furi_string_set(model->header.text, text); model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; @@ -233,7 +243,7 @@ void dialog_ex_set_text( dialog_ex->view, DialogExModel * model, { - model->text.text = text; + furi_string_set(model->text.text, text); model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; @@ -257,34 +267,41 @@ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* i void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->left_text = text; }, true); + with_view_model( + dialog_ex->view, DialogExModel * model, { furi_string_set(model->left_text, text); }, true); } void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->center_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->center_text, text); }, + true); } void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->right_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->right_text, text); }, + true); } void dialog_ex_reset(DialogEx* dialog_ex) { furi_check(dialog_ex); - TextElement clean_text_el = { - .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; - IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0}; with_view_model( dialog_ex->view, DialogExModel * model, { - model->header = clean_text_el; - model->text = clean_text_el; - model->icon = clean_icon_el; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->icon.icon = NULL; + furi_string_reset(model->header.text); + furi_string_reset(model->text.text); + + furi_string_reset(model->left_text); + furi_string_reset(model->center_text); + furi_string_reset(model->right_text); }, true); dialog_ex->context = NULL; From b040db07f46ad148eac8f9a5eaf30215b16c6e12 Mon Sep 17 00:00:00 2001 From: DerSkythe <31771569+derskythe@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:50:33 +0400 Subject: [PATCH 06/29] Gui: Add up and down button drawing functions to GUI elements (#3804) * feat: Add up and down button drawing functions to GUI elements Two button drawing functions, elements_button_up and elements_button_down, have been added to the GUI elements. These functions allow a button to be drawn at the top left and top right corner of the canvas respectively, with an accompanying string and icon. The underlying layout and design of these buttons is defined within these functions. * feat: Add null checks for Canvas parameter in button functions Added furi_check to ensure the Canvas parameter is not null in elements_button_up and elements_button_down functions. This prevents potential crashes due to dereferencing a null pointer. Co-authored-by: hedger Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/elements.c | 64 ++++++++++++++++++++++++++++ applications/services/gui/elements.h | 22 ++++++++++ targets/f18/api_symbols.csv | 16 ++++--- targets/f7/api_symbols.csv | 4 +- 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 5b38c5c7916..3e12c09cca0 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -185,6 +185,70 @@ void elements_button_right(Canvas* canvas, const char* str) { canvas_invert_color(canvas); } +void elements_button_up(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonUp_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + (int32_t)vertical_offset; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = 0; + const int32_t y = 0 + button_height; + + int32_t line_x = x + button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1); + canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2); + canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3); + + canvas_invert_color(canvas); + canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, icon); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_down(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonDown_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = canvas_width(canvas); + const int32_t y = button_height; + + int32_t line_x = x - button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, line_x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1); + canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2); + canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon( + canvas, x - horizontal_offset - icon_get_width(icon), y - icon_v_offset, icon); + canvas_invert_color(canvas); +} + void elements_button_center(Canvas* canvas, const char* str) { furi_check(canvas); diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 88a00481514..0ec0f86cb7b 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -96,6 +96,28 @@ void elements_button_left(Canvas* canvas, const char* str); */ void elements_button_right(Canvas* canvas, const char* str); +/** + * @brief This function draws a button in the top left corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_up(Canvas* canvas, const char* str); + +/** + * @brief This function draws a button in the top right corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_down(Canvas* canvas, const char* str); + /** Draw button in center * * @param canvas Canvas instance diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62e8..2c1a565651b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -13,13 +13,13 @@ Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, -Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/dialog_ex.h,, Header,+,applications/services/gui/modules/empty_screen.h,, Header,+,applications/services/gui/modules/file_browser.h,, Header,+,applications/services/gui/modules/file_browser_worker.h,, Header,+,applications/services/gui/modules/loading.h,, Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/popup.h,, Header,+,applications/services/gui/modules/submenu.h,, Header,+,applications/services/gui/modules/text_box.h,, @@ -723,11 +723,6 @@ Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" -Function,+,number_input_alloc,NumberInput*, -Function,+,number_input_free,void,NumberInput* -Function,+,number_input_get_view,View*,NumberInput* -Function,+,number_input_set_header_text,void,"NumberInput*, const char*" -Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,bzero,void,"void*, size_t" Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* @@ -883,8 +878,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" @@ -2197,6 +2194,11 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,number_input_alloc,NumberInput*, +Function,+,number_input_free,void,NumberInput* +Function,+,number_input_get_view,View*,NumberInput* +Function,+,number_input_set_header_text,void,"NumberInput*, const char*" +Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,on_exit,int,"void (*)(int, void*), void*" Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e44b3356c67..e5292d93657 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.2,, 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,, @@ -965,8 +965,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" From fa2d611652c2658c3f5499493e9020f07b549062 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:40:14 +0100 Subject: [PATCH 07/29] [FL-3889] 5V on GPIO control for ext. modules (#3830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make file extensions case-insensitive * Bump protobuf version * Add support for 5V control via RPC * Add support for 5V control via Expansion protocol * Update running instructions * Update expansion module documentation * Prettify condition * Test RPC OTG control as well * Assets: bump protobuf version * Disable PVS license expiration check, fix PVS warnings Co-authored-by: あく --- .../debug/expansion_test/expansion_test.c | 59 +++++++++++++++++-- .../services/expansion/expansion_protocol.h | 24 +++++++- .../services/expansion/expansion_worker.c | 26 ++++++-- .../services/gui/modules/byte_input.c | 6 +- applications/services/rpc/rpc_gpio.c | 45 ++++++++++++++ assets/protobuf | 2 +- documentation/ExpansionModules.md | 18 ++++-- scripts/fbt_tools/pvsstudio.py | 1 + 8 files changed, 160 insertions(+), 21 deletions(-) diff --git a/applications/debug/expansion_test/expansion_test.c b/applications/debug/expansion_test/expansion_test.c index e3bcf4e9c92..665874f4766 100644 --- a/applications/debug/expansion_test/expansion_test.c +++ b/applications/debug/expansion_test/expansion_test.c @@ -6,18 +6,27 @@ * 13 -> 16 (USART TX to LPUART RX) * 14 -> 15 (USART RX to LPUART TX) * + * Optional: Connect an LED with an appropriate series resistor + * between pins 1 and 8. It will always be on if the device is + * connected to USB power, so unplug it before running the app. + * * What this application does: * * - Enables module support and emulates the module on a single device * (hence the above connection), * - Connects to the expansion module service, sets baud rate, + * - Enables OTG (5V) on GPIO via plain expansion protocol, + * - Waits 5 cycles of idle loop (1 second), * - Starts the RPC session, + * - Disables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Creates a directory at `/ext/ExpansionTest` and writes a file * named `test.txt` under it, * - Plays an audiovisual alert (sound and blinking display), - * - Waits 10 cycles of idle loop, + * - Enables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Stops the RPC session, - * - Waits another 10 cycles of idle loop, + * - Disables OTG (5V) on GPIO via plain expansion protocol, * - Exits (plays a sound if any of the above steps failed). */ #include @@ -302,6 +311,22 @@ static bool expansion_test_app_handshake(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + do { + const ExpansionFrameControlCommand command = enable ? + ExpansionFrameControlCommandEnableOtg : + ExpansionFrameControlCommandDisableOtg; + if(!expansion_test_app_send_control_request(instance, command)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) { bool success = false; @@ -396,6 +421,27 @@ static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_rpc_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_gpio_set_otg_mode_tag; + instance->msg.content.gpio_set_otg_mode.mode = enable ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + instance->msg.has_next = false; + + do { + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(instance->msg.which_content != PB_Main_empty_tag) break; + if(instance->msg.command_status != PB_CommandStatus_OK) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) { uint32_t num_cycles_done; for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) { @@ -434,13 +480,18 @@ int32_t expansion_test_app(void* p) { if(!expansion_test_app_send_presence(instance)) break; if(!expansion_test_app_wait_ready(instance)) break; if(!expansion_test_app_handshake(instance)) break; + if(!expansion_test_app_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_start_rpc(instance)) break; + if(!expansion_test_app_rpc_enable_otg(instance, false)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_rpc_mkdir(instance)) break; if(!expansion_test_app_rpc_write(instance)) break; if(!expansion_test_app_rpc_alert(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_rpc_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_stop_rpc(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_enable_otg(instance, false)) break; success = true; } while(false); diff --git a/applications/services/expansion/expansion_protocol.h b/applications/services/expansion/expansion_protocol.h index 6ed818f82dd..a8d682330e9 100644 --- a/applications/services/expansion/expansion_protocol.h +++ b/applications/services/expansion/expansion_protocol.h @@ -64,8 +64,28 @@ typedef enum { * @brief Enumeration of suported control commands. */ typedef enum { - ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */ - ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */ + /** @brief Start an RPC session. + * + * Must only be used while the RPC session is NOT active. + */ + ExpansionFrameControlCommandStartRpc = 0x00, + /** @brief Stop an open RPC session. + * + * Must only be used while the RPC session IS active. + */ + ExpansionFrameControlCommandStopRpc = 0x01, + /** @brief Enable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandEnableOtg = 0x02, + /** @brief Disable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandDisableOtg = 0x03, } ExpansionFrameControlCommand; #pragma pack(push, 1) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index 449d02cffc9..c05b9cc8547 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -245,9 +245,18 @@ static bool expansion_worker_handle_state_connected( do { if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; - instance->state = ExpansionWorkerStateRpcActive; - if(!expansion_worker_rpc_session_open(instance)) break; + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStartRpc) { + if(!expansion_worker_rpc_session_open(instance)) break; + instance->state = ExpansionWorkerStateRpcActive; + } else if(command == ExpansionFrameControlCommandEnableOtg) { + furi_hal_power_enable_otg(); + } else if(command == ExpansionFrameControlCommandDisableOtg) { + furi_hal_power_disable_otg(); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { @@ -279,9 +288,14 @@ static bool expansion_worker_handle_state_rpc_active( if(size_consumed != rx_frame->content.data.size) break; } else if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; - instance->state = ExpansionWorkerStateConnected; - expansion_worker_rpc_session_close(instance); + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStopRpc) { + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 6e85401dabe..be94ed9ab9b 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -33,7 +33,7 @@ typedef struct { static const uint8_t keyboard_origin_x = 7; static const uint8_t keyboard_origin_y = 31; -static const uint8_t keyboard_row_count = 2; +static const int8_t keyboard_row_count = 2; static const uint8_t enter_symbol = '\r'; static const uint8_t backspace_symbol = '\b'; static const uint8_t max_drawable_bytes = 8; @@ -649,11 +649,11 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontKeyboard); // Draw keyboard - for(uint8_t row = 0; row < keyboard_row_count; row++) { + for(int8_t row = 0; row < keyboard_row_count; row++) { const uint8_t column_count = byte_input_get_row_size(row); const ByteInputKey* keys = byte_input_get_row(row); - for(size_t column = 0; column < column_count; column++) { + for(uint8_t column = 0; column < column_count; column++) { if(keys[column].value == enter_symbol) { canvas_set_color(canvas, ColorBlack); if(model->selected_row == row && model->selected_column == column) { diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 09e7385052e..40fc898a09d 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -2,6 +2,7 @@ #include "rpc_i.h" #include "gpio.pb.h" #include +#include #include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { @@ -188,6 +189,44 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) { free(response); } +void rpc_system_gpio_get_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_get_otg_mode_tag); + + RpcSession* session = context; + + const bool otg_enabled = furi_hal_power_is_otg_enabled(); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->which_content = PB_Main_gpio_get_otg_mode_response_tag; + response->content.gpio_get_otg_mode_response.mode = otg_enabled ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + + rpc_send_and_release(session, response); + + free(response); +} + +void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_set_otg_mode_tag); + + RpcSession* session = context; + + const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + + if(mode == PB_Gpio_GpioOtgMode_OFF) { + furi_hal_power_disable_otg(); + } else { + furi_hal_power_enable_otg(); + } + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + void* rpc_system_gpio_alloc(RpcSession* session) { furi_assert(session); @@ -212,5 +251,11 @@ void* rpc_system_gpio_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_gpio_set_input_pull; rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_gpio_get_otg_mode; + rpc_add_handler(session, PB_Main_gpio_get_otg_mode_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_set_otg_mode; + rpc_add_handler(session, PB_Main_gpio_set_otg_mode_tag, &rpc_handler); + return NULL; } diff --git a/assets/protobuf b/assets/protobuf index 816de200a4a..6c7c0d55e82 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a +Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7 diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index 470564e5743..fd9703adcc4 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -73,7 +73,7 @@ If the requested baud rate is supported by the host, it SHALL respond with a STA ### Control frame -CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session. +CONTROL frames are used to control various aspects of the communication and enable/disable various device features. | Header (1 byte) | Contents (1 byte) | Checksum (1 byte) | |-----------------|-------------------|-------------------| @@ -81,10 +81,18 @@ CONTROL frames are used to control various aspects of the communication. As of n The `Command` field SHALL have one of the followind values: -| Command | Meaning | -|---------|-------------------| -| 0x00 | Start RPC session | -| 0x01 | Stop RPC session | +| Command | Meaning | Note | +|---------|--------------------------|:----:| +| 0x00 | Start RPC session | 1 | +| 0x01 | Stop RPC session | 2 | +| 0x02 | Enable OTG (5V) on GPIO | 3 | +| 0x03 | Disable OTG (5V) on GPIO | 3 | + +Notes: + +1. Must only be used while the RPC session NOT active. +2. Must only be used while the RPC session IS active. +3. See 1, otherwise OTG is to be controlled via RPC messages. ### Data frame diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 290531321d6..1a55278dcc2 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,6 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", + "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ From 4a58930247ae1eb27e41b8016f1730eed4d56ca9 Mon Sep 17 00:00:00 2001 From: Filipe Paz Rodrigues Date: Thu, 5 Sep 2024 08:04:32 -0700 Subject: [PATCH 08/29] CCID: App changes (#3837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Keep ccid_callback and buffer as private to the iso7816_handler - set usb ccid callback from iso7816_handler (to ensure the right structure is being passed) - make iso7816 related code independent from furi related code (goal is to make it independently testable) - rename vars Co-authored-by: あく --- applications/debug/ccid_test/ccid_test_app.c | 7 ++- .../debug/ccid_test/iso7816/iso7816_handler.c | 46 +++++++++++++++---- .../debug/ccid_test/iso7816/iso7816_handler.h | 8 ++-- .../debug/ccid_test/iso7816/iso7816_t0_apdu.c | 37 ++++++++------- .../debug/ccid_test/iso7816/iso7816_t0_apdu.h | 3 +- 5 files changed, 64 insertions(+), 37 deletions(-) diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c index abb8ad3dd30..4158c1a6036 100644 --- a/applications/debug/ccid_test/ccid_test_app.c +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -105,7 +105,7 @@ void ccid_test_app_free(CcidTestApp* app) { furi_record_close(RECORD_GUI); app->gui = NULL; - free(app->iso7816_handler); + iso7816_handler_free(app->iso7816_handler); // Free rest free(app); @@ -121,8 +121,7 @@ int32_t ccid_test_app(void* p) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true); - furi_hal_usb_ccid_set_callbacks( - (CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler); + iso7816_handler_set_usb_ccid_callbacks(); furi_hal_usb_ccid_insert_smartcard(); //handle button events @@ -142,7 +141,7 @@ int32_t ccid_test_app(void* p) { } //tear down USB - furi_hal_usb_ccid_set_callbacks(NULL, NULL); + iso7816_handler_reset_usb_ccid_callbacks(); furi_hal_usb_set_config(usb_mode_prev, NULL); //teardown view diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.c b/applications/debug/ccid_test/iso7816/iso7816_handler.c index 97214d1b226..8f0f758b2cd 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.c +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.c @@ -6,11 +6,17 @@ #include #include +#include "iso7816_handler.h" + #include "iso7816_t0_apdu.h" #include "iso7816_atr.h" -#include "iso7816_handler.h" #include "iso7816_response.h" +static Iso7816Handler* iso7816_handler; +static CcidCallbacks* ccid_callbacks; +static uint8_t* command_apdu_buffer; +static uint8_t* response_apdu_buffer; + void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) { furi_check(context); @@ -40,12 +46,11 @@ void iso7816_xfr_datablock_callback( Iso7816Handler* handler = (Iso7816Handler*)context; - ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer; - - ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer; + ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)response_apdu_buffer; + ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)command_apdu_buffer; uint8_t result = iso7816_read_command_apdu( - command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len); + command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len, CCID_SHORT_APDU_SIZE); if(result == ISO7816_READ_COMMAND_APDU_OK) { handler->iso7816_process_command(command_apdu, response_apdu); @@ -61,8 +66,31 @@ void iso7816_xfr_datablock_callback( } Iso7816Handler* iso7816_handler_alloc() { - Iso7816Handler* handler = malloc(sizeof(Iso7816Handler)); - handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback; - handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback; - return handler; + iso7816_handler = malloc(sizeof(Iso7816Handler)); + + command_apdu_buffer = malloc(sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE); + response_apdu_buffer = malloc(sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE); + + ccid_callbacks = malloc(sizeof(CcidCallbacks)); + ccid_callbacks->icc_power_on_callback = iso7816_icc_power_on_callback; + ccid_callbacks->xfr_datablock_callback = iso7816_xfr_datablock_callback; + + return iso7816_handler; +} + +void iso7816_handler_set_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(ccid_callbacks, iso7816_handler); +} + +void iso7816_handler_reset_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(NULL, NULL); +} + +void iso7816_handler_free(Iso7816Handler* handler) { + free(ccid_callbacks); + + free(command_apdu_buffer); + free(response_apdu_buffer); + + free(handler); } diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.h b/applications/debug/ccid_test/iso7816/iso7816_handler.h index d67118ce6e7..4f9703e46b5 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.h +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.h @@ -5,14 +5,14 @@ #include "iso7816_t0_apdu.h" typedef struct { - CcidCallbacks ccid_callbacks; void (*iso7816_answer_to_reset)(Iso7816Atr* atr); void (*iso7816_process_command)( const ISO7816_Command_APDU* command, ISO7816_Response_APDU* response); - - uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE]; - uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE]; } Iso7816Handler; Iso7816Handler* iso7816_handler_alloc(); + +void iso7816_handler_free(Iso7816Handler* handler); +void iso7816_handler_set_usb_ccid_callbacks(); +void iso7816_handler_reset_usb_ccid_callbacks(); diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c index 216f2582f1a..023daebe22c 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c @@ -1,49 +1,48 @@ /* Implements rudimentary iso7816-3 support for APDU (T=0) */ #include #include -#include -#include #include "iso7816_t0_apdu.h" -//reads dataBuffer with dataLen size, translate it into a ISO7816_Command_APDU type +//reads pc_to_reader_datablock_len with pc_to_reader_datablock_len size, translate it into a ISO7816_Command_APDU type //extra data will be pointed to commandDataBuffer uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, - const uint8_t* dataBuffer, - uint32_t dataLen) { - command->CLA = dataBuffer[0]; - command->INS = dataBuffer[1]; - command->P1 = dataBuffer[2]; - command->P2 = dataBuffer[3]; + const uint8_t* pc_to_reader_datablock, + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size) { + command->CLA = pc_to_reader_datablock[0]; + command->INS = pc_to_reader_datablock[1]; + command->P1 = pc_to_reader_datablock[2]; + command->P2 = pc_to_reader_datablock[3]; - if(dataLen == 4) { + if(pc_to_reader_datablock_len == 4) { command->Lc = 0; command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == 5) { + } else if(pc_to_reader_datablock_len == 5) { //short le command->Lc = 0; - command->Le = dataBuffer[4]; + command->Le = pc_to_reader_datablock[4]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen > 5 && dataBuffer[4] != 0x00) { + } else if(pc_to_reader_datablock_len > 5 && pc_to_reader_datablock[4] != 0x00) { //short lc - command->Lc = dataBuffer[4]; - if(command->Lc > 0 && command->Lc < CCID_SHORT_APDU_SIZE) { //-V560 - memcpy(command->Data, &dataBuffer[5], command->Lc); + command->Lc = pc_to_reader_datablock[4]; + if(command->Lc > 0 && command->Lc < max_apdu_size) { //-V560 + memcpy(command->Data, &pc_to_reader_datablock[5], command->Lc); //does it have a short le too? - if(dataLen == (uint32_t)(command->Lc + 5)) { + if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 5)) { command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == (uint32_t)(command->Lc + 6)) { - command->Le = dataBuffer[dataLen - 1]; + } else if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 6)) { + command->Le = pc_to_reader_datablock[pc_to_reader_datablock_len - 1]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h index a21dfbafc33..bbd2a1b235e 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h @@ -34,7 +34,8 @@ typedef struct { uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, const uint8_t* pc_to_reader_datablock, - uint32_t pc_to_reader_datablock_len); + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size); void iso7816_write_response_apdu( const ISO7816_Response_APDU* response, uint8_t* reader_to_pc_datablock, From 6a48dd28f552a2767bb99c3725415e922edf9976 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:10:12 +0400 Subject: [PATCH 09/29] SubGhz: Fix RPC status for ButtonRelease event (#3838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/subghz/scenes/subghz_scene_rpc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index f8bf066d549..42ec98a3963 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -57,7 +57,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { default: //if(SubGhzTxRxStartTxStateOk) result = true; subghz_blink_start(subghz); - state = SubGhzRpcStateTx; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx); break; } } @@ -69,7 +70,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { subghz_blink_stop(subghz); result = true; } - state = SubGhzRpcStateIdle; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; From c9791a280aef959c69f23036676a7699f70d4bed Mon Sep 17 00:00:00 2001 From: porta Date: Thu, 5 Sep 2024 20:02:42 +0300 Subject: [PATCH 10/29] [FL-3884] Proper integer parsing (#3839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: strint_to_uint32 and tests * fix: permit explicit bases and prefixes * feat: strint_to_{int32,uint16,int16} * feat: strint_to_u?int64 * refactor: replace strtol, strtoul, sscanf with strint_to_* * fix: api symbols * docs: document parameter `end` of strint_to_uint_32 * style: apply changes requested by hedger * refactor: fix pvs-studio diagnostic * style: apply changes requested by CookiePLMonster * fix: unused var * fix: pointer type * refactor: convert atoi to strint_to_* * fix: strint_to_uint8 doesn't actually exist ._ . * fix: memory leak * style: address review comments * Toolbox: couple small comments in the code and doxygen comment update. SubGhz, Loader: fix strint usage. * Loader: fix incorrect cast Co-authored-by: あく --- .../rpc_debug_app_scene_input_error_code.c | 7 +- applications/debug/uart_echo/uart_echo.c | 4 +- applications/debug/unit_tests/application.fam | 8 + .../unit_tests/tests/strint/strint_test.c | 142 ++++++++++++++++++ .../main/bad_usb/helpers/ducky_script.c | 3 +- applications/main/infrared/infrared_cli.c | 20 ++- .../main/nfc/plugins/supported_cards/itso.c | 6 +- applications/main/subghz/subghz_cli.c | 74 +++++---- applications/services/cli/cli_commands.c | 7 +- .../services/gui/modules/number_input.c | 13 +- applications/services/loader/loader_cli.c | 13 +- applications/services/storage/storage_cli.c | 13 +- lib/flipper_format/flipper_format_stream.c | 13 +- lib/subghz/subghz_file_encoder_worker.c | 26 ++-- lib/toolbox/SConscript | 1 + lib/toolbox/args.c | 6 +- lib/toolbox/strint.c | 121 +++++++++++++++ lib/toolbox/strint.h | 70 +++++++++ lib/update_util/resources/manifest.c | 8 +- targets/f18/api_symbols.csv | 7 + targets/f7/api_symbols.csv | 7 + 21 files changed, 479 insertions(+), 90 deletions(-) create mode 100644 applications/debug/unit_tests/tests/strint/strint_test.c create mode 100644 lib/toolbox/strint.c create mode 100644 lib/toolbox/strint.h diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c index 367ca7a4ff1..be77748815c 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -1,5 +1,7 @@ #include "../rpc_debug_app.h" +#include + static bool rpc_debug_app_scene_input_error_code_validator_callback( const char* text, FuriString* error, @@ -44,9 +46,8 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventInputErrorCode) { - char* end; - int error_code = strtol(app->text_store, &end, 10); - if(!*end) { + uint32_t error_code; + if(strint_to_uint32(app->text_store, NULL, &error_code, 10) == StrintParseNoError) { rpc_system_app_set_error_code(app->rpc, error_code); } scene_manager_previous_scene(app->scene_manager); diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index be807168aef..4298dc33dbe 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -320,7 +322,7 @@ int32_t uart_echo_app(void* p) { uint32_t baudrate = DEFAULT_BAUD_RATE; if(p) { const char* baudrate_str = p; - if(sscanf(baudrate_str, "%lu", &baudrate) != 1) { + if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) { FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); baudrate = DEFAULT_BAUD_RATE; } diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index f5f84ead797..c87305847af 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -220,3 +220,11 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_strint", + sources=["tests/common/*.c", "tests/strint/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/tests/strint/strint_test.c b/applications/debug/unit_tests/tests/strint/strint_test.c new file mode 100644 index 00000000000..d8fd9113d37 --- /dev/null +++ b/applications/debug/unit_tests/tests/strint/strint_test.c @@ -0,0 +1,142 @@ +#include +#include + +#include "../test.h" // IWYU pragma: keep + +#include + +MU_TEST(strint_test_basic) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_junk) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32(" 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n\r\n 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_tail) { + uint32_t result = 0; + char* tail; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n 123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); +} + +MU_TEST(strint_test_errors) { + uint32_t result = 123; + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32("", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32(" asd\r\n", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("+++123456", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("-1", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq( + StrintParseOverflowError, + strint_to_uint32("0xAAAAAAAAAAAAAAAADEADBEEF!!!!!!", NULL, &result, 0)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseOverflowError, strint_to_uint32("4294967296", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + int32_t result_i32 = 123; + mu_assert_int_eq( + StrintParseOverflowError, strint_to_int32("-2147483649", NULL, &result_i32, 0)); + mu_assert_int_eq(123, result_i32); +} + +MU_TEST(strint_test_bases) { + uint32_t result = 0; + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0x123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0X123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 0)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 16)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 16)); + mu_assert_int_eq(0x123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 0)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0B101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); +} + +MU_TEST_SUITE(strint_test_limits) { + uint64_t result_u64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_uint64("18446744073709551615", NULL, &result_u64, 0)); + // `mu_assert_int_eq' does not support longs :( + mu_assert(UINT64_MAX == result_u64, "result does not equal UINT64_MAX"); + + int64_t result_i64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("9223372036854775807", NULL, &result_i64, 0)); + mu_assert(INT64_MAX == result_i64, "result does not equal INT64_MAX"); + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("-9223372036854775808", NULL, &result_i64, 0)); + mu_assert(INT64_MIN == result_i64, "result does not equal INT64_MIN"); + + uint32_t result_u32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("4294967295", NULL, &result_u32, 0)); + mu_assert_int_eq(UINT32_MAX, result_u32); + + int32_t result_i32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int32("2147483647", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MAX, result_i32); + mu_assert_int_eq(StrintParseNoError, strint_to_int32("-2147483648", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MIN, result_i32); + + uint16_t result_u16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint16("65535", NULL, &result_u16, 0)); + mu_assert_int_eq(UINT16_MAX, result_u16); + + int16_t result_i16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int16("32767", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MAX, result_i16); + mu_assert_int_eq(StrintParseNoError, strint_to_int16("-32768", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MIN, result_i16); +} + +MU_TEST_SUITE(test_strint_suite) { + MU_RUN_TEST(strint_test_basic); + MU_RUN_TEST(strint_test_junk); + MU_RUN_TEST(strint_test_tail); + MU_RUN_TEST(strint_test_errors); + MU_RUN_TEST(strint_test_bases); + MU_RUN_TEST(strint_test_limits); +} + +int run_minunit_test_strint(void) { + MU_RUN_SUITE(test_strint_suite); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_strint) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 2ee66955c58..ccc3caa811b 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ducky_script.h" #include "ducky_script_i.h" @@ -64,7 +65,7 @@ uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept bool ducky_get_number(const char* param, uint32_t* val) { uint32_t value = 0; - if(sscanf(param, "%lu", &value) == 1) { + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { *val = value; return true; } diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 169f0c63d94..00246a90f11 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "infrared_signal.h" @@ -176,25 +177,28 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return false; } - uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); - uint32_t frequency = atoi(frequency_str); - float duty_cycle = (float)atoi(duty_cycle_str) / 100; + uint32_t frequency; + uint32_t duty_cycle_u32; + if(strint_to_uint32(frequency_str, NULL, &frequency, 10) != StrintParseNoError || + strint_to_uint32(duty_cycle_str, NULL, &duty_cycle_u32, 10) != StrintParseNoError) + return false; + float duty_cycle = duty_cycle_u32 / 100.0f; str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE; + uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); size_t timings_size = 0; while(1) { while(*str == ' ') { ++str; } - char timing_str[INFRARED_CLI_BUF_SIZE]; - if(sscanf(str, "%9s", timing_str) != 1) { + uint32_t timing; + char* next_token; + if(strint_to_uint32(str, &next_token, &timing, 10) != StrintParseNoError) { break; } - - str += strlen(timing_str); - uint32_t timing = atoi(timing_str); + str = next_token; if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) { break; diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index 1b61c26e7c7..a9be0a1f931 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -72,7 +73,10 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { dateBuff[17] = '\0'; // DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997 - uint32_t dateStamp = (int)strtol(datep, NULL, 16); + uint32_t dateStamp; + if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) { + return false; + } uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U; furi_string_set(parsed_data, "\e#ITSO Card\n"); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 4f5c4cb623b..6375f2eee4d 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -3,18 +3,20 @@ #include #include -#include -#include +#include +#include #include #include #include #include -#include #include #include #include +#include +#include + #include "helpers/subghz_chat.h" #include @@ -71,9 +73,8 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz tx_carrier", "", furi_string_get_cstr(args)); return; } @@ -115,9 +116,8 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx_carrier", "", furi_string_get_cstr(args)); return; } @@ -181,23 +181,14 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf( - furi_string_get_cstr(args), - "%lx %lu %lu %lu %lu", - &key, - &frequency, - &te, - &repeat, - &device_ind); - if(ret != 5) { - printf( - "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", - ret, - key, - frequency, - te, - repeat, - device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &key, 16); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &te, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx", "<3 Byte Key: in hex> ", @@ -314,10 +305,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf( - "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz rx", " ", @@ -401,9 +393,8 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); return; } @@ -622,9 +613,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) } if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &repeat, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, repeat: %lu device: %lu\r\n", ret, repeat, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx_from_file:", " ", @@ -936,10 +929,11 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); - printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz chat", " ", diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index b4eeebbe638..c3539813b1c 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -9,6 +9,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -361,9 +362,9 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { } furi_string_free(light_name); // Read light value from the rest of the string - char* end_ptr; - uint32_t value = strtoul(furi_string_get_cstr(args), &end_ptr, 0); - if(!(value < 256 && *end_ptr == '\0')) { + uint32_t value; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &value, 0) != StrintParseNoError || + value >= 256) { cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); return; } diff --git a/applications/services/gui/modules/number_input.c b/applications/services/gui/modules/number_input.c index 777e5574729..5a5e56c1ce0 100644 --- a/applications/services/gui/modules/number_input.c +++ b/applications/services/gui/modules/number_input.c @@ -3,6 +3,7 @@ #include #include #include +#include struct NumberInput { View* view; @@ -163,7 +164,11 @@ static void number_input_handle_right(NumberInputModel* model) { } static bool is_number_too_large(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value > (int64_t)model->max_value) { return true; } @@ -171,7 +176,11 @@ static bool is_number_too_large(NumberInputModel* model) { } static bool is_number_too_small(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value < (int64_t)model->min_value) { return true; } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index a0254f0d0c5..f3ea30df2ab 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -4,6 +4,7 @@ #include #include #include +#include #include static void loader_cli_print_usage(void) { @@ -89,18 +90,22 @@ static void loader_cli_close(Loader* loader) { static void loader_cli_signal(FuriString* args, Loader* loader) { uint32_t signal; - void* arg = NULL; + uint32_t arg = 0; + StrintParseError parse_err = 0; + char* args_cstr = (char*)furi_string_get_cstr(args); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &signal, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &arg, 16); - if(!sscanf(furi_string_get_cstr(args), "%lu %p", &signal, &arg)) { + if(parse_err) { printf("Signal must be a decimal number\r\n"); } else if(!loader_is_locked(loader)) { printf("No application is running\r\n"); } else { - const bool is_handled = loader_signal(loader, signal, arg); + const bool is_handled = loader_signal(loader, signal, (void*)arg); printf( "Signal %lu with argument 0x%p was %s\r\n", signal, - arg, + (void*)arg, is_handled ? "handled" : "ignored"); } } diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index a18b289408c..441b58da66b 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -3,8 +3,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -267,9 +268,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); @@ -307,9 +307,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index b9d33169c9a..8a16dbfd958 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" @@ -396,14 +397,16 @@ bool flipper_format_stream_read_value_line( #endif case FlipperStreamValueInt32: { int32_t* data = _data; - scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]); + if(strint_to_int32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; + } }; break; case FlipperStreamValueUint32: { uint32_t* data = _data; - // Minus sign is allowed in scanf() for unsigned numbers, resulting in unintentionally huge values with no error reported - if(!furi_string_start_with(value, "-")) { - scan_values = - sscanf(furi_string_get_cstr(value), "%" PRIu32, &data[i]); + if(strint_to_uint32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; } }; break; case FlipperStreamValueHexUint64: { diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 1ccde73a4d7..60737250beb 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -4,6 +4,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -45,27 +46,26 @@ void subghz_file_encoder_worker_add_level_duration( } bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { - char* str1; - bool res = false; // Line sample: "RAW_Data: -1, 2, -2..." - // Look for a key in the line - str1 = strstr(strStart, "RAW_Data: "); + // Look for the key in the line + char* str = strstr(strStart, "RAW_Data: "); + bool res = false; - if(str1 != NULL) { + if(str) { // Skip key - str1 = strchr(str1, ' '); - - // Check that there is still an element in the line - while(strchr(str1, ' ') != NULL) { - str1 = strchr(str1, ' '); + str = strchr(str, ' '); - // Skip space - str1 += 1; - subghz_file_encoder_worker_add_level_duration(instance, atoi(str1)); + // Parse next element + int32_t duration; + while(strint_to_int32(str, &str, &duration, 10) == StrintParseNoError) { + subghz_file_encoder_worker_add_level_duration(instance, duration); + if(*str == ',') str++; // could also be `\0` } + res = true; } + return res; } diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 11e01a8c9da..03b8999c4d3 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -29,6 +29,7 @@ env.Append( File("stream/file_stream.h"), File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), + File("strint.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), diff --git a/lib/toolbox/args.c b/lib/toolbox/args.c index aa790ad68c5..914b093bac1 100644 --- a/lib/toolbox/args.c +++ b/lib/toolbox/args.c @@ -1,5 +1,7 @@ #include "args.h" #include "hex.h" +#include "strint.h" +#include "m-core.h" size_t args_get_first_word_length(FuriString* args) { size_t ws = furi_string_search_char(args, ' '); @@ -21,7 +23,9 @@ bool args_read_int_and_trim(FuriString* args, int* value) { return false; } - if(sscanf(furi_string_get_cstr(args), "%d", value) == 1) { + int32_t temp; + if(strint_to_int32(furi_string_get_cstr(args), NULL, &temp, 10) == StrintParseNoError) { + *value = temp; furi_string_right(args, cmd_length); furi_string_trim(args); return true; diff --git a/lib/toolbox/strint.c b/lib/toolbox/strint.c new file mode 100644 index 00000000000..8c7f36976a5 --- /dev/null +++ b/lib/toolbox/strint.c @@ -0,0 +1,121 @@ +#include "strint.h" + +#include + +// Splitting out the actual parser helps reduce code size. The manually +// monomorphized `strint_to_*`s are just wrappers around this generic +// implementation. +/** + * @brief Converts a string to a `uint64_t` and an auxillary sign bit, checking + * the bounds of the integer. + * @param [in] str Input string + * @param [out] end Pointer to first character after the number in input string + * @param [out] abs_out Absolute part of result + * @param [out] negative_out Sign part of result (true=negative, false=positive) + * @param [in] base Integer base + * @param [in] max_abs_negative Largest permissible absolute part of result if + * the sign is negative + * @param [in] max_positive Largest permissible absolute part of result if the + * sign is positive + */ +StrintParseError strint_to_uint64_internal( + const char* str, + char** end, + uint64_t* abs_out, + bool* negative_out, + uint8_t base, + uint64_t max_abs_negative, + uint64_t max_positive) { + // skip whitespace + while(((*str >= '\t') && (*str <= '\r')) || *str == ' ') { + str++; + } + + // read sign + bool negative = false; + if(*str == '+' || *str == '-') { + if(*str == '-') negative = true; + str++; + } + if(*str == '+' || *str == '-') return StrintParseSignError; + if(max_abs_negative == 0 && negative) return StrintParseSignError; + + // infer base + // not assigning directly to `base' to permit prefixes with explicit bases + uint8_t inferred_base = 0; + if(strncasecmp(str, "0x", 2) == 0) { + inferred_base = 16; + str += 2; + } else if(strncasecmp(str, "0b", 2) == 0) { + inferred_base = 2; + str += 2; + } else if(*str == '0') { + inferred_base = 8; + str++; + } else { + inferred_base = 10; + } + if(base == 0) base = inferred_base; + + // read digits + uint64_t limit = negative ? max_abs_negative : max_positive; + uint64_t mul_limit = limit / base; + uint64_t result = 0; + int read_total = 0; + while(*str != 0) { + int digit_value; + if(*str >= '0' && *str <= '9') { + digit_value = *str - '0'; + } else if(*str >= 'A' && *str <= 'Z') { + digit_value = *str - 'A' + 10; + } else if(*str >= 'a' && *str <= 'z') { + digit_value = *str - 'a' + 10; + } else { + break; + } + + if(digit_value >= base) { + break; + } + + if(result > mul_limit) return StrintParseOverflowError; + result *= base; + if(result > limit - digit_value) return StrintParseOverflowError; + result += digit_value; + + read_total++; + str++; + } + + if(read_total == 0) { + if(inferred_base == 8) { + // there's just a single zero + result = 0; + } else { + return StrintParseAbsentError; + } + } + + if(abs_out) *abs_out = result; + if(negative_out) *negative_out = negative; + if(end) *end = (char*)str; // rabbit hole: https://c-faq.com/ansi/constmismatch.html + return StrintParseNoError; +} + +#define STRINT_MONO(name, ret_type, neg_abs_limit, pos_limit) \ + StrintParseError name(const char* str, char** end, ret_type* out, uint8_t base) { \ + uint64_t absolute; \ + bool negative; \ + StrintParseError err = strint_to_uint64_internal( \ + str, end, &absolute, &negative, base, (neg_abs_limit), (pos_limit)); \ + if(err) return err; \ + if(out) *out = (negative ? (-(ret_type)absolute) : ((ret_type)absolute)); \ + return StrintParseNoError; \ + } + +STRINT_MONO(strint_to_uint64, uint64_t, 0, UINT64_MAX) +STRINT_MONO(strint_to_int64, int64_t, (uint64_t)INT64_MAX + 1, INT64_MAX) +STRINT_MONO(strint_to_uint32, uint32_t, 0, UINT32_MAX) +STRINT_MONO(strint_to_int32, int32_t, (uint64_t)INT32_MAX + 1, INT32_MAX) +STRINT_MONO(strint_to_uint16, uint16_t, 0, UINT16_MAX) +STRINT_MONO(strint_to_int16, int16_t, (uint64_t)INT16_MAX + 1, INT16_MAX) diff --git a/lib/toolbox/strint.h b/lib/toolbox/strint.h new file mode 100644 index 00000000000..c27cdcb4ea0 --- /dev/null +++ b/lib/toolbox/strint.h @@ -0,0 +1,70 @@ +/** + * @file strint.h + * Performs conversions between strings and integers. + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** String to integer conversion error */ +typedef enum { + StrintParseNoError, //!< Conversion performed successfully + StrintParseSignError, //!< Multiple leading `+` or `-` characters, or leading `-` character if the type is unsigned + StrintParseAbsentError, //!< No valid digits after the leading whitespace, sign and prefix + StrintParseOverflowError, //!< Result does not fit in the requested type +} StrintParseError; + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint64(const char* str, char** end, uint64_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int64(const char* str, char** end, int64_t* out, uint8_t base); + +/** Converts a string to a `uint32_t` + * + * @param[in] str Input string + * @param[out] end Pointer to first character after the number in input string + * @param[out] out Parse result + * @param[in] base Integer base + * + * @return Parse error + * + * Parses the number in the input string. The number may be surrounded by + * whitespace characters to the left and any non-digit characters to the right. + * What's considered a digit is determined by the input base in the following + * order: `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`. The number may be prefixed + * with either a `+` or a `-` to indicate its sign. The pointer to the first + * character after the leading whitespace, allowed prefixes and digits is + * assigned to `end`. + * + * If the input base is 0, the base is inferred from the leading characters of + * the number: + * - If it starts with `0x`, it's read in base 16; + * - If it starts with a `0`, it's read in base 8; + * - If it starts with `0b`, it's read in base 2. + * - Otherwise, it's read in base 10. + * + * For a description of the return codes, see `StrintParseError`. If the return + * code is something other than `StrintParseNoError`, the values at `end` and + * `out` are unaltered. + */ +StrintParseError strint_to_uint32(const char* str, char** end, uint32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int32(const char* str, char** end, int32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint16(const char* str, char** end, uint16_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int16(const char* str, char** end, int16_t* out, uint8_t base); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index 580a76d457c..92d84a779fc 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -1,6 +1,7 @@ #include "manifest.h" #include +#include #include struct ResourceManifestReader { @@ -97,7 +98,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right( resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); - resource_manifest->entry.size = atoi(furi_string_get_cstr(resource_manifest->linebuf)); + if(strint_to_uint32( + furi_string_get_cstr(resource_manifest->linebuf), + NULL, + &resource_manifest->entry.size, + 10) != StrintParseNoError) + break; /* Remove size */ size_t offs = furi_string_search_char(resource_manifest->linebuf, ':'); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2c1a565651b..b603813a773 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -170,6 +170,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -2589,6 +2590,12 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e5292d93657..1d5b98fe3d2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -242,6 +242,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -3266,6 +3267,12 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* From feb1b2f34942962d886683fd7fb1310c480f2a7f Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 5 Sep 2024 20:44:22 +0300 Subject: [PATCH 11/29] [FL-3882] Clean up of LFS traces (#3849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updater, storage: removed mentions of LFS from public APIs; updated corresponding strings * rpc: updated include path Co-authored-by: あく --- applications/services/rpc/rpc_storage.c | 6 +++--- applications/services/storage/storage.h | 2 +- applications/system/updater/cli/updater_cli.c | 6 +++--- applications/system/updater/util/update_task.c | 14 +++++++------- applications/system/updater/util/update_task.h | 4 ++-- .../updater/util/update_task_worker_backup.c | 14 +++++++------- .../updater/util/update_task_worker_flasher.c | 2 +- documentation/OTA.md | 4 ++-- lib/update_util/{lfs_backup.c => int_backup.c} | 16 ++++++++-------- lib/update_util/int_backup.h | 18 ++++++++++++++++++ lib/update_util/lfs_backup.h | 18 ------------------ targets/f7/furi_hal/furi_hal_rtc.h | 3 ++- 12 files changed, 54 insertions(+), 53 deletions(-) rename lib/update_util/{lfs_backup.c => int_backup.c} (77%) create mode 100644 lib/update_util/int_backup.h delete mode 100644 lib/update_util/lfs_backup.h diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 89991aa86c7..aedcf8c943e 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -656,7 +656,7 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_create( + bool backup_ok = int_backup_create( rpc_storage->api, request->content.storage_backup_create_request.archive_path); rpc_send_and_release_empty( @@ -676,7 +676,7 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_unpack( + bool backup_ok = int_backup_unpack( rpc_storage->api, request->content.storage_backup_restore_request.archive_path); rpc_send_and_release_empty( diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 6dbeb0d36b6..072db1305bb 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -504,7 +504,7 @@ FS_Error storage_sd_info(Storage* storage, SDInfo* info); */ FS_Error storage_sd_status(Storage* storage); -/******************* Internal LFS Functions *******************/ +/************ Internal Storage Backup/Restore ************/ typedef void (*StorageNameConverter)(FuriString*); diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 0b734c0f497..56a16bd9d31 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include typedef void (*cmd_handler)(FuriString* args); @@ -35,7 +35,7 @@ static void updater_cli_install(FuriString* manifest_path) { static void updater_cli_backup(FuriString* args) { printf("Backup /int to '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_create(storage, furi_string_get_cstr(args)); + bool success = int_backup_create(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } @@ -43,7 +43,7 @@ static void updater_cli_backup(FuriString* args) { static void updater_cli_restore(FuriString* args) { printf("Restore /int from '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_unpack(storage, furi_string_get_cstr(args)); + bool success = int_backup_unpack(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 8f051ff77a2..cca488475ea 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -22,8 +22,8 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageRadioInstall] = "Installing radio FW", [UpdateTaskStageRadioBusy] = "Core 2 busy", [UpdateTaskStageOBValidation] = "Validating opt. bytes", - [UpdateTaskStageLfsBackup] = "Backing up LFS", - [UpdateTaskStageLfsRestore] = "Restoring LFS", + [UpdateTaskStageIntBackup] = "Backing up configuration", + [UpdateTaskStageIntRestore] = "Restoring configuration", [UpdateTaskStageResourcesFileCleanup] = "Cleaning up files", [UpdateTaskStageResourcesDirCleanup] = "Cleaning up directories", [UpdateTaskStageResourcesFileUnpack] = "Extracting resources", @@ -82,7 +82,7 @@ static const struct { }, #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsBackup, + .stage = UpdateTaskStageIntBackup, .percent_min = 0, .percent_max = 100, .descr = "FS R/W error", @@ -193,10 +193,10 @@ static const struct { #endif #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsRestore, + .stage = UpdateTaskStageIntRestore, .percent_min = 0, .percent_max = 100, - .descr = "LFS I/O error", + .descr = "SD card I/O error", }, { .stage = UpdateTaskStageResourcesFileCleanup, @@ -245,7 +245,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 45), - [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), + [UpdateTaskStageIntBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 25), @@ -259,7 +259,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 20), - [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), + [UpdateTaskStageIntRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), [UpdateTaskStageResourcesFileCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 100), [UpdateTaskStageResourcesDirCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 50), diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index 52bdfdbd235..c346c55fa4d 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -16,7 +16,7 @@ typedef enum { UpdateTaskStageProgress = 0, UpdateTaskStageReadManifest, - UpdateTaskStageLfsBackup, + UpdateTaskStageIntBackup, UpdateTaskStageRadioImageValidate, UpdateTaskStageRadioErase, @@ -30,7 +30,7 @@ typedef enum { UpdateTaskStageFlashWrite, UpdateTaskStageFlashValidate, - UpdateTaskStageLfsRestore, + UpdateTaskStageIntRestore, UpdateTaskStageResourcesFileCleanup, UpdateTaskStageResourcesDirCleanup, UpdateTaskStageResourcesFileUnpack, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 8d5039a16da..1b5bea25b30 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -21,14 +21,14 @@ static bool update_task_pre_update(UpdateTask* update_task) { backup_file_path = furi_string_alloc(); path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, backup_file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsBackup, 0); + update_task_set_progress(update_task, UpdateTaskStageIntBackup, 0); /* to avoid bootloops */ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); if((success = - lfs_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { + int_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeUpdate); } @@ -145,12 +145,12 @@ static bool update_task_post_update(UpdateTask* update_task) { do { path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); + update_task_set_progress(update_task, UpdateTaskStageIntRestore, 0); - CHECK_RESULT(lfs_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); + CHECK_RESULT(int_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); if(update_task->state.groups & UpdateTaskStageGroupResources) { TarUnpackProgress progress = { diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index e7e1bbbedc6..a464815f0e0 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -341,7 +341,7 @@ int32_t update_task_worker_flash_writer(void* context) { } furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); - // Format LFS before restoring backup on next boot + // Clean up /int before restoring backup on next boot furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal); #ifdef FURI_NDEBUG // Production diff --git a/documentation/OTA.md b/documentation/OTA.md index 0456eab1f91..9783a704770 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -83,7 +83,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Backing up LFS | **2** | **0-100** | FS read/write error | +| Backing up configuration| **2** | **0-100** | FS read/write error | | Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | | Uninstalling radio FW | **4** | **0** | SHCI Delete command error | @@ -101,7 +101,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **99-100** | Corrupted DFU file | | Writing flash | **10** | **0-100** | Block read/write error | | Validating flash | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | FS read/write error | +| Restoring configuration | **12** | **0-100** | FS read/write error | | Updating resources | **13-15** | **0-100** | SD card read/write error | ## Building update packages diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/int_backup.c similarity index 77% rename from lib/update_util/lfs_backup.c rename to lib/update_util/int_backup.c index 7786524ef64..a904db247f1 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/int_backup.c @@ -1,4 +1,4 @@ -#include "lfs_backup.h" +#include "int_backup.h" #include @@ -9,7 +9,7 @@ #include #include -#define LFS_BACKUP_DEFAULT_LOCATION EXT_PATH(LFS_BACKUP_DEFAULT_FILENAME) +#define INT_BACKUP_DEFAULT_LOCATION EXT_PATH(INT_BACKUP_DEFAULT_FILENAME) static void backup_name_converter(FuriString* filename) { if(furi_string_empty(filename) || (furi_string_get_char(filename, 0) == '.')) { @@ -34,18 +34,18 @@ static void backup_name_converter(FuriString* filename) { } } -bool lfs_backup_create(Storage* storage, const char* destination) { +bool int_backup_create(Storage* storage, const char* destination) { const char* final_destination = - destination && strlen(destination) ? destination : LFS_BACKUP_DEFAULT_LOCATION; + destination && strlen(destination) ? destination : INT_BACKUP_DEFAULT_LOCATION; return storage_int_backup(storage, final_destination) == FSE_OK; } -bool lfs_backup_exists(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_exists(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_common_stat(storage, final_source, NULL) == FSE_OK; } -bool lfs_backup_unpack(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_unpack(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_int_restore(storage, final_source, backup_name_converter) == FSE_OK; } diff --git a/lib/update_util/int_backup.h b/lib/update_util/int_backup.h new file mode 100644 index 00000000000..168efda5043 --- /dev/null +++ b/lib/update_util/int_backup.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#define INT_BACKUP_DEFAULT_FILENAME "backup.tar" + +#ifdef __cplusplus +extern "C" { +#endif + +bool int_backup_create(Storage* storage, const char* destination); +bool int_backup_exists(Storage* storage, const char* source); +bool int_backup_unpack(Storage* storage, const char* source); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/lfs_backup.h b/lib/update_util/lfs_backup.h deleted file mode 100644 index 5a7738c86ed..00000000000 --- a/lib/update_util/lfs_backup.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include - -#define LFS_BACKUP_DEFAULT_FILENAME "backup.tar" - -#ifdef __cplusplus -extern "C" { -#endif - -bool lfs_backup_create(Storage* storage, const char* destination); -bool lfs_backup_exists(Storage* storage, const char* source); -bool lfs_backup_unpack(Storage* storage, const char* source); - -#ifdef __cplusplus -} -#endif diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index 030b464cf7a..c5eab12ec5f 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -44,7 +45,7 @@ typedef enum { FuriHalRtcRegisterHeader, /**< RTC structure header */ FuriHalRtcRegisterSystem, /**< Various system bits */ FuriHalRtcRegisterVersion, /**< Pointer to Version */ - FuriHalRtcRegisterLfsFingerprint, /**< LFS geometry fingerprint */ + FuriHalRtcRegisterLfsFingerprint FURI_DEPRECATED, /**< LFS geometry fingerprint */ FuriHalRtcRegisterFaultData, /**< Pointer to last fault message */ FuriHalRtcRegisterPinFails, /**< Failed PINs count */ /* Index of FS directory entry corresponding to FW update to be applied */ From 62bbf406be28bd5855a0a160e73e001f40c1d8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 6 Sep 2024 03:55:43 +0900 Subject: [PATCH 12/29] Debug: use proper hook for handle_exit in flipperapps (#3842) * Debug: use proper hook for handle_exit in flipperapps * fbt: flash firmware for `blackmagic` target Co-authored-by: hedger Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- SConstruct | 3 ++- scripts/debug/flipperapps.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SConstruct b/SConstruct index b4c8d7b2903..12796796888 100644 --- a/SConstruct +++ b/SConstruct @@ -234,7 +234,7 @@ firmware_debug = distenv.PhonyTarget( ) distenv.Depends(firmware_debug, firmware_flash) -distenv.PhonyTarget( +firmware_blackmagic = distenv.PhonyTarget( "blackmagic", "${GDBPYCOM}", source=firmware_env["FW_ELF"], @@ -242,6 +242,7 @@ distenv.PhonyTarget( GDBREMOTE="${BLACKMAGIC_ADDR}", FBT_FAP_DEBUG_ELF_ROOT=firmware_env["FBT_FAP_DEBUG_ELF_ROOT"], ) +distenv.Depends(firmware_blackmagic, firmware_flash) # Debug alien elf debug_other_opts = [ diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 6dba89a5640..81aa43c34cf 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -124,7 +124,7 @@ def invoke(self, arg, from_tty): print(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) - gdb.events.exited.connect(helper.handle_exit) + gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: print(f"Support for Flipper external apps debug is not available: {e}") From 49e1ae6e875d46c4f54c635895092abd682836dc Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:54:49 +0300 Subject: [PATCH 13/29] [FL-3895] Broken file interaction fixes (#3852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show error screen if corrupted filed has been loaded * Added rpc error codes and error processing to NFC * Made iButton scene on_enter handler clear to prevent showing scene before file is loaded * Added rpc error codes and error processing to iButton * Made lfRfid scene on_enter handler clear to prevent showing scene before file is loaded * Added rpc error codes and error processing to lfRfid * Made SubGHz scene on_enter handler clear to prevent showing scene before file is loaded. Also moved file_name_tmp formatting logic to a separate function * Now function returns loading status and starts rx only if load succeeded * Added show error logic on tx_button start * Introduced rpc error codes for infrared * Adjusted rpc scene logic to show scene only when loading is fine * Added new list of rpc errors which are common within several applications * Removed same enums from apps * Same rpc error in different apps replaced with common value from rpc error code list * SubGHz error codes now start from RpcAppSystemErrorCodesReserved value * Infrared error codes now start from RpcAppSystemErrorCodesReserved value * Removed unused enum * Now all rpc error codes are more generalized and can be used among all apps without any specific enums * Removed specific error codes, now rpc error codes are used instead * RPC: no plurals in enums Co-authored-by: あく --- .../main/ibutton/scenes/ibutton_scene_rpc.c | 24 +++++----- applications/main/infrared/infrared_app.c | 12 +++-- applications/main/infrared/infrared_app_i.h | 2 +- .../infrared/scenes/infrared_scene_remote.c | 7 ++- .../main/infrared/scenes/infrared_scene_rpc.c | 45 ++++++++++++------- .../main/lfrfid/scenes/lfrfid_scene_rpc.c | 30 +++++++------ .../protocol_support/nfc_protocol_support.c | 4 ++ applications/main/nfc/nfc_app.c | 2 +- .../main/subghz/helpers/subghz_error_type.h | 14 ------ .../main/subghz/scenes/subghz_scene_rpc.c | 41 ++++++++--------- applications/main/subghz/subghz_i.h | 1 - applications/services/rpc/rpc_app.h | 1 + .../services/rpc/rpc_app_error_codes.h | 11 +++++ 13 files changed, 106 insertions(+), 88 deletions(-) delete mode 100644 applications/main/subghz/helpers/subghz_error_type.h create mode 100644 applications/services/rpc/rpc_app_error_codes.h diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index f4f193a47e8..87f51f2d8b4 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -1,22 +1,26 @@ #include "../ibutton_i.h" void ibutton_scene_rpc_on_enter(void* context) { - iButton* ibutton = context; + UNUSED(context); +} + +static void ibutton_rpc_start_emulation(iButton* ibutton) { Popup* popup = ibutton->popup; popup_set_header(popup, "iButton", 82, 28, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 82, 32, AlignCenter, AlignTop); - + popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); popup_set_icon(popup, 2, 14, &I_iButtonKey_49x44); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + ibutton_worker_emulate_start(ibutton->worker, ibutton->key); + + ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); notification_message(ibutton->notifications, &sequence_display_backlight_on); } bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; - Popup* popup = ibutton->popup; bool consumed = false; @@ -27,17 +31,13 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if(ibutton_load_key(ibutton, false)) { - popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - - ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); - ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - + ibutton_rpc_start_emulation(ibutton); result = true; + } else { + rpc_system_app_set_error_code(ibutton->rpc, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(ibutton->rpc, "Cannot load key file"); } - rpc_system_app_confirm(ibutton->rpc, result); - } else if(event.event == iButtonCustomEventRpcExit) { rpc_system_app_confirm(ibutton->rpc, true); scene_manager_stop(ibutton->scene_manager); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 75c874e73b3..4ddf0776bf8 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -382,17 +382,15 @@ void infrared_tx_start(InfraredApp* infrared) { infrared->app_state.is_transmitting = true; } -void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { +bool infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); - if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) { + bool result = + infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index); + if(result) { infrared_tx_start(infrared); - } else { - infrared_show_error_message( - infrared, - "Failed to load\n\"%s\"", - infrared_remote_get_signal_name(infrared->remote, button_index)); } + return result; } void infrared_tx_stop(InfraredApp* infrared) { diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 75d4e230d25..002a8c2ee88 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -208,7 +208,7 @@ void infrared_tx_start(InfraredApp* infrared); * @param[in] button_index index of the signal to be loaded. * @returns true if the signal could be loaded, false otherwise. */ -void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); +bool infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); /** * @brief Stop transmission of the currently loaded signal. diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index 6c1d1ec4e39..e1195c51635 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -85,7 +85,12 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { if(custom_type == InfraredCustomEventTypeTransmitStarted) { furi_assert(button_index >= 0); - infrared_tx_start_button_index(infrared, button_index); + if(!infrared_tx_start_button_index(infrared, button_index)) { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(infrared->remote, button_index)); + } consumed = true; } else if(custom_type == InfraredCustomEventTypeTransmitStopped) { infrared_tx_stop(infrared); diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 4c263a11752..16724766858 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -20,18 +20,24 @@ static int32_t infrared_scene_rpc_task_callback(void* context) { void infrared_scene_rpc_on_enter(void* context) { InfraredApp* infrared = context; + scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); +} + +static void infrared_scene_rpc_show(InfraredApp* infrared) { Popup* popup = infrared->popup; popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_text(popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); - popup_set_context(popup, context); + popup_set_context(popup, infrared); popup_set_callback(popup, infrared_popup_closed_callback); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); - scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); notification_message(infrared->notifications, &sequence_display_backlight_on); } @@ -52,24 +58,20 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { } else if(event.event == InfraredCustomEventTypeTaskFinished) { const bool task_success = infrared_blocking_task_finalize(infrared); - if(task_success) { - const char* remote_name = infrared_remote_get_name(infrared->remote); - infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); - } else { - infrared_text_store_set( - infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path)); - } + FuriString* str = furi_string_alloc(); + furi_string_printf( + str, "Failed to load\n%s", furi_string_get_cstr(infrared->file_path)); - popup_set_text( - infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); + rpc_system_app_set_error_code(infrared->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(infrared->rpc_ctx, furi_string_get_cstr(str)); + furi_string_free(str); + } rpc_system_app_confirm(infrared->rpc_ctx, task_success); - } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { @@ -88,10 +90,19 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - infrared_tx_start_button_index(infrared, app_state->current_button_index); - scene_manager_set_scene_state( - infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); - result = true; + if(infrared_tx_start_button_index(infrared, app_state->current_button_index)) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name); + + infrared_scene_rpc_show(infrared); + result = true; + } else { + rpc_system_app_set_error_code( + infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + infrared->rpc_ctx, "Cannot load button data"); + result = false; + } } } rpc_system_app_confirm(infrared->rpc_ctx, result); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c index 906218d74f0..65ffa1ef658 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c @@ -2,23 +2,30 @@ void lfrfid_scene_rpc_on_enter(void* context) { LfRfid* app = context; + app->rpc_state = LfRfidRpcStateIdle; +} + +static void lfrfid_rpc_start_emulation(LfRfid* app) { Popup* popup = app->popup; + lfrfid_text_store_set(app, "emulating\n%s", furi_string_get_cstr(app->file_name)); + popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); - notification_message(app->notifications, &sequence_display_backlight_on); + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - app->rpc_state = LfRfidRpcStateIdle; + notification_message(app->notifications, &sequence_display_backlight_on); + notification_message(app->notifications, &sequence_blink_start_magenta); + app->rpc_state = LfRfidRpcStateEmulating; } bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { LfRfid* app = context; - Popup* popup = app->popup; - UNUSED(event); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -34,16 +41,11 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if(app->rpc_state == LfRfidRpcStateIdle) { if(lfrfid_load_key_data(app, app->file_path, false)) { - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - app->rpc_state = LfRfidRpcStateEmulating; - - lfrfid_text_store_set( - app, "emulating\n%s", furi_string_get_cstr(app->file_name)); - popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); - - notification_message(app->notifications, &sequence_blink_start_magenta); + lfrfid_rpc_start_emulation(app); result = true; + } else { + rpc_system_app_set_error_code(app->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(app->rpc_ctx, "Cannot load key file"); } } rpc_system_app_confirm(app->rpc_ctx, result); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 630b3beef6f..7a07404fdc1 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -717,6 +717,10 @@ static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManag if(nfc_load_file(instance, instance->file_path, false)) { nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); success = true; + } else { + rpc_system_app_set_error_code( + instance->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(instance->rpc_ctx, "Cannot load key file"); } } rpc_system_app_confirm(instance->rpc_ctx, success); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index bdf9c0e2f83..5365d6bef68 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -488,7 +488,7 @@ int32_t nfc_app(void* p) { nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); furi_string_set(nfc->file_path, args); - if(nfc_load_file(nfc, nfc->file_path, false)) { + if(nfc_load_file(nfc, nfc->file_path, true)) { nfc_show_initial_scene_for_device(nfc); } else { view_dispatcher_stop(nfc->view_dispatcher); diff --git a/applications/main/subghz/helpers/subghz_error_type.h b/applications/main/subghz/helpers/subghz_error_type.h deleted file mode 100644 index 0f86d6ea7d1..00000000000 --- a/applications/main/subghz/helpers/subghz_error_type.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include - -/** SubGhzErrorType */ -typedef enum { - SubGhzErrorTypeNoError = 0, /** There are no errors */ - SubGhzErrorTypeParseFile = - 1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ - SubGhzErrorTypeOnlyRX = - 2, /** Transmission on this frequency is blocked by regional settings */ - SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */ -} SubGhzErrorType; diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index 42ec98a3963..c1476746d4a 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -8,23 +8,33 @@ typedef enum { void subghz_scene_rpc_on_enter(void* context) { SubGhz* subghz = context; + scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); +} + +static void subghz_format_file_name_tmp(SubGhz* subghz) { + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(subghz->file_path, file_name, true); + snprintf( + subghz->file_name_tmp, SUBGHZ_MAX_LEN_NAME, "loaded\n%s", furi_string_get_cstr(file_name)); + furi_string_free(file_name); +} + +static void subghz_scene_rpc_emulation_show(SubGhz* subghz) { Popup* popup = subghz->popup; + subghz_format_file_name_tmp(subghz); popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); + popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); - scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); - notification_message(subghz->notifications, &sequence_display_backlight_on); } bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; - Popup* popup = subghz->popup; bool consumed = false; SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); @@ -43,13 +53,15 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { switch( subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { case SubGhzTxRxStartTxStateErrorOnlyRx: - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock); rpc_system_app_set_error_text( subghz->rpc_ctx, "Transmission on this frequency is restricted in your region"); break; case SubGhzTxRxStartTxStateErrorParserOthers: - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParserOthers); + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse); rpc_system_app_set_error_text( subghz->rpc_ctx, "Error in protocol parameters description"); break; @@ -77,23 +89,12 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if(state == SubGhzRpcStateIdle) { if(subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false)) { + subghz_scene_rpc_emulation_show(subghz); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded); result = true; - FuriString* file_name; - file_name = furi_string_alloc(); - path_extract_filename(subghz->file_path, file_name, true); - - snprintf( - subghz->file_name_tmp, - SUBGHZ_MAX_LEN_NAME, - "loaded\n%s", - furi_string_get_cstr(file_name)); - popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); - - furi_string_free(file_name); } else { - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParseFile); + rpc_system_app_set_error_code(subghz->rpc_ctx, RpcAppSystemErrorCodeParseFile); rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 9e58f3947dc..08687a4f79f 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -1,7 +1,6 @@ #pragma once #include "helpers/subghz_types.h" -#include "helpers/subghz_error_type.h" #include #include "subghz.h" #include "views/receiver.h" diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index 4ee5a24d379..aa6fd81cc81 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -13,6 +13,7 @@ #pragma once #include "rpc.h" +#include "rpc_app_error_codes.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/rpc/rpc_app_error_codes.h b/applications/services/rpc/rpc_app_error_codes.h new file mode 100644 index 00000000000..fc0edd4d2d1 --- /dev/null +++ b/applications/services/rpc/rpc_app_error_codes.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Enumeration of possible error codes for application which can be started through rpc + */ +typedef enum { + RpcAppSystemErrorCodeNone, /** There are no errors */ + RpcAppSystemErrorCodeParseFile, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ + RpcAppSystemErrorCodeRegionLock, /** Requested function is blocked by regional settings */ + RpcAppSystemErrorCodeInternalParse, /** Error in protocol parameters description, or some data in opened file are unsupported */ +} RpcAppSystemErrorCode; From 20aff7320f1b2fc248b0d1e3e42eb155c1b24884 Mon Sep 17 00:00:00 2001 From: Valera Olexienko Date: Thu, 5 Sep 2024 22:14:57 +0200 Subject: [PATCH 14/29] Infrared: Add Airwell AW-HKD012-N91 (#3856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 68abaf2592a..df958ffba27 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -858,3 +858,41 @@ 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 +# +# Model: Airwell AW-HKD012-N91 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4387 4398 547 1609 547 530 547 1610 547 1611 545 530 547 530 547 1608 547 530 548 530 547 1611 545 532 546 532 547 1609 547 1610 547 531 547 1608 548 530 547 530 548 530 547 1610 546 1609 547 1610 547 1609 547 1609 547 1611 545 1609 548 1610 546 530 547 530 548 529 549 531 547 531 546 531 547 1608 548 1610 547 1608 548 533 545 1608 548 532 546 532 546 1611 545 532 547 532 545 530 548 1608 547 530 549 1608 547 1609 548 5203 4386 4398 547 1609 546 530 547 1609 546 1607 548 531 547 531 547 1609 547 530 548 531 547 1609 547 531 547 531 547 1608 547 1613 544 531 546 1609 547 531 547 531 547 532 546 1609 547 1609 546 1609 547 1609 547 1608 547 1608 548 1608 548 1609 547 530 547 530 547 530 547 532 546 530 547 530 548 1610 546 1608 547 1609 547 530 547 1609 547 530 547 530 548 1609 546 530 548 530 547 532 546 1610 546 531 546 1608 548 1608 548 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4398 547 1608 548 531 546 1610 546 1609 547 530 547 529 548 1608 548 532 547 530 548 1612 544 529 549 530 548 1608 547 1609 547 531 546 1608 548 1607 549 529 549 1608 549 1609 548 1608 548 1608 548 1611 545 1608 548 530 548 1609 547 531 547 530 548 530 548 531 547 529 549 530 548 530 547 531 547 530 548 530 547 529 549 530 548 532 547 530 548 1609 547 1610 547 1608 548 1609 547 1608 548 1608 548 1608 548 1608 548 5203 4388 4396 549 1609 547 529 549 1610 546 1608 548 529 549 530 547 1609 547 530 548 529 549 1608 548 531 547 532 546 1609 547 1609 547 530 548 1609 548 1609 548 529 548 1608 548 1609 548 1609 547 1609 547 1608 548 1609 547 532 546 1608 548 531 548 531 548 530 548 530 548 531 547 530 548 531 548 531 547 530 548 530 548 530 548 531 547 529 549 529 549 1609 548 1608 548 1609 547 1608 548 1608 548 1608 548 1607 549 1607 549 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4384 4400 572 1585 571 505 572 1583 573 1584 572 508 570 503 575 1584 572 505 572 506 572 1583 573 504 573 506 571 1586 570 1585 572 532 546 1586 570 1585 571 506 571 1585 571 1583 573 1586 570 1583 573 1584 572 1589 569 505 572 1585 571 506 571 506 573 506 572 505 573 532 545 504 574 509 570 1611 545 506 572 1582 574 506 572 507 571 507 571 507 570 1584 572 507 571 1587 569 506 572 1584 572 1585 571 1583 573 1612 544 5179 4386 4400 570 1584 572 507 571 1583 572 1585 571 506 572 506 572 1584 572 505 572 504 574 1584 572 507 571 504 574 1583 573 1585 572 507 571 1584 572 1610 545 508 571 1587 569 1583 573 1583 573 1585 571 1585 572 1585 572 505 572 1584 572 505 573 507 572 506 571 504 574 505 573 505 574 508 571 1585 571 507 571 1585 571 506 571 506 572 504 574 505 572 1586 570 507 571 1586 570 505 573 1584 572 1585 571 1587 569 1584 573 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4386 4398 575 1582 574 503 575 1583 573 1582 574 505 573 504 574 1582 574 506 572 508 570 1583 573 504 574 505 573 1583 573 1584 573 505 573 1582 575 1583 574 504 574 1582 574 1583 573 1583 573 1583 573 1585 571 1586 572 504 573 1584 572 504 573 505 573 505 573 505 573 504 573 506 571 1583 574 505 573 1583 573 1583 573 1584 572 1583 573 505 572 505 573 504 574 1583 574 505 573 505 573 504 574 505 572 1584 572 1584 573 5178 4387 4400 571 1583 573 504 574 1584 572 1584 572 507 572 504 574 1582 574 505 572 505 573 1583 573 504 574 504 574 1582 574 1584 573 503 574 1583 573 1582 574 505 573 1583 573 1582 575 1583 573 1610 546 1584 572 1583 573 505 573 1610 546 506 572 505 573 504 574 504 574 505 573 505 573 1584 573 505 573 1582 574 1584 572 1583 573 1583 573 504 574 503 575 504 574 1585 571 507 571 504 573 506 572 505 572 1584 572 1585 571 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 547 1608 548 530 548 1610 547 1610 547 529 549 529 548 1608 548 530 548 530 548 1607 549 533 545 531 548 1608 548 1610 546 531 547 1609 547 1608 548 529 549 1609 547 1609 547 1609 547 1609 547 1609 547 1608 548 529 548 1638 519 530 548 530 548 530 548 529 550 528 549 530 548 530 548 1608 548 530 548 1609 548 1610 547 1609 547 531 546 529 549 1608 548 530 548 1609 548 530 548 529 548 530 548 1609 548 1609 548 5205 4387 4398 547 1609 548 531 546 1609 547 1609 547 530 548 531 546 1609 547 531 548 530 573 1583 573 507 571 506 572 1583 573 1582 574 504 574 1581 575 1582 574 506 572 1583 574 1583 573 1583 573 1585 571 1584 572 1585 570 507 571 1582 574 505 574 532 545 505 573 505 572 506 571 505 573 505 573 1584 572 506 572 1583 573 1584 572 1583 573 505 572 504 573 1583 573 505 573 1586 571 506 572 505 573 507 572 1583 573 1584 572 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 \ No newline at end of file From c6326915aeeaeb15a9b71620a3d2a1c8b51f2903 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:13:03 +0200 Subject: [PATCH 15/29] DialogEx: Fix NULL ptr crash (#3878) --- applications/services/gui/modules/dialog_ex.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index 75209a40804..d27de124206 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -222,7 +222,7 @@ void dialog_ex_set_header( dialog_ex->view, DialogExModel * model, { - furi_string_set(model->header.text, text); + furi_string_set(model->header.text, text ? text : ""); model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; @@ -243,7 +243,7 @@ void dialog_ex_set_text( dialog_ex->view, DialogExModel * model, { - furi_string_set(model->text.text, text); + furi_string_set(model->text.text, text ? text : ""); model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; @@ -268,7 +268,10 @@ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* i void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); with_view_model( - dialog_ex->view, DialogExModel * model, { furi_string_set(model->left_text, text); }, true); + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->left_text, text ? text : ""); }, + true); } void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { @@ -276,7 +279,7 @@ void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { with_view_model( dialog_ex->view, DialogExModel * model, - { furi_string_set(model->center_text, text); }, + { furi_string_set(model->center_text, text ? text : ""); }, true); } @@ -285,7 +288,7 @@ void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { with_view_model( dialog_ex->view, DialogExModel * model, - { furi_string_set(model->right_text, text); }, + { furi_string_set(model->right_text, text ? text : ""); }, true); } From 4fd76524c931a814749ab0f40c830bfd146491d3 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:33:17 +0200 Subject: [PATCH 16/29] Desktop: Sanity check PIN length for good measure (#3879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Desktop: Sanity check PIN length for good measure * Desktop: replace hardcoded values with macros in desktop_view_pin_input.c Co-authored-by: あく --- applications/services/desktop/helpers/pin_code.c | 3 ++- applications/services/desktop/helpers/pin_code.h | 1 + applications/services/desktop/views/desktop_view_pin_input.c | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/applications/services/desktop/helpers/pin_code.c b/applications/services/desktop/helpers/pin_code.c index d1a37ed24c6..82e117a53d0 100644 --- a/applications/services/desktop/helpers/pin_code.c +++ b/applications/services/desktop/helpers/pin_code.c @@ -58,7 +58,8 @@ static uint32_t desktop_pin_code_pack(const DesktopPinCode* pin_code) { } bool desktop_pin_code_is_set(void) { - return furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET; + uint8_t length = furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET; + return length >= DESKTOP_PIN_CODE_MIN_LEN && length <= DESKTOP_PIN_CODE_MAX_LEN; } void desktop_pin_code_set(const DesktopPinCode* pin_code) { diff --git a/applications/services/desktop/helpers/pin_code.h b/applications/services/desktop/helpers/pin_code.h index 848c915b6ca..d0695794bf6 100644 --- a/applications/services/desktop/helpers/pin_code.h +++ b/applications/services/desktop/helpers/pin_code.h @@ -3,6 +3,7 @@ #include #include +#define DESKTOP_PIN_CODE_MIN_LEN (4) #define DESKTOP_PIN_CODE_MAX_LEN (10) typedef struct { diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index c89a143c876..3c18f166c71 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -13,7 +13,7 @@ #define DEFAULT_PIN_X 64 #define DEFAULT_PIN_Y 32 -#define MIN_PIN_LENGTH 4 +#define MIN_PIN_LENGTH DESKTOP_PIN_CODE_MIN_LEN #define MAX_PIN_LENGTH DESKTOP_PIN_CODE_MAX_LEN struct DesktopViewPinInput { @@ -103,7 +103,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu furi_assert(canvas); furi_assert(model); - uint8_t draw_pin_size = MAX(4, model->pin.length + 1); + uint8_t draw_pin_size = MAX(MIN_PIN_LENGTH, model->pin.length + 1); if(model->locked_input || (model->pin.length == MAX_PIN_LENGTH)) { draw_pin_size = model->pin.length; } From 6a1c27ef932c75b6a7a4488dd8992e0355dda29d Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:43:07 +0200 Subject: [PATCH 17/29] Loader: Warn about missing SD card for main apps (#3875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/loader/loader.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 7e42a4488e0..0f4cc4a0c24 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -94,10 +94,20 @@ static void loader_show_gui_error( if(status.value == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps - dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + const char* header = NULL; + const char* text = NULL; + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_sd_status(storage) == FSE_OK) { + header = "Update needed"; + text = "Update firmware\nto run this app"; + } else { + header = "SD card needed"; + text = "Install SD card\nto run this app"; + } + furi_record_close(RECORD_STORAGE); + dialog_message_set_header(message, header, 64, 3, AlignCenter, AlignTop); dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); - dialog_message_set_text( - message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_set_text(message, text, 3, 26, AlignLeft, AlignTop); dialog_message_show(dialogs, message); } else if(status.value == LoaderStatusErrorUnknownApp) { loader_dialog_prepare_and_show(dialogs, &err_app_not_found); From e0654fe409d5d6afcfe6ea94faba766192252e76 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:52:00 +0300 Subject: [PATCH 18/29] [FL-3890] Infrared button operation fails now shows more informative messages (#3859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Error codes enum added * Adjusted signal api to return error codes instead of bool * Remote api adjusted to work with error codes * Brute force logic adjusted to work with error codes * Other application functions adjust to work with error codes * All task callbacks now return ErrorCode through int32t, which belongs to thread * All scenes now work with error codes * More api functions now return error code * Now signal names are buffered and restored in case of error. * New error code enumeration added. Now least significant byte is left for the button index * Some macro to simplify error setup and error check * Error code checks replaced by macro * Different message is now shown when move failed * Comments updated * Fixed error check * Fixed navigation issue while openning broken files from Favorites * Now search by index also returns index in addition to error code * Remote loading logic adjusted * New error codes added and numbers adjusted * New error message when loading library file instead of signal one * Some more remote loading logic adjusted * New error message on rename fail * Grammar mistake fix * Function signature changed * Function usage adjusted according to new signature Co-authored-by: あく --- applications/main/infrared/infrared_app.c | 48 ++-- applications/main/infrared/infrared_app_i.h | 17 +- .../main/infrared/infrared_brute_force.c | 27 ++- .../main/infrared/infrared_brute_force.h | 5 +- applications/main/infrared/infrared_cli.c | 16 +- .../main/infrared/infrared_error_code.h | 45 ++++ applications/main/infrared/infrared_remote.c | 186 +++++++++------ applications/main/infrared/infrared_remote.h | 48 ++-- applications/main/infrared/infrared_signal.c | 217 +++++++++++++----- applications/main/infrared/infrared_signal.h | 26 ++- .../common/infrared_scene_universal_common.c | 8 +- .../scenes/infrared_scene_edit_delete.c | 46 ++-- .../scenes/infrared_scene_edit_move.c | 32 ++- .../scenes/infrared_scene_edit_rename.c | 35 ++- .../scenes/infrared_scene_learn_enter_name.c | 4 +- .../infrared/scenes/infrared_scene_remote.c | 3 +- .../scenes/infrared_scene_remote_list.c | 16 +- .../main/infrared/scenes/infrared_scene_rpc.c | 17 +- 18 files changed, 538 insertions(+), 258 deletions(-) create mode 100644 applications/main/infrared/infrared_error_code.h diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 4ddf0776bf8..a93fd766df3 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -297,7 +297,7 @@ static void infrared_free(InfraredApp* infrared) { free(infrared); } -bool infrared_add_remote_with_button( +InfraredErrorCode infrared_add_remote_with_button( const InfraredApp* infrared, const char* button_name, const InfraredSignal* signal) { @@ -310,21 +310,23 @@ bool infrared_add_remote_with_button( furi_string_cat_printf( new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break; - if(!infrared_remote_append_signal(remote, signal, button_name)) break; - success = true; + error = infrared_remote_create(remote, furi_string_get_cstr(new_path)); + if(INFRARED_ERROR_PRESENT(error)) break; + + error = infrared_remote_append_signal(remote, signal, button_name); } while(false); furi_string_free(new_name); furi_string_free(new_path); - return success; + return error; } -bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { +InfraredErrorCode + infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { InfraredRemote* remote = infrared->remote; const char* old_path = infrared_remote_get_path(remote); @@ -344,12 +346,13 @@ bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr)); furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION); - const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); + const InfraredErrorCode error = + infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); furi_string_free(new_name_fstr); furi_string_free(new_path_fstr); - return success; + return error; } void infrared_tx_start(InfraredApp* infrared) { @@ -382,15 +385,16 @@ void infrared_tx_start(InfraredApp* infrared) { infrared->app_state.is_transmitting = true; } -bool infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { +InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); - bool result = + InfraredErrorCode error = infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index); - if(result) { + + if(!INFRARED_ERROR_PRESENT(error)) { infrared_tx_start(infrared); } - return result; + return error; } void infrared_tx_stop(InfraredApp* infrared) { @@ -413,7 +417,7 @@ void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback call furi_thread_start(infrared->task_thread); } -bool infrared_blocking_task_finalize(InfraredApp* infrared) { +InfraredErrorCode infrared_blocking_task_finalize(InfraredApp* infrared) { furi_thread_join(infrared->task_thread); return furi_thread_get_return_code(infrared->task_thread); } @@ -563,10 +567,18 @@ int32_t infrared_app(void* p) { is_rpc_mode = true; } else { const char* file_path = (const char*)p; - is_remote_loaded = infrared_remote_load(infrared->remote, file_path); - - if(!is_remote_loaded) { - infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); + InfraredErrorCode error = infrared_remote_load(infrared->remote, file_path); + + if(!INFRARED_ERROR_PRESENT(error)) { + is_remote_loaded = true; + } else { + is_remote_loaded = false; + bool wrong_file_type = INFRARED_ERROR_CHECK(error, InfraredErrorCodeWrongFileType); + const char* format = wrong_file_type ? + "Library file\n\"%s\" can't be openned as a remote" : + "Failed to load\n\"%s\""; + + infrared_show_error_message(infrared, format, file_path); return -1; } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 002a8c2ee88..721771ef074 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -174,9 +174,9 @@ typedef enum { * @param[in] infrared pointer to the application instance. * @param[in] name pointer to a zero-terminated string containing the signal name. * @param[in] signal pointer to the signal to be added. - * @return true if the remote was successfully created, false otherwise. + * @return InfraredErrorCodeNone if the remote was successfully created, otherwise error code. */ -bool infrared_add_remote_with_button( +InfraredErrorCode infrared_add_remote_with_button( const InfraredApp* infrared, const char* name, const InfraredSignal* signal); @@ -186,9 +186,10 @@ bool infrared_add_remote_with_button( * * @param[in] infrared pointer to the application instance. * @param[in] new_name pointer to a zero-terminated string containing the new remote name. - * @return true if the remote was successfully renamed, false otherwise. + * @return InfraredErrorCodeNone if the remote was successfully renamed, otherwise error code. */ -bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); +InfraredErrorCode + infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); /** * @brief Begin transmission of the currently loaded signal. @@ -206,9 +207,9 @@ void infrared_tx_start(InfraredApp* infrared); * * @param[in,out] infrared pointer to the application instance. * @param[in] button_index index of the signal to be loaded. - * @returns true if the signal could be loaded, false otherwise. + * @returns InfraredErrorCodeNone if the signal could be loaded, otherwise error code. */ -bool infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); +InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); /** * @brief Stop transmission of the currently loaded signal. @@ -236,9 +237,9 @@ void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback call * (e.g. to display the results), the caller code MUST set it explicitly. * * @param[in,out] infrared pointer to the application instance. - * @return true if the blocking task finished successfully, false otherwise. + * @return InfraredErrorCodeNone if the blocking task finished successfully, otherwise error code. */ -bool infrared_blocking_task_finalize(InfraredApp* infrared); +InfraredErrorCode infrared_blocking_task_finalize(InfraredApp* infrared); /** * @brief Set the internal text store with formatted text. diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index b28918489dd..8c7422d5ef9 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -50,10 +50,10 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const brute_force->db_filename = db_filename; } -bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); @@ -61,12 +61,15 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { InfraredSignal* signal = infrared_signal_alloc(); do { - if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) break; + if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } bool signals_valid = false; - while(infrared_signal_read_name(ff, signal_name)) { - signals_valid = infrared_signal_read_body(signal, ff) && - infrared_signal_is_valid(signal); + while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) { + error = infrared_signal_read_body(signal, ff); + signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); if(!signals_valid) break; InfraredBruteForceRecord* record = @@ -75,9 +78,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { ++(record->count); } } - if(!signals_valid) break; - success = true; } while(false); infrared_signal_free(signal); @@ -85,7 +86,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } bool infrared_brute_force_start( @@ -139,10 +140,12 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); + const bool success = infrared_signal_search_by_name_and_read( - brute_force->current_signal, - brute_force->ff, - furi_string_get_cstr(brute_force->current_record_name)); + brute_force->current_signal, + brute_force->ff, + furi_string_get_cstr(brute_force->current_record_name)) == + InfraredErrorCodeNone; if(success) { infrared_signal_transmit(brute_force->current_signal); } diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 8eda08a63ec..8796422576b 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -10,6 +10,7 @@ #include #include +#include "infrared_error_code.h" /** * @brief InfraredBruteForce opaque type declaration. @@ -45,9 +46,9 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const * a infrared_brute_force_set_db_filename() call. * * @param[in,out] brute_force pointer to the instance to be updated. - * @returns true on success, false otherwise. + * @returns InfraredErrorCodeNone on success, otherwise error code. */ -bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); /** * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 00246a90f11..b700cf12150 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -232,9 +232,15 @@ static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { static bool infrared_cli_save_signal(InfraredSignal* signal, FlipperFormat* file, const char* name) { - bool ret = infrared_signal_save(signal, file, name); - if(!ret) { - printf("Failed to save signal: \"%s\"\r\n", name); + bool ret = true; + InfraredErrorCode error = infrared_signal_save(signal, file, name); + if(INFRARED_ERROR_PRESENT(error)) { + printf( + "Failed to save signal: \"%s\" code: 0x%X index: 0x%02X\r\n", + name, + INFRARED_ERROR_GET_CODE(error), + INFRARED_ERROR_GET_INDEX(error)); + ret = false; } return ret; } @@ -296,7 +302,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o FuriString* tmp; tmp = furi_string_alloc(); - while(infrared_signal_read(signal, input_file, tmp)) { + while(infrared_signal_read(signal, input_file, tmp) == InfraredErrorCodeNone) { ret = false; if(!infrared_signal_is_valid(signal)) { printf("Invalid signal\r\n"); @@ -464,7 +470,7 @@ static void printf("Missing signal name.\r\n"); break; } - if(!infrared_brute_force_calculate_messages(brute_force)) { + if(infrared_brute_force_calculate_messages(brute_force) != InfraredErrorCodeNone) { printf("Invalid remote name.\r\n"); break; } diff --git a/applications/main/infrared/infrared_error_code.h b/applications/main/infrared/infrared_error_code.h new file mode 100644 index 00000000000..2af06bea128 --- /dev/null +++ b/applications/main/infrared/infrared_error_code.h @@ -0,0 +1,45 @@ +#pragma once + +typedef enum { + InfraredErrorCodeNone = 0, + InfraredErrorCodeFileOperationFailed = 0x800000, + InfraredErrorCodeWrongFileType = 0x80000100, + InfraredErrorCodeWrongFileVersion = 0x80000200, + + //Common signal errors + InfraredErrorCodeSignalTypeUnknown = 0x80000300, + InfraredErrorCodeSignalNameNotFound = 0x80000400, + InfraredErrorCodeSignalUnableToReadType = 0x80000500, + InfraredErrorCodeSignalUnableToWriteType = 0x80000600, + + //Raw signal errors + InfraredErrorCodeSignalRawUnableToReadFrequency = 0x80000700, + InfraredErrorCodeSignalRawUnableToReadDutyCycle = 0x80000800, + InfraredErrorCodeSignalRawUnableToReadTimingsSize = 0x80000900, + InfraredErrorCodeSignalRawUnableToReadTooLongData = 0x80000A00, + InfraredErrorCodeSignalRawUnableToReadData = 0x80000B00, + + InfraredErrorCodeSignalRawUnableToWriteFrequency = 0x80000C00, + InfraredErrorCodeSignalRawUnableToWriteDutyCycle = 0x80000D00, + InfraredErrorCodeSignalRawUnableToWriteData = 0x80000E00, + + //Message signal errors + InfraredErrorCodeSignalMessageUnableToReadProtocol = 0x80000F00, + InfraredErrorCodeSignalMessageUnableToReadAddress = 0x80001000, + InfraredErrorCodeSignalMessageUnableToReadCommand = 0x80001100, + InfraredErrorCodeSignalMessageIsInvalid = 0x80001200, + + InfraredErrorCodeSignalMessageUnableToWriteProtocol = 0x80001300, + InfraredErrorCodeSignalMessageUnableToWriteAddress = 0x80001400, + InfraredErrorCodeSignalMessageUnableToWriteCommand = 0x80001500, +} InfraredErrorCode; + +#define INFRARED_ERROR_CODE_MASK (0xFFFFFF00) +#define INFRARED_ERROR_INDEX_MASK (0x000000FF) + +#define INFRARED_ERROR_GET_CODE(error) (error & INFRARED_ERROR_CODE_MASK) +#define INFRARED_ERROR_GET_INDEX(error) (error & INFRARED_ERROR_INDEX_MASK) +#define INFRARED_ERROR_SET_INDEX(code, index) (code |= (index & INFRARED_ERROR_INDEX_MASK)) + +#define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone) +#define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == test_code) diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 11bbf197bc8..9da08cbd41d 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -8,8 +8,9 @@ #define TAG "InfraredRemote" -#define INFRARED_FILE_HEADER "IR signals file" -#define INFRARED_FILE_VERSION (1) +#define INFRARED_FILE_HEADER "IR signals file" +#define INFRARED_LIBRARY_HEADER "IR library file" +#define INFRARED_FILE_VERSION (1) ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575 @@ -34,7 +35,7 @@ typedef struct { const InfraredSignal* signal; } InfraredBatchTarget; -typedef bool ( +typedef InfraredErrorCode ( *InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target); InfraredRemote* infrared_remote_alloc(void) { @@ -80,7 +81,7 @@ const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t return *StringArray_cget(remote->signal_names, index); } -bool infrared_remote_load_signal( +InfraredErrorCode infrared_remote_load_signal( const InfraredRemote* remote, InfraredSignal* signal, size_t index) { @@ -89,25 +90,27 @@ bool infrared_remote_load_signal( Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { const char* path = furi_string_get_cstr(remote->path); - if(!flipper_format_buffered_file_open_existing(ff, path)) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } - if(!infrared_signal_search_by_index_and_read(signal, ff, index)) { + error = infrared_signal_search_by_index_and_read(signal, ff, index); + if(INFRARED_ERROR_PRESENT(error)) { const char* signal_name = infrared_remote_get_signal_name(remote, index); FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path); break; } - - success = true; } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } bool infrared_remote_get_signal_index( @@ -128,31 +131,35 @@ bool infrared_remote_get_signal_index( return false; } -bool infrared_remote_append_signal( +InfraredErrorCode infrared_remote_append_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; const char* path = furi_string_get_cstr(remote->path); do { - if(!flipper_format_file_open_append(ff, path)) break; - if(!infrared_signal_save(signal, ff, name)) break; + if(!flipper_format_file_open_append(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } + + error = infrared_signal_save(signal, ff, name); + if(INFRARED_ERROR_PRESENT(error)) break; StringArray_push_back(remote->signal_names, name); - success = true; } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } -static bool infrared_remote_batch_start( +static InfraredErrorCode infrared_remote_batch_start( InfraredRemote* remote, InfraredBatchCallback batch_callback, const InfraredBatchTarget* target) { @@ -179,33 +186,59 @@ static bool infrared_remote_batch_start( status = storage_common_stat(storage, path_out, NULL); } while(status == FSE_OK || status == FSE_EXIST); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; + StringArray_t buf_names; + StringArray_init_set(buf_names, remote->signal_names); do { - if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break; - if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break; - if(!flipper_format_write_header_cstr( - batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in) || + !flipper_format_buffered_file_open_always(batch_context.ff_out, path_out) || + !flipper_format_write_header_cstr( + batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) { + error = InfraredErrorCodeFileOperationFailed; break; - + } const size_t signal_count = infrared_remote_get_signal_count(remote); for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) { - if(!infrared_signal_read( - batch_context.signal, batch_context.ff_in, batch_context.signal_name)) + error = infrared_signal_read( + batch_context.signal, batch_context.ff_in, batch_context.signal_name); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, batch_context.signal_index); break; - if(!batch_callback(&batch_context, target)) break; - } + } - if(batch_context.signal_index != signal_count) break; + error = batch_callback(&batch_context, target); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, batch_context.signal_index); + break; + } + } + if(INFRARED_ERROR_PRESENT(error)) break; - if(!flipper_format_buffered_file_close(batch_context.ff_out)) break; - if(!flipper_format_buffered_file_close(batch_context.ff_in)) break; + if(!flipper_format_buffered_file_close(batch_context.ff_out) || + !flipper_format_buffered_file_close(batch_context.ff_in)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } const FS_Error status = storage_common_rename(storage, path_out, path_in); - success = (status == FSE_OK || status == FSE_EXIST); + error = (status == FSE_OK || status == FSE_EXIST) ? InfraredErrorCodeNone : + InfraredErrorCodeFileOperationFailed; } while(false); + if(INFRARED_ERROR_PRESENT(error)) { + //Remove all temp data and rollback signal names + flipper_format_buffered_file_close(batch_context.ff_out); + flipper_format_buffered_file_close(batch_context.ff_in); + status = storage_common_stat(storage, path_out, NULL); + if(status == FSE_OK || status == FSE_EXIST) storage_common_remove(storage, path_out); + + StringArray_reset(remote->signal_names); + StringArray_set(remote->signal_names, buf_names); + } + + StringArray_clear(buf_names); infrared_signal_free(batch_context.signal); furi_string_free(batch_context.signal_name); flipper_format_free(batch_context.ff_out); @@ -214,15 +247,18 @@ static bool infrared_remote_batch_start( furi_record_close(RECORD_STORAGE); - return success; + return error; } -static bool infrared_remote_insert_signal_callback( +static InfraredErrorCode infrared_remote_insert_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { // Insert a signal under the specified index if(batch->signal_index == target->signal_index) { - if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false; + InfraredErrorCode error = + infrared_signal_save(target->signal, batch->ff_out, target->signal_name); + if(INFRARED_ERROR_PRESENT(error)) return error; + StringArray_push_at( batch->remote->signal_names, target->signal_index, target->signal_name); } @@ -232,7 +268,7 @@ static bool infrared_remote_insert_signal_callback( batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); } -bool infrared_remote_insert_signal( +InfraredErrorCode infrared_remote_insert_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name, @@ -251,7 +287,7 @@ bool infrared_remote_insert_signal( remote, infrared_remote_insert_signal_callback, &insert_target); } -static bool infrared_remote_rename_signal_callback( +static InfraredErrorCode infrared_remote_rename_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { const char* signal_name; @@ -268,7 +304,8 @@ static bool infrared_remote_rename_signal_callback( return infrared_signal_save(batch->signal, batch->ff_out, signal_name); } -bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { +InfraredErrorCode + infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { furi_assert(index < infrared_remote_get_signal_count(remote)); const InfraredBatchTarget rename_target = { @@ -281,7 +318,7 @@ bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const c remote, infrared_remote_rename_signal_callback, &rename_target); } -static bool infrared_remote_delete_signal_callback( +static InfraredErrorCode infrared_remote_delete_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { if(batch->signal_index == target->signal_index) { @@ -294,10 +331,10 @@ static bool infrared_remote_delete_signal_callback( batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); } - return true; + return InfraredErrorCodeNone; } -bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { +InfraredErrorCode infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { furi_assert(index < infrared_remote_get_signal_count(remote)); const InfraredBatchTarget delete_target = { @@ -310,33 +347,35 @@ bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { remote, infrared_remote_delete_signal_callback, &delete_target); } -bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { +InfraredErrorCode + infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { const size_t signal_count = infrared_remote_get_signal_count(remote); furi_assert(index < signal_count); furi_assert(new_index < signal_count); - if(index == new_index) return true; + InfraredErrorCode error = InfraredErrorCodeNone; + if(index == new_index) return error; InfraredSignal* signal = infrared_signal_alloc(); char* signal_name = strdup(infrared_remote_get_signal_name(remote, index)); - bool success = false; - do { - if(!infrared_remote_load_signal(remote, signal, index)) break; - if(!infrared_remote_delete_signal(remote, index)) break; - if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break; + error = infrared_remote_load_signal(remote, signal, index); + if(INFRARED_ERROR_PRESENT(error)) break; - success = true; + error = infrared_remote_delete_signal(remote, index); + if(INFRARED_ERROR_PRESENT(error)) break; + + error = infrared_remote_insert_signal(remote, signal, signal_name, new_index); } while(false); free(signal_name); infrared_signal_free(signal); - return success; + return error; } -bool infrared_remote_create(InfraredRemote* remote, const char* path) { +InfraredErrorCode infrared_remote_create(InfraredRemote* remote, const char* path) { FURI_LOG_I(TAG, "Creating new file: '%s'", path); infrared_remote_reset(remote); @@ -358,45 +397,64 @@ bool infrared_remote_create(InfraredRemote* remote, const char* path) { flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } -bool infrared_remote_load(InfraredRemote* remote, const char* path) { +InfraredErrorCode infrared_remote_load(InfraredRemote* remote, const char* path) { FURI_LOG_I(TAG, "Loading file: '%s'", path); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); FuriString* tmp = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_buffered_file_open_existing(ff, path)) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } uint32_t version; - if(!flipper_format_read_header(ff, tmp, &version)) break; + if(!flipper_format_read_header(ff, tmp, &version)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } + + if(furi_string_equal(tmp, INFRARED_LIBRARY_HEADER)) { + FURI_LOG_E(TAG, "Library file can't be loaded in this context"); + error = InfraredErrorCodeWrongFileType; + break; + } + + if(!furi_string_equal(tmp, INFRARED_FILE_HEADER)) { + error = InfraredErrorCodeWrongFileType; + FURI_LOG_E(TAG, "Filetype unknown"); + break; + } - if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION)) + if(version != INFRARED_FILE_VERSION) { + error = InfraredErrorCodeWrongFileVersion; + FURI_LOG_E(TAG, "Wrong file version"); break; + } infrared_remote_set_path(remote, path); StringArray_reset(remote->signal_names); - while(infrared_signal_read_name(ff, tmp)) { + while(infrared_signal_read_name(ff, tmp) == InfraredErrorCodeNone) { StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp)); } - - success = true; } while(false); furi_string_free(tmp); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } -bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { +InfraredErrorCode infrared_remote_rename(InfraredRemote* remote, const char* new_path) { const char* old_path = infrared_remote_get_path(remote); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -409,10 +467,10 @@ bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { infrared_remote_set_path(remote, new_path); } - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } -bool infrared_remote_remove(InfraredRemote* remote) { +InfraredErrorCode infrared_remote_remove(InfraredRemote* remote) { Storage* storage = furi_record_open(RECORD_STORAGE); const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote)); furi_record_close(RECORD_STORAGE); @@ -423,5 +481,5 @@ bool infrared_remote_remove(InfraredRemote* remote) { infrared_remote_reset(remote); } - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 4604a25ef0f..14416cd6598 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -105,12 +105,10 @@ bool infrared_remote_get_signal_index( * @param[in] remote pointer to the instance to load from. * @param[out] signal pointer to the signal to load into. Must be allocated. * @param[in] index index of the signal to be loaded. Must be less than the total signal count. - * @return true if the signal was successfully loaded, false otherwise. + * @return InfraredErrorCodeNone if the signal was successfully loaded, otherwise error code. */ -bool infrared_remote_load_signal( - const InfraredRemote* remote, - InfraredSignal* signal, - size_t index); +InfraredErrorCode + infrared_remote_load_signal(const InfraredRemote* remote, InfraredSignal* signal, size_t index); /** * @brief Append a signal to the file associated with an InfraredRemote instance. @@ -121,9 +119,9 @@ bool infrared_remote_load_signal( * @param[in,out] remote pointer to the instance to append to. * @param[in] signal pointer to the signal to be appended. * @param[in] name pointer to a zero-terminated string containing the name of the signal. - * @returns true if the signal was successfully appended, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully appended, otherwise error code. */ -bool infrared_remote_append_signal( +InfraredErrorCode infrared_remote_append_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name); @@ -141,9 +139,10 @@ bool infrared_remote_append_signal( * @param[in] signal pointer to the signal to be inserted. * @param[in] name pointer to a zero-terminated string containing the name of the signal. * @param[in] index the index under which the signal shall be inserted. - * @returns true if the signal was successfully inserted, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully inserted, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_insert_signal( +InfraredErrorCode infrared_remote_insert_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name, @@ -157,9 +156,10 @@ bool infrared_remote_insert_signal( * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be renamed. Must be less than the total signal count. * @param[in] new_name pointer to a zero-terminated string containig the signal's new name. - * @returns true if the signal was successfully renamed, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully renamed, otherwise error code. */ -bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); +InfraredErrorCode + infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); /** * @brief Change a signal's position in the file associated with an InfraredRemote instance. @@ -169,17 +169,21 @@ bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const c * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be moved. Must be less than the total signal count. * @param[in] new_index index of the signal to be moved. Must be less than the total signal count. + * @returns InfraredErrorCodeNone if the signal was moved successfully, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); +InfraredErrorCode + infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); /** * @brief Delete a signal in the file associated with an InfraredRemote instance. * * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be deleted. Must be less than the total signal count. - * @returns true if the signal was successfully deleted, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully deleted, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); +InfraredErrorCode infrared_remote_delete_signal(InfraredRemote* remote, size_t index); /** * @brief Create a new file and associate it with an InfraredRemote instance. @@ -188,9 +192,9 @@ bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); * * @param[in,out] remote pointer to the instance to be assigned with a new file. * @param[in] path pointer to a zero-terminated string containing the full file path. - * @returns true if the file was successfully created, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully created, otherwise error code. */ -bool infrared_remote_create(InfraredRemote* remote, const char* path); +InfraredErrorCode infrared_remote_create(InfraredRemote* remote, const char* path); /** * @brief Associate an InfraredRemote instance with a file and load the signal names from it. @@ -200,9 +204,9 @@ bool infrared_remote_create(InfraredRemote* remote, const char* path); * * @param[in,out] remote pointer to the instance to be assigned with an existing file. * @param[in] path pointer to a zero-terminated string containing the full file path. - * @returns true if the file was successfully loaded, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully loaded, otherwise error code. */ -bool infrared_remote_load(InfraredRemote* remote, const char* path); +InfraredErrorCode infrared_remote_load(InfraredRemote* remote, const char* path); /** * @brief Rename the file associated with an InfraredRemote instance. @@ -211,9 +215,9 @@ bool infrared_remote_load(InfraredRemote* remote, const char* path); * * @param[in,out] remote pointer to the instance to be modified. * @param[in] new_path pointer to a zero-terminated string containing the new full file path. - * @returns true if the file was successfully renamed, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully renamed, otherwise error code. */ -bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); +InfraredErrorCode infrared_remote_rename(InfraredRemote* remote, const char* new_path); /** * @brief Remove the file associated with an InfraredRemote instance. @@ -224,6 +228,6 @@ bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); * infrared_remote_create() or infrared_remote_load() are successfully executed. * * @param[in,out] remote pointer to the instance to be modified. - * @returns true if the file was successfully removed, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully removed, otherwise error code. */ -bool infrared_remote_remove(InfraredRemote* remote); +InfraredErrorCode infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index a4cb28bf0ed..75643f1d3b1 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -101,104 +101,177 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { return true; } -static inline bool +static inline InfraredErrorCode infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { const char* protocol_name = infrared_get_protocol_name(message->protocol); - return flipper_format_write_string_cstr( - ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) && - flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) && - flipper_format_write_hex( - ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4) && - flipper_format_write_hex( - ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4); + InfraredErrorCode error = InfraredErrorCodeNone; + do { + if(!flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED)) { + error = InfraredErrorCodeSignalUnableToWriteType; + break; + } + + if(!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name)) { + error = InfraredErrorCodeSignalMessageUnableToWriteProtocol; + break; + } + + if(!flipper_format_write_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4)) { + error = InfraredErrorCodeSignalMessageUnableToWriteAddress; + break; + } + + if(!flipper_format_write_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4)) { + error = InfraredErrorCodeSignalMessageUnableToWriteCommand; + break; + } + + } while(false); + + return error; } -static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { +static inline InfraredErrorCode + infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); - return flipper_format_write_string_cstr( - ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) && - flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) && - flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1) && - flipper_format_write_uint32( - ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size); + + InfraredErrorCode error = InfraredErrorCodeNone; + do { + if(!flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW)) { + error = InfraredErrorCodeSignalUnableToWriteType; + break; + } + + if(!flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1)) { + error = InfraredErrorCodeSignalRawUnableToWriteFrequency; + break; + } + + if(!flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1)) { + error = InfraredErrorCodeSignalRawUnableToWriteDutyCycle; + break; + } + + if(!flipper_format_write_uint32( + ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size)) { + error = InfraredErrorCodeSignalRawUnableToWriteData; + break; + } + } while(false); + return error; } -static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { +static inline InfraredErrorCode + infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { FuriString* buf; buf = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break; + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) { + error = InfraredErrorCodeSignalMessageUnableToReadProtocol; + break; + } InfraredMessage message; message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); - if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) + if(!flipper_format_read_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) { + error = InfraredErrorCodeSignalMessageUnableToReadAddress; + break; + } + if(!flipper_format_read_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) { + error = InfraredErrorCodeSignalMessageUnableToReadCommand; break; - if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) + } + + if(!infrared_signal_is_message_valid(&message)) { + error = InfraredErrorCodeSignalMessageIsInvalid; break; - if(!infrared_signal_is_message_valid(&message)) break; + } infrared_signal_set_message(signal, &message); - success = true; } while(false); furi_string_free(buf); - return success; + return error; } -static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { - bool success = false; +static inline InfraredErrorCode + infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { + InfraredErrorCode error = InfraredErrorCodeNone; do { uint32_t frequency; - if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break; + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) { + error = InfraredErrorCodeSignalRawUnableToReadFrequency; + break; + } float duty_cycle; - if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break; + if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) { + error = InfraredErrorCodeSignalRawUnableToReadDutyCycle; + break; + } uint32_t timings_size; - if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break; + if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) { + error = InfraredErrorCodeSignalRawUnableToReadTimingsSize; + break; + } - if(timings_size > MAX_TIMINGS_AMOUNT) break; + if(timings_size > MAX_TIMINGS_AMOUNT) { + error = InfraredErrorCodeSignalRawUnableToReadTooLongData; + break; + } uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) { + error = InfraredErrorCodeSignalRawUnableToReadData; free(timings); break; } + infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); free(timings); - success = true; + error = InfraredErrorCodeNone; } while(false); - return success; + return error; } -bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { +InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { FuriString* tmp = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break; + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) { + error = InfraredErrorCodeSignalUnableToReadType; + break; + } if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) { - if(!infrared_signal_read_raw(signal, ff)) break; + error = infrared_signal_read_raw(signal, ff); } else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) { - if(!infrared_signal_read_message(signal, ff)) break; + error = infrared_signal_read_message(signal, ff); } else { FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp)); + error = InfraredErrorCodeSignalTypeUnknown; break; } - - success = true; } while(false); furi_string_free(tmp); - return success; + + return error; } InfraredSignal* infrared_signal_alloc(void) { @@ -270,68 +343,88 @@ const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) return &signal->payload.message; } -bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { +InfraredErrorCode + infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { + InfraredErrorCode error = InfraredErrorCodeNone; + if(!flipper_format_write_comment_cstr(ff, "") || !flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) { - return false; + error = InfraredErrorCodeFileOperationFailed; } else if(signal->is_raw) { - return infrared_signal_save_raw(&signal->payload.raw, ff); + error = infrared_signal_save_raw(&signal->payload.raw, ff); } else { - return infrared_signal_save_message(&signal->payload.message, ff); + error = infrared_signal_save_message(&signal->payload.message, ff); } + + return error; } -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { - bool success = false; +InfraredErrorCode + infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!infrared_signal_read_name(ff, name)) break; - if(!infrared_signal_read_body(signal, ff)) break; + error = infrared_signal_read_name(ff, name); + if(INFRARED_ERROR_PRESENT(error)) break; - success = true; //-V779 + error = infrared_signal_read_body(signal, ff); } while(false); - return success; + return error; } -bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { - return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); +InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { + return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name) ? + InfraredErrorCodeNone : + InfraredErrorCodeSignalNameNotFound; } -bool infrared_signal_search_by_name_and_read( +InfraredErrorCode infrared_signal_search_by_name_and_read( InfraredSignal* signal, FlipperFormat* ff, const char* name) { - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; FuriString* tmp = furi_string_alloc(); - while(infrared_signal_read_name(ff, tmp)) { + do { + error = infrared_signal_read_name(ff, tmp); + if(INFRARED_ERROR_PRESENT(error)) break; + if(furi_string_equal(tmp, name)) { - success = infrared_signal_read_body(signal, ff); + error = infrared_signal_read_body(signal, ff); break; } - } + } while(true); furi_string_free(tmp); - return success; + return error; } -bool infrared_signal_search_by_index_and_read( +InfraredErrorCode infrared_signal_search_by_index_and_read( InfraredSignal* signal, FlipperFormat* ff, size_t index) { - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; FuriString* tmp = furi_string_alloc(); - for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) { + for(uint32_t i = 0;; ++i) { + error = infrared_signal_read_name(ff, tmp); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, i); + break; + } + if(i == index) { - success = infrared_signal_read_body(signal, ff); + error = infrared_signal_read_body(signal, ff); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, i); + } break; } } furi_string_free(tmp); - return success; + return error; } void infrared_signal_transmit(const InfraredSignal* signal) { diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index dcfde6e729a..3fa7768b30f 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -8,6 +8,7 @@ */ #pragma once +#include "infrared_error_code.h" #include #include @@ -136,9 +137,10 @@ const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. - * @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read). + * @returns InfraredErrorCodeNone if a signal was successfully read, otherwise error code */ -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); +InfraredErrorCode + infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); /** * @brief Read a signal name from a FlipperFormat file. @@ -147,9 +149,9 @@ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. - * @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read). + * @returns InfraredErrorCodeNone if a signal name was successfully read, otherwise error code */ -bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); +InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name); /** * @brief Read a signal from a FlipperFormat file. @@ -158,9 +160,9 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. - * @returns true if a signal body was successfully read, false otherwise (e.g. syntax error). + * @returns InfraredErrorCodeNone if a signal body was successfully read, otherwise error code. */ -bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); +InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); /** * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. @@ -171,9 +173,9 @@ bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[in] name pointer to a zero-terminated string containing the requested signal name. - * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + * @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code. */ -bool infrared_signal_search_by_name_and_read( +InfraredErrorCode infrared_signal_search_by_name_and_read( InfraredSignal* signal, FlipperFormat* ff, const char* name); @@ -187,9 +189,9 @@ bool infrared_signal_search_by_name_and_read( * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[in] index the requested signal index. - * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + * @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code. */ -bool infrared_signal_search_by_index_and_read( +InfraredErrorCode infrared_signal_search_by_index_and_read( InfraredSignal* signal, FlipperFormat* ff, size_t index); @@ -203,8 +205,10 @@ bool infrared_signal_search_by_index_and_read( * @param[in] signal pointer to the instance holding the signal to be saved. * @param[in,out] ff pointer to the FlipperFormat file instance to write to. * @param[in] name pointer to a zero-terminated string contating the name of the signal. + * @returns InfraredErrorCodeNone if a signal was successfully saved, otherwise error code */ -bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); +InfraredErrorCode + infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); /** * @brief Transmit a signal contained in an InfraredSignal instance. diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 9bdcdc4a823..a52f141c47f 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -34,12 +34,12 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { static int32_t infrared_scene_universal_common_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = infrared_brute_force_calculate_messages(infrared->brute_force); + const InfraredErrorCode error = infrared_brute_force_calculate_messages(infrared->brute_force); view_dispatcher_send_custom_event( infrared->view_dispatcher, infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); - return success; + return error; } void infrared_scene_universal_common_on_enter(void* context) { @@ -93,9 +93,9 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); } } else if(event_type == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); - if(!task_success) { + if(INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); } else { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 90a2633d338..63d71318c23 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -11,12 +11,12 @@ static int32_t infrared_scene_edit_delete_task_callback(void* context) { InfraredAppState* app_state = &infrared->app_state; const InfraredEditTarget edit_target = app_state->edit_target; - bool success; + InfraredErrorCode error = InfraredErrorCodeNone; if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); + error = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_remote_remove(infrared->remote); + error = infrared_remote_remove(infrared->remote); } else { furi_crash(); } @@ -24,7 +24,7 @@ static int32_t infrared_scene_edit_delete_task_callback(void* context) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_edit_delete_on_enter(void* context) { @@ -39,11 +39,15 @@ void infrared_scene_edit_delete_on_enter(void* context) { const int32_t current_button_index = infrared->app_state.current_button_index; furi_check(current_button_index != InfraredButtonIndexNone); - if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) { + InfraredErrorCode error = + infrared_remote_load_signal(remote, infrared->current_signal, current_button_index); + if(INFRARED_ERROR_PRESENT(error)) { + const char* format = + (INFRARED_ERROR_CHECK(error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) ? + "Failed to delete\n\"%s\" is too long.\nTry to edit file from pc" : + "Failed to load\n\"%s\""; infrared_show_error_message( - infrared, - "Failed to load\n\"%s\"", - infrared_remote_get_signal_name(remote, current_button_index)); + infrared, format, infrared_remote_get_signal_name(remote, current_button_index)); scene_manager_previous_scene(infrared->scene_manager); return; } @@ -107,18 +111,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); InfraredAppState* app_state = &infrared->app_state; - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { - const char* edit_target_text = - app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; - infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text); - - const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; + if(INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) { + const uint8_t index = INFRARED_ERROR_GET_INDEX(task_error); + const char* format = + "Failed to delete\n\"%s\" is too long.\nTry to edit file from pc"; + infrared_show_error_message( + infrared, + format, + infrared_remote_get_signal_name(infrared->remote, index)); + } else { + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message( + infrared, "Failed to\ndelete %s", edit_target_text); + } + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index fcac1fc15e0..1c6415c24cd 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -2,14 +2,14 @@ static int32_t infrared_scene_edit_move_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = infrared_remote_move_signal( + const InfraredErrorCode error = infrared_remote_move_signal( infrared->remote, infrared->app_state.prev_button_index, infrared->app_state.current_button_index); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } static void infrared_scene_edit_move_button_callback( @@ -51,14 +51,26 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); - - if(!task_success) { - const char* signal_name = infrared_remote_get_signal_name( - infrared->remote, infrared->app_state.current_button_index); - infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); - scene_manager_search_and_switch_to_previous_scene( - infrared->scene_manager, InfraredSceneRemoteList); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); + + if(INFRARED_ERROR_PRESENT(task_error)) { + const char* format = "Failed to move\n\"%s\""; + uint8_t signal_index = infrared->app_state.prev_button_index; + + if(INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) { + signal_index = INFRARED_ERROR_GET_INDEX(task_error); + format = "Failed to move\n\"%s\" is too long.\nTry to edit file from pc"; + } + furi_assert(format); + + const char* signal_name = + infrared_remote_get_signal_name(infrared->remote, signal_index); + infrared_show_error_message(infrared, format, signal_name); + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; + scene_manager_search_and_switch_to_previous_scene_one_of( + infrared->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } else { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index a546ad6e50d..301e936d9b9 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -8,13 +8,13 @@ static int32_t infrared_scene_edit_rename_task_callback(void* context) { InfraredAppState* app_state = &infrared->app_state; const InfraredEditTarget edit_target = app_state->edit_target; - bool success; + InfraredErrorCode error = InfraredErrorCodeNone; if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_rename_signal( + error = infrared_remote_rename_signal( infrared->remote, app_state->current_button_index, infrared->text_store[0]); } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_rename_current_remote(infrared, infrared->text_store[0]); + error = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { furi_crash(); } @@ -22,7 +22,7 @@ static int32_t infrared_scene_edit_rename_task_callback(void* context) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_edit_rename_on_enter(void* context) { @@ -89,17 +89,30 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); InfraredAppState* app_state = &infrared->app_state; - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { - const char* edit_target_text = - app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; - infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text); - scene_manager_search_and_switch_to_previous_scene( - scene_manager, InfraredSceneRemoteList); + bool long_signal = INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData); + + const char* format = "Failed to rename\n%s"; + const char* target = infrared->app_state.edit_target == InfraredEditTargetButton ? + "button" : + "file"; + if(long_signal) { + format = "Failed to rename\n\"%s\" is too long.\nTry to edit file from pc"; + target = infrared_remote_get_signal_name( + infrared->remote, INFRARED_ERROR_GET_INDEX(task_error)); + } + + infrared_show_error_message(infrared, format, target); + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; + scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } app_state->current_button_index = InfraredButtonIndexNone; diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index be46a869168..7ffc9644baf 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -41,12 +41,12 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { const char* signal_name = infrared->text_store[0]; - const bool success = + const InfraredErrorCode error = infrared->app_state.is_learning_new_remote ? infrared_add_remote_with_button(infrared, signal_name, signal) : infrared_remote_append_signal(infrared->remote, signal, signal_name); - if(success) { + if(!INFRARED_ERROR_PRESENT(error)) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); dolphin_deed(DolphinDeedIrSave); } else { diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index e1195c51635..6dd7aae8bf8 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -85,7 +85,8 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { if(custom_type == InfraredCustomEventTypeTransmitStarted) { furi_assert(button_index >= 0); - if(!infrared_tx_start_button_index(infrared, button_index)) { + InfraredErrorCode error = infrared_tx_start_button_index(infrared, button_index); + if(INFRARED_ERROR_PRESENT(error)) { infrared_show_error_message( infrared, "Failed to load\n\"%s\"", diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 9c880ac2fb9..93665769cf5 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -2,11 +2,11 @@ static int32_t infrared_scene_remote_list_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = + const InfraredErrorCode error = infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { @@ -38,13 +38,19 @@ bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); } else { + bool wrong_file_type = + INFRARED_ERROR_CHECK(task_error, InfraredErrorCodeWrongFileType); + const char* format = wrong_file_type ? + "Library file\n\"%s\" can't be openned as a remote" : + "Failed to load\n\"%s\""; + infrared_show_error_message( - infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path)); + infrared, format, furi_string_get_cstr(infrared->file_path)); infrared_scene_remote_list_select_and_load(infrared); } } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 16724766858..8f9dc433881 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -11,11 +11,11 @@ typedef enum { static int32_t infrared_scene_rpc_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = + const InfraredErrorCode error = infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_rpc_on_enter(void* context) { @@ -57,8 +57,11 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { } } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); - if(task_success) { + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); + + if(!INFRARED_ERROR_PRESENT(task_error)) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } else { @@ -71,7 +74,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { furi_string_free(str); } - rpc_system_app_confirm(infrared->rpc_ctx, task_success); + rpc_system_app_confirm(infrared->rpc_ctx, !INFRARED_ERROR_PRESENT(task_error)); } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { @@ -90,7 +93,9 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - if(infrared_tx_start_button_index(infrared, app_state->current_button_index)) { + InfraredErrorCode error = + infrared_tx_start_button_index(infrared, app_state->current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { const char* remote_name = infrared_remote_get_name(infrared->remote); infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name); From ad27713f0d0abc4d02b8867afea696158b9eb114 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:44:32 +0900 Subject: [PATCH 19/29] [FL-3766] Fix crash on Ultralight unlock (#3855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix crash on Ultralight unlock * Infrared: safe macroses Co-authored-by: あく --- .../main/infrared/infrared_error_code.h | 8 +++--- .../iso14443_3a/iso14443_3a_render.c | 2 +- .../iso14443_3a/iso14443_3a_render.h | 2 +- .../mf_ultralight/mf_ultralight_render.c | 25 ++++++++++++------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/applications/main/infrared/infrared_error_code.h b/applications/main/infrared/infrared_error_code.h index 2af06bea128..841721b1767 100644 --- a/applications/main/infrared/infrared_error_code.h +++ b/applications/main/infrared/infrared_error_code.h @@ -37,9 +37,9 @@ typedef enum { #define INFRARED_ERROR_CODE_MASK (0xFFFFFF00) #define INFRARED_ERROR_INDEX_MASK (0x000000FF) -#define INFRARED_ERROR_GET_CODE(error) (error & INFRARED_ERROR_CODE_MASK) -#define INFRARED_ERROR_GET_INDEX(error) (error & INFRARED_ERROR_INDEX_MASK) -#define INFRARED_ERROR_SET_INDEX(code, index) (code |= (index & INFRARED_ERROR_INDEX_MASK)) +#define INFRARED_ERROR_GET_CODE(error) ((error) & INFRARED_ERROR_CODE_MASK) +#define INFRARED_ERROR_GET_INDEX(error) ((error) & INFRARED_ERROR_INDEX_MASK) +#define INFRARED_ERROR_SET_INDEX(code, index) ((code) |= ((index) & INFRARED_ERROR_INDEX_MASK)) #define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone) -#define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == test_code) +#define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == (test_code)) diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c index 810242fbc6a..6ceb4bce028 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -1,6 +1,6 @@ #include "iso14443_3a_render.h" -void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size) { +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* data, size_t size) { for(size_t i = 0; i < size; i++) { furi_string_cat_printf(str, " %02X", data[i]); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h index 34e347aa35e..b8151556631 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -11,7 +11,7 @@ void nfc_render_iso14443_3a_info( void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str); -void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* data, size_t size); void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index c4ad67ff8bc..ef83d194279 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -10,22 +10,29 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F } void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { + MfUltralightConfigPages* config; + bool all_pages = mf_ultralight_is_all_data_read(data); - if(all_pages) { + bool has_config = mf_ultralight_get_config_page(data, &config); + + if(!has_config) { + furi_string_cat_printf(str, "\e#Already Unlocked!"); + } else if(all_pages) { furi_string_cat_printf(str, "\e#All Pages Are Unlocked!"); } else { furi_string_cat_printf(str, "\e#Some Pages Are Locked!"); } - MfUltralightConfigPages* config; - mf_ultralight_get_config_page(data, &config); + if(has_config) { + furi_string_cat_printf(str, "\nPassword: "); + nfc_render_iso14443_3a_format_bytes( + str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); - furi_string_cat_printf(str, "\nPassword: "); - nfc_render_iso14443_3a_format_bytes( - str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); - - furi_string_cat_printf(str, "\nPACK: "); - nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + furi_string_cat_printf(str, "\nPACK: "); + nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + } else { + furi_string_cat_printf(str, "\nThis card does not support\npassword protection!"); + } nfc_render_mf_ultralight_pages_count(data, str); } From 8caa861cc77a050bfa3a686fff75eb1ed9f95e3c Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 6 Sep 2024 15:11:05 +0200 Subject: [PATCH 20/29] Exposed `view_dispatcher_get_event_loop` (#3858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Exposed view_dispatcher_get_event_loop * Bump api symbols Co-authored-by: あく --- targets/f18/api_symbols.csv | 4 ++-- targets/f7/api_symbols.csv | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b603813a773..12e5b97e159 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.2,, +Version,+,72.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,, @@ -2759,7 +2759,7 @@ Function,+,view_dispatcher_alloc,ViewDispatcher*, Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* -Function,-,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* +Function,+,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" Function,+,view_dispatcher_run,void,ViewDispatcher* Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1d5b98fe3d2..850c84fe00f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.2,, +Version,+,72.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,, @@ -3600,7 +3600,7 @@ Function,+,view_dispatcher_alloc,ViewDispatcher*, Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* -Function,-,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* +Function,+,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" Function,+,view_dispatcher_run,void,ViewDispatcher* Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" From 3c75356b497222d9eca7c95896a1693296b2c558 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 7 Sep 2024 20:12:06 +0900 Subject: [PATCH 21/29] Fix detection of GProx II cards and false detection of other cards (#3869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix detection of GProx II cards and false detection of other cards as GProx II * Fix incorrect parity starting bit Co-authored-by: あく --- lib/lfrfid/protocols/protocol_gproxii.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_gproxii.c b/lib/lfrfid/protocols/protocol_gproxii.c index 73cbe8f39e0..999e965ecb0 100644 --- a/lib/lfrfid/protocols/protocol_gproxii.c +++ b/lib/lfrfid/protocols/protocol_gproxii.c @@ -39,11 +39,12 @@ void protocol_gproxii_free(ProtocolGProxII* protocol) { } uint8_t* protocol_gproxii_get_data(ProtocolGProxII* proto) { - return proto->data; + return proto->decoded_data; } void protocol_gproxii_decoder_start(ProtocolGProxII* protocol) { memset(protocol->data, 0, GPROXII_ENCODED_BYTE_FULL_SIZE); + memset(protocol->decoded_data, 0, GPROXII_DATA_SIZE); protocol->last_short = false; } @@ -88,8 +89,9 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { if(bit_lib_get_bits(protocol->data, 0, 6) != 0b111110) return false; // Check always 0 parity on every 5th bit after preamble - if(bit_lib_test_parity(protocol->data, 5, GPROXII_ENCODED_BIT_SIZE, BitLibParityAlways0, 5)) + if(!bit_lib_test_parity(protocol->data, 6, GPROXII_ENCODED_BIT_SIZE, BitLibParityAlways0, 5)) { return false; + } // Start GProx II decode bit_lib_copy_bits(protocol->decoded_data, 0, GPROXII_ENCODED_BIT_SIZE, protocol->data, 6); From 266d4b3234519cc7627194ef2e4ab76732013df5 Mon Sep 17 00:00:00 2001 From: porta Date: Sat, 7 Sep 2024 14:54:23 +0300 Subject: [PATCH 22/29] [FL-3897] Happy mode (#3863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: happy mode * feat: remove sad dolphin when powering off in happy mode * style: address review comments * Dolphin: add missing furi_checks * Komi: add missing region initialization on startup Co-authored-by: あく --- applications/services/dolphin/dolphin.c | 33 +++++++++- applications/services/dolphin/dolphin.h | 8 +++ applications/services/dolphin/dolphin_i.h | 3 + .../services/dolphin/helpers/dolphin_state.h | 4 ++ .../desktop_settings/desktop_settings_app.c | 6 ++ .../desktop_settings/desktop_settings_app.h | 3 + .../desktop_settings_custom_event.h | 26 ++++++++ .../scenes/desktop_settings_scene_config.h | 2 + .../desktop_settings_scene_happy_mode.c | 64 +++++++++++++++++++ .../scenes/desktop_settings_scene_pin_auth.c | 19 +++--- .../desktop_settings_scene_pin_disable.c | 7 +- .../scenes/desktop_settings_scene_pin_error.c | 9 ++- .../scenes/desktop_settings_scene_pin_menu.c | 17 ++--- .../scenes/desktop_settings_scene_pin_setup.c | 25 ++++---- .../desktop_settings_scene_pin_setup_done.c | 7 +- .../desktop_settings_scene_pin_setup_howto.c | 7 +- .../desktop_settings_scene_pin_setup_howto2.c | 12 ++-- .../desktop_settings_scene_quick_apps_menu.c | 12 ++-- .../scenes/desktop_settings_scene_start.c | 9 ++- .../scenes/power_settings_scene_power_off.c | 21 ++++-- targets/f18/api_symbols.csv | 4 +- targets/f18/furi_hal/furi_hal.c | 1 + targets/f7/api_symbols.csv | 4 +- 23 files changed, 231 insertions(+), 72 deletions(-) create mode 100644 applications/settings/desktop_settings/desktop_settings_custom_event.h create mode 100644 applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 501e37c3c8c..5d8dc61cb9f 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -47,6 +47,26 @@ void dolphin_deed(DolphinDeed deed) { furi_record_close(RECORD_DOLPHIN); } +void dolphin_get_settings(Dolphin* dolphin, DolphinSettings* settings) { + furi_check(dolphin); + furi_check(settings); + + DolphinEvent event; + event.type = DolphinEventTypeSettingsGet; + event.settings = settings; + dolphin_event_send_wait(dolphin, &event); +} + +void dolphin_set_settings(Dolphin* dolphin, DolphinSettings* settings) { + furi_check(dolphin); + furi_check(settings); + + DolphinEvent event; + event.type = DolphinEventTypeSettingsSet; + event.settings = settings; + dolphin_event_send_wait(dolphin, &event); +} + DolphinStats dolphin_stats(Dolphin* dolphin) { furi_check(dolphin); @@ -211,7 +231,9 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; - event.stats->butthurt = dolphin->state->data.butthurt; + event.stats->butthurt = (dolphin->state->data.flags & DolphinFlagHappyMode) ? + 0 : + dolphin->state->data.butthurt; event.stats->timestamp = dolphin->state->data.timestamp; event.stats->level = dolphin_get_level(dolphin->state->data.icounter); event.stats->level_up_is_pending = @@ -228,6 +250,15 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { dolphin_state_load(dolphin->state); furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); + } else if(event.type == DolphinEventTypeSettingsGet) { + event.settings->happy_mode = dolphin->state->data.flags & DolphinFlagHappyMode; + + } else if(event.type == DolphinEventTypeSettingsSet) { + dolphin->state->data.flags &= ~DolphinFlagHappyMode; + if(event.settings->happy_mode) dolphin->state->data.flags |= DolphinFlagHappyMode; + dolphin->state->dirty = true; + dolphin_state_save(dolphin->state); + } else { furi_crash(); } diff --git a/applications/services/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h index 01da7f3f239..95f851a51be 100644 --- a/applications/services/dolphin/dolphin.h +++ b/applications/services/dolphin/dolphin.h @@ -21,6 +21,10 @@ typedef struct { bool level_up_is_pending; } DolphinStats; +typedef struct { + bool happy_mode; +} DolphinSettings; + typedef enum { DolphinPubsubEventUpdate, } DolphinPubsubEvent; @@ -31,6 +35,10 @@ typedef enum { */ void dolphin_deed(DolphinDeed deed); +void dolphin_get_settings(Dolphin* dolphin, DolphinSettings* settings); + +void dolphin_set_settings(Dolphin* dolphin, DolphinSettings* settings); + /** Retrieve dolphin stats * Thread safe, blocking */ diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index 6a6b3dfd814..8e8d40825dd 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -13,6 +13,8 @@ typedef enum { DolphinEventTypeFlush, DolphinEventTypeLevel, DolphinEventTypeReloadState, + DolphinEventTypeSettingsGet, + DolphinEventTypeSettingsSet, } DolphinEventType; typedef struct { @@ -21,6 +23,7 @@ typedef struct { union { DolphinDeed deed; DolphinStats* stats; + DolphinSettings* settings; }; } DolphinEvent; diff --git a/applications/services/dolphin/helpers/dolphin_state.h b/applications/services/dolphin/helpers/dolphin_state.h index bdbd98d4737..23a7d7b5f53 100644 --- a/applications/services/dolphin/helpers/dolphin_state.h +++ b/applications/services/dolphin/helpers/dolphin_state.h @@ -5,6 +5,10 @@ #include "dolphin_deed.h" +typedef enum { + DolphinFlagHappyMode = 1, +} DolphinFlags; + typedef struct DolphinState DolphinState; typedef struct { uint8_t icounter_daily_limit[DolphinAppMAX]; diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index ab7782a7c4a..e8bc8995782 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) { app->pin_input_view = desktop_view_pin_input_alloc(); app->pin_setup_howto_view = desktop_settings_view_pin_setup_howto_alloc(); app->pin_setup_howto2_view = desktop_settings_view_pin_setup_howto2_alloc(); + app->dialog_ex = dialog_ex_alloc(); view_dispatcher_add_view( app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); @@ -63,6 +65,8 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) { app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2, desktop_settings_view_pin_setup_howto2_get_view(app->pin_setup_howto2_view)); + view_dispatcher_add_view( + app->view_dispatcher, DesktopSettingsAppViewDialogEx, dialog_ex_get_view(app->dialog_ex)); return app; } @@ -75,12 +79,14 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); variable_item_list_free(app->variable_item_list); submenu_free(app->submenu); popup_free(app->popup); desktop_view_pin_input_free(app->pin_input_view); desktop_settings_view_pin_setup_howto_free(app->pin_setup_howto_view); desktop_settings_view_pin_setup_howto2_free(app->pin_setup_howto2_view); + dialog_ex_free(app->dialog_ex); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 348180fbf2f..18bc9fa28e9 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ typedef enum { DesktopSettingsAppViewIdPinInput, DesktopSettingsAppViewIdPinSetupHowto, DesktopSettingsAppViewIdPinSetupHowto2, + DesktopSettingsAppViewDialogEx, } DesktopSettingsAppView; typedef struct { @@ -36,6 +38,7 @@ typedef struct { DesktopViewPinInput* pin_input_view; DesktopSettingsViewPinSetupHowto* pin_setup_howto_view; DesktopSettingsViewPinSetupHowto2* pin_setup_howto2_view; + DialogEx* dialog_ex; DesktopPinCode pincode_buffer; bool pincode_buffer_filled; diff --git a/applications/settings/desktop_settings/desktop_settings_custom_event.h b/applications/settings/desktop_settings/desktop_settings_custom_event.h new file mode 100644 index 00000000000..87978069e06 --- /dev/null +++ b/applications/settings/desktop_settings/desktop_settings_custom_event.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + // reserve 100 for button presses, submenu selections, etc. + DesktopSettingsCustomEventExit = 100, + DesktopSettingsCustomEventDone, + + DesktopSettingsCustomEvent1stPinEntered, + DesktopSettingsCustomEventPinsEqual, + DesktopSettingsCustomEventPinsDifferent, + + DesktopSettingsCustomEventSetPin, + DesktopSettingsCustomEventChangePin, + DesktopSettingsCustomEventDisablePin, + + DesktopSettingsCustomEventSetDefault, + DesktopSettingsCustomEventSetDummy, +} DesktopSettingsCustomEvent; + +#ifdef __cplusplus +} +#endif 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 4a1d0370162..37d845625be 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -12,3 +12,5 @@ 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) + +ADD_SCENE(desktop_settings, happy_mode, HappyMode) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c new file mode 100644 index 00000000000..31fcbfd2a9f --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +#include "desktop_settings_scene.h" +#include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" + +static void desktop_settings_scene_happy_mode_done_callback(DialogExResult result, void* context) { + DesktopSettingsApp* app = context; + DolphinSettings settings; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + dolphin_get_settings(dolphin, &settings); + settings.happy_mode = (result == DialogExResultRight); + dolphin_set_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); +} + +void desktop_settings_scene_happy_mode_on_enter(void* context) { + DesktopSettingsApp* app = context; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + DolphinSettings settings; + dolphin_get_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); + + dialog_ex_set_header(app->dialog_ex, "Happy Mode", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + app->dialog_ex, + "I will never get angry at you\nfor not spending time with me\nas long as this mode is enabled", + 64, + 30, + AlignCenter, + AlignCenter); + dialog_ex_set_left_button_text(app->dialog_ex, settings.happy_mode ? "Disable" : "Go back"); + dialog_ex_set_right_button_text( + app->dialog_ex, settings.happy_mode ? "Keep enabled" : "Enable"); + dialog_ex_set_result_callback(app->dialog_ex, desktop_settings_scene_happy_mode_done_callback); + dialog_ex_set_context(app->dialog_ex, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); +} + +bool desktop_settings_scene_happy_mode_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopSettingsCustomEventExit: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + default: + furi_crash(); + } + } + return consumed; +} + +void desktop_settings_scene_happy_mode_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index 1e64165314c..0d154335946 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -3,15 +3,12 @@ #include #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#define SCENE_EVENT_EXIT (0U) -#define SCENE_EVENT_PINS_EQUAL (1U) -#define SCENE_EVENT_PINS_DIFFERENT (2U) - static void pin_auth_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); furi_assert(context); @@ -20,15 +17,17 @@ static void pin_auth_done_callback(const DesktopPinCode* pin_code, void* context app->pincode_buffer = *pin_code; if(desktop_pin_code_check(pin_code)) { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsEqual); } else { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsDifferent); } } static void pin_auth_back_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_auth_on_enter(void* context) { @@ -54,13 +53,13 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_PINS_DIFFERENT: + case DesktopSettingsCustomEventPinsDifferent: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinError, SCENE_STATE_PIN_ERROR_WRONG); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); consumed = true; break; - case SCENE_EVENT_PINS_EQUAL: { + case DesktopSettingsCustomEventPinsEqual: { uint32_t state = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinAuth); if(state == SCENE_STATE_PIN_AUTH_CHANGE_PIN) { @@ -73,7 +72,7 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e consumed = true; break; } - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; 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 abcce66da7a..a97ce8aaa57 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 @@ -3,15 +3,14 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include "desktop_settings_scene.h" -#define SCENE_EVENT_EXIT (0U) - static void pin_disable_back_callback(void* context) { furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_disable_on_enter(void* context) { @@ -35,7 +34,7 @@ bool desktop_settings_scene_pin_disable_on_event(void* context, SceneManagerEven if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index 711683c3fea..695f431a006 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -8,20 +8,19 @@ #include "desktop_settings_scene_i.h" #include #include "../desktop_settings_app.h" - -#define SCENE_EVENT_EXIT (0U) +#include "../desktop_settings_custom_event.h" static void pin_error_back_callback(void* context) { furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } static void pin_error_done_callback(const DesktopPinCode* pin_code, void* context) { UNUSED(pin_code); furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_error_on_enter(void* context) { @@ -55,7 +54,7 @@ bool desktop_settings_scene_pin_error_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_previous_scene(app->scene_manager); consumed = true; break; 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 e0c66cb2889..cf8436dc90b 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 @@ -4,10 +4,7 @@ #include "../desktop_settings_app.h" #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" - -#define SCENE_EVENT_SET_PIN 0 -#define SCENE_EVENT_CHANGE_PIN 1 -#define SCENE_EVENT_DISABLE_PIN 2 +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_menu_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -23,7 +20,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, "Set PIN", - SCENE_EVENT_SET_PIN, + DesktopSettingsCustomEventSetPin, desktop_settings_scene_pin_menu_submenu_callback, app); @@ -31,14 +28,14 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, "Change PIN", - SCENE_EVENT_CHANGE_PIN, + DesktopSettingsCustomEventChangePin, desktop_settings_scene_pin_menu_submenu_callback, app); submenu_add_item( submenu, "Remove PIN", - SCENE_EVENT_DISABLE_PIN, + DesktopSettingsCustomEventDisablePin, desktop_settings_scene_pin_menu_submenu_callback, app); } @@ -54,11 +51,11 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_SET_PIN: + case DesktopSettingsCustomEventSetPin: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); consumed = true; break; - case SCENE_EVENT_CHANGE_PIN: + case DesktopSettingsCustomEventChangePin: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinAuth, @@ -66,7 +63,7 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); consumed = true; break; - case SCENE_EVENT_DISABLE_PIN: + case DesktopSettingsCustomEventDisablePin: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinAuth, SCENE_STATE_PIN_AUTH_DISABLE); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); 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 08f5fcb3fcc..95f50d2e11c 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 @@ -8,11 +8,7 @@ #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" #include - -#define SCENE_EVENT_EXIT (0U) -#define SCENE_EVENT_1ST_PIN_ENTERED (1U) -#define SCENE_EVENT_PINS_EQUAL (2U) -#define SCENE_EVENT_PINS_DIFFERENT (3U) +#include "../desktop_settings_custom_event.h" static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); @@ -22,20 +18,23 @@ static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* contex if(!app->pincode_buffer_filled) { app->pincode_buffer = *pin_code; app->pincode_buffer_filled = true; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEvent1stPinEntered); } else { app->pincode_buffer_filled = false; if(desktop_pin_code_is_equal(&app->pincode_buffer, pin_code)) { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsEqual); } else { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsDifferent); } } } static void pin_setup_back_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_on_enter(void* context) { @@ -60,7 +59,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_1ST_PIN_ENTERED: + case DesktopSettingsCustomEvent1stPinEntered: desktop_view_pin_input_set_label_button(app->pin_input_view, "OK"); desktop_view_pin_input_set_label_primary(app->pin_input_view, 0, 0, NULL); desktop_view_pin_input_set_label_secondary( @@ -69,7 +68,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent desktop_view_pin_input_unlock_input(app->pin_input_view); consumed = true; break; - case SCENE_EVENT_PINS_DIFFERENT: + case DesktopSettingsCustomEventPinsDifferent: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinError, @@ -77,11 +76,11 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); consumed = true; break; - case SCENE_EVENT_PINS_EQUAL: + case DesktopSettingsCustomEventPinsEqual: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto2); consumed = true; break; - case SCENE_EVENT_EXIT: { + case DesktopSettingsCustomEventExit: { uint32_t scene_found; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); 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 aa3d2214e54..ad5784b551c 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 @@ -5,18 +5,17 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include #include "desktop_settings_scene.h" -#define SCENE_EVENT_DONE (0U) - static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_DONE); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventDone); } void desktop_settings_scene_pin_setup_done_on_enter(void* context) { @@ -48,7 +47,7 @@ bool desktop_settings_scene_pin_setup_done_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_DONE: { + case DesktopSettingsCustomEventDone: { bool scene_found = false; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c index 31eec3871ad..69cdcc074bc 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -5,12 +5,11 @@ #include "desktop_settings_scene.h" #include "../desktop_settings_app.h" #include "../views/desktop_settings_view_pin_setup_howto.h" - -#define SCENE_EXIT_EVENT (0U) +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_lock_done_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_howto_on_enter(void* context) { @@ -27,7 +26,7 @@ bool desktop_settings_scene_pin_setup_howto_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EXIT_EVENT: + case DesktopSettingsCustomEventExit: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetup); consumed = true; break; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c index efa39f1f08c..c67ab4c790e 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -4,18 +4,16 @@ #include "desktop_settings_scene.h" #include "../desktop_settings_app.h" #include "../views/desktop_settings_view_pin_setup_howto2.h" - -#define SCENE_EXIT_EVENT (0U) -#define SCENE_DONE_EVENT (1U) +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_setup_howto2_done_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_DONE_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventDone); } static void desktop_settings_scene_pin_setup_howto2_exit_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_howto2_on_enter(void* context) { @@ -35,12 +33,12 @@ bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManage if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_DONE_EVENT: { + case DesktopSettingsCustomEventDone: { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupDone); consumed = true; break; } - case SCENE_EXIT_EVENT: { + case DesktopSettingsCustomEventExit: { bool scene_found = false; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); 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 index a7000204f5b..baaee5c1e99 100644 --- 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 @@ -2,12 +2,10 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.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; @@ -22,14 +20,14 @@ void desktop_settings_scene_quick_apps_menu_on_enter(void* context) { submenu_add_item( submenu, "Default Mode", - SCENE_EVENT_SET_DEFAULT, + DesktopSettingsCustomEventSetDefault, desktop_settings_scene_quick_apps_menu_submenu_callback, app); submenu_add_item( submenu, "Dummy Mode", - SCENE_EVENT_SET_DUMMY, + DesktopSettingsCustomEventSetDummy, desktop_settings_scene_quick_apps_menu_submenu_callback, app); @@ -44,7 +42,7 @@ bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_SET_DEFAULT: + case DesktopSettingsCustomEventSetDefault: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu, @@ -53,7 +51,7 @@ bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManager app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); consumed = true; break; - case SCENE_EVENT_SET_DUMMY: + case DesktopSettingsCustomEventSetDummy: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu, 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 7b3e5b96b9e..95bdcdf406c 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -9,6 +9,7 @@ typedef enum { DesktopSettingsAutoLockDelay, DesktopSettingsClockDisplay, DesktopSettingsFavoriteApps, + DesktopSettingsHappyMode, } DesktopSettingsEntry; #define AUTO_LOCK_DELAY_COUNT 6 @@ -77,7 +78,7 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list, "Show Clock", CLOCK_ENABLE_COUNT, - desktop_settings_scene_start_clock_enable_changed, // + desktop_settings_scene_start_clock_enable_changed, app); value_index = @@ -87,6 +88,8 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list_add(variable_item_list, "Set Quick Access Apps", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Happy Mode", 1, NULL, NULL); + variable_item_list_set_enter_callback( variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); @@ -107,6 +110,10 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneQuickAppsMenu); break; + case DesktopSettingsHappyMode: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneHappyMode); + break; + default: break; } 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 6cd9c5c6769..6328a238134 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 @@ -1,4 +1,5 @@ #include "../power_settings_app.h" +#include void power_settings_scene_power_off_dialog_callback(DialogExResult result, void* context) { furi_assert(context); @@ -9,11 +10,23 @@ void power_settings_scene_power_off_dialog_callback(DialogExResult result, void* void power_settings_scene_power_off_on_enter(void* context) { PowerSettingsApp* app = context; DialogEx* dialog = app->dialog; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + DolphinSettings settings; + dolphin_get_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); - 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, 14, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_header( + dialog, + "Turn Off Device?", + 64, + settings.happy_mode ? 32 : 0, + AlignCenter, + settings.happy_mode ? AlignCenter : AlignTop); + if(!settings.happy_mode) { + dialog_ex_set_text( + 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); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 12e5b97e159..7f3e2f396d3 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.3,, +Version,+,72.4,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -869,6 +869,8 @@ Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed Function,+,dolphin_flush,void,Dolphin* Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_get_settings,void,"Dolphin*, DolphinSettings*" +Function,+,dolphin_set_settings,void,"Dolphin*, DolphinSettings*" Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 247c2ee2d5b..b2860692104 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -40,6 +40,7 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); + furi_hal_region_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 850c84fe00f..c6f44f914be 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.3,, +Version,+,72.4,, 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,, @@ -956,6 +956,8 @@ Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed Function,+,dolphin_flush,void,Dolphin* Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_get_settings,void,"Dolphin*, DolphinSettings*" +Function,+,dolphin_set_settings,void,"Dolphin*, DolphinSettings*" Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." From 9bdf41d8ff5b769b02ce6e7240471d9475a3816e Mon Sep 17 00:00:00 2001 From: Thomas Nemer <80506610+thomasnemer@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:25:13 +0200 Subject: [PATCH 23/29] feat: add linux/gnome badusb demo resource files (#3846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add linux/gnome badusb demo resource files * doc: use latest appimage package and install icon and desktop file Co-authored-by: Thomas N Co-authored-by: あく --- .../badusb/Install_qFlipper_gnome.txt | 51 +++++++++++ .../bad_usb/resources/badusb/demo_gnome.txt | 87 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt create mode 100644 applications/main/bad_usb/resources/badusb/demo_gnome.txt diff --git a/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt new file mode 100644 index 00000000000..ec2ec108eec --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt @@ -0,0 +1,51 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard + +REM This will install qFlipper on Linux/Gnome, using the latest AppImage package + +REM Open a terminal +ALT F2 +DELAY 1000 +STRINGLN gnome-terminal --maximize +DELAY 1000 + +REM Ensure we have a folder to run executables from +STRINGLN mkdir -p $HOME/.local/bin + +REM Download the latest AppImage +STRINGLN curl -fsSL "https://update.flipperzero.one/qFlipper/release/linux-amd64/AppImage" -o "$HOME/.local/bin/qFlipper" +DELAY 1000 + +REM Make it executable +STRINGLN chmod +x $HOME/.local/bin/qFlipper + +REM Extract the appimage in /tmp to install icon and .desktop file +STRINGLN cd /tmp +STRINGLN $HOME/.local/bin/qFlipper --appimage-extract > /dev/null +STRINGLN sed "s@Exec=qFlipper@Exec=$HOME/.local/bin/qFlipper@" squashfs-root/usr/share/applications/qFlipper.desktop > $HOME/.local/share/applications/qFlipper.desktop +STRINGLN mkdir -p $HOME/.local/share/icons/hicolor/512x512/apps +STRINGLN cp squashfs-root/usr/share/icons/hicolor/512x512/apps/qFlipper.png $HOME/.local/share/icons/hicolor/512x512/apps/qFlipper.png +STRINGLN rm -rf squashfs-root +STRINGLN cd + +REM Depending on the Linux distribution and display manager +REM there might be several ways to update desktop entries +REM try all +STRINGLN xdg-desktop-menu forceupdate || true +STRINGLN update-desktop-database ~/.local/share/applications || true + +STRINGLN echo " +ENTER +REPEAT 60 +STRINGLN ========================================================================================== +STRINGLN qFlipper has been installed to $HOME/.local/bin/ +STRINGLN It should appear in your Applications menu. +STRINGLN If it does not, you might want to log out and log in again. +ENTER +STRINGLN If you prefer to run qFlipper from your terminal, either use the absolute path +STRINGLN or make sure $HOME/.local/bin/ is included in your PATH environment variable. +ENTER +STRINGLN Additional configurations might be required by your Linux distribution such as +STRINGLN group membership, udev rules or else. +STRINGLN ========================================================================================== +STRINGLN " diff --git a/applications/main/bad_usb/resources/badusb/demo_gnome.txt b/applications/main/bad_usb/resources/badusb/demo_gnome.txt new file mode 100644 index 00000000000..7d91de4126f --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/demo_gnome.txt @@ -0,0 +1,87 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard +REM You can override this to use something else +REM Check the `lsusb` command to know your own devices IDs + +REM This is BadUSB demo script for Linux/Gnome + +REM Open terminal window +DELAY 1000 +ALT F2 +DELAY 500 +STRING gnome-terminal --maximize +DELAY 500 +ENTER +DELAY 750 + +REM Clear the screen in case some banner was displayed +STRING clear +ENTER + +REM Bigger shell script example +STRING cat > /dev/null << EOF +ENTER + +STRING Hello World! +ENTER + +DEFAULT_DELAY 50 + +STRING = +REPEAT 59 +ENTER +ENTER + +STRING _.-------.._ -, +ENTER +HOME +STRING .-"'''"--..,,_/ /'-, -, \ +ENTER +HOME +STRING .:" /:/ /'\ \ ,_..., '. | | +ENTER +HOME +STRING / ,----/:/ /'\ _\~'_-"' _; +ENTER +HOME +STRING ' / /'"""'\ \ \.~'_-' ,-"'/ +ENTER +HOME +STRING | | | 0 | | .-' ,/' / +ENTER +HOME +STRING | ,..\ \ ,.-"' ,/' / +ENTER +HOME +STRING ; : '/'""\' ,/--==,/-----, +ENTER +HOME +STRING | '-...| -.___-Z:_______J...---; +ENTER +HOME +STRING : ' _-' +ENTER +HOME +STRING _L_ _ ___ ___ ___ ___ ____--"' +ENTER +HOME +STRING | __|| | |_ _|| _ \| _ \| __|| _ \ +ENTER +HOME +STRING | _| | |__ | | | _/| _/| _| | / +ENTER +HOME +STRING |_| |____||___||_| |_| |___||_|_\ +ENTER +HOME +ENTER + +STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script format +ENTER +STRING More information about script syntax can be found here: +ENTER +STRING https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/file_formats/BadUsbScriptFormat.md +ENTER + +STRING EOF +ENTER From 8672a1d94cfe891c37d8f05a5a2cbc2d8a01e799 Mon Sep 17 00:00:00 2001 From: Silent Date: Sat, 7 Sep 2024 18:16:56 +0200 Subject: [PATCH 24/29] Replace all calls to strncpy with strlcpy, use strdup more, expose strlcat (#3866) strlcpy doesn't zero the buffer and ensures null termination, just like snprintf strlcat is already used by mjs and it's a safe alternative to strcat, so it should be OK to expose to apps --- .../debug/rpc_debug_app/rpc_debug_app.c | 2 +- .../rpc_debug_app_scene_input_error_code.c | 2 +- .../rpc_debug_app_scene_input_error_text.c | 2 +- ...pc_debug_app_scene_receive_data_exchange.c | 2 +- .../examples/example_thermo/example_thermo.c | 2 +- applications/main/ibutton/ibutton.c | 4 +-- applications/main/ibutton/ibutton_i.h | 4 +-- applications/main/infrared/infrared_app_i.h | 4 +-- .../scenes/infrared_scene_edit_rename.c | 4 +-- .../subghz/scenes/subghz_scene_save_name.c | 6 ++-- .../desktop/animations/animation_storage.c | 10 ++---- applications/services/rpc/rpc_storage.c | 4 +-- .../scenes/desktop_settings_scene_favorite.c | 4 +-- lib/mjs/common/frozen/frozen.c | 31 ++++++++++++------- lib/mjs/mjs_json.c | 3 +- targets/f18/api_symbols.csv | 4 +-- targets/f7/api_symbols.csv | 4 +-- targets/f7/furi_hal/furi_hal_version.c | 2 +- 18 files changed, 47 insertions(+), 47 deletions(-) diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c index 1536b8918e9..1affeae29ff 100644 --- a/applications/debug/rpc_debug_app/rpc_debug_app.c +++ b/applications/debug/rpc_debug_app/rpc_debug_app.c @@ -24,7 +24,7 @@ static void rpc_debug_app_tick_event_callback(void* context) { static void rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) { if(data == NULL || data_size == 0) { - strncpy(buf, "", buf_size); + strlcpy(buf, "", buf_size); return; } diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c index be77748815c..09d58c2c9b1 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -26,7 +26,7 @@ static void rpc_debug_app_scene_input_error_code_result_callback(void* context) void rpc_debug_app_scene_input_error_code_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "666", TEXT_STORE_SIZE); + strlcpy(app->text_store, "666", TEXT_STORE_SIZE); text_input_set_header_text(app->text_input, "Enter error code"); text_input_set_validator( app->text_input, rpc_debug_app_scene_input_error_code_validator_callback, NULL); diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c index b07f8f4af27..cca293b665d 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c @@ -7,7 +7,7 @@ static void rpc_debug_app_scene_input_error_text_result_callback(void* context) void rpc_debug_app_scene_input_error_text_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); + strlcpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); text_input_set_header_text(app->text_input, "Enter error text"); text_input_set_result_callback( app->text_input, diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c index 10d5e36f624..686a6f95df0 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c @@ -2,7 +2,7 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); + strlcpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); text_box_set_text(app->text_box, app->text_store); text_box_set_font(app->text_box, TextBoxFontHex); diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 895f05ce76f..e5af819e997 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -257,7 +257,7 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { snprintf(text_store, TEXT_STORE_SIZE, "Temperature: %+.1f%c", (double)temp, temp_units); } else { /* Or show a message that no data is available */ - strncpy(text_store, "-- No data --", TEXT_STORE_SIZE); + strlcpy(text_store, "-- No data --", TEXT_STORE_SIZE); } canvas_draw_str_aligned(canvas, middle_x, 58, AlignCenter, AlignBottom, text_store); diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 765d5361253..eff44c6deb2 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -183,7 +183,7 @@ bool ibutton_load_key(iButton* ibutton, bool show_error) { FuriString* tmp = furi_string_alloc(); path_extract_filename(ibutton->file_path, tmp, true); - strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); + strlcpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); furi_string_free(tmp); } else if(show_error) { @@ -243,7 +243,7 @@ bool ibutton_delete_key(iButton* ibutton) { } void ibutton_reset_key(iButton* ibutton) { - memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1); + ibutton->key_name[0] = '\0'; furi_string_reset(ibutton->file_path); ibutton_key_reset(ibutton->key); } diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index fc2324c6356..73f1080807f 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -32,7 +32,7 @@ #define IBUTTON_APP_FILENAME_PREFIX "iBtn" #define IBUTTON_APP_FILENAME_EXTENSION ".ibtn" -#define IBUTTON_KEY_NAME_SIZE 22 +#define IBUTTON_KEY_NAME_SIZE 23 typedef enum { iButtonWriteModeInvalid, @@ -56,7 +56,7 @@ struct iButton { iButtonWriteMode write_mode; FuriString* file_path; - char key_name[IBUTTON_KEY_NAME_SIZE + 1]; + char key_name[IBUTTON_KEY_NAME_SIZE]; Submenu* submenu; ByteInput* byte_input; diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 721771ef074..692cc967157 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -43,8 +43,8 @@ #define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_SIZE 128 -#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 -#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 +#define INFRARED_MAX_BUTTON_NAME_LENGTH 23 +#define INFRARED_MAX_REMOTE_NAME_LENGTH 23 #define INFRARED_APP_FOLDER EXT_PATH("infrared") #define INFRARED_APP_EXTENSION ".ir" diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 301e936d9b9..ea49080a1f5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -39,7 +39,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_check(current_button_index != InfraredButtonIndexNone); enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH; - strncpy( + strlcpy( infrared->text_store[0], infrared_remote_get_signal_name(remote, current_button_index), enter_name_length); @@ -47,7 +47,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { } else if(edit_target == InfraredEditTargetRemote) { text_input_set_header_text(text_input, "Name the remote"); enter_name_length = INFRARED_MAX_REMOTE_NAME_LENGTH; - strncpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length); + strlcpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length); FuriString* folder_path; folder_path = furi_string_alloc(); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 08ba0ba8252..cdf218acae4 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -6,7 +6,7 @@ #include #include -#define MAX_TEXT_INPUT_LEN 22 +#define MAX_TEXT_INPUT_LEN 23 void subghz_scene_save_name_text_input_callback(void* context) { furi_assert(context); @@ -39,7 +39,7 @@ void subghz_scene_save_name_on_enter(void* context) { FuriString* dir_name = furi_string_alloc(); if(!subghz_path_is_file(subghz->file_path)) { - char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; + char file_name_buf[SUBGHZ_MAX_LEN_NAME]; name_generator_make_auto(file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX); @@ -62,7 +62,7 @@ void subghz_scene_save_name_on_enter(void* context) { furi_string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_name_tmp, furi_string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + strlcpy(subghz->file_name_tmp, furi_string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); text_input_set_header_text(text_input, "Name signal"); text_input_set_result_callback( text_input, diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 9ee85727ba1..a055257765a 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -52,8 +52,7 @@ static bool animation_storage_load_single_manifest_info( if(furi_string_cmp_str(read_string, name)) break; flipper_format_set_strict_mode(file, true); - manifest_info->name = malloc(furi_string_size(read_string) + 1); - strcpy((char*)manifest_info->name, furi_string_get_cstr(read_string)); + manifest_info->name = strdup(furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; manifest_info->min_butthurt = u32value; @@ -105,9 +104,7 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis storage_animation->manifest_info.name = NULL; if(!flipper_format_read_string(file, "Name", read_string)) break; - storage_animation->manifest_info.name = malloc(furi_string_size(read_string) + 1); - strcpy( - (char*)storage_animation->manifest_info.name, furi_string_get_cstr(read_string)); + storage_animation->manifest_info.name = strdup(furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; storage_animation->manifest_info.min_butthurt = u32value; @@ -401,8 +398,7 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFo furi_string_replace_all(str, "\\n", "\n"); - FURI_CONST_ASSIGN_PTR(bubble->bubble.text, malloc(furi_string_size(str) + 1)); - strcpy((char*)bubble->bubble.text, furi_string_get_cstr(str)); + FURI_CONST_ASSIGN_PTR(bubble->bubble.text, strdup(furi_string_get_cstr(str))); if(!flipper_format_read_string(ff, "AlignH", str)) break; if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break; diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index aedcf8c943e..163535f9a6c 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -226,9 +226,7 @@ static void rpc_system_storage_list_root(const PB_Main* request, void* context) response.content.storage_list_response.file[i].data = NULL; response.content.storage_list_response.file[i].size = 0; response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR; - char* str = malloc(strlen(hard_coded_dirs[i]) + 1); - strcpy(str, hard_coded_dirs[i]); - response.content.storage_list_response.file[i].name = str; + response.content.storage_list_response.file[i].name = strdup(hard_coded_dirs[i]); } rpc_send_and_release(session, &response); 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 74d09b2ac39..17ef8835874 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -209,7 +209,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene - strncpy( + strlcpy( curr_favorite_app->name_or_path, furi_string_get_cstr(temp_path), sizeof(curr_favorite_app->name_or_path)); @@ -219,7 +219,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e size_t app_index = event.event - 2; const char* name = favorite_fap_get_app_name(app_index); if(name) - strncpy( + strlcpy( curr_favorite_app->name_or_path, name, sizeof(curr_favorite_app->name_or_path)); diff --git a/lib/mjs/common/frozen/frozen.c b/lib/mjs/common/frozen/frozen.c index 923e6a556b4..7646864c06c 100644 --- a/lib/mjs/common/frozen/frozen.c +++ b/lib/mjs/common/frozen/frozen.c @@ -37,7 +37,7 @@ #ifdef _WIN32 #undef snprintf #undef vsnprintf -#define snprintf cs_win_snprintf +#define snprintf cs_win_snprintf #define vsnprintf cs_win_vsnprintf int cs_win_snprintf(char* str, size_t size, const char* format, ...); int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap); @@ -150,7 +150,8 @@ static int json_isspace(int ch) { } static void json_skip_whitespaces(struct frozen* f) { - while(f->cur < f->end && json_isspace(*f->cur)) f->cur++; + while(f->cur < f->end && json_isspace(*f->cur)) + f->cur++; } static int json_cur(struct frozen* f) { @@ -263,15 +264,18 @@ static int json_parse_number(struct frozen* f) { f->cur += 2; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isxdigit(f->cur[0])) + f->cur++; } else { EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; if(f->cur < f->end && f->cur[0] == '.') { f->cur++; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; } if(f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { f->cur++; @@ -279,7 +283,8 @@ static int json_parse_number(struct frozen* f) { if((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; } } json_truncate_path(f, fstate.path_len); @@ -639,8 +644,7 @@ int json_vprintf(struct json_out* out, const char* fmt, va_list xap) { int need_len, size = sizeof(buf); char fmt2[20]; va_list ap_copy; - strncpy(fmt2, fmt, n + 1 > (int)sizeof(fmt2) ? sizeof(fmt2) : (size_t)n + 1); - fmt2[n + 1] = '\0'; + strlcpy(fmt2, fmt, sizeof(fmt2)); va_copy(ap_copy, ap); need_len = vsnprintf(pbuf, size, fmt2, ap_copy); @@ -1047,7 +1051,7 @@ int json_vscanf(const char* s, int len, const char* fmt, va_list ap) { while(fmt[i] != '\0') { if(fmt[i] == '{') { - strcat(path, "."); + strlcat(path, ".", sizeof(path)); i++; } else if(fmt[i] == '}') { if((p = strrchr(path, '.')) != NULL) *p = '\0'; @@ -1160,7 +1164,8 @@ struct json_setf_data { static int get_matched_prefix_len(const char* s1, const char* s2) { int i = 0; - while(s1[i] && s2[i] && s1[i] == s2[i]) i++; + while(s1[i] && s2[i] && s1[i] == s2[i]) + i++; return i; } @@ -1235,7 +1240,8 @@ int json_vsetf( /* Trim comma after the value that begins at object/array start */ if(s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { int i = data.end; - while(i < len && json_isspace(s[i])) i++; + while(i < len && json_isspace(s[i])) + i++; if(s[i] == ',') data.end = i + 1; /* Point after comma */ } json_printf(out, "%.*s", len - data.end, s + data.end); @@ -1305,7 +1311,8 @@ struct prettify_data { }; static void indent(struct json_out* out, int level) { - while(level-- > 0) out->printer(out, " ", 2); + while(level-- > 0) + out->printer(out, " ", 2); } static void print_key(struct prettify_data* pd, const char* path, const char* name, int name_len) { diff --git a/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c index 829b3b4c0f8..654eeb3d655 100644 --- a/lib/mjs/mjs_json.c +++ b/lib/mjs/mjs_json.c @@ -138,8 +138,7 @@ MJS_PRIVATE mjs_err_t to_json_or_debug( vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len; vp += sizeof(mjs_val_t)) { if(*(mjs_val_t*)vp == v) { - strncpy(buf, "[Circular]", size); - len = 10; + len = strlcpy(buf, "[Circular]", size); goto clean; } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7f3e2f396d3..a8da2b36630 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.4,, +Version,+,72.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -2598,7 +2598,7 @@ Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" -Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* Function,-,strlwr,char*,char* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c6f44f914be..c294a3b7d03 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.4,, +Version,+,72.5,, 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,, @@ -3275,7 +3275,7 @@ Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" -Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* Function,-,strlwr,char*,char* diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index bd449e59936..2859ae3626c 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -99,7 +99,7 @@ static void furi_hal_version_set_name(const char* name) { "xFlipper %s", furi_hal_version.name); } else { - snprintf(furi_hal_version.device_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH, "xFlipper"); + strlcpy(furi_hal_version.device_name, "xFlipper", FURI_HAL_VERSION_DEVICE_NAME_LENGTH); } furi_hal_version.device_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; From 8c2223df5dfc9c5b1119adee634e53610ca504d1 Mon Sep 17 00:00:00 2001 From: Silent Date: Sat, 7 Sep 2024 20:18:51 +0200 Subject: [PATCH 25/29] Threading, Timers improvements (#3865) * FuriTimer: Use a local variable to wait for deletion This combines the current synchronous behaviour (as we could have deferred the free call too) with a smaller FuriTimer - it's safe to pass a pointer to a local variable to this pending timer call, because we know it'll be finished before the caller returns * Tighten the use of FuriThread* vs FuriThreadId Event loop and Loader mixed those two, but the fact those are aliases should be an implementation detail. For this reason, thread.c is still allowed to mix them freely. --- applications/services/loader/loader.c | 2 +- furi/core/event_loop.c | 34 +++++++++++++++++---------- furi/core/event_loop_timer.c | 5 +++- furi/core/thread.c | 21 +++++++++-------- furi/core/timer.c | 14 ++++++----- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 0f4cc4a0c24..b76b38c25c9 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -717,7 +717,7 @@ static bool loader_do_signal(Loader* loader, uint32_t signal, void* arg) { static bool loader_do_get_application_name(Loader* loader, FuriString* name) { if(loader_is_application_running(loader)) { - furi_string_set(name, furi_thread_get_name(loader->app.thread)); + furi_string_set(name, furi_thread_get_name(furi_thread_get_id(loader->app.thread))); return true; } diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 2a6cd51d32e..f4f008a71b6 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -71,9 +71,9 @@ FuriEventLoop* furi_event_loop_alloc(void) { PendingQueue_init(instance->pending_queue); // Clear notification state and value - xTaskNotifyStateClearIndexed(instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX); - ulTaskNotifyValueClearIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0xFFFFFFFF); + TaskHandle_t task = (TaskHandle_t)instance->thread_id; + xTaskNotifyStateClearIndexed(task, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX); + ulTaskNotifyValueClearIndexed(task, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0xFFFFFFFF); return instance; } @@ -178,7 +178,7 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { if(flags) { xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits); + (TaskHandle_t)instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits); } } @@ -186,10 +186,11 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); + FuriThread* thread = furi_thread_get_current(); + // Set the default signal callback if none was previously set - if(furi_thread_get_signal_callback(instance->thread_id) == NULL) { - furi_thread_set_signal_callback( - instance->thread_id, furi_event_loop_signal_callback, instance); + if(furi_thread_get_signal_callback(thread) == NULL) { + furi_thread_set_signal_callback(thread, furi_event_loop_signal_callback, instance); } furi_event_loop_init_tick(instance); @@ -233,8 +234,8 @@ void furi_event_loop_run(FuriEventLoop* instance) { } // Disable the default signal callback - if(furi_thread_get_signal_callback(instance->thread_id) == furi_event_loop_signal_callback) { - furi_thread_set_signal_callback(instance->thread_id, NULL, NULL); + if(furi_thread_get_signal_callback(thread) == furi_event_loop_signal_callback) { + furi_thread_set_signal_callback(thread, NULL, NULL); } } @@ -242,7 +243,10 @@ void furi_event_loop_stop(FuriEventLoop* instance) { furi_check(instance); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagStop, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagStop, + eSetBits); } /* @@ -265,7 +269,10 @@ void furi_event_loop_pend_callback( PendingQueue_push_front(instance->pending_queue, item); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagPending, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagPending, + eSetBits); } /* @@ -473,7 +480,10 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance) { FURI_CRITICAL_EXIT(); xTaskNotifyIndexed( - owner->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagEvent, eSetBits); + (TaskHandle_t)owner->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagEvent, + eSetBits); } static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { diff --git a/furi/core/event_loop_timer.c b/furi/core/event_loop_timer.c index 03b6c513234..f4a79bb4f17 100644 --- a/furi/core/event_loop_timer.c +++ b/furi/core/event_loop_timer.c @@ -65,7 +65,10 @@ static void furi_event_loop_timer_enqueue_request( TimerQueue_push_back(instance->timer_queue, timer); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagTimer, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagTimer, + eSetBits); } /* diff --git a/furi/core/thread.c b/furi/core/thread.c index 69c6b0f04e8..60cc628acb3 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -97,7 +97,7 @@ static void furi_thread_body(void* context) { furi_thread_set_state(thread, FuriThreadStateRunning); if(thread->heap_trace_enabled == true) { - memmgr_heap_enable_thread_trace(thread); + memmgr_heap_enable_thread_trace((FuriThreadId)thread); } thread->ret = thread->callback(thread->context); @@ -106,14 +106,14 @@ static void furi_thread_body(void* context) { if(thread->heap_trace_enabled == true) { furi_delay_ms(33); - thread->heap_size = memmgr_heap_get_thread_memory(thread); + thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)thread); furi_log_print_format( thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo, TAG, "%s allocation balance: %zu", thread->name ? thread->name : "Thread", thread->heap_size); - memmgr_heap_disable_thread_trace(thread); + memmgr_heap_disable_thread_trace((FuriThreadId)thread); } furi_check(thread->state == FuriThreadStateRunning); @@ -275,7 +275,7 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { furi_check(thread); - TaskHandle_t hTask = furi_thread_get_id(thread); + TaskHandle_t hTask = (TaskHandle_t)thread; return (FuriThreadPriority)uxTaskPriorityGet(hTask); } @@ -390,7 +390,7 @@ bool furi_thread_join(FuriThread* thread) { FuriThreadId furi_thread_get_id(FuriThread* thread) { furi_check(thread); - return thread; + return (FuriThreadId)thread; } void furi_thread_enable_heap_trace(FuriThread* thread) { @@ -418,7 +418,7 @@ int32_t furi_thread_get_return_code(FuriThread* thread) { } FuriThreadId furi_thread_get_current_id(void) { - return xTaskGetCurrentTaskHandle(); + return (FuriThreadId)xTaskGetCurrentTaskHandle(); } FuriThread* furi_thread_get_current(void) { @@ -624,15 +624,16 @@ bool furi_thread_enumerate(FuriThreadList* thread_list) { FuriThreadListItem* item = furi_thread_list_get_or_insert(thread_list, (FuriThread*)task[i].xHandle); - item->thread = (FuriThreadId)task[i].xHandle; - item->app_id = furi_thread_get_appid(item->thread); + FuriThreadId thread_id = (FuriThreadId)task[i].xHandle; + item->thread = (FuriThread*)thread_id; + item->app_id = furi_thread_get_appid(thread_id); item->name = task[i].pcTaskName; item->priority = task[i].uxCurrentPriority; item->stack_address = (uint32_t)tcb->pxStack; - size_t thread_heap = memmgr_heap_get_thread_memory(item->thread); + size_t thread_heap = memmgr_heap_get_thread_memory(thread_id); item->heap = thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap; item->stack_size = (tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t); - item->stack_min_free = furi_thread_get_stack_space(item->thread); + item->stack_min_free = furi_thread_get_stack_space(thread_id); item->state = furi_thread_state_name(task[i].eCurrentState); item->counter_previous = item->counter_current; item->counter_current = task[i].ulRunTimeCounter; diff --git a/furi/core/timer.c b/furi/core/timer.c index 1ca56f0fa45..4ed9139583b 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -9,7 +9,6 @@ struct FuriTimer { StaticTimer_t container; FuriTimerCallback cb_func; void* cb_context; - volatile bool can_be_removed; }; // IMPORTANT: container MUST be the FIRST struct member @@ -42,9 +41,8 @@ static void furi_timer_epilogue(void* context, uint32_t arg) { furi_assert(context); UNUSED(arg); - FuriTimer* instance = context; - - instance->can_be_removed = true; + volatile bool* can_be_removed = context; + *can_be_removed = true; } void furi_timer_free(FuriTimer* instance) { @@ -53,9 +51,13 @@ void furi_timer_free(FuriTimer* instance) { TimerHandle_t hTimer = (TimerHandle_t)instance; furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - furi_check(xTimerPendFunctionCall(furi_timer_epilogue, instance, 0, portMAX_DELAY) == pdPASS); - while(!instance->can_be_removed) { + volatile bool can_be_removed = false; + furi_check( + xTimerPendFunctionCall(furi_timer_epilogue, (void*)&can_be_removed, 0, portMAX_DELAY) == + pdPASS); + + while(!can_be_removed) { furi_delay_tick(2); } From a122ee75f6711b449a1f172fc5c12d287e1997cc Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Sun, 8 Sep 2024 06:25:31 -0400 Subject: [PATCH 26/29] LFRFID: Guard GProxII Wiegand Check Against False Positive and Correct 36-bit Parsing (#3868) * Update protocol_gproxii.c * 36 bit format parsing fix * Update protocol_gproxii.c * wiegand checks as single function * LfRfid: simplify gprox wiegand payload validation flow * LfRfid: extra furi_check in gprox wiegand validation code Co-authored-by: Aleksandr Kutuzov --- lib/lfrfid/protocols/protocol_gproxii.c | 82 ++++++++++++++++++++----- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_gproxii.c b/lib/lfrfid/protocols/protocol_gproxii.c index 999e965ecb0..ab1d0b94d8c 100644 --- a/lib/lfrfid/protocols/protocol_gproxii.c +++ b/lib/lfrfid/protocols/protocol_gproxii.c @@ -38,8 +38,48 @@ void protocol_gproxii_free(ProtocolGProxII* protocol) { free(protocol); } -uint8_t* protocol_gproxii_get_data(ProtocolGProxII* proto) { - return proto->decoded_data; +uint8_t* protocol_gproxii_get_data(ProtocolGProxII* protocol) { + return protocol->decoded_data; +} + +bool wiegand_check(uint64_t fc_and_card, bool even_parity, bool odd_parity, int card_len) { + uint8_t even_parity_sum = 0; + uint8_t odd_parity_sum = 1; + switch(card_len) { + case 26: + for(int8_t i = 12; i < 24; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + if(even_parity_sum % 2 != even_parity) return false; + + for(int8_t i = 0; i < 12; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + if(odd_parity_sum % 2 != odd_parity) return false; + break; + case 36: + for(int8_t i = 17; i < 34; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + if(even_parity_sum % 2 != even_parity) return false; + + for(int8_t i = 0; i < 17; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + if(odd_parity_sum % 2 != odd_parity) return false; + break; + default: + furi_crash(); + } + return true; } void protocol_gproxii_decoder_start(ProtocolGProxII* protocol) { @@ -74,13 +114,13 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { // XORVALUE LLLLLL DD PPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUUUUUUUUUUUU // 10010000 011010 11 0000000100000000 0 00000000 0000000000000001 0 00000000000000 - Profile: 256 FC: 0 Card: 1 - // 72 Bit Guardall/Verex/Chubb GProx II 36 bit key with 26 bit profile - // 0 10 20 30 40 50 60 70 - // | | | | | | | | - // 01234567 890123 45 67890123456789012345678901 2 34567890 1234567890123456 7 8901 + // 72 Bit Guardall/Verex/Chubb GProx II 36 bit key with 16 bit profile + // 0 10 20 30 40 50 60 70 + // | | | | | | | | + // 01234567 890123 45 67890123 45678901 2 34567890123456 78901234567890123456 7 8901 // -------------------------------------------------------------------------------- - // XORVALUE LLLLLL DD PPPPPPPPPPPPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUU - // 10111000 100100 10 00000001000000000000000000 1 01000000 1000100010111000 1 0000 - Profile: 262144 FC: 64 Card: 35000 + // XORVALUE LLLLLL DD PPPPPPPP PPPPPPPP E UUUUUUFFFFFFFF UUUUCCCCCCCCCCCCCCCC O UUUU + // 10111000 100100 10 00000001 00000000 0 00000000010100 00001000100010111000 1 0000 - Profile: 256 FC: 20 Card: 35000 // X = XOR Key, L = Message length, D = 2 bit check digits, P = Profile, E = Wiegand leading even parity // F = Faclity code, C = Card number, O = Wiegand trailing odd parity, U = Unused bits @@ -111,11 +151,23 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { // Check card length is either 26 or 36 int card_len = bit_lib_get_bits(protocol->decoded_data, 8, 6); - if(card_len == 26 || card_len == 36) { - return true; + + // wiegand parity + if(card_len == 26) { + uint64_t fc_and_card = bit_lib_get_bits_64(protocol->decoded_data, 33, 24); + bool even_parity = bit_lib_get_bits(protocol->decoded_data, 32, 1); + bool odd_parity = bit_lib_get_bits(protocol->decoded_data, 57, 1); + if(!wiegand_check(fc_and_card, even_parity, odd_parity, card_len)) return false; + } else if(card_len == 36) { + uint64_t fc_and_card = bit_lib_get_bits_64(protocol->decoded_data, 33, 34); + uint8_t even_parity = bit_lib_get_bits(protocol->decoded_data, 32, 1); + uint8_t odd_parity = bit_lib_get_bits(protocol->decoded_data, 67, 1); + if(!wiegand_check(fc_and_card, even_parity, odd_parity, card_len)) return false; } else { return false; // If we don't get a 26 or 36 it's not a known card type } + + return true; } bool protocol_gproxii_decoder_feed(ProtocolGProxII* protocol, bool level, uint32_t duration) { @@ -191,7 +243,7 @@ void protocol_gproxii_render_data(ProtocolGProxII* protocol, FuriString* result) // Print FC, Card and Length furi_string_cat_printf( result, - "FC: %hhu Card: %hu LEN: %hhu\n", + "FC: %u Card: %u LEN: %hhu\n", bit_lib_get_bits(protocol->decoded_data, 33, 8), bit_lib_get_bits_16(protocol->decoded_data, 41, 16), card_len); @@ -206,17 +258,17 @@ void protocol_gproxii_render_data(ProtocolGProxII* protocol, FuriString* result) // Print FC, Card and Length furi_string_cat_printf( result, - "FC: %hhu Card: %hu LEN: %hhu\n", - bit_lib_get_bits(protocol->decoded_data, 43, 8), + "FC: %u Card: %u LEN: %hhu\n", + bit_lib_get_bits_16(protocol->decoded_data, 33, 14), bit_lib_get_bits_16(protocol->decoded_data, 51, 16), card_len); // XOR Key, CRC and Profile furi_string_cat_printf( result, - "XOR: %hhu CRC: %hhu P: %06lX", + "XOR: %hhu CRC: %hhu P: %04hX", xor_code, crc_code, - bit_lib_get_bits_32(protocol->decoded_data, 16, 26)); + bit_lib_get_bits_16(protocol->decoded_data, 16, 16)); } else { furi_string_cat_printf(result, "Read Error\n"); } From 75f4782fab640bb61278fba463544eddd8f6174e Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 8 Sep 2024 15:43:14 -0700 Subject: [PATCH 27/29] Rename 'Detect Reader' to 'Extract MF Keys' (#3874) * Rename 'Detect Reader' to 'Collect Nonces' * Updated name * Updated name * Format Sources Co-authored-by: Aleksandr Kutuzov --- .../nfc/helpers/protocol_support/mf_classic/mf_classic.c | 4 ++-- applications/main/nfc/scenes/nfc_scene_start.c | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 7a51e3d8696..4fece16be5a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -117,7 +117,7 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Detect Reader", + "Extract MF Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); @@ -155,7 +155,7 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Detect Reader", + "Extract MF Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 53857b8495e..b981b719a92 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -29,7 +29,11 @@ void nfc_scene_start_on_enter(void* context) { submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( - submenu, "Detect Reader", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc); + submenu, + "Extract MF Keys", + SubmenuIndexDetectReader, + nfc_scene_start_submenu_callback, + nfc); submenu_add_item(submenu, "Saved", SubmenuIndexSaved, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); From 543f6058e5c3cf5593c3dbcfc3beea8ccaeb8f3c Mon Sep 17 00:00:00 2001 From: christhetech131 <85564092+christhetech131@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:54:38 -0500 Subject: [PATCH 28/29] Infrared: add TCL 75S451 to TV universal remote (#3880) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tv.ir * Infrared: mark TCL 75S451 block in universal remote, cleanup extra spaces Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 94 +++++++++++++------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 4d5868ccea1..9df664a7b88 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1694,69 +1694,69 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC6 address: 00 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: RC6 address: 00 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC6 address: 00 00 00 00 command: 11 00 00 00 -# +# name: Ch_next type: parsed protocol: RC6 address: 00 00 00 00 command: 20 00 00 00 -# +# name: Ch_prev type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 -# +# # Model TCL 50P715X1 -# +# name: Power type: parsed protocol: RCA address: 0F 00 00 00 command: 54 00 00 00 -# +# name: Mute type: parsed protocol: RCA address: 0F 00 00 00 command: FC 00 00 00 -# +# name: Vol_up type: parsed protocol: RCA address: 0F 00 00 00 command: F4 00 00 00 -# +# name: Vol_dn type: parsed protocol: RCA address: 0F 00 00 00 command: 74 00 00 00 -# +# name: Ch_next type: parsed protocol: RCA address: 0F 00 00 00 command: B4 00 00 00 -# +# name: Ch_prev type: parsed protocol: RCA @@ -1770,31 +1770,31 @@ type: parsed protocol: NECext address: 01 72 00 00 command: 5C A3 00 00 -# +# name: Power type: parsed protocol: NECext address: 01 72 00 00 command: 1E E1 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 01 72 00 00 command: 0A F5 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 01 72 00 00 command: 06 F9 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 01 72 00 00 command: 48 B7 00 00 -# +# name: Ch_prev type: parsed protocol: NECext @@ -1836,39 +1836,39 @@ type: parsed protocol: NEC address: 04 00 00 00 command: 08 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 04 00 00 00 command: 03 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 04 00 00 00 command: 00 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 04 00 00 00 command: 01 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 09 00 00 00 -# +# # Emerson TV -# +# name: Power type: parsed protocol: NECext @@ -1880,28 +1880,66 @@ type: parsed protocol: NECext address: 84 E0 00 00 command: 50 AF 00 00 -# +# name: Ch_prev type: parsed protocol: NECext address: 84 E0 00 00 command: 51 AE 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 84 E0 00 00 command: 60 9F 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 84 E0 00 00 command: 61 9E 00 00 -# +# name: Mute type: parsed protocol: NECext address: 84 E0 00 00 command: 64 9B 00 00 +# +# TCL 75S451 +# +name: Power +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 17 E8 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 20 DF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 0F F0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 19 E6 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 33 CC 00 00 From 70d8951fb703d09750972be125a4bc11d2ae3b40 Mon Sep 17 00:00:00 2001 From: Silent Date: Mon, 9 Sep 2024 01:04:56 +0200 Subject: [PATCH 29/29] FuriTimer: Use an event instead of a volatile bool to wait for deletion (#3887) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/timer.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/furi/core/timer.c b/furi/core/timer.c index 4ed9139583b..21952cf1233 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -3,6 +3,7 @@ #include "kernel.h" #include +#include #include struct FuriTimer { @@ -14,6 +15,8 @@ struct FuriTimer { // IMPORTANT: container MUST be the FIRST struct member static_assert(offsetof(FuriTimer, container) == 0); +#define TIMER_DELETED_EVENT (1U << 0) + static void TimerCallback(TimerHandle_t hTimer) { FuriTimer* instance = pvTimerGetTimerID(hTimer); furi_check(instance); @@ -41,8 +44,8 @@ static void furi_timer_epilogue(void* context, uint32_t arg) { furi_assert(context); UNUSED(arg); - volatile bool* can_be_removed = context; - *can_be_removed = true; + EventGroupHandle_t hEvent = context; + xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); } void furi_timer_free(FuriTimer* instance) { @@ -52,14 +55,12 @@ void furi_timer_free(FuriTimer* instance) { TimerHandle_t hTimer = (TimerHandle_t)instance; furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - volatile bool can_be_removed = false; - furi_check( - xTimerPendFunctionCall(furi_timer_epilogue, (void*)&can_be_removed, 0, portMAX_DELAY) == - pdPASS); + StaticEventGroup_t event_container; + EventGroupHandle_t hEvent = xEventGroupCreateStatic(&event_container); + furi_check(xTimerPendFunctionCall(furi_timer_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); - while(!can_be_removed) { - furi_delay_tick(2); - } + xEventGroupWaitBits(hEvent, TIMER_DELETED_EVENT, 0, pdTRUE, portMAX_DELAY); + vEventGroupDelete(hEvent); free(instance); }