mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-30 21:58:55 -07:00
V41 Release Candidate Changes (#130)
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
name: 'Build'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7 f18
|
||||
DEFAULT_TARGET: f7
|
||||
FBT_TOOLCHAIN_PATH: /home/runner/work
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Get commit details'
|
||||
id: names
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
TYPE="pull"
|
||||
elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
TYPE="tag"
|
||||
else
|
||||
TYPE="other"
|
||||
fi
|
||||
python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE"
|
||||
echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT
|
||||
echo "event_type=$TYPE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Make artifacts directory'
|
||||
run: |
|
||||
rm -rf artifacts
|
||||
mkdir artifacts
|
||||
|
||||
- name: 'Bundle scripts'
|
||||
run: |
|
||||
tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
./fbt TARGET_HW=$TARGET copro_dist updater_package \
|
||||
${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
|
||||
done
|
||||
|
||||
- name: 'Move upload files'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
mv dist/${TARGET}-*/* artifacts/
|
||||
done
|
||||
|
||||
- name: "Check for uncommitted changes"
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: 'Bundle resources'
|
||||
run: |
|
||||
tar czpf "artifacts/flipper-z-any-resources-${SUFFIX}.tgz" -C assets resources
|
||||
|
||||
- name: 'Bundle core2 firmware'
|
||||
run: |
|
||||
cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
|
||||
|
||||
- name: 'Updater artifact'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: updater
|
||||
path: |
|
||||
artifacts/f7-*
|
||||
|
||||
- name: 'Firmware artifact'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware
|
||||
path: |
|
||||
artifacts
|
||||
|
||||
# - name: 'Find Previous Comment'
|
||||
# if: ${{ github.event.pull_request }}
|
||||
# uses: peter-evans/find-comment@v1
|
||||
# id: fc
|
||||
# with:
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# comment-author: 'github-actions[bot]'
|
||||
# body-includes: 'Compiled firmware for commit'
|
||||
|
||||
# - name: Artifact info
|
||||
# id: artifact-info
|
||||
# uses: dawidd6/action-download-artifact@v2
|
||||
# with:
|
||||
# dry_run: true
|
||||
|
||||
# - name: 'Create or update comment'
|
||||
# if: ${{ github.event.pull_request}}
|
||||
# uses: peter-evans/create-or-update-comment@v1
|
||||
# with:
|
||||
# comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# body: |
|
||||
# **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
|
||||
# - [📦 Update package](${{steps.artifact-info.outputs.artifacts[0].archive_download_url}})
|
||||
# edit-mode: replace
|
||||
|
||||
compact:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Get commit details'
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
TYPE="pull"
|
||||
elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
TYPE="tag"
|
||||
else
|
||||
TYPE="other"
|
||||
fi
|
||||
python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE"
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 updater_package
|
||||
done
|
||||
@@ -0,0 +1,47 @@
|
||||
name: 'Check submodules branch'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check_protobuf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Check protobuf branch'
|
||||
run: |
|
||||
git submodule update --init
|
||||
SUB_PATH="assets/protobuf";
|
||||
SUB_BRANCH="dev";
|
||||
SUB_COMMITS_MIN=40;
|
||||
cd "$SUB_PATH";
|
||||
SUBMODULE_HASH="$(git rev-parse HEAD)";
|
||||
BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH");
|
||||
COMMITS_IN_BRANCH="$(git rev-list --count dev)";
|
||||
if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then
|
||||
echo "name=fails::error" >> $GITHUB_OUTPUT
|
||||
echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)";
|
||||
exit 1;
|
||||
fi
|
||||
if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then
|
||||
echo "name=fails::error" >> $GITHUB_OUTPUT
|
||||
echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH";
|
||||
exit 1;
|
||||
fi
|
||||
@@ -0,0 +1,46 @@
|
||||
name: 'Lint C/C++ with clang-format'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
SET_GH_OUTPUT: 1
|
||||
|
||||
jobs:
|
||||
lint_c_cpp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Check code formatting'
|
||||
id: syntax_check
|
||||
run: ./fbt lint
|
||||
|
||||
- name: Report code formatting errors
|
||||
if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Please fix following code formatting errors:
|
||||
```
|
||||
${{ steps.syntax_check.outputs.errors }}
|
||||
```
|
||||
You might want to run `./fbt format` for an auto-fix.
|
||||
@@ -0,0 +1,93 @@
|
||||
name: 'Static C/C++ analysis with PVS-Studio'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
FBT_TOOLCHAIN_PATH: /runner/_work
|
||||
|
||||
jobs:
|
||||
analyse_c_cpp:
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
runs-on: [self-hosted, FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Get commit details'
|
||||
id: names
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
TYPE="pull"
|
||||
elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
TYPE="tag"
|
||||
else
|
||||
TYPE="other"
|
||||
fi
|
||||
python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE"
|
||||
|
||||
- name: 'Supply PVS credentials'
|
||||
run: |
|
||||
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
|
||||
|
||||
- name: 'Convert PVS-Studio output to html and detect warnings'
|
||||
id: pvs-warn
|
||||
run: |
|
||||
WARNINGS=0
|
||||
./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1
|
||||
echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Upload report'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
uses: prewk/s3-cp-action@v2
|
||||
with:
|
||||
aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}"
|
||||
aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}"
|
||||
aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}"
|
||||
source: "./build/f7-firmware-DC/pvsreport"
|
||||
dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"
|
||||
flags: "--recursive --acl public-read"
|
||||
|
||||
- name: 'Find Previous Comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: 'PVS-Studio report for commit'
|
||||
|
||||
- name: 'Create or update comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
**PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:**
|
||||
- [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html)
|
||||
edit-mode: replace
|
||||
|
||||
- name: 'Raise exception'
|
||||
if: ${{ steps.pvs-warn.outputs.warnings != 0 }}
|
||||
run: |
|
||||
echo "Please fix all PVS warnings before merge"
|
||||
exit 1
|
||||
|
||||
+10
-2
@@ -1,4 +1,5 @@
|
||||
*.swp
|
||||
*.swo
|
||||
*.gdb_history
|
||||
|
||||
|
||||
@@ -67,8 +68,15 @@ PVS-Studio.log
|
||||
# Automate files, etc
|
||||
automate.py
|
||||
deployments/
|
||||
assets/dolphin/custom/
|
||||
assets/resources/dolphin_custom/
|
||||
fbt_options.py
|
||||
commitnotes.md
|
||||
lib/STM32CubeWB
|
||||
|
||||
# Asset packs
|
||||
assets/dolphin/custom/*
|
||||
!assets/dolphin/custom/NSFW/
|
||||
!assets/dolphin/custom/WatchDogs/
|
||||
!assets/dolphin/custom/ReadMe.md
|
||||
assets/resources/dolphin_custom/*
|
||||
!assets/resources/dolphin_custom/NSFW/
|
||||
!assets/resources/dolphin_custom/WatchDogs/
|
||||
|
||||
Vendored
+5
-2
@@ -11,5 +11,8 @@
|
||||
"augustocdias.tasks-shell-input"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
"unwantedRecommendations": [
|
||||
"twxs.cmake",
|
||||
"ms-vscode.cmake-tools"
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ Almost everything in flipper firmware is built around this concept.
|
||||
|
||||
## Naming
|
||||
|
||||
### Type names are CamelCase
|
||||
### Type names are PascalCase
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<img src="https://user-images.githubusercontent.com/55334727/215170306-051eeb8f-8f72-437f-8c2d-0e4be009bdad.png">
|
||||
</p>
|
||||
|
||||
[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Docs](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme#Known-bugs) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
|
||||
[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Docs](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme/issues?q=is%3Aissue+is%3Aopen+label%3Arelease-pending) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
|
||||
-----
|
||||
|
||||
This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), it also features lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware).
|
||||
@@ -42,7 +42,7 @@ We wrote a powerful yet easy-to-use application specifically for our Firmware, t
|
||||
<br><code>Anim Speed:</code> Speed in which the animations play
|
||||
<br><code>Cycle Anims:</code> Duration of how long animations are played before switching to next
|
||||
<br><code>Unlock Anims:</code> Custom setting just for NSFW fallback animations. Figure it out ;)
|
||||
<br><code>Battery style:</code> Classic Firmware battery style toggle, just at a more convenient place
|
||||
<br><code>Battery Icon:</code> Classic Firmware battery style toggle, just at a more convenient place
|
||||
<br><code>XP Level:</code> Changes your Flippers level
|
||||
<br><code>SubGhz Extend:</code> Allows you to extend the subghz range beyond what FZ devs planned
|
||||
<br><code>SubGhz Bypass:</code> Allows you to bypass the subghz region locks of the Flipper
|
||||
@@ -113,9 +113,9 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- NSFW Animations tied to the level system. Read more above
|
||||
- Folder handling for empty ones (Now indicate they are empty)
|
||||
|
||||
- Jamming Files
|
||||
- Custom subghz presets
|
||||
- Multiple NFC protocols
|
||||
- Multiple Sub-Ghz protocols | Merged from Unleashed, thanks @xMasterX
|
||||
- Subghz and IR signal replication via gpio | Credits to @ankris812
|
||||
- Honda Keys (CVE-2022-27254) & Ford blockers
|
||||
|
||||
@@ -127,6 +127,7 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- All Assets
|
||||
|
||||
- Tons of apps
|
||||
- File browser
|
||||
- Massive compiler re-do
|
||||
- About 1k files to speed things up a lot
|
||||
- Applications to now use the new Locale setting
|
||||
@@ -147,14 +148,6 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- Tons of unused code from FAPs and system calls
|
||||
```
|
||||
|
||||
----
|
||||
<br>
|
||||
<h2 align="center">Known Bugs:</h2>
|
||||
|
||||
```txt
|
||||
- Nothing rn. Hopefully that wont change
|
||||
```
|
||||
|
||||
----
|
||||
<br>
|
||||
<h2 align="center">Install:</h2>
|
||||
|
||||
@@ -25,7 +25,7 @@ Applications for factory testing the Flipper.
|
||||
Applications for main Flipper menu.
|
||||
|
||||
- `archive` - Archive and file manager
|
||||
- `bad_usb` - Bad USB application
|
||||
- `bad_kb` - Bad KB application
|
||||
- `fap_loader` - External applications loader
|
||||
- `gpio` - GPIO application: includes USART bridge and GPIO control
|
||||
- `ibutton` - iButton application, onewire keys and more
|
||||
|
||||
@@ -2,6 +2,7 @@ App(
|
||||
appid="accessor",
|
||||
name="Accessor",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
targets=["f7"],
|
||||
entry_point="accessor_app",
|
||||
cdefines=["APP_ACCESSOR"],
|
||||
requires=["gui"],
|
||||
|
||||
@@ -11,4 +11,5 @@ App(
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "battery_info.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define LOW_CHARGE_THRESHOLD 10
|
||||
#define HIGH_DRAIN_CURRENT_THRESHOLD 100
|
||||
|
||||
struct BatteryInfo {
|
||||
View* view;
|
||||
};
|
||||
|
||||
static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) {
|
||||
canvas_draw_frame(canvas, x - 7, y + 7, 30, 13);
|
||||
canvas_draw_icon(canvas, x, y, icon);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
|
||||
};
|
||||
|
||||
static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
|
||||
char emote[20] = {};
|
||||
char header[20] = {};
|
||||
char value[20] = {};
|
||||
|
||||
int32_t drain_current = data->gauge_current * (-1000);
|
||||
uint32_t charge_current = data->gauge_current * 1000;
|
||||
|
||||
// Draw battery
|
||||
canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28);
|
||||
if(charge_current > 0) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14);
|
||||
} else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14);
|
||||
} else if(data->charge < LOW_CHARGE_THRESHOLD) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14);
|
||||
}
|
||||
|
||||
// Draw bubble
|
||||
elements_bubble(canvas, 53, 0, 71, 39);
|
||||
|
||||
// Set text
|
||||
if(charge_current > 0) {
|
||||
snprintf(emote, sizeof(emote), "%s", "Yummy!");
|
||||
snprintf(header, sizeof(header), "%s", "Charging at");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%lu.%luV %lumA",
|
||||
(uint32_t)(data->vbus_voltage),
|
||||
(uint32_t)(data->vbus_voltage * 10) % 10,
|
||||
charge_current);
|
||||
} else if(drain_current > 0) {
|
||||
snprintf(
|
||||
emote,
|
||||
sizeof(emote),
|
||||
"%s",
|
||||
drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!");
|
||||
snprintf(header, sizeof(header), "%s", "Consumption is");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%ld %s",
|
||||
drain_current,
|
||||
drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA");
|
||||
} else if(drain_current != 0) {
|
||||
snprintf(header, 20, "...");
|
||||
} else if(data->charging_voltage < 4.2) {
|
||||
// Non-default battery charging limit, mention it
|
||||
snprintf(emote, sizeof(emote), "Charged!");
|
||||
snprintf(header, sizeof(header), "Limited to");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%lu.%luV",
|
||||
(uint32_t)(data->charging_voltage),
|
||||
(uint32_t)(data->charging_voltage * 10) % 10);
|
||||
} else {
|
||||
snprintf(header, sizeof(header), "Charged!");
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
|
||||
canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
|
||||
canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
|
||||
};
|
||||
|
||||
static void battery_info_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BatteryInfoModel* model = context;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
draw_battery(canvas, model, 0, 5);
|
||||
|
||||
char batt_level[10];
|
||||
char temperature[10];
|
||||
char voltage[10];
|
||||
char health[10];
|
||||
|
||||
snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge);
|
||||
snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature);
|
||||
snprintf(
|
||||
voltage,
|
||||
sizeof(voltage),
|
||||
"%lu.%01lu V",
|
||||
(uint32_t)model->gauge_voltage,
|
||||
(uint32_t)(model->gauge_voltage * 10) % 10UL);
|
||||
snprintf(health, sizeof(health), "%d%%", model->health);
|
||||
|
||||
draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level);
|
||||
draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature);
|
||||
draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage);
|
||||
draw_stat(canvas, 104, 42, &I_Health_16x16, health);
|
||||
}
|
||||
|
||||
BatteryInfo* battery_info_alloc() {
|
||||
BatteryInfo* battery_info = malloc(sizeof(BatteryInfo));
|
||||
battery_info->view = view_alloc();
|
||||
view_set_context(battery_info->view, battery_info);
|
||||
view_allocate_model(battery_info->view, ViewModelTypeLocking, sizeof(BatteryInfoModel));
|
||||
view_set_draw_callback(battery_info->view, battery_info_draw_callback);
|
||||
|
||||
return battery_info;
|
||||
}
|
||||
|
||||
void battery_info_free(BatteryInfo* battery_info) {
|
||||
furi_assert(battery_info);
|
||||
view_free(battery_info->view);
|
||||
free(battery_info);
|
||||
}
|
||||
|
||||
View* battery_info_get_view(BatteryInfo* battery_info) {
|
||||
furi_assert(battery_info);
|
||||
return battery_info->view;
|
||||
}
|
||||
|
||||
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) {
|
||||
furi_assert(battery_info);
|
||||
furi_assert(data);
|
||||
with_view_model(
|
||||
battery_info->view,
|
||||
BatteryInfoModel * model,
|
||||
{ memcpy(model, data, sizeof(BatteryInfoModel)); },
|
||||
true);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BatteryInfo BatteryInfo;
|
||||
|
||||
typedef struct {
|
||||
float vbus_voltage;
|
||||
float gauge_voltage;
|
||||
float gauge_current;
|
||||
float gauge_temperature;
|
||||
float charging_voltage;
|
||||
uint8_t charge;
|
||||
uint8_t health;
|
||||
} BatteryInfoModel;
|
||||
|
||||
BatteryInfo* battery_info_alloc();
|
||||
|
||||
void battery_info_free(BatteryInfo* battery_info);
|
||||
|
||||
View* battery_info_get_view(BatteryInfo* battery_info);
|
||||
|
||||
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data);
|
||||
@@ -31,9 +31,6 @@ uint32_t bt_debug_start_view(void* context) {
|
||||
BtDebugApp* bt_debug_app_alloc() {
|
||||
BtDebugApp* app = malloc(sizeof(BtDebugApp));
|
||||
|
||||
// Load settings
|
||||
bt_settings_load(&app->settings);
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
@@ -105,13 +102,15 @@ int32_t bt_debug_app(void* p) {
|
||||
}
|
||||
|
||||
BtDebugApp* app = bt_debug_app_alloc();
|
||||
// Was bt active?
|
||||
const bool was_active = furi_hal_bt_is_active();
|
||||
// Stop advertising
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
// Restart advertising
|
||||
if(app->settings.enabled) {
|
||||
if(was_active) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
bt_debug_app_free(app);
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include "views/bt_carrier_test.h"
|
||||
#include "views/bt_packet_test.h"
|
||||
#include <bt/bt_settings.h>
|
||||
|
||||
typedef struct {
|
||||
BtSettings settings;
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "bt_carrier_test.h"
|
||||
#include "bt_test.h"
|
||||
#include "bt_test_types.h"
|
||||
#include "furi_hal_bt.h"
|
||||
#include <furi_hal_bt.h>
|
||||
|
||||
struct BtCarrierTest {
|
||||
BtTest* bt_test;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "bt_packet_test.h"
|
||||
#include "bt_test.h"
|
||||
#include "bt_test_types.h"
|
||||
#include "furi_hal_bt.h"
|
||||
#include <furi_hal_bt.h>
|
||||
|
||||
struct BtPacketTest {
|
||||
BtTest* bt_test;
|
||||
|
||||
@@ -5,6 +5,7 @@ App(
|
||||
entry_point="display_test_app",
|
||||
cdefines=["APP_DISPLAY_TEST"],
|
||||
requires=["gui"],
|
||||
fap_libs=["misc"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
|
||||
@@ -91,7 +91,6 @@ static void display_test_reload_config(DisplayTest* instance) {
|
||||
instance->config_contrast,
|
||||
instance->config_regulation_ratio,
|
||||
instance->config_bias);
|
||||
gui_update(instance->gui);
|
||||
}
|
||||
|
||||
static void display_config_set_bias(VariableItem* item) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
App(
|
||||
appid="example_custom_font",
|
||||
name="Example: custom font",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="example_custom_font_main",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
|
||||
//This arrays contains the font itself. You can use any u8g2 font you want
|
||||
|
||||
/*
|
||||
Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1
|
||||
Copyright:
|
||||
Glyphs: 95/203
|
||||
BBX Build Mode: 0
|
||||
*/
|
||||
const uint8_t u8g2_font_tom_thumb_4x6_tr[725] =
|
||||
"_\0\2\2\2\3\3\4\4\3\6\0\377\5\377\5\0\0\352\1\330\2\270 \5\340\315\0!\6\265\310"
|
||||
"\254\0\42\6\213\313$\25#\10\227\310\244\241\206\12$\10\227\310\215\70b\2%\10\227\310d\324F\1"
|
||||
"&\10\227\310(\65R\22'\5\251\313\10(\6\266\310\251\62)\10\226\310\304\224\24\0*\6\217\312\244"
|
||||
"\16+\7\217\311\245\225\0,\6\212\310)\0-\5\207\312\14.\5\245\310\4/\7\227\310Ve\4\60"
|
||||
"\7\227\310-k\1\61\6\226\310\255\6\62\10\227\310h\220\312\1\63\11\227\310h\220\62X\0\64\10\227"
|
||||
"\310$\65b\1\65\10\227\310\214\250\301\2\66\10\227\310\315\221F\0\67\10\227\310\314TF\0\70\10\227"
|
||||
"\310\214\64\324\10\71\10\227\310\214\64\342\2:\6\255\311\244\0;\7\222\310e\240\0<\10\227\310\246\32"
|
||||
"d\20=\6\217\311l\60>\11\227\310d\220A*\1\77\10\227\310\314\224a\2@\10\227\310UC\3"
|
||||
"\1A\10\227\310UC\251\0B\10\227\310\250\264\322\2C\7\227\310\315\32\10D\10\227\310\250d-\0"
|
||||
"E\10\227\310\214\70\342\0F\10\227\310\214\70b\4G\10\227\310\315\221\222\0H\10\227\310$\65\224\12"
|
||||
"I\7\227\310\254X\15J\7\227\310\226\252\2K\10\227\310$\265\222\12L\7\227\310\304\346\0M\10\227"
|
||||
"\310\244\61\224\12N\10\227\310\244q\250\0O\7\227\310UV\5P\10\227\310\250\264b\4Q\10\227\310"
|
||||
"Uj$\1R\10\227\310\250\64V\1S\10\227\310m\220\301\2T\7\227\310\254\330\2U\7\227\310$"
|
||||
"W\22V\10\227\310$\253L\0W\10\227\310$\65\206\12X\10\227\310$\325R\1Y\10\227\310$U"
|
||||
"V\0Z\7\227\310\314T\16[\7\227\310\214X\16\134\10\217\311d\220A\0]\7\227\310\314r\4^"
|
||||
"\5\213\313\65_\5\207\310\14`\6\212\313\304\0a\7\223\310\310\65\2b\10\227\310D\225\324\2c\7"
|
||||
"\223\310\315\14\4d\10\227\310\246\245\222\0e\6\223\310\235\2f\10\227\310\246\264b\2g\10\227\307\35"
|
||||
"\61%\0h\10\227\310D\225\254\0i\6\265\310\244\1j\10\233\307f\30U\5k\10\227\310\304\264T"
|
||||
"\1l\7\227\310\310\326\0m\7\223\310<R\0n\7\223\310\250d\5o\7\223\310U\252\2p\10\227"
|
||||
"\307\250\244V\4q\10\227\307-\225d\0r\6\223\310\315\22s\10\223\310\215\70\22\0t\10\227\310\245"
|
||||
"\25\243\0u\7\223\310$+\11v\10\223\310$\65R\2w\7\223\310\244q\4x\7\223\310\244\62\25"
|
||||
"y\11\227\307$\225dJ\0z\7\223\310\254\221\6{\10\227\310\251\32D\1|\6\265\310(\1}\11"
|
||||
"\227\310\310\14RR\0~\6\213\313\215\4\0\0\0\4\377\377\0";
|
||||
|
||||
// Screen is 128x64 px
|
||||
static void app_draw_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_custom_u8g2_font(canvas, u8g2_font_tom_thumb_4x6_tr);
|
||||
|
||||
canvas_draw_str(canvas, 0, 6, "This is a tiny custom font");
|
||||
canvas_draw_str(canvas, 0, 12, "012345.?! ,:;\"\'@#$%");
|
||||
}
|
||||
|
||||
static void app_input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
int32_t example_custom_font_main(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, app_draw_callback, view_port);
|
||||
view_port_input_callback_set(view_port, app_input_callback, event_queue);
|
||||
|
||||
// Register view port in GUI
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
InputEvent event;
|
||||
|
||||
bool running = true;
|
||||
|
||||
while(running) {
|
||||
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
|
||||
if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) {
|
||||
switch(event.key) {
|
||||
case InputKeyBack:
|
||||
running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <file_browser_test_icons.h>
|
||||
#include "file_browser_app_i.h"
|
||||
#include "gui/modules/file_browser.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <file_browser_test_icons.h>
|
||||
|
||||
#include <gui/modules/file_browser.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
static bool file_browser_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
@@ -48,7 +49,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
|
||||
|
||||
app->file_path = furi_string_alloc();
|
||||
app->file_browser = file_browser_alloc(app->file_path);
|
||||
file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badusb_10px, true);
|
||||
file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badkb_10px, true);
|
||||
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget));
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 576 B |
@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
|
||||
furi_string_set(app->file_path, ANY_PATH("badkb/demo_windows.txt"));
|
||||
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
|
||||
@@ -2,6 +2,7 @@ App(
|
||||
appid="lfrfid_debug",
|
||||
name="LF-RFID Debug",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
targets=["f7"],
|
||||
entry_point="lfrfid_debug_app",
|
||||
requires=[
|
||||
"gui",
|
||||
|
||||
@@ -8,4 +8,4 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -44,7 +44,11 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RpcDebugAppCustomEventInputErrorCode) {
|
||||
rpc_system_app_set_error_code(app->rpc, (uint32_t)atol(app->text_store));
|
||||
char* end;
|
||||
int error_code = strtol(app->text_store, &end, 10);
|
||||
if(!*end) {
|
||||
rpc_system_app_set_error_code(app->rpc, error_code);
|
||||
}
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -348,13 +348,37 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) {
|
||||
memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2);
|
||||
|
||||
MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data;
|
||||
// Check the manufacturer block (should be uid[uid_len] + 0xFF[rest])
|
||||
// Check the manufacturer block (should be uid[uid_len] + BCC (for 4byte only) + SAK + ATQA0 + ATQA1 + 0xFF[rest])
|
||||
uint8_t manufacturer_block[16] = {0};
|
||||
memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16);
|
||||
mu_assert(
|
||||
memcmp(manufacturer_block, uid, uid_len) == 0,
|
||||
"manufacturer_block uid doesn't match the file\r\n");
|
||||
for(uint8_t i = uid_len; i < 16; i++) {
|
||||
|
||||
uint8_t position = 0;
|
||||
if(uid_len == 4) {
|
||||
position = uid_len;
|
||||
|
||||
uint8_t bcc = 0;
|
||||
|
||||
for(int i = 0; i < uid_len; i++) {
|
||||
bcc ^= uid[i];
|
||||
}
|
||||
|
||||
mu_assert(manufacturer_block[position] == bcc, "manufacturer_block bcc assert failed\r\n");
|
||||
} else {
|
||||
position = uid_len - 1;
|
||||
}
|
||||
|
||||
mu_assert(manufacturer_block[position + 1] == sak, "manufacturer_block sak assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
manufacturer_block[position + 2] == atqa[0], "manufacturer_block atqa0 assert failed\r\n");
|
||||
|
||||
mu_assert(
|
||||
manufacturer_block[position + 3] == atqa[1], "manufacturer_block atqa1 assert failed\r\n");
|
||||
|
||||
for(uint8_t i = position + 4; i < 16; i++) {
|
||||
mu_assert(
|
||||
manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n");
|
||||
}
|
||||
@@ -466,6 +490,10 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) {
|
||||
nfc_device_free(nfc_keys);
|
||||
}
|
||||
|
||||
MU_TEST(mf_mini_file_test) {
|
||||
mf_classic_generator_test(4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
MU_TEST(mf_classic_1k_4b_file_test) {
|
||||
mf_classic_generator_test(4, MfClassicType1k);
|
||||
}
|
||||
@@ -486,6 +514,7 @@ MU_TEST_SUITE(nfc) {
|
||||
nfc_test_alloc();
|
||||
|
||||
MU_RUN_TEST(nfca_file_test);
|
||||
MU_RUN_TEST(mf_mini_file_test);
|
||||
MU_RUN_TEST(mf_classic_1k_4b_file_test);
|
||||
MU_RUN_TEST(mf_classic_4k_4b_file_test);
|
||||
MU_RUN_TEST(mf_classic_1k_7b_file_test);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# 1-Wire Thermometer
|
||||
This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer.
|
||||
It also covers basic GUI, input handling, threads and localisation.
|
||||
|
||||
## Electrical connections
|
||||
Before launching the application, connect the sensor to Flipper's external GPIO according to the table below:
|
||||
| DS18B20 | Flipper |
|
||||
| :-----: | :-----: |
|
||||
| VDD | 9 |
|
||||
| GND | 18 |
|
||||
| DQ | 17 |
|
||||
|
||||
*NOTE 1*: GND is also available on pins 8 and 11.
|
||||
|
||||
*NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9.
|
||||
|
||||
## Launching the application
|
||||
In order to launch this demo, follow the steps below:
|
||||
1. Make sure your Flipper has an SD card installed.
|
||||
2. Connect your Flipper to the computer via a USB cable.
|
||||
3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice.
|
||||
|
||||
## Changing the data pin
|
||||
It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below:
|
||||
|
||||
```c
|
||||
/* Possible GPIO pin choices:
|
||||
- gpio_ext_pc0
|
||||
- gpio_ext_pc1
|
||||
- gpio_ext_pc3
|
||||
- gpio_ext_pb2
|
||||
- gpio_ext_pb3
|
||||
- gpio_ext_pa4
|
||||
- gpio_ext_pa6
|
||||
- gpio_ext_pa7
|
||||
- ibutton_gpio
|
||||
*/
|
||||
|
||||
#define THERMO_GPIO_PIN (ibutton_gpio)
|
||||
```
|
||||
Do not forget about the external pull-up resistor as these pins do not have one built-in.
|
||||
|
||||
With the changes been made, recompile and launch the application again.
|
||||
The on-screen text should reflect it by asking to connect the thermometer to another pin.
|
||||
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="example_thermo",
|
||||
name="Example: Thermometer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_thermo_main",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="example_thermo_10px.png",
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* This file contains an example application that reads and displays
|
||||
* the temperature from a DS18B20 1-wire thermometer.
|
||||
*
|
||||
* It also covers basic GUI, input handling, threads and localisation.
|
||||
*
|
||||
* References:
|
||||
* [1] DS18B20 Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf
|
||||
*/
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <core/kernel.h>
|
||||
|
||||
#include <locale/locale.h>
|
||||
|
||||
#include <one_wire/maxim_crc.h>
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
#define UPDATE_PERIOD_MS 1000UL
|
||||
#define TEXT_STORE_SIZE 64U
|
||||
|
||||
#define DS18B20_CMD_CONVERT 0x44U
|
||||
#define DS18B20_CMD_READ_SCRATCHPAD 0xbeU
|
||||
|
||||
#define DS18B20_CFG_RESOLUTION_POS 5U
|
||||
#define DS18B20_CFG_RESOLUTION_MASK 0x03U
|
||||
#define DS18B20_DECIMAL_PART_MASK 0x0fU
|
||||
|
||||
#define DS18B20_SIGN_MASK 0xf0U
|
||||
|
||||
/* Possible GPIO pin choices:
|
||||
- gpio_ext_pc0
|
||||
- gpio_ext_pc1
|
||||
- gpio_ext_pc3
|
||||
- gpio_ext_pb2
|
||||
- gpio_ext_pb3
|
||||
- gpio_ext_pa4
|
||||
- gpio_ext_pa6
|
||||
- gpio_ext_pa7
|
||||
- ibutton_gpio
|
||||
*/
|
||||
|
||||
#define THERMO_GPIO_PIN (ibutton_gpio)
|
||||
|
||||
/* Flags which the reader thread responds to */
|
||||
typedef enum {
|
||||
ReaderThreadFlagExit = 1,
|
||||
} ReaderThreadFlag;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t temp_lsb; /* Least significant byte of the temperature */
|
||||
uint8_t temp_msb; /* Most significant byte of the temperature */
|
||||
uint8_t user_alarm_high; /* User register 1 (Temp high alarm) */
|
||||
uint8_t user_alarm_low; /* User register 2 (Temp low alarm) */
|
||||
uint8_t config; /* Configuration register */
|
||||
uint8_t reserved[3]; /* Not used */
|
||||
uint8_t crc; /* CRC checksum for error detection */
|
||||
} fields;
|
||||
uint8_t bytes[9];
|
||||
} DS18B20Scratchpad;
|
||||
|
||||
/* Application context structure */
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriThread* reader_thread;
|
||||
FuriMessageQueue* event_queue;
|
||||
OneWireHost* onewire;
|
||||
float temp_celsius;
|
||||
bool has_device;
|
||||
} ExampleThermoContext;
|
||||
|
||||
/*************** 1-Wire Communication and Processing *****************/
|
||||
|
||||
/* Commands the thermometer to begin measuring the temperature. */
|
||||
static void example_thermo_request_temperature(ExampleThermoContext* context) {
|
||||
OneWireHost* onewire = context->onewire;
|
||||
|
||||
/* All 1-wire transactions must happen in a critical section, i.e
|
||||
not interrupted by other threads. */
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
bool success = false;
|
||||
do {
|
||||
/* Each communication with a 1-wire device starts by a reset.
|
||||
The functon will return true if a device responded with a presence pulse. */
|
||||
if(!onewire_host_reset(onewire)) break;
|
||||
/* After the reset, a ROM operation must follow.
|
||||
If there is only one device connected, the "Skip ROM" command is most appropriate
|
||||
(it can also be used to address all of the connected devices in some cases).*/
|
||||
onewire_host_skip(onewire);
|
||||
/* After the ROM operation, a device-specific command is issued.
|
||||
In this case, it's a request to start measuring the temperature. */
|
||||
onewire_host_write(onewire, DS18B20_CMD_CONVERT);
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
context->has_device = success;
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
/* Reads the measured temperature from the thermometer. */
|
||||
static void example_thermo_read_temperature(ExampleThermoContext* context) {
|
||||
/* If there was no device detected, don't try to read the temperature */
|
||||
if(!context->has_device) {
|
||||
return;
|
||||
}
|
||||
|
||||
OneWireHost* onewire = context->onewire;
|
||||
|
||||
/* All 1-wire transactions must happen in a critical section, i.e
|
||||
not interrupted by other threads. */
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
DS18B20Scratchpad buf;
|
||||
|
||||
/* Attempt reading the temperature 10 times before giving up */
|
||||
size_t attempts_left = 10;
|
||||
do {
|
||||
/* Each communication with a 1-wire device starts by a reset.
|
||||
The functon will return true if a device responded with a presence pulse. */
|
||||
if(!onewire_host_reset(onewire)) continue;
|
||||
|
||||
/* After the reset, a ROM operation must follow.
|
||||
If there is only one device connected, the "Skip ROM" command is most appropriate
|
||||
(it can also be used to address all of the connected devices in some cases).*/
|
||||
onewire_host_skip(onewire);
|
||||
|
||||
/* After the ROM operation, a device-specific command is issued.
|
||||
This time, it will be the "Read Scratchpad" command which will
|
||||
prepare the device's internal buffer memory for reading. */
|
||||
onewire_host_write(onewire, DS18B20_CMD_READ_SCRATCHPAD);
|
||||
|
||||
/* The actual reading happens here. A total of 9 bytes is read. */
|
||||
onewire_host_read_bytes(onewire, buf.bytes, sizeof(buf.bytes));
|
||||
|
||||
/* Calculate the checksum and compare it with one provided by the device. */
|
||||
const uint8_t crc = maxim_crc8(buf.bytes, sizeof(buf.bytes) - 1, MAXIM_CRC8_INIT);
|
||||
|
||||
/* Checksums match, exit the loop */
|
||||
if(crc == buf.fields.crc) break;
|
||||
|
||||
} while(--attempts_left);
|
||||
|
||||
if(attempts_left == 0) break;
|
||||
|
||||
/* Get the measurement resolution from the configuration register. (See [1] page 9) */
|
||||
const uint8_t resolution_mode = (buf.fields.config >> DS18B20_CFG_RESOLUTION_POS) &
|
||||
DS18B20_CFG_RESOLUTION_MASK;
|
||||
|
||||
/* Generate a mask for undefined bits in the decimal part. (See [1] page 6) */
|
||||
const uint8_t decimal_mask =
|
||||
(DS18B20_DECIMAL_PART_MASK << (DS18B20_CFG_RESOLUTION_MASK - resolution_mode)) &
|
||||
DS18B20_DECIMAL_PART_MASK;
|
||||
|
||||
/* Get the integer and decimal part of the temperature (See [1] page 6) */
|
||||
const uint8_t integer_part = (buf.fields.temp_msb << 4U) | (buf.fields.temp_lsb >> 4U);
|
||||
const uint8_t decimal_part = buf.fields.temp_lsb & decimal_mask;
|
||||
|
||||
/* Calculate the sign of the temperature (See [1] page 6) */
|
||||
const bool is_negative = (buf.fields.temp_msb & DS18B20_SIGN_MASK) != 0;
|
||||
|
||||
/* Combine the integer and decimal part together */
|
||||
const float temp_celsius_abs = integer_part + decimal_part / 16.f;
|
||||
|
||||
/* Set the appropriate sign */
|
||||
context->temp_celsius = is_negative ? -temp_celsius_abs : temp_celsius_abs;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
context->has_device = success;
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
/* Periodically requests measurements and reads temperature. This function runs in a separare thread. */
|
||||
static int32_t example_thermo_reader_thread_callback(void* ctx) {
|
||||
ExampleThermoContext* context = ctx;
|
||||
|
||||
for(;;) {
|
||||
/* Tell the termometer to start measuring the temperature. The process may take up to 750ms. */
|
||||
example_thermo_request_temperature(context);
|
||||
|
||||
/* Wait for the measurement to finish. At the same time wait for an exit signal. */
|
||||
const uint32_t flags =
|
||||
furi_thread_flags_wait(ReaderThreadFlagExit, FuriFlagWaitAny, UPDATE_PERIOD_MS);
|
||||
|
||||
/* If an exit signal was received, return from this thread. */
|
||||
if(flags != (unsigned)FuriFlagErrorTimeout) break;
|
||||
|
||||
/* The measurement is now ready, read it from the termometer. */
|
||||
example_thermo_read_temperature(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************** GUI, Input and Main Loop *****************/
|
||||
|
||||
/* Draw the GUI of the application. The screen is completely redrawn during each call. */
|
||||
static void example_thermo_draw_callback(Canvas* canvas, void* ctx) {
|
||||
ExampleThermoContext* context = ctx;
|
||||
char text_store[TEXT_STORE_SIZE];
|
||||
const size_t middle_x = canvas_width(canvas) / 2U;
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, middle_x, 12, AlignCenter, AlignBottom, "Thermometer Demo");
|
||||
canvas_draw_line(canvas, 0, 16, 128, 16);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, middle_x, 30, AlignCenter, AlignBottom, "Connnect thermometer");
|
||||
|
||||
snprintf(
|
||||
text_store,
|
||||
TEXT_STORE_SIZE,
|
||||
"to GPIO pin %ld",
|
||||
furi_hal_resources_get_ext_pin_number(&THERMO_GPIO_PIN));
|
||||
canvas_draw_str_aligned(canvas, middle_x, 42, AlignCenter, AlignBottom, text_store);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
|
||||
if(context->has_device) {
|
||||
float temp;
|
||||
char temp_units;
|
||||
|
||||
/* The applicaton is locale-aware.
|
||||
Change Settings->System->Units to check it out. */
|
||||
switch(locale_get_measurement_unit()) {
|
||||
case LocaleMeasurementUnitsMetric:
|
||||
temp = context->temp_celsius;
|
||||
temp_units = 'C';
|
||||
break;
|
||||
case LocaleMeasurementUnitsImperial:
|
||||
temp = locale_celsius_to_fahrenheit(context->temp_celsius);
|
||||
temp_units = 'F';
|
||||
break;
|
||||
default:
|
||||
furi_crash("Illegal measurement units");
|
||||
}
|
||||
/* If a reading is available, display it */
|
||||
snprintf(text_store, TEXT_STORE_SIZE, "Temperature: %+.1f%c", (double)temp, temp_units);
|
||||
} else {
|
||||
/* Or show a message that no data is available */
|
||||
strncpy(text_store, "-- No data --", TEXT_STORE_SIZE);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, middle_x, 58, AlignCenter, AlignBottom, text_store);
|
||||
}
|
||||
|
||||
/* This function is called from the GUI thread. All it does is put the event
|
||||
into the application's queue so it can be processed later. */
|
||||
static void example_thermo_input_callback(InputEvent* event, void* ctx) {
|
||||
ExampleThermoContext* context = ctx;
|
||||
furi_message_queue_put(context->event_queue, event, FuriWaitForever);
|
||||
}
|
||||
|
||||
/* Starts the reader thread and handles the input */
|
||||
static void example_thermo_run(ExampleThermoContext* context) {
|
||||
/* Configure the hardware in host mode */
|
||||
onewire_host_start(context->onewire);
|
||||
|
||||
/* Start the reader thread. It will talk to the thermometer in the background. */
|
||||
furi_thread_start(context->reader_thread);
|
||||
|
||||
/* An endless loop which handles the input*/
|
||||
for(bool is_running = true; is_running;) {
|
||||
InputEvent event;
|
||||
/* Wait for an input event. Input events come from the GUI thread via a callback. */
|
||||
const FuriStatus status =
|
||||
furi_message_queue_get(context->event_queue, &event, FuriWaitForever);
|
||||
|
||||
/* This application is only interested in short button presses. */
|
||||
if((status != FuriStatusOk) || (event.type != InputTypeShort)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* When the user presses the "Back" button, break the loop and exit the application. */
|
||||
if(event.key == InputKeyBack) {
|
||||
is_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal the reader thread to cease operation and exit */
|
||||
furi_thread_flags_set(furi_thread_get_id(context->reader_thread), ReaderThreadFlagExit);
|
||||
|
||||
/* Wait for the reader thread to finish */
|
||||
furi_thread_join(context->reader_thread);
|
||||
|
||||
/* Reset the hardware */
|
||||
onewire_host_stop(context->onewire);
|
||||
}
|
||||
|
||||
/******************** Initialisation & startup *****************************/
|
||||
|
||||
/* Allocate the memory and initialise the variables */
|
||||
static ExampleThermoContext* example_thermo_context_alloc() {
|
||||
ExampleThermoContext* context = malloc(sizeof(ExampleThermoContext));
|
||||
|
||||
context->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(context->view_port, example_thermo_draw_callback, context);
|
||||
view_port_input_callback_set(context->view_port, example_thermo_input_callback, context);
|
||||
|
||||
context->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
context->reader_thread = furi_thread_alloc();
|
||||
furi_thread_set_stack_size(context->reader_thread, 1024U);
|
||||
furi_thread_set_context(context->reader_thread, context);
|
||||
furi_thread_set_callback(context->reader_thread, example_thermo_reader_thread_callback);
|
||||
|
||||
context->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(context->gui, context->view_port, GuiLayerFullscreen);
|
||||
|
||||
context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* Release the unused resources and deallocate memory */
|
||||
static void example_thermo_context_free(ExampleThermoContext* context) {
|
||||
view_port_enabled_set(context->view_port, false);
|
||||
gui_remove_view_port(context->gui, context->view_port);
|
||||
|
||||
onewire_host_free(context->onewire);
|
||||
furi_thread_free(context->reader_thread);
|
||||
furi_message_queue_free(context->event_queue);
|
||||
view_port_free(context->view_port);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
}
|
||||
|
||||
/* The application's entry point. Execution starts from here. */
|
||||
int32_t example_thermo_main(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
/* Allocate all of the necessary structures */
|
||||
ExampleThermoContext* context = example_thermo_context_alloc();
|
||||
|
||||
/* Start the applicaton's main loop. It won't return until the application was requested to exit. */
|
||||
example_thermo_run(context);
|
||||
|
||||
/* Release all unneeded resources */
|
||||
example_thermo_context_free(context);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
@@ -9,7 +9,7 @@ App(
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
"bad_usb",
|
||||
"bad_kb",
|
||||
"u2f",
|
||||
"fap_loader",
|
||||
"sub_playlist",
|
||||
@@ -25,13 +25,13 @@ App(
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"gpio",
|
||||
#"ibutton",
|
||||
# "ibutton",
|
||||
"infrared",
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
#"bad_usb",
|
||||
#"u2f",
|
||||
# "bad_kb",
|
||||
# "u2f",
|
||||
"fap_loader",
|
||||
"archive",
|
||||
],
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <archive/views/archive_browser_view.h>
|
||||
#include "archive_files.h"
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
#include "../views/archive_browser_view.h"
|
||||
|
||||
#include <core/common_defines.h>
|
||||
#include <core/log.h>
|
||||
#include "gui/modules/file_browser_worker.h"
|
||||
#include <gui/modules/file_browser_worker.h>
|
||||
#include <fap_loader/fap_loader_app.h>
|
||||
#include <math.h>
|
||||
|
||||
@@ -55,9 +56,14 @@ static void archive_list_load_cb(void* context, uint32_t list_load_offset) {
|
||||
false);
|
||||
}
|
||||
|
||||
static void
|
||||
archive_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last) {
|
||||
static void archive_list_item_cb(
|
||||
void* context,
|
||||
FuriString* item_path,
|
||||
uint32_t idx,
|
||||
bool is_folder,
|
||||
bool is_last) {
|
||||
furi_assert(context);
|
||||
UNUSED(idx);
|
||||
ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
|
||||
|
||||
if(!is_last) {
|
||||
@@ -67,7 +73,9 @@ static void
|
||||
browser->view,
|
||||
ArchiveBrowserViewModel * model,
|
||||
{
|
||||
files_array_sort(model->files);
|
||||
if(model->item_cnt <= BROWSER_SORT_THRESHOLD) {
|
||||
files_array_sort(model->files);
|
||||
}
|
||||
model->list_loading = false;
|
||||
},
|
||||
true);
|
||||
@@ -456,10 +464,14 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
|
||||
|
||||
browser->last_tab_switch_dir = key;
|
||||
|
||||
if(key == InputKeyLeft) {
|
||||
tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
|
||||
} else {
|
||||
tab = (tab + 1) % ArchiveTabTotal;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
if(key == InputKeyLeft) {
|
||||
tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
|
||||
} else {
|
||||
tab = (tab + 1) % ArchiveTabTotal;
|
||||
}
|
||||
if(tab == ArchiveTabInternal && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
browser->is_root = true;
|
||||
|
||||
@@ -14,9 +14,10 @@ static const char* tab_default_paths[] = {
|
||||
[ArchiveTabSubGhz] = ANY_PATH("subghz"),
|
||||
[ArchiveTabLFRFID] = ANY_PATH("lfrfid"),
|
||||
[ArchiveTabInfrared] = ANY_PATH("infrared"),
|
||||
[ArchiveTabBadUsb] = ANY_PATH("badusb"),
|
||||
[ArchiveTabBadKb] = ANY_PATH("badkb"),
|
||||
[ArchiveTabU2f] = "/app:u2f",
|
||||
[ArchiveTabApplications] = ANY_PATH("apps"),
|
||||
[ArchiveTabInternal] = STORAGE_INT_PATH_PREFIX,
|
||||
[ArchiveTabBrowser] = STORAGE_ANY_PATH_PREFIX,
|
||||
};
|
||||
|
||||
@@ -26,7 +27,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeSubGhz] = ".sub",
|
||||
[ArchiveFileTypeLFRFID] = ".rfid",
|
||||
[ArchiveFileTypeInfrared] = ".ir",
|
||||
[ArchiveFileTypeBadUsb] = ".txt",
|
||||
[ArchiveFileTypeBadKb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
@@ -41,9 +42,10 @@ static const ArchiveFileTypeEnum known_type[] = {
|
||||
[ArchiveTabSubGhz] = ArchiveFileTypeSubGhz,
|
||||
[ArchiveTabLFRFID] = ArchiveFileTypeLFRFID,
|
||||
[ArchiveTabInfrared] = ArchiveFileTypeInfrared,
|
||||
[ArchiveTabBadUsb] = ArchiveFileTypeBadUsb,
|
||||
[ArchiveTabBadKb] = ArchiveFileTypeBadKb,
|
||||
[ArchiveTabU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveTabApplications] = ArchiveFileTypeApplication,
|
||||
[ArchiveTabInternal] = ArchiveFileTypeUnknown,
|
||||
[ArchiveTabBrowser] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||
if(furi_string_search(file->path, known_ext[i], 0) != FURI_STRING_FAILURE) {
|
||||
if(i == ArchiveFileTypeBadUsb) {
|
||||
if(furi_string_search(
|
||||
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
|
||||
if(i == ArchiveFileTypeBadKb) {
|
||||
if(furi_string_search(file->path, archive_get_default_path(ArchiveTabBadKb)) ==
|
||||
0) {
|
||||
file->type = i;
|
||||
return; // *.txt file is a BadUSB script only if it is in BadUSB folder
|
||||
return; // *.txt file is a BadKB script only if it is in BadKB folder
|
||||
}
|
||||
} else {
|
||||
file->type = i;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
#include <m-array.h>
|
||||
#include <furi.h>
|
||||
#include <m-algo.h>
|
||||
#include <m-string.h>
|
||||
#include <storage/storage.h>
|
||||
#include "toolbox/path.h"
|
||||
#include "xtreme/settings.h"
|
||||
|
||||
#define FAP_MANIFEST_MAX_ICON_SIZE 32
|
||||
|
||||
@@ -15,7 +15,7 @@ typedef enum {
|
||||
ArchiveFileTypeSubGhz,
|
||||
ArchiveFileTypeLFRFID,
|
||||
ArchiveFileTypeInfrared,
|
||||
ArchiveFileTypeBadUsb,
|
||||
ArchiveFileTypeBadKb,
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
@@ -84,11 +84,16 @@ static void ArchiveFile_t_clear(ArchiveFile_t* obj) {
|
||||
}
|
||||
|
||||
static int ArchiveFile_t_cmp(const ArchiveFile_t* a, const ArchiveFile_t* b) {
|
||||
if(a->type == ArchiveFileTypeFolder && b->type != ArchiveFileTypeFolder) {
|
||||
return -1;
|
||||
if(!XTREME_SETTINGS()->sort_ignore_dirs) {
|
||||
if(a->type == ArchiveFileTypeFolder && b->type != ArchiveFileTypeFolder) {
|
||||
return -1;
|
||||
}
|
||||
if(a->type != ArchiveFileTypeFolder && b->type == ArchiveFileTypeFolder) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return furi_string_cmp(a->path, b->path);
|
||||
return furi_string_cmpi(a->path, b->path);
|
||||
}
|
||||
|
||||
#define M_OPL_ArchiveFile_t() \
|
||||
|
||||
@@ -17,7 +17,7 @@ static const char* flipper_app_name[] = {
|
||||
[ArchiveFileTypeSubGhz] = "Sub-GHz",
|
||||
[ArchiveFileTypeLFRFID] = "125 kHz RFID",
|
||||
[ArchiveFileTypeInfrared] = "Infrared",
|
||||
[ArchiveFileTypeBadUsb] = "Bad USB",
|
||||
[ArchiveFileTypeBadKb] = "Bad KB",
|
||||
[ArchiveFileTypeU2f] = "U2F",
|
||||
[ArchiveFileTypeApplication] = "Applications",
|
||||
[ArchiveFileTypeUpdateManifest] = "UpdaterApp",
|
||||
|
||||
@@ -16,9 +16,10 @@ static const char* ArchiveTabNames[] = {
|
||||
[ArchiveTabSubGhz] = "Sub-GHz",
|
||||
[ArchiveTabLFRFID] = "RFID LF",
|
||||
[ArchiveTabInfrared] = "Infrared",
|
||||
[ArchiveTabBadUsb] = "Bad USB",
|
||||
[ArchiveTabBadKb] = "Bad KB",
|
||||
[ArchiveTabU2f] = "U2F",
|
||||
[ArchiveTabApplications] = "Apps",
|
||||
[ArchiveTabInternal] = "Internal",
|
||||
[ArchiveTabBrowser] = "Browser",
|
||||
};
|
||||
|
||||
@@ -28,7 +29,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeSubGhz] = &I_sub1_10px,
|
||||
[ArchiveFileTypeLFRFID] = &I_125_10px,
|
||||
[ArchiveFileTypeInfrared] = &I_ir_10px,
|
||||
[ArchiveFileTypeBadUsb] = &I_badusb_10px,
|
||||
[ArchiveFileTypeBadKb] = &I_badkb_10px,
|
||||
[ArchiveFileTypeU2f] = &I_u2f_10px,
|
||||
[ArchiveFileTypeApplication] = &I_Apps_10px,
|
||||
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
|
||||
@@ -109,7 +110,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_pin,
|
||||
ArchiveBrowserEventFileMenuPin);
|
||||
if(selected->type <= ArchiveFileTypeBadUsb) {
|
||||
if(selected->type <= ArchiveFileTypeBadKb) {
|
||||
archive_menu_add_item(
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_show,
|
||||
@@ -129,7 +130,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_info,
|
||||
ArchiveBrowserEventFileMenuInfo);
|
||||
if(selected->type <= ArchiveFileTypeBadUsb) {
|
||||
if(selected->type <= ArchiveFileTypeBadKb) {
|
||||
archive_menu_add_item(
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_show,
|
||||
@@ -157,7 +158,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_info,
|
||||
ArchiveBrowserEventFileMenuInfo);
|
||||
if(selected->type <= ArchiveFileTypeBadUsb) {
|
||||
if(selected->type <= ArchiveFileTypeBadKb) {
|
||||
archive_menu_add_item(
|
||||
menu_array_push_raw(model->context_menu),
|
||||
item_show,
|
||||
@@ -397,15 +398,20 @@ static bool archive_view_input(InputEvent* event, void* context) {
|
||||
|
||||
bool in_menu;
|
||||
bool move_fav_mode;
|
||||
bool is_loading;
|
||||
with_view_model(
|
||||
browser->view,
|
||||
ArchiveBrowserViewModel * model,
|
||||
{
|
||||
in_menu = model->menu;
|
||||
move_fav_mode = model->move_fav;
|
||||
is_loading = model->folder_loading || model->list_loading;
|
||||
},
|
||||
false);
|
||||
|
||||
if(is_loading) {
|
||||
return false;
|
||||
}
|
||||
if(in_menu) {
|
||||
if(event->type != InputTypeShort) {
|
||||
return true; // RETURN
|
||||
@@ -481,7 +487,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
|
||||
model->scroll_counter = 0;
|
||||
}
|
||||
},
|
||||
true);
|
||||
false);
|
||||
archive_update_offset(browser);
|
||||
}
|
||||
|
||||
@@ -588,4 +594,4 @@ void browser_free(ArchiveBrowserView* browser) {
|
||||
|
||||
view_free(browser->view);
|
||||
free(browser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "../helpers/archive_files.h"
|
||||
#include "../helpers/archive_favorites.h"
|
||||
|
||||
#include <gui/gui_i.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
#include <gui/modules/file_browser_worker.h>
|
||||
#include <storage/storage.h>
|
||||
#include "../helpers/archive_files.h"
|
||||
#include "../helpers/archive_menu.h"
|
||||
@@ -25,9 +28,10 @@ typedef enum {
|
||||
ArchiveTabNFC,
|
||||
ArchiveTabInfrared,
|
||||
ArchiveTabIButton,
|
||||
ArchiveTabBadUsb,
|
||||
ArchiveTabBadKb,
|
||||
ArchiveTabU2f,
|
||||
ArchiveTabApplications,
|
||||
ArchiveTabInternal,
|
||||
ArchiveTabBrowser,
|
||||
ArchiveTabTotal,
|
||||
} ArchiveTabEnum;
|
||||
|
||||
+5
-5
@@ -1,15 +1,15 @@
|
||||
App(
|
||||
appid="bad_usb",
|
||||
name="Bad USB",
|
||||
appid="bad_kb",
|
||||
name="Bad KB",
|
||||
apptype=FlipperAppType.APP,
|
||||
entry_point="bad_usb_app",
|
||||
cdefines=["APP_BAD_USB"],
|
||||
entry_point="bad_kb_app",
|
||||
cdefines=["APP_BAD_KB"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
icon="A_BadUsb_14",
|
||||
icon="A_BadKb_14",
|
||||
order=70,
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "bad_kb_app_i.h"
|
||||
#include "bad_kb_settings_filename.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <xtreme/settings.h>
|
||||
|
||||
#include <bt/bt_service/bt_i.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#define BAD_KB_SETTINGS_PATH BAD_KB_APP_BASE_FOLDER "/" BAD_KB_SETTINGS_FILE_NAME
|
||||
|
||||
static bool bad_kb_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
BadKbApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool bad_kb_app_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BadKbApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void bad_kb_app_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BadKbApp* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void bad_kb_load_settings(BadKbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_KB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
char chr;
|
||||
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||
furi_string_push_back(app->keyboard_layout, chr);
|
||||
}
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
static void bad_kb_save_settings(BadKbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_KB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
storage_file_write(
|
||||
settings_file,
|
||||
furi_string_get_cstr(app->keyboard_layout),
|
||||
furi_string_size(app->keyboard_layout));
|
||||
storage_file_write(settings_file, "\n", 1);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
void bad_kb_set_name(BadKbApp* app, const char* fmt, ...) {
|
||||
furi_assert(app);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
vsnprintf(app->name, BAD_KB_ADV_NAME_MAX_LEN, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
BadKbApp* app = malloc(sizeof(BadKbApp));
|
||||
|
||||
app->bad_kb_script = NULL;
|
||||
|
||||
app->file_path = furi_string_alloc();
|
||||
app->keyboard_layout = furi_string_alloc();
|
||||
if(arg && strlen(arg)) {
|
||||
furi_string_set(app->file_path, arg);
|
||||
}
|
||||
|
||||
bad_kb_load_settings(app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&bad_kb_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, bad_kb_app_tick_event_callback, 500);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, bad_kb_app_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, bad_kb_app_back_event_callback);
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
app->bt = bt;
|
||||
app->is_bt = XTREME_SETTINGS()->bad_bt;
|
||||
const char* adv_name = bt_get_profile_adv_name(bt);
|
||||
memcpy(app->name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
|
||||
memcpy(app->bt_old_config.name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
|
||||
|
||||
const uint8_t* mac_addr = bt_get_profile_mac_address(bt);
|
||||
memcpy(app->mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
|
||||
memcpy(app->bt_old_config.mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
|
||||
|
||||
// Custom Widget
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadKbAppViewError, widget_get_view(app->widget));
|
||||
|
||||
app->var_item_list_bt = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
BadKbAppViewConfigBt,
|
||||
variable_item_list_get_view(app->var_item_list_bt));
|
||||
app->var_item_list_usb = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
BadKbAppViewConfigUsb,
|
||||
variable_item_list_get_view(app->var_item_list_usb));
|
||||
|
||||
app->bad_kb_view = bad_kb_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadKbAppViewWork, bad_kb_get_view(app->bad_kb_view));
|
||||
|
||||
app->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadKbAppViewConfigName, text_input_get_view(app->text_input));
|
||||
|
||||
app->byte_input = byte_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadKbAppViewConfigMac, byte_input_get_view(app->byte_input));
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
if(furi_hal_usb_is_locked()) {
|
||||
app->error = BadKbAppErrorCloseRpc;
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneError);
|
||||
} else {
|
||||
if(!furi_string_empty(app->file_path)) {
|
||||
app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout);
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneWork);
|
||||
} else {
|
||||
furi_string_set(app->file_path, BAD_KB_APP_BASE_FOLDER);
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneFileSelect);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void bad_kb_app_free(BadKbApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(app->bad_kb_script) {
|
||||
bad_kb_script_close(app->bad_kb_script);
|
||||
app->bad_kb_script = NULL;
|
||||
}
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewWork);
|
||||
bad_kb_free(app->bad_kb_view);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewError);
|
||||
widget_free(app->widget);
|
||||
|
||||
// Variable item list
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewConfigBt);
|
||||
variable_item_list_free(app->var_item_list_bt);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewConfigUsb);
|
||||
variable_item_list_free(app->var_item_list_usb);
|
||||
|
||||
// Text Input
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewConfigName);
|
||||
text_input_free(app->text_input);
|
||||
|
||||
// Byte Input
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadKbAppViewConfigMac);
|
||||
byte_input_free(app->byte_input);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
// restores bt config
|
||||
// BtProfile have already been switched to the previous one
|
||||
// so we directly modify the right profile
|
||||
bad_kb_connection_deinit(app->bt);
|
||||
if(strcmp(app->bt_old_config.name, app->name) != 0) {
|
||||
furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->bt_old_config.name);
|
||||
}
|
||||
if(memcmp(app->bt_old_config.mac, app->mac, BAD_KB_MAC_ADDRESS_LEN) != 0) {
|
||||
furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->bt_old_config.mac);
|
||||
}
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_BT);
|
||||
|
||||
bad_kb_save_settings(app);
|
||||
|
||||
furi_string_free(app->file_path);
|
||||
furi_string_free(app->keyboard_layout);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t bad_kb_app(void* p) {
|
||||
BadKbApp* bad_kb_app = bad_kb_app_alloc((char*)p);
|
||||
|
||||
view_dispatcher_run(bad_kb_app->view_dispatcher);
|
||||
|
||||
bad_kb_app_free(bad_kb_app);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct BadKbApp BadKbApp;
|
||||
|
||||
void bad_kb_set_name(BadKbApp* app, const char* fmt, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "bad_kb_app.h"
|
||||
#include "scenes/bad_kb_scene.h"
|
||||
#include "bad_kb_script.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include "views/bad_kb_view.h"
|
||||
|
||||
#define BAD_KB_APP_BASE_FOLDER ANY_PATH("badkb")
|
||||
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/layouts"
|
||||
#define BAD_KB_APP_SCRIPT_EXTENSION ".txt"
|
||||
#define BAD_KB_APP_LAYOUT_EXTENSION ".kl"
|
||||
|
||||
#define BAD_KB_MAC_ADDRESS_LEN 6 // need replace with MAC size maccro
|
||||
#define BAD_KB_ADV_NAME_MAX_LEN 18
|
||||
|
||||
typedef enum {
|
||||
BadKbAppErrorNoFiles,
|
||||
BadKbAppErrorCloseRpc,
|
||||
} BadKbAppError;
|
||||
|
||||
typedef enum BadKbCustomEvent {
|
||||
BadKbAppCustomEventTextEditResult,
|
||||
BadKbAppCustomEventByteInputDone,
|
||||
BadKbCustomEventErrorBack
|
||||
} BadKbCustomEvent;
|
||||
|
||||
typedef struct {
|
||||
uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
|
||||
char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
|
||||
|
||||
// number of bt keys before starting the app (all keys added in
|
||||
// the bt keys file then will be removed)
|
||||
uint16_t n_keys;
|
||||
} BadKbBtConfig;
|
||||
|
||||
struct BadKbApp {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
NotificationApp* notifications;
|
||||
DialogsApp* dialogs;
|
||||
Widget* widget;
|
||||
VariableItemList* var_item_list_bt;
|
||||
VariableItemList* var_item_list_usb;
|
||||
|
||||
Bt* bt;
|
||||
TextInput* text_input;
|
||||
ByteInput* byte_input;
|
||||
uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
|
||||
char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
|
||||
BadKbBtConfig bt_old_config;
|
||||
|
||||
BadKbAppError error;
|
||||
FuriString* file_path;
|
||||
FuriString* keyboard_layout;
|
||||
BadKb* bad_kb_view;
|
||||
BadKbScript* bad_kb_script;
|
||||
|
||||
bool is_bt;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
BadKbAppViewError,
|
||||
BadKbAppViewWork,
|
||||
BadKbAppViewConfigBt,
|
||||
BadKbAppViewConfigUsb,
|
||||
BadKbAppViewConfigMac,
|
||||
BadKbAppViewConfigName
|
||||
} BadKbAppView;
|
||||
@@ -0,0 +1,976 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <storage/storage.h>
|
||||
#include "bad_kb_script.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
|
||||
|
||||
#define TAG "BadKB"
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
#define FILE_BUFFER_LEN 16
|
||||
|
||||
#define SCRIPT_STATE_ERROR (-1)
|
||||
#define SCRIPT_STATE_END (-2)
|
||||
#define SCRIPT_STATE_NEXT_LINE (-3)
|
||||
|
||||
#define BADKB_ASCII_TO_KEY(script, x) \
|
||||
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtToggle = (1 << 0),
|
||||
WorkerEvtEnd = (1 << 1),
|
||||
WorkerEvtConnect = (1 << 2),
|
||||
WorkerEvtDisconnect = (1 << 3),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
typedef enum {
|
||||
LevelRssi122_100,
|
||||
LevelRssi99_80,
|
||||
LevelRssi79_60,
|
||||
LevelRssi59_40,
|
||||
LevelRssi39_0,
|
||||
LevelRssiNum,
|
||||
LevelRssiError = 0xFF,
|
||||
} LevelRssiRange;
|
||||
|
||||
typedef enum {
|
||||
BadKbConnectionModeNone,
|
||||
BadKbConnectionModeUsb,
|
||||
BadKbConnectionModeBt,
|
||||
} BadKbConnectionMode;
|
||||
|
||||
/**
|
||||
* Delays for waiting between HID key press and key release
|
||||
*/
|
||||
const uint8_t bt_hid_delays[LevelRssiNum] = {
|
||||
30, // LevelRssi122_100
|
||||
25, // LevelRssi99_80
|
||||
20, // LevelRssi79_60
|
||||
17, // LevelRssi59_40
|
||||
14, // LevelRssi39_0
|
||||
};
|
||||
|
||||
struct BadKbScript {
|
||||
FuriHalUsbHidConfig hid_cfg;
|
||||
BadKbState st;
|
||||
FuriString* file_path;
|
||||
FuriString* keyboard_layout;
|
||||
uint32_t defdelay;
|
||||
uint16_t layout[128];
|
||||
FuriThread* thread;
|
||||
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||
uint8_t buf_start;
|
||||
uint8_t buf_len;
|
||||
bool file_end;
|
||||
FuriString* line;
|
||||
|
||||
FuriString* line_prev;
|
||||
uint32_t repeat_cnt;
|
||||
|
||||
Bt* bt;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
uint16_t keycode;
|
||||
} DuckyKey;
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
|
||||
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
|
||||
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||
|
||||
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"UPARROW", HID_KEYBOARD_UP_ARROW},
|
||||
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||
|
||||
{"ENTER", HID_KEYBOARD_RETURN},
|
||||
{"BREAK", HID_KEYBOARD_PAUSE},
|
||||
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||
{"DELETE", HID_KEYBOARD_DELETE},
|
||||
{"BACKSPACE", HID_KEYPAD_BACKSPACE},
|
||||
{"END", HID_KEYBOARD_END},
|
||||
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||
{"ESCAPE", HID_KEYBOARD_ESCAPE},
|
||||
{"HOME", HID_KEYBOARD_HOME},
|
||||
{"INSERT", HID_KEYBOARD_INSERT},
|
||||
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||
{"TAB", HID_KEYBOARD_TAB},
|
||||
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||
{"APP", HID_KEYBOARD_APPLICATION},
|
||||
|
||||
{"F1", HID_KEYBOARD_F1},
|
||||
{"F2", HID_KEYBOARD_F2},
|
||||
{"F3", HID_KEYBOARD_F3},
|
||||
{"F4", HID_KEYBOARD_F4},
|
||||
{"F5", HID_KEYBOARD_F5},
|
||||
{"F6", HID_KEYBOARD_F6},
|
||||
{"F7", HID_KEYBOARD_F7},
|
||||
{"F8", HID_KEYBOARD_F8},
|
||||
{"F9", HID_KEYBOARD_F9},
|
||||
{"F10", HID_KEYBOARD_F10},
|
||||
{"F11", HID_KEYBOARD_F11},
|
||||
{"F12", HID_KEYBOARD_F12},
|
||||
};
|
||||
|
||||
static const char ducky_cmd_comment[] = {"REM"};
|
||||
static const char ducky_cmd_id[] = {"ID"};
|
||||
static const char ducky_cmd_delay[] = {"DELAY "};
|
||||
static const char ducky_cmd_string[] = {"STRING "};
|
||||
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
|
||||
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
|
||||
|
||||
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
|
||||
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
|
||||
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
|
||||
|
||||
static const char ducky_cmd_lang[] = {"DUCKY_LANG"};
|
||||
|
||||
static const uint8_t numpad_keys[10] = {
|
||||
HID_KEYPAD_0,
|
||||
HID_KEYPAD_1,
|
||||
HID_KEYPAD_2,
|
||||
HID_KEYPAD_3,
|
||||
HID_KEYPAD_4,
|
||||
HID_KEYPAD_5,
|
||||
HID_KEYPAD_6,
|
||||
HID_KEYPAD_7,
|
||||
HID_KEYPAD_8,
|
||||
HID_KEYPAD_9,
|
||||
};
|
||||
|
||||
BadKbConnectionMode connection_mode = BadKbConnectionModeNone;
|
||||
FuriHalUsbInterface* usb_mode_prev = NULL;
|
||||
GapPairing bt_mode_prev = GapPairingNone;
|
||||
bool bt_connected = false;
|
||||
bool usb_connected = false;
|
||||
uint8_t bt_timeout = 0;
|
||||
|
||||
static LevelRssiRange bt_remote_rssi_range(Bt* bt) {
|
||||
BtRssi rssi_data = {0};
|
||||
|
||||
if(!bt_remote_rssi(bt, &rssi_data)) return LevelRssiError;
|
||||
|
||||
if(rssi_data.rssi <= 39)
|
||||
return LevelRssi39_0;
|
||||
else if(rssi_data.rssi <= 59)
|
||||
return LevelRssi59_40;
|
||||
else if(rssi_data.rssi <= 79)
|
||||
return LevelRssi79_60;
|
||||
else if(rssi_data.rssi <= 99)
|
||||
return LevelRssi99_80;
|
||||
else if(rssi_data.rssi <= 122)
|
||||
return LevelRssi122_100;
|
||||
|
||||
return LevelRssiError;
|
||||
}
|
||||
|
||||
static inline void update_bt_timeout(Bt* bt) {
|
||||
LevelRssiRange r = bt_remote_rssi_range(bt);
|
||||
if(r < LevelRssiNum) {
|
||||
bt_timeout = bt_hid_delays[r];
|
||||
FURI_LOG_D(WORKER_TAG, "BLE Key timeout : %u", bt_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_get_number(const char* param, uint32_t* val) {
|
||||
uint32_t value = 0;
|
||||
if(sscanf(param, "%lu", &value) == 1) {
|
||||
*val = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ducky_is_line_end(const char chr) {
|
||||
return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
|
||||
}
|
||||
|
||||
static void ducky_numlock_on(BadKbScript* bad_kb) {
|
||||
if(bad_kb->bt) {
|
||||
if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) { // FIXME
|
||||
furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
furi_delay_ms(bt_timeout);
|
||||
furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
}
|
||||
} else {
|
||||
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_numpad_press(BadKbScript* bad_kb, const char num) {
|
||||
if((num < '0') || (num > '9')) return false;
|
||||
|
||||
uint16_t key = numpad_keys[num - '0'];
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_press(key);
|
||||
furi_delay_ms(bt_timeout);
|
||||
furi_hal_bt_hid_kb_release(key);
|
||||
} else {
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ducky_altchar(BadKbScript* bad_kb, const char* charcode) {
|
||||
uint8_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
|
||||
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||
} else {
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||
}
|
||||
|
||||
while(!ducky_is_line_end(charcode[i])) {
|
||||
state = ducky_numpad_press(bad_kb, charcode[i]);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||
} else {
|
||||
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool ducky_altstring(BadKbScript* bad_kb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
while(param[i] != '\0') {
|
||||
if((param[i] < ' ') || (param[i] > '~')) {
|
||||
i++;
|
||||
continue; // Skip non-printable chars
|
||||
}
|
||||
|
||||
char temp_str[4];
|
||||
snprintf(temp_str, 4, "%u", param[i]);
|
||||
|
||||
state = ducky_altchar(bad_kb, temp_str);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool ducky_string(BadKbScript* bad_kb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
while(param[i] != '\0') {
|
||||
uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_press(keycode);
|
||||
furi_delay_ms(bt_timeout);
|
||||
furi_hal_bt_hid_kb_release(keycode);
|
||||
} else {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t ducky_get_keycode(BadKbScript* bad_kb, const char* param, bool accept_chars) {
|
||||
for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
|
||||
(ducky_is_line_end(param[key_cmd_len]))) {
|
||||
return ducky_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
if((accept_chars) && (strlen(param) > 0)) {
|
||||
return (BADKB_ASCII_TO_KEY(bad_kb, param[0]) & 0xFF);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
ducky_parse_line(BadKbScript* bad_kb, 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;
|
||||
|
||||
if(line_len == 0) {
|
||||
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
|
||||
}
|
||||
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
|
||||
|
||||
// General commands
|
||||
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
||||
// REM - comment line
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
// ID - executed in ducky_script_preload
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) {
|
||||
// DUCKY_LANG - ignore command to retain compatibility with existing scripts
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||
// DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
uint32_t delay_val = 0;
|
||||
state = ducky_get_number(line_tmp, &delay_val);
|
||||
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) ||
|
||||
(strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
|
||||
// DEFAULT_DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_kb->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(bad_kb, 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(bad_kb);
|
||||
state = ducky_altchar(bad_kb, 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) ||
|
||||
(strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
|
||||
// ALTSTRING
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
ducky_numlock_on(bad_kb);
|
||||
state = ducky_altstring(bad_kb, 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_kb->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
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_kb, line_tmp, true);
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
furi_hal_bt_hid_kb_press(key);
|
||||
furi_delay_ms(bt_timeout);
|
||||
furi_hal_bt_hid_kb_release(key);
|
||||
furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
} else {
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
return (0);
|
||||
} else {
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_kb, line_tmp, false);
|
||||
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];
|
||||
key |= ducky_get_keycode(bad_kb, line_tmp, true);
|
||||
}
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_press(key);
|
||||
furi_delay_ms(bt_timeout);
|
||||
furi_hal_bt_hid_kb_release(key);
|
||||
} else {
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_set_usb_id(BadKbScript* bad_kb, const char* line) {
|
||||
if(sscanf(line, "%lX:%lX", &bad_kb->hid_cfg.vid, &bad_kb->hid_cfg.pid) == 2) {
|
||||
bad_kb->hid_cfg.manuf[0] = '\0';
|
||||
bad_kb->hid_cfg.product[0] = '\0';
|
||||
|
||||
uint8_t id_len = ducky_get_command_len(line);
|
||||
if(!ducky_is_line_end(line[id_len + 1])) {
|
||||
sscanf(
|
||||
&line[id_len + 1],
|
||||
"%31[^\r\n:]:%31[^\r\n]",
|
||||
bad_kb->hid_cfg.manuf,
|
||||
bad_kb->hid_cfg.product);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
WORKER_TAG,
|
||||
"set id: %04lX:%04lX mfr:%s product:%s",
|
||||
bad_kb->hid_cfg.vid,
|
||||
bad_kb->hid_cfg.pid,
|
||||
bad_kb->hid_cfg.manuf,
|
||||
bad_kb->hid_cfg.product);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ducky_script_preload(BadKbScript* bad_kb, File* script_file) {
|
||||
uint8_t ret = 0;
|
||||
uint32_t line_len = 0;
|
||||
|
||||
furi_string_reset(bad_kb->line);
|
||||
|
||||
do {
|
||||
ret = storage_file_read(script_file, bad_kb->file_buf, FILE_BUFFER_LEN);
|
||||
for(uint16_t i = 0; i < ret; i++) {
|
||||
if(bad_kb->file_buf[i] == '\n' && line_len > 0) {
|
||||
bad_kb->st.line_nb++;
|
||||
line_len = 0;
|
||||
} else {
|
||||
if(bad_kb->st.line_nb == 0) { // Save first line
|
||||
furi_string_push_back(bad_kb->line, bad_kb->file_buf[i]);
|
||||
}
|
||||
line_len++;
|
||||
}
|
||||
}
|
||||
if(storage_file_eof(script_file)) {
|
||||
if(line_len > 0) {
|
||||
bad_kb->st.line_nb++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(ret > 0);
|
||||
|
||||
if(!bad_kb->bt) {
|
||||
const char* line_tmp = furi_string_get_cstr(bad_kb->line);
|
||||
bool id_set = false; // Looking for ID command at first line
|
||||
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
id_set = ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1]);
|
||||
}
|
||||
|
||||
if(id_set) {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, &bad_kb->hid_cfg));
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, NULL));
|
||||
}
|
||||
}
|
||||
|
||||
storage_file_seek(script_file, 0, true);
|
||||
furi_string_reset(bad_kb->line);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t ducky_script_execute_next(BadKbScript* bad_kb, File* script_file) {
|
||||
int32_t delay_val = 0;
|
||||
|
||||
if(bad_kb->repeat_cnt > 0) {
|
||||
bad_kb->repeat_cnt--;
|
||||
delay_val = ducky_parse_line(
|
||||
bad_kb, bad_kb->line_prev, bad_kb->st.error, sizeof(bad_kb->st.error));
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||
return 0;
|
||||
} else if(delay_val < 0) { // Script error
|
||||
bad_kb->st.error_line = bad_kb->st.line_cur - 1;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_kb->st.line_cur - 1U);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_kb->defdelay);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_set(bad_kb->line_prev, bad_kb->line);
|
||||
furi_string_reset(bad_kb->line);
|
||||
|
||||
while(1) {
|
||||
if(bad_kb->buf_len == 0) {
|
||||
bad_kb->buf_len = storage_file_read(script_file, bad_kb->file_buf, FILE_BUFFER_LEN);
|
||||
if(storage_file_eof(script_file)) {
|
||||
if((bad_kb->buf_len < FILE_BUFFER_LEN) && (bad_kb->file_end == false)) {
|
||||
bad_kb->file_buf[bad_kb->buf_len] = '\n';
|
||||
bad_kb->buf_len++;
|
||||
bad_kb->file_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
bad_kb->buf_start = 0;
|
||||
if(bad_kb->buf_len == 0) return SCRIPT_STATE_END;
|
||||
}
|
||||
for(uint8_t i = bad_kb->buf_start; i < (bad_kb->buf_start + bad_kb->buf_len); i++) {
|
||||
if(bad_kb->file_buf[i] == '\n' && furi_string_size(bad_kb->line) > 0) {
|
||||
bad_kb->st.line_cur++;
|
||||
bad_kb->buf_len = bad_kb->buf_len + bad_kb->buf_start - (i + 1);
|
||||
bad_kb->buf_start = i + 1;
|
||||
furi_string_trim(bad_kb->line);
|
||||
delay_val = ducky_parse_line(
|
||||
bad_kb, bad_kb->line, bad_kb->st.error, sizeof(bad_kb->st.error));
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||
return 0;
|
||||
} else if(delay_val < 0) {
|
||||
bad_kb->st.error_line = bad_kb->st.line_cur;
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) {
|
||||
snprintf(
|
||||
bad_kb->st.error, sizeof(bad_kb->st.error), "Forbidden empty line");
|
||||
FURI_LOG_E(
|
||||
WORKER_TAG, "Forbidden empty line at line %u", bad_kb->st.line_cur);
|
||||
} else {
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_kb->st.line_cur);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_kb->defdelay);
|
||||
}
|
||||
} else {
|
||||
furi_string_push_back(bad_kb->line, bad_kb->file_buf[i]);
|
||||
}
|
||||
}
|
||||
bad_kb->buf_len = 0;
|
||||
if(bad_kb->file_end) return SCRIPT_STATE_END;
|
||||
}
|
||||
}
|
||||
|
||||
static void bad_kb_bt_hid_state_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
BadKbScript* bad_kb = (BadKbScript*)context;
|
||||
bool state = (status == BtStatusConnected);
|
||||
|
||||
if(state == true) {
|
||||
LevelRssiRange r = bt_remote_rssi_range(bad_kb->bt);
|
||||
if(r != LevelRssiError) {
|
||||
bt_timeout = bt_hid_delays[r];
|
||||
}
|
||||
bt_connected = true;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
bt_connected = false;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static void bad_kb_usb_hid_state_callback(bool state, void* context) {
|
||||
furi_assert(context);
|
||||
BadKbScript* bad_kb = context;
|
||||
|
||||
if(state == true) {
|
||||
usb_connected = true;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
usb_connected = false;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_kb_bt_init(Bt* bt) {
|
||||
bt_timeout = bt_hid_delays[LevelRssi39_0];
|
||||
bt_disconnect(bt);
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_storage_path(bt, HID_BT_KEYS_STORAGE_PATH);
|
||||
furi_assert(bt_set_profile(bt, BtProfileHidKeyboard));
|
||||
bt_mode_prev = bt_get_profile_pairing_method(bt);
|
||||
bt_set_profile_pairing_method(bt, GapPairingNone);
|
||||
furi_hal_bt_start_advertising();
|
||||
// disable peer key adding to bt SRAM storage
|
||||
bt_disable_peer_key_update(bt);
|
||||
|
||||
connection_mode = BadKbConnectionModeBt;
|
||||
}
|
||||
|
||||
void bad_kb_bt_deinit(Bt* bt) {
|
||||
// release all keys
|
||||
// bt_hid_hold_while_keyboard_buffer_full(6, 3000);
|
||||
|
||||
// stop ble
|
||||
bt_disconnect(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_default_path(bt);
|
||||
|
||||
bt_set_profile_pairing_method(bt, bt_mode_prev);
|
||||
|
||||
// fails if ble radio stack isn't ready when switching profile
|
||||
// if it happens, maybe we should increase the delay after bt_disconnect
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
|
||||
// starts saving peer keys (bounded devices)
|
||||
bt_enable_peer_key_update(bt);
|
||||
|
||||
connection_mode = BadKbConnectionModeNone;
|
||||
}
|
||||
|
||||
void bad_kb_usb_init() {
|
||||
usb_mode_prev = furi_hal_usb_get_config();
|
||||
|
||||
connection_mode = BadKbConnectionModeUsb;
|
||||
}
|
||||
|
||||
void bad_kb_usb_deinit() {
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
|
||||
connection_mode = BadKbConnectionModeNone;
|
||||
}
|
||||
|
||||
void bad_kb_connection_init(Bt* bt) {
|
||||
if(connection_mode != BadKbConnectionModeNone) return;
|
||||
|
||||
if(bt) {
|
||||
bad_kb_bt_init(bt);
|
||||
} else {
|
||||
bad_kb_usb_init();
|
||||
}
|
||||
}
|
||||
|
||||
void bad_kb_connection_deinit(Bt* bt) {
|
||||
if(connection_mode == BadKbConnectionModeNone) return;
|
||||
|
||||
if(connection_mode == BadKbConnectionModeBt) {
|
||||
bad_kb_bt_deinit(bt);
|
||||
} else {
|
||||
bad_kb_usb_deinit();
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t bad_kb_worker(void* context) {
|
||||
BadKbScript* bad_kb = context;
|
||||
|
||||
BadKbWorkerState worker_state = BadKbStateInit;
|
||||
int32_t delay_val = 0;
|
||||
|
||||
bad_kb_connection_init(bad_kb->bt);
|
||||
|
||||
if(bad_kb->bt) {
|
||||
bt_set_status_changed_callback(bad_kb->bt, bad_kb_bt_hid_state_callback, bad_kb);
|
||||
} else {
|
||||
furi_hal_hid_set_state_callback(bad_kb_usb_hid_state_callback, bad_kb);
|
||||
}
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "Init");
|
||||
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
bad_kb->line = furi_string_alloc();
|
||||
bad_kb->line_prev = furi_string_alloc();
|
||||
|
||||
while(1) {
|
||||
if(worker_state == BadKbStateInit) { // State: initialization
|
||||
if(storage_file_open(
|
||||
script_file,
|
||||
furi_string_get_cstr(bad_kb->file_path),
|
||||
FSAM_READ,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
if((ducky_script_preload(bad_kb, script_file)) && (bad_kb->st.line_nb > 0)) {
|
||||
if(bad_kb->bt) {
|
||||
worker_state = BadKbStateNotConnected; // Ready to run
|
||||
} else {
|
||||
if(furi_hal_hid_is_connected()) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else {
|
||||
worker_state = BadKbStateNotConnected; // Not connected
|
||||
}
|
||||
}
|
||||
} else {
|
||||
worker_state = BadKbStateScriptError; // Script preload error
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(WORKER_TAG, "File open error");
|
||||
worker_state = BadKbStateFileError; // File open error
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateNotConnected) { // State: Not connected
|
||||
if((bad_kb->bt && bt_connected) || (!bad_kb->bt && usb_connected)) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else {
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadKbStateWillRun; // Will run when connected
|
||||
}
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateIdle) { // State: ready to start
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtToggle) { // Start executing script
|
||||
DOLPHIN_DEED(DolphinDeedBadKbPlayScript);
|
||||
delay_val = 0;
|
||||
bad_kb->buf_len = 0;
|
||||
bad_kb->st.line_cur = 0;
|
||||
bad_kb->defdelay = 0;
|
||||
bad_kb->repeat_cnt = 0;
|
||||
bad_kb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb, bad_kb->keyboard_layout);
|
||||
worker_state = BadKbStateRunning;
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadKbStateNotConnected; // Disconnected
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateWillRun) { // State: start on connection
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) { // Start executing script
|
||||
DOLPHIN_DEED(DolphinDeedBadKbPlayScript);
|
||||
delay_val = 0;
|
||||
bad_kb->buf_len = 0;
|
||||
bad_kb->st.line_cur = 0;
|
||||
bad_kb->defdelay = 0;
|
||||
bad_kb->repeat_cnt = 0;
|
||||
bad_kb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
// extra time for PC to recognize Flipper as keyboard
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
|
||||
if(bad_kb->bt) {
|
||||
update_bt_timeout(bad_kb->bt);
|
||||
}
|
||||
bad_kb_script_set_keyboard_layout(bad_kb, bad_kb->keyboard_layout);
|
||||
worker_state = BadKbStateRunning;
|
||||
} else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
|
||||
worker_state = BadKbStateNotConnected;
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateRunning) { // State: running
|
||||
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
|
||||
delay_val -= delay_cur;
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadKbStateIdle; // Stop executing script
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
} else {
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadKbStateNotConnected; // Disconnected
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
} else {
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
continue;
|
||||
} else if(
|
||||
(flags == (unsigned)FuriFlagErrorTimeout) ||
|
||||
(flags == (unsigned)FuriFlagErrorResource)) {
|
||||
if(delay_val > 0) {
|
||||
bad_kb->st.delay_remain--;
|
||||
continue;
|
||||
}
|
||||
bad_kb->st.state = BadKbStateRunning;
|
||||
delay_val = ducky_script_execute_next(bad_kb, script_file);
|
||||
if(delay_val == SCRIPT_STATE_ERROR) { // Script error
|
||||
delay_val = 0;
|
||||
worker_state = BadKbStateScriptError;
|
||||
bad_kb->st.state = worker_state;
|
||||
} else if(delay_val == SCRIPT_STATE_END) { // End of script
|
||||
delay_val = 0;
|
||||
worker_state = BadKbStateIdle;
|
||||
bad_kb->st.state = BadKbStateDone;
|
||||
if(bad_kb->bt) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
} else {
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
continue;
|
||||
} else if(delay_val > 1000) {
|
||||
bad_kb->st.state = BadKbStateDelay; // Show long delays
|
||||
bad_kb->st.delay_remain = delay_val / 1000;
|
||||
}
|
||||
} else {
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
}
|
||||
|
||||
} else if(
|
||||
(worker_state == BadKbStateFileError) ||
|
||||
(worker_state == BadKbStateScriptError)) { // State: error
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bad_kb->bt) {
|
||||
update_bt_timeout(bad_kb->bt);
|
||||
}
|
||||
}
|
||||
|
||||
if(bad_kb->bt) {
|
||||
bt_set_status_changed_callback(bad_kb->bt, NULL, NULL);
|
||||
} else {
|
||||
furi_hal_hid_set_state_callback(NULL, NULL);
|
||||
}
|
||||
|
||||
storage_file_close(script_file);
|
||||
storage_file_free(script_file);
|
||||
furi_string_free(bad_kb->line);
|
||||
furi_string_free(bad_kb->line_prev);
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "End");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bad_kb_script_set_default_keyboard_layout(BadKbScript* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
furi_string_set_str(bad_kb->keyboard_layout, "");
|
||||
memset(bad_kb->layout, HID_KEYBOARD_NONE, sizeof(bad_kb->layout));
|
||||
memcpy(bad_kb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_kb->layout)));
|
||||
}
|
||||
|
||||
BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt) {
|
||||
furi_assert(file_path);
|
||||
|
||||
BadKbScript* bad_kb = malloc(sizeof(BadKbScript));
|
||||
bad_kb->file_path = furi_string_alloc();
|
||||
furi_string_set(bad_kb->file_path, file_path);
|
||||
bad_kb->keyboard_layout = furi_string_alloc();
|
||||
bad_kb_script_set_default_keyboard_layout(bad_kb);
|
||||
|
||||
bad_kb->st.state = BadKbStateInit;
|
||||
bad_kb->st.error[0] = '\0';
|
||||
|
||||
bad_kb->bt = bt;
|
||||
|
||||
bad_kb->thread = furi_thread_alloc_ex("BadKbWorker", 2048, bad_kb_worker, bad_kb);
|
||||
furi_thread_start(bad_kb->thread);
|
||||
return bad_kb;
|
||||
} //-V773
|
||||
|
||||
void bad_kb_script_close(BadKbScript* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtEnd);
|
||||
furi_thread_join(bad_kb->thread);
|
||||
furi_thread_free(bad_kb->thread);
|
||||
furi_string_free(bad_kb->file_path);
|
||||
furi_string_free(bad_kb->keyboard_layout);
|
||||
free(bad_kb);
|
||||
}
|
||||
|
||||
void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_path) {
|
||||
furi_assert(bad_kb);
|
||||
|
||||
if((bad_kb->st.state == BadKbStateRunning) || (bad_kb->st.state == BadKbStateDelay)) {
|
||||
// do not update keyboard layout while a script is running
|
||||
return;
|
||||
}
|
||||
|
||||
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(!furi_string_empty(layout_path)) {
|
||||
furi_string_set(bad_kb->keyboard_layout, layout_path);
|
||||
if(storage_file_open(
|
||||
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
uint16_t layout[128];
|
||||
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
|
||||
memcpy(bad_kb->layout, layout, sizeof(layout));
|
||||
}
|
||||
}
|
||||
storage_file_close(layout_file);
|
||||
} else {
|
||||
bad_kb_script_set_default_keyboard_layout(bad_kb);
|
||||
}
|
||||
storage_file_free(layout_file);
|
||||
}
|
||||
|
||||
void bad_kb_script_toggle(BadKbScript* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtToggle);
|
||||
}
|
||||
|
||||
BadKbState* bad_kb_script_get_state(BadKbScript* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
return &(bad_kb->st);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt_i.h>
|
||||
|
||||
typedef struct BadKbScript BadKbScript;
|
||||
|
||||
typedef enum {
|
||||
BadKbStateInit,
|
||||
BadKbStateNotConnected,
|
||||
BadKbStateIdle,
|
||||
BadKbStateWillRun,
|
||||
BadKbStateRunning,
|
||||
BadKbStateDelay,
|
||||
BadKbStateDone,
|
||||
BadKbStateScriptError,
|
||||
BadKbStateFileError,
|
||||
} BadKbWorkerState;
|
||||
|
||||
typedef struct {
|
||||
BadKbWorkerState state;
|
||||
uint16_t line_cur;
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
uint16_t error_line;
|
||||
char error[64];
|
||||
} BadKbState;
|
||||
|
||||
void bad_kb_connection_init(Bt* bt);
|
||||
|
||||
void bad_kb_connection_deinit(Bt* bt);
|
||||
|
||||
BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt);
|
||||
|
||||
void bad_kb_script_close(BadKbScript* bad_kb);
|
||||
|
||||
void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_path);
|
||||
|
||||
void bad_kb_script_start(BadKbScript* bad_kb);
|
||||
|
||||
void bad_kb_script_stop(BadKbScript* bad_kb);
|
||||
|
||||
void bad_kb_script_toggle(BadKbScript* bad_kb);
|
||||
|
||||
BadKbState* bad_kb_script_get_state(BadKbScript* bad_kb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define BAD_KB_SETTINGS_FILE_NAME ".badkb.settings"
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "bad_kb_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const bad_kb_scene_on_enter_handlers[])(void*) = {
|
||||
#include "bad_kb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const bad_kb_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "bad_kb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const bad_kb_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "bad_kb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers bad_kb_scene_handlers = {
|
||||
.on_enter_handlers = bad_kb_scene_on_enter_handlers,
|
||||
.on_event_handlers = bad_kb_scene_on_event_handlers,
|
||||
.on_exit_handlers = bad_kb_scene_on_exit_handlers,
|
||||
.scene_num = BadKbSceneNum,
|
||||
};
|
||||
+8
-8
@@ -3,27 +3,27 @@
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) BadUsbScene##id,
|
||||
#define ADD_SCENE(prefix, name, id) BadKbScene##id,
|
||||
typedef enum {
|
||||
#include "bad_usb_scene_config.h"
|
||||
BadUsbSceneNum,
|
||||
} BadUsbScene;
|
||||
#include "bad_kb_scene_config.h"
|
||||
BadKbSceneNum,
|
||||
} BadKbScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers bad_usb_scene_handlers;
|
||||
extern const SceneManagerHandlers bad_kb_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "bad_usb_scene_config.h"
|
||||
#include "bad_kb_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "bad_usb_scene_config.h"
|
||||
#include "bad_kb_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "bad_usb_scene_config.h"
|
||||
#include "bad_kb_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,8 @@
|
||||
ADD_SCENE(bad_kb, file_select, FileSelect)
|
||||
ADD_SCENE(bad_kb, work, Work)
|
||||
ADD_SCENE(bad_kb, error, Error)
|
||||
ADD_SCENE(bad_kb, config_bt, ConfigBt)
|
||||
ADD_SCENE(bad_kb, config_usb, ConfigUsb)
|
||||
ADD_SCENE(bad_kb, config_layout, ConfigLayout)
|
||||
ADD_SCENE(bad_kb, config_name, ConfigName)
|
||||
ADD_SCENE(bad_kb, config_mac, ConfigMac)
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <xtreme/settings.h>
|
||||
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexConnection,
|
||||
VarItemListIndexKeyboardLayout,
|
||||
VarItemListIndexAdvertisementName,
|
||||
VarItemListIndexMacAddress,
|
||||
};
|
||||
|
||||
void bad_kb_scene_config_bt_connection_callback(VariableItem* item) {
|
||||
BadKbApp* bad_kb = variable_item_get_context(item);
|
||||
bad_kb->is_bt = variable_item_get_current_value_index(item);
|
||||
XTREME_SETTINGS()->bad_bt = bad_kb->is_bt;
|
||||
XTREME_SETTINGS_SAVE();
|
||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexConnection);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_bt_var_item_list_callback(void* context, uint32_t index) {
|
||||
BadKbApp* bad_kb = context;
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_bt_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
VariableItemList* var_item_list = bad_kb->var_item_list_bt;
|
||||
VariableItem* item;
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "Connection", 2, bad_kb_scene_config_bt_connection_callback, bad_kb);
|
||||
variable_item_set_current_value_index(item, bad_kb->is_bt);
|
||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||
|
||||
item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_kb);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "BT device name", 0, NULL, bad_kb);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "BT MAC address", 0, NULL, bad_kb);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bad_kb_scene_config_bt_var_item_list_callback, bad_kb);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigBt);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_config_bt_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* bad_kb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(bad_kb->scene_manager, BadKbSceneConfigBt, event.event);
|
||||
consumed = true;
|
||||
if(event.event == VarItemListIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigLayout);
|
||||
} else if(event.event == VarItemListIndexConnection) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb_connection_deinit(bad_kb->bt);
|
||||
bad_kb->bad_kb_script =
|
||||
bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
if(bad_kb->is_bt) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
} else if(event.event == VarItemListIndexAdvertisementName) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigName);
|
||||
} else if(event.event == VarItemListIndexMacAddress) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigMac);
|
||||
// } else {
|
||||
// furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_bt_on_exit(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
VariableItemList* var_item_list = bad_kb->var_item_list_bt;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define KEYBOARD_FOLDER "/ext/badkb/layouts"
|
||||
|
||||
static bool bad_kb_layout_select(BadKbApp* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
|
||||
FuriString* predefined_path;
|
||||
predefined_path = furi_string_alloc();
|
||||
if(!furi_string_empty(bad_kb->keyboard_layout)) {
|
||||
furi_string_set(predefined_path, bad_kb->keyboard_layout);
|
||||
} else {
|
||||
furi_string_set(predefined_path, BAD_KB_APP_PATH_LAYOUT_FOLDER);
|
||||
}
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_KB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
|
||||
browser_options.base_path = KEYBOARD_FOLDER;
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_kb->dialogs, bad_kb->keyboard_layout, predefined_path, &browser_options);
|
||||
|
||||
furi_string_free(predefined_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_layout_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
if(bad_kb_layout_select(bad_kb)) {
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
}
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// BadKbApp* bad_kb = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_layout_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadKbApp* bad_kb = context;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
|
||||
#define TAG "BadKbConfigMac"
|
||||
|
||||
static uint8_t* reverse_mac_addr(uint8_t* mac) {
|
||||
uint8_t tmp;
|
||||
for(int i = 0; i < 3; i++) {
|
||||
tmp = mac[i];
|
||||
mac[i] = mac[5 - i];
|
||||
mac[5 - i] = tmp;
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_mac_byte_input_callback(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventByteInputDone);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_mac_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
// Setup view
|
||||
ByteInput* byte_input = bad_kb->byte_input;
|
||||
byte_input_set_header_text(byte_input, "Enter new MAC address");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
bad_kb_scene_config_mac_byte_input_callback,
|
||||
NULL,
|
||||
bad_kb,
|
||||
reverse_mac_addr(bad_kb->mac),
|
||||
GAP_MAC_ADDR_SIZE);
|
||||
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigMac);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_config_mac_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* bad_kb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == BadKbAppCustomEventByteInputDone) {
|
||||
bt_set_profile_mac_address(bad_kb->bt, reverse_mac_addr(bad_kb->mac));
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_mac_on_exit(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
// Clear view
|
||||
byte_input_set_result_callback(bad_kb->byte_input, NULL, NULL, NULL, NULL, 0);
|
||||
byte_input_set_header_text(bad_kb->byte_input, "");
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
|
||||
static void bad_kb_scene_config_name_text_input_callback(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, BadKbAppCustomEventTextEditResult);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_name_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
TextInput* text_input = bad_kb->text_input;
|
||||
|
||||
text_input_set_header_text(text_input, "Set BLE adv name");
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
bad_kb_scene_config_name_text_input_callback,
|
||||
bad_kb,
|
||||
bad_kb->name,
|
||||
BAD_KB_ADV_NAME_MAX_LEN,
|
||||
true);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigName);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_config_name_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* bad_kb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == BadKbAppCustomEventTextEditResult) {
|
||||
bt_set_profile_adv_name(bad_kb->bt, bad_kb->name);
|
||||
}
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_name_on_exit(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
TextInput* text_input = bad_kb->text_input;
|
||||
|
||||
text_input_reset(text_input);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <xtreme/settings.h>
|
||||
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexConnection,
|
||||
VarItemListIndexKeyboardLayout,
|
||||
};
|
||||
|
||||
void bad_kb_scene_config_usb_connection_callback(VariableItem* item) {
|
||||
BadKbApp* bad_kb = variable_item_get_context(item);
|
||||
bad_kb->is_bt = variable_item_get_current_value_index(item);
|
||||
XTREME_SETTINGS()->bad_bt = bad_kb->is_bt;
|
||||
XTREME_SETTINGS_SAVE();
|
||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexConnection);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_usb_var_item_list_callback(void* context, uint32_t index) {
|
||||
BadKbApp* bad_kb = context;
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_usb_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
VariableItemList* var_item_list = bad_kb->var_item_list_usb;
|
||||
VariableItem* item;
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "Connection", 2, bad_kb_scene_config_usb_connection_callback, bad_kb);
|
||||
variable_item_set_current_value_index(item, bad_kb->is_bt);
|
||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||
|
||||
item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_kb);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bad_kb_scene_config_usb_var_item_list_callback, bad_kb);
|
||||
|
||||
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigUsb);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_config_usb_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* bad_kb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsb, event.event);
|
||||
consumed = true;
|
||||
if(event.event == VarItemListIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigLayout);
|
||||
} else if(event.event == VarItemListIndexConnection) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb_connection_deinit(bad_kb->bt);
|
||||
bad_kb->bad_kb_script =
|
||||
bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
if(bad_kb->is_bt) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
// } else {
|
||||
// furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_usb_on_exit(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
VariableItemList* var_item_list = bad_kb->var_item_list_usb;
|
||||
|
||||
variable_item_list_reset(var_item_list);
|
||||
}
|
||||
+17
-21
@@ -1,24 +1,20 @@
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_settings.h"
|
||||
|
||||
typedef enum {
|
||||
BadUsbCustomEventErrorBack,
|
||||
} BadUsbCustomEvent;
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void
|
||||
bad_usb_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
|
||||
bad_kb_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
BadKbApp* app = context;
|
||||
|
||||
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, BadUsbCustomEventErrorBack);
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, BadKbCustomEventErrorBack);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_usb_scene_error_on_enter(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
void bad_kb_scene_error_on_enter(void* context) {
|
||||
BadKbApp* app = context;
|
||||
|
||||
if(app->error == BadUsbAppErrorNoFiles) {
|
||||
if(app->error == BadKbAppErrorNoFiles) {
|
||||
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
|
||||
widget_add_string_multiline_element(
|
||||
app->widget,
|
||||
@@ -29,10 +25,10 @@ void bad_usb_scene_error_on_enter(void* context) {
|
||||
FontSecondary,
|
||||
"No SD card or\napp data found.\nThis app will not\nwork without\nrequired files.");
|
||||
widget_add_button_element(
|
||||
app->widget, GuiButtonTypeLeft, "Back", bad_usb_scene_error_event_callback, app);
|
||||
} else if(app->error == BadUsbAppErrorCloseRpc) {
|
||||
app->widget, GuiButtonTypeLeft, "Back", bad_kb_scene_error_event_callback, app);
|
||||
} else if(app->error == BadKbAppErrorCloseRpc) {
|
||||
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
|
||||
if(XTREME_SETTINGS()->nsfw_mode) {
|
||||
if(XTREME_ASSETS()->is_nsfw) {
|
||||
widget_add_string_multiline_element(
|
||||
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
|
||||
widget_add_string_multiline_element(
|
||||
@@ -57,15 +53,15 @@ void bad_usb_scene_error_on_enter(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadKbAppViewError);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* app = context;
|
||||
bool bad_kb_scene_error_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == BadUsbCustomEventErrorBack) {
|
||||
if(event.event == BadKbCustomEventErrorBack) {
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
consumed = true;
|
||||
}
|
||||
@@ -73,7 +69,7 @@ bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) {
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_error_on_exit(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
void bad_kb_scene_error_on_exit(void* context) {
|
||||
BadKbApp* app = context;
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_usb.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_kb_file_select(BadKbApp* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_KB_APP_SCRIPT_EXTENSION, &I_badkb_10px);
|
||||
browser_options.base_path = BAD_KB_APP_BASE_FOLDER;
|
||||
browser_options.skip_assets = true;
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_kb->dialogs, bad_kb->file_path, bad_kb->file_path, &browser_options);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void bad_kb_scene_file_select_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
furi_hal_usb_disable();
|
||||
if(bad_kb->bad_kb_script) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb->bad_kb_script = NULL;
|
||||
}
|
||||
|
||||
if(bad_kb_file_select(bad_kb)) {
|
||||
bad_kb->bad_kb_script =
|
||||
bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneWork);
|
||||
} else {
|
||||
furi_hal_usb_enable();
|
||||
view_dispatcher_stop(bad_kb->view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
bool bad_kb_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// BadKbApp* bad_kb = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_kb_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadKbApp* bad_kb = context;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "../bad_kb_script.h"
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include "../views/bad_kb_view.h"
|
||||
#include <furi_hal.h>
|
||||
#include "toolbox/path.h"
|
||||
|
||||
void bad_kb_scene_work_button_callback(InputKey key, void* context) {
|
||||
furi_assert(context);
|
||||
BadKbApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, key);
|
||||
}
|
||||
|
||||
bool bad_kb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
BadKbApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyLeft) {
|
||||
if(app->is_bt) {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
bad_kb_script_toggle(app->bad_kb_script);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_kb_set_state(app->bad_kb_view, bad_kb_script_get_state(app->bad_kb_script));
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_kb_scene_work_on_enter(void* context) {
|
||||
BadKbApp* app = context;
|
||||
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_kb_set_file_name(app->bad_kb_view, furi_string_get_cstr(file_name));
|
||||
furi_string_free(file_name);
|
||||
|
||||
FuriString* layout;
|
||||
layout = furi_string_alloc();
|
||||
path_extract_filename(app->keyboard_layout, layout, true);
|
||||
bad_kb_set_layout(app->bad_kb_view, furi_string_get_cstr(layout));
|
||||
furi_string_free(layout);
|
||||
|
||||
bad_kb_set_state(app->bad_kb_view, bad_kb_script_get_state(app->bad_kb_script));
|
||||
|
||||
bad_kb_set_button_callback(app->bad_kb_view, bad_kb_scene_work_button_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadKbAppViewWork);
|
||||
}
|
||||
|
||||
void bad_kb_scene_work_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
+59
-66
@@ -1,34 +1,33 @@
|
||||
#include "bad_usb_view.h"
|
||||
#include "../bad_usb_script.h"
|
||||
#include "bad_kb_view.h"
|
||||
#include "../bad_kb_script.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
#include "../../../settings/xtreme_settings/xtreme_settings.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
#define MAX_NAME_LEN 64
|
||||
|
||||
struct BadUsb {
|
||||
struct BadKb {
|
||||
View* view;
|
||||
BadUsbButtonCallback callback;
|
||||
BadKbButtonCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char file_name[MAX_NAME_LEN];
|
||||
char layout[MAX_NAME_LEN];
|
||||
BadUsbState state;
|
||||
BadKbState state;
|
||||
uint8_t anim_frame;
|
||||
} BadUsbModel;
|
||||
} BadKbModel;
|
||||
|
||||
static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
BadUsbModel* model = _model;
|
||||
static void bad_kb_draw_callback(Canvas* canvas, void* _model) {
|
||||
BadKbModel* model = _model;
|
||||
|
||||
FuriString* disp_str;
|
||||
disp_str = furi_string_alloc_set(model->file_name);
|
||||
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
|
||||
XtremeSettings* xtreme_settings = XTREME_SETTINGS();
|
||||
|
||||
if(strlen(model->layout) == 0) {
|
||||
furi_string_set(disp_str, "(default)");
|
||||
@@ -47,49 +46,49 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
|
||||
canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22);
|
||||
|
||||
if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
|
||||
(model->state.state == BadUsbStateNotConnected)) {
|
||||
if(xtreme_settings->nsfw_mode) {
|
||||
if((model->state.state == BadKbStateIdle) || (model->state.state == BadKbStateDone) ||
|
||||
(model->state.state == BadKbStateNotConnected)) {
|
||||
if(XTREME_ASSETS()->is_nsfw) {
|
||||
elements_button_center(canvas, "Cum");
|
||||
} else {
|
||||
elements_button_center(canvas, "Start");
|
||||
}
|
||||
} else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
|
||||
} else if((model->state.state == BadKbStateRunning) || (model->state.state == BadKbStateDelay)) {
|
||||
elements_button_center(canvas, "Stop");
|
||||
} else if(model->state.state == BadUsbStateWillRun) {
|
||||
} else if(model->state.state == BadKbStateWillRun) {
|
||||
elements_button_center(canvas, "Cancel");
|
||||
}
|
||||
|
||||
if((model->state.state == BadUsbStateNotConnected) ||
|
||||
(model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) {
|
||||
if((model->state.state == BadKbStateNotConnected) || (model->state.state == BadKbStateIdle) ||
|
||||
(model->state.state == BadKbStateDone)) {
|
||||
elements_button_left(canvas, "Config");
|
||||
}
|
||||
|
||||
if(model->state.state == BadUsbStateNotConnected) {
|
||||
if(model->state.state == BadKbStateNotConnected) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(xtreme_settings->nsfw_mode) {
|
||||
if(XTREME_ASSETS()->is_nsfw) {
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me");
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy");
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to");
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device");
|
||||
}
|
||||
} else if(model->state.state == BadUsbStateWillRun) {
|
||||
} else if(model->state.state == BadKbStateWillRun) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(xtreme_settings->nsfw_mode) {
|
||||
if(XTREME_ASSETS()->is_nsfw) {
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum");
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
|
||||
}
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
|
||||
} else if(model->state.state == BadUsbStateFileError) {
|
||||
} else if(model->state.state == BadKbStateFileError) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
|
||||
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
|
||||
} else if(model->state.state == BadUsbStateScriptError) {
|
||||
} else if(model->state.state == BadKbStateScriptError) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
|
||||
@@ -99,12 +98,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
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) {
|
||||
} else if(model->state.state == BadKbStateIdle) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
|
||||
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||
} else if(model->state.state == BadUsbStateRunning) {
|
||||
} else if(model->state.state == BadKbStateRunning) {
|
||||
if(model->anim_frame == 0) {
|
||||
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
|
||||
} else {
|
||||
@@ -117,13 +116,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
|
||||
furi_string_reset(disp_str);
|
||||
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||
} else if(model->state.state == BadUsbStateDone) {
|
||||
} else if(model->state.state == BadKbStateDone) {
|
||||
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
|
||||
furi_string_reset(disp_str);
|
||||
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
|
||||
} else if(model->state.state == BadUsbStateDelay) {
|
||||
} else if(model->state.state == BadKbStateDelay) {
|
||||
if(model->anim_frame == 0) {
|
||||
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
|
||||
} else {
|
||||
@@ -148,84 +147,78 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
furi_string_free(disp_str);
|
||||
}
|
||||
|
||||
static bool bad_usb_input_callback(InputEvent* event, void* context) {
|
||||
static bool bad_kb_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsb* bad_usb = context;
|
||||
BadKb* bad_kb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
|
||||
consumed = true;
|
||||
furi_assert(bad_usb->callback);
|
||||
bad_usb->callback(event->key, bad_usb->context);
|
||||
furi_assert(bad_kb->callback);
|
||||
bad_kb->callback(event->key, bad_kb->context);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BadUsb* bad_usb_alloc() {
|
||||
BadUsb* bad_usb = malloc(sizeof(BadUsb));
|
||||
BadKb* bad_kb_alloc() {
|
||||
BadKb* bad_kb = malloc(sizeof(BadKb));
|
||||
|
||||
bad_usb->view = view_alloc();
|
||||
view_allocate_model(bad_usb->view, ViewModelTypeLocking, sizeof(BadUsbModel));
|
||||
view_set_context(bad_usb->view, bad_usb);
|
||||
view_set_draw_callback(bad_usb->view, bad_usb_draw_callback);
|
||||
view_set_input_callback(bad_usb->view, bad_usb_input_callback);
|
||||
bad_kb->view = view_alloc();
|
||||
view_allocate_model(bad_kb->view, ViewModelTypeLocking, sizeof(BadKbModel));
|
||||
view_set_context(bad_kb->view, bad_kb);
|
||||
view_set_draw_callback(bad_kb->view, bad_kb_draw_callback);
|
||||
view_set_input_callback(bad_kb->view, bad_kb_input_callback);
|
||||
|
||||
return bad_usb;
|
||||
return bad_kb;
|
||||
}
|
||||
|
||||
void bad_usb_free(BadUsb* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
view_free(bad_usb->view);
|
||||
free(bad_usb);
|
||||
void bad_kb_free(BadKb* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
view_free(bad_kb->view);
|
||||
free(bad_kb);
|
||||
}
|
||||
|
||||
View* bad_usb_get_view(BadUsb* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
return bad_usb->view;
|
||||
View* bad_kb_get_view(BadKb* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
return bad_kb->view;
|
||||
}
|
||||
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) {
|
||||
furi_assert(bad_usb);
|
||||
void bad_kb_set_button_callback(BadKb* bad_kb, BadKbButtonCallback callback, void* context) {
|
||||
furi_assert(bad_kb);
|
||||
furi_assert(callback);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
BadUsbModel * model,
|
||||
bad_kb->view,
|
||||
BadKbModel * model,
|
||||
{
|
||||
UNUSED(model);
|
||||
bad_usb->callback = callback;
|
||||
bad_usb->context = context;
|
||||
bad_kb->callback = callback;
|
||||
bad_kb->context = context;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
|
||||
void bad_kb_set_file_name(BadKb* bad_kb, const char* name) {
|
||||
furi_assert(name);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
BadUsbModel * model,
|
||||
{ strlcpy(model->file_name, name, MAX_NAME_LEN); },
|
||||
true);
|
||||
bad_kb->view, BadKbModel * model, { strlcpy(model->file_name, name, MAX_NAME_LEN); }, true);
|
||||
}
|
||||
|
||||
void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
|
||||
void bad_kb_set_layout(BadKb* bad_kb, const char* layout) {
|
||||
furi_assert(layout);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
BadUsbModel * model,
|
||||
{ strlcpy(model->layout, layout, MAX_NAME_LEN); },
|
||||
true);
|
||||
bad_kb->view, BadKbModel * model, { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true);
|
||||
}
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
void bad_kb_set_state(BadKb* bad_kb, BadKbState* st) {
|
||||
furi_assert(st);
|
||||
with_view_model(
|
||||
bad_usb->view,
|
||||
BadUsbModel * model,
|
||||
bad_kb->view,
|
||||
BadKbModel * model,
|
||||
{
|
||||
memcpy(&(model->state), st, sizeof(BadUsbState));
|
||||
memcpy(&(model->state), st, sizeof(BadKbState));
|
||||
model->anim_frame ^= 1;
|
||||
},
|
||||
true);
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../bad_kb_script.h"
|
||||
|
||||
typedef struct BadKb BadKb;
|
||||
typedef void (*BadKbButtonCallback)(InputKey key, void* context);
|
||||
|
||||
BadKb* bad_kb_alloc();
|
||||
|
||||
void bad_kb_free(BadKb* bad_kb);
|
||||
|
||||
View* bad_kb_get_view(BadKb* bad_kb);
|
||||
|
||||
void bad_kb_set_button_callback(BadKb* bad_kb, BadKbButtonCallback callback, void* context);
|
||||
|
||||
void bad_kb_set_file_name(BadKb* bad_kb, const char* name);
|
||||
|
||||
void bad_kb_set_layout(BadKb* bad_kb, const char* layout);
|
||||
|
||||
void bad_kb_set_state(BadKb* bad_kb, BadKbState* st);
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "bad_usb_app_i.h"
|
||||
#include "bad_usb_settings_filename.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME
|
||||
|
||||
static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool bad_usb_app_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void bad_usb_app_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
char chr;
|
||||
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||
furi_string_push_back(app->keyboard_layout, chr);
|
||||
}
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
static void bad_usb_save_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
storage_file_write(
|
||||
settings_file,
|
||||
furi_string_get_cstr(app->keyboard_layout),
|
||||
furi_string_size(app->keyboard_layout));
|
||||
storage_file_write(settings_file, "\n", 1);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
BadUsbApp* bad_usb_app_alloc(char* arg) {
|
||||
BadUsbApp* app = malloc(sizeof(BadUsbApp));
|
||||
|
||||
app->bad_usb_script = NULL;
|
||||
|
||||
app->file_path = furi_string_alloc();
|
||||
app->keyboard_layout = furi_string_alloc();
|
||||
if(arg && strlen(arg)) {
|
||||
furi_string_set(app->file_path, arg);
|
||||
}
|
||||
|
||||
bad_usb_load_settings(app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, bad_usb_app_tick_event_callback, 500);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, bad_usb_app_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, bad_usb_app_back_event_callback);
|
||||
|
||||
// Custom Widget
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu));
|
||||
|
||||
app->bad_usb_view = bad_usb_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
if(furi_hal_usb_is_locked()) {
|
||||
app->error = BadUsbAppErrorCloseRpc;
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
|
||||
} else {
|
||||
if(!furi_string_empty(app->file_path)) {
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
|
||||
} else {
|
||||
furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void bad_usb_app_free(BadUsbApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(app->bad_usb_script) {
|
||||
bad_usb_script_close(app->bad_usb_script);
|
||||
app->bad_usb_script = NULL;
|
||||
}
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
bad_usb_free(app->bad_usb_view);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
|
||||
widget_free(app->widget);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
bad_usb_save_settings(app);
|
||||
|
||||
furi_string_free(app->file_path);
|
||||
furi_string_free(app->keyboard_layout);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t bad_usb_app(void* p) {
|
||||
BadUsbApp* bad_usb_app = bad_usb_app_alloc((char*)p);
|
||||
|
||||
view_dispatcher_run(bad_usb_app->view_dispatcher);
|
||||
|
||||
bad_usb_app_free(bad_usb_app);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct BadUsbApp BadUsbApp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "bad_usb_app.h"
|
||||
#include "scenes/bad_usb_scene.h"
|
||||
#include "bad_usb_script.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include "views/bad_usb_view.h"
|
||||
|
||||
#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb")
|
||||
#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/layouts"
|
||||
#define BAD_USB_APP_SCRIPT_EXTENSION ".txt"
|
||||
#define BAD_USB_APP_LAYOUT_EXTENSION ".kl"
|
||||
|
||||
typedef enum {
|
||||
BadUsbAppErrorNoFiles,
|
||||
BadUsbAppErrorCloseRpc,
|
||||
} BadUsbAppError;
|
||||
|
||||
struct BadUsbApp {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
NotificationApp* notifications;
|
||||
DialogsApp* dialogs;
|
||||
Widget* widget;
|
||||
Submenu* submenu;
|
||||
|
||||
BadUsbAppError error;
|
||||
FuriString* file_path;
|
||||
FuriString* keyboard_layout;
|
||||
BadUsb* bad_usb_view;
|
||||
BadUsbScript* bad_usb_script;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
BadUsbAppViewError,
|
||||
BadUsbAppViewWork,
|
||||
BadUsbAppViewConfig,
|
||||
} BadUsbAppView;
|
||||
@@ -1,724 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <storage/storage.h>
|
||||
#include "bad_usb_script.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "BadUSB"
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
#define FILE_BUFFER_LEN 16
|
||||
|
||||
#define SCRIPT_STATE_ERROR (-1)
|
||||
#define SCRIPT_STATE_END (-2)
|
||||
#define SCRIPT_STATE_NEXT_LINE (-3)
|
||||
|
||||
#define BADUSB_ASCII_TO_KEY(script, x) \
|
||||
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtToggle = (1 << 0),
|
||||
WorkerEvtEnd = (1 << 1),
|
||||
WorkerEvtConnect = (1 << 2),
|
||||
WorkerEvtDisconnect = (1 << 3),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
struct BadUsbScript {
|
||||
FuriHalUsbHidConfig hid_cfg;
|
||||
BadUsbState st;
|
||||
FuriString* file_path;
|
||||
uint32_t defdelay;
|
||||
uint16_t layout[128];
|
||||
FuriThread* thread;
|
||||
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||
uint8_t buf_start;
|
||||
uint8_t buf_len;
|
||||
bool file_end;
|
||||
FuriString* line;
|
||||
|
||||
FuriString* line_prev;
|
||||
uint32_t repeat_cnt;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
uint16_t keycode;
|
||||
} DuckyKey;
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
|
||||
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||
|
||||
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||
{"UPARROW", HID_KEYBOARD_UP_ARROW},
|
||||
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||
|
||||
{"ENTER", HID_KEYBOARD_RETURN},
|
||||
{"BREAK", HID_KEYBOARD_PAUSE},
|
||||
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||
{"DELETE", HID_KEYBOARD_DELETE_FORWARD},
|
||||
{"BACKSPACE", HID_KEYBOARD_DELETE},
|
||||
{"END", HID_KEYBOARD_END},
|
||||
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||
{"ESCAPE", HID_KEYBOARD_ESCAPE},
|
||||
{"HOME", HID_KEYBOARD_HOME},
|
||||
{"INSERT", HID_KEYBOARD_INSERT},
|
||||
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||
{"TAB", HID_KEYBOARD_TAB},
|
||||
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||
{"APP", HID_KEYBOARD_APPLICATION},
|
||||
|
||||
{"F1", HID_KEYBOARD_F1},
|
||||
{"F2", HID_KEYBOARD_F2},
|
||||
{"F3", HID_KEYBOARD_F3},
|
||||
{"F4", HID_KEYBOARD_F4},
|
||||
{"F5", HID_KEYBOARD_F5},
|
||||
{"F6", HID_KEYBOARD_F6},
|
||||
{"F7", HID_KEYBOARD_F7},
|
||||
{"F8", HID_KEYBOARD_F8},
|
||||
{"F9", HID_KEYBOARD_F9},
|
||||
{"F10", HID_KEYBOARD_F10},
|
||||
{"F11", HID_KEYBOARD_F11},
|
||||
{"F12", HID_KEYBOARD_F12},
|
||||
};
|
||||
|
||||
static const char ducky_cmd_comment[] = {"REM"};
|
||||
static const char ducky_cmd_id[] = {"ID"};
|
||||
static const char ducky_cmd_delay[] = {"DELAY "};
|
||||
static const char ducky_cmd_string[] = {"STRING "};
|
||||
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
|
||||
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
|
||||
|
||||
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
|
||||
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
|
||||
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
|
||||
|
||||
static const char ducky_cmd_lang[] = {"DUCKY_LANG"};
|
||||
|
||||
static const uint8_t numpad_keys[10] = {
|
||||
HID_KEYPAD_0,
|
||||
HID_KEYPAD_1,
|
||||
HID_KEYPAD_2,
|
||||
HID_KEYPAD_3,
|
||||
HID_KEYPAD_4,
|
||||
HID_KEYPAD_5,
|
||||
HID_KEYPAD_6,
|
||||
HID_KEYPAD_7,
|
||||
HID_KEYPAD_8,
|
||||
HID_KEYPAD_9,
|
||||
};
|
||||
|
||||
static bool ducky_get_number(const char* param, uint32_t* val) {
|
||||
uint32_t value = 0;
|
||||
if(sscanf(param, "%lu", &value) == 1) {
|
||||
*val = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ducky_is_line_end(const char chr) {
|
||||
return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
|
||||
}
|
||||
|
||||
static void ducky_numlock_on() {
|
||||
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_numpad_press(const char num) {
|
||||
if((num < '0') || (num > '9')) return false;
|
||||
|
||||
uint16_t key = numpad_keys[num - '0'];
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ducky_altchar(const char* charcode) {
|
||||
uint8_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
|
||||
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||
|
||||
while(!ducky_is_line_end(charcode[i])) {
|
||||
state = ducky_numpad_press(charcode[i]);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
|
||||
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool ducky_altstring(const char* param) {
|
||||
uint32_t i = 0;
|
||||
bool state = false;
|
||||
|
||||
while(param[i] != '\0') {
|
||||
if((param[i] < ' ') || (param[i] > '~')) {
|
||||
i++;
|
||||
continue; // Skip non-printable chars
|
||||
}
|
||||
|
||||
char temp_str[4];
|
||||
snprintf(temp_str, 4, "%u", param[i]);
|
||||
|
||||
state = ducky_altchar(temp_str);
|
||||
if(state == false) break;
|
||||
i++;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
while(param[i] != '\0') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
|
||||
for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
|
||||
(ducky_is_line_end(param[key_cmd_len]))) {
|
||||
return ducky_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
if((accept_chars) && (strlen(param) > 0)) {
|
||||
return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if(line_len == 0) {
|
||||
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
|
||||
}
|
||||
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
|
||||
|
||||
// General commands
|
||||
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
||||
// REM - comment line
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
// ID - executed in ducky_script_preload
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) {
|
||||
// DUCKY_LANG - ignore command to retain compatibility with existing scripts
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||
// DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
uint32_t delay_val = 0;
|
||||
state = ducky_get_number(line_tmp, &delay_val);
|
||||
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) ||
|
||||
(strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
|
||||
// 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(bad_usb, 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) ||
|
||||
(strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
|
||||
// ALTSTRING
|
||||
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
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release_all();
|
||||
return (0);
|
||||
} else {
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
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];
|
||||
key |= ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
}
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
|
||||
if(sscanf(line, "%lX:%lX", &bad_usb->hid_cfg.vid, &bad_usb->hid_cfg.pid) == 2) {
|
||||
bad_usb->hid_cfg.manuf[0] = '\0';
|
||||
bad_usb->hid_cfg.product[0] = '\0';
|
||||
|
||||
uint8_t id_len = ducky_get_command_len(line);
|
||||
if(!ducky_is_line_end(line[id_len + 1])) {
|
||||
sscanf(
|
||||
&line[id_len + 1],
|
||||
"%31[^\r\n:]:%31[^\r\n]",
|
||||
bad_usb->hid_cfg.manuf,
|
||||
bad_usb->hid_cfg.product);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
WORKER_TAG,
|
||||
"set id: %04lX:%04lX mfr:%s product:%s",
|
||||
bad_usb->hid_cfg.vid,
|
||||
bad_usb->hid_cfg.pid,
|
||||
bad_usb->hid_cfg.manuf,
|
||||
bad_usb->hid_cfg.product);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) {
|
||||
uint8_t ret = 0;
|
||||
uint32_t line_len = 0;
|
||||
|
||||
furi_string_reset(bad_usb->line);
|
||||
|
||||
do {
|
||||
ret = storage_file_read(script_file, bad_usb->file_buf, FILE_BUFFER_LEN);
|
||||
for(uint16_t i = 0; i < ret; i++) {
|
||||
if(bad_usb->file_buf[i] == '\n' && line_len > 0) {
|
||||
bad_usb->st.line_nb++;
|
||||
line_len = 0;
|
||||
} else {
|
||||
if(bad_usb->st.line_nb == 0) { // Save first line
|
||||
furi_string_push_back(bad_usb->line, bad_usb->file_buf[i]);
|
||||
}
|
||||
line_len++;
|
||||
}
|
||||
}
|
||||
if(storage_file_eof(script_file)) {
|
||||
if(line_len > 0) {
|
||||
bad_usb->st.line_nb++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(ret > 0);
|
||||
|
||||
const char* line_tmp = furi_string_get_cstr(bad_usb->line);
|
||||
bool id_set = false; // Looking for ID command at first line
|
||||
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
id_set = ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1]);
|
||||
}
|
||||
|
||||
if(id_set) {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, &bad_usb->hid_cfg));
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_hid, NULL));
|
||||
}
|
||||
|
||||
storage_file_seek(script_file, 0, true);
|
||||
furi_string_reset(bad_usb->line);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_file) {
|
||||
int32_t delay_val = 0;
|
||||
|
||||
if(bad_usb->repeat_cnt > 0) {
|
||||
bad_usb->repeat_cnt--;
|
||||
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
|
||||
bad_usb->st.error_line = bad_usb->st.line_cur - 1;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_usb->defdelay);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_set(bad_usb->line_prev, bad_usb->line);
|
||||
furi_string_reset(bad_usb->line);
|
||||
|
||||
while(1) {
|
||||
if(bad_usb->buf_len == 0) {
|
||||
bad_usb->buf_len = storage_file_read(script_file, bad_usb->file_buf, FILE_BUFFER_LEN);
|
||||
if(storage_file_eof(script_file)) {
|
||||
if((bad_usb->buf_len < FILE_BUFFER_LEN) && (bad_usb->file_end == false)) {
|
||||
bad_usb->file_buf[bad_usb->buf_len] = '\n';
|
||||
bad_usb->buf_len++;
|
||||
bad_usb->file_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
bad_usb->buf_start = 0;
|
||||
if(bad_usb->buf_len == 0) return SCRIPT_STATE_END;
|
||||
}
|
||||
for(uint8_t i = bad_usb->buf_start; i < (bad_usb->buf_start + bad_usb->buf_len); i++) {
|
||||
if(bad_usb->file_buf[i] == '\n' && furi_string_size(bad_usb->line) > 0) {
|
||||
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;
|
||||
furi_string_trim(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 == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||
return 0;
|
||||
} else 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);
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_usb->defdelay);
|
||||
}
|
||||
} else {
|
||||
furi_string_push_back(bad_usb->line, bad_usb->file_buf[i]);
|
||||
}
|
||||
}
|
||||
bad_usb->buf_len = 0;
|
||||
if(bad_usb->file_end) return SCRIPT_STATE_END;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bad_usb_hid_state_callback(bool state, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbScript* bad_usb = context;
|
||||
|
||||
if(state == true)
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect);
|
||||
else
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
|
||||
static int32_t bad_usb_worker(void* context) {
|
||||
BadUsbScript* bad_usb = context;
|
||||
|
||||
BadUsbWorkerState worker_state = BadUsbStateInit;
|
||||
int32_t delay_val = 0;
|
||||
|
||||
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "Init");
|
||||
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
bad_usb->line = furi_string_alloc();
|
||||
bad_usb->line_prev = furi_string_alloc();
|
||||
|
||||
furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb);
|
||||
|
||||
while(1) {
|
||||
if(worker_state == BadUsbStateInit) { // State: initialization
|
||||
if(storage_file_open(
|
||||
script_file,
|
||||
furi_string_get_cstr(bad_usb->file_path),
|
||||
FSAM_READ,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
if((ducky_script_preload(bad_usb, script_file)) && (bad_usb->st.line_nb > 0)) {
|
||||
if(furi_hal_hid_is_connected()) {
|
||||
worker_state = BadUsbStateIdle; // Ready to run
|
||||
} else {
|
||||
worker_state = BadUsbStateNotConnected; // USB not connected
|
||||
}
|
||||
} else {
|
||||
worker_state = BadUsbStateScriptError; // Script preload error
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(WORKER_TAG, "File open error");
|
||||
worker_state = BadUsbStateFileError; // File open error
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
worker_state = BadUsbStateIdle; // Ready to run
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadUsbStateWillRun; // Will run when USB is connected
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateIdle) { // State: ready to start
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtToggle) { // Start executing script
|
||||
DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
|
||||
delay_val = 0;
|
||||
bad_usb->buf_len = 0;
|
||||
bad_usb->st.line_cur = 0;
|
||||
bad_usb->defdelay = 0;
|
||||
bad_usb->repeat_cnt = 0;
|
||||
bad_usb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
worker_state = BadUsbStateRunning;
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateWillRun) { // State: start on connection
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) { // Start executing script
|
||||
DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
|
||||
delay_val = 0;
|
||||
bad_usb->buf_len = 0;
|
||||
bad_usb->st.line_cur = 0;
|
||||
bad_usb->defdelay = 0;
|
||||
bad_usb->repeat_cnt = 0;
|
||||
bad_usb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
// extra time for PC to recognize Flipper as keyboard
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
|
||||
worker_state = BadUsbStateRunning;
|
||||
} else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
|
||||
worker_state = BadUsbStateNotConnected;
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateRunning) { // State: running
|
||||
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
|
||||
delay_val -= delay_cur;
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadUsbStateIdle; // Stop executing script
|
||||
furi_hal_hid_kb_release_all();
|
||||
} else if(flags & WorkerEvtDisconnect) {
|
||||
worker_state = BadUsbStateNotConnected; // USB disconnected
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
bad_usb->st.state = worker_state;
|
||||
continue;
|
||||
} else if(
|
||||
(flags == (unsigned)FuriFlagErrorTimeout) ||
|
||||
(flags == (unsigned)FuriFlagErrorResource)) {
|
||||
if(delay_val > 0) {
|
||||
bad_usb->st.delay_remain--;
|
||||
continue;
|
||||
}
|
||||
bad_usb->st.state = BadUsbStateRunning;
|
||||
delay_val = ducky_script_execute_next(bad_usb, script_file);
|
||||
if(delay_val == SCRIPT_STATE_ERROR) { // Script error
|
||||
delay_val = 0;
|
||||
worker_state = BadUsbStateScriptError;
|
||||
bad_usb->st.state = worker_state;
|
||||
} else if(delay_val == SCRIPT_STATE_END) { // End of script
|
||||
delay_val = 0;
|
||||
worker_state = BadUsbStateIdle;
|
||||
bad_usb->st.state = BadUsbStateDone;
|
||||
furi_hal_hid_kb_release_all();
|
||||
continue;
|
||||
} else if(delay_val > 1000) {
|
||||
bad_usb->st.state = BadUsbStateDelay; // Show long delays
|
||||
bad_usb->st.delay_remain = delay_val / 1000;
|
||||
}
|
||||
} else {
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
}
|
||||
|
||||
} else if(
|
||||
(worker_state == BadUsbStateFileError) ||
|
||||
(worker_state == BadUsbStateScriptError)) { // State: error
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_hid_set_state_callback(NULL, NULL);
|
||||
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
|
||||
storage_file_close(script_file);
|
||||
storage_file_free(script_file);
|
||||
furi_string_free(bad_usb->line);
|
||||
furi_string_free(bad_usb->line_prev);
|
||||
|
||||
FURI_LOG_I(WORKER_TAG, "End");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout));
|
||||
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
|
||||
}
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path) {
|
||||
furi_assert(file_path);
|
||||
|
||||
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
|
||||
bad_usb->file_path = furi_string_alloc();
|
||||
furi_string_set(bad_usb->file_path, file_path);
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
bad_usb->st.error[0] = '\0';
|
||||
|
||||
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
|
||||
furi_thread_start(bad_usb->thread);
|
||||
return bad_usb;
|
||||
} //-V773
|
||||
|
||||
void bad_usb_script_close(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtEnd);
|
||||
furi_thread_join(bad_usb->thread);
|
||||
furi_thread_free(bad_usb->thread);
|
||||
furi_string_free(bad_usb->file_path);
|
||||
free(bad_usb);
|
||||
}
|
||||
|
||||
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) {
|
||||
// do not update keyboard layout while a script is running
|
||||
return;
|
||||
}
|
||||
|
||||
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(!furi_string_empty(layout_path)) {
|
||||
if(storage_file_open(
|
||||
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
uint16_t layout[128];
|
||||
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
|
||||
memcpy(bad_usb->layout, layout, sizeof(layout));
|
||||
}
|
||||
}
|
||||
storage_file_close(layout_file);
|
||||
} else {
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
}
|
||||
storage_file_free(layout_file);
|
||||
}
|
||||
|
||||
void bad_usb_script_toggle(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle);
|
||||
}
|
||||
|
||||
BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
return &(bad_usb->st);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
typedef struct BadUsbScript BadUsbScript;
|
||||
|
||||
typedef enum {
|
||||
BadUsbStateInit,
|
||||
BadUsbStateNotConnected,
|
||||
BadUsbStateIdle,
|
||||
BadUsbStateWillRun,
|
||||
BadUsbStateRunning,
|
||||
BadUsbStateDelay,
|
||||
BadUsbStateDone,
|
||||
BadUsbStateScriptError,
|
||||
BadUsbStateFileError,
|
||||
} BadUsbWorkerState;
|
||||
|
||||
typedef struct {
|
||||
BadUsbWorkerState state;
|
||||
uint16_t line_cur;
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
uint16_t error_line;
|
||||
char error[64];
|
||||
} BadUsbState;
|
||||
|
||||
BadUsbScript* bad_usb_script_open(FuriString* file_path);
|
||||
|
||||
void bad_usb_script_close(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path);
|
||||
|
||||
void bad_usb_script_start(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_stop(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_toggle(BadUsbScript* bad_usb);
|
||||
|
||||
BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings"
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "bad_usb_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const bad_usb_scene_on_enter_handlers[])(void*) = {
|
||||
#include "bad_usb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const bad_usb_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "bad_usb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const bad_usb_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "bad_usb_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers bad_usb_scene_handlers = {
|
||||
.on_enter_handlers = bad_usb_scene_on_enter_handlers,
|
||||
.on_event_handlers = bad_usb_scene_on_event_handlers,
|
||||
.on_exit_handlers = bad_usb_scene_on_exit_handlers,
|
||||
.scene_num = BadUsbSceneNum,
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexKeyboardLayout,
|
||||
};
|
||||
|
||||
void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Keyboard layout",
|
||||
SubmenuIndexKeyboardLayout,
|
||||
bad_usb_scene_config_submenu_callback,
|
||||
bad_usb);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
|
||||
|
||||
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
|
||||
} else {
|
||||
furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_exit(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
|
||||
submenu_reset(submenu);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
ADD_SCENE(bad_usb, file_select, FileSelect)
|
||||
ADD_SCENE(bad_usb, work, Work)
|
||||
ADD_SCENE(bad_usb, error, Error)
|
||||
ADD_SCENE(bad_usb, config, Config)
|
||||
ADD_SCENE(bad_usb, config_layout, ConfigLayout)
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
FuriString* predefined_path;
|
||||
predefined_path = furi_string_alloc();
|
||||
if(!furi_string_empty(bad_usb->keyboard_layout)) {
|
||||
furi_string_set(predefined_path, bad_usb->keyboard_layout);
|
||||
} else {
|
||||
furi_string_set(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER);
|
||||
}
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_USB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_usb->dialogs, bad_usb->keyboard_layout, predefined_path, &browser_options);
|
||||
|
||||
furi_string_free(predefined_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_layout_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
|
||||
if(bad_usb_layout_select(bad_usb)) {
|
||||
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
|
||||
}
|
||||
scene_manager_previous_scene(bad_usb->scene_manager);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_layout_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_usb_file_select(BadUsbApp* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_USB_APP_SCRIPT_EXTENSION, &I_badusb_10px);
|
||||
browser_options.base_path = BAD_USB_APP_BASE_FOLDER;
|
||||
browser_options.skip_assets = true;
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_usb->dialogs, bad_usb->file_path, bad_usb->file_path, &browser_options);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void bad_usb_scene_file_select_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
|
||||
furi_hal_usb_disable();
|
||||
if(bad_usb->bad_usb_script) {
|
||||
bad_usb_script_close(bad_usb->bad_usb_script);
|
||||
bad_usb->bad_usb_script = NULL;
|
||||
}
|
||||
|
||||
if(bad_usb_file_select(bad_usb)) {
|
||||
bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path);
|
||||
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
|
||||
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
|
||||
} else {
|
||||
furi_hal_usb_enable();
|
||||
view_dispatcher_stop(bad_usb->view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
bool bad_usb_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_usb_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "../bad_usb_script.h"
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "../views/bad_usb_view.h"
|
||||
#include "furi_hal.h"
|
||||
#include "toolbox/path.h"
|
||||
|
||||
void bad_usb_scene_work_button_callback(InputKey key, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, key);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyLeft) {
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
bad_usb_script_toggle(app->bad_usb_script);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_work_on_enter(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name));
|
||||
furi_string_free(file_name);
|
||||
|
||||
FuriString* layout;
|
||||
layout = furi_string_alloc();
|
||||
path_extract_filename(app->keyboard_layout, layout, true);
|
||||
bad_usb_set_layout(app->bad_usb_view, furi_string_get_cstr(layout));
|
||||
furi_string_free(layout);
|
||||
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
|
||||
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
}
|
||||
|
||||
void bad_usb_scene_work_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../bad_usb_script.h"
|
||||
|
||||
typedef struct BadUsb BadUsb;
|
||||
typedef void (*BadUsbButtonCallback)(InputKey key, void* context);
|
||||
|
||||
BadUsb* bad_usb_alloc();
|
||||
|
||||
void bad_usb_free(BadUsb* bad_usb);
|
||||
|
||||
View* bad_usb_get_view(BadUsb* bad_usb);
|
||||
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context);
|
||||
|
||||
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
|
||||
|
||||
void bad_usb_set_layout(BadUsb* bad_usb, const char* layout);
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);
|
||||
@@ -25,6 +25,7 @@ GpioApp* gpio_app_alloc() {
|
||||
GpioApp* app = malloc(sizeof(GpioApp));
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->gpio_items = gpio_items_alloc();
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
|
||||
@@ -47,7 +48,7 @@ GpioApp* gpio_app_alloc() {
|
||||
app->view_dispatcher,
|
||||
GpioAppViewVarItemList,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
app->gpio_test = gpio_test_alloc();
|
||||
app->gpio_test = gpio_test_alloc(app->gpio_items);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test));
|
||||
|
||||
@@ -105,6 +106,7 @@ void gpio_app_free(GpioApp* app) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
gpio_items_free(app->gpio_items);
|
||||
free(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "gpio_app.h"
|
||||
#include "gpio_item.h"
|
||||
#include "gpio_items.h"
|
||||
#include "scenes/gpio_scene.h"
|
||||
#include "gpio_custom_event.h"
|
||||
#include "usb_uart_bridge.h"
|
||||
@@ -30,6 +30,7 @@ struct GpioApp {
|
||||
VariableItem* var_item_flow;
|
||||
GpioTest* gpio_test;
|
||||
GpioUsbUart* gpio_usb_uart;
|
||||
GPIOItems* gpio_items;
|
||||
UsbUartBridge* usb_uart_bridge;
|
||||
GpioI2CScanner* gpio_i2c_scanner;
|
||||
GpioI2CSfp* gpio_i2c_sfp;
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "gpio_item.h"
|
||||
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const GpioPin* pin;
|
||||
} GpioItem;
|
||||
|
||||
static const GpioItem gpio_item[GPIO_ITEM_COUNT] = {
|
||||
{"1.2: PA7", &gpio_ext_pa7},
|
||||
{"1.3: PA6", &gpio_ext_pa6},
|
||||
{"1.4: PA4", &gpio_ext_pa4},
|
||||
{"1.5: PB3", &gpio_ext_pb3},
|
||||
{"1.6: PB2", &gpio_ext_pb2},
|
||||
{"1.7: PC3", &gpio_ext_pc3},
|
||||
{"2.7: PC1", &gpio_ext_pc1},
|
||||
{"2.8: PC0", &gpio_ext_pc0},
|
||||
};
|
||||
|
||||
void gpio_item_configure_pin(uint8_t index, GpioMode mode) {
|
||||
furi_assert(index < GPIO_ITEM_COUNT);
|
||||
furi_hal_gpio_write(gpio_item[index].pin, false);
|
||||
furi_hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh);
|
||||
}
|
||||
|
||||
void gpio_item_configure_all_pins(GpioMode mode) {
|
||||
for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) {
|
||||
gpio_item_configure_pin(i, mode);
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_item_set_pin(uint8_t index, bool level) {
|
||||
furi_assert(index < GPIO_ITEM_COUNT);
|
||||
furi_hal_gpio_write(gpio_item[index].pin, level);
|
||||
}
|
||||
|
||||
void gpio_item_set_all_pins(bool level) {
|
||||
for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) {
|
||||
gpio_item_set_pin(i, level);
|
||||
}
|
||||
}
|
||||
|
||||
const char* gpio_item_get_pin_name(uint8_t index) {
|
||||
furi_assert(index < GPIO_ITEM_COUNT + 1);
|
||||
if(index == GPIO_ITEM_COUNT) {
|
||||
return "ALL";
|
||||
} else {
|
||||
return gpio_item[index].name;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
#define GPIO_ITEM_COUNT 8
|
||||
|
||||
void gpio_item_configure_pin(uint8_t index, GpioMode mode);
|
||||
|
||||
void gpio_item_configure_all_pins(GpioMode mode);
|
||||
|
||||
void gpio_item_set_pin(uint8_t index, bool level);
|
||||
|
||||
void gpio_item_set_all_pins(bool level);
|
||||
|
||||
const char* gpio_item_get_pin_name(uint8_t index);
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "gpio_items.h"
|
||||
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
struct GPIOItems {
|
||||
GpioPinRecord* pins;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
GPIOItems* gpio_items_alloc() {
|
||||
GPIOItems* items = malloc(sizeof(GPIOItems));
|
||||
|
||||
items->count = 0;
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
if(!gpio_pins[i].debug) {
|
||||
items->count++;
|
||||
}
|
||||
}
|
||||
|
||||
items->pins = malloc(sizeof(GpioPinRecord) * items->count);
|
||||
for(size_t i = 0; i < items->count; i++) {
|
||||
if(!gpio_pins[i].debug) {
|
||||
items->pins[i].pin = gpio_pins[i].pin;
|
||||
items->pins[i].name = gpio_pins[i].name;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
void gpio_items_free(GPIOItems* items) {
|
||||
free(items->pins);
|
||||
free(items);
|
||||
}
|
||||
|
||||
uint8_t gpio_items_get_count(GPIOItems* items) {
|
||||
return items->count;
|
||||
}
|
||||
|
||||
void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode) {
|
||||
furi_assert(index < items->count);
|
||||
furi_hal_gpio_write(items->pins[index].pin, false);
|
||||
furi_hal_gpio_init(items->pins[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh);
|
||||
}
|
||||
|
||||
void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode) {
|
||||
for(uint8_t i = 0; i < items->count; i++) {
|
||||
gpio_items_configure_pin(items, i, mode);
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level) {
|
||||
furi_assert(index < items->count);
|
||||
furi_hal_gpio_write(items->pins[index].pin, level);
|
||||
}
|
||||
|
||||
void gpio_items_set_all_pins(GPIOItems* items, bool level) {
|
||||
for(uint8_t i = 0; i < items->count; i++) {
|
||||
gpio_items_set_pin(items, i, level);
|
||||
}
|
||||
}
|
||||
|
||||
const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index) {
|
||||
furi_assert(index < items->count + 1);
|
||||
if(index == items->count) {
|
||||
return "ALL";
|
||||
} else {
|
||||
return items->pins[index].name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct GPIOItems GPIOItems;
|
||||
|
||||
GPIOItems* gpio_items_alloc();
|
||||
|
||||
void gpio_items_free(GPIOItems* items);
|
||||
|
||||
uint8_t gpio_items_get_count(GPIOItems* items);
|
||||
|
||||
void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode);
|
||||
|
||||
void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode);
|
||||
|
||||
void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level);
|
||||
|
||||
void gpio_items_set_all_pins(GPIOItems* items, bool level);
|
||||
|
||||
const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef __GPIO_SCENE_START_H__
|
||||
#define __GPIO_SCENE_START_H__
|
||||
|
||||
#include "../gpio_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_usb.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum GpioItem {
|
||||
GpioItemUsbUart,
|
||||
@@ -117,3 +121,5 @@ void gpio_scene_start_on_exit(void* context) {
|
||||
GpioApp* app = context;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
}
|
||||
|
||||
#endif // __GPIO_SCENE_START_H__
|
||||
@@ -12,8 +12,9 @@ void gpio_scene_test_ok_callback(InputType type, void* context) {
|
||||
}
|
||||
|
||||
void gpio_scene_test_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
GpioApp* app = context;
|
||||
gpio_item_configure_all_pins(GpioModeOutputPushPull);
|
||||
gpio_items_configure_all_pins(app->gpio_items, GpioModeOutputPushPull);
|
||||
gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest);
|
||||
}
|
||||
@@ -25,6 +26,7 @@ bool gpio_scene_test_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void gpio_scene_test_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
gpio_item_configure_all_pins(GpioModeAnalog);
|
||||
furi_assert(context);
|
||||
GpioApp* app = context;
|
||||
gpio_items_configure_all_pins(app->gpio_items, GpioModeAnalog);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "../usb_uart_bridge.h"
|
||||
#include "../gpio_app_i.h"
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef enum {
|
||||
UsbUartLineIndexVcp,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "usb_uart_bridge.h"
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
#include "usb_cdc.h"
|
||||
#include "cli/cli_vcp.h"
|
||||
#include <cli/cli_vcp.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include "cli/cli.h"
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
|
||||
#define USB_CDC_PKT_LEN CDC_DATA_SZ
|
||||
#define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gui/elements.h>
|
||||
#include "gpio_i2c_scanner.h"
|
||||
#include "../gpio_item.h"
|
||||
#include "../gpio_items.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gui/elements.h>
|
||||
#include "gpio_i2c_sfp.h"
|
||||
#include "../gpio_item.h"
|
||||
#include "../gpio_items.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "gpio_test.h"
|
||||
#include "../gpio_item.h"
|
||||
#include "../gpio_items.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
|
||||
@@ -11,6 +11,7 @@ struct GpioTest {
|
||||
|
||||
typedef struct {
|
||||
uint8_t pin_idx;
|
||||
GPIOItems* gpio_items;
|
||||
} GpioTestModel;
|
||||
|
||||
static bool gpio_test_process_left(GpioTest* gpio_test);
|
||||
@@ -25,7 +26,12 @@ static void gpio_test_draw_callback(Canvas* canvas, void* _model) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin");
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 32, AlignCenter, AlignTop, gpio_item_get_pin_name(model->pin_idx));
|
||||
canvas,
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
gpio_items_get_pin_name(model->gpio_items, model->pin_idx));
|
||||
}
|
||||
|
||||
static bool gpio_test_input_callback(InputEvent* event, void* context) {
|
||||
@@ -64,7 +70,7 @@ static bool gpio_test_process_right(GpioTest* gpio_test) {
|
||||
gpio_test->view,
|
||||
GpioTestModel * model,
|
||||
{
|
||||
if(model->pin_idx < GPIO_ITEM_COUNT) {
|
||||
if(model->pin_idx < gpio_items_get_count(model->gpio_items)) {
|
||||
model->pin_idx++;
|
||||
}
|
||||
},
|
||||
@@ -80,17 +86,17 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) {
|
||||
GpioTestModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
if(model->pin_idx < GPIO_ITEM_COUNT) {
|
||||
gpio_item_set_pin(model->pin_idx, true);
|
||||
if(model->pin_idx < gpio_items_get_count(model->gpio_items)) {
|
||||
gpio_items_set_pin(model->gpio_items, model->pin_idx, true);
|
||||
} else {
|
||||
gpio_item_set_all_pins(true);
|
||||
gpio_items_set_all_pins(model->gpio_items, true);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(model->pin_idx < GPIO_ITEM_COUNT) {
|
||||
gpio_item_set_pin(model->pin_idx, false);
|
||||
if(model->pin_idx < gpio_items_get_count(model->gpio_items)) {
|
||||
gpio_items_set_pin(model->gpio_items, model->pin_idx, false);
|
||||
} else {
|
||||
gpio_item_set_all_pins(false);
|
||||
gpio_items_set_all_pins(model->gpio_items, false);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
@@ -101,11 +107,15 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) {
|
||||
return consumed;
|
||||
}
|
||||
|
||||
GpioTest* gpio_test_alloc() {
|
||||
GpioTest* gpio_test_alloc(GPIOItems* gpio_items) {
|
||||
GpioTest* gpio_test = malloc(sizeof(GpioTest));
|
||||
|
||||
gpio_test->view = view_alloc();
|
||||
view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel));
|
||||
|
||||
with_view_model(
|
||||
gpio_test->view, GpioTestModel * model, { model->gpio_items = gpio_items; }, false);
|
||||
|
||||
view_set_context(gpio_test->view, gpio_test);
|
||||
view_set_draw_callback(gpio_test->view, gpio_test_draw_callback);
|
||||
view_set_input_callback(gpio_test->view, gpio_test_input_callback);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "../gpio_items.h"
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct GpioTest GpioTest;
|
||||
typedef void (*GpioTestOkCallback)(InputType type, void* context);
|
||||
|
||||
GpioTest* gpio_test_alloc();
|
||||
GpioTest* gpio_test_alloc(GPIOItems* gpio_items);
|
||||
|
||||
void gpio_test_free(GpioTest* gpio_test);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "../usb_uart_bridge.h"
|
||||
#include "../gpio_app_i.h"
|
||||
#include "furi_hal.h"
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct GpioUsbUart {
|
||||
|
||||
@@ -2,6 +2,7 @@ App(
|
||||
appid="ibutton",
|
||||
name="iButton",
|
||||
apptype=FlipperAppType.APP,
|
||||
targets=["f7"],
|
||||
entry_point="ibutton_app",
|
||||
cdefines=["APP_IBUTTON"],
|
||||
requires=[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void ibutton_scene_delete_success_popup_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void ibutton_scene_read_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void ibutton_scene_save_success_popup_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void ibutton_scene_write_success_popup_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
@@ -3,6 +3,7 @@ App(
|
||||
name="Infrared",
|
||||
apptype=FlipperAppType.APP,
|
||||
entry_point="infrared_app",
|
||||
targets=["f7"],
|
||||
cdefines=["APP_INFRARED"],
|
||||
requires=[
|
||||
"gui",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../infrared_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
void infrared_scene_edit_rename_done_on_enter(void* context) {
|
||||
Infrared* infrared = context;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../infrared_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
void infrared_scene_learn_done_on_enter(void* context) {
|
||||
Infrared* infrared = context;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../infrared_i.h"
|
||||
#include "../../../settings/xtreme_settings/xtreme_assets.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
static void
|
||||
infrared_scene_learn_success_dialog_result_callback(DialogExResult result, void* context) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../infrared_i.h"
|
||||
#include "gui/canvas.h"
|
||||
#include <gui/canvas.h>
|
||||
|
||||
typedef enum {
|
||||
InfraredRpcStateIdle,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "infrared_debug_view.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define INFRARED_DEBUG_TEXT_LENGTH 64
|
||||
|
||||
struct InfraredDebugView {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#include <core/check.h>
|
||||
#include "furi_hal_resources.h"
|
||||
#include "assets_icons.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/view.h"
|
||||
#include "input/input.h"
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
#include "infrared_progress_view.h"
|
||||
#include "gui/modules/button_panel.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/modules/button_panel.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <core/check.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct InfraredProgressView {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user