mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-12 16:18:35 -07:00
Merge remote-tracking branch 'upstream/dev' into shutdown_idle
This commit is contained in:
13
applications/services/application.fam
Normal file
13
applications/services/application.fam
Normal file
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="basic_services",
|
||||
name="Basic services",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"crypto_start",
|
||||
"rpc_start",
|
||||
"bt",
|
||||
"desktop",
|
||||
"loader",
|
||||
"power",
|
||||
],
|
||||
)
|
||||
72
applications/services/applications.h
Normal file
72
applications/services/applications.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/icon.h>
|
||||
|
||||
typedef enum {
|
||||
FlipperApplicationFlagDefault = 0,
|
||||
FlipperApplicationFlagInsomniaSafe = (1 << 0),
|
||||
} FlipperApplicationFlag;
|
||||
|
||||
typedef struct {
|
||||
const FuriThreadCallback app;
|
||||
const char* name;
|
||||
const size_t stack_size;
|
||||
const Icon* icon;
|
||||
const FlipperApplicationFlag flags;
|
||||
} FlipperApplication;
|
||||
|
||||
typedef void (*FlipperOnStartHook)(void);
|
||||
|
||||
extern const char* FLIPPER_AUTORUN_APP_NAME;
|
||||
|
||||
/* Services list
|
||||
* Spawned on startup
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_SERVICES[];
|
||||
extern const size_t FLIPPER_SERVICES_COUNT;
|
||||
|
||||
/* Apps list
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_APPS[];
|
||||
extern const size_t FLIPPER_APPS_COUNT;
|
||||
|
||||
/* On system start hooks
|
||||
* Called by loader, after OS initialization complete
|
||||
*/
|
||||
extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[];
|
||||
extern const size_t FLIPPER_ON_SYSTEM_START_COUNT;
|
||||
|
||||
/* Plugins list
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_PLUGINS[];
|
||||
extern const size_t FLIPPER_PLUGINS_COUNT;
|
||||
|
||||
/* Debug menu apps
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_DEBUG_APPS[];
|
||||
extern const size_t FLIPPER_DEBUG_APPS_COUNT;
|
||||
|
||||
/* System apps
|
||||
* Can only be spawned by loader by name
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_SYSTEM_APPS[];
|
||||
extern const size_t FLIPPER_SYSTEM_APPS_COUNT;
|
||||
|
||||
/* Seperate scene app holder
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_SCENE;
|
||||
extern const FlipperApplication FLIPPER_SCENE_APPS[];
|
||||
extern const size_t FLIPPER_SCENE_APPS_COUNT;
|
||||
|
||||
extern const FlipperApplication FLIPPER_ARCHIVE;
|
||||
|
||||
/* Settings list
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_SETTINGS_APPS[];
|
||||
extern const size_t FLIPPER_SETTINGS_APPS_COUNT;
|
||||
25
applications/services/bt/application.fam
Normal file
25
applications/services/bt/application.fam
Normal file
@@ -0,0 +1,25 @@
|
||||
App(
|
||||
appid="bt",
|
||||
name="BtSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="bt_srv",
|
||||
cdefines=["SRV_BT"],
|
||||
requires=[
|
||||
"cli",
|
||||
"dialogs",
|
||||
],
|
||||
provides=[
|
||||
"bt_start",
|
||||
"bt_settings",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
sdk_headers=["bt_service/bt.h"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="bt_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="bt_on_system_start",
|
||||
order=70,
|
||||
)
|
||||
237
applications/services/bt/bt_cli.c
Normal file
237
applications/services/bt/bt_cli.c
Normal file
@@ -0,0 +1,237 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include "bt_settings.h"
|
||||
#include "bt_service/bt.h"
|
||||
|
||||
static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
string_t buffer;
|
||||
string_init(buffer);
|
||||
furi_hal_bt_dump_state(buffer);
|
||||
printf("%s", string_get_cstr(buffer));
|
||||
string_clear(buffer);
|
||||
}
|
||||
|
||||
static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int power = 0;
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
|
||||
printf("Incorrect or missing channel, expected int 0-39");
|
||||
break;
|
||||
}
|
||||
if(!args_read_int_and_trim(args, &power) && (power < 0 || power > 6)) {
|
||||
printf("Incorrect or missing power, expected int 0-6");
|
||||
break;
|
||||
}
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
furi_hal_bt_reinit();
|
||||
printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_tone_tx(channel, 0x19 + power);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
furi_hal_bt_stop_tone_tx();
|
||||
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close(RECORD_BT);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
|
||||
printf("Incorrect or missing channel, expected int 0-39");
|
||||
break;
|
||||
}
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
furi_hal_bt_reinit();
|
||||
printf("Receiving carrier at %d channel\r\n", channel);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
|
||||
furi_hal_bt_start_packet_rx(channel, 1);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
furi_hal_bt_stop_packet_test();
|
||||
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close(RECORD_BT);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int pattern = 0;
|
||||
int datarate = 1;
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
|
||||
printf("Incorrect or missing channel, expected int 0-39");
|
||||
break;
|
||||
}
|
||||
if(!args_read_int_and_trim(args, &pattern) && (pattern < 0 || pattern > 5)) {
|
||||
printf("Incorrect or missing pattern, expected int 0-5 \r\n");
|
||||
printf("0 - Pseudo-Random bit sequence 9\r\n");
|
||||
printf("1 - Pattern of alternating bits '11110000'\r\n");
|
||||
printf("2 - Pattern of alternating bits '10101010'\r\n");
|
||||
printf("3 - Pseudo-Random bit sequence 15\r\n");
|
||||
printf("4 - Pattern of All '1' bits\r\n");
|
||||
printf("5 - Pattern of All '0' bits\r\n");
|
||||
break;
|
||||
}
|
||||
if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) {
|
||||
printf("Incorrect or missing datarate, expected int 1-2");
|
||||
break;
|
||||
}
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
furi_hal_bt_reinit();
|
||||
printf(
|
||||
"Transmitting %d pattern packet at %d channel at %d M datarate\r\n",
|
||||
pattern,
|
||||
channel,
|
||||
datarate);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_packet_tx(channel, pattern, datarate);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
furi_hal_bt_stop_packet_test();
|
||||
printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets());
|
||||
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close(RECORD_BT);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int datarate = 1;
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
|
||||
printf("Incorrect or missing channel, expected int 0-39");
|
||||
break;
|
||||
}
|
||||
if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) {
|
||||
printf("Incorrect or missing datarate, expected int 1-2");
|
||||
break;
|
||||
}
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_disconnect(bt);
|
||||
furi_hal_bt_reinit();
|
||||
printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_packet_rx(channel, datarate);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi());
|
||||
fflush(stdout);
|
||||
}
|
||||
uint16_t packets_received = furi_hal_bt_stop_packet_test();
|
||||
printf("Received %hu packets", packets_received);
|
||||
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close(RECORD_BT);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("bt <cmd> <args>\r\n");
|
||||
printf("Cmd list:\r\n");
|
||||
printf("\thci_info\t - HCI info\r\n");
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
|
||||
printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n");
|
||||
printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n");
|
||||
printf(
|
||||
"\ttx_packet <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n");
|
||||
printf("\trx_packet <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_cli(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
furi_record_open(RECORD_BT);
|
||||
|
||||
string_t cmd;
|
||||
string_init(cmd);
|
||||
BtSettings bt_settings;
|
||||
bt_settings_load(&bt_settings);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, cmd)) {
|
||||
bt_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
if(string_cmp_str(cmd, "hci_info") == 0) {
|
||||
bt_cli_command_hci_info(cli, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
|
||||
if(string_cmp_str(cmd, "tx_carrier") == 0) {
|
||||
bt_cli_command_carrier_tx(cli, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(string_cmp_str(cmd, "rx_carrier") == 0) {
|
||||
bt_cli_command_carrier_rx(cli, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(string_cmp_str(cmd, "tx_packet") == 0) {
|
||||
bt_cli_command_packet_tx(cli, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(string_cmp_str(cmd, "rx_packet") == 0) {
|
||||
bt_cli_command_packet_rx(cli, args, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bt_cli_print_usage();
|
||||
} while(false);
|
||||
|
||||
if(bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
string_clear(cmd);
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
|
||||
void bt_on_system_start() {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(bt_cli);
|
||||
#endif
|
||||
}
|
||||
417
applications/services/bt/bt_service/bt.c
Normal file
417
applications/services/bt/bt_service/bt.c
Normal file
@@ -0,0 +1,417 @@
|
||||
#include "bt_i.h"
|
||||
#include "battery_service.h"
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#define TAG "BtSrv"
|
||||
|
||||
#define BT_RPC_EVENT_BUFF_SENT (1UL << 0)
|
||||
#define BT_RPC_EVENT_DISCONNECTED (1UL << 1)
|
||||
#define BT_RPC_EVENT_ALL (BT_RPC_EVENT_BUFF_SENT | BT_RPC_EVENT_DISCONNECTED)
|
||||
|
||||
static void bt_draw_statusbar_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Bt* bt = context;
|
||||
if(bt->status == BtStatusAdvertising) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Idle_5x8);
|
||||
} else if(bt->status == BtStatusConnected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Connected_16x8);
|
||||
}
|
||||
}
|
||||
|
||||
static ViewPort* bt_statusbar_view_port_alloc(Bt* bt) {
|
||||
ViewPort* statusbar_view_port = view_port_alloc();
|
||||
view_port_set_width(statusbar_view_port, 5);
|
||||
view_port_draw_callback_set(statusbar_view_port, bt_draw_statusbar_callback, bt);
|
||||
view_port_enabled_set(statusbar_view_port, false);
|
||||
return statusbar_view_port;
|
||||
}
|
||||
|
||||
static void bt_pin_code_view_port_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
char pin_code_info[24];
|
||||
canvas_draw_icon(canvas, 0, 0, &I_BLE_Pairing_128x64);
|
||||
snprintf(pin_code_info, sizeof(pin_code_info), "Pairing code\n%06ld", bt->pin_code);
|
||||
elements_multiline_text_aligned(canvas, 64, 4, AlignCenter, AlignTop, pin_code_info);
|
||||
elements_button_left(canvas, "Quit");
|
||||
}
|
||||
|
||||
static void bt_pin_code_view_port_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyLeft || event->key == InputKeyBack) {
|
||||
view_port_enabled_set(bt->pin_code_view_port, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) {
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, bt_pin_code_view_port_draw_callback, bt);
|
||||
view_port_input_callback_set(view_port, bt_pin_code_view_port_input_callback, bt);
|
||||
view_port_enabled_set(view_port, false);
|
||||
return view_port;
|
||||
}
|
||||
|
||||
static void bt_pin_code_show(Bt* bt, uint32_t pin_code) {
|
||||
bt->pin_code = pin_code;
|
||||
notification_message(bt->notification, &sequence_display_backlight_on);
|
||||
gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port);
|
||||
view_port_enabled_set(bt->pin_code_view_port, true);
|
||||
}
|
||||
|
||||
static void bt_pin_code_hide(Bt* bt) {
|
||||
bt->pin_code = 0;
|
||||
if(view_port_is_enabled(bt->pin_code_view_port)) {
|
||||
view_port_enabled_set(bt->pin_code_view_port, false);
|
||||
}
|
||||
}
|
||||
|
||||
static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
notification_message(bt->notification, &sequence_display_backlight_on);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Verify code\n%06d", pin);
|
||||
dialog_message_set_text(
|
||||
bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop);
|
||||
dialog_message_set_buttons(bt->dialog_message, "Cancel", "OK", NULL);
|
||||
DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message);
|
||||
string_clear(pin_str);
|
||||
return button == DialogMessageButtonCenter;
|
||||
}
|
||||
|
||||
static void bt_battery_level_changed_callback(const void* _event, void* context) {
|
||||
furi_assert(_event);
|
||||
furi_assert(context);
|
||||
|
||||
Bt* bt = context;
|
||||
BtMessage message = {};
|
||||
const PowerEvent* event = _event;
|
||||
if(event->type == PowerEventTypeBatteryLevelChanged) {
|
||||
message.type = BtMessageTypeUpdateBatteryLevel;
|
||||
message.data.battery_level = event->data.battery_level;
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
} else if(
|
||||
event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged ||
|
||||
event->type == PowerEventTypeStopCharging) {
|
||||
message.type = BtMessageTypeUpdatePowerState;
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
Bt* bt_alloc() {
|
||||
Bt* bt = malloc(sizeof(Bt));
|
||||
// Init default maximum packet size
|
||||
bt->max_packet_size = FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX;
|
||||
bt->profile = BtProfileSerial;
|
||||
// Load settings
|
||||
if(!bt_settings_load(&bt->bt_settings)) {
|
||||
bt_settings_save(&bt->bt_settings);
|
||||
}
|
||||
// Alloc queue
|
||||
bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage));
|
||||
|
||||
// Setup statusbar view port
|
||||
bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt);
|
||||
// Pin code view port
|
||||
bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt);
|
||||
// Notification
|
||||
bt->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
// Gui
|
||||
bt->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft);
|
||||
gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen);
|
||||
|
||||
// Dialogs
|
||||
bt->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
bt->dialog_message = dialog_message_alloc();
|
||||
|
||||
// Power
|
||||
bt->power = furi_record_open(RECORD_POWER);
|
||||
FuriPubSub* power_pubsub = power_get_pubsub(bt->power);
|
||||
furi_pubsub_subscribe(power_pubsub, bt_battery_level_changed_callback, bt);
|
||||
|
||||
// RPC
|
||||
bt->rpc = furi_record_open(RECORD_RPC);
|
||||
bt->rpc_event = furi_event_flag_alloc();
|
||||
|
||||
// API evnent
|
||||
bt->api_event = furi_event_flag_alloc();
|
||||
|
||||
return bt;
|
||||
}
|
||||
|
||||
// Called from GAP thread from Serial service
|
||||
static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
uint16_t ret = 0;
|
||||
|
||||
if(event.event == SerialServiceEventTypeDataReceived) {
|
||||
size_t bytes_processed =
|
||||
rpc_session_feed(bt->rpc_session, event.data.buffer, event.data.size, 1000);
|
||||
if(bytes_processed != event.data.size) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Only %d of %d bytes processed by RPC", bytes_processed, event.data.size);
|
||||
}
|
||||
ret = rpc_session_get_available_size(bt->rpc_session);
|
||||
} else if(event.event == SerialServiceEventTypeDataSent) {
|
||||
furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Called from RPC thread
|
||||
static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
|
||||
if(furi_event_flag_get(bt->rpc_event) & BT_RPC_EVENT_DISCONNECTED) {
|
||||
// Early stop from sending if we're already disconnected
|
||||
return;
|
||||
}
|
||||
furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED));
|
||||
size_t bytes_sent = 0;
|
||||
while(bytes_sent < bytes_len) {
|
||||
size_t bytes_remain = bytes_len - bytes_sent;
|
||||
if(bytes_remain > bt->max_packet_size) {
|
||||
furi_hal_bt_serial_tx(&bytes[bytes_sent], bt->max_packet_size);
|
||||
bytes_sent += bt->max_packet_size;
|
||||
} else {
|
||||
furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain);
|
||||
bytes_sent += bytes_remain;
|
||||
}
|
||||
// We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear
|
||||
uint32_t event_flag = furi_event_flag_wait(
|
||||
bt->rpc_event, BT_RPC_EVENT_ALL, FuriFlagWaitAny | FuriFlagNoClear, FuriWaitForever);
|
||||
if(event_flag & BT_RPC_EVENT_DISCONNECTED) {
|
||||
break;
|
||||
} else {
|
||||
// If we didn't get BT_RPC_EVENT_DISCONNECTED, then clear everything else
|
||||
furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_ALL & (~BT_RPC_EVENT_DISCONNECTED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called from GAP thread
|
||||
static bool bt_on_gap_event_callback(GapEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
bool ret = false;
|
||||
|
||||
if(event.type == GapEventTypeConnected) {
|
||||
// Update status bar
|
||||
bt->status = BtStatusConnected;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatus};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session
|
||||
furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
|
||||
if(bt->profile == BtProfileSerial) {
|
||||
// Open RPC session
|
||||
bt->rpc_session = rpc_session_open(bt->rpc);
|
||||
if(bt->rpc_session) {
|
||||
FURI_LOG_I(TAG, "Open RPC connection");
|
||||
rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback);
|
||||
rpc_session_set_buffer_is_empty_callback(
|
||||
bt->rpc_session, furi_hal_bt_serial_notify_buffer_is_empty);
|
||||
rpc_session_set_context(bt->rpc_session, bt);
|
||||
furi_hal_bt_serial_set_event_callback(
|
||||
RPC_BUFFER_SIZE, bt_serial_event_callback, bt);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "RPC is busy, failed to open new session");
|
||||
}
|
||||
}
|
||||
// Update battery level
|
||||
PowerInfo info;
|
||||
power_get_info(bt->power, &info);
|
||||
message.type = BtMessageTypeUpdateBatteryLevel;
|
||||
message.data.battery_level = info.charge;
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
ret = true;
|
||||
} else if(event.type == GapEventTypeDisconnected) {
|
||||
if(bt->profile == BtProfileSerial && bt->rpc_session) {
|
||||
FURI_LOG_I(TAG, "Close RPC connection");
|
||||
furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
|
||||
rpc_session_close(bt->rpc_session);
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
bt->rpc_session = NULL;
|
||||
}
|
||||
ret = true;
|
||||
} else if(event.type == GapEventTypeStartAdvertising) {
|
||||
bt->status = BtStatusAdvertising;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatus};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
ret = true;
|
||||
} else if(event.type == GapEventTypeStopAdvertising) {
|
||||
bt->status = BtStatusOff;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatus};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
ret = true;
|
||||
} else if(event.type == GapEventTypePinCodeShow) {
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
ret = true;
|
||||
} else if(event.type == GapEventTypePinCodeVerify) {
|
||||
ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code);
|
||||
} else if(event.type == GapEventTypeUpdateMTU) {
|
||||
bt->max_packet_size = event.data.max_packet_size;
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
FURI_LOG_I(TAG, "Changed addr start: %08lX, size changed: %d", addr, size);
|
||||
BtMessage message = {.type = BtMessageTypeKeysStorageUpdated};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static void bt_statusbar_update(Bt* bt) {
|
||||
if(bt->status == BtStatusAdvertising) {
|
||||
view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Idle_5x8));
|
||||
view_port_enabled_set(bt->statusbar_view_port, true);
|
||||
} else if(bt->status == BtStatusConnected) {
|
||||
view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Connected_16x8));
|
||||
view_port_enabled_set(bt->statusbar_view_port, true);
|
||||
} else {
|
||||
view_port_enabled_set(bt->statusbar_view_port, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_show_warning(Bt* bt, const char* text) {
|
||||
dialog_message_set_text(bt->dialog_message, text, 64, 28, AlignCenter, AlignCenter);
|
||||
dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL);
|
||||
dialog_message_show(bt->dialogs, bt->dialog_message);
|
||||
}
|
||||
|
||||
static void bt_close_rpc_connection(Bt* bt) {
|
||||
if(bt->profile == BtProfileSerial && bt->rpc_session) {
|
||||
FURI_LOG_I(TAG, "Close RPC connection");
|
||||
furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
|
||||
rpc_session_close(bt->rpc_session);
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
bt->rpc_session = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
if(furi_hal_bt_is_ble_gatt_gap_supported()) {
|
||||
bt_settings_load(&bt->bt_settings);
|
||||
bt_close_rpc_connection(bt);
|
||||
|
||||
FuriHalBtProfile furi_profile;
|
||||
if(message->data.profile == BtProfileHidKeyboard) {
|
||||
furi_profile = FuriHalBtProfileHidKeyboard;
|
||||
} else {
|
||||
furi_profile = FuriHalBtProfileSerial;
|
||||
}
|
||||
|
||||
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_I(TAG, "Bt App started");
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
bt->profile = message->data.profile;
|
||||
*message->result = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to start Bt App");
|
||||
*message->result = false;
|
||||
}
|
||||
} else {
|
||||
bt_show_warning(bt, "Radio stack doesn't support this app");
|
||||
*message->result = false;
|
||||
}
|
||||
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
}
|
||||
|
||||
static void bt_close_connection(Bt* bt) {
|
||||
bt_close_rpc_connection(bt);
|
||||
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
}
|
||||
|
||||
int32_t bt_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Bt* bt = bt_alloc();
|
||||
|
||||
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
|
||||
FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode");
|
||||
ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT);
|
||||
furi_record_create(RECORD_BT, bt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read keys
|
||||
if(!bt_keys_storage_load(bt)) {
|
||||
FURI_LOG_W(TAG, "Failed to load bonding keys");
|
||||
}
|
||||
|
||||
// Start radio stack
|
||||
if(!furi_hal_bt_start_radio_stack()) {
|
||||
FURI_LOG_E(TAG, "Radio stack start failed");
|
||||
}
|
||||
|
||||
if(furi_hal_bt_is_ble_gatt_gap_supported()) {
|
||||
if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_E(TAG, "BLE App start failed");
|
||||
} else {
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
}
|
||||
} else {
|
||||
bt_show_warning(bt, "Unsupported radio stack");
|
||||
bt->status = BtStatusUnavailable;
|
||||
}
|
||||
|
||||
furi_record_create(RECORD_BT, bt);
|
||||
|
||||
BtMessage message;
|
||||
while(1) {
|
||||
furi_check(
|
||||
furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
if(message.type == BtMessageTypeUpdateStatus) {
|
||||
// Update view ports
|
||||
bt_statusbar_update(bt);
|
||||
bt_pin_code_hide(bt);
|
||||
if(bt->status_changed_cb) {
|
||||
bt->status_changed_cb(bt->status, bt->status_changed_ctx);
|
||||
}
|
||||
} else if(message.type == BtMessageTypeUpdateBatteryLevel) {
|
||||
// Update battery level
|
||||
furi_hal_bt_update_battery_level(message.data.battery_level);
|
||||
} else if(message.type == BtMessageTypeUpdatePowerState) {
|
||||
furi_hal_bt_update_power_state();
|
||||
} else if(message.type == BtMessageTypePinCodeShow) {
|
||||
// Display PIN code
|
||||
bt_pin_code_show(bt, message.data.pin_code);
|
||||
} else if(message.type == BtMessageTypeKeysStorageUpdated) {
|
||||
bt_keys_storage_save(bt);
|
||||
} else if(message.type == BtMessageTypeSetProfile) {
|
||||
bt_change_profile(bt, &message);
|
||||
} else if(message.type == BtMessageTypeDisconnect) {
|
||||
bt_close_connection(bt);
|
||||
} else if(message.type == BtMessageTypeForgetBondedDevices) {
|
||||
bt_keys_storage_delete(bt);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
61
applications/services/bt/bt_service/bt.h
Normal file
61
applications/services/bt/bt_service/bt.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RECORD_BT "bt"
|
||||
|
||||
typedef struct Bt Bt;
|
||||
|
||||
typedef enum {
|
||||
BtStatusUnavailable,
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
BtStatusConnected,
|
||||
} BtStatus;
|
||||
|
||||
typedef enum {
|
||||
BtProfileSerial,
|
||||
BtProfileHidKeyboard,
|
||||
} BtProfile;
|
||||
|
||||
typedef void (*BtStatusChangedCallback)(BtStatus status, void* context);
|
||||
|
||||
/** Change BLE Profile
|
||||
* @note Call of this function leads to 2nd core restart
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param profile BtProfile
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile);
|
||||
|
||||
/** Disconnect from Central
|
||||
*
|
||||
* @param bt Bt instance
|
||||
*/
|
||||
void bt_disconnect(Bt* bt);
|
||||
|
||||
/** Set callback for Bluetooth status change notification
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param callback BtStatusChangedCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context);
|
||||
|
||||
/** Forget bonded devices
|
||||
* @note Leads to wipe ble key storage and deleting bt.keys
|
||||
*
|
||||
* @param bt Bt instance
|
||||
*/
|
||||
void bt_forget_bonded_devices(Bt* bt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
41
applications/services/bt/bt_service/bt_api.c
Normal file
41
applications/services/bt/bt_service/bt_api.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "bt_i.h"
|
||||
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile) {
|
||||
furi_assert(bt);
|
||||
|
||||
// Send message
|
||||
bool result = false;
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Wait for unlock
|
||||
furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void bt_disconnect(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
|
||||
// Send message
|
||||
BtMessage message = {.type = BtMessageTypeDisconnect};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
// Wait for unlock
|
||||
furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
}
|
||||
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
|
||||
furi_assert(bt);
|
||||
|
||||
bt->status_changed_cb = callback;
|
||||
bt->status_changed_ctx = context;
|
||||
}
|
||||
|
||||
void bt_forget_bonded_devices(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
BtMessage message = {.type = BtMessageTypeForgetBondedDevices};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
66
applications/services/bt/bt_service/bt_i.h
Normal file
66
applications/services/bt/bt_service/bt_i.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <notification/notification.h>
|
||||
|
||||
#include <bt/bt_settings.h>
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
BtMessageTypeUpdateStatus,
|
||||
BtMessageTypeUpdateBatteryLevel,
|
||||
BtMessageTypeUpdatePowerState,
|
||||
BtMessageTypePinCodeShow,
|
||||
BtMessageTypeKeysStorageUpdated,
|
||||
BtMessageTypeSetProfile,
|
||||
BtMessageTypeDisconnect,
|
||||
BtMessageTypeForgetBondedDevices,
|
||||
} BtMessageType;
|
||||
|
||||
typedef union {
|
||||
uint32_t pin_code;
|
||||
uint8_t battery_level;
|
||||
BtProfile profile;
|
||||
} BtMessageData;
|
||||
|
||||
typedef struct {
|
||||
BtMessageType type;
|
||||
BtMessageData data;
|
||||
bool* result;
|
||||
} BtMessage;
|
||||
|
||||
struct Bt {
|
||||
uint8_t* bt_keys_addr_start;
|
||||
uint16_t bt_keys_size;
|
||||
uint16_t max_packet_size;
|
||||
BtSettings bt_settings;
|
||||
BtStatus status;
|
||||
BtProfile profile;
|
||||
FuriMessageQueue* message_queue;
|
||||
NotificationApp* notification;
|
||||
Gui* gui;
|
||||
ViewPort* statusbar_view_port;
|
||||
ViewPort* pin_code_view_port;
|
||||
uint32_t pin_code;
|
||||
DialogsApp* dialogs;
|
||||
DialogMessage* dialog_message;
|
||||
Power* power;
|
||||
Rpc* rpc;
|
||||
RpcSession* rpc_session;
|
||||
FuriEventFlag* rpc_event;
|
||||
FuriEventFlag* api_event;
|
||||
BtStatusChangedCallback status_changed_cb;
|
||||
void* status_changed_ctx;
|
||||
};
|
||||
3
applications/services/bt/bt_service/bt_keys_filename.h
Normal file
3
applications/services/bt/bt_service/bt_keys_filename.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define BT_KEYS_STORAGE_FILE_NAME ".bt.keys"
|
||||
57
applications/services/bt/bt_service/bt_keys_storage.c
Normal file
57
applications/services/bt/bt_service/bt_keys_storage.c
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
|
||||
#define BT_KEYS_STORAGE_VERSION (0)
|
||||
#define BT_KEYS_STORAGE_MAGIC (0x18)
|
||||
|
||||
bool bt_keys_storage_load(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
bool file_loaded = false;
|
||||
|
||||
furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size);
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
file_loaded = saved_struct_load(
|
||||
BT_KEYS_STORAGE_PATH,
|
||||
bt->bt_keys_addr_start,
|
||||
bt->bt_keys_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
return file_loaded;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_save(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
furi_assert(bt->bt_keys_addr_start);
|
||||
bool file_saved = false;
|
||||
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
file_saved = saved_struct_save(
|
||||
BT_KEYS_STORAGE_PATH,
|
||||
bt->bt_keys_addr_start,
|
||||
bt->bt_keys_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
return file_saved;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_delete(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
bool delete_succeed = false;
|
||||
bool bt_is_active = furi_hal_bt_is_active();
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
delete_succeed = furi_hal_bt_clear_white_list();
|
||||
if(bt_is_active) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
return delete_succeed;
|
||||
}
|
||||
10
applications/services/bt/bt_service/bt_keys_storage.h
Normal file
10
applications/services/bt/bt_service/bt_keys_storage.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt_i.h"
|
||||
#include "bt_keys_filename.h"
|
||||
|
||||
bool bt_keys_storage_load(Bt* bt);
|
||||
|
||||
bool bt_keys_storage_save(Bt* bt);
|
||||
|
||||
bool bt_keys_storage_delete(Bt* bt);
|
||||
23
applications/services/bt/bt_settings.c
Normal file
23
applications/services/bt/bt_settings.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "bt_settings.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define BT_SETTINGS_PATH INT_PATH(BT_SETTINGS_FILE_NAME)
|
||||
#define BT_SETTINGS_VERSION (0)
|
||||
#define BT_SETTINGS_MAGIC (0x19)
|
||||
|
||||
bool bt_settings_load(BtSettings* bt_settings) {
|
||||
furi_assert(bt_settings);
|
||||
|
||||
return saved_struct_load(
|
||||
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
||||
}
|
||||
|
||||
bool bt_settings_save(BtSettings* bt_settings) {
|
||||
furi_assert(bt_settings);
|
||||
|
||||
return saved_struct_save(
|
||||
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
||||
}
|
||||
14
applications/services/bt/bt_settings.h
Normal file
14
applications/services/bt/bt_settings.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt_settings_filename.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
} BtSettings;
|
||||
|
||||
bool bt_settings_load(BtSettings* bt_settings);
|
||||
|
||||
bool bt_settings_save(BtSettings* bt_settings);
|
||||
3
applications/services/bt/bt_settings_filename.h
Normal file
3
applications/services/bt/bt_settings_filename.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define BT_SETTINGS_FILE_NAME ".bt.settings"
|
||||
10
applications/services/cli/application.fam
Normal file
10
applications/services/cli/application.fam
Normal file
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="cli",
|
||||
name="CliSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="cli_srv",
|
||||
cdefines=["SRV_CLI"],
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
sdk_headers=["cli.h", "cli_vcp.h"],
|
||||
)
|
||||
492
applications/services/cli/cli.c
Normal file
492
applications/services/cli/cli.c
Normal file
@@ -0,0 +1,492 @@
|
||||
#include "cli_i.h"
|
||||
#include "cli_commands.h"
|
||||
#include "cli_vcp.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#define TAG "CliSrv"
|
||||
|
||||
Cli* cli_alloc() {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
|
||||
CliCommandTree_init(cli->commands);
|
||||
|
||||
string_init(cli->last_line);
|
||||
string_init(cli->line);
|
||||
|
||||
cli->session = NULL;
|
||||
|
||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_check(cli->mutex);
|
||||
|
||||
cli->idle_sem = furi_semaphore_alloc(1, 0);
|
||||
|
||||
return cli;
|
||||
}
|
||||
|
||||
void cli_putc(Cli* cli, char c) {
|
||||
furi_assert(cli);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->tx((uint8_t*)&c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
char cli_getc(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
char c = 0;
|
||||
if(cli->session != NULL) {
|
||||
if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) {
|
||||
cli_reset(cli);
|
||||
}
|
||||
} else {
|
||||
cli_reset(cli);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void cli_write(Cli* cli, const uint8_t* buffer, size_t size) {
|
||||
furi_assert(cli);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->tx(buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) {
|
||||
furi_assert(cli);
|
||||
if(cli->session != NULL) {
|
||||
return cli->session->rx(buffer, size, FuriWaitForever);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) {
|
||||
furi_assert(cli);
|
||||
if(cli->session != NULL) {
|
||||
return cli->session->rx(buffer, size, timeout);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool cli_is_connected(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
if(cli->session != NULL) {
|
||||
return (cli->session->is_connected());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cli_cmd_interrupt_received(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
char c = '\0';
|
||||
if(cli_is_connected(cli)) {
|
||||
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
|
||||
return c == CliSymbolAsciiETX;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
furi_assert(cmd);
|
||||
furi_assert(arg);
|
||||
furi_assert(usage);
|
||||
|
||||
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
|
||||
}
|
||||
|
||||
void cli_motd() {
|
||||
printf("\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
|
||||
" | | | 0 | | .-' ,/` /\r\n"
|
||||
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
|
||||
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
|
||||
" | `-...| -.___-Z:_______J...---;\r\n"
|
||||
" : ` _-'\r\n"
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n"
|
||||
"Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"Read Manual https://docs.flipperzero.one\r\n"
|
||||
"\r\n");
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf(
|
||||
"Firmware version: %s %s (%s%s built on %s)\r\n",
|
||||
version_get_gitbranch(firmware_version),
|
||||
version_get_version(firmware_version),
|
||||
version_get_githash(firmware_version),
|
||||
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
|
||||
version_get_builddate(firmware_version));
|
||||
}
|
||||
}
|
||||
|
||||
void cli_nl(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
void cli_prompt(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
printf("\r\n>: %s", string_get_cstr(cli->line));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_reset(Cli* cli) {
|
||||
// cli->last_line is cleared and cli->line's buffer moved to cli->last_line
|
||||
string_move(cli->last_line, cli->line);
|
||||
// Reiniting cli->line
|
||||
string_init(cli->line);
|
||||
cli->cursor_position = 0;
|
||||
}
|
||||
|
||||
static void cli_handle_backspace(Cli* cli) {
|
||||
if(cli->cursor_position > 0) {
|
||||
furi_assert(string_size(cli->line) > 0);
|
||||
// Other side
|
||||
printf("\e[D\e[1P");
|
||||
fflush(stdout);
|
||||
// Our side
|
||||
string_t temp;
|
||||
string_init(temp);
|
||||
string_reserve(temp, string_size(cli->line) - 1);
|
||||
string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position - 1);
|
||||
string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position);
|
||||
|
||||
// cli->line is cleared and temp's buffer moved to cli->line
|
||||
string_move(cli->line, temp);
|
||||
// NO MEMORY LEAK, STOP REPORTING IT
|
||||
|
||||
cli->cursor_position--;
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_normalize_line(Cli* cli) {
|
||||
string_strim(cli->line);
|
||||
cli->cursor_position = string_size(cli->line);
|
||||
}
|
||||
|
||||
static void cli_execute_command(Cli* cli, CliCommand* command, string_t args) {
|
||||
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
}
|
||||
|
||||
// Ensure that we running alone
|
||||
if(!(command->flags & CliCommandFlagParallelSafe)) {
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
bool safety_lock = loader_lock(loader);
|
||||
if(safety_lock) {
|
||||
// Execute command
|
||||
command->callback(cli, args, command->context);
|
||||
loader_unlock(loader);
|
||||
} else {
|
||||
printf("Other application is running, close it first");
|
||||
}
|
||||
furi_record_close(RECORD_LOADER);
|
||||
} else {
|
||||
// Execute command
|
||||
command->callback(cli, args, command->context);
|
||||
}
|
||||
|
||||
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_handle_enter(Cli* cli) {
|
||||
cli_normalize_line(cli);
|
||||
|
||||
if(string_size(cli->line) == 0) {
|
||||
cli_prompt(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
// Command and args container
|
||||
string_t command;
|
||||
string_init(command);
|
||||
string_t args;
|
||||
string_init(args);
|
||||
|
||||
// Split command and args
|
||||
size_t ws = string_search_char(cli->line, ' ');
|
||||
if(ws == STRING_FAILURE) {
|
||||
string_set(command, cli->line);
|
||||
} else {
|
||||
string_set_n(command, cli->line, 0, ws);
|
||||
string_set_n(args, cli->line, ws, string_size(cli->line));
|
||||
string_strim(args);
|
||||
}
|
||||
|
||||
// Search for command
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command);
|
||||
|
||||
if(cli_command_ptr) {
|
||||
CliCommand cli_command;
|
||||
memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand));
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
cli_nl(cli);
|
||||
cli_execute_command(cli, &cli_command, args);
|
||||
} else {
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
cli_nl(cli);
|
||||
printf(
|
||||
"`%s` command not found, use `help` or `?` to list all available commands",
|
||||
string_get_cstr(command));
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
|
||||
// Cleanup command and args
|
||||
string_clear(command);
|
||||
string_clear(args);
|
||||
}
|
||||
|
||||
static void cli_handle_autocomplete(Cli* cli) {
|
||||
cli_normalize_line(cli);
|
||||
|
||||
if(string_size(cli->line) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_nl(cli);
|
||||
|
||||
// Prepare common base for autocomplete
|
||||
string_t common;
|
||||
string_init(common);
|
||||
// Iterate throw commands
|
||||
for
|
||||
M_EACH(cli_command, cli->commands, CliCommandTree_t) {
|
||||
// Process only if starts with line buffer
|
||||
if(string_start_with_string_p(*cli_command->key_ptr, cli->line)) {
|
||||
// Show autocomplete option
|
||||
printf("%s\r\n", string_get_cstr(*cli_command->key_ptr));
|
||||
// Process common base for autocomplete
|
||||
if(string_size(common) > 0) {
|
||||
// Choose shortest string
|
||||
const size_t key_size = string_size(*cli_command->key_ptr);
|
||||
const size_t common_size = string_size(common);
|
||||
const size_t min_size = key_size > common_size ? common_size : key_size;
|
||||
size_t i = 0;
|
||||
while(i < min_size) {
|
||||
// Stop when do not match
|
||||
if(string_get_char(*cli_command->key_ptr, i) !=
|
||||
string_get_char(common, i)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Cut right part if any
|
||||
string_left(common, i);
|
||||
} else {
|
||||
// Start with something
|
||||
string_set(common, *cli_command->key_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace line buffer if autocomplete better
|
||||
if(string_size(common) > string_size(cli->line)) {
|
||||
string_set(cli->line, common);
|
||||
cli->cursor_position = string_size(cli->line);
|
||||
}
|
||||
// Cleanup
|
||||
string_clear(common);
|
||||
// Show prompt
|
||||
cli_prompt(cli);
|
||||
}
|
||||
|
||||
static void cli_handle_escape(Cli* cli, char c) {
|
||||
if(c == 'A') {
|
||||
// Use previous command if line buffer is empty
|
||||
if(string_size(cli->line) == 0 && string_cmp(cli->line, cli->last_line) != 0) {
|
||||
// Set line buffer and cursor position
|
||||
string_set(cli->line, cli->last_line);
|
||||
cli->cursor_position = string_size(cli->line);
|
||||
// Show new line to user
|
||||
printf("%s", string_get_cstr(cli->line));
|
||||
}
|
||||
} else if(c == 'B') {
|
||||
} else if(c == 'C') {
|
||||
if(cli->cursor_position < string_size(cli->line)) {
|
||||
cli->cursor_position++;
|
||||
printf("\e[C");
|
||||
}
|
||||
} else if(c == 'D') {
|
||||
if(cli->cursor_position > 0) {
|
||||
cli->cursor_position--;
|
||||
printf("\e[D");
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_process_input(Cli* cli) {
|
||||
char in_chr = cli_getc(cli);
|
||||
size_t rx_len;
|
||||
|
||||
if(in_chr == CliSymbolAsciiTab) {
|
||||
cli_handle_autocomplete(cli);
|
||||
} else if(in_chr == CliSymbolAsciiSOH) {
|
||||
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
|
||||
cli_motd();
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiETX) {
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEOT) {
|
||||
cli_reset(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEsc) {
|
||||
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
if((rx_len > 0) && (in_chr == '[')) {
|
||||
cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
cli_handle_escape(cli, in_chr);
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
|
||||
cli_handle_backspace(cli);
|
||||
} else if(in_chr == CliSymbolAsciiCR) {
|
||||
cli_handle_enter(cli);
|
||||
} else if(in_chr >= 0x20 && in_chr < 0x7F) {
|
||||
if(cli->cursor_position == string_size(cli->line)) {
|
||||
string_push_back(cli->line, in_chr);
|
||||
cli_putc(cli, in_chr);
|
||||
} else {
|
||||
// ToDo: better way?
|
||||
string_t temp;
|
||||
string_init(temp);
|
||||
string_reserve(temp, string_size(cli->line) + 1);
|
||||
string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position);
|
||||
string_push_back(temp, in_chr);
|
||||
string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position);
|
||||
|
||||
// cli->line is cleared and temp's buffer moved to cli->line
|
||||
string_move(cli->line, temp);
|
||||
// NO MEMORY LEAK, STOP REPORTING IT
|
||||
|
||||
// Print character in replace mode
|
||||
printf("\e[4h%c\e[4l", in_chr);
|
||||
fflush(stdout);
|
||||
}
|
||||
cli->cursor_position++;
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliCallback callback,
|
||||
void* context) {
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
string_strim(name_str);
|
||||
|
||||
size_t name_replace;
|
||||
do {
|
||||
name_replace = string_replace_str(name_str, " ", "_");
|
||||
} while(name_replace != STRING_FAILURE);
|
||||
|
||||
CliCommand c;
|
||||
c.callback = callback;
|
||||
c.context = context;
|
||||
c.flags = flags;
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_set_at(cli->commands, name_str, c);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
||||
|
||||
void cli_delete_command(Cli* cli, const char* name) {
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
string_strim(name_str);
|
||||
|
||||
size_t name_replace;
|
||||
do {
|
||||
name_replace = string_replace_str(name_str, " ", "_");
|
||||
} while(name_replace != STRING_FAILURE);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_erase(cli->commands, name_str);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
||||
|
||||
void cli_session_open(Cli* cli, void* session) {
|
||||
furi_assert(cli);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
cli->session = session;
|
||||
if(cli->session != NULL) {
|
||||
cli->session->init();
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
}
|
||||
furi_semaphore_release(cli->idle_sem);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_session_close(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->deinit();
|
||||
}
|
||||
cli->session = NULL;
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
int32_t cli_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Cli* cli = cli_alloc();
|
||||
|
||||
// Init basic cli commands
|
||||
cli_commands_init(cli);
|
||||
|
||||
furi_record_create(RECORD_CLI, cli);
|
||||
|
||||
if(cli->session != NULL) {
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Skipped CLI session open: device in special startup mode");
|
||||
}
|
||||
|
||||
while(1) {
|
||||
if(cli->session != NULL) {
|
||||
cli_process_input(cli);
|
||||
} else {
|
||||
furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
135
applications/services/cli/cli.h
Normal file
135
applications/services/cli/cli.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file cli.h
|
||||
* Cli API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <m-string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CliSymbolAsciiSOH = 0x01,
|
||||
CliSymbolAsciiETX = 0x03,
|
||||
CliSymbolAsciiEOT = 0x04,
|
||||
CliSymbolAsciiBell = 0x07,
|
||||
CliSymbolAsciiBackspace = 0x08,
|
||||
CliSymbolAsciiTab = 0x09,
|
||||
CliSymbolAsciiLF = 0x0A,
|
||||
CliSymbolAsciiCR = 0x0D,
|
||||
CliSymbolAsciiEsc = 0x1B,
|
||||
CliSymbolAsciiUS = 0x1F,
|
||||
CliSymbolAsciiSpace = 0x20,
|
||||
CliSymbolAsciiDel = 0x7F,
|
||||
} CliSymbols;
|
||||
|
||||
typedef enum {
|
||||
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
|
||||
CliCommandFlagParallelSafe =
|
||||
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
|
||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||
} CliCommandFlag;
|
||||
|
||||
#define RECORD_CLI "cli"
|
||||
|
||||
/** Cli type anonymous structure */
|
||||
typedef struct Cli Cli;
|
||||
|
||||
/** Cli callback function pointer. Implement this interface and use
|
||||
* add_cli_command
|
||||
* @param args string with what was passed after command
|
||||
* @param context pointer to whatever you gave us on cli_add_command
|
||||
*/
|
||||
typedef void (*CliCallback)(Cli* cli, string_t args, void* context);
|
||||
|
||||
/** Add cli command Registers you command callback
|
||||
*
|
||||
* @param cli pointer to cli instance
|
||||
* @param name command name
|
||||
* @param flags CliCommandFlag
|
||||
* @param callback callback function
|
||||
* @param context pointer to whatever we need to pass to callback
|
||||
*/
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Print unified cmd usage tip
|
||||
*
|
||||
* @param cmd cmd name
|
||||
* @param usage usage tip
|
||||
* @param arg arg passed by user
|
||||
*/
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
|
||||
|
||||
/** Delete cli command
|
||||
*
|
||||
* @param cli pointer to cli instance
|
||||
* @param name command name
|
||||
*/
|
||||
void cli_delete_command(Cli* cli, const char* name);
|
||||
|
||||
/** Read from terminal
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
*
|
||||
* @return bytes read
|
||||
*/
|
||||
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size);
|
||||
|
||||
/** Non-blocking read from terminal
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
* @param timeout timeout value in ms
|
||||
*
|
||||
* @return bytes read
|
||||
*/
|
||||
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
|
||||
/** Non-blocking check for interrupt command received
|
||||
*
|
||||
* @param cli Cli instance
|
||||
*
|
||||
* @return true if received
|
||||
*/
|
||||
bool cli_cmd_interrupt_received(Cli* cli);
|
||||
|
||||
/** Write to terminal Do it only from inside of cli call.
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
*/
|
||||
void cli_write(Cli* cli, const uint8_t* buffer, size_t size);
|
||||
|
||||
/** Read character
|
||||
*
|
||||
* @param cli Cli instance
|
||||
*
|
||||
* @return char
|
||||
*/
|
||||
char cli_getc(Cli* cli);
|
||||
|
||||
/** New line Send new ine sequence
|
||||
*/
|
||||
void cli_nl();
|
||||
|
||||
void cli_session_open(Cli* cli, void* session);
|
||||
|
||||
void cli_session_close(Cli* cli);
|
||||
|
||||
bool cli_is_connected(Cli* cli);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
228
applications/services/cli/cli_command_gpio.c
Normal file
228
applications/services/cli/cli_command_gpio.c
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "cli_command_gpio.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
|
||||
typedef struct {
|
||||
const GpioPin* pin;
|
||||
const char* name;
|
||||
const bool debug;
|
||||
} CliCommandGpio;
|
||||
|
||||
const CliCommandGpio cli_command_gpio_pins[] = {
|
||||
{.pin = &gpio_ext_pc0, .name = "PC0", .debug = false},
|
||||
{.pin = &gpio_ext_pc1, .name = "PC1", .debug = false},
|
||||
{.pin = &gpio_ext_pc3, .name = "PC3", .debug = false},
|
||||
{.pin = &gpio_ext_pb2, .name = "PB2", .debug = false},
|
||||
{.pin = &gpio_ext_pb3, .name = "PB3", .debug = false},
|
||||
{.pin = &gpio_ext_pa4, .name = "PA4", .debug = false},
|
||||
{.pin = &gpio_ext_pa6, .name = "PA6", .debug = false},
|
||||
{.pin = &gpio_ext_pa7, .name = "PA7", .debug = false},
|
||||
/* Dangerous pins, may damage hardware */
|
||||
{.pin = &gpio_infrared_rx, .name = "PA0", .debug = true},
|
||||
{.pin = &gpio_usart_rx, .name = "PB7", .debug = true},
|
||||
{.pin = &gpio_speaker, .name = "PB8", .debug = true},
|
||||
{.pin = &gpio_infrared_tx, .name = "PB9", .debug = true},
|
||||
};
|
||||
|
||||
void cli_command_gpio_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("gpio <cmd> <args>\r\n");
|
||||
printf("Cmd list:\r\n");
|
||||
printf("\tmode <pin_name> <0|1>\t - Set gpio mode: 0 - input, 1 - output\r\n");
|
||||
printf("\tset <pin_name> <0|1>\t - Set gpio value\r\n");
|
||||
printf("\tread <pin_name>\t - Read gpio value\r\n");
|
||||
}
|
||||
|
||||
static bool pin_name_to_int(string_t pin_name, size_t* result) {
|
||||
bool found = false;
|
||||
bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
|
||||
for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
|
||||
if(!string_cmp(pin_name, cli_command_gpio_pins[i].name)) {
|
||||
if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
|
||||
*result = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void gpio_print_pins(void) {
|
||||
printf("Wrong pin name. Available pins: ");
|
||||
bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
|
||||
for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
|
||||
if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
|
||||
printf("%s ", cli_command_gpio_pins[i].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError;
|
||||
|
||||
static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t* value) {
|
||||
string_t pin_name;
|
||||
string_init(pin_name);
|
||||
|
||||
size_t ws = string_search_char(args, ' ');
|
||||
if(ws == STRING_FAILURE) {
|
||||
return ERR_CMD_SYNTAX;
|
||||
}
|
||||
|
||||
string_set_n(pin_name, args, 0, ws);
|
||||
string_right(args, ws);
|
||||
string_strim(args);
|
||||
|
||||
if(!pin_name_to_int(pin_name, pin_num)) {
|
||||
string_clear(pin_name);
|
||||
return ERR_PIN;
|
||||
}
|
||||
|
||||
string_clear(pin_name);
|
||||
|
||||
if(!string_cmp(args, "0")) {
|
||||
*value = 0;
|
||||
} else if(!string_cmp(args, "1")) {
|
||||
*value = 1;
|
||||
} else {
|
||||
return ERR_VALUE;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void cli_command_gpio_mode(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
uint8_t value = 255;
|
||||
|
||||
GpioParseError err = gpio_command_parse(args, &num, &value);
|
||||
|
||||
if(ERR_CMD_SYNTAX == err) {
|
||||
cli_print_usage("gpio mode", "<pin_name> <0|1>", string_get_cstr(args));
|
||||
return;
|
||||
} else if(ERR_PIN == err) {
|
||||
gpio_print_pins();
|
||||
return;
|
||||
} else if(ERR_VALUE == err) {
|
||||
printf("Value is invalid. Enter 1 for input or 0 for output");
|
||||
return;
|
||||
}
|
||||
|
||||
if(cli_command_gpio_pins[num].debug) {
|
||||
printf(
|
||||
"Changeing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
|
||||
char c = cli_getc(cli);
|
||||
if(c != 'y' && c != 'Y') {
|
||||
printf("Cancelled.\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(value == 1) { // output
|
||||
furi_hal_gpio_write(cli_command_gpio_pins[num].pin, false);
|
||||
furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeOutputPushPull);
|
||||
printf("Pin %s is now an output (low)", cli_command_gpio_pins[num].name);
|
||||
} else { // input
|
||||
furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeInput);
|
||||
printf("Pin %s is now an input", cli_command_gpio_pins[num].name);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_gpio_read(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
if(!pin_name_to_int(args, &num)) {
|
||||
gpio_print_pins();
|
||||
return;
|
||||
}
|
||||
|
||||
if(LL_GPIO_MODE_INPUT !=
|
||||
LL_GPIO_GetPinMode(
|
||||
cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
|
||||
printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t val = !!furi_hal_gpio_read(cli_command_gpio_pins[num].pin);
|
||||
|
||||
printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val);
|
||||
}
|
||||
|
||||
void cli_command_gpio_set(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
uint8_t value = 0;
|
||||
GpioParseError err = gpio_command_parse(args, &num, &value);
|
||||
|
||||
if(ERR_CMD_SYNTAX == err) {
|
||||
cli_print_usage("gpio set", "<pin_name> <0|1>", string_get_cstr(args));
|
||||
return;
|
||||
} else if(ERR_PIN == err) {
|
||||
gpio_print_pins();
|
||||
return;
|
||||
} else if(ERR_VALUE == err) {
|
||||
printf("Value is invalid. Enter 1 for high or 0 for low");
|
||||
return;
|
||||
}
|
||||
|
||||
if(LL_GPIO_MODE_OUTPUT !=
|
||||
LL_GPIO_GetPinMode(
|
||||
cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
|
||||
printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra check if debug pins used
|
||||
if(cli_command_gpio_pins[num].debug) {
|
||||
printf(
|
||||
"Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
|
||||
char c = cli_getc(cli);
|
||||
if(c != 'y' && c != 'Y') {
|
||||
printf("Cancelled.\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_gpio_write(cli_command_gpio_pins[num].pin, !!value);
|
||||
printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value);
|
||||
}
|
||||
|
||||
void cli_command_gpio(Cli* cli, string_t args, void* context) {
|
||||
string_t cmd;
|
||||
string_init(cmd);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, cmd)) {
|
||||
cli_command_gpio_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "mode") == 0) {
|
||||
cli_command_gpio_mode(cli, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "set") == 0) {
|
||||
cli_command_gpio_set(cli, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "read") == 0) {
|
||||
cli_command_gpio_read(cli, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
cli_command_gpio_print_usage();
|
||||
} while(false);
|
||||
|
||||
string_clear(cmd);
|
||||
}
|
||||
5
applications/services/cli/cli_command_gpio.h
Normal file
5
applications/services/cli/cli_command_gpio.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
|
||||
void cli_command_gpio(Cli* cli, string_t args, void* context);
|
||||
338
applications/services/cli/cli_commands.c
Normal file
338
applications/services/cli/cli_commands.c
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "cli_commands.h"
|
||||
#include "cli_command_gpio.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_info.h>
|
||||
#include <task_control_block.h>
|
||||
#include <time.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <loader/loader.h>
|
||||
#include <stream_buffer.h>
|
||||
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
|
||||
|
||||
void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) {
|
||||
UNUSED(context);
|
||||
UNUSED(last);
|
||||
printf("%-30s: %s\r\n", key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Device Info Command
|
||||
* This command is intended to be used by humans
|
||||
*/
|
||||
void cli_command_device_info(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
furi_hal_info_get(cli_command_device_info_callback, context);
|
||||
}
|
||||
|
||||
void cli_command_help(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("Commands we have:");
|
||||
|
||||
// Command count
|
||||
const size_t commands_count = CliCommandTree_size(cli->commands);
|
||||
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
|
||||
|
||||
// Use 2 iterators from start and middle to show 2 columns
|
||||
CliCommandTree_it_t it_left;
|
||||
CliCommandTree_it(it_left, cli->commands);
|
||||
CliCommandTree_it_t it_right;
|
||||
CliCommandTree_it(it_right, cli->commands);
|
||||
for(size_t i = 0; i < commands_count_mid; i++) CliCommandTree_next(it_right);
|
||||
|
||||
// Iterate throw tree
|
||||
for(size_t i = 0; i < commands_count_mid; i++) {
|
||||
printf("\r\n");
|
||||
// Left Column
|
||||
if(!CliCommandTree_end_p(it_left)) {
|
||||
printf("%-30s", string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
|
||||
CliCommandTree_next(it_left);
|
||||
}
|
||||
// Right Column
|
||||
if(!CliCommandTree_end_p(it_right)) {
|
||||
printf("%s", string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
|
||||
CliCommandTree_next(it_right);
|
||||
}
|
||||
};
|
||||
|
||||
if(string_size(args) > 0) {
|
||||
cli_nl();
|
||||
printf("Also I have no clue what '");
|
||||
printf("%s", string_get_cstr(args));
|
||||
printf("' is.");
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_date(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
|
||||
FuriHalRtcDateTime datetime = {0};
|
||||
|
||||
if(string_size(args) > 0) {
|
||||
uint16_t hours, minutes, seconds, month, day, year, weekday;
|
||||
int ret = sscanf(
|
||||
string_get_cstr(args),
|
||||
"%hu-%hu-%hu %hu:%hu:%hu %hu",
|
||||
&year,
|
||||
&month,
|
||||
&day,
|
||||
&hours,
|
||||
&minutes,
|
||||
&seconds,
|
||||
&weekday);
|
||||
|
||||
// Some variables are going to discard upper byte
|
||||
// There will be some funky behaviour which is not breaking anything
|
||||
datetime.hour = hours;
|
||||
datetime.minute = minutes;
|
||||
datetime.second = seconds;
|
||||
datetime.weekday = weekday;
|
||||
datetime.month = month;
|
||||
datetime.day = day;
|
||||
datetime.year = year;
|
||||
|
||||
if(ret != 7) {
|
||||
printf(
|
||||
"Invalid datetime format, use `%s`. sscanf %d %s",
|
||||
"%Y-%m-%d %H:%M:%S %u",
|
||||
ret,
|
||||
string_get_cstr(args));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!furi_hal_rtc_validate_datetime(&datetime)) {
|
||||
printf("Invalid datetime data");
|
||||
return;
|
||||
}
|
||||
|
||||
furi_hal_rtc_set_datetime(&datetime);
|
||||
// Verification
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
printf(
|
||||
"New datetime is: " CLI_DATE_FORMAT,
|
||||
datetime.year,
|
||||
datetime.month,
|
||||
datetime.day,
|
||||
datetime.hour,
|
||||
datetime.minute,
|
||||
datetime.second,
|
||||
datetime.weekday);
|
||||
} else {
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
printf(
|
||||
CLI_DATE_FORMAT,
|
||||
datetime.year,
|
||||
datetime.month,
|
||||
datetime.day,
|
||||
datetime.hour,
|
||||
datetime.minute,
|
||||
datetime.second,
|
||||
datetime.weekday);
|
||||
}
|
||||
}
|
||||
|
||||
#define CLI_COMMAND_LOG_RING_SIZE 2048
|
||||
#define CLI_COMMAND_LOG_BUFFER_SIZE 64
|
||||
|
||||
void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) {
|
||||
xStreamBufferSend(context, buffer, size, 0);
|
||||
}
|
||||
|
||||
void cli_command_log(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1);
|
||||
uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE];
|
||||
|
||||
furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring);
|
||||
|
||||
printf("Press CTRL+C to stop...\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
size_t ret = xStreamBufferReceive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
|
||||
cli_write(cli, buffer, ret);
|
||||
}
|
||||
|
||||
furi_hal_console_set_tx_callback(NULL, NULL);
|
||||
|
||||
vStreamBufferDelete(ring);
|
||||
}
|
||||
|
||||
void cli_command_vibro(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
if(!string_cmp(args, "0")) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message_block(notification, &sequence_reset_vibro);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
} else if(!string_cmp(args, "1")) {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message_block(notification, &sequence_set_vibro_on);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
} else {
|
||||
cli_print_usage("vibro", "<1|0>", string_get_cstr(args));
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_debug(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
if(!string_cmp(args, "0")) {
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
|
||||
loader_update_menu();
|
||||
printf("Debug disabled.");
|
||||
} else if(!string_cmp(args, "1")) {
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagDebug);
|
||||
loader_update_menu();
|
||||
printf("Debug enabled.");
|
||||
} else {
|
||||
cli_print_usage("debug", "<1|0>", string_get_cstr(args));
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_led(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
// Get first word as light name
|
||||
NotificationMessage notification_led_message;
|
||||
string_t light_name;
|
||||
string_init(light_name);
|
||||
size_t ws = string_search_char(args, ' ');
|
||||
if(ws == STRING_FAILURE) {
|
||||
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
|
||||
string_clear(light_name);
|
||||
return;
|
||||
} else {
|
||||
string_set_n(light_name, args, 0, ws);
|
||||
string_right(args, ws);
|
||||
string_strim(args);
|
||||
}
|
||||
// Check light name
|
||||
if(!string_cmp(light_name, "r")) {
|
||||
notification_led_message.type = NotificationMessageTypeLedRed;
|
||||
} else if(!string_cmp(light_name, "g")) {
|
||||
notification_led_message.type = NotificationMessageTypeLedGreen;
|
||||
} else if(!string_cmp(light_name, "b")) {
|
||||
notification_led_message.type = NotificationMessageTypeLedBlue;
|
||||
} else if(!string_cmp(light_name, "bl")) {
|
||||
notification_led_message.type = NotificationMessageTypeLedDisplayBacklight;
|
||||
} else {
|
||||
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
|
||||
string_clear(light_name);
|
||||
return;
|
||||
}
|
||||
string_clear(light_name);
|
||||
// Read light value from the rest of the string
|
||||
char* end_ptr;
|
||||
uint32_t value = strtoul(string_get_cstr(args), &end_ptr, 0);
|
||||
if(!(value < 256 && *end_ptr == '\0')) {
|
||||
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set led value
|
||||
notification_led_message.data.led.value = value;
|
||||
|
||||
// Form notification sequence
|
||||
const NotificationSequence notification_sequence = {
|
||||
¬ification_led_message,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// Send notification
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_internal_message_block(notification, ¬ification_sequence);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
void cli_command_ps(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
const uint8_t threads_num_max = 32;
|
||||
FuriThreadId threads_ids[threads_num_max];
|
||||
uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max);
|
||||
printf(
|
||||
"%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free");
|
||||
for(uint8_t i = 0; i < thread_num; i++) {
|
||||
TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i];
|
||||
printf(
|
||||
"%-20s 0x%-12lx %-8d %-8ld %-8ld\r\n",
|
||||
furi_thread_get_name(threads_ids[i]),
|
||||
(uint32_t)tcb->pxStack,
|
||||
memmgr_heap_get_thread_memory(threads_ids[i]),
|
||||
(uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t),
|
||||
furi_thread_get_stack_space(threads_ids[i]));
|
||||
}
|
||||
printf("\r\nTotal: %d", thread_num);
|
||||
}
|
||||
|
||||
void cli_command_free(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
printf("Free heap size: %d\r\n", memmgr_get_free_heap());
|
||||
printf("Total heap size: %d\r\n", memmgr_get_total_heap());
|
||||
printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap());
|
||||
printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block());
|
||||
|
||||
printf("Pool free: %d\r\n", memmgr_pool_get_free());
|
||||
printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block());
|
||||
}
|
||||
|
||||
void cli_command_free_blocks(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
memmgr_heap_printf_free_blocks();
|
||||
}
|
||||
|
||||
void cli_command_i2c(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n"
|
||||
"Clock: 100khz, 7bit address\r\n"
|
||||
"\r\n");
|
||||
printf(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
|
||||
printf("--+--------------------------------\r\n");
|
||||
for(uint8_t row = 0; row < 0x8; row++) {
|
||||
printf("%x | ", row);
|
||||
for(uint8_t column = 0; column <= 0xF; column++) {
|
||||
bool ret = furi_hal_i2c_is_device_ready(
|
||||
&furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2);
|
||||
printf("%c ", ret ? '#' : '-');
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
|
||||
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "debug", CliCommandFlagDefault, cli_command_debug, NULL);
|
||||
cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL);
|
||||
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
|
||||
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
|
||||
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
|
||||
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
|
||||
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
|
||||
}
|
||||
5
applications/services/cli/cli_commands.h
Normal file
5
applications/services/cli/cli_commands.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
|
||||
void cli_commands_init(Cli* cli);
|
||||
67
applications/services/cli/cli_i.h
Normal file
67
applications/services/cli/cli_i.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <m-dict.h>
|
||||
#include <m-bptree.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#include "cli_vcp.h"
|
||||
|
||||
#define CLI_LINE_SIZE_MAX
|
||||
#define CLI_COMMANDS_TREE_RANK 4
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
CliCallback callback;
|
||||
void* context;
|
||||
uint32_t flags;
|
||||
} CliCommand;
|
||||
|
||||
struct CliSession {
|
||||
void (*init)(void);
|
||||
void (*deinit)(void);
|
||||
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
void (*tx)(const uint8_t* buffer, size_t size);
|
||||
void (*tx_stdout)(const char* data, size_t size);
|
||||
bool (*is_connected)(void);
|
||||
};
|
||||
|
||||
BPTREE_DEF2(
|
||||
CliCommandTree,
|
||||
CLI_COMMANDS_TREE_RANK,
|
||||
string_t,
|
||||
STRING_OPLIST,
|
||||
CliCommand,
|
||||
M_POD_OPLIST)
|
||||
|
||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST)
|
||||
|
||||
struct Cli {
|
||||
CliCommandTree_t commands;
|
||||
FuriMutex* mutex;
|
||||
FuriSemaphore* idle_sem;
|
||||
string_t last_line;
|
||||
string_t line;
|
||||
CliSession* session;
|
||||
|
||||
size_t cursor_position;
|
||||
};
|
||||
|
||||
Cli* cli_alloc();
|
||||
|
||||
void cli_reset(Cli* cli);
|
||||
|
||||
void cli_putc(Cli* cli, char c);
|
||||
|
||||
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
326
applications/services/cli/cli_vcp.c
Normal file
326
applications/services/cli/cli_vcp.c
Normal file
@@ -0,0 +1,326 @@
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
#include <stream_buffer.h>
|
||||
#include "cli_i.h"
|
||||
|
||||
#define TAG "CliVcp"
|
||||
|
||||
#define USB_CDC_PKT_LEN CDC_DATA_SZ
|
||||
#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
||||
#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
||||
|
||||
#define VCP_IF_NUM 0
|
||||
|
||||
typedef enum {
|
||||
VcpEvtStop = (1 << 0),
|
||||
VcpEvtConnect = (1 << 1),
|
||||
VcpEvtDisconnect = (1 << 2),
|
||||
VcpEvtStreamRx = (1 << 3),
|
||||
VcpEvtRx = (1 << 4),
|
||||
VcpEvtStreamTx = (1 << 5),
|
||||
VcpEvtTx = (1 << 6),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
#define VCP_THREAD_FLAG_ALL \
|
||||
(VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
|
||||
VcpEvtStreamTx)
|
||||
|
||||
typedef struct {
|
||||
FuriThread* thread;
|
||||
|
||||
StreamBufferHandle_t tx_stream;
|
||||
StreamBufferHandle_t rx_stream;
|
||||
|
||||
volatile bool connected;
|
||||
volatile bool running;
|
||||
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
|
||||
uint8_t data_buffer[USB_CDC_PKT_LEN];
|
||||
} CliVcp;
|
||||
|
||||
static int32_t vcp_worker(void* context);
|
||||
static void vcp_on_cdc_tx_complete(void* context);
|
||||
static void vcp_on_cdc_rx(void* context);
|
||||
static void vcp_state_callback(void* context, uint8_t state);
|
||||
static void vcp_on_cdc_control_line(void* context, uint8_t state);
|
||||
|
||||
static CdcCallbacks cdc_cb = {
|
||||
vcp_on_cdc_tx_complete,
|
||||
vcp_on_cdc_rx,
|
||||
vcp_state_callback,
|
||||
vcp_on_cdc_control_line,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static CliVcp* vcp = NULL;
|
||||
|
||||
static const uint8_t ascii_soh = 0x01;
|
||||
static const uint8_t ascii_eot = 0x04;
|
||||
|
||||
static void cli_vcp_init() {
|
||||
if(vcp == NULL) {
|
||||
vcp = malloc(sizeof(CliVcp));
|
||||
vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1);
|
||||
vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1);
|
||||
}
|
||||
furi_assert(vcp->thread == NULL);
|
||||
|
||||
vcp->connected = false;
|
||||
|
||||
vcp->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(vcp->thread, "CliVcpWorker");
|
||||
furi_thread_set_stack_size(vcp->thread, 1024);
|
||||
furi_thread_set_callback(vcp->thread, vcp_worker);
|
||||
furi_thread_start(vcp->thread);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
static void cli_vcp_deinit() {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop);
|
||||
furi_thread_join(vcp->thread);
|
||||
furi_thread_free(vcp->thread);
|
||||
vcp->thread = NULL;
|
||||
}
|
||||
|
||||
static int32_t vcp_worker(void* context) {
|
||||
UNUSED(context);
|
||||
bool tx_idle = true;
|
||||
size_t missed_rx = 0;
|
||||
uint8_t last_tx_pkt_len = 0;
|
||||
|
||||
// Switch USB to VCP mode (if it is not set yet)
|
||||
vcp->usb_if_prev = furi_hal_usb_get_config();
|
||||
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
|
||||
furi_hal_usb_set_config(&usb_cdc_single, NULL);
|
||||
}
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
|
||||
|
||||
FURI_LOG_D(TAG, "Start");
|
||||
vcp->running = true;
|
||||
|
||||
while(1) {
|
||||
uint32_t flags =
|
||||
furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_assert((flags & FuriFlagError) == 0);
|
||||
|
||||
// VCP session opened
|
||||
if(flags & VcpEvtConnect) {
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "Connect");
|
||||
#endif
|
||||
if(vcp->connected == false) {
|
||||
vcp->connected = true;
|
||||
xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// VCP session closed
|
||||
if(flags & VcpEvtDisconnect) {
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "Disconnect");
|
||||
#endif
|
||||
if(vcp->connected == true) {
|
||||
vcp->connected = false;
|
||||
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// Rx buffer was read, maybe there is enough space for new data?
|
||||
if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "StreamRx");
|
||||
#endif
|
||||
if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
|
||||
flags |= VcpEvtRx;
|
||||
missed_rx--;
|
||||
}
|
||||
}
|
||||
|
||||
// New data received
|
||||
if(flags & VcpEvtRx) {
|
||||
if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
|
||||
int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "Rx %d", len);
|
||||
#endif
|
||||
if(len > 0) {
|
||||
furi_check(
|
||||
xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) ==
|
||||
(size_t)len);
|
||||
}
|
||||
} else {
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "Rx missed");
|
||||
#endif
|
||||
missed_rx++;
|
||||
}
|
||||
}
|
||||
|
||||
// New data in Tx buffer
|
||||
if(flags & VcpEvtStreamTx) {
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "StreamTx");
|
||||
#endif
|
||||
if(tx_idle) {
|
||||
flags |= VcpEvtTx;
|
||||
}
|
||||
}
|
||||
|
||||
// CDC write transfer done
|
||||
if(flags & VcpEvtTx) {
|
||||
size_t len =
|
||||
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "Tx %d", len);
|
||||
#endif
|
||||
if(len > 0) { // Some data left in Tx buffer. Sending it now
|
||||
tx_idle = false;
|
||||
furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len);
|
||||
last_tx_pkt_len = len;
|
||||
} else { // There is nothing to send.
|
||||
if(last_tx_pkt_len == 64) {
|
||||
// Send extra zero-length packet if last packet len is 64 to indicate transfer end
|
||||
furi_hal_cdc_send(VCP_IF_NUM, NULL, 0);
|
||||
} else {
|
||||
// Set flag to start next transfer instantly
|
||||
tx_idle = true;
|
||||
}
|
||||
last_tx_pkt_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(flags & VcpEvtStop) {
|
||||
vcp->connected = false;
|
||||
vcp->running = false;
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
||||
// Restore previous USB mode (if it was set during init)
|
||||
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
|
||||
furi_hal_usb_unlock();
|
||||
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
|
||||
}
|
||||
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
|
||||
break;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D(TAG, "End");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
|
||||
furi_assert(vcp);
|
||||
furi_assert(buffer);
|
||||
|
||||
if(vcp->running == false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "rx %u start", size);
|
||||
#endif
|
||||
|
||||
size_t rx_cnt = 0;
|
||||
|
||||
while(size > 0) {
|
||||
size_t batch_size = size;
|
||||
if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
|
||||
|
||||
size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout);
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "rx %u ", batch_size);
|
||||
#endif
|
||||
if(len == 0) break;
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx);
|
||||
size -= len;
|
||||
buffer += len;
|
||||
rx_cnt += len;
|
||||
}
|
||||
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "rx %u end", size);
|
||||
#endif
|
||||
return rx_cnt;
|
||||
}
|
||||
|
||||
static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
|
||||
furi_assert(vcp);
|
||||
furi_assert(buffer);
|
||||
|
||||
if(vcp->running == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "tx %u start", size);
|
||||
#endif
|
||||
|
||||
while(size > 0 && vcp->connected) {
|
||||
size_t batch_size = size;
|
||||
if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN;
|
||||
|
||||
xStreamBufferSend(vcp->tx_stream, buffer, batch_size, FuriWaitForever);
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx);
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "tx %u", batch_size);
|
||||
#endif
|
||||
|
||||
size -= batch_size;
|
||||
buffer += batch_size;
|
||||
}
|
||||
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
FURI_LOG_D(TAG, "tx %u end", size);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cli_vcp_tx_stdout(const char* data, size_t size) {
|
||||
cli_vcp_tx((const uint8_t*)data, size);
|
||||
}
|
||||
|
||||
static void vcp_state_callback(void* context, uint8_t state) {
|
||||
UNUSED(context);
|
||||
if(state == 0) {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_control_line(void* context, uint8_t state) {
|
||||
UNUSED(context);
|
||||
// bit 0: DTR state, bit 1: RTS state
|
||||
bool dtr = state & (1 << 0);
|
||||
|
||||
if(dtr == true) {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect);
|
||||
} else {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_rx(void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx);
|
||||
furi_check((ret & FuriFlagError) == 0);
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_tx_complete(void* context) {
|
||||
UNUSED(context);
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx);
|
||||
}
|
||||
|
||||
static bool cli_vcp_is_connected(void) {
|
||||
furi_assert(vcp);
|
||||
return vcp->connected;
|
||||
}
|
||||
|
||||
CliSession cli_vcp = {
|
||||
cli_vcp_init,
|
||||
cli_vcp_deinit,
|
||||
cli_vcp_rx,
|
||||
cli_vcp_tx,
|
||||
cli_vcp_tx_stdout,
|
||||
cli_vcp_is_connected,
|
||||
};
|
||||
18
applications/services/cli/cli_vcp.h
Normal file
18
applications/services/cli/cli_vcp.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @file cli_vcp.h
|
||||
* VCP HAL API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CliSession CliSession;
|
||||
|
||||
extern CliSession cli_vcp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
6
applications/services/crypto/application.fam
Normal file
6
applications/services/crypto/application.fam
Normal file
@@ -0,0 +1,6 @@
|
||||
App(
|
||||
appid="crypto_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="crypto_on_system_start",
|
||||
order=10,
|
||||
)
|
||||
326
applications/services/crypto/crypto_cli.c
Normal file
326
applications/services/crypto/crypto_cli.c
Normal file
@@ -0,0 +1,326 @@
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <cli/cli.h>
|
||||
|
||||
void crypto_cli_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("crypto <cmd> <args>\r\n");
|
||||
printf("Cmd list:\r\n");
|
||||
printf(
|
||||
"\tencrypt <key_slot:int> <iv:hex>\t - Using key from secure enclave and IV encrypt plain text with AES256CBC and encode to hex\r\n");
|
||||
printf(
|
||||
"\tdecrypt <key_slot:int> <iv:hex>\t - Using key from secure enclave and IV decrypt hex encoded encrypted with AES256CBC data to plain text\r\n");
|
||||
printf("\thas_key <key_slot:int>\t - Check if secure enclave has key in slot\r\n");
|
||||
printf(
|
||||
"\tstore_key <key_slot:int> <key_type:str> <key_size:int> <key_data:hex>\t - Store key in secure enclave. !!! NON-REVERSABLE OPERATION - READ MANUAL FIRST !!!\r\n");
|
||||
};
|
||||
|
||||
void crypto_cli_encrypt(Cli* cli, string_t args) {
|
||||
int key_slot = 0;
|
||||
bool key_loaded = false;
|
||||
uint8_t iv[16];
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &key_slot) || !(key_slot > 0 && key_slot <= 100)) {
|
||||
printf("Incorrect or missing slot, expected int 1-100");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_hex_bytes(args, iv, 16)) {
|
||||
printf("Incorrect or missing IV, expected 16 bytes in hex");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_store_load_key(key_slot, iv)) {
|
||||
printf("Unable to load key from slot %d", key_slot);
|
||||
break;
|
||||
}
|
||||
key_loaded = true;
|
||||
|
||||
printf("Enter plain text and press Ctrl+C to complete encryption:\r\n");
|
||||
|
||||
string_t input;
|
||||
string_init(input);
|
||||
char c;
|
||||
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
|
||||
if(c == CliSymbolAsciiETX) {
|
||||
printf("\r\n");
|
||||
break;
|
||||
} else if(c >= 0x20 && c < 0x7F) {
|
||||
putc(c, stdout);
|
||||
fflush(stdout);
|
||||
string_push_back(input, c);
|
||||
} else if(c == CliSymbolAsciiCR) {
|
||||
printf("\r\n");
|
||||
string_cat_str(input, "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
size_t size = string_size(input);
|
||||
if(size > 0) {
|
||||
// C-string null termination and block alignments
|
||||
size++;
|
||||
size_t remain = size % 16;
|
||||
if(remain) {
|
||||
size = size - remain + 16;
|
||||
}
|
||||
string_reserve(input, size);
|
||||
uint8_t* output = malloc(size);
|
||||
if(!furi_hal_crypto_encrypt((const uint8_t*)string_get_cstr(input), output, size)) {
|
||||
printf("Failed to encrypt input");
|
||||
} else {
|
||||
printf("Hex-encoded encrypted data:\r\n");
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
if(i % 80 == 0) printf("\r\n");
|
||||
printf("%02x", output[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
free(output);
|
||||
} else {
|
||||
printf("No input");
|
||||
}
|
||||
|
||||
string_clear(input);
|
||||
} while(0);
|
||||
|
||||
if(key_loaded) {
|
||||
furi_hal_crypto_store_unload_key(key_slot);
|
||||
}
|
||||
}
|
||||
|
||||
void crypto_cli_decrypt(Cli* cli, string_t args) {
|
||||
int key_slot = 0;
|
||||
bool key_loaded = false;
|
||||
uint8_t iv[16];
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &key_slot) || !(key_slot > 0 && key_slot <= 100)) {
|
||||
printf("Incorrect or missing slot, expected int 1-100");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_hex_bytes(args, iv, 16)) {
|
||||
printf("Incorrect or missing IV, expected 16 bytes in hex");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_store_load_key(key_slot, iv)) {
|
||||
printf("Unable to load key from slot %d", key_slot);
|
||||
break;
|
||||
}
|
||||
key_loaded = true;
|
||||
|
||||
printf("Enter Hex-encoded data and press Ctrl+C to complete decryption:\r\n");
|
||||
|
||||
string_t hex_input;
|
||||
string_init(hex_input);
|
||||
char c;
|
||||
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
|
||||
if(c == CliSymbolAsciiETX) {
|
||||
printf("\r\n");
|
||||
break;
|
||||
} else if(c >= 0x20 && c < 0x7F) {
|
||||
putc(c, stdout);
|
||||
fflush(stdout);
|
||||
string_push_back(hex_input, c);
|
||||
} else if(c == CliSymbolAsciiCR) {
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
string_strim(hex_input);
|
||||
size_t hex_size = string_size(hex_input);
|
||||
if(hex_size > 0 && hex_size % 2 == 0) {
|
||||
size_t size = hex_size / 2;
|
||||
uint8_t* input = malloc(size);
|
||||
uint8_t* output = malloc(size);
|
||||
|
||||
if(args_read_hex_bytes(hex_input, input, size)) {
|
||||
if(furi_hal_crypto_decrypt(input, output, size)) {
|
||||
printf("Decrypted data:\r\n");
|
||||
printf("%s\r\n", output);
|
||||
} else {
|
||||
printf("Failed to decrypt\r\n");
|
||||
}
|
||||
} else {
|
||||
printf("Failed to parse input");
|
||||
}
|
||||
|
||||
free(input);
|
||||
free(output);
|
||||
} else {
|
||||
printf("Invalid or empty input");
|
||||
}
|
||||
|
||||
string_clear(hex_input);
|
||||
} while(0);
|
||||
|
||||
if(key_loaded) {
|
||||
furi_hal_crypto_store_unload_key(key_slot);
|
||||
}
|
||||
}
|
||||
|
||||
void crypto_cli_has_key(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
int key_slot = 0;
|
||||
uint8_t iv[16];
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &key_slot) || !(key_slot > 0 && key_slot <= 100)) {
|
||||
printf("Incorrect or missing slot, expected int 1-100");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_store_load_key(key_slot, iv)) {
|
||||
printf("Unable to load key from slot %d", key_slot);
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Successfully loaded key from slot %d", key_slot);
|
||||
|
||||
furi_hal_crypto_store_unload_key(key_slot);
|
||||
} while(0);
|
||||
}
|
||||
|
||||
void crypto_cli_store_key(Cli* cli, string_t args) {
|
||||
UNUSED(cli);
|
||||
int key_slot = 0;
|
||||
int key_size = 0;
|
||||
string_t key_type;
|
||||
string_init(key_type);
|
||||
|
||||
uint8_t data[32 + 12] = {};
|
||||
FuriHalCryptoKey key;
|
||||
key.data = data;
|
||||
size_t data_size = 0;
|
||||
|
||||
do {
|
||||
if(!args_read_int_and_trim(args, &key_slot)) {
|
||||
printf("Incorrect or missing key type, expected master, simple or encrypted");
|
||||
break;
|
||||
}
|
||||
if(!args_read_string_and_trim(args, key_type)) {
|
||||
printf("Incorrect or missing key type, expected master, simple or encrypted");
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(key_type, "master") == 0) {
|
||||
if(key_slot != 0) {
|
||||
printf("Master keyslot must be is 0");
|
||||
break;
|
||||
}
|
||||
key.type = FuriHalCryptoKeyTypeMaster;
|
||||
} else if(string_cmp_str(key_type, "simple") == 0) {
|
||||
if(key_slot < 1 || key_slot > 99) {
|
||||
printf("Simple keyslot must be in range");
|
||||
break;
|
||||
}
|
||||
key.type = FuriHalCryptoKeyTypeSimple;
|
||||
} else if(string_cmp_str(key_type, "encrypted") == 0) {
|
||||
key.type = FuriHalCryptoKeyTypeEncrypted;
|
||||
data_size += 12;
|
||||
} else {
|
||||
printf("Incorrect or missing key type, expected master, simple or encrypted");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_int_and_trim(args, &key_size)) {
|
||||
printf("Incorrect or missing key size, expected 128 or 256");
|
||||
break;
|
||||
}
|
||||
|
||||
if(key_size == 128) {
|
||||
key.size = FuriHalCryptoKeySize128;
|
||||
data_size += 16;
|
||||
} else if(key_size == 256) {
|
||||
key.size = FuriHalCryptoKeySize256;
|
||||
data_size += 32;
|
||||
} else {
|
||||
printf("Incorrect or missing key size, expected 128 or 256");
|
||||
}
|
||||
|
||||
if(!args_read_hex_bytes(args, data, data_size)) {
|
||||
printf("Incorrect or missing key data, expected hex encoded key with or without IV.");
|
||||
break;
|
||||
}
|
||||
|
||||
if(key_slot > 0) {
|
||||
uint8_t iv[16];
|
||||
if(key_slot > 1) {
|
||||
if(!furi_hal_crypto_store_load_key(key_slot - 1, iv)) {
|
||||
printf(
|
||||
"Slot %d before %d is empty, which is not allowed",
|
||||
key_slot - 1,
|
||||
key_slot);
|
||||
break;
|
||||
}
|
||||
furi_hal_crypto_store_unload_key(key_slot - 1);
|
||||
}
|
||||
|
||||
if(furi_hal_crypto_store_load_key(key_slot, iv)) {
|
||||
furi_hal_crypto_store_unload_key(key_slot);
|
||||
printf("Key slot %d is already used", key_slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t slot;
|
||||
if(furi_hal_crypto_store_add_key(&key, &slot)) {
|
||||
printf("Success. Stored to slot: %d", slot);
|
||||
} else {
|
||||
printf("Failure");
|
||||
}
|
||||
} while(0);
|
||||
|
||||
string_clear(key_type);
|
||||
}
|
||||
|
||||
static void crypto_cli(Cli* cli, string_t args, void* context) {
|
||||
UNUSED(context);
|
||||
string_t cmd;
|
||||
string_init(cmd);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, cmd)) {
|
||||
crypto_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "encrypt") == 0) {
|
||||
crypto_cli_encrypt(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "decrypt") == 0) {
|
||||
crypto_cli_decrypt(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "has_key") == 0) {
|
||||
crypto_cli_has_key(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "store_key") == 0) {
|
||||
crypto_cli_store_key(cli, args);
|
||||
break;
|
||||
}
|
||||
|
||||
crypto_cli_print_usage();
|
||||
} while(false);
|
||||
|
||||
string_clear(cmd);
|
||||
}
|
||||
|
||||
void crypto_on_system_start() {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(crypto_cli);
|
||||
#endif
|
||||
}
|
||||
575
applications/services/desktop/animations/animation_manager.c
Normal file
575
applications/services/desktop/animations/animation_manager.c
Normal file
@@ -0,0 +1,575 @@
|
||||
#include <gui/view_stack.h>
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <m-string.h>
|
||||
#include <portmacro.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <storage/storage.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include "views/bubble_animation_view.h"
|
||||
#include "views/one_shot_animation_view.h"
|
||||
#include "animation_storage.h"
|
||||
#include "animation_manager.h"
|
||||
|
||||
#define TAG "AnimationManager"
|
||||
|
||||
#define HARDCODED_ANIMATION_NAME "L1_Tv_128x47"
|
||||
#define NO_SD_ANIMATION_NAME "L1_NoSd_128x49"
|
||||
#define BAD_BATTERY_ANIMATION_NAME "L1_BadBattery_128x47"
|
||||
|
||||
#define NO_DB_ANIMATION_NAME "L0_NoDb_128x51"
|
||||
#define BAD_SD_ANIMATION_NAME "L0_SdBad_128x51"
|
||||
#define SD_OK_ANIMATION_NAME "L0_SdOk_128x51"
|
||||
#define URL_ANIMATION_NAME "L0_Url_128x51"
|
||||
#define NEW_MAIL_ANIMATION_NAME "L0_NewMail_128x51"
|
||||
|
||||
typedef enum {
|
||||
AnimationManagerStateIdle,
|
||||
AnimationManagerStateBlocked,
|
||||
AnimationManagerStateFreezedIdle,
|
||||
AnimationManagerStateFreezedBlocked,
|
||||
} AnimationManagerState;
|
||||
|
||||
struct AnimationManager {
|
||||
bool sd_show_url;
|
||||
bool sd_shown_no_db;
|
||||
bool sd_shown_sd_ok;
|
||||
bool levelup_pending;
|
||||
bool levelup_active;
|
||||
AnimationManagerState state;
|
||||
FuriPubSubSubscription* pubsub_subscription_storage;
|
||||
FuriPubSubSubscription* pubsub_subscription_dolphin;
|
||||
BubbleAnimationView* animation_view;
|
||||
OneShotView* one_shot_view;
|
||||
FuriTimer* idle_animation_timer;
|
||||
StorageAnimation* current_animation;
|
||||
AnimationManagerInteractCallback interact_callback;
|
||||
AnimationManagerSetNewIdleAnimationCallback new_idle_callback;
|
||||
AnimationManagerSetNewIdleAnimationCallback check_blocking_callback;
|
||||
void* context;
|
||||
string_t freezed_animation_name;
|
||||
int32_t freezed_animation_time_left;
|
||||
ViewStack* view_stack;
|
||||
};
|
||||
|
||||
static StorageAnimation*
|
||||
animation_manager_select_idle_animation(AnimationManager* animation_manager);
|
||||
static void animation_manager_replace_current_animation(
|
||||
AnimationManager* animation_manager,
|
||||
StorageAnimation* storage_animation);
|
||||
static void animation_manager_start_new_idle(AnimationManager* animation_manager);
|
||||
static bool animation_manager_check_blocking(AnimationManager* animation_manager);
|
||||
static bool animation_manager_is_valid_idle_animation(
|
||||
const StorageAnimationManifestInfo* info,
|
||||
const DolphinStats* stats);
|
||||
static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager);
|
||||
static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager);
|
||||
|
||||
void animation_manager_set_context(AnimationManager* animation_manager, void* context) {
|
||||
furi_assert(animation_manager);
|
||||
animation_manager->context = context;
|
||||
}
|
||||
|
||||
void animation_manager_set_new_idle_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerSetNewIdleAnimationCallback callback) {
|
||||
furi_assert(animation_manager);
|
||||
animation_manager->new_idle_callback = callback;
|
||||
}
|
||||
|
||||
void animation_manager_set_check_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerCheckBlockingCallback callback) {
|
||||
furi_assert(animation_manager);
|
||||
animation_manager->check_blocking_callback = callback;
|
||||
}
|
||||
|
||||
void animation_manager_set_interact_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerInteractCallback callback) {
|
||||
furi_assert(animation_manager);
|
||||
animation_manager->interact_callback = callback;
|
||||
}
|
||||
|
||||
static void animation_manager_check_blocking_callback(const void* message, void* context) {
|
||||
const StorageEvent* storage_event = message;
|
||||
|
||||
switch(storage_event->type) {
|
||||
case StorageEventTypeCardMount:
|
||||
case StorageEventTypeCardUnmount:
|
||||
case StorageEventTypeCardMountError:
|
||||
furi_assert(context);
|
||||
AnimationManager* animation_manager = context;
|
||||
if(animation_manager->check_blocking_callback) {
|
||||
animation_manager->check_blocking_callback(animation_manager->context);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void animation_manager_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
AnimationManager* animation_manager = context;
|
||||
if(animation_manager->new_idle_callback) {
|
||||
animation_manager->new_idle_callback(animation_manager->context);
|
||||
}
|
||||
}
|
||||
|
||||
static void animation_manager_interact_callback(void* context) {
|
||||
furi_assert(context);
|
||||
AnimationManager* animation_manager = context;
|
||||
if(animation_manager->interact_callback) {
|
||||
animation_manager->interact_callback(animation_manager->context);
|
||||
}
|
||||
}
|
||||
|
||||
/* reaction to animation_manager->check_blocking_callback() */
|
||||
void animation_manager_check_blocking_process(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
if(animation_manager->state == AnimationManagerStateIdle) {
|
||||
bool blocked = animation_manager_check_blocking(animation_manager);
|
||||
|
||||
if(!blocked) {
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
|
||||
const StorageAnimationManifestInfo* manifest_info =
|
||||
animation_storage_get_meta(animation_manager->current_animation);
|
||||
bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
|
||||
|
||||
if(!valid) {
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reaction to animation_manager->new_idle_callback() */
|
||||
void animation_manager_new_idle_process(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
if(animation_manager->state == AnimationManagerStateIdle) {
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
}
|
||||
|
||||
/* reaction to animation_manager->interact_callback() */
|
||||
bool animation_manager_interact_process(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
bool consumed = true;
|
||||
|
||||
if(animation_manager->levelup_pending) {
|
||||
animation_manager->levelup_pending = false;
|
||||
animation_manager->levelup_active = true;
|
||||
animation_manager_switch_to_one_shot_view(animation_manager);
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
dolphin_upgrade_level(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
} else if(animation_manager->levelup_active) {
|
||||
animation_manager->levelup_active = false;
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
animation_manager_switch_to_animation_view(animation_manager);
|
||||
} else if(animation_manager->state == AnimationManagerStateBlocked) {
|
||||
bool blocked = animation_manager_check_blocking(animation_manager);
|
||||
|
||||
if(!blocked) {
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
} else {
|
||||
consumed = false;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void animation_manager_start_new_idle(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
StorageAnimation* new_animation = animation_manager_select_idle_animation(animation_manager);
|
||||
animation_manager_replace_current_animation(animation_manager, new_animation);
|
||||
const BubbleAnimation* bubble_animation =
|
||||
animation_storage_get_bubble_animation(animation_manager->current_animation);
|
||||
animation_manager->state = AnimationManagerStateIdle;
|
||||
furi_timer_start(animation_manager->idle_animation_timer, bubble_animation->duration * 1000);
|
||||
}
|
||||
|
||||
static bool animation_manager_check_blocking(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
StorageAnimation* blocking_animation = NULL;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FS_Error sd_status = storage_sd_status(storage);
|
||||
|
||||
if(sd_status == FSE_INTERNAL) {
|
||||
blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME);
|
||||
furi_assert(blocking_animation);
|
||||
} else if(sd_status == FSE_NOT_READY) {
|
||||
animation_manager->sd_shown_sd_ok = false;
|
||||
animation_manager->sd_shown_no_db = false;
|
||||
} else if(sd_status == FSE_OK) {
|
||||
if(!animation_manager->sd_shown_sd_ok) {
|
||||
blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME);
|
||||
furi_assert(blocking_animation);
|
||||
animation_manager->sd_shown_sd_ok = true;
|
||||
} else if(!animation_manager->sd_shown_no_db) {
|
||||
if(!storage_file_exists(storage, EXT_PATH("Manifest"))) {
|
||||
blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME);
|
||||
furi_assert(blocking_animation);
|
||||
animation_manager->sd_shown_no_db = true;
|
||||
animation_manager->sd_show_url = true;
|
||||
}
|
||||
} else if(animation_manager->sd_show_url) {
|
||||
blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME);
|
||||
furi_assert(blocking_animation);
|
||||
animation_manager->sd_show_url = false;
|
||||
}
|
||||
}
|
||||
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
if(!blocking_animation && stats.level_up_is_pending) {
|
||||
blocking_animation = animation_storage_find_animation(NEW_MAIL_ANIMATION_NAME);
|
||||
furi_assert(blocking_animation);
|
||||
if(blocking_animation) {
|
||||
animation_manager->levelup_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(blocking_animation) {
|
||||
furi_timer_stop(animation_manager->idle_animation_timer);
|
||||
animation_manager_replace_current_animation(animation_manager, blocking_animation);
|
||||
/* no timer starting because this is blocking animation */
|
||||
animation_manager->state = AnimationManagerStateBlocked;
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return !!blocking_animation;
|
||||
}
|
||||
|
||||
static void animation_manager_replace_current_animation(
|
||||
AnimationManager* animation_manager,
|
||||
StorageAnimation* storage_animation) {
|
||||
furi_assert(storage_animation);
|
||||
StorageAnimation* previous_animation = animation_manager->current_animation;
|
||||
|
||||
const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation);
|
||||
bubble_animation_view_set_animation(animation_manager->animation_view, animation);
|
||||
const char* new_name = animation_storage_get_meta(storage_animation)->name;
|
||||
FURI_LOG_I(TAG, "Select \'%s\' animation", new_name);
|
||||
animation_manager->current_animation = storage_animation;
|
||||
|
||||
if(previous_animation) {
|
||||
animation_storage_free_storage_animation(&previous_animation);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationManager* animation_manager_alloc(void) {
|
||||
AnimationManager* animation_manager = malloc(sizeof(AnimationManager));
|
||||
animation_manager->animation_view = bubble_animation_view_alloc();
|
||||
animation_manager->view_stack = view_stack_alloc();
|
||||
View* animation_view = bubble_animation_get_view(animation_manager->animation_view);
|
||||
view_stack_add_view(animation_manager->view_stack, animation_view);
|
||||
string_init(animation_manager->freezed_animation_name);
|
||||
|
||||
animation_manager->idle_animation_timer =
|
||||
furi_timer_alloc(animation_manager_timer_callback, FuriTimerTypeOnce, animation_manager);
|
||||
bubble_animation_view_set_interact_callback(
|
||||
animation_manager->animation_view, animation_manager_interact_callback, animation_manager);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe(
|
||||
storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe(
|
||||
dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
|
||||
animation_manager->sd_shown_sd_ok = true;
|
||||
if(!animation_manager_check_blocking(animation_manager)) {
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
|
||||
return animation_manager;
|
||||
}
|
||||
|
||||
void animation_manager_free(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
furi_pubsub_unsubscribe(
|
||||
dolphin_get_pubsub(dolphin), animation_manager->pubsub_subscription_dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_unsubscribe(
|
||||
storage_get_pubsub(storage), animation_manager->pubsub_subscription_storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
string_clear(animation_manager->freezed_animation_name);
|
||||
View* animation_view = bubble_animation_get_view(animation_manager->animation_view);
|
||||
view_stack_remove_view(animation_manager->view_stack, animation_view);
|
||||
bubble_animation_view_free(animation_manager->animation_view);
|
||||
furi_timer_free(animation_manager->idle_animation_timer);
|
||||
}
|
||||
|
||||
View* animation_manager_get_animation_view(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
|
||||
return view_stack_get_view(animation_manager->view_stack);
|
||||
}
|
||||
|
||||
static bool animation_manager_is_valid_idle_animation(
|
||||
const StorageAnimationManifestInfo* info,
|
||||
const DolphinStats* stats) {
|
||||
furi_assert(info);
|
||||
furi_assert(info->name);
|
||||
|
||||
bool result = true;
|
||||
|
||||
if(!strcmp(info->name, BAD_BATTERY_ANIMATION_NAME)) {
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
bool battery_is_well = power_is_battery_healthy(power);
|
||||
furi_record_close(RECORD_POWER);
|
||||
|
||||
result = !battery_is_well;
|
||||
}
|
||||
if(!strcmp(info->name, NO_SD_ANIMATION_NAME)) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FS_Error sd_status = storage_sd_status(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
result = (sd_status == FSE_NOT_READY);
|
||||
}
|
||||
if((stats->butthurt < info->min_butthurt) || (stats->butthurt > info->max_butthurt)) {
|
||||
result = false;
|
||||
}
|
||||
if((stats->level < info->min_level) || (stats->level > info->max_level)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static StorageAnimation*
|
||||
animation_manager_select_idle_animation(AnimationManager* animation_manager) {
|
||||
UNUSED(animation_manager);
|
||||
StorageAnimationList_t animation_list;
|
||||
StorageAnimationList_init(animation_list);
|
||||
animation_storage_fill_animation_list(&animation_list);
|
||||
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
uint32_t whole_weight = 0;
|
||||
|
||||
StorageAnimationList_it_t it;
|
||||
for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
|
||||
StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
|
||||
const StorageAnimationManifestInfo* manifest_info =
|
||||
animation_storage_get_meta(storage_animation);
|
||||
bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
|
||||
|
||||
if(valid) {
|
||||
whole_weight += manifest_info->weight;
|
||||
StorageAnimationList_next(it);
|
||||
} else {
|
||||
animation_storage_free_storage_animation(&storage_animation);
|
||||
/* remove and increase iterator */
|
||||
StorageAnimationList_remove(animation_list, it);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t lucky_number = furi_hal_random_get() % whole_weight;
|
||||
uint32_t weight = 0;
|
||||
|
||||
StorageAnimation* selected = NULL;
|
||||
for
|
||||
M_EACH(item, animation_list, StorageAnimationList_t) {
|
||||
if(lucky_number < weight) {
|
||||
break;
|
||||
}
|
||||
weight += animation_storage_get_meta(*item)->weight;
|
||||
selected = *item;
|
||||
}
|
||||
|
||||
for
|
||||
M_EACH(item, animation_list, StorageAnimationList_t) {
|
||||
if(*item != selected) {
|
||||
animation_storage_free_storage_animation(item);
|
||||
}
|
||||
}
|
||||
|
||||
StorageAnimationList_clear(animation_list);
|
||||
|
||||
/* cache animation, if failed - choose reliable animation */
|
||||
if(!animation_storage_get_bubble_animation(selected)) {
|
||||
const char* name = animation_storage_get_meta(selected)->name;
|
||||
FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name);
|
||||
animation_storage_free_storage_animation(&selected);
|
||||
selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME);
|
||||
}
|
||||
|
||||
furi_assert(selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool animation_manager_is_animation_loaded(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
return animation_manager->current_animation;
|
||||
}
|
||||
|
||||
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
furi_assert(animation_manager->current_animation);
|
||||
furi_assert(!string_size(animation_manager->freezed_animation_name));
|
||||
furi_assert(
|
||||
(animation_manager->state == AnimationManagerStateIdle) ||
|
||||
(animation_manager->state == AnimationManagerStateBlocked));
|
||||
|
||||
if(animation_manager->state == AnimationManagerStateBlocked) {
|
||||
animation_manager->state = AnimationManagerStateFreezedBlocked;
|
||||
} else if(animation_manager->state == AnimationManagerStateIdle) {
|
||||
animation_manager->state = AnimationManagerStateFreezedIdle;
|
||||
|
||||
animation_manager->freezed_animation_time_left =
|
||||
xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount();
|
||||
if(animation_manager->freezed_animation_time_left < 0) {
|
||||
animation_manager->freezed_animation_time_left = 0;
|
||||
}
|
||||
furi_timer_stop(animation_manager->idle_animation_timer);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Unload animation \'%s\'",
|
||||
animation_storage_get_meta(animation_manager->current_animation)->name);
|
||||
|
||||
StorageAnimationManifestInfo* meta =
|
||||
animation_storage_get_meta(animation_manager->current_animation);
|
||||
/* copy str, not move, because it can be internal animation */
|
||||
string_set_str(animation_manager->freezed_animation_name, meta->name);
|
||||
|
||||
bubble_animation_freeze(animation_manager->animation_view);
|
||||
animation_storage_free_storage_animation(&animation_manager->current_animation);
|
||||
}
|
||||
|
||||
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
furi_assert(!animation_manager->current_animation);
|
||||
furi_assert(string_size(animation_manager->freezed_animation_name));
|
||||
furi_assert(
|
||||
(animation_manager->state == AnimationManagerStateFreezedIdle) ||
|
||||
(animation_manager->state == AnimationManagerStateFreezedBlocked));
|
||||
|
||||
if(animation_manager->state == AnimationManagerStateFreezedBlocked) {
|
||||
StorageAnimation* restore_animation = animation_storage_find_animation(
|
||||
string_get_cstr(animation_manager->freezed_animation_name));
|
||||
/* all blocked animations must be in flipper -> we can
|
||||
* always find blocking animation */
|
||||
furi_assert(restore_animation);
|
||||
animation_manager_replace_current_animation(animation_manager, restore_animation);
|
||||
animation_manager->state = AnimationManagerStateBlocked;
|
||||
} else if(animation_manager->state == AnimationManagerStateFreezedIdle) {
|
||||
/* check if we missed some system notifications, and set current_animation */
|
||||
bool blocked = animation_manager_check_blocking(animation_manager);
|
||||
if(!blocked) {
|
||||
/* if no blocking - try restore last one idle */
|
||||
StorageAnimation* restore_animation = animation_storage_find_animation(
|
||||
string_get_cstr(animation_manager->freezed_animation_name));
|
||||
if(restore_animation) {
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
const StorageAnimationManifestInfo* manifest_info =
|
||||
animation_storage_get_meta(restore_animation);
|
||||
bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
|
||||
if(valid) {
|
||||
animation_manager_replace_current_animation(
|
||||
animation_manager, restore_animation);
|
||||
animation_manager->state = AnimationManagerStateIdle;
|
||||
|
||||
if(animation_manager->freezed_animation_time_left) {
|
||||
furi_timer_start(
|
||||
animation_manager->idle_animation_timer,
|
||||
animation_manager->freezed_animation_time_left);
|
||||
} else {
|
||||
const BubbleAnimation* animation = animation_storage_get_bubble_animation(
|
||||
animation_manager->current_animation);
|
||||
furi_timer_start(
|
||||
animation_manager->idle_animation_timer, animation->duration * 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Failed to restore \'%s\'",
|
||||
string_get_cstr(animation_manager->freezed_animation_name));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Unknown state is an error. But not in release version.*/
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
/* if can't restore previous animation - select new */
|
||||
if(!animation_manager->current_animation) {
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Load animation \'%s\'",
|
||||
animation_storage_get_meta(animation_manager->current_animation)->name);
|
||||
|
||||
bubble_animation_unfreeze(animation_manager->animation_view);
|
||||
string_reset(animation_manager->freezed_animation_name);
|
||||
furi_assert(animation_manager->current_animation);
|
||||
}
|
||||
|
||||
static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
furi_assert(!animation_manager->one_shot_view);
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
|
||||
animation_manager->one_shot_view = one_shot_view_alloc();
|
||||
one_shot_view_set_interact_callback(
|
||||
animation_manager->one_shot_view, animation_manager_interact_callback, animation_manager);
|
||||
View* prev_view = bubble_animation_get_view(animation_manager->animation_view);
|
||||
View* next_view = one_shot_view_get_view(animation_manager->one_shot_view);
|
||||
view_stack_remove_view(animation_manager->view_stack, prev_view);
|
||||
view_stack_add_view(animation_manager->view_stack, next_view);
|
||||
if(stats.level == 1) {
|
||||
one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup1_128x64);
|
||||
} else if(stats.level == 2) {
|
||||
one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup2_128x64);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager) {
|
||||
furi_assert(animation_manager);
|
||||
furi_assert(animation_manager->one_shot_view);
|
||||
|
||||
View* prev_view = one_shot_view_get_view(animation_manager->one_shot_view);
|
||||
View* next_view = bubble_animation_get_view(animation_manager->animation_view);
|
||||
view_stack_remove_view(animation_manager->view_stack, prev_view);
|
||||
view_stack_add_view(animation_manager->view_stack, next_view);
|
||||
one_shot_view_free(animation_manager->one_shot_view);
|
||||
animation_manager->one_shot_view = NULL;
|
||||
}
|
||||
159
applications/services/desktop/animations/animation_manager.h
Normal file
159
applications/services/desktop/animations/animation_manager.h
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <stdint.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
typedef struct AnimationManager AnimationManager;
|
||||
|
||||
typedef struct {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
const char* text;
|
||||
Align align_h;
|
||||
Align align_v;
|
||||
} Bubble;
|
||||
|
||||
typedef struct FrameBubble {
|
||||
Bubble bubble;
|
||||
uint8_t start_frame;
|
||||
uint8_t end_frame;
|
||||
const struct FrameBubble* next_bubble;
|
||||
} FrameBubble;
|
||||
|
||||
typedef struct {
|
||||
const FrameBubble* const* frame_bubble_sequences;
|
||||
uint8_t frame_bubble_sequences_count;
|
||||
const Icon icon_animation;
|
||||
const uint8_t* frame_order;
|
||||
uint8_t passive_frames;
|
||||
uint8_t active_frames;
|
||||
uint8_t active_cycles;
|
||||
uint16_t duration;
|
||||
uint16_t active_cooldown;
|
||||
} BubbleAnimation;
|
||||
|
||||
typedef void (*AnimationManagerSetNewIdleAnimationCallback)(void* context);
|
||||
typedef void (*AnimationManagerCheckBlockingCallback)(void* context);
|
||||
typedef void (*AnimationManagerInteractCallback)(void*);
|
||||
|
||||
/**
|
||||
* Allocate Animation Manager
|
||||
*
|
||||
* @return animation manager instance
|
||||
*/
|
||||
AnimationManager* animation_manager_alloc(void);
|
||||
|
||||
/**
|
||||
* Free Animation Manager
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
void animation_manager_free(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Get View of Animation Manager
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @return view
|
||||
*/
|
||||
View* animation_manager_get_animation_view(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Set context for all callbacks for Animation Manager
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @context context
|
||||
*/
|
||||
void animation_manager_set_context(AnimationManager* animation_manager, void* context);
|
||||
|
||||
/**
|
||||
* Set callback for Animation Manager for defered calls
|
||||
* for animation_manager_new_idle_process().
|
||||
* Animation Manager doesn't have it's own thread, so main thread gives
|
||||
* callbacks to A.M. to call when it should perform some inner manipulations.
|
||||
* This callback is called from other threads and should notify main thread
|
||||
* when to call animation_manager_new_idle_process().
|
||||
* So scheme is this:
|
||||
* A.M. sets callbacks,
|
||||
* callbacks notifies main thread
|
||||
* main thread in its own context calls appropriate *_process() function.
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @callback callback
|
||||
*/
|
||||
void animation_manager_set_new_idle_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerSetNewIdleAnimationCallback callback);
|
||||
|
||||
/**
|
||||
* Function to call in main thread as a response to
|
||||
* set_new_idle_callback's call.
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
void animation_manager_new_idle_process(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Set callback for Animation Manager for defered calls
|
||||
* for animation_manager_check_blocking_process().
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @callback callback
|
||||
*/
|
||||
void animation_manager_set_check_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerCheckBlockingCallback callback);
|
||||
|
||||
/**
|
||||
* Function to call in main thread as a response to
|
||||
* set_new_idle_callback's call.
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
void animation_manager_check_blocking_process(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Set callback for Animation Manager for defered calls
|
||||
* for animation_manager_interact_process().
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @callback callback
|
||||
*/
|
||||
void animation_manager_set_interact_callback(
|
||||
AnimationManager* animation_manager,
|
||||
AnimationManagerInteractCallback callback);
|
||||
|
||||
/**
|
||||
* Function to call in main thread as a response to
|
||||
* set_new_idle_callback's call.
|
||||
*
|
||||
* @animation_manager instance
|
||||
* @return true if event was consumed
|
||||
*/
|
||||
bool animation_manager_interact_process(AnimationManager* animation_manager);
|
||||
|
||||
/** Check if animation loaded
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
bool animation_manager_is_animation_loaded(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Unload and Stall animation actions. Draw callback in view
|
||||
* paints first frame of current animation until
|
||||
* animation_manager_load_and_continue_animation() is called.
|
||||
* Can't be called multiple times. Every Stall has to be finished
|
||||
* with Continue.
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager);
|
||||
|
||||
/**
|
||||
* Load and Contunue execution of animation manager.
|
||||
*
|
||||
* @animation_manager instance
|
||||
*/
|
||||
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager);
|
||||
535
applications/services/desktop/animations/animation_storage.c
Normal file
535
applications/services/desktop/animations/animation_storage.c
Normal file
@@ -0,0 +1,535 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
#include <core/dangerous_defines.h>
|
||||
#include <storage/storage.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <m-string.h>
|
||||
|
||||
#include "animation_manager.h"
|
||||
#include "animation_storage.h"
|
||||
#include "animation_storage_i.h"
|
||||
#include <assets_dolphin_internal.h>
|
||||
#include <assets_dolphin_blocking.h>
|
||||
|
||||
#define ANIMATION_META_FILE "meta.txt"
|
||||
#define ANIMATION_DIR EXT_PATH("dolphin")
|
||||
#define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt"
|
||||
#define TAG "AnimationStorage"
|
||||
|
||||
static void animation_storage_free_bubbles(BubbleAnimation* animation);
|
||||
static void animation_storage_free_frames(BubbleAnimation* animation);
|
||||
static void animation_storage_free_animation(BubbleAnimation** storage_animation);
|
||||
static BubbleAnimation* animation_storage_load_animation(const char* name);
|
||||
|
||||
static bool animation_storage_load_single_manifest_info(
|
||||
StorageAnimationManifestInfo* manifest_info,
|
||||
const char* name) {
|
||||
furi_assert(manifest_info);
|
||||
|
||||
bool result = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
flipper_format_set_strict_mode(file, true);
|
||||
string_t read_string;
|
||||
string_init(read_string);
|
||||
|
||||
do {
|
||||
uint32_t u32value;
|
||||
if(FSE_OK != storage_sd_status(storage)) break;
|
||||
if(!flipper_format_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
|
||||
|
||||
if(!flipper_format_read_header(file, read_string, &u32value)) break;
|
||||
if(string_cmp_str(read_string, "Flipper Animation Manifest")) break;
|
||||
|
||||
manifest_info->name = NULL;
|
||||
|
||||
/* skip other animation names */
|
||||
flipper_format_set_strict_mode(file, false);
|
||||
while(flipper_format_read_string(file, "Name", read_string) &&
|
||||
string_cmp_str(read_string, name))
|
||||
;
|
||||
if(string_cmp_str(read_string, name)) break;
|
||||
flipper_format_set_strict_mode(file, true);
|
||||
|
||||
manifest_info->name = malloc(string_size(read_string) + 1);
|
||||
strcpy((char*)manifest_info->name, string_get_cstr(read_string));
|
||||
|
||||
if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break;
|
||||
manifest_info->min_butthurt = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Max butthurt", &u32value, 1)) break;
|
||||
manifest_info->max_butthurt = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Min level", &u32value, 1)) break;
|
||||
manifest_info->min_level = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Max level", &u32value, 1)) break;
|
||||
manifest_info->max_level = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Weight", &u32value, 1)) break;
|
||||
manifest_info->weight = u32value;
|
||||
result = true;
|
||||
} while(0);
|
||||
|
||||
if(!result && manifest_info->name) {
|
||||
free((void*)manifest_info->name);
|
||||
}
|
||||
string_clear(read_string);
|
||||
flipper_format_free(file);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) {
|
||||
furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*));
|
||||
furi_assert(!StorageAnimationList_size(*animation_list));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
/* Forbid skipping fields */
|
||||
flipper_format_set_strict_mode(file, true);
|
||||
string_t read_string;
|
||||
string_init(read_string);
|
||||
|
||||
do {
|
||||
uint32_t u32value;
|
||||
StorageAnimation* storage_animation = NULL;
|
||||
|
||||
if(FSE_OK != storage_sd_status(storage)) break;
|
||||
if(!flipper_format_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
|
||||
if(!flipper_format_read_header(file, read_string, &u32value)) break;
|
||||
if(string_cmp_str(read_string, "Flipper Animation Manifest")) break;
|
||||
do {
|
||||
storage_animation = malloc(sizeof(StorageAnimation));
|
||||
storage_animation->external = true;
|
||||
storage_animation->animation = NULL;
|
||||
storage_animation->manifest_info.name = NULL;
|
||||
|
||||
if(!flipper_format_read_string(file, "Name", read_string)) break;
|
||||
storage_animation->manifest_info.name = malloc(string_size(read_string) + 1);
|
||||
strcpy((char*)storage_animation->manifest_info.name, string_get_cstr(read_string));
|
||||
|
||||
if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break;
|
||||
storage_animation->manifest_info.min_butthurt = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Max butthurt", &u32value, 1)) break;
|
||||
storage_animation->manifest_info.max_butthurt = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Min level", &u32value, 1)) break;
|
||||
storage_animation->manifest_info.min_level = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Max level", &u32value, 1)) break;
|
||||
storage_animation->manifest_info.max_level = u32value;
|
||||
if(!flipper_format_read_uint32(file, "Weight", &u32value, 1)) break;
|
||||
storage_animation->manifest_info.weight = u32value;
|
||||
|
||||
StorageAnimationList_push_back(*animation_list, storage_animation);
|
||||
} while(1);
|
||||
|
||||
animation_storage_free_storage_animation(&storage_animation);
|
||||
} while(0);
|
||||
|
||||
string_clear(read_string);
|
||||
flipper_format_free(file);
|
||||
|
||||
// add hard-coded animations
|
||||
for(size_t i = 0; i < dolphin_internal_size; ++i) {
|
||||
StorageAnimationList_push_back(*animation_list, (StorageAnimation*)&dolphin_internal[i]);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
StorageAnimation* animation_storage_find_animation(const char* name) {
|
||||
furi_assert(name);
|
||||
furi_assert(strlen(name));
|
||||
StorageAnimation* storage_animation = NULL;
|
||||
|
||||
for(size_t i = 0; i < dolphin_blocking_size; ++i) {
|
||||
if(!strcmp(dolphin_blocking[i].manifest_info.name, name)) {
|
||||
storage_animation = (StorageAnimation*)&dolphin_blocking[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!storage_animation) {
|
||||
for(size_t i = 0; i < dolphin_internal_size; ++i) {
|
||||
if(!strcmp(dolphin_internal[i].manifest_info.name, name)) {
|
||||
storage_animation = (StorageAnimation*)&dolphin_internal[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* look through external animations */
|
||||
if(!storage_animation) {
|
||||
storage_animation = malloc(sizeof(StorageAnimation));
|
||||
storage_animation->external = true;
|
||||
|
||||
bool result = false;
|
||||
result =
|
||||
animation_storage_load_single_manifest_info(&storage_animation->manifest_info, name);
|
||||
if(result) {
|
||||
storage_animation->animation = animation_storage_load_animation(name);
|
||||
result = !!storage_animation->animation;
|
||||
}
|
||||
if(!result) {
|
||||
animation_storage_free_storage_animation(&storage_animation);
|
||||
}
|
||||
}
|
||||
|
||||
return storage_animation;
|
||||
}
|
||||
|
||||
StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation) {
|
||||
furi_assert(storage_animation);
|
||||
return &storage_animation->manifest_info;
|
||||
}
|
||||
|
||||
const BubbleAnimation*
|
||||
animation_storage_get_bubble_animation(StorageAnimation* storage_animation) {
|
||||
furi_assert(storage_animation);
|
||||
animation_storage_cache_animation(storage_animation);
|
||||
return storage_animation->animation;
|
||||
}
|
||||
|
||||
void animation_storage_cache_animation(StorageAnimation* storage_animation) {
|
||||
furi_assert(storage_animation);
|
||||
|
||||
if(storage_animation->external) {
|
||||
if(!storage_animation->animation) {
|
||||
storage_animation->animation =
|
||||
animation_storage_load_animation(storage_animation->manifest_info.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void animation_storage_free_animation(BubbleAnimation** animation) {
|
||||
furi_assert(animation);
|
||||
|
||||
if(*animation) {
|
||||
animation_storage_free_bubbles(*animation);
|
||||
animation_storage_free_frames(*animation);
|
||||
if((*animation)->frame_order) {
|
||||
free((void*)(*animation)->frame_order);
|
||||
}
|
||||
free(*animation);
|
||||
*animation = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void animation_storage_free_storage_animation(StorageAnimation** storage_animation) {
|
||||
furi_assert(storage_animation);
|
||||
furi_assert(*storage_animation);
|
||||
|
||||
if((*storage_animation)->external) {
|
||||
animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation);
|
||||
|
||||
if((*storage_animation)->manifest_info.name) {
|
||||
free((void*)(*storage_animation)->manifest_info.name);
|
||||
}
|
||||
free(*storage_animation);
|
||||
}
|
||||
|
||||
*storage_animation = NULL;
|
||||
}
|
||||
|
||||
static bool animation_storage_cast_align(string_t align_str, Align* align) {
|
||||
if(!string_cmp_str(align_str, "Bottom")) {
|
||||
*align = AlignBottom;
|
||||
} else if(!string_cmp_str(align_str, "Top")) {
|
||||
*align = AlignTop;
|
||||
} else if(!string_cmp_str(align_str, "Left")) {
|
||||
*align = AlignLeft;
|
||||
} else if(!string_cmp_str(align_str, "Right")) {
|
||||
*align = AlignRight;
|
||||
} else if(!string_cmp_str(align_str, "Center")) {
|
||||
*align = AlignCenter;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void animation_storage_free_frames(BubbleAnimation* animation) {
|
||||
furi_assert(animation);
|
||||
|
||||
const Icon* icon = &animation->icon_animation;
|
||||
for(int i = 0; i < icon->frame_count; ++i) {
|
||||
if(icon->frames[i]) {
|
||||
free((void*)icon->frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free((void*)icon->frames);
|
||||
}
|
||||
|
||||
static bool animation_storage_load_frames(
|
||||
Storage* storage,
|
||||
const char* name,
|
||||
BubbleAnimation* animation,
|
||||
uint32_t* frame_order,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
uint16_t frame_order_count = animation->passive_frames + animation->active_frames;
|
||||
|
||||
/* The frames should go in order (0...N), without omissions */
|
||||
size_t max_frame_count = 0;
|
||||
for(int i = 0; i < frame_order_count; ++i) {
|
||||
max_frame_count = MAX(max_frame_count, frame_order[i]);
|
||||
}
|
||||
|
||||
if((max_frame_count >= frame_order_count) || (max_frame_count >= 256 /* max uint8_t */)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Icon* icon = (Icon*)&animation->icon_animation;
|
||||
FURI_CONST_ASSIGN(icon->frame_count, max_frame_count + 1);
|
||||
FURI_CONST_ASSIGN(icon->frame_rate, 0);
|
||||
FURI_CONST_ASSIGN(icon->height, height);
|
||||
FURI_CONST_ASSIGN(icon->width, width);
|
||||
icon->frames = malloc(sizeof(const uint8_t*) * icon->frame_count);
|
||||
|
||||
bool frames_ok = false;
|
||||
File* file = storage_file_alloc(storage);
|
||||
FileInfo file_info;
|
||||
string_t filename;
|
||||
string_init(filename);
|
||||
size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1;
|
||||
|
||||
for(int i = 0; i < icon->frame_count; ++i) {
|
||||
frames_ok = false;
|
||||
string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, i);
|
||||
|
||||
if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break;
|
||||
if(file_info.size > max_filesize) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Filesize %d, max: %d (width %d, height %d)",
|
||||
file_info.size,
|
||||
max_filesize,
|
||||
width,
|
||||
height);
|
||||
break;
|
||||
}
|
||||
if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename));
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_CONST_ASSIGN_PTR(icon->frames[i], malloc(file_info.size));
|
||||
if(storage_file_read(file, (void*)icon->frames[i], file_info.size) != file_info.size) {
|
||||
FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename));
|
||||
break;
|
||||
}
|
||||
storage_file_close(file);
|
||||
frames_ok = true;
|
||||
}
|
||||
|
||||
if(!frames_ok) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Load \'%s\' failed, %dx%d, size: %d",
|
||||
string_get_cstr(filename),
|
||||
width,
|
||||
height,
|
||||
file_info.size);
|
||||
animation_storage_free_frames(animation);
|
||||
} else {
|
||||
furi_check(animation->icon_animation.frames);
|
||||
for(int i = 0; i < animation->icon_animation.frame_count; ++i) {
|
||||
furi_check(animation->icon_animation.frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
storage_file_free(file);
|
||||
string_clear(filename);
|
||||
|
||||
return frames_ok;
|
||||
}
|
||||
|
||||
static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFormat* ff) {
|
||||
uint32_t u32value;
|
||||
string_t str;
|
||||
string_init(str);
|
||||
bool success = false;
|
||||
furi_assert(!animation->frame_bubble_sequences);
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_uint32(ff, "Bubble slots", &u32value, 1)) break;
|
||||
if(u32value > 20) break;
|
||||
animation->frame_bubble_sequences_count = u32value;
|
||||
if(animation->frame_bubble_sequences_count == 0) {
|
||||
animation->frame_bubble_sequences = NULL;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
animation->frame_bubble_sequences =
|
||||
malloc(sizeof(FrameBubble*) * animation->frame_bubble_sequences_count);
|
||||
|
||||
int32_t current_slot = 0;
|
||||
for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
|
||||
FURI_CONST_ASSIGN_PTR(
|
||||
animation->frame_bubble_sequences[i], malloc(sizeof(FrameBubble)));
|
||||
}
|
||||
|
||||
const FrameBubble* bubble = animation->frame_bubble_sequences[0];
|
||||
int8_t index = -1;
|
||||
for(;;) {
|
||||
if(!flipper_format_read_int32(ff, "Slot", ¤t_slot, 1)) break;
|
||||
if((current_slot != 0) && (index == -1)) break;
|
||||
|
||||
if(current_slot == index) {
|
||||
FURI_CONST_ASSIGN_PTR(bubble->next_bubble, malloc(sizeof(FrameBubble)));
|
||||
bubble = bubble->next_bubble;
|
||||
} else if(current_slot == index + 1) {
|
||||
++index;
|
||||
bubble = animation->frame_bubble_sequences[index];
|
||||
} else {
|
||||
/* slots have to start from 0, be ascending sorted, and
|
||||
* have exact number of slots as specified in "Bubble slots" */
|
||||
break;
|
||||
}
|
||||
if(index >= animation->frame_bubble_sequences_count) break;
|
||||
|
||||
if(!flipper_format_read_uint32(ff, "X", &u32value, 1)) break;
|
||||
FURI_CONST_ASSIGN(bubble->bubble.x, u32value);
|
||||
if(!flipper_format_read_uint32(ff, "Y", &u32value, 1)) break;
|
||||
FURI_CONST_ASSIGN(bubble->bubble.y, u32value);
|
||||
|
||||
if(!flipper_format_read_string(ff, "Text", str)) break;
|
||||
if(string_size(str) > 100) break;
|
||||
|
||||
string_replace_all_str(str, "\\n", "\n");
|
||||
|
||||
FURI_CONST_ASSIGN_PTR(bubble->bubble.text, malloc(string_size(str) + 1));
|
||||
strcpy((char*)bubble->bubble.text, string_get_cstr(str));
|
||||
|
||||
if(!flipper_format_read_string(ff, "AlignH", str)) break;
|
||||
if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break;
|
||||
if(!flipper_format_read_string(ff, "AlignV", str)) break;
|
||||
if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_v)) break;
|
||||
|
||||
if(!flipper_format_read_uint32(ff, "StartFrame", &u32value, 1)) break;
|
||||
FURI_CONST_ASSIGN(bubble->start_frame, u32value);
|
||||
if(!flipper_format_read_uint32(ff, "EndFrame", &u32value, 1)) break;
|
||||
FURI_CONST_ASSIGN(bubble->end_frame, u32value);
|
||||
}
|
||||
success = (index + 1) == animation->frame_bubble_sequences_count;
|
||||
} while(0);
|
||||
|
||||
if(!success) {
|
||||
if(animation->frame_bubble_sequences) {
|
||||
FURI_LOG_E(TAG, "Failed to load animation bubbles");
|
||||
animation_storage_free_bubbles(animation);
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(str);
|
||||
return success;
|
||||
}
|
||||
|
||||
static BubbleAnimation* animation_storage_load_animation(const char* name) {
|
||||
furi_assert(name);
|
||||
BubbleAnimation* animation = malloc(sizeof(BubbleAnimation));
|
||||
|
||||
uint32_t height = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t* u32array = NULL;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
/* Forbid skipping fields */
|
||||
flipper_format_set_strict_mode(ff, true);
|
||||
string_t str;
|
||||
string_init(str);
|
||||
animation->frame_bubble_sequences = NULL;
|
||||
|
||||
bool success = false;
|
||||
do {
|
||||
uint32_t u32value;
|
||||
|
||||
if(FSE_OK != storage_sd_status(storage)) break;
|
||||
|
||||
string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name);
|
||||
if(!flipper_format_file_open_existing(ff, string_get_cstr(str))) break;
|
||||
if(!flipper_format_read_header(ff, str, &u32value)) break;
|
||||
if(string_cmp_str(str, "Flipper Animation")) break;
|
||||
|
||||
if(!flipper_format_read_uint32(ff, "Width", &width, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Height", &height, 1)) break;
|
||||
|
||||
if(!flipper_format_read_uint32(ff, "Passive frames", &u32value, 1)) break;
|
||||
animation->passive_frames = u32value;
|
||||
if(!flipper_format_read_uint32(ff, "Active frames", &u32value, 1)) break;
|
||||
animation->active_frames = u32value;
|
||||
|
||||
uint8_t frames = animation->passive_frames + animation->active_frames;
|
||||
uint32_t count = 0;
|
||||
if(!flipper_format_get_value_count(ff, "Frames order", &count)) break;
|
||||
if(count != frames) {
|
||||
FURI_LOG_E(TAG, "Error loading animation: frames order");
|
||||
break;
|
||||
}
|
||||
u32array = malloc(sizeof(uint32_t) * frames);
|
||||
if(!flipper_format_read_uint32(ff, "Frames order", u32array, frames)) break;
|
||||
animation->frame_order = malloc(sizeof(uint8_t) * frames);
|
||||
for(int i = 0; i < frames; ++i) {
|
||||
FURI_CONST_ASSIGN(animation->frame_order[i], u32array[i]);
|
||||
}
|
||||
|
||||
/* passive and active frames must be loaded up to this point */
|
||||
if(!animation_storage_load_frames(storage, name, animation, u32array, width, height))
|
||||
break;
|
||||
|
||||
if(!flipper_format_read_uint32(ff, "Active cycles", &u32value, 1)) break;
|
||||
animation->active_cycles = u32value;
|
||||
if(!flipper_format_read_uint32(ff, "Frame rate", &u32value, 1)) break;
|
||||
FURI_CONST_ASSIGN(animation->icon_animation.frame_rate, u32value);
|
||||
if(!flipper_format_read_uint32(ff, "Duration", &u32value, 1)) break;
|
||||
animation->duration = u32value;
|
||||
if(!flipper_format_read_uint32(ff, "Active cooldown", &u32value, 1)) break;
|
||||
animation->active_cooldown = u32value;
|
||||
|
||||
if(!animation_storage_load_bubbles(animation, ff)) break;
|
||||
success = true;
|
||||
} while(0);
|
||||
|
||||
string_clear(str);
|
||||
flipper_format_free(ff);
|
||||
if(u32array) {
|
||||
free(u32array);
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
if(animation->frame_order) {
|
||||
free((void*)animation->frame_order);
|
||||
}
|
||||
free(animation);
|
||||
animation = NULL;
|
||||
}
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
static void animation_storage_free_bubbles(BubbleAnimation* animation) {
|
||||
if(!animation->frame_bubble_sequences) return;
|
||||
|
||||
for(int i = 0; i < animation->frame_bubble_sequences_count;) {
|
||||
const FrameBubble* const* bubble = &animation->frame_bubble_sequences[i];
|
||||
|
||||
if((*bubble) == NULL) break;
|
||||
|
||||
while((*bubble)->next_bubble != NULL) {
|
||||
bubble = &(*bubble)->next_bubble;
|
||||
}
|
||||
|
||||
if((*bubble)->bubble.text) {
|
||||
free((void*)(*bubble)->bubble.text);
|
||||
}
|
||||
if((*bubble) == animation->frame_bubble_sequences[i]) {
|
||||
++i;
|
||||
}
|
||||
free((void*)*bubble);
|
||||
FURI_CONST_ASSIGN_PTR(*bubble, NULL);
|
||||
}
|
||||
free((void*)animation->frame_bubble_sequences);
|
||||
animation->frame_bubble_sequences = NULL;
|
||||
}
|
||||
89
applications/services/desktop/animations/animation_storage.h
Normal file
89
applications/services/desktop/animations/animation_storage.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <m-list.h>
|
||||
#include "views/bubble_animation_view.h"
|
||||
#include <m-string.h>
|
||||
|
||||
/** Main structure to handle animation data.
|
||||
* Contains all, including animation playing data (BubbleAnimation),
|
||||
* data for random animation selection (StorageAnimationMeta) and
|
||||
* flag of location internal/external */
|
||||
typedef struct StorageAnimation StorageAnimation;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint8_t min_butthurt;
|
||||
uint8_t max_butthurt;
|
||||
uint8_t min_level;
|
||||
uint8_t max_level;
|
||||
uint8_t weight;
|
||||
} StorageAnimationManifestInfo;
|
||||
|
||||
/** Container to return available animations list */
|
||||
LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST)
|
||||
#define M_OPL_StorageAnimationList_t() LIST_OPLIST(StorageAnimationList)
|
||||
|
||||
/**
|
||||
* Fill list of available animations.
|
||||
* List will contain all idle animations on inner flash
|
||||
* and all available on SD-card, mentioned in manifest.txt.
|
||||
* Performs caching of animation. If fail - falls back to
|
||||
* inner animation.
|
||||
* List has to be initialized.
|
||||
*
|
||||
* @list list to fill with animations data
|
||||
*/
|
||||
void animation_storage_fill_animation_list(StorageAnimationList_t* list);
|
||||
|
||||
/**
|
||||
* Get bubble animation of storage animation.
|
||||
* Bubble Animation is a structure which describes animation
|
||||
* independent of it's place of storage and meta data.
|
||||
* It contain all what is need to be played.
|
||||
* If storage_animation is not cached - caches it.
|
||||
*
|
||||
* @storage_animation animation from which extract bubble animation
|
||||
* @return bubble_animation, NULL if failed to cache data.
|
||||
*/
|
||||
const BubbleAnimation* animation_storage_get_bubble_animation(StorageAnimation* storage_animation);
|
||||
|
||||
/**
|
||||
* Performs caching animation data (Bubble Animation)
|
||||
* if this is not done yet.
|
||||
*
|
||||
* @storage_animation animation to cache
|
||||
*/
|
||||
void animation_storage_cache_animation(StorageAnimation* storage_animation);
|
||||
|
||||
/**
|
||||
* Find animation by name.
|
||||
* Search through the inner flash, and SD-card if has.
|
||||
*
|
||||
* @name name of animation
|
||||
* @return found animation. NULL if nothing found.
|
||||
*/
|
||||
StorageAnimation* animation_storage_find_animation(const char* name);
|
||||
|
||||
/**
|
||||
* Get meta information of storage animation.
|
||||
* This information allows to randomly select animation.
|
||||
* Also it contains name. Never returns NULL.
|
||||
*
|
||||
* @storage_animation item of whom we have to extract meta.
|
||||
* @return meta itself
|
||||
*/
|
||||
StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation);
|
||||
|
||||
/**
|
||||
* Free storage_animation, which previously acquired
|
||||
* by Animation Storage.
|
||||
*
|
||||
* @storage_animation item to free. NULL-ed after all.
|
||||
*/
|
||||
void animation_storage_free_storage_animation(StorageAnimation** storage_animation);
|
||||
|
||||
/**
|
||||
* Has to be called at least 1 time to initialize runtime structures
|
||||
* of animations in inner flash.
|
||||
*/
|
||||
void animation_storage_initialize_internal_animations(void);
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "animation_storage.h"
|
||||
#include "animation_manager.h"
|
||||
|
||||
struct StorageAnimation {
|
||||
const BubbleAnimation* animation;
|
||||
bool external;
|
||||
StorageAnimationManifestInfo manifest_info;
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
|
||||
#include "../animation_manager.h"
|
||||
#include "../animation_storage.h"
|
||||
#include "bubble_animation_view.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <input/input.h>
|
||||
#include <stdint.h>
|
||||
#include <core/dangerous_defines.h>
|
||||
|
||||
#define ACTIVE_SHIFT 2
|
||||
|
||||
typedef struct {
|
||||
const BubbleAnimation* current;
|
||||
const FrameBubble* current_bubble;
|
||||
uint8_t current_frame;
|
||||
uint8_t active_cycle;
|
||||
uint8_t active_bubbles;
|
||||
uint8_t passive_bubbles;
|
||||
uint8_t active_shift;
|
||||
TickType_t active_ended_at;
|
||||
Icon* freeze_frame;
|
||||
} BubbleAnimationViewModel;
|
||||
|
||||
struct BubbleAnimationView {
|
||||
View* view;
|
||||
FuriTimer* timer;
|
||||
BubbleAnimationInteractCallback interact_callback;
|
||||
void* interact_callback_context;
|
||||
};
|
||||
|
||||
static void bubble_animation_activate(BubbleAnimationView* view, bool force);
|
||||
static void bubble_animation_activate_right_now(BubbleAnimationView* view);
|
||||
|
||||
static uint8_t bubble_animation_get_frame_index(BubbleAnimationViewModel* model) {
|
||||
furi_assert(model);
|
||||
uint8_t icon_index = 0;
|
||||
const BubbleAnimation* animation = model->current;
|
||||
|
||||
if(model->current_frame < animation->passive_frames) {
|
||||
icon_index = model->current_frame;
|
||||
} else {
|
||||
icon_index =
|
||||
(model->current_frame - animation->passive_frames) % animation->active_frames +
|
||||
animation->passive_frames;
|
||||
}
|
||||
furi_assert(icon_index < (animation->passive_frames + animation->active_frames));
|
||||
|
||||
return animation->frame_order[icon_index];
|
||||
}
|
||||
|
||||
static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
|
||||
furi_assert(model_);
|
||||
furi_assert(canvas);
|
||||
|
||||
BubbleAnimationViewModel* model = model_;
|
||||
const BubbleAnimation* animation = model->current;
|
||||
|
||||
if(model->freeze_frame) {
|
||||
uint8_t y_offset = canvas_height(canvas) - icon_get_height(model->freeze_frame);
|
||||
canvas_draw_icon(canvas, 0, y_offset, model->freeze_frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!animation) {
|
||||
return;
|
||||
}
|
||||
|
||||
furi_assert(model->current_frame < 255);
|
||||
|
||||
uint8_t index = bubble_animation_get_frame_index(model);
|
||||
uint8_t width = icon_get_width(&animation->icon_animation);
|
||||
uint8_t height = icon_get_height(&animation->icon_animation);
|
||||
uint8_t y_offset = canvas_height(canvas) - height;
|
||||
canvas_draw_bitmap(
|
||||
canvas, 0, y_offset, width, height, animation->icon_animation.frames[index]);
|
||||
|
||||
const FrameBubble* bubble = model->current_bubble;
|
||||
if(bubble) {
|
||||
if((model->current_frame >= bubble->start_frame) &&
|
||||
(model->current_frame <= bubble->end_frame)) {
|
||||
const Bubble* b = &bubble->bubble;
|
||||
elements_bubble_str(canvas, b->x, b->y, b->text, b->align_h, b->align_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const FrameBubble*
|
||||
bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
|
||||
const FrameBubble* bubble = NULL;
|
||||
|
||||
if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t index =
|
||||
furi_hal_random_get() % (active ? model->active_bubbles : model->passive_bubbles);
|
||||
const BubbleAnimation* animation = model->current;
|
||||
|
||||
for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
|
||||
if((animation->frame_bubble_sequences[i]->start_frame < animation->passive_frames) ^
|
||||
active) {
|
||||
if(!index) {
|
||||
bubble = animation->frame_bubble_sequences[i];
|
||||
}
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
return bubble;
|
||||
}
|
||||
|
||||
static bool bubble_animation_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event);
|
||||
|
||||
BubbleAnimationView* animation_view = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bubble_animation_activate(animation_view, false);
|
||||
}
|
||||
|
||||
if(event->key == InputKeyRight) {
|
||||
/* Right button reserved for animation activation, so consume */
|
||||
consumed = true;
|
||||
if(event->type == InputTypeShort) {
|
||||
if(animation_view->interact_callback) {
|
||||
animation_view->interact_callback(animation_view->interact_callback_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void bubble_animation_activate(BubbleAnimationView* view, bool force) {
|
||||
furi_assert(view);
|
||||
bool activate = true;
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
if(model->current == NULL) {
|
||||
activate = false;
|
||||
} else if(model->freeze_frame) {
|
||||
activate = false;
|
||||
} else if(model->current->active_frames == 0) {
|
||||
activate = false;
|
||||
}
|
||||
|
||||
if(model->current != NULL) {
|
||||
if(!force) {
|
||||
if((model->active_ended_at + model->current->active_cooldown * 1000) >
|
||||
xTaskGetTickCount()) {
|
||||
activate = false;
|
||||
} else if(model->active_shift) {
|
||||
activate = false;
|
||||
} else if(model->current_frame >= model->current->passive_frames) {
|
||||
activate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
view_commit_model(view->view, false);
|
||||
|
||||
if(!activate && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(ACTIVE_SHIFT > 0) {
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
model->active_shift = ACTIVE_SHIFT;
|
||||
view_commit_model(view->view, false);
|
||||
} else {
|
||||
bubble_animation_activate_right_now(view);
|
||||
}
|
||||
}
|
||||
|
||||
static void bubble_animation_activate_right_now(BubbleAnimationView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
uint8_t frame_rate = 0;
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) {
|
||||
model->current_frame = model->current->passive_frames;
|
||||
model->current_bubble = bubble_animation_pick_bubble(model, true);
|
||||
frame_rate = model->current->icon_animation.frame_rate;
|
||||
}
|
||||
view_commit_model(view->view, true);
|
||||
|
||||
if(frame_rate) {
|
||||
furi_timer_start(view->timer, 1000 / frame_rate);
|
||||
}
|
||||
}
|
||||
|
||||
static void bubble_animation_next_frame(BubbleAnimationViewModel* model) {
|
||||
furi_assert(model);
|
||||
|
||||
if(!model->current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(model->current_frame < model->current->passive_frames) {
|
||||
model->current_frame = (model->current_frame + 1) % model->current->passive_frames;
|
||||
} else {
|
||||
++model->current_frame;
|
||||
model->active_cycle +=
|
||||
!((model->current_frame - model->current->passive_frames) %
|
||||
model->current->active_frames);
|
||||
if(model->active_cycle >= model->current->active_cycles) {
|
||||
// switch to passive
|
||||
model->active_cycle = 0;
|
||||
model->current_frame = 0;
|
||||
model->current_bubble = bubble_animation_pick_bubble(model, false);
|
||||
model->active_ended_at = xTaskGetTickCount();
|
||||
}
|
||||
|
||||
if(model->current_bubble) {
|
||||
if(model->current_frame > model->current_bubble->end_frame) {
|
||||
model->current_bubble = model->current_bubble->next_bubble;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bubble_animation_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
BubbleAnimationView* view = context;
|
||||
bool activate = false;
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
|
||||
if(model->active_shift > 0) {
|
||||
activate = (--model->active_shift == 0);
|
||||
}
|
||||
|
||||
if(!model->freeze_frame && !activate) {
|
||||
bubble_animation_next_frame(model);
|
||||
}
|
||||
|
||||
view_commit_model(view->view, !activate);
|
||||
|
||||
if(activate) {
|
||||
bubble_animation_activate_right_now(view);
|
||||
}
|
||||
}
|
||||
|
||||
/* always freeze first passive frame, because
|
||||
* animation is always activated at unfreezing and played
|
||||
* passive frame first, and 2 frames after - active
|
||||
*/
|
||||
static Icon* bubble_animation_clone_first_frame(const Icon* icon_orig) {
|
||||
furi_assert(icon_orig);
|
||||
furi_assert(icon_orig->frames);
|
||||
furi_assert(icon_orig->frames[0]);
|
||||
|
||||
Icon* icon_clone = malloc(sizeof(Icon));
|
||||
memcpy(icon_clone, icon_orig, sizeof(Icon));
|
||||
|
||||
icon_clone->frames = malloc(sizeof(uint8_t*));
|
||||
/* icon bitmap can be either compressed or not. It is compressed if
|
||||
* compressed size is less than original, so max size for bitmap is
|
||||
* uncompressed (width * height) + 1 byte (in uncompressed case)
|
||||
* for compressed header
|
||||
*/
|
||||
size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1;
|
||||
FURI_CONST_ASSIGN_PTR(icon_clone->frames[0], malloc(max_bitmap_size));
|
||||
memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size);
|
||||
FURI_CONST_ASSIGN(icon_clone->frame_count, 1);
|
||||
|
||||
return icon_clone;
|
||||
}
|
||||
|
||||
static void bubble_animation_release_frame(Icon** icon) {
|
||||
furi_assert(icon);
|
||||
furi_assert(*icon);
|
||||
|
||||
free((void*)(*icon)->frames[0]);
|
||||
free((void*)(*icon)->frames);
|
||||
free(*icon);
|
||||
*icon = NULL;
|
||||
}
|
||||
|
||||
static void bubble_animation_enter(void* context) {
|
||||
furi_assert(context);
|
||||
BubbleAnimationView* view = context;
|
||||
bubble_animation_activate(view, false);
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
uint8_t frame_rate = 0;
|
||||
if(model->current != NULL) {
|
||||
frame_rate = model->current->icon_animation.frame_rate;
|
||||
}
|
||||
view_commit_model(view->view, false);
|
||||
|
||||
if(frame_rate) {
|
||||
furi_timer_start(view->timer, 1000 / frame_rate);
|
||||
}
|
||||
}
|
||||
|
||||
static void bubble_animation_exit(void* context) {
|
||||
furi_assert(context);
|
||||
BubbleAnimationView* view = context;
|
||||
furi_timer_stop(view->timer);
|
||||
}
|
||||
|
||||
BubbleAnimationView* bubble_animation_view_alloc(void) {
|
||||
BubbleAnimationView* view = malloc(sizeof(BubbleAnimationView));
|
||||
view->view = view_alloc();
|
||||
view->interact_callback = NULL;
|
||||
view->timer = furi_timer_alloc(bubble_animation_timer_callback, FuriTimerTypePeriodic, view);
|
||||
|
||||
view_allocate_model(view->view, ViewModelTypeLocking, sizeof(BubbleAnimationViewModel));
|
||||
view_set_context(view->view, view);
|
||||
view_set_draw_callback(view->view, bubble_animation_draw_callback);
|
||||
view_set_input_callback(view->view, bubble_animation_input_callback);
|
||||
view_set_enter_callback(view->view, bubble_animation_enter);
|
||||
view_set_exit_callback(view->view, bubble_animation_exit);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
void bubble_animation_view_free(BubbleAnimationView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
view_set_draw_callback(view->view, NULL);
|
||||
view_set_input_callback(view->view, NULL);
|
||||
view_set_context(view->view, NULL);
|
||||
|
||||
view_free(view->view);
|
||||
view->view = NULL;
|
||||
free(view);
|
||||
}
|
||||
|
||||
void bubble_animation_view_set_interact_callback(
|
||||
BubbleAnimationView* view,
|
||||
BubbleAnimationInteractCallback callback,
|
||||
void* context) {
|
||||
furi_assert(view);
|
||||
|
||||
view->interact_callback_context = context;
|
||||
view->interact_callback = callback;
|
||||
}
|
||||
|
||||
void bubble_animation_view_set_animation(
|
||||
BubbleAnimationView* view,
|
||||
const BubbleAnimation* new_animation) {
|
||||
furi_assert(view);
|
||||
furi_assert(new_animation);
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
furi_assert(model);
|
||||
model->current = new_animation;
|
||||
|
||||
model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000);
|
||||
model->active_bubbles = 0;
|
||||
model->passive_bubbles = 0;
|
||||
for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) {
|
||||
if(new_animation->frame_bubble_sequences[i]->start_frame < new_animation->passive_frames) {
|
||||
++model->passive_bubbles;
|
||||
} else {
|
||||
++model->active_bubbles;
|
||||
}
|
||||
}
|
||||
|
||||
/* select bubble sequence */
|
||||
model->current_bubble = bubble_animation_pick_bubble(model, false);
|
||||
model->current_frame = 0;
|
||||
model->active_cycle = 0;
|
||||
view_commit_model(view->view, true);
|
||||
|
||||
furi_timer_start(view->timer, 1000 / new_animation->icon_animation.frame_rate);
|
||||
}
|
||||
|
||||
void bubble_animation_freeze(BubbleAnimationView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
furi_assert(model->current);
|
||||
furi_assert(!model->freeze_frame);
|
||||
model->freeze_frame = bubble_animation_clone_first_frame(&model->current->icon_animation);
|
||||
model->current = NULL;
|
||||
view_commit_model(view->view, false);
|
||||
furi_timer_stop(view->timer);
|
||||
}
|
||||
|
||||
void bubble_animation_unfreeze(BubbleAnimationView* view) {
|
||||
furi_assert(view);
|
||||
uint8_t frame_rate;
|
||||
|
||||
BubbleAnimationViewModel* model = view_get_model(view->view);
|
||||
furi_assert(model->freeze_frame);
|
||||
bubble_animation_release_frame(&model->freeze_frame);
|
||||
furi_assert(model->current);
|
||||
frame_rate = model->current->icon_animation.frame_rate;
|
||||
view_commit_model(view->view, true);
|
||||
|
||||
furi_timer_start(view->timer, 1000 / frame_rate);
|
||||
bubble_animation_activate(view, false);
|
||||
}
|
||||
|
||||
View* bubble_animation_get_view(BubbleAnimationView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
return view->view;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../animation_manager.h"
|
||||
|
||||
/** Bubble Animation instance */
|
||||
typedef struct BubbleAnimationView BubbleAnimationView;
|
||||
|
||||
/** Callback type to be called when interact button pressed */
|
||||
typedef void (*BubbleAnimationInteractCallback)(void*);
|
||||
|
||||
/**
|
||||
* Allocate bubble animation view.
|
||||
* This is animation with bubbles, and 2 phases:
|
||||
* active and passive.
|
||||
*
|
||||
* @return instance of new bubble animation
|
||||
*/
|
||||
BubbleAnimationView* bubble_animation_view_alloc(void);
|
||||
|
||||
/**
|
||||
* Free bubble animation view.
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
*/
|
||||
void bubble_animation_view_free(BubbleAnimationView* view);
|
||||
|
||||
/**
|
||||
* Set callback for interact action for animation.
|
||||
* Currently this is right button.
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
* @callback callback to call when button pressed
|
||||
* @context context
|
||||
*/
|
||||
void bubble_animation_view_set_interact_callback(
|
||||
BubbleAnimationView* view,
|
||||
BubbleAnimationInteractCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Set new animation.
|
||||
* BubbleAnimation doesn't posses Bubble Animation object
|
||||
* so it doesn't handle any memory manipulation on Bubble Animations.
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
* @new_bubble_animation new animation to set
|
||||
*/
|
||||
void bubble_animation_view_set_animation(
|
||||
BubbleAnimationView* view,
|
||||
const BubbleAnimation* new_bubble_animation);
|
||||
|
||||
/**
|
||||
* Get view of bubble animation.
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
* @return view
|
||||
*/
|
||||
View* bubble_animation_get_view(BubbleAnimationView* view);
|
||||
|
||||
/**
|
||||
* Freeze current playing animation. Saves a frame to be shown
|
||||
* during next unfreeze called.
|
||||
* bubble_animation_freeze() stops any reference to 'current' animation
|
||||
* so it can be freed. Therefore lock unfreeze should be preceeded with
|
||||
* new animation set.
|
||||
*
|
||||
* Freeze/Unfreeze usage example:
|
||||
*
|
||||
* animation_view_alloc()
|
||||
* set_animation()
|
||||
* ...
|
||||
* freeze_animation()
|
||||
* // release animation
|
||||
* ...
|
||||
* // allocate animation
|
||||
* set_animation()
|
||||
* unfreeze()
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
*/
|
||||
void bubble_animation_freeze(BubbleAnimationView* view);
|
||||
|
||||
/**
|
||||
* Starts bubble animation after freezing.
|
||||
*
|
||||
* @view bubble animation view instance
|
||||
*/
|
||||
void bubble_animation_unfreeze(BubbleAnimationView* view);
|
||||
@@ -0,0 +1,130 @@
|
||||
|
||||
#include "one_shot_animation_view.h"
|
||||
#include <furi.h>
|
||||
#include <portmacro.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*OneShotInteractCallback)(void*);
|
||||
|
||||
struct OneShotView {
|
||||
View* view;
|
||||
TimerHandle_t update_timer;
|
||||
OneShotInteractCallback interact_callback;
|
||||
void* interact_callback_context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const Icon* icon;
|
||||
uint32_t index;
|
||||
bool block_input;
|
||||
} OneShotViewModel;
|
||||
|
||||
static void one_shot_view_update_timer_callback(TimerHandle_t xTimer) {
|
||||
OneShotView* view = (void*)pvTimerGetTimerID(xTimer);
|
||||
|
||||
OneShotViewModel* model = view_get_model(view->view);
|
||||
if((model->index + 1) < model->icon->frame_count) {
|
||||
++model->index;
|
||||
} else {
|
||||
model->block_input = false;
|
||||
model->index = model->icon->frame_count - 2;
|
||||
}
|
||||
view_commit_model(view->view, true);
|
||||
}
|
||||
|
||||
static void one_shot_view_draw(Canvas* canvas, void* model_) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(model_);
|
||||
|
||||
OneShotViewModel* model = model_;
|
||||
furi_check(model->index < model->icon->frame_count);
|
||||
uint8_t y_offset = canvas_height(canvas) - model->icon->height;
|
||||
canvas_draw_bitmap(
|
||||
canvas,
|
||||
0,
|
||||
y_offset,
|
||||
model->icon->width,
|
||||
model->icon->height,
|
||||
model->icon->frames[model->index]);
|
||||
}
|
||||
|
||||
static bool one_shot_view_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event);
|
||||
|
||||
OneShotView* view = context;
|
||||
bool consumed = false;
|
||||
|
||||
OneShotViewModel* model = view_get_model(view->view);
|
||||
consumed = model->block_input;
|
||||
view_commit_model(view->view, false);
|
||||
|
||||
if(!consumed) {
|
||||
if(event->key == InputKeyRight) {
|
||||
/* Right button reserved for animation activation, so consume */
|
||||
consumed = true;
|
||||
if(event->type == InputTypeShort) {
|
||||
if(view->interact_callback) {
|
||||
view->interact_callback(view->interact_callback_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
OneShotView* one_shot_view_alloc(void) {
|
||||
OneShotView* view = malloc(sizeof(OneShotView));
|
||||
view->view = view_alloc();
|
||||
view->update_timer =
|
||||
xTimerCreate(NULL, 1000, pdTRUE, view, one_shot_view_update_timer_callback);
|
||||
|
||||
view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel));
|
||||
view_set_context(view->view, view);
|
||||
view_set_draw_callback(view->view, one_shot_view_draw);
|
||||
view_set_input_callback(view->view, one_shot_view_input);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
void one_shot_view_free(OneShotView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
xTimerDelete(view->update_timer, portMAX_DELAY);
|
||||
view_free(view->view);
|
||||
view->view = NULL;
|
||||
free(view);
|
||||
}
|
||||
|
||||
void one_shot_view_set_interact_callback(
|
||||
OneShotView* view,
|
||||
OneShotInteractCallback callback,
|
||||
void* context) {
|
||||
furi_assert(view);
|
||||
|
||||
view->interact_callback_context = context;
|
||||
view->interact_callback = callback;
|
||||
}
|
||||
|
||||
void one_shot_view_start_animation(OneShotView* view, const Icon* icon) {
|
||||
furi_assert(view);
|
||||
furi_assert(icon);
|
||||
furi_check(icon->frame_count >= 2);
|
||||
|
||||
OneShotViewModel* model = view_get_model(view->view);
|
||||
model->index = 0;
|
||||
model->icon = icon;
|
||||
model->block_input = true;
|
||||
view_commit_model(view->view, true);
|
||||
xTimerChangePeriod(view->update_timer, 1000 / model->icon->frame_rate, portMAX_DELAY);
|
||||
}
|
||||
|
||||
View* one_shot_view_get_view(OneShotView* view) {
|
||||
furi_assert(view);
|
||||
|
||||
return view->view;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/view.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*OneShotInteractCallback)(void*);
|
||||
typedef struct OneShotView OneShotView;
|
||||
|
||||
OneShotView* one_shot_view_alloc(void);
|
||||
void one_shot_view_free(OneShotView* view);
|
||||
void one_shot_view_set_interact_callback(
|
||||
OneShotView* view,
|
||||
OneShotInteractCallback callback,
|
||||
void* context);
|
||||
void one_shot_view_start_animation(OneShotView* view, const Icon* icon);
|
||||
View* one_shot_view_get_view(OneShotView* view);
|
||||
17
applications/services/desktop/application.fam
Normal file
17
applications/services/desktop/application.fam
Normal file
@@ -0,0 +1,17 @@
|
||||
App(
|
||||
appid="desktop",
|
||||
name="DesktopSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="desktop_srv",
|
||||
cdefines=["SRV_DESKTOP"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dolphin",
|
||||
"storage",
|
||||
"input",
|
||||
],
|
||||
provides=["desktop_settings"],
|
||||
conflicts=["updater"],
|
||||
stack_size=2 * 1024,
|
||||
order=60,
|
||||
)
|
||||
362
applications/services/desktop/desktop.c
Normal file
362
applications/services/desktop/desktop.c
Normal file
@@ -0,0 +1,362 @@
|
||||
#include <storage/storage.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "animations/animation_manager.h"
|
||||
#include "desktop/scenes/desktop_scene.h"
|
||||
#include "desktop/scenes/desktop_scene_i.h"
|
||||
#include "desktop/views/desktop_view_locked.h"
|
||||
#include "desktop/views/desktop_view_pin_input.h"
|
||||
#include "desktop/views/desktop_view_pin_timeout.h"
|
||||
#include "desktop_i.h"
|
||||
#include "helpers/pin_lock.h"
|
||||
#include "helpers/slideshow_filename.h"
|
||||
|
||||
static void desktop_auto_lock_arm(Desktop*);
|
||||
static void desktop_auto_lock_inhibit(Desktop*);
|
||||
static void desktop_start_auto_lock_timer(Desktop*);
|
||||
|
||||
static void desktop_loader_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
const LoaderEvent* event = message;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStarted) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted);
|
||||
} else if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished);
|
||||
}
|
||||
}
|
||||
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
|
||||
UNUSED(context);
|
||||
furi_assert(canvas);
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8);
|
||||
}
|
||||
|
||||
static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) {
|
||||
UNUSED(context);
|
||||
furi_assert(canvas);
|
||||
canvas_draw_icon(canvas, 0, 0, &I_GameMode_11x8);
|
||||
}
|
||||
|
||||
static bool desktop_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
switch(event) {
|
||||
case DesktopGlobalBeforeAppStarted:
|
||||
animation_manager_unload_and_stall_animation(desktop->animation_manager);
|
||||
desktop_auto_lock_inhibit(desktop);
|
||||
return true;
|
||||
case DesktopGlobalAfterAppFinished:
|
||||
animation_manager_load_and_continue_animation(desktop->animation_manager);
|
||||
// TODO: Implement a message mechanism for loading settings and (optionally)
|
||||
// locking and unlocking
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_auto_lock_arm(desktop);
|
||||
return true;
|
||||
case DesktopGlobalAutoLock:
|
||||
if(!loader_is_locked(desktop->loader)) {
|
||||
desktop_lock(desktop);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return scene_manager_handle_custom_event(desktop->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool desktop_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
return scene_manager_handle_back_event(desktop->scene_manager);
|
||||
}
|
||||
|
||||
static void desktop_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void desktop_input_event_callback(const void* value, void* context) {
|
||||
furi_assert(value);
|
||||
furi_assert(context);
|
||||
const InputEvent* event = value;
|
||||
Desktop* desktop = context;
|
||||
if(event->type == InputTypePress) {
|
||||
desktop_start_auto_lock_timer(desktop);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_auto_lock_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAutoLock);
|
||||
}
|
||||
|
||||
static void desktop_start_auto_lock_timer(Desktop* desktop) {
|
||||
furi_timer_start(
|
||||
desktop->auto_lock_timer, furi_ms_to_ticks(desktop->settings.auto_lock_delay_ms));
|
||||
}
|
||||
|
||||
static void desktop_stop_auto_lock_timer(Desktop* desktop) {
|
||||
furi_timer_stop(desktop->auto_lock_timer);
|
||||
}
|
||||
|
||||
static void desktop_auto_lock_arm(Desktop* desktop) {
|
||||
if(desktop->settings.auto_lock_delay_ms) {
|
||||
desktop->input_events_subscription = furi_pubsub_subscribe(
|
||||
desktop->input_events_pubsub, desktop_input_event_callback, desktop);
|
||||
desktop_start_auto_lock_timer(desktop);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_auto_lock_inhibit(Desktop* desktop) {
|
||||
desktop_stop_auto_lock_timer(desktop);
|
||||
if(desktop->input_events_subscription) {
|
||||
furi_pubsub_unsubscribe(desktop->input_events_pubsub, desktop->input_events_subscription);
|
||||
desktop->input_events_subscription = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_lock(Desktop* desktop) {
|
||||
desktop_auto_lock_inhibit(desktop);
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_FIRST_ENTER);
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
|
||||
notification_message(desktop->notification, &sequence_display_backlight_off_delay_1000);
|
||||
}
|
||||
|
||||
void desktop_unlock(Desktop* desktop) {
|
||||
view_port_enabled_set(desktop->lock_icon_viewport, false);
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_set_lockdown(gui, false);
|
||||
furi_record_close(RECORD_GUI);
|
||||
desktop_view_locked_unlock(desktop->locked_view);
|
||||
scene_manager_search_and_switch_to_previous_scene(desktop->scene_manager, DesktopSceneMain);
|
||||
desktop_auto_lock_arm(desktop);
|
||||
}
|
||||
|
||||
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
|
||||
view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled);
|
||||
desktop_main_set_dummy_mode_state(desktop->main_view, enabled);
|
||||
desktop->settings.dummy_mode = enabled;
|
||||
DESKTOP_SETTINGS_SAVE(&desktop->settings);
|
||||
}
|
||||
|
||||
Desktop* desktop_alloc() {
|
||||
Desktop* desktop = malloc(sizeof(Desktop));
|
||||
|
||||
desktop->animation_manager = animation_manager_alloc();
|
||||
desktop->gui = furi_record_open(RECORD_GUI);
|
||||
desktop->scene_thread = furi_thread_alloc();
|
||||
desktop->view_dispatcher = view_dispatcher_alloc();
|
||||
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
|
||||
|
||||
view_dispatcher_enable_queue(desktop->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
desktop->view_dispatcher, desktop_tick_event_callback, 500);
|
||||
|
||||
view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
desktop->view_dispatcher, desktop_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
desktop->view_dispatcher, desktop_back_event_callback);
|
||||
|
||||
desktop->lock_menu = desktop_lock_menu_alloc();
|
||||
desktop->debug_view = desktop_debug_alloc();
|
||||
desktop->hw_mismatch_popup = popup_alloc();
|
||||
desktop->locked_view = desktop_view_locked_alloc();
|
||||
desktop->pin_input_view = desktop_view_pin_input_alloc();
|
||||
desktop->pin_timeout_view = desktop_view_pin_timeout_alloc();
|
||||
desktop->slideshow_view = desktop_view_slideshow_alloc();
|
||||
|
||||
desktop->main_view_stack = view_stack_alloc();
|
||||
desktop->main_view = desktop_main_alloc();
|
||||
View* dolphin_view = animation_manager_get_animation_view(desktop->animation_manager);
|
||||
view_stack_add_view(desktop->main_view_stack, desktop_main_get_view(desktop->main_view));
|
||||
view_stack_add_view(desktop->main_view_stack, dolphin_view);
|
||||
view_stack_add_view(
|
||||
desktop->main_view_stack, desktop_view_locked_get_view(desktop->locked_view));
|
||||
|
||||
/* locked view (as animation view) attends in 2 scenes: main & locked,
|
||||
* because it has to draw "Unlocked" label on main scene */
|
||||
desktop->locked_view_stack = view_stack_alloc();
|
||||
view_stack_add_view(desktop->locked_view_stack, dolphin_view);
|
||||
view_stack_add_view(
|
||||
desktop->locked_view_stack, desktop_view_locked_get_view(desktop->locked_view));
|
||||
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdMain,
|
||||
view_stack_get_view(desktop->main_view_stack));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdLocked,
|
||||
view_stack_get_view(desktop->locked_view_stack));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdLockMenu,
|
||||
desktop_lock_menu_get_view(desktop->lock_menu));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdHwMismatch,
|
||||
popup_get_view(desktop->hw_mismatch_popup));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdPinTimeout,
|
||||
desktop_view_pin_timeout_get_view(desktop->pin_timeout_view));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdPinInput,
|
||||
desktop_view_pin_input_get_view(desktop->pin_input_view));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdSlideshow,
|
||||
desktop_view_slideshow_get_view(desktop->slideshow_view));
|
||||
|
||||
// Lock icon
|
||||
desktop->lock_icon_viewport = view_port_alloc();
|
||||
view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8));
|
||||
view_port_draw_callback_set(
|
||||
desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop);
|
||||
view_port_enabled_set(desktop->lock_icon_viewport, false);
|
||||
gui_add_view_port(desktop->gui, desktop->lock_icon_viewport, GuiLayerStatusBarLeft);
|
||||
|
||||
// Dummy mode icon
|
||||
desktop->dummy_mode_icon_viewport = view_port_alloc();
|
||||
view_port_set_width(desktop->dummy_mode_icon_viewport, icon_get_width(&I_GameMode_11x8));
|
||||
view_port_draw_callback_set(
|
||||
desktop->dummy_mode_icon_viewport, desktop_dummy_mode_icon_draw_callback, desktop);
|
||||
view_port_enabled_set(desktop->dummy_mode_icon_viewport, false);
|
||||
gui_add_view_port(desktop->gui, desktop->dummy_mode_icon_viewport, GuiLayerStatusBarLeft);
|
||||
|
||||
// Special case: autostart application is already running
|
||||
desktop->loader = furi_record_open(RECORD_LOADER);
|
||||
if(loader_is_locked(desktop->loader) &&
|
||||
animation_manager_is_animation_loaded(desktop->animation_manager)) {
|
||||
animation_manager_unload_and_stall_animation(desktop->animation_manager);
|
||||
}
|
||||
|
||||
desktop->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
desktop->app_start_stop_subscription = furi_pubsub_subscribe(
|
||||
loader_get_pubsub(desktop->loader), desktop_loader_callback, desktop);
|
||||
|
||||
desktop->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
|
||||
desktop->input_events_subscription = NULL;
|
||||
|
||||
desktop->auto_lock_timer =
|
||||
furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop);
|
||||
|
||||
return desktop;
|
||||
}
|
||||
|
||||
void desktop_free(Desktop* desktop) {
|
||||
furi_assert(desktop);
|
||||
|
||||
furi_pubsub_unsubscribe(
|
||||
loader_get_pubsub(desktop->loader), desktop->app_start_stop_subscription);
|
||||
|
||||
if(desktop->input_events_subscription) {
|
||||
furi_pubsub_unsubscribe(desktop->input_events_pubsub, desktop->input_events_subscription);
|
||||
desktop->input_events_subscription = NULL;
|
||||
}
|
||||
|
||||
desktop->loader = NULL;
|
||||
desktop->input_events_pubsub = NULL;
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_INPUT_EVENTS);
|
||||
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdMain);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput);
|
||||
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout);
|
||||
|
||||
view_dispatcher_free(desktop->view_dispatcher);
|
||||
scene_manager_free(desktop->scene_manager);
|
||||
|
||||
animation_manager_free(desktop->animation_manager);
|
||||
view_stack_free(desktop->main_view_stack);
|
||||
desktop_main_free(desktop->main_view);
|
||||
view_stack_free(desktop->locked_view_stack);
|
||||
desktop_view_locked_free(desktop->locked_view);
|
||||
desktop_lock_menu_free(desktop->lock_menu);
|
||||
desktop_view_locked_free(desktop->locked_view);
|
||||
desktop_debug_free(desktop->debug_view);
|
||||
popup_free(desktop->hw_mismatch_popup);
|
||||
desktop_view_pin_timeout_free(desktop->pin_timeout_view);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
desktop->gui = NULL;
|
||||
|
||||
furi_thread_free(desktop->scene_thread);
|
||||
|
||||
furi_record_close("menu");
|
||||
|
||||
furi_timer_free(desktop->auto_lock_timer);
|
||||
|
||||
free(desktop);
|
||||
}
|
||||
|
||||
static bool desktop_check_file_flag(const char* flag_path) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
int32_t desktop_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Desktop* desktop = desktop_alloc();
|
||||
|
||||
bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
if(!loaded) {
|
||||
memset(&desktop->settings, 0, sizeof(desktop->settings));
|
||||
DESKTOP_SETTINGS_SAVE(&desktop->settings);
|
||||
}
|
||||
|
||||
view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode);
|
||||
desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode);
|
||||
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
|
||||
|
||||
desktop_pin_lock_init(&desktop->settings);
|
||||
|
||||
if(!desktop_pin_lock_is_locked()) {
|
||||
if(!loader_is_locked(desktop->loader)) {
|
||||
desktop_auto_lock_arm(desktop);
|
||||
}
|
||||
} else {
|
||||
desktop_lock(desktop);
|
||||
}
|
||||
|
||||
if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
|
||||
}
|
||||
|
||||
if(!furi_hal_version_do_i_belong_here()) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_get_fault_data()) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault);
|
||||
}
|
||||
|
||||
view_dispatcher_run(desktop->view_dispatcher);
|
||||
desktop_free(desktop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
3
applications/services/desktop/desktop.h
Normal file
3
applications/services/desktop/desktop.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct Desktop Desktop;
|
||||
79
applications/services/desktop/desktop_i.h
Normal file
79
applications/services/desktop/desktop_i.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "desktop.h"
|
||||
#include "animations/animation_manager.h"
|
||||
#include "views/desktop_view_pin_timeout.h"
|
||||
#include "views/desktop_view_pin_input.h"
|
||||
#include "views/desktop_view_locked.h"
|
||||
#include "views/desktop_view_main.h"
|
||||
#include "views/desktop_view_lock_menu.h"
|
||||
#include "views/desktop_view_debug.h"
|
||||
#include "views/desktop_view_slideshow.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
#include <loader/loader.h>
|
||||
#include <notification/notification_app.h>
|
||||
|
||||
#define STATUS_BAR_Y_SHIFT 13
|
||||
|
||||
typedef enum {
|
||||
DesktopViewIdMain,
|
||||
DesktopViewIdLockMenu,
|
||||
DesktopViewIdLocked,
|
||||
DesktopViewIdDebug,
|
||||
DesktopViewIdHwMismatch,
|
||||
DesktopViewIdPinInput,
|
||||
DesktopViewIdPinTimeout,
|
||||
DesktopViewIdSlideshow,
|
||||
DesktopViewIdTotal,
|
||||
} DesktopViewId;
|
||||
|
||||
struct Desktop {
|
||||
// Scene
|
||||
FuriThread* scene_thread;
|
||||
// GUI
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
|
||||
Popup* hw_mismatch_popup;
|
||||
DesktopLockMenuView* lock_menu;
|
||||
DesktopDebugView* debug_view;
|
||||
DesktopViewLocked* locked_view;
|
||||
DesktopMainView* main_view;
|
||||
DesktopViewPinTimeout* pin_timeout_view;
|
||||
DesktopSlideshowView* slideshow_view;
|
||||
|
||||
ViewStack* main_view_stack;
|
||||
ViewStack* locked_view_stack;
|
||||
|
||||
DesktopSettings settings;
|
||||
DesktopViewPinInput* pin_input_view;
|
||||
|
||||
ViewPort* lock_icon_viewport;
|
||||
ViewPort* dummy_mode_icon_viewport;
|
||||
|
||||
AnimationManager* animation_manager;
|
||||
|
||||
Loader* loader;
|
||||
NotificationApp* notification;
|
||||
|
||||
FuriPubSubSubscription* app_start_stop_subscription;
|
||||
FuriPubSub* input_events_pubsub;
|
||||
FuriPubSubSubscription* input_events_subscription;
|
||||
FuriTimer* auto_lock_timer;
|
||||
};
|
||||
|
||||
Desktop* desktop_alloc();
|
||||
|
||||
void desktop_free(Desktop* desktop);
|
||||
void desktop_lock(Desktop* desktop);
|
||||
void desktop_unlock(Desktop* desktop);
|
||||
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled);
|
||||
50
applications/services/desktop/desktop_settings.h
Normal file
50
applications/services/desktop/desktop_settings.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "desktop_settings_filename.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <toolbox/saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define DESKTOP_SETTINGS_VER (5)
|
||||
|
||||
#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
|
||||
#define DESKTOP_SETTINGS_MAGIC (0x17)
|
||||
#define PIN_MAX_LENGTH 12
|
||||
|
||||
#define DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG "run_pin_setup"
|
||||
|
||||
#define DESKTOP_SETTINGS_SAVE(x) \
|
||||
saved_struct_save( \
|
||||
DESKTOP_SETTINGS_PATH, \
|
||||
(x), \
|
||||
sizeof(DesktopSettings), \
|
||||
DESKTOP_SETTINGS_MAGIC, \
|
||||
DESKTOP_SETTINGS_VER)
|
||||
|
||||
#define DESKTOP_SETTINGS_LOAD(x) \
|
||||
saved_struct_load( \
|
||||
DESKTOP_SETTINGS_PATH, \
|
||||
(x), \
|
||||
sizeof(DesktopSettings), \
|
||||
DESKTOP_SETTINGS_MAGIC, \
|
||||
DESKTOP_SETTINGS_VER)
|
||||
|
||||
#define MAX_PIN_SIZE 10
|
||||
#define MIN_PIN_SIZE 4
|
||||
|
||||
typedef struct {
|
||||
InputKey data[MAX_PIN_SIZE];
|
||||
uint8_t length;
|
||||
} PinCode;
|
||||
|
||||
typedef struct {
|
||||
uint16_t favorite_primary;
|
||||
uint16_t favorite_secondary;
|
||||
PinCode pin_code;
|
||||
uint8_t is_locked;
|
||||
uint32_t auto_lock_delay_ms;
|
||||
uint8_t dummy_mode;
|
||||
} DesktopSettings;
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define DESKTOP_SETTINGS_FILE_NAME ".desktop.settings"
|
||||
140
applications/services/desktop/helpers/pin_lock.c
Normal file
140
applications/services/desktop/helpers/pin_lock.c
Normal file
@@ -0,0 +1,140 @@
|
||||
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <stddef.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include "../helpers/pin_lock.h"
|
||||
#include "../desktop_i.h"
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_vcp.h>
|
||||
|
||||
static const NotificationSequence sequence_pin_fail = {
|
||||
&message_display_backlight_on,
|
||||
|
||||
&message_red_255,
|
||||
&message_vibro_on,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_red_0,
|
||||
|
||||
&message_delay_250,
|
||||
|
||||
&message_red_255,
|
||||
&message_vibro_on,
|
||||
&message_delay_100,
|
||||
&message_vibro_off,
|
||||
&message_red_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const uint8_t desktop_helpers_fails_timeout[] = {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
30,
|
||||
60,
|
||||
90,
|
||||
120,
|
||||
150,
|
||||
180,
|
||||
/* +60 for every next fail */
|
||||
};
|
||||
|
||||
void desktop_pin_lock_error_notify() {
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(notification, &sequence_pin_fail);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
uint32_t desktop_pin_lock_get_fail_timeout() {
|
||||
uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
|
||||
uint32_t pin_timeout = 0;
|
||||
uint32_t max_index = COUNT_OF(desktop_helpers_fails_timeout) - 1;
|
||||
if(pin_fails <= max_index) {
|
||||
pin_timeout = desktop_helpers_fails_timeout[pin_fails];
|
||||
} else {
|
||||
pin_timeout = desktop_helpers_fails_timeout[max_index] + (pin_fails - max_index) * 60;
|
||||
}
|
||||
|
||||
return pin_timeout;
|
||||
}
|
||||
|
||||
void desktop_pin_lock(DesktopSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
furi_hal_rtc_set_pin_fails(0);
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
settings->is_locked = 1;
|
||||
DESKTOP_SETTINGS_SAVE(settings);
|
||||
}
|
||||
|
||||
void desktop_pin_unlock(DesktopSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
settings->is_locked = 0;
|
||||
DESKTOP_SETTINGS_SAVE(settings);
|
||||
}
|
||||
|
||||
void desktop_pin_lock_init(DesktopSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
if(settings->pin_code.length > 0) {
|
||||
if(settings->is_locked == 1) {
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
|
||||
} else {
|
||||
if(desktop_pin_lock_is_locked()) {
|
||||
settings->is_locked = 1;
|
||||
DESKTOP_SETTINGS_SAVE(settings);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
furi_hal_rtc_set_pin_fails(0);
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
|
||||
}
|
||||
|
||||
if(desktop_pin_lock_is_locked()) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
}
|
||||
|
||||
bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) {
|
||||
bool result = false;
|
||||
if(desktop_pins_are_equal(pin_set, pin_entered)) {
|
||||
furi_hal_rtc_set_pin_fails(0);
|
||||
result = true;
|
||||
} else {
|
||||
uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
|
||||
furi_hal_rtc_set_pin_fails(pin_fails + 1);
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool desktop_pin_lock_is_locked() {
|
||||
return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock);
|
||||
}
|
||||
|
||||
bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2) {
|
||||
furi_assert(pin_code1);
|
||||
furi_assert(pin_code2);
|
||||
bool result = false;
|
||||
|
||||
if(pin_code1->length == pin_code2->length) {
|
||||
result = !memcmp(pin_code1->data, pin_code2->data, pin_code1->length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
21
applications/services/desktop/helpers/pin_lock.h
Normal file
21
applications/services/desktop/helpers/pin_lock.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "../desktop.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
void desktop_pin_lock_error_notify();
|
||||
|
||||
uint32_t desktop_pin_lock_get_fail_timeout();
|
||||
|
||||
void desktop_pin_lock(DesktopSettings* settings);
|
||||
|
||||
void desktop_pin_unlock(DesktopSettings* settings);
|
||||
|
||||
bool desktop_pin_lock_is_locked();
|
||||
|
||||
void desktop_pin_lock_init(DesktopSettings* settings);
|
||||
|
||||
bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered);
|
||||
|
||||
bool desktop_pins_are_equal(const PinCode* pin_code1, const PinCode* pin_code2);
|
||||
125
applications/services/desktop/helpers/slideshow.c
Normal file
125
applications/services/desktop/helpers/slideshow.c
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "slideshow.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <storage/storage.h>
|
||||
#include <gui/icon.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <core/dangerous_defines.h>
|
||||
|
||||
#define SLIDESHOW_MAGIC 0x72676468
|
||||
#define SLIDESHOW_MAX_SUPPORTED_VERSION 1
|
||||
|
||||
struct Slideshow {
|
||||
Icon icon;
|
||||
uint32_t current_frame;
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint8_t version;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t frame_count;
|
||||
} SlideshowFileHeader;
|
||||
_Static_assert(sizeof(SlideshowFileHeader) == 8, "Incorrect SlideshowFileHeader size");
|
||||
|
||||
typedef struct {
|
||||
uint16_t size;
|
||||
} SlideshowFrameHeader;
|
||||
_Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeader size");
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
Slideshow* slideshow_alloc() {
|
||||
Slideshow* ret = malloc(sizeof(Slideshow));
|
||||
ret->loaded = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void slideshow_free(Slideshow* slideshow) {
|
||||
Icon* icon = &slideshow->icon;
|
||||
if(icon) {
|
||||
for(int frame_idx = 0; frame_idx < icon->frame_count; ++frame_idx) {
|
||||
uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx];
|
||||
free(frame_data);
|
||||
}
|
||||
free((uint8_t**)icon->frames);
|
||||
}
|
||||
free(slideshow);
|
||||
}
|
||||
|
||||
bool slideshow_load(Slideshow* slideshow, const char* fspath) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* slideshow_file = storage_file_alloc(storage);
|
||||
slideshow->loaded = false;
|
||||
do {
|
||||
if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
break;
|
||||
}
|
||||
SlideshowFileHeader header;
|
||||
if((storage_file_read(slideshow_file, &header, sizeof(header)) != sizeof(header)) ||
|
||||
(header.magic != SLIDESHOW_MAGIC) ||
|
||||
(header.version > SLIDESHOW_MAX_SUPPORTED_VERSION)) {
|
||||
break;
|
||||
}
|
||||
Icon* icon = &slideshow->icon;
|
||||
FURI_CONST_ASSIGN(icon->frame_count, header.frame_count);
|
||||
FURI_CONST_ASSIGN(icon->width, header.width);
|
||||
FURI_CONST_ASSIGN(icon->height, header.height);
|
||||
icon->frames = malloc(header.frame_count * sizeof(uint8_t*));
|
||||
for(int frame_idx = 0; frame_idx < header.frame_count; ++frame_idx) {
|
||||
SlideshowFrameHeader frame_header;
|
||||
if(storage_file_read(slideshow_file, &frame_header, sizeof(frame_header)) !=
|
||||
sizeof(frame_header)) {
|
||||
break;
|
||||
}
|
||||
FURI_CONST_ASSIGN_PTR(icon->frames[frame_idx], malloc(frame_header.size));
|
||||
uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx];
|
||||
if(storage_file_read(slideshow_file, frame_data, frame_header.size) !=
|
||||
frame_header.size) {
|
||||
break;
|
||||
}
|
||||
slideshow->loaded = (frame_idx + 1) == header.frame_count;
|
||||
}
|
||||
} while(false);
|
||||
storage_file_free(slideshow_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return slideshow->loaded;
|
||||
}
|
||||
|
||||
bool slideshow_is_loaded(Slideshow* slideshow) {
|
||||
return slideshow->loaded;
|
||||
}
|
||||
|
||||
bool slideshow_is_one_page(Slideshow* slideshow) {
|
||||
return slideshow->loaded && (slideshow->icon.frame_count == 1);
|
||||
}
|
||||
|
||||
bool slideshow_advance(Slideshow* slideshow) {
|
||||
uint8_t next_frame = slideshow->current_frame + 1;
|
||||
if(next_frame < slideshow->icon.frame_count) {
|
||||
slideshow->current_frame = next_frame;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void slideshow_goback(Slideshow* slideshow) {
|
||||
if(slideshow->current_frame > 0) {
|
||||
slideshow->current_frame--;
|
||||
}
|
||||
}
|
||||
|
||||
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y) {
|
||||
furi_assert(slideshow->current_frame < slideshow->icon.frame_count);
|
||||
canvas_draw_bitmap(
|
||||
canvas,
|
||||
x,
|
||||
y,
|
||||
slideshow->icon.width,
|
||||
slideshow->icon.height,
|
||||
slideshow->icon.frames[slideshow->current_frame]);
|
||||
}
|
||||
15
applications/services/desktop/helpers/slideshow.h
Normal file
15
applications/services/desktop/helpers/slideshow.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/canvas.h>
|
||||
|
||||
typedef struct Slideshow Slideshow;
|
||||
|
||||
Slideshow* slideshow_alloc();
|
||||
|
||||
void slideshow_free(Slideshow* slideshow);
|
||||
bool slideshow_load(Slideshow* slideshow, const char* fspath);
|
||||
bool slideshow_is_loaded(Slideshow* slideshow);
|
||||
bool slideshow_is_one_page(Slideshow* slideshow);
|
||||
void slideshow_goback(Slideshow* slideshow);
|
||||
bool slideshow_advance(Slideshow* slideshow);
|
||||
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define SLIDESHOW_FILE_NAME ".slideshow"
|
||||
30
applications/services/desktop/scenes/desktop_scene.c
Normal file
30
applications/services/desktop/scenes/desktop_scene.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "desktop_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const desktop_on_enter_handlers[])(void*) = {
|
||||
#include "desktop_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 desktop_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "desktop_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 desktop_on_exit_handlers[])(void* context) = {
|
||||
#include "desktop_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers desktop_scene_handlers = {
|
||||
.on_enter_handlers = desktop_on_enter_handlers,
|
||||
.on_event_handlers = desktop_on_event_handlers,
|
||||
.on_exit_handlers = desktop_on_exit_handlers,
|
||||
.scene_num = DesktopSceneNum,
|
||||
};
|
||||
29
applications/services/desktop/scenes/desktop_scene.h
Normal file
29
applications/services/desktop/scenes/desktop_scene.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) DesktopScene##id,
|
||||
typedef enum {
|
||||
#include "desktop_scene_config.h"
|
||||
DesktopSceneNum,
|
||||
} DesktopScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers desktop_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "desktop_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 "desktop_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 "desktop_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,9 @@
|
||||
ADD_SCENE(desktop, main, Main)
|
||||
ADD_SCENE(desktop, lock_menu, LockMenu)
|
||||
ADD_SCENE(desktop, debug, Debug)
|
||||
ADD_SCENE(desktop, hw_mismatch, HwMismatch)
|
||||
ADD_SCENE(desktop, fault, Fault)
|
||||
ADD_SCENE(desktop, locked, Locked)
|
||||
ADD_SCENE(desktop, pin_input, PinInput)
|
||||
ADD_SCENE(desktop, pin_timeout, PinTimeout)
|
||||
ADD_SCENE(desktop, slideshow, Slideshow)
|
||||
65
applications/services/desktop/scenes/desktop_scene_debug.c
Normal file
65
applications/services/desktop/scenes/desktop_scene_debug.c
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/helpers/dolphin_deed.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_view_debug.h"
|
||||
#include "desktop_scene.h"
|
||||
|
||||
void desktop_scene_debug_callback(DesktopEvent event, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void desktop_scene_debug_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
desktop_debug_get_dolphin_data(desktop->debug_view);
|
||||
|
||||
desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdDebug);
|
||||
}
|
||||
|
||||
bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopDebugEventExit:
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
|
||||
dolphin_flush(dolphin);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopDebugEventDeed:
|
||||
dolphin_deed(dolphin, DolphinDeedTestRight);
|
||||
desktop_debug_get_dolphin_data(desktop->debug_view);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopDebugEventWrongDeed:
|
||||
dolphin_deed(dolphin, DolphinDeedTestLeft);
|
||||
desktop_debug_get_dolphin_data(desktop->debug_view);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopDebugEventSaveState:
|
||||
dolphin_flush(dolphin);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_debug_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
desktop_debug_reset_screen_idx(desktop->debug_view);
|
||||
}
|
||||
52
applications/services/desktop/scenes/desktop_scene_fault.c
Normal file
52
applications/services/desktop/scenes/desktop_scene_fault.c
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
|
||||
#define DesktopFaultEventExit 0x00FF00FF
|
||||
|
||||
void desktop_scene_fault_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopFaultEventExit);
|
||||
}
|
||||
|
||||
void desktop_scene_fault_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
popup_set_context(popup, desktop);
|
||||
popup_set_header(
|
||||
popup,
|
||||
"Flipper crashed\n and was rebooted",
|
||||
60,
|
||||
14 + STATUS_BAR_Y_SHIFT,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
|
||||
char* message = (char*)furi_hal_rtc_get_fault_data();
|
||||
popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_callback(popup, desktop_scene_fault_callback);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch);
|
||||
}
|
||||
|
||||
bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopFaultEventExit:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_fault_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
furi_hal_rtc_set_fault_data(0);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <gui/scene_manager.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "desktop_scene.h"
|
||||
#include "../desktop_i.h"
|
||||
|
||||
#define HW_MISMATCH_BACK_EVENT (0UL)
|
||||
|
||||
void desktop_scene_hw_mismatch_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, HW_MISMATCH_BACK_EVENT);
|
||||
}
|
||||
|
||||
void desktop_scene_hw_mismatch_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
|
||||
char* text_buffer = malloc(256);
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopSceneHwMismatch, (uint32_t)text_buffer);
|
||||
|
||||
snprintf(
|
||||
text_buffer,
|
||||
256,
|
||||
"HW target: %d\nFW target: %d",
|
||||
furi_hal_version_get_hw_target(),
|
||||
version_get_target(NULL));
|
||||
popup_set_context(popup, desktop);
|
||||
popup_set_header(
|
||||
popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_callback(popup, desktop_scene_hw_mismatch_callback);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch);
|
||||
}
|
||||
|
||||
bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case HW_MISMATCH_BACK_EVENT:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_hw_mismatch_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
|
||||
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
popup_set_callback(popup, NULL);
|
||||
popup_set_context(popup, NULL);
|
||||
char* text_buffer =
|
||||
(char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch);
|
||||
free(text_buffer);
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneHwMismatch, 0);
|
||||
}
|
||||
4
applications/services/desktop/scenes/desktop_scene_i.h
Normal file
4
applications/services/desktop/scenes/desktop_scene_i.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define SCENE_LOCKED_FIRST_ENTER 0
|
||||
#define SCENE_LOCKED_REPEAT_ENTER 1
|
||||
@@ -0,0 +1,92 @@
|
||||
#include <gui/scene_manager.h>
|
||||
#include <applications.h>
|
||||
#include <furi_hal.h>
|
||||
#include <toolbox/saved_struct.h>
|
||||
#include <stdbool.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
#include "../views/desktop_view_lock_menu.h"
|
||||
#include "desktop_scene_i.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "../helpers/pin_lock.h"
|
||||
|
||||
#define TAG "DesktopSceneLock"
|
||||
|
||||
void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void desktop_scene_lock_menu_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
|
||||
desktop_lock_menu_set_pin_state(desktop->lock_menu, desktop->settings.pin_code.length > 0);
|
||||
desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode);
|
||||
desktop_lock_menu_set_idx(desktop->lock_menu, 0);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLockMenu);
|
||||
}
|
||||
|
||||
bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
bool check_pin_changed =
|
||||
scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu);
|
||||
if(check_pin_changed) {
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
if(desktop->settings.pin_code.length > 0) {
|
||||
desktop_lock_menu_set_pin_state(desktop->lock_menu, true);
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
}
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopLockMenuEventLock:
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
desktop_lock(desktop);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopLockMenuEventPinLock:
|
||||
if(desktop->settings.pin_code.length > 0) {
|
||||
desktop_pin_lock(&desktop->settings);
|
||||
desktop_lock(desktop);
|
||||
} else {
|
||||
LoaderStatus status =
|
||||
loader_start(desktop->loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG);
|
||||
if(status == LoaderStatusOk) {
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Unable to start desktop settings");
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopLockMenuEventDummyModeOn:
|
||||
desktop_set_dummy_mode_state(desktop, true);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
desktop->scene_manager, DesktopSceneMain);
|
||||
break;
|
||||
case DesktopLockMenuEventDummyModeOff:
|
||||
desktop_set_dummy_mode_state(desktop, false);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
desktop->scene_manager, DesktopSceneMain);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_lock_menu_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
113
applications/services/desktop/scenes/desktop_scene_locked.c
Normal file
113
applications/services/desktop/scenes/desktop_scene_locked.c
Normal file
@@ -0,0 +1,113 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <stdint.h>
|
||||
#include <portmacro.h>
|
||||
|
||||
#include "../desktop.h"
|
||||
#include "../desktop_i.h"
|
||||
#include "../helpers/pin_lock.h"
|
||||
#include "../animations/animation_manager.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include "../views/desktop_view_pin_input.h"
|
||||
#include "../views/desktop_view_locked.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "desktop_scene_i.h"
|
||||
|
||||
#define WRONG_PIN_HEADER_TIMEOUT 3000
|
||||
#define INPUT_PIN_VIEW_TIMEOUT 15000
|
||||
|
||||
static void desktop_scene_locked_callback(DesktopEvent event, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void desktop_scene_locked_new_idle_animation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopAnimationEventNewIdleAnimation);
|
||||
}
|
||||
|
||||
void desktop_scene_locked_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
// callbacks for 1-st layer
|
||||
animation_manager_set_new_idle_callback(
|
||||
desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback);
|
||||
animation_manager_set_check_callback(desktop->animation_manager, NULL);
|
||||
animation_manager_set_interact_callback(desktop->animation_manager, NULL);
|
||||
|
||||
// callbacks for 2-nd layer
|
||||
desktop_view_locked_set_callback(desktop->locked_view, desktop_scene_locked_callback, desktop);
|
||||
|
||||
bool switch_to_timeout_scene = false;
|
||||
uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLocked);
|
||||
if(state == SCENE_LOCKED_FIRST_ENTER) {
|
||||
bool pin_locked = desktop_pin_lock_is_locked();
|
||||
view_port_enabled_set(desktop->lock_icon_viewport, true);
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_set_lockdown(gui, true);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
if(pin_locked) {
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_view_locked_lock(desktop->locked_view, true);
|
||||
uint32_t pin_timeout = desktop_pin_lock_get_fail_timeout();
|
||||
if(pin_timeout > 0) {
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopScenePinTimeout, pin_timeout);
|
||||
switch_to_timeout_scene = true;
|
||||
} else {
|
||||
desktop_view_locked_close_doors(desktop->locked_view);
|
||||
}
|
||||
} else {
|
||||
desktop_view_locked_lock(desktop->locked_view, false);
|
||||
desktop_view_locked_close_doors(desktop->locked_view);
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_REPEAT_ENTER);
|
||||
}
|
||||
|
||||
if(switch_to_timeout_scene) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopScenePinTimeout);
|
||||
} else {
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLocked);
|
||||
}
|
||||
}
|
||||
|
||||
bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopLockedEventUnlocked:
|
||||
desktop_unlock(desktop);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopLockedEventUpdate:
|
||||
if(desktop_view_locked_is_locked_hint_visible(desktop->locked_view)) {
|
||||
notification_message(desktop->notification, &sequence_display_backlight_off);
|
||||
}
|
||||
desktop_view_locked_update(desktop->locked_view);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopLockedEventShowPinInput:
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopScenePinInput);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopAnimationEventNewIdleAnimation:
|
||||
animation_manager_new_idle_process(desktop->animation_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_locked_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
195
applications/services/desktop/scenes/desktop_scene_main.c
Normal file
195
applications/services/desktop/scenes/desktop_scene_main.c
Normal file
@@ -0,0 +1,195 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <applications.h>
|
||||
#include <assets_icons.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include "../views/desktop_view_main.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "desktop_scene_i.h"
|
||||
|
||||
#define TAG "DesktopSrv"
|
||||
|
||||
static void desktop_scene_main_new_idle_animation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopAnimationEventNewIdleAnimation);
|
||||
}
|
||||
|
||||
static void desktop_scene_main_check_animation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopAnimationEventCheckAnimation);
|
||||
}
|
||||
|
||||
static void desktop_scene_main_interact_animation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopAnimationEventInteractAnimation);
|
||||
}
|
||||
|
||||
#ifdef APP_ARCHIVE
|
||||
static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
|
||||
furi_assert(desktop);
|
||||
furi_assert(flipper_app);
|
||||
furi_assert(flipper_app->app);
|
||||
furi_assert(flipper_app->name);
|
||||
|
||||
if(furi_thread_get_state(desktop->scene_thread) != FuriThreadStateStopped) {
|
||||
FURI_LOG_E("Desktop", "Thread is already running");
|
||||
return;
|
||||
}
|
||||
|
||||
furi_thread_set_name(desktop->scene_thread, flipper_app->name);
|
||||
furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size);
|
||||
furi_thread_set_callback(desktop->scene_thread, flipper_app->app);
|
||||
|
||||
furi_thread_start(desktop->scene_thread);
|
||||
}
|
||||
#endif
|
||||
|
||||
void desktop_scene_main_callback(DesktopEvent event, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void desktop_scene_main_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
DesktopMainView* main_view = desktop->main_view;
|
||||
|
||||
animation_manager_set_context(desktop->animation_manager, desktop);
|
||||
animation_manager_set_new_idle_callback(
|
||||
desktop->animation_manager, desktop_scene_main_new_idle_animation_callback);
|
||||
animation_manager_set_check_callback(
|
||||
desktop->animation_manager, desktop_scene_main_check_animation_callback);
|
||||
animation_manager_set_interact_callback(
|
||||
desktop->animation_manager, desktop_scene_main_interact_animation_callback);
|
||||
|
||||
desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdMain);
|
||||
}
|
||||
|
||||
bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopMainEventOpenMenu:
|
||||
loader_show_menu();
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopMainEventOpenLockMenu:
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopMainEventOpenDebug:
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneDebug);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopMainEventOpenArchive:
|
||||
#ifdef APP_ARCHIVE
|
||||
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
|
||||
#endif
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case DesktopMainEventOpenPowerOff: {
|
||||
LoaderStatus status = loader_start(desktop->loader, "Power", "off");
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case DesktopMainEventOpenFavoritePrimary:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
if(desktop->settings.favorite_primary < FLIPPER_APPS_COUNT) {
|
||||
LoaderStatus status = loader_start(
|
||||
desktop->loader, FLIPPER_APPS[desktop->settings.favorite_primary].name, NULL);
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Can't find primary favorite application");
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopMainEventOpenFavoriteSecondary:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
if(desktop->settings.favorite_secondary < FLIPPER_APPS_COUNT) {
|
||||
LoaderStatus status = loader_start(
|
||||
desktop->loader,
|
||||
FLIPPER_APPS[desktop->settings.favorite_secondary].name,
|
||||
NULL);
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Can't find secondary favorite application");
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopAnimationEventCheckAnimation:
|
||||
animation_manager_check_blocking_process(desktop->animation_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopAnimationEventNewIdleAnimation:
|
||||
animation_manager_new_idle_process(desktop->animation_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopAnimationEventInteractAnimation:
|
||||
if(!animation_manager_interact_process(desktop->animation_manager)) {
|
||||
LoaderStatus status = loader_start(desktop->loader, "Passport", NULL);
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopMainEventOpenPassport: {
|
||||
LoaderStatus status = loader_start(desktop->loader, "Passport", NULL);
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DesktopMainEventOpenGameMenu: {
|
||||
LoaderStatus status = loader_start(
|
||||
desktop->loader, "Applications", EXT_PATH("/apps/Games/snake_game.fap"));
|
||||
if(status != LoaderStatusOk) {
|
||||
FURI_LOG_E(TAG, "loader_start failed: %d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DesktopLockedEventUpdate:
|
||||
desktop_view_locked_update(desktop->locked_view);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_main_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
|
||||
animation_manager_set_check_callback(desktop->animation_manager, NULL);
|
||||
animation_manager_set_interact_callback(desktop->animation_manager, NULL);
|
||||
animation_manager_set_context(desktop->animation_manager, desktop);
|
||||
}
|
||||
157
applications/services/desktop/scenes/desktop_scene_pin_input.c
Normal file
157
applications/services/desktop/scenes/desktop_scene_pin_input.c
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <stdint.h>
|
||||
#include <portmacro.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include "../desktop.h"
|
||||
#include "../desktop_i.h"
|
||||
#include "../animations/animation_manager.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include "../views/desktop_view_pin_input.h"
|
||||
#include "../helpers/pin_lock.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "desktop_scene_i.h"
|
||||
|
||||
#define WRONG_PIN_HEADER_TIMEOUT 3000
|
||||
#define INPUT_PIN_VIEW_TIMEOUT 15000
|
||||
|
||||
typedef struct {
|
||||
TimerHandle_t timer;
|
||||
} DesktopScenePinInputState;
|
||||
|
||||
static void desktop_scene_locked_light_red(bool value) {
|
||||
NotificationApp* app = furi_record_open(RECORD_NOTIFICATION);
|
||||
if(value) {
|
||||
notification_message(app, &sequence_set_only_red_255);
|
||||
} else {
|
||||
notification_message(app, &sequence_reset_red);
|
||||
}
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
static void
|
||||
desktop_scene_pin_input_set_timer(Desktop* desktop, bool enable, TickType_t new_period) {
|
||||
furi_assert(desktop);
|
||||
|
||||
DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state(
|
||||
desktop->scene_manager, DesktopScenePinInput);
|
||||
furi_assert(state);
|
||||
if(enable) {
|
||||
xTimerChangePeriod(state->timer, new_period, portMAX_DELAY);
|
||||
} else {
|
||||
xTimerStop(state->timer, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_scene_pin_input_back_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventBack);
|
||||
}
|
||||
|
||||
static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
if(desktop_pin_lock_verify(&desktop->settings.pin_code, pin_code)) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked);
|
||||
} else {
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopPinInputEventUnlockFailed);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_scene_pin_input_timer_callback(TimerHandle_t timer) {
|
||||
Desktop* desktop = pvTimerGetTimerID(timer);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
desktop->view_dispatcher, DesktopPinInputEventResetWrongPinLabel);
|
||||
}
|
||||
|
||||
void desktop_scene_pin_input_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
desktop_view_pin_input_set_context(desktop->pin_input_view, desktop);
|
||||
desktop_view_pin_input_set_back_callback(
|
||||
desktop->pin_input_view, desktop_scene_pin_input_back_callback);
|
||||
desktop_view_pin_input_set_timeout_callback(
|
||||
desktop->pin_input_view, desktop_scene_pin_input_back_callback);
|
||||
desktop_view_pin_input_set_done_callback(
|
||||
desktop->pin_input_view, desktop_scene_pin_input_done_callback);
|
||||
|
||||
DesktopScenePinInputState* state = malloc(sizeof(DesktopScenePinInputState));
|
||||
state->timer =
|
||||
xTimerCreate(NULL, 10000, pdFALSE, desktop, desktop_scene_pin_input_timer_callback);
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopScenePinInput, (uint32_t)state);
|
||||
|
||||
desktop_view_pin_input_hide_pin(desktop->pin_input_view, true);
|
||||
desktop_view_pin_input_set_label_button(desktop->pin_input_view, "OK");
|
||||
desktop_view_pin_input_set_label_secondary(desktop->pin_input_view, 44, 25, "Enter PIN:");
|
||||
desktop_view_pin_input_set_pin_position(desktop->pin_input_view, 64, 37);
|
||||
desktop_view_pin_input_reset_pin(desktop->pin_input_view);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPinInput);
|
||||
}
|
||||
|
||||
bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
uint32_t pin_timeout = 0;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopPinInputEventUnlockFailed:
|
||||
pin_timeout = desktop_pin_lock_get_fail_timeout();
|
||||
if(pin_timeout > 0) {
|
||||
desktop_pin_lock_error_notify();
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopScenePinTimeout, pin_timeout);
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopScenePinTimeout);
|
||||
} else {
|
||||
desktop_scene_locked_light_red(true);
|
||||
desktop_view_pin_input_set_label_primary(desktop->pin_input_view, 0, 0, NULL);
|
||||
desktop_view_pin_input_set_label_secondary(
|
||||
desktop->pin_input_view, 25, 25, "Wrong PIN try again:");
|
||||
desktop_scene_pin_input_set_timer(desktop, true, WRONG_PIN_HEADER_TIMEOUT);
|
||||
desktop_view_pin_input_reset_pin(desktop->pin_input_view);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopPinInputEventResetWrongPinLabel:
|
||||
desktop_scene_locked_light_red(false);
|
||||
desktop_view_pin_input_set_label_primary(desktop->pin_input_view, 0, 0, NULL);
|
||||
desktop_view_pin_input_set_label_secondary(
|
||||
desktop->pin_input_view, 44, 25, "Enter PIN:");
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopPinInputEventUnlocked:
|
||||
desktop_pin_unlock(&desktop->settings);
|
||||
desktop_unlock(desktop);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopPinInputEventBack:
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
desktop->scene_manager, DesktopSceneLocked);
|
||||
notification_message(desktop->notification, &sequence_display_backlight_off);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_pin_input_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
desktop_scene_locked_light_red(false);
|
||||
|
||||
DesktopScenePinInputState* state = (DesktopScenePinInputState*)scene_manager_get_scene_state(
|
||||
desktop->scene_manager, DesktopScenePinInput);
|
||||
xTimerStop(state->timer, portMAX_DELAY);
|
||||
while(xTimerIsTimerActive(state->timer)) {
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
xTimerDelete(state->timer, portMAX_DELAY);
|
||||
free(state);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <furi.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <portmacro.h>
|
||||
#include <timer.h>
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_view_pin_timeout.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "desktop_scene_i.h"
|
||||
|
||||
static void desktop_scene_pin_timeout_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinTimeoutExit);
|
||||
}
|
||||
|
||||
void desktop_scene_pin_timeout_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
uint32_t timeout =
|
||||
scene_manager_get_scene_state(desktop->scene_manager, DesktopScenePinTimeout);
|
||||
desktop_view_pin_timeout_start(desktop->pin_timeout_view, timeout);
|
||||
desktop_view_pin_timeout_set_callback(
|
||||
desktop->pin_timeout_view, desktop_scene_pin_timeout_callback, desktop);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPinTimeout);
|
||||
}
|
||||
|
||||
bool desktop_scene_pin_timeout_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopPinTimeoutExit:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_pin_timeout_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_view_slideshow.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
void desktop_scene_slideshow_callback(DesktopEvent event, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void desktop_scene_slideshow_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
DesktopSlideshowView* slideshow_view = desktop->slideshow_view;
|
||||
|
||||
desktop_view_slideshow_set_callback(slideshow_view, desktop_scene_slideshow_callback, desktop);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdSlideshow);
|
||||
}
|
||||
|
||||
bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
Storage* storage = NULL;
|
||||
Power* power = NULL;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopSlideshowCompleted:
|
||||
storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_remove(storage, SLIDESHOW_FS_PATH);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopSlideshowPoweroff:
|
||||
power = furi_record_open(RECORD_POWER);
|
||||
power_off(power);
|
||||
furi_record_close(RECORD_POWER);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_slideshow_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
47
applications/services/desktop/views/desktop_events.h
Normal file
47
applications/services/desktop/views/desktop_events.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
DesktopMainEventOpenLockMenu,
|
||||
DesktopMainEventOpenArchive,
|
||||
DesktopMainEventOpenFavoritePrimary,
|
||||
DesktopMainEventOpenFavoriteSecondary,
|
||||
DesktopMainEventOpenMenu,
|
||||
DesktopMainEventOpenDebug,
|
||||
DesktopMainEventOpenPassport,
|
||||
DesktopMainEventOpenPowerOff,
|
||||
|
||||
DesktopMainEventOpenGameMenu,
|
||||
|
||||
DesktopLockedEventUnlocked,
|
||||
DesktopLockedEventUpdate,
|
||||
DesktopLockedEventShowPinInput,
|
||||
|
||||
DesktopPinInputEventResetWrongPinLabel,
|
||||
DesktopPinInputEventUnlocked,
|
||||
DesktopPinInputEventUnlockFailed,
|
||||
DesktopPinInputEventBack,
|
||||
|
||||
DesktopPinTimeoutExit,
|
||||
|
||||
DesktopDebugEventDeed,
|
||||
DesktopDebugEventWrongDeed,
|
||||
DesktopDebugEventSaveState,
|
||||
DesktopDebugEventExit,
|
||||
|
||||
DesktopLockMenuEventLock,
|
||||
DesktopLockMenuEventPinLock,
|
||||
DesktopLockMenuEventDummyModeOn,
|
||||
DesktopLockMenuEventDummyModeOff,
|
||||
|
||||
DesktopAnimationEventCheckAnimation,
|
||||
DesktopAnimationEventNewIdleAnimation,
|
||||
DesktopAnimationEventInteractAnimation,
|
||||
|
||||
DesktopSlideshowCompleted,
|
||||
DesktopSlideshowPoweroff,
|
||||
|
||||
// Global events
|
||||
DesktopGlobalBeforeAppStarted,
|
||||
DesktopGlobalAfterAppFinished,
|
||||
DesktopGlobalAutoLock,
|
||||
} DesktopEvent;
|
||||
200
applications/services/desktop/views/desktop_view_debug.c
Normal file
200
applications/services/desktop/views/desktop_view_debug.c
Normal file
@@ -0,0 +1,200 @@
|
||||
#include <toolbox/version.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <dolphin/helpers/dolphin_state.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "desktop_view_debug.h"
|
||||
|
||||
void desktop_debug_set_callback(
|
||||
DesktopDebugView* debug_view,
|
||||
DesktopDebugViewCallback callback,
|
||||
void* context) {
|
||||
furi_assert(debug_view);
|
||||
furi_assert(callback);
|
||||
debug_view->callback = callback;
|
||||
debug_view->context = context;
|
||||
}
|
||||
|
||||
void desktop_debug_render(Canvas* canvas, void* model) {
|
||||
canvas_clear(canvas);
|
||||
DesktopDebugViewModel* m = model;
|
||||
const Version* ver;
|
||||
char buffer[64];
|
||||
|
||||
static const char* headers[] = {"Device Info:", "Dolphin Info:"};
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
if(m->screen != DesktopViewStatsMeta) {
|
||||
// Hardware version
|
||||
const char* my_name = furi_hal_version_get_name_ptr();
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%d.F%dB%dC%d %s:%s %s",
|
||||
furi_hal_version_get_hw_version(),
|
||||
furi_hal_version_get_hw_target(),
|
||||
furi_hal_version_get_hw_body(),
|
||||
furi_hal_version_get_hw_connect(),
|
||||
furi_hal_version_get_hw_region_name(),
|
||||
furi_hal_region_get_name(),
|
||||
my_name ? my_name : "Unknown");
|
||||
canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
ver = furi_hal_version_get_firmware_version();
|
||||
const BleGlueC2Info* c2_ver = NULL;
|
||||
#ifdef SRV_BT
|
||||
c2_ver = ble_glue_get_c2_info();
|
||||
#endif
|
||||
if(!ver) {
|
||||
canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%s [%s]",
|
||||
version_get_version(ver),
|
||||
version_get_builddate(ver));
|
||||
canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%s%s [%s] %s",
|
||||
version_get_dirty_flag(ver) ? "[!] " : "",
|
||||
version_get_githash(ver),
|
||||
version_get_gitbranchnum(ver),
|
||||
c2_ver ? c2_ver->StackTypeString : "<none>");
|
||||
canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
snprintf(
|
||||
buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver));
|
||||
canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
} else {
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
|
||||
uint32_t current_lvl = stats.level;
|
||||
uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
snprintf(buffer, sizeof(buffer), "Icounter: %ld Butthurt %ld", m->icounter, m->butthurt);
|
||||
canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"Level: %ld To level up: %ld",
|
||||
current_lvl,
|
||||
(remaining == (uint32_t)(-1) ? remaining : 0));
|
||||
canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
|
||||
// even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t
|
||||
snprintf(buffer, sizeof(buffer), "%ld", (uint32_t)m->timestamp);
|
||||
|
||||
canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer);
|
||||
canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value [ok] save");
|
||||
}
|
||||
}
|
||||
|
||||
View* desktop_debug_get_view(DesktopDebugView* debug_view) {
|
||||
furi_assert(debug_view);
|
||||
return debug_view->view;
|
||||
}
|
||||
|
||||
bool desktop_debug_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
DesktopDebugView* debug_view = context;
|
||||
|
||||
if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DesktopViewStatsScreens current = 0;
|
||||
with_view_model(
|
||||
debug_view->view, (DesktopDebugViewModel * model) {
|
||||
|
||||
#ifdef SRV_DOLPHIN_STATE_DEBUG
|
||||
if((event->key == InputKeyDown) || (event->key == InputKeyUp)) {
|
||||
model->screen = !model->screen;
|
||||
}
|
||||
#endif
|
||||
current = model->screen;
|
||||
return true;
|
||||
});
|
||||
|
||||
size_t count = (event->type == InputTypeRepeat) ? 10 : 1;
|
||||
if(current == DesktopViewStatsMeta) {
|
||||
if(event->key == InputKeyLeft) {
|
||||
while(count-- > 0) {
|
||||
debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context);
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
while(count-- > 0) {
|
||||
debug_view->callback(DesktopDebugEventDeed, debug_view->context);
|
||||
}
|
||||
} else if(event->key == InputKeyOk) {
|
||||
debug_view->callback(DesktopDebugEventSaveState, debug_view->context);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
debug_view->callback(DesktopDebugEventExit, debug_view->context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DesktopDebugView* desktop_debug_alloc() {
|
||||
DesktopDebugView* debug_view = malloc(sizeof(DesktopDebugView));
|
||||
debug_view->view = view_alloc();
|
||||
view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel));
|
||||
view_set_context(debug_view->view, debug_view);
|
||||
view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render);
|
||||
view_set_input_callback(debug_view->view, desktop_debug_input);
|
||||
|
||||
return debug_view;
|
||||
}
|
||||
|
||||
void desktop_debug_free(DesktopDebugView* debug_view) {
|
||||
furi_assert(debug_view);
|
||||
|
||||
view_free(debug_view->view);
|
||||
free(debug_view);
|
||||
}
|
||||
|
||||
void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) {
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
with_view_model(
|
||||
debug_view->view, (DesktopDebugViewModel * model) {
|
||||
model->icounter = stats.icounter;
|
||||
model->butthurt = stats.butthurt;
|
||||
model->timestamp = stats.timestamp;
|
||||
return true;
|
||||
});
|
||||
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
}
|
||||
|
||||
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) {
|
||||
with_view_model(
|
||||
debug_view->view, (DesktopDebugViewModel * model) {
|
||||
model->screen = 0;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
42
applications/services/desktop/views/desktop_view_debug.h
Normal file
42
applications/services/desktop/views/desktop_view_debug.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/view.h>
|
||||
#include "desktop_events.h"
|
||||
|
||||
typedef struct DesktopDebugView DesktopDebugView;
|
||||
|
||||
typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context);
|
||||
|
||||
// Debug info
|
||||
typedef enum {
|
||||
DesktopViewStatsFw,
|
||||
DesktopViewStatsMeta,
|
||||
DesktopViewStatsTotalCount,
|
||||
} DesktopViewStatsScreens;
|
||||
|
||||
struct DesktopDebugView {
|
||||
View* view;
|
||||
DesktopDebugViewCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t icounter;
|
||||
uint32_t butthurt;
|
||||
uint64_t timestamp;
|
||||
DesktopViewStatsScreens screen;
|
||||
} DesktopDebugViewModel;
|
||||
|
||||
void desktop_debug_set_callback(
|
||||
DesktopDebugView* debug_view,
|
||||
DesktopDebugViewCallback callback,
|
||||
void* context);
|
||||
|
||||
View* desktop_debug_get_view(DesktopDebugView* debug_view);
|
||||
|
||||
DesktopDebugView* desktop_debug_alloc();
|
||||
void desktop_debug_free(DesktopDebugView* debug_view);
|
||||
|
||||
void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view);
|
||||
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view);
|
||||
160
applications/services/desktop/views/desktop_view_lock_menu.c
Normal file
160
applications/services/desktop/views/desktop_view_lock_menu.c
Normal file
@@ -0,0 +1,160 @@
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "desktop_view_lock_menu.h"
|
||||
|
||||
typedef enum {
|
||||
DesktopLockMenuIndexLock,
|
||||
DesktopLockMenuIndexPinLock,
|
||||
DesktopLockMenuIndexDummy,
|
||||
|
||||
DesktopLockMenuIndexTotalCount
|
||||
} DesktopLockMenuIndex;
|
||||
|
||||
void desktop_lock_menu_set_callback(
|
||||
DesktopLockMenuView* lock_menu,
|
||||
DesktopLockMenuViewCallback callback,
|
||||
void* context) {
|
||||
furi_assert(lock_menu);
|
||||
furi_assert(callback);
|
||||
lock_menu->callback = callback;
|
||||
lock_menu->context = context;
|
||||
}
|
||||
|
||||
void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set) {
|
||||
with_view_model(
|
||||
lock_menu->view, (DesktopLockMenuViewModel * model) {
|
||||
model->pin_is_set = pin_is_set;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode) {
|
||||
with_view_model(
|
||||
lock_menu->view, (DesktopLockMenuViewModel * model) {
|
||||
model->dummy_mode = dummy_mode;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) {
|
||||
furi_assert(idx < DesktopLockMenuIndexTotalCount);
|
||||
with_view_model(
|
||||
lock_menu->view, (DesktopLockMenuViewModel * model) {
|
||||
model->idx = idx;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) {
|
||||
DesktopLockMenuViewModel* m = model;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, -57, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55);
|
||||
canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
for(uint8_t i = 0; i < DesktopLockMenuIndexTotalCount; ++i) {
|
||||
const char* str = NULL;
|
||||
|
||||
if(i == DesktopLockMenuIndexLock) {
|
||||
str = "Lock";
|
||||
} else if(i == DesktopLockMenuIndexPinLock) {
|
||||
if(m->pin_is_set) {
|
||||
str = "Lock with PIN";
|
||||
} else {
|
||||
str = "Set PIN";
|
||||
}
|
||||
} else if(i == DesktopLockMenuIndexDummy) {
|
||||
if(m->dummy_mode) {
|
||||
str = "Brainiac Mode";
|
||||
} else {
|
||||
str = "Dummy Mode";
|
||||
}
|
||||
}
|
||||
|
||||
if(str)
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str);
|
||||
|
||||
if(m->idx == i) elements_frame(canvas, 15, 1 + (i * 17) + STATUS_BAR_Y_SHIFT, 98, 15);
|
||||
}
|
||||
}
|
||||
|
||||
View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu) {
|
||||
furi_assert(lock_menu);
|
||||
return lock_menu->view;
|
||||
}
|
||||
|
||||
bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
DesktopLockMenuView* lock_menu = context;
|
||||
uint8_t idx = 0;
|
||||
bool consumed = false;
|
||||
bool dummy_mode = false;
|
||||
|
||||
with_view_model(
|
||||
lock_menu->view, (DesktopLockMenuViewModel * model) {
|
||||
bool ret = false;
|
||||
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
|
||||
if(event->key == InputKeyUp) {
|
||||
if(model->idx == 0) {
|
||||
model->idx = DesktopLockMenuIndexTotalCount - 1;
|
||||
} else {
|
||||
model->idx = CLAMP(model->idx - 1, DesktopLockMenuIndexTotalCount - 1, 0);
|
||||
}
|
||||
ret = true;
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
if(model->idx == DesktopLockMenuIndexTotalCount - 1) {
|
||||
model->idx = 0;
|
||||
} else {
|
||||
model->idx = CLAMP(model->idx + 1, DesktopLockMenuIndexTotalCount - 1, 0);
|
||||
}
|
||||
ret = true;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
idx = model->idx;
|
||||
dummy_mode = model->dummy_mode;
|
||||
return ret;
|
||||
});
|
||||
|
||||
if(event->key == InputKeyOk) {
|
||||
if((idx == DesktopLockMenuIndexLock) && (event->type == InputTypeShort)) {
|
||||
lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
|
||||
} else if((idx == DesktopLockMenuIndexPinLock) && (event->type == InputTypeShort)) {
|
||||
lock_menu->callback(DesktopLockMenuEventPinLock, lock_menu->context);
|
||||
} else if(idx == DesktopLockMenuIndexDummy) {
|
||||
if((dummy_mode == false) && (event->type == InputTypeShort)) {
|
||||
lock_menu->callback(DesktopLockMenuEventDummyModeOn, lock_menu->context);
|
||||
} else if((dummy_mode == true) && (event->type == InputTypeShort)) {
|
||||
lock_menu->callback(DesktopLockMenuEventDummyModeOff, lock_menu->context);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
DesktopLockMenuView* desktop_lock_menu_alloc() {
|
||||
DesktopLockMenuView* lock_menu = malloc(sizeof(DesktopLockMenuView));
|
||||
lock_menu->view = view_alloc();
|
||||
view_allocate_model(lock_menu->view, ViewModelTypeLocking, sizeof(DesktopLockMenuViewModel));
|
||||
view_set_context(lock_menu->view, lock_menu);
|
||||
view_set_draw_callback(lock_menu->view, (ViewDrawCallback)desktop_lock_menu_draw_callback);
|
||||
view_set_input_callback(lock_menu->view, desktop_lock_menu_input_callback);
|
||||
|
||||
return lock_menu;
|
||||
}
|
||||
|
||||
void desktop_lock_menu_free(DesktopLockMenuView* lock_menu_view) {
|
||||
furi_assert(lock_menu_view);
|
||||
|
||||
view_free(lock_menu_view->view);
|
||||
free(lock_menu_view);
|
||||
}
|
||||
34
applications/services/desktop/views/desktop_view_lock_menu.h
Normal file
34
applications/services/desktop/views/desktop_view_lock_menu.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "desktop_events.h"
|
||||
|
||||
#define HINT_TIMEOUT 2
|
||||
|
||||
typedef struct DesktopLockMenuView DesktopLockMenuView;
|
||||
|
||||
typedef void (*DesktopLockMenuViewCallback)(DesktopEvent event, void* context);
|
||||
|
||||
struct DesktopLockMenuView {
|
||||
View* view;
|
||||
DesktopLockMenuViewCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t idx;
|
||||
bool pin_is_set;
|
||||
bool dummy_mode;
|
||||
} DesktopLockMenuViewModel;
|
||||
|
||||
void desktop_lock_menu_set_callback(
|
||||
DesktopLockMenuView* lock_menu,
|
||||
DesktopLockMenuViewCallback callback,
|
||||
void* context);
|
||||
|
||||
View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
|
||||
void desktop_lock_menu_set_pin_state(DesktopLockMenuView* lock_menu, bool pin_is_set);
|
||||
void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode);
|
||||
void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx);
|
||||
DesktopLockMenuView* desktop_lock_menu_alloc();
|
||||
void desktop_lock_menu_free(DesktopLockMenuView* lock_menu);
|
||||
245
applications/services/desktop/views/desktop_view_locked.c
Normal file
245
applications/services/desktop/views/desktop_view_locked.c
Normal file
@@ -0,0 +1,245 @@
|
||||
#include <projdefs.h>
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/icon.h>
|
||||
#include <gui/view.h>
|
||||
#include <portmacro.h>
|
||||
|
||||
#include <desktop/desktop_settings.h>
|
||||
#include "../desktop_i.h"
|
||||
#include "desktop_view_locked.h"
|
||||
|
||||
#define DOOR_MOVING_INTERVAL_MS (1000 / 16)
|
||||
#define LOCKED_HINT_TIMEOUT_MS (1000)
|
||||
#define UNLOCKED_HINT_TIMEOUT_MS (2000)
|
||||
|
||||
#define DOOR_OFFSET_START -55
|
||||
#define DOOR_OFFSET_END 0
|
||||
|
||||
#define DOOR_L_FINAL_POS 0
|
||||
#define DOOR_R_FINAL_POS 60
|
||||
|
||||
#define UNLOCK_CNT 3
|
||||
#define UNLOCK_RST_TIMEOUT 600
|
||||
|
||||
struct DesktopViewLocked {
|
||||
View* view;
|
||||
DesktopViewLockedCallback callback;
|
||||
void* context;
|
||||
|
||||
TimerHandle_t timer;
|
||||
uint8_t lock_count;
|
||||
uint32_t lock_lastpress;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
DesktopViewLockedStateUnlocked,
|
||||
DesktopViewLockedStateLocked,
|
||||
DesktopViewLockedStateDoorsClosing,
|
||||
DesktopViewLockedStateLockedHintShown,
|
||||
DesktopViewLockedStateUnlockedHintShown
|
||||
} DesktopViewLockedState;
|
||||
|
||||
typedef struct {
|
||||
bool pin_locked;
|
||||
int8_t door_offset;
|
||||
DesktopViewLockedState view_state;
|
||||
} DesktopViewLockedModel;
|
||||
|
||||
void desktop_view_locked_set_callback(
|
||||
DesktopViewLocked* locked_view,
|
||||
DesktopViewLockedCallback callback,
|
||||
void* context) {
|
||||
furi_assert(locked_view);
|
||||
furi_assert(callback);
|
||||
locked_view->callback = callback;
|
||||
locked_view->context = context;
|
||||
}
|
||||
|
||||
static void locked_view_timer_callback(TimerHandle_t timer) {
|
||||
DesktopViewLocked* locked_view = pvTimerGetTimerID(timer);
|
||||
locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
|
||||
}
|
||||
|
||||
static void desktop_view_locked_doors_draw(Canvas* canvas, DesktopViewLockedModel* model) {
|
||||
int8_t offset = model->door_offset;
|
||||
uint8_t door_left_x = DOOR_L_FINAL_POS + offset;
|
||||
uint8_t door_right_x = DOOR_R_FINAL_POS - offset;
|
||||
uint8_t height = icon_get_height(&I_DoorLeft_70x55);
|
||||
canvas_draw_icon(canvas, door_left_x, canvas_height(canvas) - height, &I_DoorLeft_70x55);
|
||||
canvas_draw_icon(canvas, door_right_x, canvas_height(canvas) - height, &I_DoorRight_70x55);
|
||||
}
|
||||
|
||||
static bool desktop_view_locked_doors_move(DesktopViewLockedModel* model) {
|
||||
bool stop = false;
|
||||
if(model->door_offset < DOOR_OFFSET_END) {
|
||||
model->door_offset = CLAMP(model->door_offset + 5, DOOR_OFFSET_END, DOOR_OFFSET_START);
|
||||
stop = true;
|
||||
}
|
||||
|
||||
return stop;
|
||||
}
|
||||
|
||||
static void desktop_view_locked_update_hint_icon_timeout(DesktopViewLocked* locked_view) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
const bool change_state = (model->view_state == DesktopViewLockedStateLocked) &&
|
||||
!model->pin_locked;
|
||||
if(change_state) {
|
||||
model->view_state = DesktopViewLockedStateLockedHintShown;
|
||||
}
|
||||
view_commit_model(locked_view->view, change_state);
|
||||
xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(LOCKED_HINT_TIMEOUT_MS), portMAX_DELAY);
|
||||
}
|
||||
|
||||
void desktop_view_locked_update(DesktopViewLocked* locked_view) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
DesktopViewLockedState view_state = model->view_state;
|
||||
|
||||
if(view_state == DesktopViewLockedStateDoorsClosing &&
|
||||
!desktop_view_locked_doors_move(model)) {
|
||||
model->view_state = DesktopViewLockedStateLocked;
|
||||
} else if(view_state == DesktopViewLockedStateLockedHintShown) {
|
||||
model->view_state = DesktopViewLockedStateLocked;
|
||||
} else if(view_state == DesktopViewLockedStateUnlockedHintShown) {
|
||||
model->view_state = DesktopViewLockedStateUnlocked;
|
||||
}
|
||||
|
||||
view_commit_model(locked_view->view, true);
|
||||
|
||||
if(view_state != DesktopViewLockedStateDoorsClosing) {
|
||||
xTimerStop(locked_view->timer, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_view_locked_draw(Canvas* canvas, void* model) {
|
||||
DesktopViewLockedModel* m = model;
|
||||
DesktopViewLockedState view_state = m->view_state;
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(view_state == DesktopViewLockedStateDoorsClosing) {
|
||||
desktop_view_locked_doors_draw(canvas, m);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked");
|
||||
} else if(view_state == DesktopViewLockedStateLockedHintShown) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_bold_rounded_frame(canvas, 14, 2 + STATUS_BAR_Y_SHIFT, 99, 48);
|
||||
elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:");
|
||||
canvas_draw_icon(canvas, 65, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8);
|
||||
canvas_draw_icon(canvas, 80, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8);
|
||||
canvas_draw_icon(canvas, 95, 36 + STATUS_BAR_Y_SHIFT, &I_Pin_back_arrow_10x8);
|
||||
canvas_draw_icon(canvas, 16, 7 + STATUS_BAR_Y_SHIFT, &I_WarningDolphin_45x42);
|
||||
canvas_draw_dot(canvas, 17, 61);
|
||||
} else if(view_state == DesktopViewLockedStateUnlockedHintShown) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
View* desktop_view_locked_get_view(DesktopViewLocked* locked_view) {
|
||||
furi_assert(locked_view);
|
||||
return locked_view->view;
|
||||
}
|
||||
|
||||
static bool desktop_view_locked_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
bool is_changed = false;
|
||||
const uint32_t press_time = xTaskGetTickCount();
|
||||
DesktopViewLocked* locked_view = context;
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
if(model->view_state == DesktopViewLockedStateUnlockedHintShown &&
|
||||
event->type == InputTypePress) {
|
||||
model->view_state = DesktopViewLockedStateUnlocked;
|
||||
is_changed = true;
|
||||
}
|
||||
const DesktopViewLockedState view_state = model->view_state;
|
||||
const bool pin_locked = model->pin_locked;
|
||||
view_commit_model(locked_view->view, is_changed);
|
||||
|
||||
if(view_state == DesktopViewLockedStateUnlocked) {
|
||||
return view_state != DesktopViewLockedStateUnlocked;
|
||||
} else if(view_state == DesktopViewLockedStateLocked && pin_locked) {
|
||||
locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context);
|
||||
} else if(
|
||||
view_state == DesktopViewLockedStateLocked ||
|
||||
view_state == DesktopViewLockedStateLockedHintShown) {
|
||||
if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count = 0;
|
||||
}
|
||||
|
||||
desktop_view_locked_update_hint_icon_timeout(locked_view);
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(event->type == InputTypeShort) {
|
||||
locked_view->lock_lastpress = press_time;
|
||||
locked_view->lock_count++;
|
||||
if(locked_view->lock_count == UNLOCK_CNT) {
|
||||
locked_view->callback(DesktopLockedEventUnlocked, locked_view->context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locked_view->lock_count = 0;
|
||||
}
|
||||
|
||||
locked_view->lock_lastpress = press_time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DesktopViewLocked* desktop_view_locked_alloc() {
|
||||
DesktopViewLocked* locked_view = malloc(sizeof(DesktopViewLocked));
|
||||
locked_view->view = view_alloc();
|
||||
locked_view->timer =
|
||||
xTimerCreate(NULL, 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback);
|
||||
|
||||
view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopViewLockedModel));
|
||||
view_set_context(locked_view->view, locked_view);
|
||||
view_set_draw_callback(locked_view->view, desktop_view_locked_draw);
|
||||
view_set_input_callback(locked_view->view, desktop_view_locked_input);
|
||||
|
||||
return locked_view;
|
||||
}
|
||||
|
||||
void desktop_view_locked_free(DesktopViewLocked* locked_view) {
|
||||
furi_assert(locked_view);
|
||||
furi_timer_free(locked_view->timer);
|
||||
view_free(locked_view->view);
|
||||
free(locked_view);
|
||||
}
|
||||
|
||||
void desktop_view_locked_close_doors(DesktopViewLocked* locked_view) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
furi_assert(model->view_state == DesktopViewLockedStateLocked);
|
||||
model->view_state = DesktopViewLockedStateDoorsClosing;
|
||||
model->door_offset = DOOR_OFFSET_START;
|
||||
view_commit_model(locked_view->view, true);
|
||||
xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(DOOR_MOVING_INTERVAL_MS), portMAX_DELAY);
|
||||
}
|
||||
|
||||
void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
furi_assert(model->view_state == DesktopViewLockedStateUnlocked);
|
||||
model->view_state = DesktopViewLockedStateLocked;
|
||||
model->pin_locked = pin_locked;
|
||||
view_commit_model(locked_view->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_locked_unlock(DesktopViewLocked* locked_view) {
|
||||
locked_view->lock_count = 0;
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
model->view_state = DesktopViewLockedStateUnlockedHintShown;
|
||||
model->pin_locked = false;
|
||||
view_commit_model(locked_view->view, true);
|
||||
xTimerChangePeriod(locked_view->timer, pdMS_TO_TICKS(UNLOCKED_HINT_TIMEOUT_MS), portMAX_DELAY);
|
||||
}
|
||||
|
||||
bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
const DesktopViewLockedState view_state = model->view_state;
|
||||
view_commit_model(locked_view->view, false);
|
||||
return view_state == DesktopViewLockedStateLockedHintShown;
|
||||
}
|
||||
22
applications/services/desktop/views/desktop_view_locked.h
Normal file
22
applications/services/desktop/views/desktop_view_locked.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <desktop/desktop_settings.h>
|
||||
#include "../views/desktop_events.h"
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct DesktopViewLocked DesktopViewLocked;
|
||||
|
||||
typedef void (*DesktopViewLockedCallback)(DesktopEvent event, void* context);
|
||||
|
||||
void desktop_view_locked_set_callback(
|
||||
DesktopViewLocked* locked_view,
|
||||
DesktopViewLockedCallback callback,
|
||||
void* context);
|
||||
void desktop_view_locked_update(DesktopViewLocked* locked_view);
|
||||
View* desktop_view_locked_get_view(DesktopViewLocked* locked_view);
|
||||
DesktopViewLocked* desktop_view_locked_alloc();
|
||||
void desktop_view_locked_free(DesktopViewLocked* locked_view);
|
||||
void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked);
|
||||
void desktop_view_locked_unlock(DesktopViewLocked* locked_view);
|
||||
void desktop_view_locked_close_doors(DesktopViewLocked* locked_view);
|
||||
bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view);
|
||||
124
applications/services/desktop/views/desktop_view_main.c
Normal file
124
applications/services/desktop/views/desktop_view_main.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <gui/gui_i.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <furi.h>
|
||||
#include <input/input.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "desktop_view_main.h"
|
||||
|
||||
struct DesktopMainView {
|
||||
View* view;
|
||||
DesktopMainViewCallback callback;
|
||||
void* context;
|
||||
TimerHandle_t poweroff_timer;
|
||||
bool dummy_mode;
|
||||
};
|
||||
|
||||
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000
|
||||
|
||||
static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) {
|
||||
DesktopMainView* main_view = pvTimerGetTimerID(timer);
|
||||
main_view->callback(DesktopMainEventOpenPowerOff, main_view->context);
|
||||
}
|
||||
|
||||
void desktop_main_set_callback(
|
||||
DesktopMainView* main_view,
|
||||
DesktopMainViewCallback callback,
|
||||
void* context) {
|
||||
furi_assert(main_view);
|
||||
furi_assert(callback);
|
||||
main_view->callback = callback;
|
||||
main_view->context = context;
|
||||
}
|
||||
|
||||
View* desktop_main_get_view(DesktopMainView* main_view) {
|
||||
furi_assert(main_view);
|
||||
return main_view->view;
|
||||
}
|
||||
|
||||
void desktop_main_set_dummy_mode_state(DesktopMainView* main_view, bool dummy_mode) {
|
||||
furi_assert(main_view);
|
||||
main_view->dummy_mode = dummy_mode;
|
||||
}
|
||||
|
||||
bool desktop_main_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
DesktopMainView* main_view = context;
|
||||
|
||||
if(main_view->dummy_mode == false) {
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
main_view->callback(DesktopMainEventOpenMenu, main_view->context);
|
||||
} else if(event->key == InputKeyUp) {
|
||||
main_view->callback(DesktopMainEventOpenLockMenu, main_view->context);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
main_view->callback(DesktopMainEventOpenArchive, main_view->context);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
main_view->callback(DesktopMainEventOpenFavoritePrimary, main_view->context);
|
||||
}
|
||||
// Right key is handled by animation manager
|
||||
} else if(event->type == InputTypeLong) {
|
||||
if(event->key == InputKeyDown) {
|
||||
main_view->callback(DesktopMainEventOpenDebug, main_view->context);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
main_view->callback(DesktopMainEventOpenFavoriteSecondary, main_view->context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
main_view->callback(DesktopMainEventOpenGameMenu, main_view->context);
|
||||
} else if(event->key == InputKeyUp) {
|
||||
main_view->callback(DesktopMainEventOpenLockMenu, main_view->context);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
main_view->callback(DesktopMainEventOpenPassport, main_view->context);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
main_view->callback(DesktopMainEventOpenPassport, main_view->context);
|
||||
}
|
||||
// Right key is handled by animation manager
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(event->type == InputTypePress) {
|
||||
xTimerChangePeriod(
|
||||
main_view->poweroff_timer,
|
||||
pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT),
|
||||
portMAX_DELAY);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
xTimerStop(main_view->poweroff_timer, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DesktopMainView* desktop_main_alloc() {
|
||||
DesktopMainView* main_view = malloc(sizeof(DesktopMainView));
|
||||
|
||||
main_view->view = view_alloc();
|
||||
view_allocate_model(main_view->view, ViewModelTypeLockFree, 1);
|
||||
view_set_context(main_view->view, main_view);
|
||||
view_set_input_callback(main_view->view, desktop_main_input_callback);
|
||||
|
||||
main_view->poweroff_timer = xTimerCreate(
|
||||
NULL,
|
||||
pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT),
|
||||
pdFALSE,
|
||||
main_view,
|
||||
desktop_main_poweroff_timer_callback);
|
||||
|
||||
return main_view;
|
||||
}
|
||||
|
||||
void desktop_main_free(DesktopMainView* main_view) {
|
||||
furi_assert(main_view);
|
||||
view_free(main_view->view);
|
||||
furi_timer_free(main_view->poweroff_timer);
|
||||
free(main_view);
|
||||
}
|
||||
18
applications/services/desktop/views/desktop_view_main.h
Normal file
18
applications/services/desktop/views/desktop_view_main.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "desktop_events.h"
|
||||
|
||||
typedef struct DesktopMainView DesktopMainView;
|
||||
|
||||
typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context);
|
||||
|
||||
void desktop_main_set_callback(
|
||||
DesktopMainView* main_view,
|
||||
DesktopMainViewCallback callback,
|
||||
void* context);
|
||||
|
||||
View* desktop_main_get_view(DesktopMainView* main_view);
|
||||
void desktop_main_set_dummy_mode_state(DesktopMainView* main_view, bool dummy_mode);
|
||||
DesktopMainView* desktop_main_alloc();
|
||||
void desktop_main_free(DesktopMainView* main_view);
|
||||
340
applications/services/desktop/views/desktop_view_pin_input.c
Normal file
340
applications/services/desktop/views/desktop_view_pin_input.c
Normal file
@@ -0,0 +1,340 @@
|
||||
#include <gui/canvas.h>
|
||||
#include <furi.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/elements.h>
|
||||
#include <stdint.h>
|
||||
#include <portmacro.h>
|
||||
|
||||
#include "desktop_view_pin_input.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
#define NO_ACTIVITY_TIMEOUT 15000
|
||||
|
||||
#define PIN_CELL_WIDTH 13
|
||||
#define DEFAULT_PIN_X 64
|
||||
#define DEFAULT_PIN_Y 32
|
||||
|
||||
struct DesktopViewPinInput {
|
||||
View* view;
|
||||
DesktopViewPinInputCallback back_callback;
|
||||
DesktopViewPinInputCallback timeout_callback;
|
||||
DesktopViewPinInputDoneCallback done_callback;
|
||||
void* context;
|
||||
TimerHandle_t timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
PinCode pin;
|
||||
bool pin_hidden;
|
||||
bool locked_input;
|
||||
uint8_t pin_x;
|
||||
uint8_t pin_y;
|
||||
const char* primary_str;
|
||||
uint8_t primary_str_x;
|
||||
uint8_t primary_str_y;
|
||||
const char* secondary_str;
|
||||
uint8_t secondary_str_x;
|
||||
uint8_t secondary_str_y;
|
||||
const char* button_label;
|
||||
} DesktopViewPinInputModel;
|
||||
|
||||
static bool desktop_view_pin_input_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
DesktopViewPinInput* pin_input = context;
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
|
||||
bool call_back_callback = false;
|
||||
bool call_done_callback = false;
|
||||
PinCode pin_code = {0};
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
case InputKeyRight:
|
||||
case InputKeyLeft:
|
||||
case InputKeyDown:
|
||||
case InputKeyUp:
|
||||
if(!model->locked_input) {
|
||||
if(model->pin.length < MAX_PIN_SIZE) {
|
||||
model->pin.data[model->pin.length++] = event->key;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(model->pin.length >= MIN_PIN_SIZE) {
|
||||
call_done_callback = true;
|
||||
pin_code = model->pin;
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(!model->locked_input) {
|
||||
if(model->pin.length > 0) {
|
||||
model->pin.length = 0;
|
||||
} else {
|
||||
call_back_callback = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
furi_assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
view_commit_model(pin_input->view, true);
|
||||
|
||||
if(call_done_callback && pin_input->done_callback) {
|
||||
pin_input->done_callback(&pin_code, pin_input->context);
|
||||
} else if(call_back_callback && pin_input->back_callback) {
|
||||
pin_input->back_callback(pin_input->context);
|
||||
}
|
||||
|
||||
xTimerStart(pin_input->timer, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInputModel* model) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(model);
|
||||
|
||||
uint8_t draw_pin_size = MAX(4, model->pin.length + 1);
|
||||
if(model->locked_input || (model->pin.length == MAX_PIN_SIZE)) {
|
||||
draw_pin_size = model->pin.length;
|
||||
}
|
||||
|
||||
uint8_t x = model->pin_x - (draw_pin_size * (PIN_CELL_WIDTH - 1)) / 2;
|
||||
uint8_t y = model->pin_y - (PIN_CELL_WIDTH / 2);
|
||||
|
||||
for(int i = 0; i < draw_pin_size; ++i) {
|
||||
canvas_draw_frame(canvas, x, y, PIN_CELL_WIDTH, PIN_CELL_WIDTH);
|
||||
if(i < model->pin.length) {
|
||||
if(model->pin_hidden) {
|
||||
canvas_draw_icon(canvas, x + 3, y + 3, &I_Pin_star_7x7);
|
||||
} else {
|
||||
switch(model->pin.data[i]) {
|
||||
case InputKeyDown:
|
||||
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7);
|
||||
break;
|
||||
default:
|
||||
furi_assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(i == model->pin.length) {
|
||||
canvas_draw_icon(canvas, x + 4, y + PIN_CELL_WIDTH + 1, &I_Pin_pointer_5x3);
|
||||
}
|
||||
x += PIN_CELL_WIDTH - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_view_pin_input_draw(Canvas* canvas, void* context) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(context);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
DesktopViewPinInputModel* model = context;
|
||||
desktop_view_pin_input_draw_cells(canvas, model);
|
||||
|
||||
if((model->pin.length > 0) && !model->locked_input) {
|
||||
canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8);
|
||||
}
|
||||
|
||||
if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) {
|
||||
elements_button_center(canvas, model->button_label);
|
||||
}
|
||||
|
||||
if(model->primary_str) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, model->primary_str_x, model->primary_str_y, model->primary_str);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
|
||||
if(model->secondary_str) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(
|
||||
canvas, model->secondary_str_x, model->secondary_str_y, model->secondary_str);
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_timer_callback(TimerHandle_t timer) {
|
||||
DesktopViewPinInput* pin_input = pvTimerGetTimerID(timer);
|
||||
|
||||
if(pin_input->timeout_callback) {
|
||||
pin_input->timeout_callback(pin_input->context);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_view_pin_input_enter(void* context) {
|
||||
DesktopViewPinInput* pin_input = context;
|
||||
xTimerStart(pin_input->timer, portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void desktop_view_pin_input_exit(void* context) {
|
||||
DesktopViewPinInput* pin_input = context;
|
||||
xTimerStop(pin_input->timer, portMAX_DELAY);
|
||||
}
|
||||
|
||||
DesktopViewPinInput* desktop_view_pin_input_alloc(void) {
|
||||
DesktopViewPinInput* pin_input = malloc(sizeof(DesktopViewPinInput));
|
||||
pin_input->view = view_alloc();
|
||||
view_allocate_model(pin_input->view, ViewModelTypeLocking, sizeof(DesktopViewPinInputModel));
|
||||
view_set_context(pin_input->view, pin_input);
|
||||
view_set_draw_callback(pin_input->view, desktop_view_pin_input_draw);
|
||||
view_set_input_callback(pin_input->view, desktop_view_pin_input_input);
|
||||
pin_input->timer = xTimerCreate(
|
||||
NULL,
|
||||
pdMS_TO_TICKS(NO_ACTIVITY_TIMEOUT),
|
||||
pdFALSE,
|
||||
pin_input,
|
||||
desktop_view_pin_input_timer_callback);
|
||||
view_set_enter_callback(pin_input->view, desktop_view_pin_input_enter);
|
||||
view_set_exit_callback(pin_input->view, desktop_view_pin_input_exit);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->pin_x = DEFAULT_PIN_X;
|
||||
model->pin_y = DEFAULT_PIN_Y;
|
||||
model->pin.length = 0;
|
||||
view_commit_model(pin_input->view, false);
|
||||
|
||||
return pin_input;
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_free(DesktopViewPinInput* pin_input) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
xTimerStop(pin_input->timer, portMAX_DELAY);
|
||||
while(xTimerIsTimerActive(pin_input->timer)) {
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
xTimerDelete(pin_input->timer, portMAX_DELAY);
|
||||
|
||||
view_free(pin_input->view);
|
||||
free(pin_input);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_lock_input(DesktopViewPinInput* pin_input) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->locked_input = true;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->locked_input = false;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin) {
|
||||
furi_assert(pin_input);
|
||||
furi_assert(pin);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->pin = *pin;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_reset_pin(DesktopViewPinInput* pin_input) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->pin.length = 0;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_hide_pin(DesktopViewPinInput* pin_input, bool pin_hidden) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->pin_hidden = pin_hidden;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_label_button(DesktopViewPinInput* pin_input, const char* label) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->button_label = label;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_label_primary(
|
||||
DesktopViewPinInput* pin_input,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* label) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->primary_str = label;
|
||||
model->primary_str_x = x;
|
||||
model->primary_str_y = y;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_label_secondary(
|
||||
DesktopViewPinInput* pin_input,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* label) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->secondary_str = label;
|
||||
model->secondary_str_x = x;
|
||||
model->secondary_str_y = y;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_pin_position(DesktopViewPinInput* pin_input, uint8_t x, uint8_t y) {
|
||||
furi_assert(pin_input);
|
||||
|
||||
DesktopViewPinInputModel* model = view_get_model(pin_input->view);
|
||||
model->pin_x = x;
|
||||
model->pin_y = y;
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_context(DesktopViewPinInput* pin_input, void* context) {
|
||||
furi_assert(pin_input);
|
||||
pin_input->context = context;
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_timeout_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputCallback callback) {
|
||||
furi_assert(pin_input);
|
||||
pin_input->timeout_callback = callback;
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_back_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputCallback callback) {
|
||||
furi_assert(pin_input);
|
||||
pin_input->back_callback = callback;
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_done_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputDoneCallback callback) {
|
||||
furi_assert(pin_input);
|
||||
pin_input->done_callback = callback;
|
||||
}
|
||||
|
||||
View* desktop_view_pin_input_get_view(DesktopViewPinInput* pin_input) {
|
||||
furi_assert(pin_input);
|
||||
return pin_input->view;
|
||||
}
|
||||
40
applications/services/desktop/views/desktop_view_pin_input.h
Normal file
40
applications/services/desktop/views/desktop_view_pin_input.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
typedef void (*DesktopViewPinInputCallback)(void*);
|
||||
typedef void (*DesktopViewPinInputDoneCallback)(const PinCode* pin_code, void*);
|
||||
typedef struct DesktopViewPinInput DesktopViewPinInput;
|
||||
|
||||
DesktopViewPinInput* desktop_view_pin_input_alloc(void);
|
||||
void desktop_view_pin_input_free(DesktopViewPinInput*);
|
||||
|
||||
void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin);
|
||||
void desktop_view_pin_input_reset_pin(DesktopViewPinInput* pin_input);
|
||||
void desktop_view_pin_input_hide_pin(DesktopViewPinInput* pin_input, bool pin_hidden);
|
||||
void desktop_view_pin_input_set_label_button(DesktopViewPinInput* pin_input, const char* label);
|
||||
void desktop_view_pin_input_set_label_primary(
|
||||
DesktopViewPinInput* pin_input,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* label);
|
||||
void desktop_view_pin_input_set_label_secondary(
|
||||
DesktopViewPinInput* pin_input,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char* label);
|
||||
void desktop_view_pin_input_set_pin_position(DesktopViewPinInput* pin_input, uint8_t x, uint8_t y);
|
||||
View* desktop_view_pin_input_get_view(DesktopViewPinInput*);
|
||||
void desktop_view_pin_input_set_done_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputDoneCallback callback);
|
||||
void desktop_view_pin_input_set_back_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputCallback callback);
|
||||
void desktop_view_pin_input_set_timeout_callback(
|
||||
DesktopViewPinInput* pin_input,
|
||||
DesktopViewPinInputCallback callback);
|
||||
void desktop_view_pin_input_set_context(DesktopViewPinInput* pin_input, void* context);
|
||||
void desktop_view_pin_input_lock_input(DesktopViewPinInput* pin_input);
|
||||
void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input);
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <toolbox/version.h>
|
||||
#include <assets_icons.h>
|
||||
#include <dolphin/helpers/dolphin_state.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "desktop_view_pin_setup_done.h"
|
||||
|
||||
struct DesktopViewPinSetupDone {
|
||||
View* view;
|
||||
DesktopViewPinSetupDoneDoneCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
static void desktop_view_pin_done_draw(Canvas* canvas, void* model) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(model);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 0, AlignCenter, AlignTop, "Prepare to use\narrows as\nPIN symbols");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text(canvas, 58, 24, "Prepare to use\narrows as\nPIN symbols");
|
||||
|
||||
canvas_draw_icon(canvas, 16, 18, &I_Pin_attention_dpad_29x29);
|
||||
elements_button_right(canvas, "Next");
|
||||
}
|
||||
|
||||
static bool desktop_view_pin_done_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
furi_assert(context);
|
||||
|
||||
DesktopViewPinSetupDone* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
if((event->key == InputKeyRight) && (event->type == InputTypeShort)) {
|
||||
instance->callback(instance->context);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_view_pin_done_set_callback(
|
||||
DesktopViewPinSetupDone* instance,
|
||||
DesktopViewPinSetupDoneDoneCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
DesktopViewPinSetupDone* desktop_view_pin_done_alloc() {
|
||||
DesktopViewPinSetupDone* view = malloc(sizeof(DesktopViewPinSetupDone));
|
||||
view->view = view_alloc();
|
||||
view_allocate_model(view->view, ViewModelTypeLockFree, 1);
|
||||
view_set_context(view->view, view);
|
||||
view_set_draw_callback(view->view, desktop_view_pin_done_draw);
|
||||
view_set_input_callback(view->view, desktop_view_pin_done_input);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct DesktopViewPinSetupDone DesktopViewPinSetupDone;
|
||||
|
||||
typedef void (*DesktopViewPinSetupDoneDoneCallback)(void*);
|
||||
|
||||
void desktop_view_pin_done_set_callback(
|
||||
DesktopViewPinSetupDone* instance,
|
||||
DesktopViewPinSetupDoneDoneCallback callback,
|
||||
void* context);
|
||||
DesktopViewPinSetupDone* desktop_view_pin_done_alloc();
|
||||
void desktop_view_pin_done_free(DesktopViewPinSetupDone* instance);
|
||||
View* desktop_view_pin_done_get_view(DesktopViewPinSetupDone* instance);
|
||||
111
applications/services/desktop/views/desktop_view_pin_timeout.c
Normal file
111
applications/services/desktop/views/desktop_view_pin_timeout.c
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <portmacro.h>
|
||||
#include <projdefs.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
#include "desktop_view_pin_timeout.h"
|
||||
|
||||
struct DesktopViewPinTimeout {
|
||||
View* view;
|
||||
TimerHandle_t timer;
|
||||
DesktopViewPinTimeoutDoneCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t time_left;
|
||||
} DesktopViewPinTimeoutModel;
|
||||
|
||||
void desktop_view_pin_timeout_set_callback(
|
||||
DesktopViewPinTimeout* instance,
|
||||
DesktopViewPinTimeoutDoneCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static void desktop_view_pin_timeout_timer_callback(TimerHandle_t timer) {
|
||||
DesktopViewPinTimeout* instance = pvTimerGetTimerID(timer);
|
||||
bool stop = false;
|
||||
|
||||
DesktopViewPinTimeoutModel* model = view_get_model(instance->view);
|
||||
if(model->time_left > 0) {
|
||||
--model->time_left;
|
||||
} else {
|
||||
stop = true;
|
||||
}
|
||||
view_commit_model(instance->view, true);
|
||||
|
||||
if(stop) {
|
||||
xTimerStop(instance->timer, portMAX_DELAY);
|
||||
instance->callback(instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
static bool desktop_view_pin_timeout_input(InputEvent* event, void* context) {
|
||||
UNUSED(event);
|
||||
UNUSED(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void desktop_view_pin_timeout_draw(Canvas* canvas, void* _model) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(_model);
|
||||
|
||||
DesktopViewPinTimeoutModel* model = _model;
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 36, 31, "Wrong PIN!");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char str[30] = {0};
|
||||
snprintf(str, sizeof(str), "Timeout: %lds", model->time_left);
|
||||
canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignCenter, str);
|
||||
}
|
||||
|
||||
void desktop_view_pin_timeout_free(DesktopViewPinTimeout* instance) {
|
||||
view_free(instance->view);
|
||||
xTimerDelete(instance->timer, portMAX_DELAY);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void) {
|
||||
DesktopViewPinTimeout* instance = malloc(sizeof(DesktopViewPinTimeout));
|
||||
instance->timer = xTimerCreate(
|
||||
NULL, pdMS_TO_TICKS(1000), pdTRUE, instance, desktop_view_pin_timeout_timer_callback);
|
||||
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLockFree, sizeof(DesktopViewPinTimeoutModel));
|
||||
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, desktop_view_pin_timeout_draw);
|
||||
view_set_input_callback(instance->view, desktop_view_pin_timeout_input);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t time_left) {
|
||||
furi_assert(instance);
|
||||
|
||||
DesktopViewPinTimeoutModel* model = view_get_model(instance->view);
|
||||
// no race - always called when timer is stopped
|
||||
model->time_left = time_left;
|
||||
view_commit_model(instance->view, true);
|
||||
|
||||
xTimerStart(instance->timer, portMAX_DELAY);
|
||||
}
|
||||
|
||||
View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->view;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef void (*DesktopViewPinTimeoutDoneCallback)(void*);
|
||||
typedef struct DesktopViewPinTimeout DesktopViewPinTimeout;
|
||||
|
||||
void desktop_view_pin_timeout_set_callback(
|
||||
DesktopViewPinTimeout* instance,
|
||||
DesktopViewPinTimeoutDoneCallback callback,
|
||||
void* context);
|
||||
DesktopViewPinTimeout* desktop_view_pin_timeout_alloc(void);
|
||||
void desktop_view_pin_timeout_free(DesktopViewPinTimeout*);
|
||||
void desktop_view_pin_timeout_start(DesktopViewPinTimeout* instance, uint32_t time_left);
|
||||
View* desktop_view_pin_timeout_get_view(DesktopViewPinTimeout* instance);
|
||||
141
applications/services/desktop/views/desktop_view_slideshow.c
Normal file
141
applications/services/desktop/views/desktop_view_slideshow.c
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include "desktop_view_slideshow.h"
|
||||
#include "../desktop_i.h"
|
||||
#include "../helpers/slideshow.h"
|
||||
#include "../helpers/slideshow_filename.h"
|
||||
|
||||
#define DESKTOP_SLIDESHOW_POWEROFF_SHORT 5000
|
||||
#define DESKTOP_SLIDESHOW_POWEROFF_LONG (60 * 60 * 1000)
|
||||
|
||||
struct DesktopSlideshowView {
|
||||
View* view;
|
||||
DesktopSlideshowViewCallback callback;
|
||||
void* context;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t page;
|
||||
Slideshow* slideshow;
|
||||
} DesktopSlideshowViewModel;
|
||||
|
||||
static void desktop_view_slideshow_draw(Canvas* canvas, void* model) {
|
||||
DesktopSlideshowViewModel* m = model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
if(slideshow_is_loaded(m->slideshow)) {
|
||||
slideshow_draw(m->slideshow, canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
|
||||
furi_assert(event);
|
||||
DesktopSlideshowView* instance = context;
|
||||
|
||||
DesktopSlideshowViewModel* model = view_get_model(instance->view);
|
||||
bool update_view = false;
|
||||
if(event->type == InputTypeShort) {
|
||||
bool end_slideshow = false;
|
||||
switch(event->key) {
|
||||
case InputKeyLeft:
|
||||
slideshow_goback(model->slideshow);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
case InputKeyOk:
|
||||
end_slideshow = !slideshow_advance(model->slideshow);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
end_slideshow = true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(end_slideshow) {
|
||||
instance->callback(DesktopSlideshowCompleted, instance->context);
|
||||
}
|
||||
update_view = true;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypePress) {
|
||||
furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT);
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
furi_timer_stop(instance->timer);
|
||||
if(!slideshow_is_one_page(model->slideshow)) {
|
||||
furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
view_commit_model(instance->view, update_view);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void desktop_first_start_timer_callback(void* context) {
|
||||
DesktopSlideshowView* instance = context;
|
||||
instance->callback(DesktopSlideshowPoweroff, instance->context);
|
||||
}
|
||||
|
||||
static void desktop_view_slideshow_enter(void* context) {
|
||||
DesktopSlideshowView* instance = context;
|
||||
|
||||
furi_assert(instance->timer == NULL);
|
||||
instance->timer =
|
||||
furi_timer_alloc(desktop_first_start_timer_callback, FuriTimerTypeOnce, instance);
|
||||
|
||||
DesktopSlideshowViewModel* model = view_get_model(instance->view);
|
||||
model->slideshow = slideshow_alloc();
|
||||
if(!slideshow_load(model->slideshow, SLIDESHOW_FS_PATH)) {
|
||||
instance->callback(DesktopSlideshowCompleted, instance->context);
|
||||
} else if(!slideshow_is_one_page(model->slideshow)) {
|
||||
furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
|
||||
}
|
||||
view_commit_model(instance->view, false);
|
||||
}
|
||||
|
||||
static void desktop_view_slideshow_exit(void* context) {
|
||||
DesktopSlideshowView* instance = context;
|
||||
|
||||
furi_timer_stop(instance->timer);
|
||||
furi_timer_free(instance->timer);
|
||||
instance->timer = NULL;
|
||||
|
||||
DesktopSlideshowViewModel* model = view_get_model(instance->view);
|
||||
slideshow_free(model->slideshow);
|
||||
view_commit_model(instance->view, false);
|
||||
}
|
||||
|
||||
DesktopSlideshowView* desktop_view_slideshow_alloc() {
|
||||
DesktopSlideshowView* instance = malloc(sizeof(DesktopSlideshowView));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopSlideshowViewModel));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_view_slideshow_draw);
|
||||
view_set_input_callback(instance->view, desktop_view_slideshow_input);
|
||||
view_set_enter_callback(instance->view, desktop_view_slideshow_enter);
|
||||
view_set_exit_callback(instance->view, desktop_view_slideshow_exit);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void desktop_view_slideshow_free(DesktopSlideshowView* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* desktop_view_slideshow_get_view(DesktopSlideshowView* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void desktop_view_slideshow_set_callback(
|
||||
DesktopSlideshowView* instance,
|
||||
DesktopSlideshowViewCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
23
applications/services/desktop/views/desktop_view_slideshow.h
Normal file
23
applications/services/desktop/views/desktop_view_slideshow.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#include "desktop_events.h"
|
||||
#include "../helpers/slideshow_filename.h"
|
||||
|
||||
#define SLIDESHOW_FS_PATH INT_PATH(SLIDESHOW_FILE_NAME)
|
||||
|
||||
typedef struct DesktopSlideshowView DesktopSlideshowView;
|
||||
|
||||
typedef void (*DesktopSlideshowViewCallback)(DesktopEvent event, void* context);
|
||||
|
||||
DesktopSlideshowView* desktop_view_slideshow_alloc();
|
||||
|
||||
void desktop_view_slideshow_free(DesktopSlideshowView* main_view);
|
||||
|
||||
View* desktop_view_slideshow_get_view(DesktopSlideshowView* main_view);
|
||||
|
||||
void desktop_view_slideshow_set_callback(
|
||||
DesktopSlideshowView* main_view,
|
||||
DesktopSlideshowViewCallback callback,
|
||||
void* context);
|
||||
11
applications/services/dialogs/application.fam
Normal file
11
applications/services/dialogs/application.fam
Normal file
@@ -0,0 +1,11 @@
|
||||
App(
|
||||
appid="dialogs",
|
||||
name="DialogsSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="dialogs_srv",
|
||||
cdefines=["SRV_DIALOGS"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=40,
|
||||
sdk_headers=["dialogs.h"],
|
||||
)
|
||||
54
applications/services/dialogs/dialogs.c
Normal file
54
applications/services/dialogs/dialogs.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "dialogs/dialogs_message.h"
|
||||
#include "dialogs_i.h"
|
||||
#include "dialogs_api_lock.h"
|
||||
#include "dialogs_module_file_browser.h"
|
||||
#include "dialogs_module_message.h"
|
||||
|
||||
void dialog_file_browser_set_basic_options(
|
||||
DialogsFileBrowserOptions* options,
|
||||
const char* extension,
|
||||
const Icon* icon) {
|
||||
options->extension = extension;
|
||||
options->skip_assets = true;
|
||||
options->icon = icon;
|
||||
options->hide_ext = true;
|
||||
options->item_loader_callback = NULL;
|
||||
options->item_loader_context = NULL;
|
||||
}
|
||||
|
||||
static DialogsApp* dialogs_app_alloc() {
|
||||
DialogsApp* app = malloc(sizeof(DialogsApp));
|
||||
app->message_queue = furi_message_queue_alloc(8, sizeof(DialogsAppMessage));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) {
|
||||
UNUSED(app);
|
||||
switch(message->command) {
|
||||
case DialogsAppCommandFileBrowser:
|
||||
message->return_data->bool_value =
|
||||
dialogs_app_process_module_file_browser(&message->data->file_browser);
|
||||
break;
|
||||
case DialogsAppCommandDialog:
|
||||
message->return_data->dialog_value =
|
||||
dialogs_app_process_module_message(&message->data->dialog);
|
||||
break;
|
||||
}
|
||||
API_LOCK_UNLOCK(message->lock);
|
||||
}
|
||||
|
||||
int32_t dialogs_srv(void* p) {
|
||||
UNUSED(p);
|
||||
DialogsApp* app = dialogs_app_alloc();
|
||||
furi_record_create(RECORD_DIALOGS, app);
|
||||
|
||||
DialogsAppMessage message;
|
||||
while(1) {
|
||||
if(furi_message_queue_get(app->message_queue, &message, FuriWaitForever) == FuriStatusOk) {
|
||||
dialogs_app_process_message(app, &message);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
165
applications/services/dialogs/dialogs.h
Normal file
165
applications/services/dialogs/dialogs.h
Normal file
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/canvas.h>
|
||||
#include "m-string.h"
|
||||
#include <gui/modules/file_browser.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/****************** COMMON ******************/
|
||||
|
||||
#define RECORD_DIALOGS "dialogs"
|
||||
|
||||
typedef struct DialogsApp DialogsApp;
|
||||
|
||||
/****************** FILE BROWSER ******************/
|
||||
|
||||
/**
|
||||
* File browser dialog extra options
|
||||
* @param extension file extension to be offered for selection
|
||||
* @param skip_assets true - do not show assets folders
|
||||
* @param icon file icon pointer, NULL for default icon
|
||||
* @param hide_ext true - hide extensions for files
|
||||
* @param item_loader_callback callback function for providing custom icon & entry name
|
||||
* @param hide_ext callback context
|
||||
*/
|
||||
typedef struct {
|
||||
const char* extension;
|
||||
bool skip_assets;
|
||||
const Icon* icon;
|
||||
bool hide_ext;
|
||||
FileBrowserLoadItemCallback item_loader_callback;
|
||||
void* item_loader_context;
|
||||
} DialogsFileBrowserOptions;
|
||||
|
||||
/**
|
||||
* Initialize file browser dialog options
|
||||
* and set default values
|
||||
* @param options pointer to options structure
|
||||
* @param extension file extension to filter
|
||||
* @param icon file icon pointer, NULL for default icon
|
||||
*/
|
||||
void dialog_file_browser_set_basic_options(
|
||||
DialogsFileBrowserOptions* options,
|
||||
const char* extension,
|
||||
const Icon* icon);
|
||||
|
||||
/**
|
||||
* Shows and processes the file browser dialog
|
||||
* @param context api pointer
|
||||
* @param result_path selected file path string pointer
|
||||
* @param path preselected file path string pointer
|
||||
* @param options file browser dialog extra options, may be null
|
||||
* @return bool whether a file was selected
|
||||
*/
|
||||
bool dialog_file_browser_show(
|
||||
DialogsApp* context,
|
||||
string_ptr result_path,
|
||||
string_ptr path,
|
||||
const DialogsFileBrowserOptions* options);
|
||||
|
||||
/****************** MESSAGE ******************/
|
||||
|
||||
/**
|
||||
* Message result type
|
||||
*/
|
||||
typedef enum {
|
||||
DialogMessageButtonBack,
|
||||
DialogMessageButtonLeft,
|
||||
DialogMessageButtonCenter,
|
||||
DialogMessageButtonRight,
|
||||
} DialogMessageButton;
|
||||
|
||||
/**
|
||||
* Message struct
|
||||
*/
|
||||
typedef struct DialogMessage DialogMessage;
|
||||
|
||||
/**
|
||||
* Allocate and fill message
|
||||
* @return DialogMessage*
|
||||
*/
|
||||
DialogMessage* dialog_message_alloc();
|
||||
|
||||
/**
|
||||
* Free message struct
|
||||
* @param message message pointer
|
||||
*/
|
||||
void dialog_message_free(DialogMessage* message);
|
||||
|
||||
/**
|
||||
* Set message text
|
||||
* @param message message pointer
|
||||
* @param text text, can be NULL if you don't want to display the text
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical alignment
|
||||
*/
|
||||
void dialog_message_set_text(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/**
|
||||
* Set message header
|
||||
* @param message message pointer
|
||||
* @param text text, can be NULL if you don't want to display the header
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical alignment
|
||||
*/
|
||||
void dialog_message_set_header(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/**
|
||||
* Set message icon
|
||||
* @param message message pointer
|
||||
* @param icon icon pointer, can be NULL if you don't want to display the icon
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
*/
|
||||
void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y);
|
||||
|
||||
/**
|
||||
* Set message buttons text, button text can be NULL if you don't want to display and process some buttons
|
||||
* @param message message pointer
|
||||
* @param left left button text, can be NULL if you don't want to display the left button
|
||||
* @param center center button text, can be NULL if you don't want to display the center button
|
||||
* @param right right button text, can be NULL if you don't want to display the right button
|
||||
*/
|
||||
void dialog_message_set_buttons(
|
||||
DialogMessage* message,
|
||||
const char* left,
|
||||
const char* center,
|
||||
const char* right);
|
||||
|
||||
/**
|
||||
* Show message from filled struct
|
||||
* @param context api pointer
|
||||
* @param message message struct pointer to be shown
|
||||
* @return DialogMessageButton type
|
||||
*/
|
||||
DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message);
|
||||
|
||||
/**
|
||||
* Show SD error message (with question sign)
|
||||
* @param context
|
||||
* @param error_text
|
||||
*/
|
||||
void dialog_message_show_storage_error(DialogsApp* context, const char* error_text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
78
applications/services/dialogs/dialogs_api.c
Normal file
78
applications/services/dialogs/dialogs_api.c
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "dialogs/dialogs_message.h"
|
||||
#include "dialogs_i.h"
|
||||
#include "dialogs_api_lock.h"
|
||||
#include "m-string.h"
|
||||
|
||||
/****************** File browser ******************/
|
||||
|
||||
bool dialog_file_browser_show(
|
||||
DialogsApp* context,
|
||||
string_ptr result_path,
|
||||
string_ptr path,
|
||||
const DialogsFileBrowserOptions* options) {
|
||||
FuriApiLock lock = API_LOCK_INIT_LOCKED();
|
||||
furi_check(lock != NULL);
|
||||
|
||||
DialogsAppData data = {
|
||||
.file_browser = {
|
||||
.extension = options ? options->extension : "",
|
||||
.result_path = result_path,
|
||||
.file_icon = options ? options->icon : NULL,
|
||||
.hide_ext = options ? options->hide_ext : true,
|
||||
.skip_assets = options ? options->skip_assets : true,
|
||||
.preselected_filename = path,
|
||||
.item_callback = options ? options->item_loader_callback : NULL,
|
||||
.item_callback_context = options ? options->item_loader_context : NULL,
|
||||
}};
|
||||
|
||||
DialogsAppReturn return_data;
|
||||
DialogsAppMessage message = {
|
||||
.lock = lock,
|
||||
.command = DialogsAppCommandFileBrowser,
|
||||
.data = &data,
|
||||
.return_data = &return_data,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock);
|
||||
|
||||
return return_data.bool_value;
|
||||
}
|
||||
|
||||
/****************** Message ******************/
|
||||
|
||||
DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) {
|
||||
FuriApiLock lock = API_LOCK_INIT_LOCKED();
|
||||
furi_check(lock != NULL);
|
||||
|
||||
DialogsAppData data = {
|
||||
.dialog = {
|
||||
.message = dialog_message,
|
||||
}};
|
||||
|
||||
DialogsAppReturn return_data;
|
||||
DialogsAppMessage message = {
|
||||
.lock = lock,
|
||||
.command = DialogsAppCommandDialog,
|
||||
.data = &data,
|
||||
.return_data = &return_data,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock);
|
||||
|
||||
return return_data.dialog_value;
|
||||
}
|
||||
|
||||
/****************** Storage error ******************/
|
||||
|
||||
void dialog_message_show_storage_error(DialogsApp* context, const char* error_text) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6);
|
||||
dialog_message_set_buttons(message, "Back", NULL, NULL);
|
||||
dialog_message_show(context, message);
|
||||
dialog_message_free(message);
|
||||
}
|
||||
18
applications/services/dialogs/dialogs_api_lock.h
Normal file
18
applications/services/dialogs/dialogs_api_lock.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
typedef FuriEventFlag* FuriApiLock;
|
||||
|
||||
#define API_LOCK_EVENT (1U << 0)
|
||||
|
||||
#define API_LOCK_INIT_LOCKED() furi_event_flag_alloc();
|
||||
|
||||
#define API_LOCK_WAIT_UNTIL_UNLOCK(_lock) \
|
||||
furi_event_flag_wait(_lock, API_LOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
|
||||
#define API_LOCK_FREE(_lock) furi_event_flag_free(_lock);
|
||||
|
||||
#define API_LOCK_UNLOCK(_lock) furi_event_flag_set(_lock, API_LOCK_EVENT);
|
||||
|
||||
#define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK(_lock); \
|
||||
API_LOCK_FREE(_lock);
|
||||
16
applications/services/dialogs/dialogs_i.h
Normal file
16
applications/services/dialogs/dialogs_i.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "dialogs.h"
|
||||
#include "dialogs_message.h"
|
||||
#include "view_holder.h"
|
||||
#include <gui/modules/file_browser.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
struct DialogsApp {
|
||||
FuriMessageQueue* message_queue;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
50
applications/services/dialogs/dialogs_message.h
Normal file
50
applications/services/dialogs/dialogs_message.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "dialogs_i.h"
|
||||
#include "dialogs_api_lock.h"
|
||||
#include "m-string.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const char* extension;
|
||||
bool skip_assets;
|
||||
bool hide_ext;
|
||||
const Icon* file_icon;
|
||||
string_ptr result_path;
|
||||
string_ptr preselected_filename;
|
||||
FileBrowserLoadItemCallback item_callback;
|
||||
void* item_callback_context;
|
||||
} DialogsAppMessageDataFileBrowser;
|
||||
|
||||
typedef struct {
|
||||
const DialogMessage* message;
|
||||
} DialogsAppMessageDataDialog;
|
||||
|
||||
typedef union {
|
||||
DialogsAppMessageDataFileBrowser file_browser;
|
||||
DialogsAppMessageDataDialog dialog;
|
||||
} DialogsAppData;
|
||||
|
||||
typedef union {
|
||||
bool bool_value;
|
||||
DialogMessageButton dialog_value;
|
||||
} DialogsAppReturn;
|
||||
|
||||
typedef enum {
|
||||
DialogsAppCommandFileBrowser,
|
||||
DialogsAppCommandDialog,
|
||||
} DialogsAppCommand;
|
||||
|
||||
typedef struct {
|
||||
FuriApiLock lock;
|
||||
DialogsAppCommand command;
|
||||
DialogsAppData* data;
|
||||
DialogsAppReturn* return_data;
|
||||
} DialogsAppMessage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
60
applications/services/dialogs/dialogs_module_file_browser.c
Normal file
60
applications/services/dialogs/dialogs_module_file_browser.c
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "dialogs_i.h"
|
||||
#include "dialogs_api_lock.h"
|
||||
#include "gui/modules/file_browser.h"
|
||||
|
||||
typedef struct {
|
||||
FuriApiLock lock;
|
||||
bool result;
|
||||
} DialogsAppFileBrowserContext;
|
||||
|
||||
static void dialogs_app_file_browser_back_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppFileBrowserContext* file_browser_context = context;
|
||||
file_browser_context->result = false;
|
||||
API_LOCK_UNLOCK(file_browser_context->lock);
|
||||
}
|
||||
|
||||
static void dialogs_app_file_browser_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppFileBrowserContext* file_browser_context = context;
|
||||
file_browser_context->result = true;
|
||||
API_LOCK_UNLOCK(file_browser_context->lock);
|
||||
}
|
||||
|
||||
bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) {
|
||||
bool ret = false;
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
DialogsAppFileBrowserContext* file_browser_context =
|
||||
malloc(sizeof(DialogsAppFileBrowserContext));
|
||||
file_browser_context->lock = API_LOCK_INIT_LOCKED();
|
||||
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
view_holder_set_back_callback(
|
||||
view_holder, dialogs_app_file_browser_back_callback, file_browser_context);
|
||||
|
||||
FileBrowser* file_browser = file_browser_alloc(data->result_path);
|
||||
file_browser_set_callback(
|
||||
file_browser, dialogs_app_file_browser_callback, file_browser_context);
|
||||
file_browser_configure(
|
||||
file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext);
|
||||
file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context);
|
||||
file_browser_start(file_browser, data->preselected_filename);
|
||||
|
||||
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
|
||||
view_holder_start(view_holder);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock);
|
||||
|
||||
ret = file_browser_context->result;
|
||||
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_free(view_holder);
|
||||
file_browser_stop(file_browser);
|
||||
file_browser_free(file_browser);
|
||||
API_LOCK_FREE(file_browser_context->lock);
|
||||
free(file_browser_context);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return ret;
|
||||
}
|
||||
12
applications/services/dialogs/dialogs_module_file_browser.h
Normal file
12
applications/services/dialogs/dialogs_module_file_browser.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "dialogs_message.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
155
applications/services/dialogs/dialogs_module_message.c
Normal file
155
applications/services/dialogs/dialogs_module_message.c
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "dialogs_i.h"
|
||||
#include "dialogs_api_lock.h"
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
|
||||
typedef struct {
|
||||
FuriApiLock lock;
|
||||
DialogMessageButton result;
|
||||
} DialogsAppMessageContext;
|
||||
|
||||
struct DialogMessage {
|
||||
const char* header_text;
|
||||
uint8_t header_text_x;
|
||||
uint8_t header_text_y;
|
||||
Align header_horizontal;
|
||||
Align header_vertical;
|
||||
const char* dialog_text;
|
||||
uint8_t dialog_text_x;
|
||||
uint8_t dialog_text_y;
|
||||
Align dialog_text_horizontal;
|
||||
Align dialog_text_vertical;
|
||||
const Icon* icon;
|
||||
uint8_t icon_x;
|
||||
uint8_t icon_y;
|
||||
const char* left_button_text;
|
||||
const char* center_button_text;
|
||||
const char* right_button_text;
|
||||
};
|
||||
|
||||
static void dialogs_app_message_back_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppMessageContext* message_context = context;
|
||||
message_context->result = DialogMessageButtonBack;
|
||||
API_LOCK_UNLOCK(message_context->lock);
|
||||
}
|
||||
|
||||
static void dialogs_app_message_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppMessageContext* message_context = context;
|
||||
switch(result) {
|
||||
case DialogExResultLeft:
|
||||
message_context->result = DialogMessageButtonLeft;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
message_context->result = DialogMessageButtonRight;
|
||||
break;
|
||||
case DialogExResultCenter:
|
||||
message_context->result = DialogMessageButtonCenter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
API_LOCK_UNLOCK(message_context->lock);
|
||||
}
|
||||
|
||||
DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) {
|
||||
DialogMessageButton ret = DialogMessageButtonBack;
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
const DialogMessage* message = data->message;
|
||||
DialogsAppMessageContext* message_context = malloc(sizeof(DialogsAppMessageContext));
|
||||
message_context->lock = API_LOCK_INIT_LOCKED();
|
||||
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
view_holder_set_back_callback(view_holder, dialogs_app_message_back_callback, message_context);
|
||||
|
||||
DialogEx* dialog_ex = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(dialog_ex, dialogs_app_message_callback);
|
||||
dialog_ex_set_context(dialog_ex, message_context);
|
||||
dialog_ex_set_header(
|
||||
dialog_ex,
|
||||
message->header_text,
|
||||
message->header_text_x,
|
||||
message->header_text_y,
|
||||
message->header_horizontal,
|
||||
message->header_vertical);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
message->dialog_text,
|
||||
message->dialog_text_x,
|
||||
message->dialog_text_y,
|
||||
message->dialog_text_horizontal,
|
||||
message->dialog_text_vertical);
|
||||
dialog_ex_set_icon(dialog_ex, message->icon_x, message->icon_y, message->icon);
|
||||
dialog_ex_set_left_button_text(dialog_ex, message->left_button_text);
|
||||
dialog_ex_set_center_button_text(dialog_ex, message->center_button_text);
|
||||
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
|
||||
|
||||
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
|
||||
view_holder_start(view_holder);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK(message_context->lock);
|
||||
|
||||
ret = message_context->result;
|
||||
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_free(view_holder);
|
||||
dialog_ex_free(dialog_ex);
|
||||
API_LOCK_FREE(message_context->lock);
|
||||
free(message_context);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DialogMessage* dialog_message_alloc() {
|
||||
DialogMessage* message = malloc(sizeof(DialogMessage));
|
||||
return message;
|
||||
}
|
||||
|
||||
void dialog_message_free(DialogMessage* message) {
|
||||
free(message);
|
||||
}
|
||||
|
||||
void dialog_message_set_text(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical) {
|
||||
message->dialog_text = text;
|
||||
message->dialog_text_x = x;
|
||||
message->dialog_text_y = y;
|
||||
message->dialog_text_horizontal = horizontal;
|
||||
message->dialog_text_vertical = vertical;
|
||||
}
|
||||
|
||||
void dialog_message_set_header(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical) {
|
||||
message->header_text = text;
|
||||
message->header_text_x = x;
|
||||
message->header_text_y = y;
|
||||
message->header_horizontal = horizontal;
|
||||
message->header_vertical = vertical;
|
||||
}
|
||||
|
||||
void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y) {
|
||||
message->icon = icon;
|
||||
message->icon_x = x;
|
||||
message->icon_y = y;
|
||||
}
|
||||
|
||||
void dialog_message_set_buttons(
|
||||
DialogMessage* message,
|
||||
const char* left,
|
||||
const char* center,
|
||||
const char* right) {
|
||||
message->left_button_text = left;
|
||||
message->center_button_text = center;
|
||||
message->right_button_text = right;
|
||||
}
|
||||
12
applications/services/dialogs/dialogs_module_message.h
Normal file
12
applications/services/dialogs/dialogs_module_message.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "dialogs_message.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
150
applications/services/dialogs/view_holder.c
Normal file
150
applications/services/dialogs/view_holder.c
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "view_holder.h"
|
||||
#include <gui/view_i.h>
|
||||
|
||||
#define TAG "ViewHolder"
|
||||
|
||||
struct ViewHolder {
|
||||
View* view;
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
|
||||
FreeCallback free_callback;
|
||||
void* free_context;
|
||||
|
||||
BackCallback back_callback;
|
||||
void* back_context;
|
||||
|
||||
uint8_t ongoing_input;
|
||||
};
|
||||
|
||||
static void view_holder_draw_callback(Canvas* canvas, void* context);
|
||||
static void view_holder_input_callback(InputEvent* event, void* context);
|
||||
|
||||
ViewHolder* view_holder_alloc() {
|
||||
ViewHolder* view_holder = malloc(sizeof(ViewHolder));
|
||||
|
||||
view_holder->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_holder->view_port, view_holder_draw_callback, view_holder);
|
||||
view_port_input_callback_set(view_holder->view_port, view_holder_input_callback, view_holder);
|
||||
view_port_enabled_set(view_holder->view_port, false);
|
||||
|
||||
return view_holder;
|
||||
}
|
||||
|
||||
void view_holder_free(ViewHolder* view_holder) {
|
||||
furi_assert(view_holder);
|
||||
|
||||
if(view_holder->gui) {
|
||||
gui_remove_view_port(view_holder->gui, view_holder->view_port);
|
||||
}
|
||||
|
||||
view_port_free(view_holder->view_port);
|
||||
|
||||
if(view_holder->free_callback) {
|
||||
view_holder->free_callback(view_holder->free_context);
|
||||
}
|
||||
|
||||
free(view_holder);
|
||||
}
|
||||
|
||||
void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||
furi_assert(view_holder);
|
||||
if(view_holder->view) {
|
||||
view_set_update_callback(view_holder->view, NULL);
|
||||
view_set_update_callback_context(view_holder->view, NULL);
|
||||
}
|
||||
|
||||
view_holder->view = view;
|
||||
|
||||
if(view_holder->view) {
|
||||
view_set_update_callback(view_holder->view, view_holder_update);
|
||||
view_set_update_callback_context(view_holder->view, view_holder);
|
||||
}
|
||||
}
|
||||
|
||||
void view_holder_set_free_callback(
|
||||
ViewHolder* view_holder,
|
||||
FreeCallback free_callback,
|
||||
void* free_context) {
|
||||
furi_assert(view_holder);
|
||||
view_holder->free_callback = free_callback;
|
||||
view_holder->free_context = free_context;
|
||||
}
|
||||
|
||||
void* view_holder_get_free_context(ViewHolder* view_holder) {
|
||||
return view_holder->free_context;
|
||||
}
|
||||
|
||||
void view_holder_set_back_callback(
|
||||
ViewHolder* view_holder,
|
||||
BackCallback back_callback,
|
||||
void* back_context) {
|
||||
furi_assert(view_holder);
|
||||
view_holder->back_callback = back_callback;
|
||||
view_holder->back_context = back_context;
|
||||
}
|
||||
|
||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
|
||||
furi_assert(gui);
|
||||
furi_assert(view_holder);
|
||||
view_holder->gui = gui;
|
||||
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
|
||||
}
|
||||
|
||||
void view_holder_start(ViewHolder* view_holder) {
|
||||
view_port_enabled_set(view_holder->view_port, true);
|
||||
}
|
||||
|
||||
void view_holder_stop(ViewHolder* view_holder) {
|
||||
while(view_holder->ongoing_input) furi_delay_tick(1);
|
||||
view_port_enabled_set(view_holder->view_port, false);
|
||||
}
|
||||
|
||||
void view_holder_update(View* view, void* context) {
|
||||
furi_assert(view);
|
||||
furi_assert(context);
|
||||
|
||||
ViewHolder* view_holder = context;
|
||||
if(view == view_holder->view) {
|
||||
view_port_update(view_holder->view_port);
|
||||
}
|
||||
}
|
||||
|
||||
static void view_holder_draw_callback(Canvas* canvas, void* context) {
|
||||
ViewHolder* view_holder = context;
|
||||
if(view_holder->view) {
|
||||
view_draw(view_holder->view, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
static void view_holder_input_callback(InputEvent* event, void* context) {
|
||||
ViewHolder* view_holder = context;
|
||||
|
||||
uint8_t key_bit = (1 << event->key);
|
||||
if(event->type == InputTypePress) {
|
||||
view_holder->ongoing_input |= key_bit;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
view_holder->ongoing_input &= ~key_bit;
|
||||
} else if(!(view_holder->ongoing_input & key_bit)) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"non-complementary input, discarding key: %s, type: %s",
|
||||
input_get_key_name(event->key),
|
||||
input_get_type_name(event->type));
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_consumed = false;
|
||||
|
||||
if(view_holder->view) {
|
||||
is_consumed = view_input(view_holder->view, event);
|
||||
}
|
||||
|
||||
if(!is_consumed && event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
if(view_holder->back_callback) {
|
||||
view_holder->back_callback(view_holder->back_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
applications/services/dialogs/view_holder.h
Normal file
98
applications/services/dialogs/view_holder.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ViewHolder ViewHolder;
|
||||
|
||||
/**
|
||||
* @brief Free callback type
|
||||
*/
|
||||
typedef void (*FreeCallback)(void* free_context);
|
||||
|
||||
/**
|
||||
* @brief Back callback type
|
||||
* @warning comes from GUI thread
|
||||
*/
|
||||
typedef void (*BackCallback)(void* back_context);
|
||||
|
||||
/**
|
||||
* @brief Allocate ViewHolder
|
||||
* @return pointer to ViewHolder instance
|
||||
*/
|
||||
ViewHolder* view_holder_alloc();
|
||||
|
||||
/**
|
||||
* @brief Free ViewHolder and call Free callback
|
||||
* @param view_holder pointer to ViewHolder
|
||||
*/
|
||||
void view_holder_free(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Set view for ViewHolder
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @param view View instance
|
||||
*/
|
||||
void view_holder_set_view(ViewHolder* view_holder, View* view);
|
||||
|
||||
/**
|
||||
* @brief Set Free callback
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @param free_callback callback pointer
|
||||
* @param free_context callback context
|
||||
*/
|
||||
void view_holder_set_free_callback(
|
||||
ViewHolder* view_holder,
|
||||
FreeCallback free_callback,
|
||||
void* free_context);
|
||||
|
||||
/**
|
||||
* @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @return void* free callback context
|
||||
*/
|
||||
void* view_holder_get_free_context(ViewHolder* view_holder);
|
||||
|
||||
void view_holder_set_back_callback(
|
||||
ViewHolder* view_holder,
|
||||
BackCallback back_callback,
|
||||
void* back_context);
|
||||
|
||||
/**
|
||||
* @brief Attach ViewHolder to GUI
|
||||
*
|
||||
* @param view_holder ViewHolder instance
|
||||
* @param gui GUI instance to attach to
|
||||
*/
|
||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
|
||||
|
||||
/**
|
||||
* @brief Enable view processing
|
||||
*
|
||||
* @param view_holder
|
||||
*/
|
||||
void view_holder_start(ViewHolder* view_holder);
|
||||
|
||||
/**
|
||||
* @brief Disable view processing
|
||||
*
|
||||
* @param view_holder
|
||||
*/
|
||||
void view_holder_stop(ViewHolder* view_holder);
|
||||
|
||||
/** View Update Handler
|
||||
* @param view, View Instance
|
||||
* @param context, ViewHolder instance
|
||||
*/
|
||||
void view_holder_update(View* view, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
10
applications/services/dolphin/application.fam
Normal file
10
applications/services/dolphin/application.fam
Normal file
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="dolphin",
|
||||
name="DolphinSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="dolphin_srv",
|
||||
cdefines=["SRV_DOLPHIN"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
sdk_headers=["dolphin.h"],
|
||||
)
|
||||
209
applications/services/dolphin/dolphin.c
Normal file
209
applications/services/dolphin/dolphin.c
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "dolphin/dolphin.h"
|
||||
#include "dolphin/helpers/dolphin_state.h"
|
||||
#include "dolphin_i.h"
|
||||
#include "portmacro.h"
|
||||
#include "projdefs.h"
|
||||
#include <furi_hal.h>
|
||||
#include <stdint.h>
|
||||
#include <furi.h>
|
||||
#define DOLPHIN_LOCK_EVENT_FLAG (0x1)
|
||||
|
||||
#define TAG "Dolphin"
|
||||
#define HOURS_IN_TICKS(x) ((x)*60 * 60 * 1000)
|
||||
|
||||
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin);
|
||||
|
||||
void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) {
|
||||
furi_assert(dolphin);
|
||||
DolphinEvent event;
|
||||
event.type = DolphinEventTypeDeed;
|
||||
event.deed = deed;
|
||||
dolphin_event_send_async(dolphin, &event);
|
||||
}
|
||||
|
||||
DolphinStats dolphin_stats(Dolphin* dolphin) {
|
||||
furi_assert(dolphin);
|
||||
|
||||
DolphinStats stats;
|
||||
DolphinEvent event;
|
||||
|
||||
event.type = DolphinEventTypeStats;
|
||||
event.stats = &stats;
|
||||
|
||||
dolphin_event_send_wait(dolphin, &event);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void dolphin_flush(Dolphin* dolphin) {
|
||||
furi_assert(dolphin);
|
||||
|
||||
DolphinEvent event;
|
||||
event.type = DolphinEventTypeFlush;
|
||||
|
||||
dolphin_event_send_wait(dolphin, &event);
|
||||
}
|
||||
|
||||
void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) {
|
||||
Dolphin* dolphin = pvTimerGetTimerID(xTimer);
|
||||
furi_assert(dolphin);
|
||||
|
||||
DolphinEvent event;
|
||||
event.type = DolphinEventTypeIncreaseButthurt;
|
||||
dolphin_event_send_async(dolphin, &event);
|
||||
}
|
||||
|
||||
void dolphin_flush_timer_callback(TimerHandle_t xTimer) {
|
||||
Dolphin* dolphin = pvTimerGetTimerID(xTimer);
|
||||
furi_assert(dolphin);
|
||||
|
||||
DolphinEvent event;
|
||||
event.type = DolphinEventTypeFlush;
|
||||
dolphin_event_send_async(dolphin, &event);
|
||||
}
|
||||
|
||||
void dolphin_clear_limits_timer_callback(TimerHandle_t xTimer) {
|
||||
Dolphin* dolphin = pvTimerGetTimerID(xTimer);
|
||||
furi_assert(dolphin);
|
||||
|
||||
xTimerChangePeriod(dolphin->clear_limits_timer, HOURS_IN_TICKS(24), portMAX_DELAY);
|
||||
|
||||
DolphinEvent event;
|
||||
event.type = DolphinEventTypeClearLimits;
|
||||
dolphin_event_send_async(dolphin, &event);
|
||||
}
|
||||
|
||||
Dolphin* dolphin_alloc() {
|
||||
Dolphin* dolphin = malloc(sizeof(Dolphin));
|
||||
|
||||
dolphin->state = dolphin_state_alloc();
|
||||
dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent));
|
||||
dolphin->pubsub = furi_pubsub_alloc();
|
||||
dolphin->butthurt_timer = xTimerCreate(
|
||||
NULL, HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback);
|
||||
dolphin->flush_timer =
|
||||
xTimerCreate(NULL, 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback);
|
||||
dolphin->clear_limits_timer = xTimerCreate(
|
||||
NULL, HOURS_IN_TICKS(24), pdTRUE, dolphin, dolphin_clear_limits_timer_callback);
|
||||
|
||||
return dolphin;
|
||||
}
|
||||
|
||||
void dolphin_free(Dolphin* dolphin) {
|
||||
furi_assert(dolphin);
|
||||
|
||||
dolphin_state_free(dolphin->state);
|
||||
furi_message_queue_free(dolphin->event_queue);
|
||||
|
||||
free(dolphin);
|
||||
}
|
||||
|
||||
void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) {
|
||||
furi_assert(dolphin);
|
||||
furi_assert(event);
|
||||
event->flag = NULL;
|
||||
furi_check(
|
||||
furi_message_queue_put(dolphin->event_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) {
|
||||
furi_assert(dolphin);
|
||||
furi_assert(event);
|
||||
event->flag = furi_event_flag_alloc();
|
||||
furi_check(event->flag);
|
||||
furi_check(
|
||||
furi_message_queue_put(dolphin->event_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||
furi_check(
|
||||
furi_event_flag_wait(
|
||||
event->flag, DOLPHIN_LOCK_EVENT_FLAG, FuriFlagWaitAny, FuriWaitForever) ==
|
||||
DOLPHIN_LOCK_EVENT_FLAG);
|
||||
furi_event_flag_free(event->flag);
|
||||
}
|
||||
|
||||
void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) {
|
||||
UNUSED(dolphin);
|
||||
if(event->flag) {
|
||||
furi_event_flag_set(event->flag, DOLPHIN_LOCK_EVENT_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
|
||||
return dolphin->pubsub;
|
||||
}
|
||||
|
||||
static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) {
|
||||
furi_assert(dolphin);
|
||||
TickType_t now_ticks = xTaskGetTickCount();
|
||||
TickType_t timer_expires_at = xTimerGetExpiryTime(dolphin->clear_limits_timer);
|
||||
|
||||
if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) {
|
||||
FuriHalRtcDateTime date;
|
||||
furi_hal_rtc_get_datetime(&date);
|
||||
TickType_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000;
|
||||
TickType_t time_to_clear_limits = 0;
|
||||
|
||||
if(date.hour < 5) {
|
||||
time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms;
|
||||
} else {
|
||||
time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms;
|
||||
}
|
||||
|
||||
xTimerChangePeriod(dolphin->clear_limits_timer, time_to_clear_limits, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t dolphin_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Dolphin* dolphin = dolphin_alloc();
|
||||
furi_record_create(RECORD_DOLPHIN, dolphin);
|
||||
|
||||
dolphin_state_load(dolphin->state);
|
||||
xTimerReset(dolphin->butthurt_timer, portMAX_DELAY);
|
||||
dolphin_update_clear_limits_timer_period(dolphin);
|
||||
xTimerReset(dolphin->clear_limits_timer, portMAX_DELAY);
|
||||
|
||||
DolphinEvent event;
|
||||
while(1) {
|
||||
if(furi_message_queue_get(dolphin->event_queue, &event, HOURS_IN_TICKS(1)) ==
|
||||
FuriStatusOk) {
|
||||
if(event.type == DolphinEventTypeDeed) {
|
||||
dolphin_state_on_deed(dolphin->state, event.deed);
|
||||
DolphinPubsubEvent event = DolphinPubsubEventUpdate;
|
||||
furi_pubsub_publish(dolphin->pubsub, &event);
|
||||
xTimerReset(dolphin->butthurt_timer, portMAX_DELAY);
|
||||
xTimerReset(dolphin->flush_timer, portMAX_DELAY);
|
||||
} else if(event.type == DolphinEventTypeStats) {
|
||||
event.stats->icounter = dolphin->state->data.icounter;
|
||||
event.stats->butthurt = dolphin->state->data.butthurt;
|
||||
event.stats->timestamp = dolphin->state->data.timestamp;
|
||||
event.stats->level = dolphin_get_level(dolphin->state->data.icounter);
|
||||
event.stats->level_up_is_pending =
|
||||
!dolphin_state_xp_to_levelup(dolphin->state->data.icounter);
|
||||
} else if(event.type == DolphinEventTypeFlush) {
|
||||
FURI_LOG_I(TAG, "Flush stats");
|
||||
dolphin_state_save(dolphin->state);
|
||||
} else if(event.type == DolphinEventTypeClearLimits) {
|
||||
FURI_LOG_I(TAG, "Clear limits");
|
||||
dolphin_state_clear_limits(dolphin->state);
|
||||
dolphin_state_save(dolphin->state);
|
||||
} else if(event.type == DolphinEventTypeIncreaseButthurt) {
|
||||
FURI_LOG_I(TAG, "Increase butthurt");
|
||||
dolphin_state_butthurted(dolphin->state);
|
||||
dolphin_state_save(dolphin->state);
|
||||
}
|
||||
dolphin_event_release(dolphin, &event);
|
||||
} else {
|
||||
/* once per hour check rtc time is not changed */
|
||||
dolphin_update_clear_limits_timer_period(dolphin);
|
||||
}
|
||||
}
|
||||
|
||||
dolphin_free(dolphin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dolphin_upgrade_level(Dolphin* dolphin) {
|
||||
dolphin_state_increase_level(dolphin->state);
|
||||
dolphin_flush(dolphin);
|
||||
}
|
||||
57
applications/services/dolphin/dolphin.h
Normal file
57
applications/services/dolphin/dolphin.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/pubsub.h>
|
||||
#include "gui/view.h"
|
||||
#include "helpers/dolphin_deed.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RECORD_DOLPHIN "dolphin"
|
||||
|
||||
typedef struct Dolphin Dolphin;
|
||||
|
||||
typedef struct {
|
||||
uint32_t icounter;
|
||||
uint32_t butthurt;
|
||||
uint64_t timestamp;
|
||||
uint8_t level;
|
||||
bool level_up_is_pending;
|
||||
} DolphinStats;
|
||||
|
||||
typedef enum {
|
||||
DolphinPubsubEventUpdate,
|
||||
} DolphinPubsubEvent;
|
||||
|
||||
#define DOLPHIN_DEED(deed) \
|
||||
do { \
|
||||
Dolphin* dolphin = (Dolphin*)furi_record_open("dolphin"); \
|
||||
dolphin_deed(dolphin, deed); \
|
||||
furi_record_close("dolphin"); \
|
||||
} while(0)
|
||||
|
||||
/** Deed complete notification. Call it on deed completion.
|
||||
* See dolphin_deed.h for available deeds. In futures it will become part of assets.
|
||||
* Thread safe, async
|
||||
*/
|
||||
void dolphin_deed(Dolphin* dolphin, DolphinDeed deed);
|
||||
|
||||
/** Retrieve dolphin stats
|
||||
* Thread safe, blocking
|
||||
*/
|
||||
DolphinStats dolphin_stats(Dolphin* dolphin);
|
||||
|
||||
/** Flush dolphin queue and save state
|
||||
* Thread safe, blocking
|
||||
*/
|
||||
void dolphin_flush(Dolphin* dolphin);
|
||||
|
||||
void dolphin_upgrade_level(Dolphin* dolphin);
|
||||
|
||||
FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
46
applications/services/dolphin/dolphin_i.h
Normal file
46
applications/services/dolphin/dolphin_i.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/pubsub.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "dolphin.h"
|
||||
#include "helpers/dolphin_state.h"
|
||||
|
||||
typedef enum {
|
||||
DolphinEventTypeDeed,
|
||||
DolphinEventTypeStats,
|
||||
DolphinEventTypeFlush,
|
||||
DolphinEventTypeIncreaseButthurt,
|
||||
DolphinEventTypeClearLimits,
|
||||
} DolphinEventType;
|
||||
|
||||
typedef struct {
|
||||
DolphinEventType type;
|
||||
FuriEventFlag* flag;
|
||||
union {
|
||||
DolphinDeed deed;
|
||||
DolphinStats* stats;
|
||||
};
|
||||
} DolphinEvent;
|
||||
|
||||
struct Dolphin {
|
||||
// State
|
||||
DolphinState* state;
|
||||
// Queue
|
||||
FuriMessageQueue* event_queue;
|
||||
FuriPubSub* pubsub;
|
||||
TimerHandle_t butthurt_timer;
|
||||
TimerHandle_t flush_timer;
|
||||
TimerHandle_t clear_limits_timer;
|
||||
};
|
||||
|
||||
Dolphin* dolphin_alloc();
|
||||
|
||||
void dolphin_free(Dolphin* dolphin);
|
||||
|
||||
void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event);
|
||||
|
||||
void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event);
|
||||
|
||||
void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event);
|
||||
65
applications/services/dolphin/helpers/dolphin_deed.c
Normal file
65
applications/services/dolphin/helpers/dolphin_deed.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "dolphin_deed.h"
|
||||
#include <furi.h>
|
||||
|
||||
static const DolphinDeedWeight dolphin_deed_weights[] = {
|
||||
{1, DolphinAppSubGhz}, // DolphinDeedSubGhzReceiverInfo
|
||||
{3, DolphinAppSubGhz}, // DolphinDeedSubGhzSave
|
||||
{1, DolphinAppSubGhz}, // DolphinDeedSubGhzRawRec
|
||||
{2, DolphinAppSubGhz}, // DolphinDeedSubGhzAddManually
|
||||
{2, DolphinAppSubGhz}, // DolphinDeedSubGhzSend
|
||||
{1, DolphinAppSubGhz}, // DolphinDeedSubGhzFrequencyAnalyzer
|
||||
|
||||
{1, DolphinAppRfid}, // DolphinDeedRfidRead
|
||||
{3, DolphinAppRfid}, // DolphinDeedRfidReadSuccess
|
||||
{3, DolphinAppRfid}, // DolphinDeedRfidSave
|
||||
{2, DolphinAppRfid}, // DolphinDeedRfidEmulate
|
||||
{2, DolphinAppRfid}, // DolphinDeedRfidAdd
|
||||
|
||||
{1, DolphinAppNfc}, // DolphinDeedNfcRead
|
||||
{3, DolphinAppNfc}, // DolphinDeedNfcReadSuccess
|
||||
{3, DolphinAppNfc}, // DolphinDeedNfcSave
|
||||
{2, DolphinAppNfc}, // DolphinDeedNfcEmulate
|
||||
{2, DolphinAppNfc}, // DolphinDeedNfcAdd
|
||||
|
||||
{1, DolphinAppIr}, // DolphinDeedIrSend
|
||||
{3, DolphinAppIr}, // DolphinDeedIrLearnSuccess
|
||||
{3, DolphinAppIr}, // DolphinDeedIrSave
|
||||
{2, DolphinAppIr}, // DolphinDeedIrBruteForce
|
||||
|
||||
{1, DolphinAppIbutton}, // DolphinDeedIbuttonRead
|
||||
{3, DolphinAppIbutton}, // DolphinDeedIbuttonReadSuccess
|
||||
{3, DolphinAppIbutton}, // DolphinDeedIbuttonSave
|
||||
{2, DolphinAppIbutton}, // DolphinDeedIbuttonEmulate
|
||||
{2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd
|
||||
|
||||
{3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript
|
||||
{3, DolphinAppU2f}, // DolphinDeedU2fAuthorized
|
||||
};
|
||||
|
||||
static uint8_t dolphin_deed_limits[] = {
|
||||
15, // DolphinAppSubGhz
|
||||
15, // DolphinAppRfid
|
||||
15, // DolphinAppNfc
|
||||
15, // DolphinAppIr
|
||||
15, // DolphinAppIbutton
|
||||
15, // DolphinAppBadusb
|
||||
15, // DolphinAppU2f
|
||||
};
|
||||
|
||||
_Static_assert(COUNT_OF(dolphin_deed_weights) == DolphinDeedMAX, "dolphin_deed_weights size error");
|
||||
_Static_assert(COUNT_OF(dolphin_deed_limits) == DolphinAppMAX, "dolphin_deed_limits size error");
|
||||
|
||||
uint8_t dolphin_deed_get_weight(DolphinDeed deed) {
|
||||
furi_check(deed < DolphinDeedMAX);
|
||||
return dolphin_deed_weights[deed].icounter;
|
||||
}
|
||||
|
||||
DolphinApp dolphin_deed_get_app(DolphinDeed deed) {
|
||||
furi_check(deed < DolphinDeedMAX);
|
||||
return dolphin_deed_weights[deed].app;
|
||||
}
|
||||
|
||||
uint8_t dolphin_deed_get_app_limit(DolphinApp app) {
|
||||
furi_check(app < DolphinAppMAX);
|
||||
return dolphin_deed_limits[app];
|
||||
}
|
||||
77
applications/services/dolphin/helpers/dolphin_deed.h
Normal file
77
applications/services/dolphin/helpers/dolphin_deed.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
DolphinAppSubGhz,
|
||||
DolphinAppRfid,
|
||||
DolphinAppNfc,
|
||||
DolphinAppIr,
|
||||
DolphinAppIbutton,
|
||||
DolphinAppBadusb,
|
||||
DolphinAppU2f,
|
||||
DolphinAppMAX,
|
||||
} DolphinApp;
|
||||
|
||||
typedef enum {
|
||||
DolphinDeedSubGhzReceiverInfo,
|
||||
DolphinDeedSubGhzSave,
|
||||
DolphinDeedSubGhzRawRec,
|
||||
DolphinDeedSubGhzAddManually,
|
||||
DolphinDeedSubGhzSend,
|
||||
DolphinDeedSubGhzFrequencyAnalyzer,
|
||||
|
||||
DolphinDeedRfidRead,
|
||||
DolphinDeedRfidReadSuccess,
|
||||
DolphinDeedRfidSave,
|
||||
DolphinDeedRfidEmulate,
|
||||
DolphinDeedRfidAdd,
|
||||
|
||||
DolphinDeedNfcRead,
|
||||
DolphinDeedNfcReadSuccess,
|
||||
DolphinDeedNfcSave,
|
||||
DolphinDeedNfcEmulate,
|
||||
DolphinDeedNfcAdd,
|
||||
|
||||
DolphinDeedIrSend,
|
||||
DolphinDeedIrLearnSuccess,
|
||||
DolphinDeedIrSave,
|
||||
DolphinDeedIrBruteForce,
|
||||
|
||||
DolphinDeedIbuttonRead,
|
||||
DolphinDeedIbuttonReadSuccess,
|
||||
DolphinDeedIbuttonSave,
|
||||
DolphinDeedIbuttonEmulate,
|
||||
DolphinDeedIbuttonAdd,
|
||||
|
||||
DolphinDeedBadUsbPlayScript,
|
||||
|
||||
DolphinDeedU2fAuthorized,
|
||||
|
||||
DolphinDeedMAX,
|
||||
|
||||
DolphinDeedTestLeft,
|
||||
DolphinDeedTestRight,
|
||||
} DolphinDeed;
|
||||
|
||||
typedef struct {
|
||||
uint8_t icounter;
|
||||
DolphinApp app;
|
||||
} DolphinDeedWeight;
|
||||
|
||||
typedef struct {
|
||||
DolphinApp app;
|
||||
uint8_t icounter_limit;
|
||||
} DolphinDeedLimits;
|
||||
|
||||
DolphinApp dolphin_deed_get_app(DolphinDeed deed);
|
||||
uint8_t dolphin_deed_get_app_limit(DolphinApp app);
|
||||
uint8_t dolphin_deed_get_weight(DolphinDeed deed);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
201
applications/services/dolphin/helpers/dolphin_state.c
Normal file
201
applications/services/dolphin/helpers/dolphin_state.c
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "dolphin_state.h"
|
||||
#include "dolphin/helpers/dolphin_deed.h"
|
||||
#include "dolphin_state_filename.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <storage/storage.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <math.h>
|
||||
#include <toolbox/saved_struct.h>
|
||||
|
||||
#define TAG "DolphinState"
|
||||
|
||||
#define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME)
|
||||
#define DOLPHIN_STATE_HEADER_MAGIC 0xD0
|
||||
#define DOLPHIN_STATE_HEADER_VERSION 0x01
|
||||
#define LEVEL2_THRESHOLD 300
|
||||
#define LEVEL3_THRESHOLD 1800
|
||||
#define BUTTHURT_MAX 14
|
||||
#define BUTTHURT_MIN 0
|
||||
|
||||
DolphinState* dolphin_state_alloc() {
|
||||
return malloc(sizeof(DolphinState));
|
||||
}
|
||||
|
||||
void dolphin_state_free(DolphinState* dolphin_state) {
|
||||
free(dolphin_state);
|
||||
}
|
||||
|
||||
bool dolphin_state_save(DolphinState* dolphin_state) {
|
||||
if(!dolphin_state->dirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result = saved_struct_save(
|
||||
DOLPHIN_STATE_PATH,
|
||||
&dolphin_state->data,
|
||||
sizeof(DolphinStoreData),
|
||||
DOLPHIN_STATE_HEADER_MAGIC,
|
||||
DOLPHIN_STATE_HEADER_VERSION);
|
||||
|
||||
if(result) {
|
||||
FURI_LOG_I(TAG, "State saved");
|
||||
dolphin_state->dirty = false;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to save state");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool dolphin_state_load(DolphinState* dolphin_state) {
|
||||
bool success = saved_struct_load(
|
||||
DOLPHIN_STATE_PATH,
|
||||
&dolphin_state->data,
|
||||
sizeof(DolphinStoreData),
|
||||
DOLPHIN_STATE_HEADER_MAGIC,
|
||||
DOLPHIN_STATE_HEADER_VERSION);
|
||||
|
||||
if(success) {
|
||||
if((dolphin_state->data.butthurt > BUTTHURT_MAX) ||
|
||||
(dolphin_state->data.butthurt < BUTTHURT_MIN)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Reset dolphin-state");
|
||||
memset(dolphin_state, 0, sizeof(*dolphin_state));
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
uint64_t dolphin_state_timestamp() {
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
return furi_hal_rtc_datetime_to_timestamp(&datetime);
|
||||
}
|
||||
|
||||
bool dolphin_state_is_levelup(uint32_t icounter) {
|
||||
return (icounter == LEVEL2_THRESHOLD) || (icounter == LEVEL3_THRESHOLD);
|
||||
}
|
||||
|
||||
uint8_t dolphin_get_level(uint32_t icounter) {
|
||||
if(icounter <= LEVEL2_THRESHOLD) {
|
||||
return 1;
|
||||
} else if(icounter <= LEVEL3_THRESHOLD) {
|
||||
return 2;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t dolphin_state_xp_above_last_levelup(uint32_t icounter) {
|
||||
uint32_t threshold = 0;
|
||||
if(icounter <= LEVEL2_THRESHOLD) {
|
||||
threshold = 0;
|
||||
} else if(icounter <= LEVEL3_THRESHOLD) {
|
||||
threshold = LEVEL2_THRESHOLD + 1;
|
||||
} else {
|
||||
threshold = LEVEL3_THRESHOLD + 1;
|
||||
}
|
||||
return icounter - threshold;
|
||||
}
|
||||
|
||||
uint32_t dolphin_state_xp_to_levelup(uint32_t icounter) {
|
||||
uint32_t threshold = 0;
|
||||
if(icounter <= LEVEL2_THRESHOLD) {
|
||||
threshold = LEVEL2_THRESHOLD;
|
||||
} else if(icounter <= LEVEL3_THRESHOLD) {
|
||||
threshold = LEVEL3_THRESHOLD;
|
||||
} else {
|
||||
threshold = (uint32_t)-1;
|
||||
}
|
||||
return threshold - icounter;
|
||||
}
|
||||
|
||||
void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
|
||||
// Special case for testing
|
||||
if(deed > DolphinDeedMAX) {
|
||||
if(deed == DolphinDeedTestLeft) {
|
||||
dolphin_state->data.butthurt =
|
||||
CLAMP(dolphin_state->data.butthurt + 1, BUTTHURT_MAX, BUTTHURT_MIN);
|
||||
if(dolphin_state->data.icounter > 0) dolphin_state->data.icounter--;
|
||||
dolphin_state->data.timestamp = dolphin_state_timestamp();
|
||||
dolphin_state->dirty = true;
|
||||
} else if(deed == DolphinDeedTestRight) {
|
||||
dolphin_state->data.butthurt = BUTTHURT_MIN;
|
||||
if(dolphin_state->data.icounter < UINT32_MAX) dolphin_state->data.icounter++;
|
||||
dolphin_state->data.timestamp = dolphin_state_timestamp();
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DolphinApp app = dolphin_deed_get_app(deed);
|
||||
int8_t weight_limit =
|
||||
dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app];
|
||||
uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0);
|
||||
|
||||
uint32_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter);
|
||||
if(xp_to_levelup) {
|
||||
deed_weight = MIN(xp_to_levelup, deed_weight);
|
||||
dolphin_state->data.icounter += deed_weight;
|
||||
dolphin_state->data.icounter_daily_limit[app] += deed_weight;
|
||||
}
|
||||
|
||||
/* decrease butthurt:
|
||||
* 0 deeds accumulating --> 0 butthurt
|
||||
* +1....+15 deeds accumulating --> -1 butthurt
|
||||
* +16...+30 deeds accumulating --> -1 butthurt
|
||||
* +31...+45 deeds accumulating --> -1 butthurt
|
||||
* +46...... deeds accumulating --> -1 butthurt
|
||||
* -4 butthurt per day is maximum
|
||||
* */
|
||||
uint8_t butthurt_icounter_level_old = dolphin_state->data.butthurt_daily_limit / 15 +
|
||||
!!(dolphin_state->data.butthurt_daily_limit % 15);
|
||||
dolphin_state->data.butthurt_daily_limit =
|
||||
CLAMP(dolphin_state->data.butthurt_daily_limit + deed_weight, 46, 0);
|
||||
uint8_t butthurt_icounter_level_new = dolphin_state->data.butthurt_daily_limit / 15 +
|
||||
!!(dolphin_state->data.butthurt_daily_limit % 15);
|
||||
int32_t new_butthurt = ((int32_t)dolphin_state->data.butthurt) -
|
||||
(butthurt_icounter_level_old != butthurt_icounter_level_new);
|
||||
new_butthurt = CLAMP(new_butthurt, BUTTHURT_MAX, BUTTHURT_MIN);
|
||||
|
||||
dolphin_state->data.butthurt = new_butthurt;
|
||||
dolphin_state->data.timestamp = dolphin_state_timestamp();
|
||||
dolphin_state->dirty = true;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"icounter %d, butthurt %d",
|
||||
dolphin_state->data.icounter,
|
||||
dolphin_state->data.butthurt);
|
||||
}
|
||||
|
||||
void dolphin_state_butthurted(DolphinState* dolphin_state) {
|
||||
if(dolphin_state->data.butthurt < BUTTHURT_MAX) {
|
||||
dolphin_state->data.butthurt++;
|
||||
dolphin_state->data.timestamp = dolphin_state_timestamp();
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void dolphin_state_increase_level(DolphinState* dolphin_state) {
|
||||
furi_assert(dolphin_state_is_levelup(dolphin_state->data.icounter));
|
||||
++dolphin_state->data.icounter;
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
|
||||
void dolphin_state_clear_limits(DolphinState* dolphin_state) {
|
||||
furi_assert(dolphin_state);
|
||||
|
||||
for(int i = 0; i < DolphinAppMAX; ++i) {
|
||||
dolphin_state->data.icounter_daily_limit[i] = 0;
|
||||
}
|
||||
dolphin_state->data.butthurt_daily_limit = 0;
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
48
applications/services/dolphin/helpers/dolphin_state.h
Normal file
48
applications/services/dolphin/helpers/dolphin_state.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "dolphin_deed.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef struct DolphinState DolphinState;
|
||||
typedef struct {
|
||||
uint8_t icounter_daily_limit[DolphinAppMAX];
|
||||
uint8_t butthurt_daily_limit;
|
||||
|
||||
uint32_t flags;
|
||||
uint32_t icounter;
|
||||
int32_t butthurt;
|
||||
uint64_t timestamp;
|
||||
} DolphinStoreData;
|
||||
|
||||
struct DolphinState {
|
||||
DolphinStoreData data;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
DolphinState* dolphin_state_alloc();
|
||||
|
||||
void dolphin_state_free(DolphinState* dolphin_state);
|
||||
|
||||
bool dolphin_state_save(DolphinState* dolphin_state);
|
||||
|
||||
bool dolphin_state_load(DolphinState* dolphin_state);
|
||||
|
||||
void dolphin_state_clear_limits(DolphinState* dolphin_state);
|
||||
|
||||
uint64_t dolphin_state_timestamp();
|
||||
|
||||
void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed);
|
||||
|
||||
void dolphin_state_butthurted(DolphinState* dolphin_state);
|
||||
|
||||
uint32_t dolphin_state_xp_to_levelup(uint32_t icounter);
|
||||
|
||||
uint32_t dolphin_state_xp_above_last_levelup(uint32_t icounter);
|
||||
|
||||
bool dolphin_state_is_levelup(uint32_t icounter);
|
||||
|
||||
void dolphin_state_increase_level(DolphinState* dolphin_state);
|
||||
|
||||
uint8_t dolphin_get_level(uint32_t icounter);
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define DOLPHIN_STATE_FILE_NAME ".dolphin.state"
|
||||
36
applications/services/gui/application.fam
Normal file
36
applications/services/gui/application.fam
Normal file
@@ -0,0 +1,36 @@
|
||||
App(
|
||||
appid="gui",
|
||||
name="GuiSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="gui_srv",
|
||||
cdefines=["SRV_GUI"],
|
||||
requires=[
|
||||
"input",
|
||||
"notification",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
sdk_headers=[
|
||||
"gui.h",
|
||||
"elements.h",
|
||||
"view_dispatcher.h",
|
||||
"view_stack.h",
|
||||
"modules/button_menu.h",
|
||||
"modules/byte_input.h",
|
||||
"modules/popup.h",
|
||||
"modules/text_input.h",
|
||||
"modules/widget.h",
|
||||
"modules/validators.h",
|
||||
"modules/file_browser.h",
|
||||
"modules/button_panel.h",
|
||||
"modules/variable_item_list.h",
|
||||
"modules/file_browser_worker.h",
|
||||
"modules/menu.h",
|
||||
"modules/dialog_ex.h",
|
||||
"modules/loading.h",
|
||||
"modules/text_box.h",
|
||||
"modules/submenu.h",
|
||||
"modules/widget_elements/widget_element.h",
|
||||
"modules/empty_screen.h",
|
||||
],
|
||||
)
|
||||
389
applications/services/gui/canvas.c
Normal file
389
applications/services/gui/canvas.c
Normal file
@@ -0,0 +1,389 @@
|
||||
#include "canvas_i.h"
|
||||
#include "icon_i.h"
|
||||
#include "icon_animation_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdint.h>
|
||||
#include <u8g2_glue.h>
|
||||
|
||||
const CanvasFontParameters canvas_font_params[FontTotalNumber] = {
|
||||
[FontPrimary] = {.leading_default = 12, .leading_min = 11, .height = 8, .descender = 2},
|
||||
[FontSecondary] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2},
|
||||
[FontKeyboard] = {.leading_default = 11, .leading_min = 9, .height = 7, .descender = 2},
|
||||
[FontBigNumbers] = {.leading_default = 18, .leading_min = 16, .height = 15, .descender = 0},
|
||||
};
|
||||
|
||||
Canvas* canvas_init() {
|
||||
Canvas* canvas = malloc(sizeof(Canvas));
|
||||
|
||||
// Setup u8g2
|
||||
u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
|
||||
canvas->orientation = CanvasOrientationHorizontal;
|
||||
// Initialize display
|
||||
u8g2_InitDisplay(&canvas->fb);
|
||||
// Wake up display
|
||||
u8g2_SetPowerSave(&canvas->fb, 0);
|
||||
|
||||
// Clear buffer and send to device
|
||||
canvas_clear(canvas);
|
||||
canvas_commit(canvas);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
void canvas_free(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
free(canvas);
|
||||
}
|
||||
|
||||
void canvas_reset(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
|
||||
}
|
||||
|
||||
void canvas_commit(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SendBuffer(&canvas->fb);
|
||||
}
|
||||
|
||||
uint8_t* canvas_get_buffer(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetBufferPtr(&canvas->fb);
|
||||
}
|
||||
|
||||
size_t canvas_get_buffer_size(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetBufferTileWidth(&canvas->fb) * u8g2_GetBufferTileHeight(&canvas->fb) * 8;
|
||||
}
|
||||
|
||||
void canvas_frame_set(
|
||||
Canvas* canvas,
|
||||
uint8_t offset_x,
|
||||
uint8_t offset_y,
|
||||
uint8_t width,
|
||||
uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
canvas->offset_x = offset_x;
|
||||
canvas->offset_y = offset_y;
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
}
|
||||
|
||||
uint8_t canvas_width(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return canvas->width;
|
||||
}
|
||||
|
||||
uint8_t canvas_height(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
return canvas->height;
|
||||
}
|
||||
|
||||
uint8_t canvas_current_font_height(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
uint8_t font_height = u8g2_GetMaxCharHeight(&canvas->fb);
|
||||
|
||||
if(canvas->fb.font == u8g2_font_haxrcorp4089_tr) {
|
||||
font_height += 1;
|
||||
}
|
||||
|
||||
return font_height;
|
||||
}
|
||||
|
||||
CanvasFontParameters* canvas_get_font_params(Canvas* canvas, Font font) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(font < FontTotalNumber);
|
||||
return (CanvasFontParameters*)&canvas_font_params[font];
|
||||
}
|
||||
|
||||
void canvas_clear(Canvas* canvas) {
|
||||
furi_assert(canvas);
|
||||
u8g2_ClearBuffer(&canvas->fb);
|
||||
}
|
||||
|
||||
void canvas_set_color(Canvas* canvas, Color color) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetDrawColor(&canvas->fb, color);
|
||||
}
|
||||
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetFontDirection(&canvas->fb, dir);
|
||||
}
|
||||
|
||||
void canvas_invert_color(Canvas* canvas) {
|
||||
canvas->fb.draw_color = !canvas->fb.draw_color;
|
||||
}
|
||||
|
||||
void canvas_set_font(Canvas* canvas, Font font) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetFontMode(&canvas->fb, 1);
|
||||
if(font == FontPrimary) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_helvB08_tr);
|
||||
} else if(font == FontSecondary) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_haxrcorp4089_tr);
|
||||
} else if(font == FontKeyboard) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_profont11_mr);
|
||||
} else if(font == FontBigNumbers) {
|
||||
u8g2_SetFont(&canvas->fb, u8g2_font_profont22_tn);
|
||||
} else {
|
||||
furi_crash(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_str(Canvas* canvas, uint8_t x, uint8_t y, const char* str) {
|
||||
furi_assert(canvas);
|
||||
if(!str) return;
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawStr(&canvas->fb, x, y, str);
|
||||
}
|
||||
|
||||
void canvas_draw_str_aligned(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical,
|
||||
const char* str) {
|
||||
furi_assert(canvas);
|
||||
if(!str) return;
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
|
||||
switch(horizontal) {
|
||||
case AlignLeft:
|
||||
break;
|
||||
case AlignRight:
|
||||
x -= u8g2_GetStrWidth(&canvas->fb, str);
|
||||
break;
|
||||
case AlignCenter:
|
||||
x -= (u8g2_GetStrWidth(&canvas->fb, str) / 2);
|
||||
break;
|
||||
default:
|
||||
furi_crash(NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
switch(vertical) {
|
||||
case AlignTop:
|
||||
y += u8g2_GetAscent(&canvas->fb);
|
||||
break;
|
||||
case AlignBottom:
|
||||
break;
|
||||
case AlignCenter:
|
||||
y += (u8g2_GetAscent(&canvas->fb) / 2);
|
||||
break;
|
||||
default:
|
||||
furi_crash(NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
u8g2_DrawStr(&canvas->fb, x, y, str);
|
||||
}
|
||||
|
||||
uint16_t canvas_string_width(Canvas* canvas, const char* str) {
|
||||
furi_assert(canvas);
|
||||
if(!str) return 0;
|
||||
return u8g2_GetStrWidth(&canvas->fb, str);
|
||||
}
|
||||
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
|
||||
}
|
||||
|
||||
void canvas_draw_bitmap(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
const uint8_t* compressed_bitmap_data) {
|
||||
furi_assert(canvas);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* bitmap_data = NULL;
|
||||
furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data);
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
|
||||
}
|
||||
|
||||
void canvas_draw_icon_animation(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
IconAnimation* icon_animation) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(icon_animation);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* icon_data = NULL;
|
||||
furi_hal_compress_icon_decode(icon_animation_get_data(icon_animation), &icon_data);
|
||||
u8g2_DrawXBM(
|
||||
&canvas->fb,
|
||||
x,
|
||||
y,
|
||||
icon_animation_get_width(icon_animation),
|
||||
icon_animation_get_height(icon_animation),
|
||||
icon_data);
|
||||
}
|
||||
|
||||
void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(icon);
|
||||
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
uint8_t* icon_data = NULL;
|
||||
furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data);
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data);
|
||||
}
|
||||
|
||||
void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawPixel(&canvas->fb, x, y);
|
||||
}
|
||||
|
||||
void canvas_draw_box(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawBox(&canvas->fb, x, y, width, height);
|
||||
}
|
||||
|
||||
void canvas_draw_rbox(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawRBox(&canvas->fb, x, y, width, height, radius);
|
||||
}
|
||||
|
||||
void canvas_draw_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawFrame(&canvas->fb, x, y, width, height);
|
||||
}
|
||||
|
||||
void canvas_draw_rframe(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
uint8_t height,
|
||||
uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawRFrame(&canvas->fb, x, y, width, height, radius);
|
||||
}
|
||||
|
||||
void canvas_draw_line(Canvas* canvas, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
|
||||
furi_assert(canvas);
|
||||
x1 += canvas->offset_x;
|
||||
y1 += canvas->offset_y;
|
||||
x2 += canvas->offset_x;
|
||||
y2 += canvas->offset_y;
|
||||
u8g2_DrawLine(&canvas->fb, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void canvas_draw_circle(Canvas* canvas, uint8_t x, uint8_t y, uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawCircle(&canvas->fb, x, y, radius, U8G2_DRAW_ALL);
|
||||
}
|
||||
|
||||
void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t radius) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawDisc(&canvas->fb, x, y, radius, U8G2_DRAW_ALL);
|
||||
}
|
||||
|
||||
void canvas_draw_triangle(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t base,
|
||||
uint8_t height,
|
||||
CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y - height + 1);
|
||||
canvas_draw_line(canvas, x, y - height + 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y + height - 1);
|
||||
canvas_draw_line(canvas, x, y + height - 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x - height + 1, y);
|
||||
canvas_draw_line(canvas, x - height + 1, y, x, y + base / 2);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x + height - 1, y);
|
||||
canvas_draw_line(canvas, x + height - 1, y, x, y + base / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_xbm(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
const uint8_t* bitmap) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap);
|
||||
}
|
||||
|
||||
void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) {
|
||||
furi_assert(canvas);
|
||||
x += canvas->offset_x;
|
||||
y += canvas->offset_y;
|
||||
u8g2_DrawGlyph(&canvas->fb, x, y, ch);
|
||||
}
|
||||
|
||||
void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) {
|
||||
u8g2_SetBitmapMode(&canvas->fb, alpha ? 1 : 0);
|
||||
}
|
||||
|
||||
void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) {
|
||||
furi_assert(canvas);
|
||||
if(canvas->orientation != orientation) {
|
||||
canvas->orientation = orientation;
|
||||
if(canvas->orientation == CanvasOrientationHorizontal) {
|
||||
FURI_SWAP(canvas->width, canvas->height);
|
||||
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0);
|
||||
} else if(canvas->orientation == CanvasOrientationVertical) {
|
||||
FURI_SWAP(canvas->width, canvas->height);
|
||||
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CanvasOrientation canvas_get_orientation(const Canvas* canvas) {
|
||||
return canvas->orientation;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user