Merge remote-tracking branch 'OFW/dev' into dev
40
SConstruct
@@ -322,7 +322,12 @@ firmware_env.Append(
|
|||||||
"SConstruct",
|
"SConstruct",
|
||||||
"firmware.scons",
|
"firmware.scons",
|
||||||
"fbt_options.py",
|
"fbt_options.py",
|
||||||
]
|
],
|
||||||
|
IMG_LINT_SOURCES=[
|
||||||
|
# Image assets
|
||||||
|
"applications",
|
||||||
|
"assets",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -359,6 +364,39 @@ distenv.PhonyTarget(
|
|||||||
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
|
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Image assets linting
|
||||||
|
distenv.PhonyTarget(
|
||||||
|
"lint_img",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||||
|
"check",
|
||||||
|
"${IMG_LINT_SOURCES}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||||
|
)
|
||||||
|
|
||||||
|
distenv.PhonyTarget(
|
||||||
|
"format_img",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||||
|
"format",
|
||||||
|
"${IMG_LINT_SOURCES}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||||
|
)
|
||||||
|
|
||||||
|
distenv.Alias("lint_all", ["lint", "lint_py", "lint_img"])
|
||||||
|
distenv.Alias("format_all", ["format", "format_py", "format_img"])
|
||||||
|
|
||||||
|
|
||||||
# Start Flipper CLI via PySerial's miniterm
|
# Start Flipper CLI via PySerial's miniterm
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
8
applications/debug/infrared_test/application.fam
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
App(
|
||||||
|
appid="infrared_test",
|
||||||
|
name="Infrared Test",
|
||||||
|
apptype=FlipperAppType.DEBUG,
|
||||||
|
entry_point="infrared_test_app",
|
||||||
|
fap_category="Debug",
|
||||||
|
targets=["f7"],
|
||||||
|
)
|
||||||
61
applications/debug/infrared_test/infrared_test.c
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_infrared.h>
|
||||||
|
|
||||||
|
#define TAG "InfraredTest"
|
||||||
|
|
||||||
|
#define CARRIER_FREQ_HZ (38000UL)
|
||||||
|
#define CARRIER_DUTY (0.33f)
|
||||||
|
|
||||||
|
#define BURST_DURATION_US (600UL)
|
||||||
|
#define BURST_COUNT (50UL)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool level;
|
||||||
|
uint32_t count;
|
||||||
|
} InfraredTestApp;
|
||||||
|
|
||||||
|
static FuriHalInfraredTxGetDataState
|
||||||
|
infrared_test_app_tx_data_callback(void* context, uint32_t* duration, bool* level) {
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(duration);
|
||||||
|
furi_assert(level);
|
||||||
|
|
||||||
|
InfraredTestApp* app = context;
|
||||||
|
|
||||||
|
*duration = BURST_DURATION_US;
|
||||||
|
*level = app->level;
|
||||||
|
|
||||||
|
app->level = !app->level;
|
||||||
|
app->count += 1;
|
||||||
|
|
||||||
|
if(app->count < BURST_COUNT * 2) {
|
||||||
|
return FuriHalInfraredTxGetDataStateOk;
|
||||||
|
} else {
|
||||||
|
return FuriHalInfraredTxGetDataStateLastDone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t infrared_test_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
InfraredTestApp app = {
|
||||||
|
.level = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Starting test signal on PA7");
|
||||||
|
|
||||||
|
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinExtPA7);
|
||||||
|
furi_hal_infrared_async_tx_set_data_isr_callback(infrared_test_app_tx_data_callback, &app);
|
||||||
|
furi_hal_infrared_async_tx_start(CARRIER_FREQ_HZ, CARRIER_DUTY);
|
||||||
|
furi_hal_infrared_async_tx_wait_termination();
|
||||||
|
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Test signal end");
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"The measured signal should be %luus +-%.1fus",
|
||||||
|
(app.count - 1) * BURST_DURATION_US,
|
||||||
|
(double)1000000.0 / CARRIER_FREQ_HZ);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 96 B |
@@ -0,0 +1,7 @@
|
|||||||
|
Filetype: Flipper SubGhz Key File
|
||||||
|
Version: 1
|
||||||
|
Frequency: 433920000
|
||||||
|
Preset: FuriHalSubGhzPresetOok650Async
|
||||||
|
Protocol: Dickert_MAHS
|
||||||
|
Bit: 36
|
||||||
|
Key: 00 00 00 01 55 57 55 15
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Filetype: Flipper SubGhz RAW File
|
||||||
|
Version: 1
|
||||||
|
Frequency: 433920000
|
||||||
|
Preset: FuriHalSubGhzPresetOok650Async
|
||||||
|
Protocol: RAW
|
||||||
|
RAW_Data: 112254 -62882 64 -8912 798 -844 416 -418 806 -850 396 -45206 440 -428 794 -442 804 -422 822 -810 414 -414 824 -832 412 -416 808 -848 376 -446 792 -846 382 -448 816 -828 410 -416 810 -844 382 -416 834 -818 410 -414 810 -856 408 -810 412 -836 384 -442 808 -814 402 -844 414 -834 378 -436 808 -844 396 -422 798 -844 416 -416 814 -404 812 -440 810 -842 396 -422 798 -840 414 -414 806 -850 398 -45210 450 -420 796 -436 780 -446 802 -848 380 -434 806 -846 400 -422 800 -840 410 -408 836 -812 414 -410 826 -840 378 -440 804 -848 396 -426 812 -810 426 -394 826 -844 414 -810 420 -834 378 -442 808 -832 412 -812 416 -830 410 -406 810 -844 400 -420 832 -810 414 -416 800 -446 798 -440 812 -808 426 -410 800 -836 412 -414 806 -836 412 -45216 450 -420 798 -434 806 -414 802 -846 382 -438 814 -832 410 -410 838 -834 396 -430 810 -842 394 -392 826 -840 414 -414 802 -850 396 -428 812 -842 394 -394 828 -842 414 -810 424 -812 392 -434 812 -844 398 -848 380 -844 408 -416 820 -810 414 -406 816 -836 412 -416 836 -414 816 -398 816 -840 420 -410 802 -844 416 -416 804 -824 410 -45232 446 -400 802 -442 810 -432 804 -842 396 -392 826 -842 410 -410 834 -818 378 -442 804 -854 406 -408 806 -838 408 -428 804 -844 396 -392 826 -840 410 -410 834 -810 414 -832 408 -834 380 -440 802 -826 410 -836 412 -838 396 -424 796 -842 414 -414 804 -848 396 -426 812 -412 814 -414 824 -832 410 -416 806 -848 382 -420 834 -814 422 -45228 416 -422 802 -446 810 -420 790 -846 382 -448 818 -828 408 -416 808 -848 382 -418 830 -816 410 -412 812 -856 410 -382 834 -846 382 -418 832 -818 408 -412 812 -856 408 -814 414 -838 396 -428 810 -808 424 -836 380 -844 404 -416 802 -840 424 -394 826 -840 414 -382 836 -412 822 -436 812 -806 424 -394 826 -844 416 -382 838 -816 402 -45228 438 -430 796 -444 806 -424 822 -810 412 -416 822 -832 412 -416 804 -844 408 -414 824 -812 412 -408 812 -834 410 -414 804 -848 408 -412 802 -840 424 -412 802 -834 412 -842 384 -848 396 -426 814 -808 424 -816 392 -866 382 -414 838 -816 414 -428 792 -846 380 -440 810 -438 812 -412 802 -846 380 -438 826 -840 380 -416 838 -814 404 -45226 450 -404 820 -408 806 -452 792 -848 382 -440 814 -832 410 -416 810 -846 378 -450 792 -846 380 -446 816 -830 410 -386 836 -846 376 -410 828 -846 380 -446 814 -828 410 -814 414 -836 396 -428 810 -842 394 -816 410 -836 406 -430 812 -810 426 -394 826 -838
|
||||||
|
RAW_Data: 414 -414 808 -416 826 -438 814 -816 420 -414 834 -814 418 -418 808 -848 398 -45218 412 -438 824 -412 812 -418 832 -852 378 -446 782 -862 410 -386 838 -848 384 -420 836 -820 418 -414 814 -854 408 -388 838 -814 418 -422 836 -816 394 -434 812 -846 398 -850 380 -848 410 -418 822 -812 416 -850 368 -854 412 -418 810 -850 384 -422 834 -820 416 -414 812 -428 836 -412 804 -848 382 -450 818 -828 412 -418 808 -850 380 -45228 452 -420 798 -434 806 -416 834 -818 384 -440 810 -820 404 -420 834 -814 416 -418 834 -824 386 -442 810 -818 404 -420 834 -814 416 -418 834 -820 410 -414 810 -850 406 -812 414 -816 404 -420 818 -838 386 -848 394 -828 414 -414 838 -814 406 -420 820 -842 384 -446 794 -438 810 -412 802 -848 394 -432 812 -842 394 -392 830 -842 414 -105578 64 -1760 130 -196 130 -832 160 -128 62 -1278 194 -1316 230 -96 362 -64 64 -398
|
||||||
@@ -663,6 +663,13 @@ MU_TEST(subghz_decoder_mastercode_test) {
|
|||||||
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(subghz_decoder_dickert_test) {
|
||||||
|
mu_assert(
|
||||||
|
subghz_decoder_test(
|
||||||
|
EXT_PATH("unit_tests/subghz/dickert_raw.sub"), SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME),
|
||||||
|
"Test decoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
//test encoders
|
//test encoders
|
||||||
MU_TEST(subghz_encoder_princeton_test) {
|
MU_TEST(subghz_encoder_princeton_test) {
|
||||||
mu_assert(
|
mu_assert(
|
||||||
@@ -820,6 +827,12 @@ MU_TEST(subghz_encoder_mastercode_test) {
|
|||||||
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(subghz_encoder_dickert_test) {
|
||||||
|
mu_assert(
|
||||||
|
subghz_encoder_test(EXT_PATH("unit_tests/subghz/dickert_mahs.sub")),
|
||||||
|
"Test encoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST(subghz_random_test) {
|
MU_TEST(subghz_random_test) {
|
||||||
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
||||||
}
|
}
|
||||||
@@ -871,6 +884,7 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||||
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
||||||
|
MU_RUN_TEST(subghz_decoder_dickert_test);
|
||||||
|
|
||||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||||
MU_RUN_TEST(subghz_encoder_came_test);
|
MU_RUN_TEST(subghz_encoder_came_test);
|
||||||
@@ -898,6 +912,7 @@ MU_TEST_SUITE(subghz) {
|
|||||||
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
||||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||||
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
||||||
|
MU_RUN_TEST(subghz_encoder_dickert_test);
|
||||||
|
|
||||||
MU_RUN_TEST(subghz_random_test);
|
MU_RUN_TEST(subghz_random_test);
|
||||||
subghz_test_deinit();
|
subghz_test_deinit();
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 258 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 226 B |
7
applications/examples/example_number_input/ReadMe.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Number Input
|
||||||
|
|
||||||
|
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
|
||||||
|
|
||||||
|
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
|
||||||
|
|
||||||
|
It is also possible to define a header text, shown in this example app with the 3 different input options.
|
||||||
10
applications/examples/example_number_input/application.fam
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
App(
|
||||||
|
appid="example_number_input",
|
||||||
|
name="Example: Number Input",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="example_number_input",
|
||||||
|
requires=["gui"],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
fap_icon="example_number_input_10px.png",
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include "example_number_input.h"
|
||||||
|
|
||||||
|
bool example_number_input_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool example_number_input_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExampleNumberInput* example_number_input_alloc() {
|
||||||
|
ExampleNumberInput* app = malloc(sizeof(ExampleNumberInput));
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
|
||||||
|
app->scene_manager = scene_manager_alloc(&example_number_input_scene_handlers, app);
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
app->view_dispatcher, example_number_input_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, example_number_input_back_event_callback);
|
||||||
|
|
||||||
|
app->number_input = number_input_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher,
|
||||||
|
ExampleNumberInputViewIdNumberInput,
|
||||||
|
number_input_get_view(app->number_input));
|
||||||
|
|
||||||
|
app->dialog_ex = dialog_ex_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher,
|
||||||
|
ExampleNumberInputViewIdShowNumber,
|
||||||
|
dialog_ex_get_view(app->dialog_ex));
|
||||||
|
|
||||||
|
app->current_number = 5;
|
||||||
|
app->min_value = INT32_MIN;
|
||||||
|
app->max_value = INT32_MAX;
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void example_number_input_free(ExampleNumberInput* app) {
|
||||||
|
furi_assert(app);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||||
|
dialog_ex_free(app->dialog_ex);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||||
|
number_input_free(app->number_input);
|
||||||
|
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
app->gui = NULL;
|
||||||
|
|
||||||
|
//Remove whatever is left
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t example_number_input(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
ExampleNumberInput* app = example_number_input_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneShowNumber);
|
||||||
|
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
|
||||||
|
example_number_input_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/modules/dialog_ex.h>
|
||||||
|
#include <gui/modules/number_input.h>
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
|
||||||
|
#include "scenes/example_number_input_scene.h"
|
||||||
|
|
||||||
|
typedef struct ExampleNumberInputShowNumber ExampleNumberInputShowNumber;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ExampleNumberInputViewIdShowNumber,
|
||||||
|
ExampleNumberInputViewIdNumberInput,
|
||||||
|
} ExampleNumberInputViewId;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Gui* gui;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
|
||||||
|
NumberInput* number_input;
|
||||||
|
DialogEx* dialog_ex;
|
||||||
|
|
||||||
|
int32_t current_number;
|
||||||
|
int32_t min_value;
|
||||||
|
int32_t max_value;
|
||||||
|
} ExampleNumberInput;
|
||||||
|
After Width: | Height: | Size: 87 B |
@@ -0,0 +1,30 @@
|
|||||||
|
#include "example_number_input_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const example_number_input_on_enter_handlers[])(void*) = {
|
||||||
|
#include "example_number_input_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 example_number_input_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "example_number_input_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 example_number_input_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "example_number_input_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers example_number_input_scene_handlers = {
|
||||||
|
.on_enter_handlers = example_number_input_on_enter_handlers,
|
||||||
|
.on_event_handlers = example_number_input_on_event_handlers,
|
||||||
|
.on_exit_handlers = example_number_input_on_exit_handlers,
|
||||||
|
.scene_num = ExampleNumberInputSceneNum,
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "example_number_input_scene_config.h"
|
||||||
|
ExampleNumberInputSceneNum,
|
||||||
|
} ExampleNumberInputScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers example_number_input_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "example_number_input_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 "example_number_input_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 "example_number_input_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ADD_SCENE(example_number_input, input_number, InputNumber)
|
||||||
|
ADD_SCENE(example_number_input, show_number, ShowNumber)
|
||||||
|
ADD_SCENE(example_number_input, input_max, InputMax)
|
||||||
|
ADD_SCENE(example_number_input, input_min, InputMin)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "../example_number_input.h"
|
||||||
|
|
||||||
|
void example_number_input_scene_input_max_callback(void* context, int32_t number) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
app->max_value = number;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_max_on_enter(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
NumberInput* number_input = app->number_input;
|
||||||
|
|
||||||
|
number_input_set_header_text(number_input, "Enter the maximum value");
|
||||||
|
number_input_set_result_callback(
|
||||||
|
number_input,
|
||||||
|
example_number_input_scene_input_max_callback,
|
||||||
|
context,
|
||||||
|
app->max_value,
|
||||||
|
app->min_value,
|
||||||
|
INT32_MAX);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool example_number_input_scene_input_max_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
scene_manager_previous_scene(app->scene_manager);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_max_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "../example_number_input.h"
|
||||||
|
|
||||||
|
void example_number_input_scene_input_min_callback(void* context, int32_t number) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
app->min_value = number;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_min_on_enter(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
NumberInput* number_input = app->number_input;
|
||||||
|
|
||||||
|
number_input_set_header_text(number_input, "Enter the minimum value");
|
||||||
|
number_input_set_result_callback(
|
||||||
|
number_input,
|
||||||
|
example_number_input_scene_input_min_callback,
|
||||||
|
context,
|
||||||
|
app->min_value,
|
||||||
|
INT32_MIN,
|
||||||
|
app->max_value);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool example_number_input_scene_input_min_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
scene_manager_previous_scene(app->scene_manager);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_min_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#include "../example_number_input.h"
|
||||||
|
|
||||||
|
void example_number_input_scene_input_number_callback(void* context, int32_t number) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
app->current_number = number;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_number_on_enter(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
NumberInput* number_input = app->number_input;
|
||||||
|
|
||||||
|
char str[50];
|
||||||
|
snprintf(str, sizeof(str), "Set Number (%ld - %ld)", app->min_value, app->max_value);
|
||||||
|
|
||||||
|
number_input_set_header_text(number_input, str);
|
||||||
|
number_input_set_result_callback(
|
||||||
|
number_input,
|
||||||
|
example_number_input_scene_input_number_callback,
|
||||||
|
context,
|
||||||
|
app->current_number,
|
||||||
|
app->min_value,
|
||||||
|
app->max_value);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool example_number_input_scene_input_number_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
|
||||||
|
scene_manager_previous_scene(app->scene_manager);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_input_number_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#include "../example_number_input.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
example_number_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void example_number_input_scene_update_view(void* context) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
DialogEx* dialog_ex = app->dialog_ex;
|
||||||
|
|
||||||
|
dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop);
|
||||||
|
|
||||||
|
static char buffer[12]; //needs static for extended lifetime
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%ld", app->current_number);
|
||||||
|
dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
|
||||||
|
|
||||||
|
dialog_ex_set_left_button_text(dialog_ex, "Min");
|
||||||
|
dialog_ex_set_right_button_text(dialog_ex, "Max");
|
||||||
|
dialog_ex_set_center_button_text(dialog_ex, "Change");
|
||||||
|
|
||||||
|
dialog_ex_set_result_callback(dialog_ex, example_number_input_scene_confirm_dialog_callback);
|
||||||
|
dialog_ex_set_context(dialog_ex, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_show_number_on_enter(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
|
||||||
|
example_number_input_scene_update_view(app);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool example_number_input_scene_show_number_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
ExampleNumberInput* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
switch(event.event) {
|
||||||
|
case DialogExResultCenter:
|
||||||
|
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputNumber);
|
||||||
|
consumed = true;
|
||||||
|
break;
|
||||||
|
case DialogExResultLeft:
|
||||||
|
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMin);
|
||||||
|
consumed = true;
|
||||||
|
break;
|
||||||
|
case DialogExResultRight:
|
||||||
|
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMax);
|
||||||
|
consumed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_number_input_scene_show_number_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 91 B |
@@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
|||||||
} else {
|
} else {
|
||||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||||
if(furi_string_end_with(file->path, known_ext[i])) {
|
if(furi_string_end_withi(file->path, known_ext[i])) {
|
||||||
if((i == ArchiveFileTypeBadUsb) || (i == ArchiveFileTypeSubGhzRemote)) {
|
if((i == ArchiveFileTypeBadUsb) || (i == ArchiveFileTypeSubGhzRemote)) {
|
||||||
if(furi_string_search(
|
if(furi_string_search(
|
||||||
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
|
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 91 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 95 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 95 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 98 B |
@@ -19,6 +19,7 @@ App(
|
|||||||
"view_holder.h",
|
"view_holder.h",
|
||||||
"modules/button_menu.h",
|
"modules/button_menu.h",
|
||||||
"modules/byte_input.h",
|
"modules/byte_input.h",
|
||||||
|
"modules/number_input.h",
|
||||||
"modules/popup.h",
|
"modules/popup.h",
|
||||||
"modules/text_input.h",
|
"modules/text_input.h",
|
||||||
"modules/widget.h",
|
"modules/widget.h",
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo
|
|||||||
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
|
if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(furi_string_end_with(name, ext)) {
|
if(furi_string_end_withi(name, ext)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
443
applications/services/gui/modules/number_input.c
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
#include "number_input.h"
|
||||||
|
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
|
|
||||||
|
struct NumberInput {
|
||||||
|
View* view;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char text;
|
||||||
|
const size_t x;
|
||||||
|
const size_t y;
|
||||||
|
} NumberInputKey;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriString* header;
|
||||||
|
FuriString* text_buffer;
|
||||||
|
|
||||||
|
int32_t current_number;
|
||||||
|
int32_t max_value;
|
||||||
|
int32_t min_value;
|
||||||
|
|
||||||
|
NumberInputCallback callback;
|
||||||
|
void* callback_context;
|
||||||
|
|
||||||
|
size_t selected_row;
|
||||||
|
size_t selected_column;
|
||||||
|
} NumberInputModel;
|
||||||
|
|
||||||
|
static const size_t keyboard_origin_x = 7;
|
||||||
|
static const size_t keyboard_origin_y = 31;
|
||||||
|
static const size_t keyboard_row_count = 2;
|
||||||
|
static const char enter_symbol = '\r';
|
||||||
|
static const char backspace_symbol = '\b';
|
||||||
|
static const char sign_symbol = '-';
|
||||||
|
|
||||||
|
static const NumberInputKey keyboard_keys_row_1[] = {
|
||||||
|
{'0', 0, 12},
|
||||||
|
{'1', 11, 12},
|
||||||
|
{'2', 22, 12},
|
||||||
|
{'3', 33, 12},
|
||||||
|
{'4', 44, 12},
|
||||||
|
{backspace_symbol, 103, 4},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NumberInputKey keyboard_keys_row_2[] = {
|
||||||
|
{'5', 0, 26},
|
||||||
|
{'6', 11, 26},
|
||||||
|
{'7', 22, 26},
|
||||||
|
{'8', 33, 26},
|
||||||
|
{'9', 44, 26},
|
||||||
|
{sign_symbol, 55, 17},
|
||||||
|
{enter_symbol, 95, 17},
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t number_input_get_row_size(size_t row_index) {
|
||||||
|
size_t row_size = 0;
|
||||||
|
|
||||||
|
switch(row_index + 1) {
|
||||||
|
case 1:
|
||||||
|
row_size = COUNT_OF(keyboard_keys_row_1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
row_size = COUNT_OF(keyboard_keys_row_2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return row_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const NumberInputKey* number_input_get_row(size_t row_index) {
|
||||||
|
const NumberInputKey* row = NULL;
|
||||||
|
|
||||||
|
switch(row_index + 1) {
|
||||||
|
case 1:
|
||||||
|
row = keyboard_keys_row_1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
row = keyboard_keys_row_2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_draw_input(Canvas* canvas, NumberInputModel* model) {
|
||||||
|
const size_t text_x = 8;
|
||||||
|
const size_t text_y = 25;
|
||||||
|
|
||||||
|
elements_slightly_rounded_frame(canvas, 6, 14, 116, 15);
|
||||||
|
|
||||||
|
canvas_draw_str(canvas, text_x, text_y, furi_string_get_cstr(model->text_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool number_input_use_sign(NumberInputModel* model) {
|
||||||
|
//only show sign button if allowed number range needs it
|
||||||
|
if(model->min_value < 0 && model->max_value >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_backspace_cb(NumberInputModel* model) {
|
||||||
|
size_t text_length = furi_string_utf8_length(model->text_buffer);
|
||||||
|
if(text_length < 1 || (text_length < 2 && model->current_number <= 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
furi_string_set_strn(
|
||||||
|
model->text_buffer, furi_string_get_cstr(model->text_buffer), text_length - 1);
|
||||||
|
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_handle_up(NumberInputModel* model) {
|
||||||
|
if(model->selected_row > 0) {
|
||||||
|
model->selected_row--;
|
||||||
|
if(model->selected_column > number_input_get_row_size(model->selected_row) - 1) {
|
||||||
|
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_handle_down(NumberInputModel* model) {
|
||||||
|
if(model->selected_row < keyboard_row_count - 1) {
|
||||||
|
if(model->selected_column >= number_input_get_row_size(model->selected_row) - 1) {
|
||||||
|
model->selected_column = number_input_get_row_size(model->selected_row + 1) - 1;
|
||||||
|
}
|
||||||
|
model->selected_row += 1;
|
||||||
|
}
|
||||||
|
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||||
|
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||||
|
model->selected_column--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_handle_left(NumberInputModel* model) {
|
||||||
|
if(model->selected_column > 0) {
|
||||||
|
model->selected_column--;
|
||||||
|
} else {
|
||||||
|
model->selected_column = number_input_get_row_size(model->selected_row) - 1;
|
||||||
|
}
|
||||||
|
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||||
|
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||||
|
model->selected_column--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_handle_right(NumberInputModel* model) {
|
||||||
|
if(model->selected_column < number_input_get_row_size(model->selected_row) - 1) {
|
||||||
|
model->selected_column++;
|
||||||
|
} else {
|
||||||
|
model->selected_column = 0;
|
||||||
|
}
|
||||||
|
const NumberInputKey* keys = number_input_get_row(model->selected_row);
|
||||||
|
if(keys[model->selected_column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||||
|
model->selected_column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_number_too_large(NumberInputModel* model) {
|
||||||
|
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
if(value > (int64_t)model->max_value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_number_too_small(NumberInputModel* model) {
|
||||||
|
int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
if(value < (int64_t)model->min_value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_sign(NumberInputModel* model) {
|
||||||
|
int32_t number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
if(number == 0 && furi_string_cmp_str(model->text_buffer, "-") != 0) {
|
||||||
|
furi_string_set_str(model->text_buffer, "-");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
number = number * -1;
|
||||||
|
furi_string_printf(model->text_buffer, "%ld", number);
|
||||||
|
if(is_number_too_large(model) || is_number_too_small(model)) {
|
||||||
|
furi_string_printf(model->text_buffer, "%ld", model->current_number);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
if(model->current_number == 0) {
|
||||||
|
furi_string_set_str(model->text_buffer, ""); //show empty if 0, better for usability
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_add_digit(NumberInputModel* model, char* newChar) {
|
||||||
|
furi_string_cat_str(model->text_buffer, newChar);
|
||||||
|
if((model->max_value >= 0 && is_number_too_large(model)) ||
|
||||||
|
(model->min_value < 0 && is_number_too_small(model))) {
|
||||||
|
//you still need to be able to type invalid numbers in some cases to reach valid numbers on later keypress
|
||||||
|
furi_string_printf(model->text_buffer, "%ld", model->current_number);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
if(model->current_number == 0) {
|
||||||
|
furi_string_reset(model->text_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_handle_ok(NumberInputModel* model) {
|
||||||
|
char selected = number_input_get_row(model->selected_row)[model->selected_column].text;
|
||||||
|
char temp_str[2] = {selected, '\0'};
|
||||||
|
if(selected == enter_symbol) {
|
||||||
|
if(is_number_too_large(model) || is_number_too_small(model)) {
|
||||||
|
return; //Do nothing if number outside allowed range
|
||||||
|
}
|
||||||
|
model->current_number = strtol(furi_string_get_cstr(model->text_buffer), NULL, 10);
|
||||||
|
model->callback(model->callback_context, model->current_number);
|
||||||
|
} else if(selected == backspace_symbol) {
|
||||||
|
number_input_backspace_cb(model);
|
||||||
|
} else if(selected == sign_symbol) {
|
||||||
|
number_input_sign(model);
|
||||||
|
} else {
|
||||||
|
number_input_add_digit(model, temp_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number_input_view_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
NumberInputModel* model = _model;
|
||||||
|
|
||||||
|
number_input_draw_input(canvas, model);
|
||||||
|
|
||||||
|
if(!furi_string_empty(model->header)) {
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str(canvas, 2, 9, furi_string_get_cstr(model->header));
|
||||||
|
}
|
||||||
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
|
// Draw keyboard
|
||||||
|
for(size_t row = 0; row < keyboard_row_count; row++) {
|
||||||
|
const size_t column_count = number_input_get_row_size(row);
|
||||||
|
const NumberInputKey* keys = number_input_get_row(row);
|
||||||
|
|
||||||
|
for(size_t column = 0; column < column_count; column++) {
|
||||||
|
if(keys[column].text == sign_symbol && !number_input_use_sign(model)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(keys[column].text == enter_symbol) {
|
||||||
|
if(is_number_too_small(model) || is_number_too_large(model)) {
|
||||||
|
//in some cases you need to be able to type a number smaller/larger than the limits (expl. min = 50, clear all and editor must allow to type 9 and later 0 for 90)
|
||||||
|
if(model->selected_row == row && model->selected_column == column) {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySaveBlockedSelected_24x11);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySaveBlocked_24x11);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(model->selected_row == row && model->selected_column == column) {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySaveSelected_24x11);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySave_24x11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(keys[column].text == backspace_symbol) {
|
||||||
|
if(model->selected_row == row && model->selected_column == column) {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeyBackspaceSelected_16x9);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeyBackspace_16x9);
|
||||||
|
}
|
||||||
|
} else if(keys[column].text == sign_symbol) {
|
||||||
|
if(model->selected_row == row && model->selected_column == column) {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySignSelected_21x11);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
&I_KeySign_21x11);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(model->selected_row == row && model->selected_column == column) {
|
||||||
|
canvas_draw_box(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x - 3,
|
||||||
|
keyboard_origin_y + keys[column].y - 10,
|
||||||
|
11,
|
||||||
|
13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_draw_glyph(
|
||||||
|
canvas,
|
||||||
|
keyboard_origin_x + keys[column].x,
|
||||||
|
keyboard_origin_y + keys[column].y,
|
||||||
|
keys[column].text);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool number_input_view_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
NumberInput* number_input = context;
|
||||||
|
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
// Fetch the model
|
||||||
|
NumberInputModel* model = view_get_model(number_input->view);
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort || event->type == InputTypeLong ||
|
||||||
|
event->type == InputTypeRepeat) {
|
||||||
|
consumed = true;
|
||||||
|
switch(event->key) {
|
||||||
|
case InputKeyLeft:
|
||||||
|
number_input_handle_left(model);
|
||||||
|
break;
|
||||||
|
case InputKeyRight:
|
||||||
|
number_input_handle_right(model);
|
||||||
|
break;
|
||||||
|
case InputKeyUp:
|
||||||
|
number_input_handle_up(model);
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
number_input_handle_down(model);
|
||||||
|
break;
|
||||||
|
case InputKeyOk:
|
||||||
|
number_input_handle_ok(model);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
consumed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit view
|
||||||
|
view_commit_model(number_input->view, consumed);
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInput* number_input_alloc(void) {
|
||||||
|
NumberInput* number_input = malloc(sizeof(NumberInput));
|
||||||
|
number_input->view = view_alloc();
|
||||||
|
view_set_context(number_input->view, number_input);
|
||||||
|
view_allocate_model(number_input->view, ViewModelTypeLocking, sizeof(NumberInputModel));
|
||||||
|
view_set_draw_callback(number_input->view, number_input_view_draw_callback);
|
||||||
|
view_set_input_callback(number_input->view, number_input_view_input_callback);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
number_input->view,
|
||||||
|
NumberInputModel * model,
|
||||||
|
{
|
||||||
|
model->header = furi_string_alloc();
|
||||||
|
model->text_buffer = furi_string_alloc();
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
return number_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void number_input_free(NumberInput* number_input) {
|
||||||
|
furi_check(number_input);
|
||||||
|
with_view_model(
|
||||||
|
number_input->view,
|
||||||
|
NumberInputModel * model,
|
||||||
|
{
|
||||||
|
furi_string_free(model->header);
|
||||||
|
furi_string_free(model->text_buffer);
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
view_free(number_input->view);
|
||||||
|
free(number_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* number_input_get_view(NumberInput* number_input) {
|
||||||
|
furi_check(number_input);
|
||||||
|
return number_input->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void number_input_set_result_callback(
|
||||||
|
NumberInput* number_input,
|
||||||
|
NumberInputCallback callback,
|
||||||
|
void* callback_context,
|
||||||
|
int32_t current_number,
|
||||||
|
int32_t min_value,
|
||||||
|
int32_t max_value) {
|
||||||
|
furi_check(number_input);
|
||||||
|
|
||||||
|
current_number = CLAMP(current_number, max_value, min_value);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
number_input->view,
|
||||||
|
NumberInputModel * model,
|
||||||
|
{
|
||||||
|
model->callback = callback;
|
||||||
|
model->callback_context = callback_context;
|
||||||
|
model->current_number = current_number;
|
||||||
|
furi_string_printf(model->text_buffer, "%ld", current_number);
|
||||||
|
model->min_value = min_value;
|
||||||
|
model->max_value = max_value;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void number_input_set_header_text(NumberInput* number_input, const char* text) {
|
||||||
|
furi_check(number_input);
|
||||||
|
with_view_model(
|
||||||
|
number_input->view,
|
||||||
|
NumberInputModel * model,
|
||||||
|
{ furi_string_set(model->header, text); },
|
||||||
|
true);
|
||||||
|
}
|
||||||
69
applications/services/gui/modules/number_input.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @file number_input.h
|
||||||
|
* GUI: Integer string keyboard view module API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Number input anonymous structure */
|
||||||
|
typedef struct NumberInput NumberInput;
|
||||||
|
|
||||||
|
/** Callback to be called on save button press */
|
||||||
|
typedef void (*NumberInputCallback)(void* context, int32_t number);
|
||||||
|
|
||||||
|
/** Allocate and initialize Number input.
|
||||||
|
*
|
||||||
|
* This Number input is used to enter Numbers (Integers).
|
||||||
|
*
|
||||||
|
* @return NumberInput instance pointer
|
||||||
|
*/
|
||||||
|
NumberInput* number_input_alloc(void);
|
||||||
|
|
||||||
|
/** Deinitialize and free byte input
|
||||||
|
*
|
||||||
|
* @param number_input Number input instance
|
||||||
|
*/
|
||||||
|
void number_input_free(NumberInput* number_input);
|
||||||
|
|
||||||
|
/** Get byte input view
|
||||||
|
*
|
||||||
|
* @param number_input byte input instance
|
||||||
|
*
|
||||||
|
* @return View instance that can be used for embedding
|
||||||
|
*/
|
||||||
|
View* number_input_get_view(NumberInput* number_input);
|
||||||
|
|
||||||
|
/** Set byte input result callback
|
||||||
|
*
|
||||||
|
* @param number_input byte input instance
|
||||||
|
* @param input_callback input callback fn
|
||||||
|
* @param callback_context callback context
|
||||||
|
* @param[in] current_number The current number
|
||||||
|
* @param min_value Min number value
|
||||||
|
* @param max_value Max number value
|
||||||
|
*/
|
||||||
|
|
||||||
|
void number_input_set_result_callback(
|
||||||
|
NumberInput* number_input,
|
||||||
|
NumberInputCallback input_callback,
|
||||||
|
void* callback_context,
|
||||||
|
int32_t current_number,
|
||||||
|
int32_t min_value,
|
||||||
|
int32_t max_value);
|
||||||
|
|
||||||
|
/** Set byte input header text
|
||||||
|
*
|
||||||
|
* @param number_input byte input instance
|
||||||
|
* @param text text to be shown
|
||||||
|
*/
|
||||||
|
void number_input_set_header_text(NumberInput* number_input, const char* text);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
10
applications/services/region/application.fam
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
App(
|
||||||
|
appid="region",
|
||||||
|
name="RegionSrv",
|
||||||
|
apptype=FlipperAppType.STARTUP,
|
||||||
|
targets=["f7"],
|
||||||
|
entry_point="region_on_system_start",
|
||||||
|
cdefines=["SRV_REGION"],
|
||||||
|
requires=["storage"],
|
||||||
|
order=170,
|
||||||
|
)
|
||||||
147
applications/services/region/region.c
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#include <furi_hal_region.h>
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
|
||||||
|
#include <flipper.pb.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
|
||||||
|
#define TAG "RegionSrv"
|
||||||
|
|
||||||
|
#define SUBGHZ_REGION_FILENAME INT_PATH(".region_data")
|
||||||
|
|
||||||
|
static bool region_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
|
||||||
|
File* file = istream->state;
|
||||||
|
size_t ret = storage_file_read(file, buf, count);
|
||||||
|
return count == ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool region_istream_decode_band(pb_istream_t* stream, const pb_field_t* field, void** arg) {
|
||||||
|
UNUSED(field);
|
||||||
|
|
||||||
|
FuriHalRegion* region = *arg;
|
||||||
|
|
||||||
|
PB_Region_Band band = {0};
|
||||||
|
if(!pb_decode(stream, PB_Region_Band_fields, &band)) {
|
||||||
|
FURI_LOG_E(TAG, "PB Region band decode error: %s", PB_GET_ERROR(stream));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
region->bands_count += 1;
|
||||||
|
region = realloc( //-V701
|
||||||
|
region,
|
||||||
|
sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count);
|
||||||
|
size_t pos = region->bands_count - 1;
|
||||||
|
region->bands[pos].start = band.start;
|
||||||
|
region->bands[pos].end = band.end;
|
||||||
|
region->bands[pos].power_limit = band.power_limit;
|
||||||
|
region->bands[pos].duty_cycle = band.duty_cycle;
|
||||||
|
*arg = region;
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%",
|
||||||
|
band.start,
|
||||||
|
band.end,
|
||||||
|
band.power_limit,
|
||||||
|
band.duty_cycle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t region_load_file(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
File* file = storage_file_alloc(storage);
|
||||||
|
|
||||||
|
PB_Region pb_region = {0};
|
||||||
|
pb_region.bands.funcs.decode = region_istream_decode_band;
|
||||||
|
|
||||||
|
do {
|
||||||
|
FileInfo fileinfo = {0};
|
||||||
|
|
||||||
|
if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK ||
|
||||||
|
fileinfo.size == 0) {
|
||||||
|
FURI_LOG_W(TAG, "Region file missing or empty");
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to open region file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pb_istream_t istream = {
|
||||||
|
.callback = region_istream_read,
|
||||||
|
.state = file,
|
||||||
|
.errmsg = NULL,
|
||||||
|
.bytes_left = fileinfo.size,
|
||||||
|
};
|
||||||
|
|
||||||
|
pb_region.bands.arg = malloc(sizeof(FuriHalRegion));
|
||||||
|
|
||||||
|
if(!pb_decode(&istream, PB_Region_fields, &pb_region)) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to decode region file");
|
||||||
|
free(pb_region.bands.arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriHalRegion* region = pb_region.bands.arg;
|
||||||
|
|
||||||
|
memcpy(
|
||||||
|
region->country_code,
|
||||||
|
pb_region.country_code->bytes,
|
||||||
|
MIN(pb_region.country_code->size, sizeof(region->country_code) - 1));
|
||||||
|
|
||||||
|
furi_hal_region_set(region);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Dynamic region set: %s", region->country_code);
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
pb_release(PB_Region_fields, &pb_region);
|
||||||
|
storage_file_free(file);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void region_loader_pending_callback(void* context, uint32_t arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
FuriThread* loader = context;
|
||||||
|
furi_thread_join(loader);
|
||||||
|
furi_thread_free(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void region_loader_state_callback(FuriThreadState state, void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
|
||||||
|
if(state == FuriThreadStateStopped) {
|
||||||
|
furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void region_storage_callback(const void* message, void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
const StorageEvent* event = message;
|
||||||
|
|
||||||
|
if(event->type == StorageEventTypeCardMount) {
|
||||||
|
FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL);
|
||||||
|
furi_thread_set_state_callback(loader, region_loader_state_callback);
|
||||||
|
furi_thread_start(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t region_on_system_start(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
furi_pubsub_subscribe(storage_get_pubsub(storage), region_storage_callback, NULL);
|
||||||
|
|
||||||
|
if(storage_sd_status(storage) != FSE_OK) {
|
||||||
|
FURI_LOG_D(TAG, "SD Card not ready, skipping dynamic region");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
region_load_file(NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define STORAGE_INTERNAL_DIR_NAME ".int"
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "storage_processing.h"
|
|
||||||
#include <m-list.h>
|
#include <m-list.h>
|
||||||
#include <m-dict.h>
|
#include <m-dict.h>
|
||||||
|
|
||||||
|
#include "storage_processing.h"
|
||||||
|
#include "storage_internal_dirname_i.h"
|
||||||
|
|
||||||
#define TAG "Storage"
|
#define TAG "Storage"
|
||||||
|
|
||||||
#define STORAGE_PATH_PREFIX_LEN 4u
|
#define STORAGE_PATH_PREFIX_LEN 4u
|
||||||
@@ -555,9 +557,9 @@ void storage_process_alias(
|
|||||||
|
|
||||||
} else if(furi_string_start_with(path, STORAGE_INT_PATH_PREFIX)) {
|
} else if(furi_string_start_with(path, STORAGE_INT_PATH_PREFIX)) {
|
||||||
furi_string_replace_at(
|
furi_string_replace_at(
|
||||||
path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX "/.int");
|
path, 0, strlen(STORAGE_INT_PATH_PREFIX), EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
|
||||||
|
|
||||||
FuriString* int_on_ext_path = furi_string_alloc_set(STORAGE_EXT_PATH_PREFIX "/.int");
|
FuriString* int_on_ext_path = furi_string_alloc_set(EXT_PATH(STORAGE_INTERNAL_DIR_NAME));
|
||||||
if(storage_process_common_stat(app, int_on_ext_path, NULL) != FSE_OK) {
|
if(storage_process_common_stat(app, int_on_ext_path, NULL) != FSE_OK) {
|
||||||
storage_process_common_mkdir(app, int_on_ext_path);
|
storage_process_common_mkdir(app, int_on_ext_path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#include "fatfs.h"
|
#include <fatfs.h>
|
||||||
#include "../filesystem_api_internal.h"
|
|
||||||
#include "storage_ext.h"
|
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include "sd_notify.h"
|
|
||||||
#include <furi_hal_sd.h>
|
#include <furi_hal_sd.h>
|
||||||
|
|
||||||
|
#include "sd_notify.h"
|
||||||
|
#include "storage_ext.h"
|
||||||
|
|
||||||
|
#include "../filesystem_api_internal.h"
|
||||||
|
#include "../storage_internal_dirname_i.h"
|
||||||
|
|
||||||
typedef FIL SDFile;
|
typedef FIL SDFile;
|
||||||
typedef DIR SDDir;
|
typedef DIR SDDir;
|
||||||
typedef FILINFO SDFileInfo;
|
typedef FILINFO SDFileInfo;
|
||||||
@@ -93,6 +96,64 @@ static bool sd_mount_card_internal(StorageData* storage, bool notify) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool sd_remove_recursive(const char* path) {
|
||||||
|
SDDir* current_dir = malloc(sizeof(DIR));
|
||||||
|
SDFileInfo* file_info = malloc(sizeof(FILINFO));
|
||||||
|
FuriString* current_path = furi_string_alloc_set(path);
|
||||||
|
|
||||||
|
bool go_deeper = false;
|
||||||
|
SDError status;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
status = f_opendir(current_dir, furi_string_get_cstr(current_path));
|
||||||
|
if(status != FR_OK) break;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
status = f_readdir(current_dir, file_info);
|
||||||
|
if(status != FR_OK || !strlen(file_info->fname)) break;
|
||||||
|
|
||||||
|
if(file_info->fattrib & AM_DIR) {
|
||||||
|
furi_string_cat_printf(current_path, "/%s", file_info->fname);
|
||||||
|
go_deeper = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
FuriString* file_path = furi_string_alloc_printf(
|
||||||
|
"%s/%s", furi_string_get_cstr(current_path), file_info->fname);
|
||||||
|
status = f_unlink(furi_string_get_cstr(file_path));
|
||||||
|
furi_string_free(file_path);
|
||||||
|
|
||||||
|
if(status != FR_OK) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = f_closedir(current_dir);
|
||||||
|
if(status != FR_OK) break;
|
||||||
|
|
||||||
|
if(go_deeper) {
|
||||||
|
go_deeper = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = f_unlink(furi_string_get_cstr(current_path));
|
||||||
|
if(status != FR_OK) break;
|
||||||
|
|
||||||
|
if(!furi_string_equal(current_path, path)) {
|
||||||
|
size_t last_char_pos = furi_string_search_rchar(current_path, '/');
|
||||||
|
furi_assert(last_char_pos != FURI_STRING_FAILURE);
|
||||||
|
furi_string_left(current_path, last_char_pos);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(current_dir);
|
||||||
|
free(file_info);
|
||||||
|
furi_string_free(current_path);
|
||||||
|
|
||||||
|
return status == FR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
FS_Error sd_unmount_card(StorageData* storage) {
|
FS_Error sd_unmount_card(StorageData* storage) {
|
||||||
SDData* sd_data = storage->data;
|
SDData* sd_data = storage->data;
|
||||||
SDError error;
|
SDError error;
|
||||||
@@ -112,21 +173,32 @@ FS_Error sd_mount_card(StorageData* storage, bool notify) {
|
|||||||
|
|
||||||
if(storage->status != StorageStatusOK) {
|
if(storage->status != StorageStatusOK) {
|
||||||
FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
|
FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
|
||||||
if(notify) {
|
|
||||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
|
||||||
sd_notify_error(notification);
|
|
||||||
furi_record_close(RECORD_NOTIFICATION);
|
|
||||||
}
|
|
||||||
error = FSE_INTERNAL;
|
error = FSE_INTERNAL;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
FURI_LOG_I(TAG, "card mounted");
|
FURI_LOG_I(TAG, "card mounted");
|
||||||
if(notify) {
|
|
||||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
|
||||||
sd_notify_success(notification);
|
|
||||||
furi_record_close(RECORD_NOTIFICATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#ifndef FURI_RAM_EXEC
|
||||||
|
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
|
||||||
|
FURI_LOG_I(TAG, "deleting internal storage directory");
|
||||||
|
error = sd_remove_recursive(STORAGE_INTERNAL_DIR_NAME) ? FSE_OK : FSE_INTERNAL;
|
||||||
|
} else {
|
||||||
|
error = FSE_OK;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
UNUSED(sd_remove_recursive);
|
||||||
error = FSE_OK;
|
error = FSE_OK;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(notify) {
|
||||||
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
if(error != FSE_OK) {
|
||||||
|
sd_notify_error(notification);
|
||||||
|
} else {
|
||||||
|
sd_notify_success(notification);
|
||||||
|
}
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
@@ -654,4 +726,8 @@ void storage_ext_init(StorageData* storage) {
|
|||||||
|
|
||||||
// do not notify on first launch, notifications app is waiting for our thread to read settings
|
// do not notify on first launch, notifications app is waiting for our thread to read settings
|
||||||
storage_ext_tick_internal(storage, false);
|
storage_ext_tick_internal(storage, false);
|
||||||
|
#ifndef FURI_RAM_EXEC
|
||||||
|
// always reset the flag to prevent accidental wipe on SD card insertion
|
||||||
|
furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
#include <desktop/desktop.h>
|
#include <desktop/desktop.h>
|
||||||
#include <desktop/views/desktop_view_pin_input.h>
|
#include <desktop/views/desktop_view_pin_input.h>
|
||||||
|
|
||||||
|
#include <desktop/desktop.h>
|
||||||
|
#include <desktop/views/desktop_view_pin_input.h>
|
||||||
|
|
||||||
#include "desktop_settings_app.h"
|
#include "desktop_settings_app.h"
|
||||||
#include "scenes/desktop_settings_scene.h"
|
#include "scenes/desktop_settings_scene.h"
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 112 B After Width: | Height: | Size: 100 B |
BIN
applications/system/hid_app/assets/Alt_active_17x9.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 93 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 111 B |
|
Before Width: | Height: | Size: 657 B After Width: | Height: | Size: 113 B |
|
Before Width: | Height: | Size: 102 B After Width: | Height: | Size: 73 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 102 B After Width: | Height: | Size: 73 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 102 B |
BIN
applications/system/hid_app/assets/Cmd_active_17x9.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 102 B |
BIN
applications/system/hid_app/assets/Ctrl_active_17x9.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 101 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 448 B |
BIN
applications/system/hid_app/assets/Enter_11x7.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 117 B After Width: | Height: | Size: 106 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 90 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 89 B |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 113 B After Width: | Height: | Size: 84 B |
|
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 83 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 116 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 112 B |
|
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 101 B |
BIN
applications/system/hid_app/assets/Tab_19x12.png
Executable file → Normal file
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 77 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 77 B |
|
Before Width: | Height: | Size: 94 B After Width: | Height: | Size: 85 B |
BIN
applications/system/hid_app/assets/backslash_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 90 B |
BIN
applications/system/hid_app/assets/backspace_19x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 105 B |
BIN
applications/system/hid_app/assets/backspace_hovered_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 94 B |
BIN
applications/system/hid_app/assets/backtick_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 84 B |
BIN
applications/system/hid_app/assets/brace_left_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 92 B |
BIN
applications/system/hid_app/assets/brace_right_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 92 B |
BIN
applications/system/hid_app/assets/equals_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 80 B |
BIN
applications/system/hid_app/assets/hash_button_9x11.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 88 B |