From b3d95233224f61778413faf6d6455b96b7cac4ed Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Wed, 12 Oct 2022 19:31:25 +0400 Subject: [PATCH 1/6] Github actions on kubernetes runners (#1861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change toolchain path and runner tag * fix check_submdules.yml * try to fix errors * create .ssh directory * fix toolchain path * add empty line for test * testing 3 k8s nodes speed * Test speed again * change tag, move reindex job * bring reindex.yml back * fix build.yml * fix reindex.yml Co-authored-by: あく --- .github/workflows/amap_analyse.yml | 3 +++ .github/workflows/build.yml | 8 +++++--- .github/workflows/check_submodules.yml | 1 + .github/workflows/lint_c.yml | 2 +- .github/workflows/lint_python.yml | 2 +- .github/workflows/pvs_studio.yml | 6 ++++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index 6be99c9d1..a50c5436f 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -62,6 +62,8 @@ jobs: - name: 'Download build artifacts' run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avzP \ @@ -97,3 +99,4 @@ jobs: ${{ secrets.AMAP_MARIADB_PORT }} \ ${{ secrets.AMAP_MARIADB_DATABASE }} \ artifacts/flipper-z-f7-firmware-$SUFFIX.elf.map.all + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15b3966a7..1304c5d7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done @@ -97,7 +97,7 @@ jobs: - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt copro_dist tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware - name: 'Copy .map file' @@ -107,6 +107,8 @@ jobs: - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avzP --delete --mkpath \ @@ -174,6 +176,6 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package DEBUG=0 COMPACT=1 done diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index e4178c3c7..eba4affc3 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -27,6 +27,7 @@ jobs: - name: 'Check protobuf branch' run: | + git submodule update --init SUB_PATH="assets/protobuf"; SUB_BRANCH="dev"; SUB_COMMITS_MIN=40; diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index becafcab0..23dc6c699 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -30,7 +30,7 @@ jobs: - name: 'Check code formatting' id: syntax_check - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint + run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint - name: Report code formatting errors if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index d5ff834ea..c2f092110 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -26,4 +26,4 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py + run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint_py diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 981575551..e3d5fc132 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -57,11 +57,11 @@ jobs: - name: 'Generate compile_comands.json' run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking + FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking - name: 'Static code analysis' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ @@ -76,6 +76,8 @@ jobs: - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} run: | + mkdir -p ~/.ssh + ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; chmod 600 ./deploy_key; rsync -avrzP --mkpath \ From 92a738bf7784ea13208a532322a1e96d4d3ff274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 13 Oct 2022 00:39:39 +0900 Subject: [PATCH 2/6] Dolphin: add L1_Painting animation (#1863) Co-authored-by: hedger --- .../external/L1_Painting_128x64/frame_0.png | Bin 0 -> 1607 bytes .../external/L1_Painting_128x64/frame_1.png | Bin 0 -> 1618 bytes .../external/L1_Painting_128x64/frame_10.png | Bin 0 -> 1606 bytes .../external/L1_Painting_128x64/frame_11.png | Bin 0 -> 1579 bytes .../external/L1_Painting_128x64/frame_2.png | Bin 0 -> 1608 bytes .../external/L1_Painting_128x64/frame_3.png | Bin 0 -> 1585 bytes .../external/L1_Painting_128x64/frame_4.png | Bin 0 -> 1600 bytes .../external/L1_Painting_128x64/frame_5.png | Bin 0 -> 1609 bytes .../external/L1_Painting_128x64/frame_6.png | Bin 0 -> 1588 bytes .../external/L1_Painting_128x64/frame_7.png | Bin 0 -> 1630 bytes .../external/L1_Painting_128x64/frame_8.png | Bin 0 -> 1623 bytes .../external/L1_Painting_128x64/frame_9.png | Bin 0 -> 1614 bytes .../external/L1_Painting_128x64/meta.txt | 32 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++ .../dolphin/L1_Painting_128x64/frame_0.bm | Bin 0 -> 763 bytes .../dolphin/L1_Painting_128x64/frame_1.bm | Bin 0 -> 764 bytes .../dolphin/L1_Painting_128x64/frame_10.bm | Bin 0 -> 772 bytes .../dolphin/L1_Painting_128x64/frame_11.bm | Bin 0 -> 767 bytes .../dolphin/L1_Painting_128x64/frame_2.bm | Bin 0 -> 762 bytes .../dolphin/L1_Painting_128x64/frame_3.bm | Bin 0 -> 759 bytes .../dolphin/L1_Painting_128x64/frame_4.bm | Bin 0 -> 759 bytes .../dolphin/L1_Painting_128x64/frame_5.bm | Bin 0 -> 757 bytes .../dolphin/L1_Painting_128x64/frame_6.bm | Bin 0 -> 785 bytes .../dolphin/L1_Painting_128x64/frame_7.bm | Bin 0 -> 803 bytes .../dolphin/L1_Painting_128x64/frame_8.bm | Bin 0 -> 797 bytes .../dolphin/L1_Painting_128x64/frame_9.bm | Bin 0 -> 777 bytes .../dolphin/L1_Painting_128x64/meta.txt | 32 ++++++++++++++++++ assets/resources/dolphin/manifest.txt | 7 ++++ 28 files changed, 78 insertions(+) create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_Painting_128x64/meta.txt create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_0.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_1.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_10.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_11.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_2.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_3.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_4.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_5.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_6.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_7.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_8.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/frame_9.bm create mode 100644 assets/resources/dolphin/L1_Painting_128x64/meta.txt diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_0.png b/assets/dolphin/external/L1_Painting_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f9bc775cc50b2914c5b6db268535d21d006399 GIT binary patch literal 1607 zcmV-N2Dtf&P) z&R|;roM609XshvMfQPl!_%gu5+G?~b5mwP??26yDNr}6iSj7YttnjWdD1wOdS5O_% zXQZKO99ILJqQ|LiHIAzRPSNAkwi;gscvxGFF9STR)%FsB93LtI`{Yq1YD)EIcL%`< zPP{V>NH%FiWO|c38l|97knnzMbE{Ip3C^o~Ku0x{V zTN_!|TVwSJo~Z>Q?ELFslfeK#6yZ2O>UgO{gaM+4j;=eBfd`z4W9vRD0 zxPOjP50|gCnK@AzPiKHxa5+#4(0=cNWFG>Q=6_KYxJHx#PDi8$$iTe?c&ER$rqb3d zcu=8vEdqp2Pr~Y;ePsl4<3*K11EtI6>q$90NKjM;>n^NverwIH0VJg1_&hWfWHulf zL33cH0D5{Fm_?VTrU2<^SrB@Pjuy;rK&G86qBjQ=Qm-m`Bj;xe@IL3oA9f%)KeOPh zp(|*61p};Nk?J5N`Zr*H0$?PZ=j6)hp|R2vg1AjTR!>?@xf0cC?cvDOR(MHaFrN)PCMApwjj)&14vl;Q2vg{(qvjjmuV&<)9U9eCu<(rukCMJ4dCu%Gc>1Yr6ew7=`*qv#Z9jzNn%BWI2~p zHZp%?eXe%?bzQ%Hh$h|xoX7w~{+C7`Eqma> z;uTz;O(JVo`@#$MqicW}Ser&2Z#fL93A9>3qkB)z4(Ah1z6sokNF^bG#Q&QO3|@^))M;ugTY*!QTaNB|i}eTa1j&17#Et zD4U>d7SMYDMCm7E=44wZd8Uq-WdQW_69KmXzN!BWoRT?4pV2-KJ8l83HNOVv_+;lJ zRC>q$qqV?DmcmoAlP2>P_lsl!CkE$V}&J{Mk0?)lp&(E z4vBtiZ)9I;O}{3EfO!T@iIFq|KCJXa#Z08v9n_w{6;!LwSvXP=1uTWjs< z8B0@me2!8Nl`oB%HBlLNXMifG9H<0HKMz6t9Rif*e^3>;N0b3hN2CYHz`O-m+24B? zX{!nvRA^m`0HM>9usUcT8G+PzQKis88M5hqTn-Hq6f=W$7gjmH_pbK<9Nchp9a;)9 z2jGk#IWW@z-MtJ{G31FQfIFHugqEV-hS>w~vXe#h=756hHA|l7{A>e0);#FL0i@<< zHmn+Y25qlkfK@CqJ4jpm9Wd_z@PyNvoY`8*uuK4WYdxb%*cAX4Bt2)`rUu|^AXlo=iMLkokoi1M2O4M@=WEYaV#W6W z9NYe1fQCv|MQ$Yg^Nf4-%Kjk2R24kYMnwAg4$#Vulv$i&CEG~Z7;z42KkF7S)AkSK zj-GjU0<_@PGD-^+`b+~42&p7R%d8cwv&L#Uv{7s;DxNK9RT>~puOK%TJp43%G&J?*dMXqgIoK8mg?*V3kXAjzcjL+7Z$a|j2 ztGa9t8k_72f=fz(MvKTgsS%%` zt_}jJ#vBIAq9zK^>(e>lw3L2JPH zT2AK?JtWMs&#qEj>H)@qSHm-?WOT{-^qJLtM|4HuazI7gI z;7s$FDOJ0CadZ!miRdyVI!s~NKw=dBoXX^Lq<~!Mi{AU$3=lD7K)0{+`La&dgKq^W6^K9q8UcbLs`zQuz&Hkm4N6UOZfA2M1 zNFSlCmE_Fwg%!BvTL5yP-H3F>+FaiFWo2p4REG%rqP~!1db*ds3ET-UL-{*TM=wZt zKY-|Gl@#xpwD%65q;3IR(UtHrRC-6}SefmidOk0Bw4k2ld}RMH18`+m!^+SBNv}xb z=K%>CsZ=1DuI;mm0l2L~41gYNH;|gkpCRB5MQV3e_Rx=(or?1{_T$fB2B1ow3`(P8 z%s5_?jTjbX4C~!dJpiKgX~^nIoryGoS|)4nB06N9k7j>s00ZzteKMpPK7xSHvp%Em z5M5R>K=0jVfPug0e2Od1AMFJ^SsEj#e-AK#9KLc@Isw%>x(X2nh=8vB2SGc>cI6W{ QG5`Po07*qoM6N<$f)%Lgd;kCd literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_10.png b/assets/dolphin/external/L1_Painting_128x64/frame_10.png new file mode 100644 index 0000000000000000000000000000000000000000..ae3148c323e53a435cb5d395a91dde8fca4fd0c7 GIT binary patch literal 1606 zcmV-M2D$l(P)4I23Q=PH;3&4*c%d zbzLoi-g}EaI`;n7`L(t7W_QFF9FNpf37lZOZ&)?EW|0bj4AT>g_YG?`eih(mt7Ov| zYzu%BjQ0&|HGUP~W@|Nm72sxTHTsnZD>NF1;Ga^GLU>U~gTzyARVz5bc@@sL{1Ua@pk=-Xc!?B7Jmair z5LlJOgC!3Vgc|LY8H~`ebhGg0$EH*O?s(1s?LAaT51N8A2AZXT1n)}vo_!*qd!L@M zxB!x%QKpvg2n}!bXxzO5%!1N^TEKW%y7my+PnFa9T!ar&0Zs=p17u*H0<0`YT0t~e zD9y}_5kaJ!o`lsw`#=aWX zM5ep8fmsZCVhf-K%{p+_`2*EXLQf7+a2c!UdCs5lO>5-40Br!v7{Ai6RGx}JSWy91 zRgvl-ZS6f^{sO=vjxa_9MtV38f{GacH7dgM#9)>5E&V|VYFOkXG4{`9Le{9#91`V) zel}3~9zf#Pd$0XA&~uO^Je~MaUKwPJI+J-b?@v}a|7E_gUIb|bJV6l}*&#+lg`xozg_czkB;~El(u}oo(34;Vs}-n`0bUJ&2+-q> zmH7xA>sg70tg4{#&&Y}T zCUgv}#H_K$(3q_lQKL%qciD*Yt9}o_0sSbnGE*{V_zbwwDlOK$+IT9^M_ zfR-K9NL5FzksX?$qvvUEXi68I9k{`@eUdI#5R|P=`AwjPY(=onuoZ~B4AGoumHZ4p zWMA}@vT68&_|}~=y^Lj$Z0g` zJ+8;D3b5^O12tr-O4RY%;K%#C*FG}fQ@b=!TEV!h0&M&L0W?UxSOYRgM4T_tLt241 zOmf;1Pd;o)*Gp%Bkfw@qRj|sWw9iA-`H?b?g4>5*qeJHMwSlJqM2X$tJvHk5S$(9o zLhnm^t!qU7=@~#OMb>&EMxPx(j8FlD9^b>O5T8pj>v%r0KC=u?fF#SObm2jO&JweF zWP|#7N9S`nX3O9TRJRp0^P{*;1_i?K8M_=dteW0ROQW{mdRoQ7} z*?-~lU*@%a1`^0Hp2U|%f3^Z-`u0l9>Ve?68GtImolE(1Wh>jjj6o$xGfU)|9lklP z0=x+*ID-emtn)c-S`ql?l5A8KO-BA4cUrX`5%k{w|0*~WWxGqI#yb-EvqqDG-nth_ z627M@C_J7N*LB_J6d(<3*Y)~i5^YA9)n&HNYBas;N}s6qxl0A$kcK~R?+nnkAXNuW zOM8B%9auCaKo#I5c$x1#hrk;^=|29sH)m*csw#lTD|`x|%AXPpNZ;cbOz(N%&Xann zCQJ05MsIb3hAKc*a0Ih-k<6$2v{}NEkM6NbJ1V2bqssXa{%!@}!d1a5le3_*OZ26R zK=(_I*0Nc1KqS*8nO9bTw`$&u_W+ilD~7;mAVf>Qm#tC5A%kYrF4n1q^Nc#*n=_<) z>URND`CYiK%XLcD`INqu{d6C~)0-2l{nBR@(|-hyQ=CsRGt@*4!ktX-OaDd?31kbi z+c}vd7V8FvmI!G!UAIStM2vAhp zg9hEK^R0^TJT3DjM0;JNFKVAh0>v8KR@iM-1)$IT1OFokWDyH7p8x;=07*qoM6N<$ Ef;{l{egFUf literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_11.png b/assets/dolphin/external/L1_Painting_128x64/frame_11.png new file mode 100644 index 0000000000000000000000000000000000000000..89d003d071e2cc1f55f2c13f361641f996385ece GIT binary patch literal 1579 zcmV+`2Gse9P)7$I zpU>yBCD40s=+^P^ACbw1+6gv zQuSP6^l-}vDp&iN2aiE2Xm&M?XBZ&E!cYs~3Q_~2EdY65N(9uvCH?A7FC z2B5pQfmsZBVhf-S<%eb_{)(!jNic)x%>f11t4f|1{Om1k?D?S|4se0l%&T;;q*n$j z7+@8PR3~X`?+Not07Q87nt>`Fscb$V&ddOrs}p)=Rbo{pt7Pwz-v&kR}Oj- ztYozk74^mInFs?6p%NV-iDPAMgc|9737!*BTJ@5(3feiDD=Xp?iq{N~?Wb92ilTa; z?Zd zRsVaIKaJ9_vlBZqptM%YM#v1H%^{H+OY|K^_B}v319;B9sx?$#IE_}ST%5Iz0ScENSA^jp0`e-BWF%?W?aaR&-YXQU9ECvm8XHj}a z$>uPP=W< zhCFm{R{z**PW$)EpdHy(0nNbK;8)pF;g_r(nR9$M)MVdh>lEOQa z+m(6`uzL`ULUIJ+B*`u-L1ua{-y?lr84Jz!V`l&amAzWC$zz4yfB!lfk>lxD9TO|@ zZ}le7t4_CYzs;)qOVjX!W;dFaS5^Do}kQj@7ELvb|70S%X*xI~bt% zE;GOYI)trGl59m;10I?P1Cb%J0?}C@qC5TU?e75wuC9CuXiGq?)C2$k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_2.png b/assets/dolphin/external/L1_Painting_128x64/frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8bfe6b33c57afb008d06a5b1743c5bd6164d2522 GIT binary patch literal 1608 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_4.png b/assets/dolphin/external/L1_Painting_128x64/frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..d39cddea149d9f0f70b259df9151940b13736955 GIT binary patch literal 1600 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 Hu0mjfn3n@9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_6.png b/assets/dolphin/external/L1_Painting_128x64/frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..3f492eab5e1f5c86ae35be949cb4af2cc7d78776 GIT binary patch literal 1588 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/frame_8.png b/assets/dolphin/external/L1_Painting_128x64/frame_8.png new file mode 100644 index 0000000000000000000000000000000000000000..a44a7315da3d4a550f3453dd8d221dd9131bee83 GIT binary patch literal 1623 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 M07*qoM6N<$g29Fd+5i9m literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Painting_128x64/meta.txt b/assets/dolphin/external/L1_Painting_128x64/meta.txt new file mode 100644 index 000000000..6964b479b --- /dev/null +++ b/assets/dolphin/external/L1_Painting_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 13 +Frames order: 0 1 2 3 4 5 2 3 4 10 6 7 8 7 8 7 8 7 8 9 10 11 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 57 +Y: 24 +Text: No mistakes, +AlignH: Left +AlignV: Center +StartFrame: 11 +EndFrame: 14 + +Slot: 0 +X: 57 +Y: 21 +Text: only happy\n accidents +AlignH: Left +AlignV: Center +StartFrame: 15 +EndFrame: 18 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 197060672..6bf6957c3 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -85,6 +85,13 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Painting_128x64 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 3 +Weight: 6 + Name: L3_Hijack_radio_128x64 Min butthurt: 0 Max butthurt: 8 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_0.bm new file mode 100644 index 0000000000000000000000000000000000000000..2694219ef9eefda1c8d52e3ce99cd1284d0d8d31 GIT binary patch literal 763 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIh_adpMUy@#sNqNo&#Y~)W$gb{ZIfw<%qwoMxiOILU335r~rTTb_l@W@(qL~OfVgI zeCQ0(z(yk#h%A5}HsJXK%7K8eLA8=uh6f3v53GX$#DLYLaheS-${$vcoX1}goXBPx zfcn5D6RZVhBl(EHZ3Xi-3W>5=-f&+BIKrB^N0FX!kSqp_gX>^~C7P% z5DX>*Z>qx=#&i!H0)Su~h%)?Z6x76{hvh|tRbb%t t!9;K;@{=FN1`k*l`~Wxltd|IW5PF(K20Q@x(4WHt2Oa_t4=MnBbU+(OFu?!- literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_1.bm new file mode 100644 index 0000000000000000000000000000000000000000..3c9623d4c79a19e74bfe403f8819221e10322d62 GIT binary patch literal 764 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m#7kjym! z^?*z#SPINX!x4bm3+8MX2Lcb^*g$j`sD{KoTB}mbpce}S2VsPOSgNW4AdmpFoCpp= zlK{|ap->H1s2+Iy1O_n)fLJQO(D?O1#el=WAajU_e4xSg_)+H&Ys7>;P*?zcKhxSI zGx!D&`XEGHG4|NPF3j?jV2LDx(;Sa(OSRfd<0r8I}kAEZeiLFwq0vN0iUOd2f&LEKuLdZY`8i5i< z8v%oO0pr*_kN{$!@qoc#5PSy~Bd7)KEAWA;h5;di+#nj8@I05|qab+9VBk@$&;A4gii65A0QiH;RX?~G2Yw$Y3?v?W z3f^Wx{O8~PqA`FZL>^KNbw*Pda1O3<3vNf`iK3A0T-E2Y~?9$#579CWt@GV27^@! zWC(ox191n5U`V6=53lfSAn|+%d|<)!;BUj?(;)dlU;*>^LG^3#6A*py6WYC2(gqmt zec&E3_~dXreF&6b*bP+Z9#PN#RslkkP%eS-yj5nP^0+}Ya0+Ci0k98@5I&p;7yJJ+ zm`5rB@qtxms`G%uiK7^ihsp!0mn+Tzb2prd zf#MJ4CW20Z<$#35A@S%p^W2$+0l7vTBA0pp;6LMRac`2Roj0Qq47 z<52KG_y_TUf4}oE`B;O4D2PEY3-N=OM*bfd6h;v+jzS^$500-KFCX}Pagq23LI;E~ z41T|V`~$|r75G5M!vNw(I1n9W_#R8~QII@@g8;5i{saPDhyZQ?^wdx81_9rP$^$6} zrGGOZ{&VmDQ5e7_0Q10VDl(YIAAhO<2t2tH2nB+%DXT&-SNf;`fAV%{i-kTgngHl^ z;q#CvL>@58t@94-g323+F!{kVJJ*7zQdKu@9E2)DJtwR311;1&XSm8m-hGd*Jy}AQ}x+DgYl$ z1zbKzVX#$yq46+?0Dc<1R1HQU@`DH0{vQ}Qpo8NDfCtZkAJ%H2p<)lj0=ZT6j0QX( zZ~*=_|NQSCLPZ!h16Aq=ox}O~xXLIOK=`5ust-7XBjNazz%~K#f(O$94}^aw^El-| zJ}@N^tBwhb@xbHKF!?}rTcCMEpkK&@5(xkglm@Gv7Z?5m3h==2m<_(G3||~@JY(<( z4-^U^@vK#Fw+gNtJT?pj0&))+H2{7zXhy3BFDeEByfFI^{HU;wt^}S`6M*0$0f7&U xnhEL^hyY~V355d@|Nr0WvRoz9R8R#_@F*NdIS0mtItQFUqBt8E7-#|U(EtcKLwx`M literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_2.bm new file mode 100644 index 0000000000000000000000000000000000000000..13916806fd62d4aed731cb58265eab34adb4b755 GIT binary patch literal 762 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}3?1VOfZOV@#qpg3 z%71{MVLr?8tW|Kg3aoSm5U~%+iwLU0$L9u+&*K;q s`ALuC0|%TV!$ALk|M&W=mk53o`bKaNGbsngg#Hjg6b26fD;RuqKoWI0G5`Po literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_3.bm new file mode 100644 index 0000000000000000000000000000000000000000..751fbc3efb282916cc38d5999018e804cd4c0a37 GIT binary patch literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noeJW~Up z)k2^euTVVk_yBmqAQlR*^gcaMabPg;2pX6}ciuJ510UA7%7K_Pe8f{#_%5zxU5Qv;P^?P8vyvh z1L6-k1mfXnnnx-D@qq`{!2pj*Ba$%rKu_WiItKznA~lG7pfy{lJfqwRg#v?zz-{$d zV))L1PFMTAvg;PWVy pM*@E-G5la)^`3vg1AnT?aEIXstOyKT0Qk_K!vhDPSm*)q(Ew_YJf;8u literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_4.bm new file mode 100644 index 0000000000000000000000000000000000000000..c1135b467cc5fe2e313984e30a04ded3dd814e7d GIT binary patch literal 759 zcmVrBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fOpudH`hRdR4*Wh)9!NbZ zIhhCZpMUy@#sNqNo&#Y~l*Tyw{ZIfw<%qwoMxiOILNHhQr~rTTb_l@W@(qM0fI1y` ze8A)2z(yk#h$uX*!SV-{0|8)zYb3Y~1`|XdSq1}%0jo&xGzwjmKCK}+j=m!Skjym! z^?*z#SPIBIKqF``nXq6S2tR{i0f1tn8xZ+wtxGb1Tr2?b!ayulRREAk09noi2jNM8 zXf;r%2CLK$JbnTL_=UhM6<_FldZ6OKVc-xyq)a|gVEX(h^N2T!P9G>N06w4T?GhRB z4TyaZA}$#FY;hNeBzQj%2*wJrDB@^!Ve!BR%m6VA6!J+bpj`vwcn^vsRwYE?)1`0? zfP7$q@duoPae(;DW0e5-z=P{xYK{gmBM+1W{vh(cP$mHJf8L-2#t(j-z3jS2iPFmd1^0P>&*$3*xWIm`e6 literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_5.bm new file mode 100644 index 0000000000000000000000000000000000000000..a4681af98e85a780faca27f632565d56a90abee8 GIT binary patch literal 757 zcmVB0PzBVP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vqjg1#Sx_{cBtNru2bFs=pzg&nX(e-Qe|)~{6o3|1Ks zpV(mf2-Q+6sFiCqKp~AmBoql03&9`$N9z+>rBnnlSRlM?kSPVi2@upQgaA>f5hQr9 zSqB0O5bPdE05MQ_z-+K%nDOE|fONp1LOSv&;#X!fI=t` z5Wt_t1^)lc!{uTJj)MUKz%RxQT^sm(U{M%EBB20>U_3g!aJ+xv@y18T<2XbV?!ABf z1IEJ@_(0&F2sk{U`pfV8P)bMOCA7{DnI3j{I&fY?-JF^)d}Q~(fpOcVuBl+~daEB#aeKl(capz;lbCV)B} zcznR(Ef~aNu>}W}xIRGiutBwwTm}OPq7T0k16GmXXcW6BeW3?2*TiTt8HS)f(+SoB zvk~ybU^asJ-@)U+gZMTO9R?~Pu@9E2)UzlB!odO9VIUSNs(?r&04(PM1CXS^G#aQ> z16Aq=o<9Krj6zofRsM&^stzm$9svWKL__5U53jfcU^cr6ZnJ5c|hPO1_=NUlm@GH2bT|nFn~aCm<_(G z3||@0yz$V81RMl4imn#nRgQztV0airGJroCG$U0G2b;Kj5epFfsIZEx93Ef+$V6}_ n@{=FN1`k+A`~Wxltd|IW5PHD?#lR1Z3H&fHdP0HaKo5?HvfnzS literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_6.bm new file mode 100644 index 0000000000000000000000000000000000000000..36f2d084fe3277d555dda68b3a9675916ce237c3 GIT binary patch literal 785 zcmV+s1Md6*01X3xP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}1|oq7Vr#7-N!1m0(_h@t{H>K=F(N$51I5i%Xy{O8~P zqA?GT_#QAVR;r^ZjB)qiVbxF|ANYBQYV}!7S`mW3)j$J(fPY{Z2nMfJ2@DJeULQue zU;^U>2bH)!Kw*qG$AA|ZE&~CC(Fe&^Fo*;~z!w=F27ybmhsbsy@q}yw;}L+!W*UI` z1H^7HYQS7U;sF~#e81rF!(erRae%-vQ4NTEy+HB2U@F0J2aXZ}VydVIst*_k0PsNZ zq(C$ps8j$DK=H4J2(Ct9{)faKRsi_Nuo3uxe4xSfoL9cEIP}6mA12U;&V96I0|$?S0TQeMhJ65_C&nR!3=9In;187I{2w?*5(45cgiJm^&;0;CSWF}W@c@W^ z1Lgkz%){kk#y~K_LH;ks4qY4gd|*a_fIwhSPxE|n!twuy#~B~EKrb*MSii>q_y>)K zEAWBQFdxVy0{$Nx@I05|TN(xf%#jdy)+hf00YyRUfS-rPe{e7k{61712lyT#p|9p- zAI^RM>IfPH1OE@;YxP>HjHWTi-+_lz4JrmYCUU%fg3b0>gwY4f01qf9^08Hs;Aj-PD13q902~~DQL2c* zWHSILht2`PPZd-iAQ7|&%l;1&0Khexs0;%Y5ZH&y)Gr_a)vBQJ!ayulRRHxt0stNc ztUgqT27^@!fNHQH^1Kbi9wu?HRez!J2cGzDAn}L?$_yVK2qb(9@#>YvKp!Y91FOM< zz(37_pWpz(A82F)@qg-r2KfMw2hOWOXO07ZKAe0w9DNCtVAu^+4mcYh1P+Y|z{+SB zK=_(R2ObBF0yrfY46XsN4~!F3%oKS@r``wz$~jOEj0>v+pM%E$BPaxFMjt2+st0Tb zmjVU^64-~z1J!|Y0p&4*cpx}j2H#bNz-P#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0FF1|O98#2^spFn(9#Aiu!jf%=7TI}jK^e18!7$JVb^0Ss0d z5cl9e`uNpSE2x!gH9#SaK_nN81P}2Q{zvN*TBTG3F<2)8VbFkJBXCHDp=2NejXI17Q$+fWtn3P!r=2!UhHbV7^4d<2b(u&Jl!wgU5l6hzG~{|DXrU350-N0pkNN zj0^q$nTN{7jDTUFJO?1wAm!1&hsFgFtUw?zDFgxqsK*P({vRAVa1LJ-NlKe|!K)`vDBM5<3C;tKgMM3QZ4~+icU>*2;s5lSsJWYdN z%*a2S`~TENGzbU&9%3C{s-r24argS50E5T-0Kh;h^;u0?5rV(fKm&hco&Y{Dkifuo z;q;6*$AA|YC_Jsf@>Pr?0TA#7#!G;}VKhPV9f&+(7XY}(@H7ftls-W56O0>h7Z{8N zLon0_(ZE%}xP!z3Hh}qm!Q*&5czt6;s6@>TtVZ6fLN-k0qTMV0l+*E z7--To62B0rGm~YybuYLa-bg=7gh&92aW(nPzcnFK2RN04%iPa1Pllz zu@967s{-T$%3}oZKybJXzN-y@^+B=?5`@Ac7$x}DD!5yR*NqVslrCWq;X^0`@jDHw z4I)wu0u?5RSZqV`qQWk)N+Q6a(jNrD&>``YK_^g3MED**LxBE2|NH$`ON5@mG8_n=;>2_ literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm b/assets/resources/dolphin/L1_Painting_128x64/frame_9.bm new file mode 100644 index 0000000000000000000000000000000000000000..99ed507179a3dc7a64a99f871d18c55796a97cb1 GIT binary patch literal 777 zcmV+k1NQs@00jepP#_wh1V*4h^Q%=r4;6re#y}sidI#q!KOp_)f#@Ib|JA;~|NVSF zaiA#b1NRjIjbK0F{}0djKPmBu1I7ne55oLp7ysl0KTxg)0_;C8#6Ge0tJOe56^2A7 z@;C?jH&lx1C0fl;2xCx51p-X~fkpq3`oz|$RRIiE2rnAoJY$eZhM{C20*ye4BWHj> zZUFJ@9!LN&P$bQ$M&E2Yw$Y3=|%4 ziq2+1{O8~PqA`F$0pkY(4!)x)jB)q+pa6n~0WlJc3acrrLNHhQr~rTA!G}OF7#&@`ss42EH- z53BqiZEy(eDD+a z33bwjuI>)nnlDkWeER*d`kRx77g0 zRTmbF4nZJ-SYVLI1!Aj(xOHM{frdmRAB+SJ5kMb}8WHMI|3CNotd|LO2b2N}C@_p71Q!ta(5F)Xc|fHc0C>Pq H!XF(FZihX= literal 0 HcmV?d00001 diff --git a/assets/resources/dolphin/L1_Painting_128x64/meta.txt b/assets/resources/dolphin/L1_Painting_128x64/meta.txt new file mode 100644 index 000000000..e5f5fc0a6 --- /dev/null +++ b/assets/resources/dolphin/L1_Painting_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 9 +Active frames: 13 +Frames order: 0 1 2 3 4 5 2 3 4 10 6 7 8 7 8 7 8 7 8 9 10 11 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 57 +Y: 24 +Text: No mistakes, +AlignH: Left +AlignV: Center +StartFrame: 11 +EndFrame: 14 + +Slot: 0 +X: 57 +Y: 21 +Text: only happy\n accidents +AlignH: Left +AlignV: Center +StartFrame: 15 +EndFrame: 18 diff --git a/assets/resources/dolphin/manifest.txt b/assets/resources/dolphin/manifest.txt index 197060672..6bf6957c3 100644 --- a/assets/resources/dolphin/manifest.txt +++ b/assets/resources/dolphin/manifest.txt @@ -85,6 +85,13 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L1_Painting_128x64 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 3 +Weight: 6 + Name: L3_Hijack_radio_128x64 Min butthurt: 0 Max butthurt: 8 From afff1adf8fc09c37703b8a79a2c97c97bf675501 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 12 Oct 2022 20:48:13 +0500 Subject: [PATCH 3/6] [FL-2882] BLE tiktok controller (#1859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bt hid: introduce tiktok controller * assets: add bt tiktok assets * bt hid: finish tiktok draw * bt hid: add input process to tiktok view * bt hid: add tiktok swipe emulation * bt hid: fix exit from tiktok controller * ble hid: add delay to emulate double tap * ble hid: change options order * bt hid: build as external application * ble hid: fix naming Co-authored-by: あく --- applications/plugins/application.fam | 1 - .../plugins/bt_hid_app/application.fam | 11 +- .../plugins/bt_hid_app/assets/Arr_dwn_7x9.png | Bin 0 -> 3602 bytes .../plugins/bt_hid_app/assets/Arr_up_7x9.png | Bin 0 -> 3605 bytes .../bt_hid_app/assets/Ble_connected_15x15.png | Bin 0 -> 3634 bytes .../assets/Ble_disconnected_15x15.png | Bin 0 -> 3632 bytes .../bt_hid_app/assets/Button_18x18.png | Bin 0 -> 3609 bytes .../bt_hid_app/assets/Circles_47x47.png | Bin 0 -> 3712 bytes .../bt_hid_app/assets/Left_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../bt_hid_app/assets/Like_def_11x9.png | Bin 0 -> 3616 bytes .../bt_hid_app/assets/Like_pressed_17x17.png | Bin 0 -> 3643 bytes .../assets/Ok_btn_pressed_13x13.png | Bin 0 -> 3625 bytes .../assets/Pressed_Button_13x13.png | Bin 0 -> 3606 bytes .../assets/Right_mouse_icon_9x9.png | Bin 0 -> 3622 bytes .../plugins/bt_hid_app/assets/Space_65x18.png | Bin 0 -> 3619 bytes .../plugins/bt_hid_app/assets/Voldwn_6x6.png | Bin 0 -> 3593 bytes .../plugins/bt_hid_app/assets/Volup_8x6.png | Bin 0 -> 3595 bytes applications/plugins/bt_hid_app/bt_hid.c | 16 ++ applications/plugins/bt_hid_app/bt_hid.h | 3 + .../bt_hid_app/views/bt_hid_keyboard.c | 2 + .../plugins/bt_hid_app/views/bt_hid_keynote.c | 2 + .../plugins/bt_hid_app/views/bt_hid_media.c | 2 + .../plugins/bt_hid_app/views/bt_hid_mouse.c | 2 + .../plugins/bt_hid_app/views/bt_hid_tiktok.c | 207 ++++++++++++++++++ .../plugins/bt_hid_app/views/bt_hid_tiktok.h | 13 ++ 25 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Arr_up_7x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Ble_connected_15x15.png create mode 100644 applications/plugins/bt_hid_app/assets/Ble_disconnected_15x15.png create mode 100644 applications/plugins/bt_hid_app/assets/Button_18x18.png create mode 100644 applications/plugins/bt_hid_app/assets/Circles_47x47.png create mode 100644 applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Like_def_11x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Like_pressed_17x17.png create mode 100644 applications/plugins/bt_hid_app/assets/Ok_btn_pressed_13x13.png create mode 100644 applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png create mode 100644 applications/plugins/bt_hid_app/assets/Right_mouse_icon_9x9.png create mode 100644 applications/plugins/bt_hid_app/assets/Space_65x18.png create mode 100644 applications/plugins/bt_hid_app/assets/Voldwn_6x6.png create mode 100644 applications/plugins/bt_hid_app/assets/Volup_8x6.png create mode 100644 applications/plugins/bt_hid_app/views/bt_hid_tiktok.c create mode 100644 applications/plugins/bt_hid_app/views/bt_hid_tiktok.h diff --git a/applications/plugins/application.fam b/applications/plugins/application.fam index c88f6d289..6d25e45aa 100644 --- a/applications/plugins/application.fam +++ b/applications/plugins/application.fam @@ -5,6 +5,5 @@ App( provides=[ "music_player", "snake_game", - "bt_hid", ], ) diff --git a/applications/plugins/bt_hid_app/application.fam b/applications/plugins/bt_hid_app/application.fam index e6a3b1752..2712fded7 100644 --- a/applications/plugins/bt_hid_app/application.fam +++ b/applications/plugins/bt_hid_app/application.fam @@ -1,15 +1,10 @@ App( appid="bt_hid", name="Bluetooth Remote", - apptype=FlipperAppType.PLUGIN, + apptype=FlipperAppType.EXTERNAL, entry_point="bt_hid_app", stack_size=1 * 1024, - cdefines=["APP_BLE_HID"], - requires=[ - "bt", - "gui", - ], - order=10, - fap_icon="bt_remote_10px.png", fap_category="Tools", + fap_icon="bt_remote_10px.png", + fap_icon_assets="assets", ) diff --git a/applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png b/applications/plugins/bt_hid_app/assets/Arr_dwn_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..d4034efc432b102f6f751d001dc6891b0763c55c GIT binary patch literal 3602 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 JTZ;Aw|2NmVF(Lo} literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Arr_up_7x9.png b/applications/plugins/bt_hid_app/assets/Arr_up_7x9.png new file mode 100644 index 0000000000000000000000000000000000000000..28b4236a292708b412629ffafe30f6d011491505 GIT binary patch literal 3605 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Circles_47x47.png b/applications/plugins/bt_hid_app/assets/Circles_47x47.png new file mode 100644 index 0000000000000000000000000000000000000000..6a16ebf7bbe999fa143c683bef713a6e2f466cbb GIT binary patch literal 3712 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 biU<_Yn+9Io=xs3K`2!qm58ISjpSt)z2v?8| literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png b/applications/plugins/bt_hid_app/assets/Left_mouse_icon_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..c533d85729f9b778cba33227141c90dea298f5e3 GIT binary patch literal 3622 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Like_def_11x9.png b/applications/plugins/bt_hid_app/assets/Like_def_11x9.png new file mode 100644 index 0000000000000000000000000000000000000000..555bea3d4b0239ae1be630d0306316d3e9494c4c GIT binary patch literal 3616 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png b/applications/plugins/bt_hid_app/assets/Pressed_Button_13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..823926b842b8f9868fd70d6f1434dff071c04d5d GIT binary patch literal 3606 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/bt_hid_app/assets/Voldwn_6x6.png b/applications/plugins/bt_hid_app/assets/Voldwn_6x6.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a82a2df8262667a9a03419f437ff9b350e645f GIT binary patch literal 3593 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 = BtHidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); + } else if(index == BtHidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); } } @@ -65,6 +69,7 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); + bt_hid_tiktok_set_connected_status(bt_hid->bt_hid_tiktok, connected); } BtHid* bt_hid_app_alloc() { @@ -91,6 +96,8 @@ BtHid* bt_hid_app_alloc() { submenu_add_item( app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "TikTok Controller", BtHidSubmenuIndexTikTok, bt_hid_submenu_callback, app); submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); view_dispatcher_add_view( @@ -127,6 +134,13 @@ BtHid* bt_hid_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); + // TikTok view + app->bt_hid_tiktok = bt_hid_tiktok_alloc(); + view_set_previous_callback( + bt_hid_tiktok_get_view(app->bt_hid_tiktok), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewTikTok, bt_hid_tiktok_get_view(app->bt_hid_tiktok)); + // Mouse view app->bt_hid_mouse = bt_hid_mouse_alloc(); view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); @@ -159,6 +173,8 @@ void bt_hid_app_free(BtHid* app) { bt_hid_media_free(app->bt_hid_media); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); bt_hid_mouse_free(app->bt_hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + bt_hid_tiktok_free(app->bt_hid_tiktok); view_dispatcher_free(app->view_dispatcher); // Close records diff --git a/applications/plugins/bt_hid_app/bt_hid.h b/applications/plugins/bt_hid_app/bt_hid.h index 0f4c7be9f..89e8807fe 100644 --- a/applications/plugins/bt_hid_app/bt_hid.h +++ b/applications/plugins/bt_hid_app/bt_hid.h @@ -13,6 +13,7 @@ #include "views/bt_hid_keyboard.h" #include "views/bt_hid_media.h" #include "views/bt_hid_mouse.h" +#include "views/bt_hid_tiktok.h" typedef struct { Bt* bt; @@ -25,6 +26,7 @@ typedef struct { BtHidKeyboard* bt_hid_keyboard; BtHidMedia* bt_hid_media; BtHidMouse* bt_hid_mouse; + BtHidTikTok* bt_hid_tiktok; uint32_t view_id; } BtHid; @@ -34,5 +36,6 @@ typedef enum { BtHidViewKeyboard, BtHidViewMedia, BtHidViewMouse, + BtHidViewTikTok, BtHidViewExitConfirm, } BtHidView; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c index 2c65f6ab1..a1077b798 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keyboard.c @@ -5,6 +5,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidKeyboard { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c index db88b8000..0e81c5fa0 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_keynote.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidKeynote { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_media.c b/applications/plugins/bt_hid_app/views/bt_hid_media.c index 181cd347b..df7349a97 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_media.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_media.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidMedia { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c index 098adb732..bd48bab16 100644 --- a/applications/plugins/bt_hid_app/views/bt_hid_mouse.c +++ b/applications/plugins/bt_hid_app/views/bt_hid_mouse.c @@ -4,6 +4,8 @@ #include #include +#include "bt_hid_icons.h" + struct BtHidMouse { View* view; }; diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c new file mode 100644 index 000000000..9af00157d --- /dev/null +++ b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.c @@ -0,0 +1,207 @@ +#include "bt_hid_tiktok.h" +#include +#include +#include +#include + +#include "bt_hid_icons.h" + +struct BtHidTikTok { + View* view; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; +} BtHidTikTokModel; + +static void bt_hid_tiktok_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidTikTokModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 91, 23, &I_Like_pressed_17x17); + } else { + canvas_draw_icon(canvas, 94, 27, &I_Like_def_11x9); + } + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void bt_hid_tiktok_process_press(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { + with_view_model( + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + } + }, + true); +} + +static void bt_hid_tiktok_process_release(BtHidTikTok* bt_hid_tiktok, InputEvent* event) { + with_view_model( + bt_hid_tiktok->view, + BtHidTikTokModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + } + }, + true); +} + +static bool bt_hid_tiktok_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidTikTok* bt_hid_tiktok = context; + bool consumed = false; + + if(event->type == InputTypePress) { + bt_hid_tiktok_process_press(bt_hid_tiktok, event); + consumed = true; + } else if(event->type == InputTypeRelease) { + bt_hid_tiktok_process_release(bt_hid_tiktok, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_delay_ms(50); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + consumed = true; + } else if(event->key == InputKeyUp) { + // Emulate up swipe + furi_hal_bt_hid_mouse_scroll(-6); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-19); + furi_hal_bt_hid_mouse_scroll(-12); + furi_hal_bt_hid_mouse_scroll(-6); + consumed = true; + } else if(event->key == InputKeyDown) { + // Emulate down swipe + furi_hal_bt_hid_mouse_scroll(6); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(19); + furi_hal_bt_hid_mouse_scroll(12); + furi_hal_bt_hid_mouse_scroll(6); + consumed = true; + } else if(event->key == InputKeyBack) { + furi_hal_bt_hid_consumer_key_release_all(); + consumed = true; + } + } + + return consumed; +} + +BtHidTikTok* bt_hid_tiktok_alloc() { + BtHidTikTok* bt_hid_tiktok = malloc(sizeof(BtHidTikTok)); + bt_hid_tiktok->view = view_alloc(); + view_set_context(bt_hid_tiktok->view, bt_hid_tiktok); + view_allocate_model(bt_hid_tiktok->view, ViewModelTypeLocking, sizeof(BtHidTikTokModel)); + view_set_draw_callback(bt_hid_tiktok->view, bt_hid_tiktok_draw_callback); + view_set_input_callback(bt_hid_tiktok->view, bt_hid_tiktok_input_callback); + + return bt_hid_tiktok; +} + +void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok) { + furi_assert(bt_hid_tiktok); + view_free(bt_hid_tiktok->view); + free(bt_hid_tiktok); +} + +View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok) { + furi_assert(bt_hid_tiktok); + return bt_hid_tiktok->view; +} + +void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected) { + furi_assert(bt_hid_tiktok); + with_view_model( + bt_hid_tiktok->view, BtHidTikTokModel * model, { model->connected = connected; }, true); +} diff --git a/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h new file mode 100644 index 000000000..03c9afeca --- /dev/null +++ b/applications/plugins/bt_hid_app/views/bt_hid_tiktok.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidTikTok BtHidTikTok; + +BtHidTikTok* bt_hid_tiktok_alloc(); + +void bt_hid_tiktok_free(BtHidTikTok* bt_hid_tiktok); + +View* bt_hid_tiktok_get_view(BtHidTikTok* bt_hid_tiktok); + +void bt_hid_tiktok_set_connected_status(BtHidTikTok* bt_hid_tiktok, bool connected); From eb4ff3c0fd4cee9e710732d98cc728bbcf2d31cf Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 12 Oct 2022 20:12:05 +0400 Subject: [PATCH 4/6] [FL-2832] fbt: more fixes & improvements (#1854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github: bundling debug folder with scripts; docs: fixes & updates; fbt: added FAP_EXAMPLES variable to enable building example apps. Disabled by default. fbt: added TERM to list of proxied environment variables * fbt: better help output; disabled implicit_deps_unchanged; added color to import validator reports * fbt: moved debug configuration to separate tool * fbt: proper dependency tracker for SDK source file; renamed linker script for external apps * fbt: fixed debug elf path * fbt: packaging sdk archive * scripts: fixed sconsdist.py * fbt: reworked sdk packing; docs: updates * docs: info on cli target; linter fixes * fbt: moved main code to scripts folder * scripts: packing update into .tgz * fbt, scripts: reworked copro_dist to build .tgz * scripts: fixed naming for archived updater package * Scripts: fix ぐるぐる回る Co-authored-by: Aleksandr Kutuzov --- .github/workflows/build.yml | 18 +- SConstruct | 63 ++--- .../examples/example_images/example_images.c | 2 + documentation/AppManifests.md | 4 +- documentation/AppsOnSDCard.md | 4 +- documentation/fbt.md | 3 +- firmware.scons | 17 +- ...{application-ext.ld => application_ext.ld} | 0 {site_scons => scripts}/fbt/__init__.py | 0 {site_scons => scripts}/fbt/appmanifest.py | 0 {site_scons => scripts}/fbt/elfmanifest.py | 0 {site_scons => scripts}/fbt/sdk.py | 0 {site_scons => scripts}/fbt/util.py | 22 -- {site_scons => scripts}/fbt/version.py | 0 .../fbt_tools}/blackmagic.py | 0 .../fbt_tools}/ccache.py | 0 .../fbt_tools}/crosscc.py | 0 .../fbt_tools}/fbt_apps.py | 0 .../fbt_tools}/fbt_assets.py | 0 scripts/fbt_tools/fbt_debugopts.py | 41 ++++ .../fbt_tools}/fbt_dist.py | 3 +- .../fbt_tools}/fbt_extapps.py | 8 +- scripts/fbt_tools/fbt_help.py | 44 ++++ .../fbt_tools}/fbt_sdk.py | 80 ++++++- .../fbt_tools}/fbt_version.py | 0 .../site_tools => scripts/fbt_tools}/fwbin.py | 0 .../site_tools => scripts/fbt_tools}/gdb.py | 0 .../fbt_tools}/jflash.py | 0 .../fbt_tools}/objdump.py | 0 .../fbt_tools}/openocd.py | 0 .../fbt_tools}/python3.py | 0 .../fbt_tools}/sconsmodular.py | 0 .../fbt_tools}/sconsrecursiveglob.py | 0 .../site_tools => scripts/fbt_tools}/strip.py | 0 scripts/flipper/assets/copro.py | 36 +-- scripts/guruguru.py | 2 +- scripts/sconsdist.py | 71 ++++-- site_scons/commandline.scons | 224 ++++++++---------- site_scons/environ.scons | 17 +- site_scons/extapps.scons | 3 +- site_scons/fbt_extra/util.py | 23 ++ 41 files changed, 413 insertions(+), 272 deletions(-) rename firmware/targets/f7/{application-ext.ld => application_ext.ld} (100%) rename {site_scons => scripts}/fbt/__init__.py (100%) rename {site_scons => scripts}/fbt/appmanifest.py (100%) rename {site_scons => scripts}/fbt/elfmanifest.py (100%) rename {site_scons => scripts}/fbt/sdk.py (100%) rename {site_scons => scripts}/fbt/util.py (58%) rename {site_scons => scripts}/fbt/version.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/blackmagic.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/ccache.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/crosscc.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_apps.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_assets.py (100%) create mode 100644 scripts/fbt_tools/fbt_debugopts.py rename {site_scons/site_tools => scripts/fbt_tools}/fbt_dist.py (98%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_extapps.py (96%) create mode 100644 scripts/fbt_tools/fbt_help.py rename {site_scons/site_tools => scripts/fbt_tools}/fbt_sdk.py (71%) rename {site_scons/site_tools => scripts/fbt_tools}/fbt_version.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/fwbin.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/gdb.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/jflash.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/objdump.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/openocd.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/python3.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/sconsmodular.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/sconsrecursiveglob.py (100%) rename {site_scons/site_tools => scripts/fbt_tools}/strip.py (100%) create mode 100644 site_scons/fbt_extra/util.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1304c5d7b..8fb67ed1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,14 +56,14 @@ jobs: - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts + tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug - name: 'Build the firmware' run: | set -e for TARGET in ${TARGETS}; do FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ - updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} + copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done - name: 'Move upload files' @@ -74,17 +74,6 @@ jobs: mv dist/${TARGET}-*/* artifacts/ done - - name: 'Bundle self-update package' - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - set -e - for UPDATEBUNDLE in artifacts/*/; do - BUNDLE_NAME="$(echo "$UPDATEBUNDLE" | cut -d'/' -f2)" - echo Packaging "${BUNDLE_NAME}" - tar czpf "artifacts/flipper-z-${BUNDLE_NAME}.tgz" -C artifacts "${BUNDLE_NAME}" - rm -rf "artifacts/${BUNDLE_NAME}" - done - - name: "Check for uncommitted changes" run: | git diff --exit-code @@ -97,8 +86,7 @@ jobs: - name: 'Bundle core2 firmware' if: ${{ !github.event.pull_request.head.repo.fork }} run: | - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt copro_dist - tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware + cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - name: 'Copy .map file' run: | diff --git a/SConstruct b/SConstruct index 5ad2ac3c8..74fa5667b 100644 --- a/SConstruct +++ b/SConstruct @@ -7,7 +7,6 @@ # construction of certain targets behind command-line options. import os -import subprocess DefaultEnvironment(tools=[]) @@ -15,17 +14,22 @@ EnsurePythonVersion(3, 8) # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) - # This environment is created only for loading options & validating file/dir existence fbt_variables = SConscript("site_scons/commandline.scons") -cmd_environment = Environment(tools=[], variables=fbt_variables) -Help(fbt_variables.GenerateHelpText(cmd_environment)) +cmd_environment = Environment( + toolpath=["#/scripts/fbt_tools"], + tools=[ + ("fbt_help", {"vars": fbt_variables}), + ], + variables=fbt_variables, +) # Building basic environment - tools, utility methods, cross-compilation # settings, gcc flags for Cortex-M4, basic builders and more coreenv = SConscript( "site_scons/environ.scons", exports={"VAR_ENV": cmd_environment}, + toolpath=["#/scripts/fbt_tools"], ) SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) @@ -35,41 +39,13 @@ coreenv["ROOT_DIR"] = Dir(".") # Create a separate "dist" environment and add construction envs to it distenv = coreenv.Clone( - tools=["fbt_dist", "openocd", "blackmagic", "jflash"], - OPENOCD_GDB_PIPE=[ - "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" + tools=[ + "fbt_dist", + "fbt_debugopts", + "openocd", + "blackmagic", + "jflash", ], - GDBOPTS_BASE=[ - "-ex", - "target extended-remote ${GDBREMOTE}", - "-ex", - "set confirm off", - "-ex", - "set pagination off", - ], - GDBOPTS_BLACKMAGIC=[ - "-ex", - "monitor swdp_scan", - "-ex", - "monitor debug_bmp enable", - "-ex", - "attach 1", - "-ex", - "set mem inaccessible-by-default off", - ], - GDBPYOPTS=[ - "-ex", - "source debug/FreeRTOS/FreeRTOS.py", - "-ex", - "source debug/flipperapps.py", - "-ex", - "source debug/PyCortexMDebug/PyCortexMDebug.py", - "-ex", - "svd_load ${SVD_FILE}", - "-ex", - "compare-sections", - ], - JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", ENV=os.environ, ) @@ -166,7 +142,7 @@ basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) distenv.Default(basic_dist) dist_dir = distenv.GetProjetDirName() -plugin_dist = [ +fap_dist = [ distenv.Install( f"#/dist/{dist_dir}/apps/debug_elf", firmware_env["FW_EXTAPPS"]["debug"].values(), @@ -176,9 +152,9 @@ plugin_dist = [ for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() ), ] -Depends(plugin_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) -Alias("plugin_dist", plugin_dist) -# distenv.Default(plugin_dist) +Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) +Alias("fap_dist", fap_dist) +# distenv.Default(fap_dist) plugin_resources_dist = list( distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1]) @@ -189,9 +165,10 @@ distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist) # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( - distenv.Dir("assets/core2_firmware"), + "#/build/core2_firmware.tgz", [], ) +distenv.AlwaysBuild(copro_dist) distenv.Alias("copro_dist", copro_dist) firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index 48fa5e77e..b00818cd6 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -4,6 +4,8 @@ #include #include +/* Magic happens here -- this file is generated by fbt. + * Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */ #include "example_images_icons.h" typedef struct { diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 4fec9d22c..c7c73110b 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -30,7 +30,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio | METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | * **name**: Name that is displayed in menus. -* **entry_point**: C function to be used as application's entry point. +* **entry_point**: C function to be used as application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` in order to use them as entry points. * **flags**: Internal flags for system apps. Do not use. * **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. * **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. @@ -55,7 +55,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): * **fap_author**: string, may be empty. Application's author. * **fap_weburl**: string, may be empty. Application's homepage. * **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. -* **fap_extbuild**: provides support for parts of application sources to be build by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +* **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Example for building an app from Rust sources: diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index aff8314dc..4acb3ec37 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -2,7 +2,7 @@ [fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. -FAPs are built with `faps` **`fbt`** target. They can also be deployed to `dist` folder with `plugin_dist` **`fbt`** target. +FAPs are built with `faps` target. They can also be deployed to `dist` folder with `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). @@ -15,7 +15,7 @@ To build your application as a FAP, just create a folder with your app's source * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). - * To build all FAPs, run `./fbt plugin_dist`. + * To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets diff --git a/documentation/fbt.md b/documentation/fbt.md index e20d43177..3fac7ce75 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -43,7 +43,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. ### High-level (what you most likely need) - `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified -- `plugin_dist` - build external plugins & publish to `dist` folder +- `fap_dist` - build external plugins & publish to `dist` folder - `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link @@ -56,6 +56,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option. - `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration - `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests +- `cli` - start Flipper CLI session over USB ### Firmware targets diff --git a/firmware.scons b/firmware.scons index dd13b6b3d..d28309d0e 100644 --- a/firmware.scons +++ b/firmware.scons @@ -3,7 +3,7 @@ Import("ENV", "fw_build_meta") from SCons.Errors import UserError import itertools -from fbt.util import ( +from fbt_extra.util import ( should_gen_cdb_and_link_dir, link_elf_dir_as_latest, ) @@ -141,6 +141,10 @@ else: if extra_int_apps := GetOption("extra_int_apps"): fwenv.Append(APPS=extra_int_apps.split(",")) + +if fwenv["FAP_EXAMPLES"]: + fwenv.Append(APPDIRS=[("applications/examples", False)]) + fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() @@ -316,10 +320,13 @@ if fwenv["IS_BASE_FIRMWARE"]: "-D__inline__=inline", ], ) - Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) + # Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) + Depends(sdk_source, fwenv.ProcessSdkDepends("sdk_origin.d")) - sdk_tree = fwenv.SDKTree("sdk/sdk.opts", "sdk_origin") - AlwaysBuild(sdk_tree) + fwenv["SDK_DIR"] = fwenv.Dir("sdk") + sdk_tree = fwenv.SDKTree(fwenv["SDK_DIR"], "sdk_origin") + fw_artifacts.append(sdk_tree) + # AlwaysBuild(sdk_tree) Alias("sdk_tree", sdk_tree) sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin") @@ -329,7 +336,7 @@ if fwenv["IS_BASE_FIRMWARE"]: Alias("sdk_check", sdk_apicheck) sdk_apisyms = fwenv.SDKSymGenerator( - "assets/compiled/symbols.h", fwenv.subst("$SDK_DEFINITION") + "assets/compiled/symbols.h", fwenv["SDK_DEFINITION"] ) Alias("api_syms", sdk_apisyms) diff --git a/firmware/targets/f7/application-ext.ld b/firmware/targets/f7/application_ext.ld similarity index 100% rename from firmware/targets/f7/application-ext.ld rename to firmware/targets/f7/application_ext.ld diff --git a/site_scons/fbt/__init__.py b/scripts/fbt/__init__.py similarity index 100% rename from site_scons/fbt/__init__.py rename to scripts/fbt/__init__.py diff --git a/site_scons/fbt/appmanifest.py b/scripts/fbt/appmanifest.py similarity index 100% rename from site_scons/fbt/appmanifest.py rename to scripts/fbt/appmanifest.py diff --git a/site_scons/fbt/elfmanifest.py b/scripts/fbt/elfmanifest.py similarity index 100% rename from site_scons/fbt/elfmanifest.py rename to scripts/fbt/elfmanifest.py diff --git a/site_scons/fbt/sdk.py b/scripts/fbt/sdk.py similarity index 100% rename from site_scons/fbt/sdk.py rename to scripts/fbt/sdk.py diff --git a/site_scons/fbt/util.py b/scripts/fbt/util.py similarity index 58% rename from site_scons/fbt/util.py rename to scripts/fbt/util.py index 8d8af5183..baa4ddfee 100644 --- a/site_scons/fbt/util.py +++ b/scripts/fbt/util.py @@ -41,25 +41,3 @@ def link_dir(target_path, source_path, is_windows): def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) - - -def link_elf_dir_as_latest(env, elf_node): - elf_dir = elf_node.Dir(".") - latest_dir = env.Dir("#build/latest") - print(f"Setting {elf_dir} as latest built dir (./build/latest/)") - return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") - - -def should_gen_cdb_and_link_dir(env, requested_targets): - explicitly_building_updater = False - # Hacky way to check if updater-related targets were requested - for build_target in requested_targets: - if "updater" in str(build_target): - explicitly_building_updater = True - - is_updater = not env["IS_BASE_FIRMWARE"] - # If updater is explicitly requested, link to the latest updater - # Otherwise, link to firmware - return (is_updater and explicitly_building_updater) or ( - not is_updater and not explicitly_building_updater - ) diff --git a/site_scons/fbt/version.py b/scripts/fbt/version.py similarity index 100% rename from site_scons/fbt/version.py rename to scripts/fbt/version.py diff --git a/site_scons/site_tools/blackmagic.py b/scripts/fbt_tools/blackmagic.py similarity index 100% rename from site_scons/site_tools/blackmagic.py rename to scripts/fbt_tools/blackmagic.py diff --git a/site_scons/site_tools/ccache.py b/scripts/fbt_tools/ccache.py similarity index 100% rename from site_scons/site_tools/ccache.py rename to scripts/fbt_tools/ccache.py diff --git a/site_scons/site_tools/crosscc.py b/scripts/fbt_tools/crosscc.py similarity index 100% rename from site_scons/site_tools/crosscc.py rename to scripts/fbt_tools/crosscc.py diff --git a/site_scons/site_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py similarity index 100% rename from site_scons/site_tools/fbt_apps.py rename to scripts/fbt_tools/fbt_apps.py diff --git a/site_scons/site_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py similarity index 100% rename from site_scons/site_tools/fbt_assets.py rename to scripts/fbt_tools/fbt_assets.py diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py new file mode 100644 index 000000000..3e7b07010 --- /dev/null +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -0,0 +1,41 @@ +def generate(env, **kw): + env.SetDefault( + OPENOCD_GDB_PIPE=[ + "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" + ], + GDBOPTS_BASE=[ + "-ex", + "target extended-remote ${GDBREMOTE}", + "-ex", + "set confirm off", + "-ex", + "set pagination off", + ], + GDBOPTS_BLACKMAGIC=[ + "-ex", + "monitor swdp_scan", + "-ex", + "monitor debug_bmp enable", + "-ex", + "attach 1", + "-ex", + "set mem inaccessible-by-default off", + ], + GDBPYOPTS=[ + "-ex", + "source debug/FreeRTOS/FreeRTOS.py", + "-ex", + "source debug/flipperapps.py", + "-ex", + "source debug/PyCortexMDebug/PyCortexMDebug.py", + "-ex", + "svd_load ${SVD_FILE}", + "-ex", + "compare-sections", + ], + JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", + ) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py similarity index 98% rename from site_scons/site_tools/fbt_dist.py rename to scripts/fbt_tools/fbt_dist.py index 2b5c83dff..853013e9f 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -136,7 +136,6 @@ def generate(env): "CoproBuilder": Builder( action=Action( [ - Mkdir("$TARGET"), '${PYTHON3} "${ROOT_DIR.abspath}/scripts/assets.py" ' "copro ${COPRO_CUBE_DIR} " "${TARGET} ${COPRO_MCU_FAMILY} " @@ -145,7 +144,7 @@ def generate(env): '--stack_file="${COPRO_STACK_BIN}" ' "--stack_addr=${COPRO_STACK_ADDR} ", ], - "", + "\tCOPRO\t${TARGET}", ) ), } diff --git a/site_scons/site_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py similarity index 96% rename from site_scons/site_tools/fbt_extapps.py rename to scripts/fbt_tools/fbt_extapps.py index a1fa77140..34fb942ab 100644 --- a/site_scons/site_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -6,12 +6,10 @@ import SCons.Warnings import os import pathlib from fbt.elfmanifest import assemble_manifest_data -from fbt.appmanifest import FlipperManifestException +from fbt.appmanifest import FlipperApplication, FlipperManifestException from fbt.sdk import SdkCache import itertools -from site_scons.fbt.appmanifest import FlipperApplication - def BuildAppElf(env, app): ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") @@ -111,7 +109,7 @@ def BuildAppElf(env, app): ) app_elf_raw = app_env.Program( - os.path.join(app_work_dir, f"{app.appid}_d"), + os.path.join(ext_apps_work_dir, f"{app.appid}_d"), app_sources, APP_ENTRY=app.entry_point, ) @@ -180,7 +178,7 @@ def validate_app_imports(target, source, env): if unresolved_syms: SCons.Warnings.warn( SCons.Warnings.LinkWarning, - f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}", + f"\033[93m{source[0].path}: app won't run. Unresolved symbols: \033[95m{unresolved_syms}\033[0m", ) diff --git a/scripts/fbt_tools/fbt_help.py b/scripts/fbt_tools/fbt_help.py new file mode 100644 index 000000000..0475f51bc --- /dev/null +++ b/scripts/fbt_tools/fbt_help.py @@ -0,0 +1,44 @@ +targets_help = """Configuration variables: +""" + +tail_help = """ + +TASKS: +Building: + firmware_all, fw_dist: + Build firmware; create distribution package + faps, fap_dist: + Build all FAP apps + fap_{APPID}, launch_app APPSRC={APPID}: + Build FAP app with appid={APPID}; upload & start it over USB + +Flashing & debugging: + flash, flash_blackmagic, jflash: + Flash firmware to target using debug probe + flash_usb, flash_usb_full: + Install firmware using self-update package + debug, debug_other, blackmagic: + Start GDB + +Other: + cli: + Open a Flipper CLI session over USB + firmware_cdb, updater_cdb: + Generate сompilation_database.json + lint, lint_py: + run linters + format, format_py: + run code formatters + +For more targets & info, see documentation/fbt.md +""" + + +def generate(env, **kw): + vars = kw["vars"] + basic_help = vars.GenerateHelpText(env) + env.Help(targets_help + basic_help + tail_help) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py similarity index 71% rename from site_scons/site_tools/fbt_sdk.py rename to scripts/fbt_tools/fbt_sdk.py index f6c2d452e..ed0abdff6 100644 --- a/site_scons/site_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -9,10 +9,32 @@ from SCons.Util import LogicalLines import os.path import posixpath import pathlib +import json from fbt.sdk import SdkCollector, SdkCache +def ProcessSdkDepends(env, filename): + try: + with open(filename, "r") as fin: + lines = LogicalLines(fin).readlines() + except IOError: + return [] + + _, depends = lines[0].split(":", 1) + depends = depends.split() + depends.pop(0) # remove the .c file + depends = list( + # Don't create dependency on non-existing files + # (e.g. when they were renamed since last build) + filter( + lambda file: file.exists(), + (env.File(f"#{path}") for path in depends), + ) + ) + return depends + + def prebuild_sdk_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) @@ -25,6 +47,25 @@ def prebuild_sdk_create_origin_file(target, source, env): sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"])) +class SdkMeta: + def __init__(self, env): + self.env = env + + def save_to(self, json_manifest_path: str): + meta_contents = { + "sdk_symbols": self.env["SDK_DEFINITION"].name, + "cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"), + "cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"), + "linker_args": self._wrap_scons_vars("$LINKFLAGS"), + } + with open(json_manifest_path, "wt") as f: + json.dump(meta_contents, f, indent=4) + + def _wrap_scons_vars(self, vars: str): + expanded_vars = self.env.subst(vars, target=Entry("dummy")) + return expanded_vars.replace("\\", "/") + + class SdkTreeBuilder: def __init__(self, env, target, source) -> None: self.env = env @@ -34,8 +75,9 @@ class SdkTreeBuilder: self.header_depends = [] self.header_dirs = [] - self.target_sdk_dir = env.subst("f${TARGET_HW}_sdk") - self.sdk_deploy_dir = target[0].Dir(self.target_sdk_dir) + self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk") + self.sdk_root_dir = target[0].Dir(".") + self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name) def _parse_sdk_depends(self): deps_file = self.source[0] @@ -50,7 +92,7 @@ class SdkTreeBuilder: ) def _generate_sdk_meta(self): - filtered_paths = [self.target_sdk_dir] + filtered_paths = [self.target_sdk_dir_name] full_fw_paths = list( map( os.path.normpath, @@ -62,17 +104,18 @@ class SdkTreeBuilder: for dir in full_fw_paths: if dir in sdk_dirs: filtered_paths.append( - posixpath.normpath(posixpath.join(self.target_sdk_dir, dir)) + posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir)) ) sdk_env = self.env.Clone() sdk_env.Replace(CPPPATH=filtered_paths) - with open(self.target[0].path, "wt") as f: - cmdline_options = sdk_env.subst( - "$CCFLAGS $_CCCOMCOM", target=Entry("dummy") - ) - f.write(cmdline_options.replace("\\", "/")) - f.write("\n") + meta = SdkMeta(sdk_env) + meta.save_to(self.target[0].path) + + def emitter(self, target, source, env): + target_folder = target[0] + target = [target_folder.File("sdk.opts")] + return target, source def _create_deploy_commands(self): dirs_to_create = set( @@ -81,13 +124,17 @@ class SdkTreeBuilder: actions = [ Delete(self.sdk_deploy_dir), Mkdir(self.sdk_deploy_dir), + Copy( + self.sdk_root_dir, + self.env["SDK_DEFINITION"], + ), ] actions += [Mkdir(d) for d in dirs_to_create] actions += [ - Copy( - self.sdk_deploy_dir.File(h).path, - h, + Action( + Copy(self.sdk_deploy_dir.File(h).path, h), + # f"Copy {h} to {self.sdk_deploy_dir}", ) for h in self.header_depends ] @@ -108,6 +155,11 @@ def deploy_sdk_tree(target, source, env, for_signature): return sdk_tree.generate_actions() +def deploy_sdk_tree_emitter(target, source, env): + sdk_tree = SdkTreeBuilder(env, target, source) + return sdk_tree.emitter(target, source, env) + + def gen_sdk_data(sdk_cache: SdkCache): api_def = [] api_def.extend( @@ -165,6 +217,7 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): + env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ "SDKPrebuilder": Builder( @@ -183,6 +236,7 @@ def generate(env, **kw): ), "SDKTree": Builder( generator=deploy_sdk_tree, + emitter=deploy_sdk_tree_emitter, src_suffix=".d", ), "SDKSymUpdater": Builder( diff --git a/site_scons/site_tools/fbt_version.py b/scripts/fbt_tools/fbt_version.py similarity index 100% rename from site_scons/site_tools/fbt_version.py rename to scripts/fbt_tools/fbt_version.py diff --git a/site_scons/site_tools/fwbin.py b/scripts/fbt_tools/fwbin.py similarity index 100% rename from site_scons/site_tools/fwbin.py rename to scripts/fbt_tools/fwbin.py diff --git a/site_scons/site_tools/gdb.py b/scripts/fbt_tools/gdb.py similarity index 100% rename from site_scons/site_tools/gdb.py rename to scripts/fbt_tools/gdb.py diff --git a/site_scons/site_tools/jflash.py b/scripts/fbt_tools/jflash.py similarity index 100% rename from site_scons/site_tools/jflash.py rename to scripts/fbt_tools/jflash.py diff --git a/site_scons/site_tools/objdump.py b/scripts/fbt_tools/objdump.py similarity index 100% rename from site_scons/site_tools/objdump.py rename to scripts/fbt_tools/objdump.py diff --git a/site_scons/site_tools/openocd.py b/scripts/fbt_tools/openocd.py similarity index 100% rename from site_scons/site_tools/openocd.py rename to scripts/fbt_tools/openocd.py diff --git a/site_scons/site_tools/python3.py b/scripts/fbt_tools/python3.py similarity index 100% rename from site_scons/site_tools/python3.py rename to scripts/fbt_tools/python3.py diff --git a/site_scons/site_tools/sconsmodular.py b/scripts/fbt_tools/sconsmodular.py similarity index 100% rename from site_scons/site_tools/sconsmodular.py rename to scripts/fbt_tools/sconsmodular.py diff --git a/site_scons/site_tools/sconsrecursiveglob.py b/scripts/fbt_tools/sconsrecursiveglob.py similarity index 100% rename from site_scons/site_tools/sconsrecursiveglob.py rename to scripts/fbt_tools/sconsrecursiveglob.py diff --git a/site_scons/site_tools/strip.py b/scripts/fbt_tools/strip.py similarity index 100% rename from site_scons/site_tools/strip.py rename to scripts/fbt_tools/strip.py diff --git a/scripts/flipper/assets/copro.py b/scripts/flipper/assets/copro.py index 0c78e889d..33c3ac237 100644 --- a/scripts/flipper/assets/copro.py +++ b/scripts/flipper/assets/copro.py @@ -1,10 +1,9 @@ import logging -import datetime -import shutil import json -from os.path import basename - +from io import BytesIO +import tarfile import xml.etree.ElementTree as ET + from flipper.utils import * from flipper.assets.coprobin import CoproBinary, get_stack_type @@ -51,20 +50,19 @@ class Copro: raise Exception(f"Unsupported cube version") self.version = cube_version + @staticmethod + def _getFileName(name): + return os.path.join("core2_firmware", name) + def addFile(self, array, filename, **kwargs): source_file = os.path.join(self.mcu_copro, filename) - destination_file = os.path.join(self.output_dir, filename) - shutil.copyfile(source_file, destination_file) - array.append( - {"name": filename, "sha256": file_sha256(destination_file), **kwargs} - ) + self.output_tar.add(source_file, arcname=self._getFileName(filename)) + array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs}) + + def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): + self.output_tar = tarfile.open(output_file, "w:gz") - def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None): - if not os.path.isdir(output_dir): - raise Exception(f'"{output_dir}" doesn\'t exists') - self.output_dir = output_dir stack_file = os.path.join(self.mcu_copro, stack_file_name) - manifest_file = os.path.join(self.output_dir, "Manifest.json") # Form Manifest manifest = dict(MANIFEST_TEMPLATE) manifest["manifest"]["timestamp"] = timestamp() @@ -105,6 +103,10 @@ class Copro: stack_file_name, address=f"0x{stack_addr:X}", ) - # Save manifest to - with open(manifest_file, "w", newline="\n") as file: - json.dump(manifest, file) + + # Save manifest + manifest_data = json.dumps(manifest, indent=4).encode("utf-8") + info = tarfile.TarInfo(self._getFileName("Manifest.json")) + info.size = len(manifest_data) + self.output_tar.addfile(info, BytesIO(manifest_data)) + self.output_tar.close() diff --git a/scripts/guruguru.py b/scripts/guruguru.py index 3560bcd96..c227e17ee 100755 --- a/scripts/guruguru.py +++ b/scripts/guruguru.py @@ -17,7 +17,7 @@ class Main(App): async def rebuild(self, line): self.clearConsole() self.logger.info(f"Triggered by: {line}") - proc = await asyncio.create_subprocess_exec("make") + proc = await asyncio.create_subprocess_exec("./fbt") await proc.wait() await asyncio.sleep(1) self.is_building = False diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 1e95ee2f2..45ae03e3b 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 from flipper.app import App -from os.path import join, exists -from os import makedirs +from os.path import join, exists, relpath +from os import makedirs, walk from update import Main as UpdateMain import shutil +import zipfile +import tarfile class ProjectDir: @@ -17,6 +19,8 @@ class ProjectDir: class Main(App): + DIST_FILE_PREFIX = "flipper-z-" + def init(self): self.subparsers = self.parser.add_subparsers(help="sub-command help") @@ -45,9 +49,13 @@ class Main(App): def get_project_filename(self, project, filetype): # Temporary fix project_name = project.project - if project_name == "firmware" and filetype != "elf": - project_name = "full" - return f"flipper-z-{self.target}-{project_name}-{self.args.suffix}.{filetype}" + if project_name == "firmware": + if filetype == "zip": + project_name = "sdk" + elif filetype != "elf": + project_name = "full" + + return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}" def get_dist_filepath(self, filename): return join(self.output_dir_path, filename) @@ -56,10 +64,28 @@ class Main(App): obj_directory = join("build", project.dir) for filetype in ("elf", "bin", "dfu", "json"): - shutil.copyfile( - join(obj_directory, f"{project.project}.{filetype}"), - self.get_dist_filepath(self.get_project_filename(project, filetype)), - ) + if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): + shutil.copyfile( + src_file, + self.get_dist_filepath( + self.get_project_filename(project, filetype) + ), + ) + if exists(sdk_folder := join(obj_directory, "sdk")): + with zipfile.ZipFile( + self.get_dist_filepath(self.get_project_filename(project, "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for root, dirs, files in walk(sdk_folder): + for file in files: + zf.write( + join(root, file), + relpath( + join(root, file), + sdk_folder, + ), + ) def copy(self): self.projects = dict( @@ -103,9 +129,8 @@ class Main(App): ) if self.args.version: - bundle_dir = join( - self.output_dir_path, f"{self.target}-update-{self.args.suffix}" - ) + bundle_dir_name = f"{self.target}-update-{self.args.suffix}" + bundle_dir = join(self.output_dir_path, bundle_dir_name) bundle_args = [ "generate", "-d", @@ -131,10 +156,24 @@ class Main(App): ) ) bundle_args.extend(self.other_args) - self.logger.info( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" - ) - return UpdateMain(no_exit=True)(bundle_args) + + if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: + self.logger.info( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) + + # Create tgz archive + with tarfile.open( + join( + self.output_dir_path, + f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", + ), + "w:gz", + compresslevel=9, + ) as tar: + tar.add(bundle_dir, arcname=bundle_dir_name) + + return bundle_result return 0 diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 765af08f1..324dfe332 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -81,46 +81,41 @@ vars.AddVariables( help="Enable debug tools to be built", default=False, ), -) - -vars.Add( - "DIST_SUFFIX", - help="Suffix for binaries in build output for dist targets", - default="local", -) - -vars.Add( - "UPDATE_VERSION_STRING", - help="Version string for updater package", - default="${DIST_SUFFIX}", -) - - -vars.Add( - "COPRO_CUBE_VERSION", - help="Cube version", - default="", -) - -vars.Add( - "COPRO_STACK_ADDR", - help="Core2 Firmware address", - default="0", -) - -vars.Add( - "COPRO_STACK_BIN", - help="Core2 Firmware file name", - default="", -) - -vars.Add( - "COPRO_DISCLAIMER", - help="Value to pass to bundling script to confirm dangerous operations", - default="", -) - -vars.AddVariables( + BoolVariable( + "FAP_EXAMPLES", + help="Enable example applications to be built", + default=False, + ), + ( + "DIST_SUFFIX", + "Suffix for binaries in build output for dist targets", + "local", + ), + ( + "UPDATE_VERSION_STRING", + "Version string for updater package", + "${DIST_SUFFIX}", + ), + ( + "COPRO_CUBE_VERSION", + "Cube version", + "", + ), + ( + "COPRO_STACK_ADDR", + "Core2 Firmware address", + "0", + ), + ( + "COPRO_STACK_BIN", + "Core2 Firmware file name", + "", + ), + ( + "COPRO_DISCLAIMER", + "Value to pass to bundling script to confirm dangerous operations", + "", + ), PathVariable( "COPRO_OB_DATA", help="Path to OB reference data", @@ -161,86 +156,75 @@ vars.AddVariables( validator=PathVariable.PathAccept, default="", ), -) - -vars.Add( - "FBT_TOOLCHAIN_VERSIONS", - help="Whitelisted toolchain versions (leave empty for no check)", - default=tuple(), -) - -vars.Add( - "OPENOCD_OPTS", - help="Options to pass to OpenOCD", - default="", -) - -vars.Add( - "BLACKMAGIC", - help="Blackmagic probe location", - default="auto", -) - -vars.Add( - "UPDATE_SPLASH", - help="Directory name with slideshow frames to render after installing update package", - default="update_default", -) - -vars.Add( - "LOADER_AUTOSTART", - help="Application name to automatically run on Flipper boot", - default="", -) - - -vars.Add( - "FIRMWARE_APPS", - help="Map of (configuration_name->application_list)", - default={ - "default": ( - # Svc - "basic_services", - # Apps - "main_apps", - "system_apps", - # Settings - "settings_apps", - # Plugins - # "basic_plugins", - # Debug - # "debug_apps", - ) - }, -) - -vars.Add( - "FIRMWARE_APP_SET", - help="Application set to use from FIRMWARE_APPS", - default="default", -) - -vars.Add( - "APPSRC", - help="Application source directory for app to build & upload", - default="", -) - -# List of tuples (directory, add_to_global_include_path) -vars.Add( - "APPDIRS", - help="Directories to search for firmware components & external apps", - default=[ - ("applications", False), - ("applications/services", True), - ("applications/main", True), - ("applications/settings", False), - ("applications/system", False), - ("applications/debug", False), - ("applications/plugins", False), - ("applications/examples", False), - ("applications_user", False), - ], + ( + "FBT_TOOLCHAIN_VERSIONS", + "Whitelisted toolchain versions (leave empty for no check)", + tuple(), + ), + ( + "OPENOCD_OPTS", + "Options to pass to OpenOCD", + "", + ), + ( + "BLACKMAGIC", + "Blackmagic probe location", + "auto", + ), + ( + "UPDATE_SPLASH", + "Directory name with slideshow frames to render after installing update package", + "update_default", + ), + ( + "LOADER_AUTOSTART", + "Application name to automatically run on Flipper boot", + "", + ), + ( + "FIRMWARE_APPS", + "Map of (configuration_name->application_list)", + { + "default": ( + # Svc + "basic_services", + # Apps + "main_apps", + "system_apps", + # Settings + "settings_apps", + # Plugins + # "basic_plugins", + # Debug + # "debug_apps", + ) + }, + ), + ( + "FIRMWARE_APP_SET", + "Application set to use from FIRMWARE_APPS", + "default", + ), + ( + "APPSRC", + "Application source directory for app to build & upload", + "", + ), + # List of tuples (directory, add_to_global_include_path) + ( + "APPDIRS", + "Directories to search for firmware components & external apps", + [ + ("applications", False), + ("applications/services", True), + ("applications/main", True), + ("applications/settings", False), + ("applications/system", False), + ("applications/debug", False), + ("applications/plugins", False), + ("applications_user", False), + ], + ), ) Return("vars") diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 3e0c6bea7..2d082838a 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -1,6 +1,5 @@ -import SCons from SCons.Platform import TempFileMunge -from fbt import util +from fbt.util import tempfile_arg_esc_func, single_quote, wrap_tempfile import os import multiprocessing @@ -13,14 +12,18 @@ forward_os_env = { } # Proxying CI environment to child processes & scripts variables_to_forward = [ + # CI/CD variables "WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", + # Python & other tools "HOME", "APPDATA", "PYTHONHOME", "PYTHONNOUSERSITE", "TMP", "TEMP", + # Colors for tools + "TERM", ] if proxy_env := GetOption("proxy_env"): variables_to_forward.extend(proxy_env.split(",")) @@ -79,7 +82,7 @@ if not coreenv["VERBOSE"]: SetOption("num_jobs", multiprocessing.cpu_count()) # Avoiding re-scan of all sources on every startup SetOption("implicit_cache", True) -SetOption("implicit_deps_unchanged", True) +# SetOption("implicit_deps_unchanged", True) # More aggressive caching SetOption("max_drift", 1) # Random task queue - to discover isses with build logic faster @@ -87,10 +90,10 @@ SetOption("max_drift", 1) # Setting up temp file parameters - to overcome command line length limits -coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func -util.wrap_tempfile(coreenv, "LINKCOM") -util.wrap_tempfile(coreenv, "ARCOM") +coreenv["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func +wrap_tempfile(coreenv, "LINKCOM") +wrap_tempfile(coreenv, "ARCOM") -coreenv["SINGLEQUOTEFUNC"] = util.single_quote +coreenv["SINGLEQUOTEFUNC"] = single_quote Return("coreenv") diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index c976fbbe5..9edc6b5c1 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -21,7 +21,7 @@ appenv = ENV.Clone( ) appenv.Replace( - LINKER_SCRIPT="application-ext", + LINKER_SCRIPT="application_ext", ) appenv.AppendUnique( @@ -106,6 +106,7 @@ appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None Alias("faps", extapps["compact"].values()) +Alias("faps", extapps["validators"].values()) if appsrc := appenv.subst("$APPSRC"): app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) diff --git a/site_scons/fbt_extra/util.py b/site_scons/fbt_extra/util.py new file mode 100644 index 000000000..aa3d50b64 --- /dev/null +++ b/site_scons/fbt_extra/util.py @@ -0,0 +1,23 @@ +from fbt.util import link_dir + + +def link_elf_dir_as_latest(env, elf_node): + elf_dir = elf_node.Dir(".") + latest_dir = env.Dir("#build/latest") + print(f"Setting {elf_dir} as latest built dir (./build/latest/)") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def should_gen_cdb_and_link_dir(env, requested_targets): + explicitly_building_updater = False + # Hacky way to check if updater-related targets were requested + for build_target in requested_targets: + if "updater" in str(build_target): + explicitly_building_updater = True + + is_updater = not env["IS_BASE_FIRMWARE"] + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to firmware + return (is_updater and explicitly_building_updater) or ( + not is_updater and not explicitly_building_updater + ) From ede3bac799f8c2c7e1184b09473fd0ecc1d2c511 Mon Sep 17 00:00:00 2001 From: Thibaut CHARLES Date: Wed, 12 Oct 2022 18:21:54 +0200 Subject: [PATCH 5/6] Badusb: show script errors on screen (#1506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/bad_usb/bad_usb_script.c | 39 +++++++++++++++++-- applications/main/bad_usb/bad_usb_script.h | 1 + .../main/bad_usb/views/bad_usb_view.c | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/applications/main/bad_usb/bad_usb_script.c b/applications/main/bad_usb/bad_usb_script.c index 78aba88ed..8ff38ef66 100644 --- a/applications/main/bad_usb/bad_usb_script.c +++ b/applications/main/bad_usb/bad_usb_script.c @@ -231,7 +231,8 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { return 0; } -static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { +static int32_t + ducky_parse_line(BadUsbScript* bad_usb, FuriString* line, char* error, size_t error_len) { uint32_t line_len = furi_string_size(line); const char* line_tmp = furi_string_get_cstr(line); bool state = false; @@ -261,6 +262,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { if((state) && (delay_val > 0)) { return (int32_t)delay_val; } + if(error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) || @@ -268,17 +272,26 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // DEFAULT_DELAY line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->defdelay); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { // STRING line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_string(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid string %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) { // ALTCHAR line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altchar(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altchar %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if( (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) || @@ -287,11 +300,17 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; ducky_numlock_on(); state = ducky_altstring(line_tmp); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid altstring %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) { // REPEAT line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt); + if(!state && error != NULL) { + snprintf(error, error_len, "Invalid number %s", line_tmp); + } return (state) ? (0) : SCRIPT_STATE_ERROR; } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { // SYSRQ @@ -304,7 +323,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); - if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR; + if(key == HID_KEYBOARD_NONE) { + if(error != NULL) { + snprintf(error, error_len, "No keycode defined for %s", line_tmp); + } + return SCRIPT_STATE_ERROR; + } if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; @@ -314,6 +338,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { furi_hal_hid_kb_release(key); return (0); } + if(error != NULL) { + strncpy(error, "Unknown error", error_len); + } return SCRIPT_STATE_ERROR; } @@ -392,7 +419,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil if(bad_usb->repeat_cnt > 0) { bad_usb->repeat_cnt--; - delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev); + delay_val = ducky_parse_line( + bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error)); if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line return 0; } else if(delay_val < 0) { // Script error @@ -426,7 +454,9 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil bad_usb->st.line_cur++; bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1); bad_usb->buf_start = i + 1; - delay_val = ducky_parse_line(bad_usb, bad_usb->line); + delay_val = ducky_parse_line( + bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error)); + if(delay_val < 0) { bad_usb->st.error_line = bad_usb->st.line_cur; FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); @@ -602,6 +632,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_string_set(bad_usb->file_path, file_path); bad_usb->st.state = BadUsbStateInit; + bad_usb->st.error[0] = '\0'; bad_usb->thread = furi_thread_alloc(); furi_thread_set_name(bad_usb->thread, "BadUsbWorker"); diff --git a/applications/main/bad_usb/bad_usb_script.h b/applications/main/bad_usb/bad_usb_script.h index 5fee6505f..f24372fab 100644 --- a/applications/main/bad_usb/bad_usb_script.h +++ b/applications/main/bad_usb/bad_usb_script.h @@ -25,6 +25,7 @@ typedef struct { uint16_t line_nb; uint32_t delay_remain; uint16_t error_line; + char error[64]; } BadUsbState; BadUsbScript* bad_usb_script_open(FuriString* file_path); diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index db10d01ee..6c6a15847 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -53,6 +53,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { canvas_draw_str_aligned( canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); furi_string_reset(disp_str); + canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); } else if(model->state.state == BadUsbStateIdle) { canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18); canvas_set_font(canvas, FontBigNumbers); From 50dc2d738991205f2d05905517fac96c619c3b5a Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 12 Oct 2022 11:31:54 -0500 Subject: [PATCH 6/6] 36-bit AWID (L11601 Lenel) (#1838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 36-bit AWID * Rfid: correct vendor name AWIG -> AWID Co-authored-by: Sergey Gavrilov Co-authored-by: あく --- lib/lfrfid/protocols/protocol_awid.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 88093900e..38c7793b8 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -81,7 +81,7 @@ static bool protocol_awid_can_be_decoded(uint8_t* data) { // Avoid detection for invalid formats uint8_t len = bit_lib_get_bits(data, 8, 8); - if(len != 26 && len != 50 && len != 37 && len != 34) break; + if(len != 26 && len != 50 && len != 37 && len != 34 && len != 36) break; result = true; } while(false); @@ -207,7 +207,7 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { // Fix incorrect length byte if(protocol->data[0] != 26 && protocol->data[0] != 50 && protocol->data[0] != 37 && - protocol->data[0] != 34) { + protocol->data[0] != 34 && protocol->data[0] != 36 ) { protocol->data[0] = 26; } @@ -232,7 +232,7 @@ bool protocol_awid_write_data(ProtocolAwid* protocol, void* data) { const ProtocolBase protocol_awid = { .name = "AWID", - .manufacturer = "AWIG", + .manufacturer = "AWID", .data_size = AWID_DECODED_DATA_SIZE, .features = LFRFIDFeatureASK, .validate_count = 3,