mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Storage: remove LFS (#3577)
* Storage: drop internal storage * Storage: rollback some unnecessary changes * Storage: rollback some unnecessary changes part 2 * Storage: cleanup various defines and int handling. Ble: allow short connection interval if internal flash is not used. * Storage: do not return storage if it is not ready * Save PIN code to RTC, update settings * Simplify the code, clean up includes * Rearrange some code * apps: storage_move_to_sd: conditionally enable with --extra-define=STORAGE_INT_ON_LFS * Load Desktop settings automatically * Redirect /any to /ext * Abolish storage_move_to_sd app * Remove as many mentions of ANY_PATH as possible * Fix desktop settings wrongly not loading * Improve desktop settings handling and strings * Load BLE settings and keys automatically * Improve BLE configuration procedure * Do not load bluetooth keys twice if they were already loaded * Load dolphin state automatically * Fix merge artifact * Load notification settings automatically * Update desktop settings strings * Load expansion settings automatically * Do not use thread signals to reload desktop settings * Load region data automatically, separate to its own hook * Improve ble behaviour with no keys * Fix Dolphin state not resetting correctly * Add a status check * Make Desktop save its own settings * Check result when taking and releasing mutex * Improve default thread signal handling in FuriEventLoop * Make bt service in charge of saving settings, add settings api * Fix a deadlock due to timer thread not receiving time * Lock core2 when reinitialising bt * Update clang-format * Revert "Update clang-format" This reverts commit d61295ac063c6ec879375ceeab54d6ff2c90a9a1. * Format sources with clang-format * Revert old stack size for desktop settings * Allocate big struct dynamically * Simplify PIN comparison * Save pointer to storage in Desktop object * Fix region provisioning for hardware regions * Remove stale TODO + siimplify code * Clean up region.c * Use sizeof instead of macro define * Limit PIN length to 10 for consistency * Emit a warning upon usage of /any * Add delay after finding flipper * Remove unnecessary delay * Remove all mentions of STORAGE_INT_ON_LFS * Remove littlefs and internal storage * Remove all possible LittleFS mentions * Fix browser tab in Archive * Ble: fix connection interval explanation * Bump API Symbols * BLE: Update comments interval connection comments * Storage: clear FuriHalRtcFlagStorageFormatInternal if set --------- Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com> Co-authored-by: hedger <hedger@nanode.su> Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com>
This commit is contained in:
@@ -61,6 +61,21 @@ static void bt_pin_code_view_port_input_callback(InputEvent* event, void* contex
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_storage_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
const BtMessage msg = {
|
||||
.type = BtMessageTypeReloadKeysSettings,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -143,8 +158,6 @@ Bt* bt_alloc(void) {
|
||||
// Init default maximum packet size
|
||||
bt->max_packet_size = BLE_PROFILE_SERIAL_PACKET_SIZE_MAX;
|
||||
bt->current_profile = NULL;
|
||||
// Load settings
|
||||
bt_settings_load(&bt->bt_settings);
|
||||
// Keys storage
|
||||
bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH);
|
||||
// Alloc queue
|
||||
@@ -396,6 +409,8 @@ void bt_close_rpc_connection(Bt* bt) {
|
||||
|
||||
static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
if(furi_hal_bt_is_gatt_gap_supported()) {
|
||||
bt_settings_load(&bt->bt_settings);
|
||||
|
||||
bt_close_rpc_connection(bt);
|
||||
|
||||
bt_keys_storage_load(bt->keys_storage);
|
||||
@@ -439,6 +454,87 @@ static void bt_close_connection(Bt* bt, BtMessage* message) {
|
||||
if(message->lock) api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_apply_settings(Bt* bt) {
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
} else {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_load_keys(Bt* bt) {
|
||||
if(!furi_hal_bt_is_gatt_gap_supported()) {
|
||||
bt_show_warning(bt, "Unsupported radio stack");
|
||||
bt->status = BtStatusUnavailable;
|
||||
return;
|
||||
|
||||
} else if(bt_keys_storage_is_changed(bt->keys_storage)) {
|
||||
FURI_LOG_I(TAG, "Loading new keys");
|
||||
|
||||
bt_close_rpc_connection(bt);
|
||||
bt_keys_storage_load(bt->keys_storage);
|
||||
|
||||
bt->current_profile = NULL;
|
||||
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Keys unchanged");
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_start_application(Bt* bt) {
|
||||
if(!bt->current_profile) {
|
||||
bt->current_profile =
|
||||
furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
|
||||
|
||||
if(!bt->current_profile) {
|
||||
FURI_LOG_E(TAG, "BLE App start failed");
|
||||
bt->status = BtStatusUnavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_load_settings(Bt* bt) {
|
||||
bt_settings_load(&bt->bt_settings);
|
||||
bt_apply_settings(bt);
|
||||
}
|
||||
|
||||
static void bt_handle_get_settings(Bt* bt, BtMessage* message) {
|
||||
furi_assert(message->lock);
|
||||
*message->data.settings = bt->bt_settings;
|
||||
api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_handle_set_settings(Bt* bt, BtMessage* message) {
|
||||
furi_assert(message->lock);
|
||||
bt->bt_settings = *message->data.csettings;
|
||||
|
||||
bt_apply_settings(bt);
|
||||
bt_settings_save(&bt->bt_settings);
|
||||
|
||||
api_lock_unlock(message->lock);
|
||||
}
|
||||
|
||||
static void bt_handle_reload_keys_settings(Bt* bt) {
|
||||
bt_load_keys(bt);
|
||||
bt_start_application(bt);
|
||||
bt_load_settings(bt);
|
||||
}
|
||||
|
||||
static void bt_init_keys_settings(Bt* bt) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), bt_storage_callback, bt);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
|
||||
|
||||
// Just start the BLE serial application without loading the keys or settings
|
||||
bt_start_application(bt);
|
||||
return;
|
||||
}
|
||||
|
||||
bt_handle_reload_keys_settings(bt);
|
||||
}
|
||||
|
||||
bool bt_remote_rssi(Bt* bt, uint8_t* rssi) {
|
||||
furi_assert(bt);
|
||||
|
||||
@@ -465,35 +561,18 @@ int32_t bt_srv(void* p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Load keys
|
||||
if(!bt_keys_storage_load(bt->keys_storage)) {
|
||||
FURI_LOG_W(TAG, "Failed to load bonding keys");
|
||||
}
|
||||
if(furi_hal_bt_start_radio_stack()) {
|
||||
bt_init_keys_settings(bt);
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
|
||||
// Start radio stack
|
||||
if(!furi_hal_bt_start_radio_stack()) {
|
||||
FURI_LOG_E(TAG, "Radio stack start failed");
|
||||
}
|
||||
|
||||
if(furi_hal_bt_is_gatt_gap_supported()) {
|
||||
bt->current_profile =
|
||||
furi_hal_bt_start_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
|
||||
if(!bt->current_profile) {
|
||||
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_LOG_E(TAG, "Radio stack start failed");
|
||||
}
|
||||
|
||||
furi_record_create(RECORD_BT, bt);
|
||||
|
||||
BtMessage message;
|
||||
|
||||
while(1) {
|
||||
furi_check(
|
||||
furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
@@ -523,7 +602,14 @@ int32_t bt_srv(void* p) {
|
||||
bt_close_connection(bt, &message);
|
||||
} else if(message.type == BtMessageTypeForgetBondedDevices) {
|
||||
bt_keys_storage_delete(bt->keys_storage);
|
||||
} else if(message.type == BtMessageTypeGetSettings) {
|
||||
bt_handle_get_settings(bt, &message);
|
||||
} else if(message.type == BtMessageTypeSetSettings) {
|
||||
bt_handle_set_settings(bt, &message);
|
||||
} else if(message.type == BtMessageTypeReloadKeysSettings) {
|
||||
bt_handle_reload_keys_settings(bt);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -77,3 +77,39 @@ void bt_keys_storage_set_default_path(Bt* bt) {
|
||||
|
||||
bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH);
|
||||
}
|
||||
|
||||
/*
|
||||
* Private API for the Settings app
|
||||
*/
|
||||
|
||||
void bt_get_settings(Bt* bt, BtSettings* settings) {
|
||||
furi_assert(bt);
|
||||
furi_assert(settings);
|
||||
|
||||
BtMessage message = {
|
||||
.lock = api_lock_alloc_locked(),
|
||||
.type = BtMessageTypeGetSettings,
|
||||
.data.settings = settings,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
api_lock_wait_unlock_and_free(message.lock);
|
||||
}
|
||||
|
||||
void bt_set_settings(Bt* bt, const BtSettings* settings) {
|
||||
furi_assert(bt);
|
||||
furi_assert(settings);
|
||||
|
||||
BtMessage message = {
|
||||
.lock = api_lock_alloc_locked(),
|
||||
.type = BtMessageTypeSetSettings,
|
||||
.data.csettings = settings,
|
||||
};
|
||||
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
api_lock_wait_unlock_and_free(message.lock);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ typedef enum {
|
||||
BtMessageTypeSetProfile,
|
||||
BtMessageTypeDisconnect,
|
||||
BtMessageTypeForgetBondedDevices,
|
||||
BtMessageTypeGetSettings,
|
||||
BtMessageTypeSetSettings,
|
||||
BtMessageTypeReloadKeysSettings,
|
||||
} BtMessageType;
|
||||
|
||||
typedef struct {
|
||||
@@ -49,6 +52,8 @@ typedef union {
|
||||
} profile;
|
||||
FuriHalBleProfileParams profile_params;
|
||||
BtKeyStorageUpdateData key_storage_data;
|
||||
BtSettings* settings;
|
||||
const BtSettings* csettings;
|
||||
} BtMessageData;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
struct BtKeysStorage {
|
||||
uint8_t* nvm_sram_buff;
|
||||
uint16_t nvm_sram_buff_size;
|
||||
uint16_t current_size;
|
||||
FuriString* file_path;
|
||||
};
|
||||
|
||||
@@ -66,44 +67,114 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint
|
||||
instance->nvm_sram_buff_size = size;
|
||||
}
|
||||
|
||||
static bool bt_keys_storage_file_exists(const char* file_path) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo file_info;
|
||||
const bool ret = storage_common_stat(storage, file_path, &file_info) == FSE_OK &&
|
||||
file_info.size != 0;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) {
|
||||
uint8_t magic, version;
|
||||
size_t size;
|
||||
|
||||
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
|
||||
FURI_LOG_E(TAG, "Failed to get metadata");
|
||||
return false;
|
||||
|
||||
} else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
|
||||
FURI_LOG_E(TAG, "File version mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
*payload_size = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool is_changed = false;
|
||||
uint8_t* data_buffer = NULL;
|
||||
|
||||
do {
|
||||
const char* file_path = furi_string_get_cstr(instance->file_path);
|
||||
size_t payload_size;
|
||||
|
||||
if(!bt_keys_storage_file_exists(file_path)) {
|
||||
FURI_LOG_W(TAG, "Missing or empty file");
|
||||
break;
|
||||
|
||||
} else if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
|
||||
FURI_LOG_E(TAG, "Invalid or corrupted file");
|
||||
break;
|
||||
}
|
||||
|
||||
data_buffer = malloc(payload_size);
|
||||
|
||||
const bool data_loaded = saved_struct_load(
|
||||
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION);
|
||||
|
||||
if(!data_loaded) {
|
||||
FURI_LOG_E(TAG, "Failed to load file");
|
||||
break;
|
||||
|
||||
} else if(payload_size == instance->current_size) {
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Size mismatch");
|
||||
is_changed = true;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if(data_buffer) {
|
||||
free(data_buffer);
|
||||
}
|
||||
|
||||
return is_changed;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_load(BtKeysStorage* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
do {
|
||||
const char* file_path = furi_string_get_cstr(instance->file_path);
|
||||
|
||||
// Get payload size
|
||||
uint8_t magic = 0, version = 0;
|
||||
size_t payload_size = 0;
|
||||
if(!saved_struct_get_metadata(
|
||||
furi_string_get_cstr(instance->file_path), &magic, &version, &payload_size)) {
|
||||
FURI_LOG_E(TAG, "Failed to read payload size");
|
||||
size_t payload_size;
|
||||
if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
|
||||
FURI_LOG_E(TAG, "Invalid or corrupted file");
|
||||
break;
|
||||
}
|
||||
|
||||
if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
|
||||
FURI_LOG_E(TAG, "Saved data version is mismatched");
|
||||
break;
|
||||
}
|
||||
|
||||
if(payload_size > instance->nvm_sram_buff_size) {
|
||||
FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer");
|
||||
} else if(payload_size > instance->nvm_sram_buff_size) {
|
||||
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
|
||||
break;
|
||||
}
|
||||
|
||||
// Load saved data to ram
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
bool data_loaded = saved_struct_load(
|
||||
furi_string_get_cstr(instance->file_path),
|
||||
const bool data_loaded = saved_struct_load(
|
||||
file_path,
|
||||
instance->nvm_sram_buff,
|
||||
payload_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
if(!data_loaded) {
|
||||
FURI_LOG_E(TAG, "Failed to load struct");
|
||||
FURI_LOG_E(TAG, "Failed to load file");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->current_size = payload_size;
|
||||
|
||||
loaded = true;
|
||||
} while(false);
|
||||
|
||||
@@ -130,6 +201,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
|
||||
break;
|
||||
}
|
||||
|
||||
instance->current_size = new_size;
|
||||
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
bool data_updated = saved_struct_save(
|
||||
furi_string_get_cstr(instance->file_path),
|
||||
@@ -138,10 +211,12 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
if(!data_updated) {
|
||||
FURI_LOG_E(TAG, "Failed to update key storage");
|
||||
break;
|
||||
}
|
||||
|
||||
updated = true;
|
||||
} while(false);
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path);
|
||||
|
||||
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size);
|
||||
|
||||
bool bt_keys_storage_is_changed(BtKeysStorage* instance);
|
||||
|
||||
bool bt_keys_storage_load(BtKeysStorage* instance);
|
||||
|
||||
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
|
||||
|
||||
8
applications/services/bt/bt_service/bt_settings_api_i.h
Normal file
8
applications/services/bt/bt_service/bt_settings_api_i.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt.h"
|
||||
#include "../bt_settings.h"
|
||||
|
||||
void bt_get_settings(Bt* bt, BtSettings* settings);
|
||||
|
||||
void bt_set_settings(Bt* bt, const BtSettings* settings);
|
||||
@@ -1,23 +1,36 @@
|
||||
#include "bt_settings.h"
|
||||
#include "bt_settings_filename.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/saved_struct.h>
|
||||
|
||||
#define TAG "BtSettings"
|
||||
|
||||
#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) {
|
||||
void bt_settings_load(BtSettings* bt_settings) {
|
||||
furi_assert(bt_settings);
|
||||
|
||||
return saved_struct_load(
|
||||
const bool success = saved_struct_load(
|
||||
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Failed to load settings, using defaults");
|
||||
memset(bt_settings, 0, sizeof(BtSettings));
|
||||
bt_settings_save(bt_settings);
|
||||
}
|
||||
}
|
||||
|
||||
bool bt_settings_save(const BtSettings* bt_settings) {
|
||||
void bt_settings_save(const BtSettings* bt_settings) {
|
||||
furi_assert(bt_settings);
|
||||
|
||||
return saved_struct_save(
|
||||
const bool success = saved_struct_save(
|
||||
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_E(TAG, "Failed to save settings");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt_settings_filename.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -13,9 +10,9 @@ typedef struct {
|
||||
bool enabled;
|
||||
} BtSettings;
|
||||
|
||||
bool bt_settings_load(BtSettings* bt_settings);
|
||||
void bt_settings_load(BtSettings* bt_settings);
|
||||
|
||||
bool bt_settings_save(const BtSettings* bt_settings);
|
||||
void bt_settings_save(const BtSettings* bt_settings);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -97,8 +97,11 @@ void animation_manager_set_interact_callback(
|
||||
|
||||
void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled) {
|
||||
furi_assert(animation_manager);
|
||||
animation_manager->dummy_mode = enabled;
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
// Prevent change of animations if mode is the same
|
||||
if(animation_manager->dummy_mode != enabled) {
|
||||
animation_manager->dummy_mode = enabled;
|
||||
animation_manager_start_new_idle(animation_manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void animation_manager_check_blocking_callback(const void* message, void* context) {
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
#include <storage/storage.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/gui_i.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "desktop_i.h"
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_vcp.h>
|
||||
#include <locale/locale.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.h"
|
||||
#include "helpers/slideshow_filename.h"
|
||||
#include <gui/gui_i.h>
|
||||
|
||||
#include <locale/locale.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include "scenes/desktop_scene.h"
|
||||
#include "scenes/desktop_scene_locked.h"
|
||||
|
||||
#define TAG "Desktop"
|
||||
|
||||
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_apply_settings(Desktop*);
|
||||
|
||||
static void desktop_loader_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
@@ -42,6 +35,16 @@ static void desktop_loader_callback(const void* message, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_storage_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalReloadSettings);
|
||||
}
|
||||
}
|
||||
|
||||
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
|
||||
UNUSED(context);
|
||||
furi_assert(canvas);
|
||||
@@ -122,31 +125,39 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
switch(event) {
|
||||
case DesktopGlobalBeforeAppStarted:
|
||||
if(event == DesktopGlobalBeforeAppStarted) {
|
||||
if(animation_manager_is_animation_loaded(desktop->animation_manager)) {
|
||||
animation_manager_unload_and_stall_animation(desktop->animation_manager);
|
||||
}
|
||||
desktop_auto_lock_inhibit(desktop);
|
||||
furi_semaphore_release(desktop->animation_semaphore);
|
||||
return true;
|
||||
case DesktopGlobalAfterAppFinished:
|
||||
animation_manager_load_and_continue_animation(desktop->animation_manager);
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
|
||||
desktop_clock_reconfigure(desktop);
|
||||
if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
|
||||
desktop_auto_lock_arm(desktop);
|
||||
}
|
||||
return true;
|
||||
case DesktopGlobalAutoLock:
|
||||
if(!loader_is_locked(desktop->loader) && !desktop->locked) {
|
||||
desktop_auto_lock_inhibit(desktop);
|
||||
desktop->app_running = true;
|
||||
|
||||
furi_semaphore_release(desktop->animation_semaphore);
|
||||
|
||||
} else if(event == DesktopGlobalAfterAppFinished) {
|
||||
animation_manager_load_and_continue_animation(desktop->animation_manager);
|
||||
desktop_auto_lock_arm(desktop);
|
||||
desktop->app_running = false;
|
||||
|
||||
} else if(event == DesktopGlobalAutoLock) {
|
||||
if(!desktop->app_running && !desktop->locked) {
|
||||
desktop_lock(desktop);
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event == DesktopGlobalSaveSettings) {
|
||||
desktop_settings_save(&desktop->settings);
|
||||
desktop_apply_settings(desktop);
|
||||
|
||||
} else if(event == DesktopGlobalReloadSettings) {
|
||||
desktop_settings_load(&desktop->settings);
|
||||
desktop_apply_settings(desktop);
|
||||
|
||||
} else {
|
||||
return scene_manager_handle_custom_event(desktop->scene_manager, event);
|
||||
}
|
||||
|
||||
return scene_manager_handle_custom_event(desktop->scene_manager, event);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool desktop_back_event_callback(void* context) {
|
||||
@@ -206,84 +217,45 @@ static void desktop_clock_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Desktop* desktop = context;
|
||||
|
||||
if(gui_active_view_port_count(desktop->gui, GuiLayerStatusBarLeft) < 6) {
|
||||
const bool clock_enabled = gui_active_view_port_count(desktop->gui, GuiLayerStatusBarLeft) < 6;
|
||||
|
||||
if(clock_enabled) {
|
||||
desktop_clock_update(desktop);
|
||||
|
||||
view_port_enabled_set(desktop->clock_viewport, true);
|
||||
} else {
|
||||
view_port_enabled_set(desktop->clock_viewport, false);
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_lock(Desktop* desktop) {
|
||||
furi_assert(!desktop->locked);
|
||||
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
|
||||
|
||||
if(desktop->settings.pin_code.length) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
DesktopStatus status = {.locked = true};
|
||||
furi_pubsub_publish(desktop->status_pubsub, &status);
|
||||
|
||||
desktop->locked = true;
|
||||
view_port_enabled_set(desktop->clock_viewport, clock_enabled);
|
||||
}
|
||||
|
||||
void desktop_unlock(Desktop* desktop) {
|
||||
furi_assert(desktop->locked);
|
||||
|
||||
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);
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
|
||||
furi_hal_rtc_set_pin_fails(0);
|
||||
|
||||
if(desktop->settings.pin_code.length) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
DesktopStatus status = {.locked = false};
|
||||
furi_pubsub_publish(desktop->status_pubsub, &status);
|
||||
|
||||
desktop->locked = false;
|
||||
}
|
||||
|
||||
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
|
||||
static void desktop_apply_settings(Desktop* desktop) {
|
||||
desktop->in_transition = true;
|
||||
view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled);
|
||||
desktop_main_set_dummy_mode_state(desktop->main_view, enabled);
|
||||
animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled);
|
||||
desktop->settings.dummy_mode = enabled;
|
||||
DESKTOP_SETTINGS_SAVE(&desktop->settings);
|
||||
|
||||
desktop_clock_reconfigure(desktop);
|
||||
|
||||
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);
|
||||
animation_manager_set_dummy_mode_state(
|
||||
desktop->animation_manager, desktop->settings.dummy_mode);
|
||||
|
||||
if(!desktop->app_running && !desktop->locked) {
|
||||
desktop_auto_lock_arm(desktop);
|
||||
}
|
||||
|
||||
desktop->in_transition = false;
|
||||
}
|
||||
|
||||
void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
|
||||
desktop->in_transition = true;
|
||||
if(enabled) {
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStealthMode);
|
||||
} else {
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagStealthMode);
|
||||
static void desktop_init_settings(Desktop* desktop) {
|
||||
furi_pubsub_subscribe(storage_get_pubsub(desktop->storage), desktop_storage_callback, desktop);
|
||||
|
||||
if(storage_sd_status(desktop->storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
|
||||
return;
|
||||
}
|
||||
view_port_enabled_set(desktop->stealth_mode_icon_viewport, enabled);
|
||||
desktop->in_transition = false;
|
||||
|
||||
desktop_settings_load(&desktop->settings);
|
||||
desktop_apply_settings(desktop);
|
||||
}
|
||||
|
||||
Desktop* desktop_alloc(void) {
|
||||
static Desktop* desktop_alloc(void) {
|
||||
Desktop* desktop = malloc(sizeof(Desktop));
|
||||
|
||||
desktop->animation_semaphore = furi_semaphore_alloc(1, 0);
|
||||
@@ -392,14 +364,13 @@ Desktop* desktop_alloc(void) {
|
||||
}
|
||||
gui_add_view_port(desktop->gui, desktop->stealth_mode_icon_viewport, GuiLayerStatusBarLeft);
|
||||
|
||||
// Unload animations before starting an application
|
||||
desktop->loader = furi_record_open(RECORD_LOADER);
|
||||
furi_pubsub_subscribe(loader_get_pubsub(desktop->loader), desktop_loader_callback, desktop);
|
||||
|
||||
desktop->storage = furi_record_open(RECORD_STORAGE);
|
||||
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);
|
||||
@@ -409,19 +380,95 @@ Desktop* desktop_alloc(void) {
|
||||
desktop->update_clock_timer =
|
||||
furi_timer_alloc(desktop_clock_timer_callback, FuriTimerTypePeriodic, desktop);
|
||||
|
||||
desktop->app_running = loader_is_locked(desktop->loader);
|
||||
|
||||
furi_record_create(RECORD_DESKTOP, desktop);
|
||||
|
||||
return 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);
|
||||
/*
|
||||
* Private API
|
||||
*/
|
||||
|
||||
return exists;
|
||||
void desktop_lock(Desktop* desktop) {
|
||||
furi_assert(!desktop->locked);
|
||||
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
|
||||
|
||||
if(desktop_pin_code_is_set()) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
desktop_auto_lock_inhibit(desktop);
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopSceneLocked, DesktopSceneLockedStateFirstEnter);
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
|
||||
|
||||
DesktopStatus status = {.locked = true};
|
||||
furi_pubsub_publish(desktop->status_pubsub, &status);
|
||||
|
||||
desktop->locked = true;
|
||||
}
|
||||
|
||||
void desktop_unlock(Desktop* desktop) {
|
||||
furi_assert(desktop->locked);
|
||||
|
||||
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);
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
|
||||
furi_hal_rtc_set_pin_fails(0);
|
||||
|
||||
if(desktop_pin_code_is_set()) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
DesktopStatus status = {.locked = false};
|
||||
furi_pubsub_publish(desktop->status_pubsub, &status);
|
||||
|
||||
desktop->locked = false;
|
||||
}
|
||||
|
||||
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {
|
||||
desktop->in_transition = true;
|
||||
|
||||
view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled);
|
||||
desktop_main_set_dummy_mode_state(desktop->main_view, enabled);
|
||||
animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled);
|
||||
desktop->settings.dummy_mode = enabled;
|
||||
|
||||
desktop->in_transition = false;
|
||||
|
||||
desktop_settings_save(&desktop->settings);
|
||||
}
|
||||
|
||||
void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
|
||||
desktop->in_transition = true;
|
||||
|
||||
if(enabled) {
|
||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStealthMode);
|
||||
} else {
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagStealthMode);
|
||||
}
|
||||
|
||||
view_port_enabled_set(desktop->stealth_mode_icon_viewport, enabled);
|
||||
|
||||
desktop->in_transition = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
bool desktop_api_is_locked(Desktop* instance) {
|
||||
furi_assert(instance);
|
||||
return furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock);
|
||||
@@ -437,6 +484,30 @@ FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) {
|
||||
return instance->status_pubsub;
|
||||
}
|
||||
|
||||
void desktop_api_reload_settings(Desktop* instance) {
|
||||
furi_assert(instance);
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalReloadSettings);
|
||||
}
|
||||
|
||||
void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings) {
|
||||
furi_assert(instance);
|
||||
furi_assert(settings);
|
||||
|
||||
*settings = instance->settings;
|
||||
}
|
||||
|
||||
void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings) {
|
||||
furi_assert(instance);
|
||||
furi_assert(settings);
|
||||
|
||||
instance->settings = *settings;
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalSaveSettings);
|
||||
}
|
||||
|
||||
/*
|
||||
* Application thread
|
||||
*/
|
||||
|
||||
int32_t desktop_srv(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
@@ -449,31 +520,15 @@ int32_t desktop_srv(void* 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_clock_reconfigure(desktop);
|
||||
|
||||
desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode);
|
||||
animation_manager_set_dummy_mode_state(
|
||||
desktop->animation_manager, desktop->settings.dummy_mode);
|
||||
desktop_init_settings(desktop);
|
||||
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
|
||||
desktop_lock(desktop);
|
||||
} else {
|
||||
if(!loader_is_locked(desktop->loader)) {
|
||||
desktop_auto_lock_arm(desktop);
|
||||
}
|
||||
}
|
||||
|
||||
if(desktop_check_file_flag(SLIDESHOW_FS_PATH)) {
|
||||
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
|
||||
}
|
||||
|
||||
@@ -497,14 +552,12 @@ int32_t desktop_srv(void* p) {
|
||||
}
|
||||
|
||||
// Special case: autostart application is already running
|
||||
if(loader_is_locked(desktop->loader) &&
|
||||
animation_manager_is_animation_loaded(desktop->animation_manager)) {
|
||||
if(desktop->app_running && animation_manager_is_animation_loaded(desktop->animation_manager)) {
|
||||
animation_manager_unload_and_stall_animation(desktop->animation_manager);
|
||||
}
|
||||
|
||||
view_dispatcher_run(desktop->view_dispatcher);
|
||||
|
||||
furi_crash("That was unexpected");
|
||||
|
||||
// Should never get here (a service thread will crash automatically if it returns)
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
typedef struct Desktop Desktop;
|
||||
#include "desktop_settings.h"
|
||||
|
||||
#define RECORD_DESKTOP "desktop"
|
||||
|
||||
bool desktop_api_is_locked(Desktop* instance);
|
||||
|
||||
void desktop_api_unlock(Desktop* instance);
|
||||
typedef struct Desktop Desktop;
|
||||
|
||||
typedef struct {
|
||||
bool locked;
|
||||
} DesktopStatus;
|
||||
|
||||
bool desktop_api_is_locked(Desktop* instance);
|
||||
|
||||
void desktop_api_unlock(Desktop* instance);
|
||||
|
||||
FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance);
|
||||
|
||||
void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings);
|
||||
|
||||
void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "desktop.h"
|
||||
#include "desktop_settings.h"
|
||||
|
||||
#include "animations/animation_manager.h"
|
||||
#include "views/desktop_view_pin_timeout.h"
|
||||
#include "views/desktop_view_pin_input.h"
|
||||
@@ -9,9 +11,7 @@
|
||||
#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>
|
||||
@@ -42,9 +42,8 @@ typedef struct {
|
||||
} DesktopClock;
|
||||
|
||||
struct Desktop {
|
||||
// Scene
|
||||
FuriThread* scene_thread;
|
||||
// GUI
|
||||
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
@@ -56,42 +55,38 @@ struct Desktop {
|
||||
DesktopMainView* main_view;
|
||||
DesktopViewPinTimeout* pin_timeout_view;
|
||||
DesktopSlideshowView* slideshow_view;
|
||||
DesktopViewPinInput* pin_input_view;
|
||||
|
||||
ViewStack* main_view_stack;
|
||||
ViewStack* locked_view_stack;
|
||||
|
||||
DesktopSettings settings;
|
||||
DesktopViewPinInput* pin_input_view;
|
||||
|
||||
ViewPort* lock_icon_viewport;
|
||||
ViewPort* dummy_mode_icon_viewport;
|
||||
ViewPort* clock_viewport;
|
||||
ViewPort* stealth_mode_icon_viewport;
|
||||
|
||||
AnimationManager* animation_manager;
|
||||
|
||||
Loader* loader;
|
||||
Storage* storage;
|
||||
NotificationApp* notification;
|
||||
|
||||
FuriPubSubSubscription* app_start_stop_subscription;
|
||||
FuriPubSub* status_pubsub;
|
||||
FuriPubSub* input_events_pubsub;
|
||||
FuriPubSubSubscription* input_events_subscription;
|
||||
|
||||
FuriTimer* auto_lock_timer;
|
||||
FuriTimer* update_clock_timer;
|
||||
|
||||
FuriPubSub* status_pubsub;
|
||||
AnimationManager* animation_manager;
|
||||
FuriSemaphore* animation_semaphore;
|
||||
|
||||
DesktopClock clock;
|
||||
DesktopSettings settings;
|
||||
|
||||
bool in_transition : 1;
|
||||
bool locked : 1;
|
||||
|
||||
FuriSemaphore* animation_semaphore;
|
||||
bool in_transition;
|
||||
bool app_running;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
Desktop* desktop_alloc(void);
|
||||
|
||||
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);
|
||||
|
||||
79
applications/services/desktop/desktop_settings.c
Normal file
79
applications/services/desktop/desktop_settings.c
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "desktop_settings.h"
|
||||
#include "desktop_settings_filename.h"
|
||||
|
||||
#include <saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "DesktopSettings"
|
||||
|
||||
#define DESKTOP_SETTINGS_VER_10 (10)
|
||||
#define DESKTOP_SETTINGS_VER (11)
|
||||
|
||||
#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
|
||||
#define DESKTOP_SETTINGS_MAGIC (0x17)
|
||||
|
||||
typedef struct {
|
||||
uint8_t reserved[11];
|
||||
DesktopSettings settings;
|
||||
} DesktopSettingsV10;
|
||||
|
||||
// Actual size of DesktopSettings v10
|
||||
static_assert(sizeof(DesktopSettingsV10) == 1044);
|
||||
|
||||
void desktop_settings_load(DesktopSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
uint8_t version;
|
||||
if(!saved_struct_get_metadata(DESKTOP_SETTINGS_PATH, NULL, &version, NULL)) break;
|
||||
|
||||
if(version == DESKTOP_SETTINGS_VER) {
|
||||
success = saved_struct_load(
|
||||
DESKTOP_SETTINGS_PATH,
|
||||
settings,
|
||||
sizeof(DesktopSettings),
|
||||
DESKTOP_SETTINGS_MAGIC,
|
||||
DESKTOP_SETTINGS_VER);
|
||||
|
||||
} else if(version == DESKTOP_SETTINGS_VER_10) {
|
||||
DesktopSettingsV10* settings_v10 = malloc(sizeof(DesktopSettingsV10));
|
||||
|
||||
success = saved_struct_load(
|
||||
DESKTOP_SETTINGS_PATH,
|
||||
settings_v10,
|
||||
sizeof(DesktopSettingsV10),
|
||||
DESKTOP_SETTINGS_MAGIC,
|
||||
DESKTOP_SETTINGS_VER_10);
|
||||
|
||||
if(success) {
|
||||
*settings = settings_v10->settings;
|
||||
}
|
||||
|
||||
free(settings_v10);
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Failed to load file, using defaults");
|
||||
memset(settings, 0, sizeof(DesktopSettings));
|
||||
desktop_settings_save(settings);
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_settings_save(const DesktopSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
const bool success = saved_struct_save(
|
||||
DESKTOP_SETTINGS_PATH,
|
||||
settings,
|
||||
sizeof(DesktopSettings),
|
||||
DESKTOP_SETTINGS_MAGIC,
|
||||
DESKTOP_SETTINGS_VER);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_E(TAG, "Failed to save file");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
#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 (13)
|
||||
|
||||
@@ -44,7 +38,7 @@
|
||||
#define DISPLAY_BATTERY_BAR_PERCENT 5
|
||||
|
||||
typedef enum {
|
||||
FavoriteAppLeftShort = 0,
|
||||
FavoriteAppLeftShort,
|
||||
FavoriteAppLeftLong,
|
||||
FavoriteAppRightShort,
|
||||
FavoriteAppRightLong,
|
||||
@@ -67,16 +61,10 @@ typedef enum {
|
||||
} DummyAppShortcut;
|
||||
|
||||
typedef struct {
|
||||
InputKey data[MAX_PIN_SIZE];
|
||||
uint8_t length;
|
||||
} PinCode;
|
||||
|
||||
typedef struct {
|
||||
char name_or_path[MAX_APP_LENGTH];
|
||||
char name_or_path[128];
|
||||
} FavoriteApp;
|
||||
|
||||
typedef struct {
|
||||
PinCode pin_code;
|
||||
uint32_t auto_lock_delay_ms;
|
||||
uint8_t displayBatteryPercentage;
|
||||
uint8_t dummy_mode;
|
||||
@@ -84,3 +72,6 @@ typedef struct {
|
||||
FavoriteApp favorite_apps[FavoriteAppNumber];
|
||||
FavoriteApp dummy_apps[DummyAppNumber];
|
||||
} DesktopSettings;
|
||||
|
||||
void desktop_settings_load(DesktopSettings* settings);
|
||||
void desktop_settings_save(const DesktopSettings* settings);
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "pin.h"
|
||||
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <stddef.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.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(void) {
|
||||
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(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool desktop_pin_compare(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;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "../desktop.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
void desktop_pin_lock_error_notify(void);
|
||||
|
||||
uint32_t desktop_pin_lock_get_fail_timeout(void);
|
||||
|
||||
bool desktop_pin_compare(const PinCode* pin_code1, const PinCode* pin_code2);
|
||||
103
applications/services/desktop/helpers/pin_code.c
Normal file
103
applications/services/desktop/helpers/pin_code.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "pin_code.h"
|
||||
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH (2)
|
||||
#define DESKTOP_PIN_CODE_LENGTH_OFFSET (28)
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
static uint32_t desktop_pin_code_pack(const DesktopPinCode* pin_code) {
|
||||
furi_check(pin_code);
|
||||
furi_check(pin_code->length <= sizeof(pin_code->data));
|
||||
|
||||
uint32_t reg_value = 0;
|
||||
|
||||
for(uint8_t i = 0; i < pin_code->length; ++i) {
|
||||
furi_check(pin_code->data[i] < (1 << DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH));
|
||||
reg_value |= (uint32_t)pin_code->data[i] << (i * DESKTOP_PIN_CODE_DIGIT_BIT_WIDTH);
|
||||
}
|
||||
|
||||
reg_value |= (uint32_t)pin_code->length << DESKTOP_PIN_CODE_LENGTH_OFFSET;
|
||||
|
||||
return reg_value;
|
||||
}
|
||||
|
||||
bool desktop_pin_code_is_set(void) {
|
||||
return furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET;
|
||||
}
|
||||
|
||||
void desktop_pin_code_set(const DesktopPinCode* pin_code) {
|
||||
furi_hal_rtc_set_pin_value(desktop_pin_code_pack(pin_code));
|
||||
}
|
||||
|
||||
void desktop_pin_code_reset(void) {
|
||||
furi_hal_rtc_set_pin_value(0);
|
||||
}
|
||||
|
||||
bool desktop_pin_code_check(const DesktopPinCode* pin_code) {
|
||||
return furi_hal_rtc_get_pin_value() == desktop_pin_code_pack(pin_code);
|
||||
}
|
||||
|
||||
bool desktop_pin_code_is_equal(const DesktopPinCode* pin_code1, const DesktopPinCode* pin_code2) {
|
||||
furi_check(pin_code1);
|
||||
furi_check(pin_code1->length <= sizeof(pin_code1->data));
|
||||
furi_check(pin_code2);
|
||||
furi_check(pin_code2->length <= sizeof(pin_code2->data));
|
||||
|
||||
return pin_code1->length == pin_code2->length &&
|
||||
memcmp(pin_code1->data, pin_code2->data, pin_code1->length) == 0;
|
||||
}
|
||||
|
||||
void desktop_pin_lock_error_notify(void) {
|
||||
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(void) {
|
||||
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;
|
||||
}
|
||||
25
applications/services/desktop/helpers/pin_code.h
Normal file
25
applications/services/desktop/helpers/pin_code.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DESKTOP_PIN_CODE_MAX_LEN (10)
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[DESKTOP_PIN_CODE_MAX_LEN];
|
||||
uint8_t length;
|
||||
} DesktopPinCode;
|
||||
|
||||
bool desktop_pin_code_is_set(void);
|
||||
|
||||
void desktop_pin_code_set(const DesktopPinCode* pin_code);
|
||||
|
||||
void desktop_pin_code_reset(void);
|
||||
|
||||
bool desktop_pin_code_check(const DesktopPinCode* pin_code);
|
||||
|
||||
bool desktop_pin_code_is_equal(const DesktopPinCode* pin_code1, const DesktopPinCode* pin_code2);
|
||||
|
||||
void desktop_pin_lock_error_notify(void);
|
||||
|
||||
uint32_t desktop_pin_lock_get_fail_timeout(void);
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define SCENE_LOCKED_FIRST_ENTER 0
|
||||
#define SCENE_LOCKED_REPEAT_ENTER 1
|
||||
@@ -20,7 +20,6 @@ void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
|
||||
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_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode);
|
||||
@@ -38,11 +37,8 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
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) {
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
}
|
||||
if(check_pin_changed && desktop_pin_code_is_set()) {
|
||||
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
#include "../desktop.h"
|
||||
#include "../desktop_i.h"
|
||||
#include "../helpers/pin.h"
|
||||
#include "../helpers/pin_code.h"
|
||||
#include "../animations/animation_manager.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include "../views/desktop_view_locked.h"
|
||||
#include "desktop_scene.h"
|
||||
#include "desktop_scene_i.h"
|
||||
#include "desktop_scene_locked.h"
|
||||
|
||||
#define WRONG_PIN_HEADER_TIMEOUT 3000
|
||||
#define INPUT_PIN_VIEW_TIMEOUT 15000
|
||||
@@ -42,15 +42,13 @@ void desktop_scene_locked_on_enter(void* context) {
|
||||
|
||||
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->settings.pin_code.length > 0;
|
||||
if(state == DesktopSceneLockedStateFirstEnter) {
|
||||
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);
|
||||
if(desktop_pin_code_is_set()) {
|
||||
desktop_view_locked_lock(desktop->locked_view, true);
|
||||
uint32_t pin_timeout = desktop_pin_lock_get_fail_timeout();
|
||||
if(pin_timeout > 0) {
|
||||
@@ -65,7 +63,7 @@ void desktop_scene_locked_on_enter(void* context) {
|
||||
desktop_view_locked_close_doors(desktop->locked_view);
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
desktop->scene_manager, DesktopSceneLocked, SCENE_LOCKED_REPEAT_ENTER);
|
||||
desktop->scene_manager, DesktopSceneLocked, DesktopSceneLockedStateRepeatEnter);
|
||||
}
|
||||
|
||||
if(switch_to_timeout_scene) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
DesktopSceneLockedStateFirstEnter,
|
||||
DesktopSceneLockedStateRepeatEnter,
|
||||
} DesktopSceneLockedState;
|
||||
@@ -155,25 +155,21 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
case DesktopMainEventOpenFavoriteLeftShort:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_scene_main_start_favorite(
|
||||
desktop, &desktop->settings.favorite_apps[FavoriteAppLeftShort]);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopMainEventOpenFavoriteLeftLong:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_scene_main_start_favorite(
|
||||
desktop, &desktop->settings.favorite_apps[FavoriteAppLeftLong]);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopMainEventOpenFavoriteRightShort:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_scene_main_start_favorite(
|
||||
desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopMainEventOpenFavoriteRightLong:
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
desktop_scene_main_start_favorite(
|
||||
desktop, &desktop->settings.favorite_apps[FavoriteAppRightLong]);
|
||||
consumed = true;
|
||||
@@ -189,7 +185,6 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
|
||||
break;
|
||||
case DesktopAnimationEventInteractAnimation:
|
||||
if(!animation_manager_interact_process(desktop->animation_manager)) {
|
||||
DESKTOP_SETTINGS_LOAD(&desktop->settings);
|
||||
if(!desktop->settings.dummy_mode) {
|
||||
desktop_scene_main_open_app_or_profile(
|
||||
desktop, &desktop->settings.favorite_apps[FavoriteAppRightShort]);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_events.h"
|
||||
#include "../views/desktop_view_pin_input.h"
|
||||
#include "../helpers/pin.h"
|
||||
#include "../helpers/pin_code.h"
|
||||
#include "desktop_scene.h"
|
||||
|
||||
#define WRONG_PIN_HEADER_TIMEOUT 3000
|
||||
@@ -49,10 +49,12 @@ static void desktop_scene_pin_input_back_callback(void* context) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventBack);
|
||||
}
|
||||
|
||||
static void desktop_scene_pin_input_done_callback(const PinCode* pin_code, void* context) {
|
||||
static void desktop_scene_pin_input_done_callback(const DesktopPinCode* pin_code, void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
if(desktop_pin_compare(&desktop->settings.pin_code, pin_code)) {
|
||||
|
||||
if(desktop_pin_code_check(pin_code)) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopPinInputEventUnlocked);
|
||||
|
||||
} else {
|
||||
uint32_t pin_fails = furi_hal_rtc_get_pin_fails();
|
||||
furi_hal_rtc_set_pin_fails(pin_fails + 1);
|
||||
|
||||
@@ -45,9 +45,6 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void desktop_scene_slideshow_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_remove(storage, SLIDESHOW_FS_PATH);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
Desktop* desktop = context;
|
||||
storage_common_remove(desktop->storage, SLIDESHOW_FS_PATH);
|
||||
}
|
||||
|
||||
@@ -60,4 +60,6 @@ typedef enum {
|
||||
DesktopGlobalAfterAppFinished,
|
||||
DesktopGlobalAutoLock,
|
||||
DesktopGlobalApiUnlock,
|
||||
DesktopGlobalSaveSettings,
|
||||
DesktopGlobalReloadSettings,
|
||||
} DesktopEvent;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "desktop_view_pin_input.h"
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
#define NO_ACTIVITY_TIMEOUT 15000
|
||||
|
||||
@@ -14,6 +13,9 @@
|
||||
#define DEFAULT_PIN_X 64
|
||||
#define DEFAULT_PIN_Y 32
|
||||
|
||||
#define MIN_PIN_LENGTH 4
|
||||
#define MAX_PIN_LENGTH DESKTOP_PIN_CODE_MAX_LEN
|
||||
|
||||
struct DesktopViewPinInput {
|
||||
View* view;
|
||||
DesktopViewPinInputCallback back_callback;
|
||||
@@ -24,7 +26,7 @@ struct DesktopViewPinInput {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
PinCode pin;
|
||||
DesktopPinCode pin;
|
||||
bool pin_hidden;
|
||||
bool locked_input;
|
||||
uint8_t pin_x;
|
||||
@@ -50,7 +52,7 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) {
|
||||
|
||||
bool call_back_callback = false;
|
||||
bool call_done_callback = false;
|
||||
PinCode pin_code = {0};
|
||||
DesktopPinCode pin_code = {0};
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
switch(event->key) {
|
||||
@@ -59,13 +61,13 @@ static bool desktop_view_pin_input_input(InputEvent* event, void* context) {
|
||||
case InputKeyDown:
|
||||
case InputKeyUp:
|
||||
if(!model->locked_input) {
|
||||
if(model->pin.length < MAX_PIN_SIZE) {
|
||||
if(model->pin.length < MAX_PIN_LENGTH) {
|
||||
model->pin.data[model->pin.length++] = event->key;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(model->pin.length >= MIN_PIN_SIZE) {
|
||||
if(model->pin.length >= MIN_PIN_LENGTH) {
|
||||
call_done_callback = true;
|
||||
pin_code = model->pin;
|
||||
}
|
||||
@@ -102,7 +104,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu
|
||||
furi_assert(model);
|
||||
|
||||
uint8_t draw_pin_size = MAX(4, model->pin.length + 1);
|
||||
if(model->locked_input || (model->pin.length == MAX_PIN_SIZE)) {
|
||||
if(model->locked_input || (model->pin.length == MAX_PIN_LENGTH)) {
|
||||
draw_pin_size = model->pin.length;
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) {
|
||||
canvas_draw_str(canvas, 16, 60, "= clear");
|
||||
}
|
||||
|
||||
if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) {
|
||||
if(model->button_label && ((model->pin.length >= MIN_PIN_LENGTH) || model->locked_input)) {
|
||||
elements_button_center(canvas, model->button_label);
|
||||
}
|
||||
|
||||
@@ -247,7 +249,7 @@ void desktop_view_pin_input_unlock_input(DesktopViewPinInput* pin_input) {
|
||||
view_commit_model(pin_input->view, true);
|
||||
}
|
||||
|
||||
void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const PinCode* pin) {
|
||||
void desktop_view_pin_input_set_pin(DesktopViewPinInput* pin_input, const DesktopPinCode* pin) {
|
||||
furi_assert(pin_input);
|
||||
furi_assert(pin);
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include <desktop/desktop_settings.h>
|
||||
|
||||
#include "../helpers/pin_code.h"
|
||||
|
||||
typedef void (*DesktopViewPinInputCallback)(void*);
|
||||
typedef void (*DesktopViewPinInputDoneCallback)(const PinCode* pin_code, void*);
|
||||
typedef void (*DesktopViewPinInputDoneCallback)(const DesktopPinCode* 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_set_pin(DesktopViewPinInput* pin_input, const DesktopPinCode* pin_code);
|
||||
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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "dolphin_i.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "Dolphin"
|
||||
|
||||
@@ -223,6 +224,10 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
|
||||
dolphin_state_increase_level(dolphin->state);
|
||||
furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS);
|
||||
|
||||
} else if(event.type == DolphinEventTypeReloadState) {
|
||||
dolphin_state_load(dolphin->state);
|
||||
furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS);
|
||||
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
@@ -232,6 +237,32 @@ static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dolphin_storage_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
Dolphin* dolphin = context;
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
DolphinEvent event = {
|
||||
.type = DolphinEventTypeReloadState,
|
||||
};
|
||||
|
||||
dolphin_event_send_async(dolphin, &event);
|
||||
}
|
||||
}
|
||||
|
||||
static void dolphin_init_state(Dolphin* dolphin) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), dolphin_storage_callback, dolphin);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping state");
|
||||
return;
|
||||
}
|
||||
|
||||
dolphin_state_load(dolphin->state);
|
||||
}
|
||||
|
||||
// Application thread
|
||||
|
||||
int32_t dolphin_srv(void* p) {
|
||||
@@ -247,7 +278,7 @@ int32_t dolphin_srv(void* p) {
|
||||
Dolphin* dolphin = dolphin_alloc();
|
||||
furi_record_create(RECORD_DOLPHIN, dolphin);
|
||||
|
||||
dolphin_state_load(dolphin->state);
|
||||
dolphin_init_state(dolphin);
|
||||
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
dolphin->event_loop,
|
||||
|
||||
@@ -12,6 +12,7 @@ typedef enum {
|
||||
DolphinEventTypeStats,
|
||||
DolphinEventTypeFlush,
|
||||
DolphinEventTypeLevel,
|
||||
DolphinEventTypeReloadState,
|
||||
} DolphinEventType;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#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 <storage/storage.h>
|
||||
#include <toolbox/saved_struct.h>
|
||||
|
||||
#define TAG "DolphinState"
|
||||
@@ -26,29 +25,28 @@ void dolphin_state_free(DolphinState* dolphin_state) {
|
||||
free(dolphin_state);
|
||||
}
|
||||
|
||||
bool dolphin_state_save(DolphinState* dolphin_state) {
|
||||
void dolphin_state_save(DolphinState* dolphin_state) {
|
||||
if(!dolphin_state->dirty) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = saved_struct_save(
|
||||
bool success = saved_struct_save(
|
||||
DOLPHIN_STATE_PATH,
|
||||
&dolphin_state->data,
|
||||
sizeof(DolphinStoreData),
|
||||
DOLPHIN_STATE_HEADER_MAGIC,
|
||||
DOLPHIN_STATE_HEADER_VERSION);
|
||||
|
||||
if(result) {
|
||||
if(success) {
|
||||
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) {
|
||||
void dolphin_state_load(DolphinState* dolphin_state) {
|
||||
bool success = saved_struct_load(
|
||||
DOLPHIN_STATE_PATH,
|
||||
&dolphin_state->data,
|
||||
@@ -64,12 +62,12 @@ bool dolphin_state_load(DolphinState* dolphin_state) {
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Reset dolphin-state");
|
||||
memset(dolphin_state, 0, sizeof(*dolphin_state));
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
FURI_LOG_W(TAG, "Reset Dolphin state");
|
||||
memset(dolphin_state, 0, sizeof(DolphinState));
|
||||
|
||||
return success;
|
||||
dolphin_state->dirty = true;
|
||||
dolphin_state_save(dolphin_state);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t dolphin_state_timestamp(void) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "dolphin_deed.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "dolphin_deed.h"
|
||||
|
||||
typedef struct DolphinState DolphinState;
|
||||
typedef struct {
|
||||
@@ -25,9 +25,9 @@ DolphinState* dolphin_state_alloc(void);
|
||||
|
||||
void dolphin_state_free(DolphinState* dolphin_state);
|
||||
|
||||
bool dolphin_state_save(DolphinState* dolphin_state);
|
||||
void dolphin_state_save(DolphinState* dolphin_state);
|
||||
|
||||
bool dolphin_state_load(DolphinState* dolphin_state);
|
||||
void dolphin_state_load(DolphinState* dolphin_state);
|
||||
|
||||
void dolphin_state_clear_limits(DolphinState* dolphin_state);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <furi_hal_serial_control.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
|
||||
#include "expansion_worker.h"
|
||||
@@ -25,6 +26,7 @@ typedef enum {
|
||||
ExpansionMessageTypeEnable,
|
||||
ExpansionMessageTypeDisable,
|
||||
ExpansionMessageTypeSetListenSerial,
|
||||
ExpansionMessageTypeReloadSettings,
|
||||
ExpansionMessageTypeModuleConnected,
|
||||
ExpansionMessageTypeModuleDisconnected,
|
||||
ExpansionMessageTypeConnectionEstablished,
|
||||
@@ -103,7 +105,10 @@ static void
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->settings.uart_index < FuriHalSerialIdMax) {
|
||||
ExpansionSettings settings;
|
||||
expansion_settings_load(&settings);
|
||||
|
||||
if(settings.uart_index < FuriHalSerialIdMax) {
|
||||
instance->state = ExpansionStateEnabled;
|
||||
instance->serial_id = instance->settings.uart_index;
|
||||
furi_hal_serial_control_set_expansion_callback(
|
||||
@@ -116,7 +121,6 @@ static void
|
||||
static void
|
||||
expansion_control_handler_disable(Expansion* instance, const ExpansionMessageData* data) {
|
||||
UNUSED(data);
|
||||
|
||||
if(instance->state == ExpansionStateDisabled) {
|
||||
return;
|
||||
} else if(
|
||||
@@ -136,10 +140,10 @@ static void
|
||||
static void expansion_control_handler_set_listen_serial(
|
||||
Expansion* instance,
|
||||
const ExpansionMessageData* data) {
|
||||
furi_check(data->serial_id < FuriHalSerialIdMax);
|
||||
if(instance->state != ExpansionStateDisabled && instance->serial_id == data->serial_id) {
|
||||
return;
|
||||
|
||||
if(instance->state == ExpansionStateRunning ||
|
||||
instance->state == ExpansionStateConnectionEstablished) {
|
||||
} else if(instance->state == ExpansionStateRunning) {
|
||||
expansion_worker_stop(instance->worker);
|
||||
expansion_worker_free(instance->worker);
|
||||
|
||||
@@ -156,6 +160,26 @@ static void expansion_control_handler_set_listen_serial(
|
||||
FURI_LOG_D(TAG, "Listen serial changed to %s", expansion_uart_names[instance->serial_id]);
|
||||
}
|
||||
|
||||
static void expansion_control_handler_reload_settings(
|
||||
Expansion* instance,
|
||||
const ExpansionMessageData* data) {
|
||||
UNUSED(data);
|
||||
|
||||
ExpansionSettings settings;
|
||||
expansion_settings_load(&settings);
|
||||
|
||||
if(settings.uart_index < FuriHalSerialIdMax) {
|
||||
const ExpansionMessageData data = {
|
||||
.serial_id = settings.uart_index,
|
||||
};
|
||||
|
||||
expansion_control_handler_set_listen_serial(instance, &data);
|
||||
|
||||
} else {
|
||||
expansion_control_handler_disable(instance, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void expansion_control_handler_module_connected(
|
||||
Expansion* instance,
|
||||
const ExpansionMessageData* data) {
|
||||
@@ -211,6 +235,7 @@ static const ExpansionControlHandler expansion_control_handlers[] = {
|
||||
[ExpansionMessageTypeEnable] = expansion_control_handler_enable,
|
||||
[ExpansionMessageTypeDisable] = expansion_control_handler_disable,
|
||||
[ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial,
|
||||
[ExpansionMessageTypeReloadSettings] = expansion_control_handler_reload_settings,
|
||||
[ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected,
|
||||
[ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected,
|
||||
[ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established,
|
||||
@@ -249,6 +274,22 @@ static Expansion* expansion_alloc(void) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void expansion_storage_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
const StorageEvent* event = message;
|
||||
Expansion* instance = context;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
ExpansionMessage em = {
|
||||
.type = ExpansionMessageTypeReloadSettings,
|
||||
.api_lock = NULL,
|
||||
};
|
||||
|
||||
furi_check(furi_message_queue_put(instance->queue, &em, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
void expansion_on_system_start(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
@@ -256,7 +297,14 @@ void expansion_on_system_start(void* arg) {
|
||||
furi_record_create(RECORD_EXPANSION, instance);
|
||||
furi_thread_start(instance->thread);
|
||||
|
||||
expansion_settings_load(&instance->settings);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), expansion_storage_callback, instance);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
|
||||
return;
|
||||
}
|
||||
|
||||
expansion_enable(instance);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,29 +6,40 @@
|
||||
|
||||
#include "expansion_settings_filename.h"
|
||||
|
||||
#define TAG "ExpansionSettings"
|
||||
|
||||
#define EXPANSION_SETTINGS_PATH INT_PATH(EXPANSION_SETTINGS_FILE_NAME)
|
||||
#define EXPANSION_SETTINGS_VERSION (0)
|
||||
#define EXPANSION_SETTINGS_MAGIC (0xEA)
|
||||
|
||||
bool expansion_settings_load(ExpansionSettings* settings) {
|
||||
void expansion_settings_load(ExpansionSettings* settings) {
|
||||
furi_assert(settings);
|
||||
if(!saved_struct_load(
|
||||
EXPANSION_SETTINGS_PATH,
|
||||
settings,
|
||||
sizeof(ExpansionSettings),
|
||||
EXPANSION_SETTINGS_MAGIC,
|
||||
EXPANSION_SETTINGS_VERSION)) {
|
||||
settings->uart_index = FuriHalSerialIdMax;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool expansion_settings_save(const ExpansionSettings* settings) {
|
||||
furi_assert(settings);
|
||||
return saved_struct_save(
|
||||
const bool success = saved_struct_load(
|
||||
EXPANSION_SETTINGS_PATH,
|
||||
settings,
|
||||
sizeof(ExpansionSettings),
|
||||
EXPANSION_SETTINGS_MAGIC,
|
||||
EXPANSION_SETTINGS_VERSION);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Failed to load file, using defaults");
|
||||
memset(settings, 0, sizeof(ExpansionSettings));
|
||||
expansion_settings_save(settings);
|
||||
}
|
||||
}
|
||||
|
||||
void expansion_settings_save(const ExpansionSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
const bool success = saved_struct_save(
|
||||
EXPANSION_SETTINGS_PATH,
|
||||
settings,
|
||||
sizeof(ExpansionSettings),
|
||||
EXPANSION_SETTINGS_MAGIC,
|
||||
EXPANSION_SETTINGS_VERSION);
|
||||
|
||||
if(!success) {
|
||||
FURI_LOG_E(TAG, "Failed to save file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,18 +25,16 @@ typedef struct {
|
||||
/**
|
||||
* @brief Load expansion module support settings from file.
|
||||
*
|
||||
* @param[out] settings pointer to an ExpansionSettings instance to load settings into.
|
||||
* @returns true if the settings were successfully loaded, false otherwise.
|
||||
* @param[in,out] settings pointer to an ExpansionSettings instance to load settings into.
|
||||
*/
|
||||
bool expansion_settings_load(ExpansionSettings* settings);
|
||||
void expansion_settings_load(ExpansionSettings* settings);
|
||||
|
||||
/**
|
||||
* @brief Save expansion module support settings to file.
|
||||
*
|
||||
* @param[in] settings pointer to an ExpansionSettings instance to save settings from.
|
||||
* @returns true if the settings were successfully saved, false otherwise.
|
||||
*/
|
||||
bool expansion_settings_save(const ExpansionSettings* settings);
|
||||
void expansion_settings_save(const ExpansionSettings* settings);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#define TAG "BrowserWorker"
|
||||
|
||||
#define ASSETS_DIR "assets"
|
||||
#define BROWSER_ROOT STORAGE_ANY_PATH_PREFIX
|
||||
#define BROWSER_ROOT STORAGE_EXT_PATH_PREFIX
|
||||
#define FILE_NAME_LEN_MAX 256
|
||||
#define LONG_LOAD_THRESHOLD 100
|
||||
|
||||
|
||||
@@ -438,7 +438,7 @@ static bool notification_load_settings(NotificationApp* app) {
|
||||
File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
const size_t settings_size = sizeof(NotificationSettings);
|
||||
|
||||
FURI_LOG_I(TAG, "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
bool fs_result =
|
||||
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
@@ -451,8 +451,6 @@ static bool notification_load_settings(NotificationApp* app) {
|
||||
}
|
||||
|
||||
if(fs_result) {
|
||||
FURI_LOG_I(TAG, "load success");
|
||||
|
||||
if(settings.version != NOTIFICATION_SETTINGS_VERSION) {
|
||||
FURI_LOG_E(
|
||||
TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION);
|
||||
@@ -462,7 +460,7 @@ static bool notification_load_settings(NotificationApp* app) {
|
||||
furi_kernel_unlock();
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "load failed, %s", storage_file_get_error_desc(file));
|
||||
FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
@@ -477,7 +475,7 @@ static bool notification_save_settings(NotificationApp* app) {
|
||||
File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
const size_t settings_size = sizeof(NotificationSettings);
|
||||
|
||||
FURI_LOG_I(TAG, "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
|
||||
furi_kernel_lock();
|
||||
memcpy(&settings, &app->settings, settings_size);
|
||||
@@ -495,9 +493,8 @@ static bool notification_save_settings(NotificationApp* app) {
|
||||
}
|
||||
|
||||
if(fs_result) {
|
||||
FURI_LOG_I(TAG, "save success");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "save failed, %s", storage_file_get_error_desc(file));
|
||||
FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
@@ -556,14 +553,46 @@ static NotificationApp* notification_app_alloc(void) {
|
||||
return app;
|
||||
}
|
||||
|
||||
static void notification_storage_callback(const void* message, void* context) {
|
||||
furi_assert(context);
|
||||
NotificationApp* app = context;
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
NotificationAppMessage m = {
|
||||
.type = LoadSettingsMessage,
|
||||
};
|
||||
|
||||
furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
static void notification_apply_settings(NotificationApp* app) {
|
||||
if(!notification_load_settings(app)) {
|
||||
notification_save_settings(app);
|
||||
}
|
||||
|
||||
notification_apply_lcd_contrast(app);
|
||||
}
|
||||
|
||||
static void notification_init_settings(NotificationApp* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
|
||||
return;
|
||||
}
|
||||
|
||||
notification_apply_settings(app);
|
||||
}
|
||||
|
||||
// App
|
||||
int32_t notification_srv(void* p) {
|
||||
UNUSED(p);
|
||||
NotificationApp* app = notification_app_alloc();
|
||||
|
||||
if(!notification_load_settings(app)) {
|
||||
notification_save_settings(app);
|
||||
}
|
||||
notification_init_settings(app);
|
||||
|
||||
notification_vibro_off();
|
||||
notification_sound_off();
|
||||
@@ -571,7 +600,6 @@ int32_t notification_srv(void* p) {
|
||||
notification_apply_internal_led_layer(&app->led[0], 0x00);
|
||||
notification_apply_internal_led_layer(&app->led[1], 0x00);
|
||||
notification_apply_internal_led_layer(&app->led[2], 0x00);
|
||||
notification_apply_lcd_contrast(app);
|
||||
|
||||
furi_record_create(RECORD_NOTIFICATION, app);
|
||||
|
||||
@@ -589,6 +617,9 @@ int32_t notification_srv(void* p) {
|
||||
case SaveSettingsMessage:
|
||||
notification_save_settings(app);
|
||||
break;
|
||||
case LoadSettingsMessage:
|
||||
notification_load_settings(app);
|
||||
break;
|
||||
}
|
||||
|
||||
if(message.back_event != NULL) {
|
||||
|
||||
@@ -11,6 +11,7 @@ typedef enum {
|
||||
NotificationLayerMessage,
|
||||
InternalLayerMessage,
|
||||
SaveSettingsMessage,
|
||||
LoadSettingsMessage,
|
||||
} NotificationAppMessageType;
|
||||
|
||||
typedef struct {
|
||||
|
||||
10
applications/services/region/application.fam
Normal file
10
applications/services/region/application.fam
Normal file
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="region",
|
||||
name="RegionSrv",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
targets=["f7"],
|
||||
entry_point="region_on_system_start",
|
||||
cdefines=["SRV_REGION"],
|
||||
requires=["storage"],
|
||||
order=170,
|
||||
)
|
||||
147
applications/services/region/region.c
Normal file
147
applications/services/region/region.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#include <furi_hal_region.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <flipper.pb.h>
|
||||
#include <pb_decode.h>
|
||||
|
||||
#define TAG "RegionSrv"
|
||||
|
||||
#define SUBGHZ_REGION_FILENAME INT_PATH(".region_data")
|
||||
|
||||
static bool region_istream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
|
||||
File* file = istream->state;
|
||||
size_t ret = storage_file_read(file, buf, count);
|
||||
return count == ret;
|
||||
}
|
||||
|
||||
static bool region_istream_decode_band(pb_istream_t* stream, const pb_field_t* field, void** arg) {
|
||||
UNUSED(field);
|
||||
|
||||
FuriHalRegion* region = *arg;
|
||||
|
||||
PB_Region_Band band = {0};
|
||||
if(!pb_decode(stream, PB_Region_Band_fields, &band)) {
|
||||
FURI_LOG_E(TAG, "PB Region band decode error: %s", PB_GET_ERROR(stream));
|
||||
return false;
|
||||
}
|
||||
|
||||
region->bands_count += 1;
|
||||
region = realloc( //-V701
|
||||
region,
|
||||
sizeof(FuriHalRegion) + sizeof(FuriHalRegionBand) * region->bands_count);
|
||||
size_t pos = region->bands_count - 1;
|
||||
region->bands[pos].start = band.start;
|
||||
region->bands[pos].end = band.end;
|
||||
region->bands[pos].power_limit = band.power_limit;
|
||||
region->bands[pos].duty_cycle = band.duty_cycle;
|
||||
*arg = region;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Add allowed band: start %luHz, stop %luHz, power_limit %ddBm, duty_cycle %u%%",
|
||||
band.start,
|
||||
band.end,
|
||||
band.power_limit,
|
||||
band.duty_cycle);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t region_load_file(void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
PB_Region pb_region = {0};
|
||||
pb_region.bands.funcs.decode = region_istream_decode_band;
|
||||
|
||||
do {
|
||||
FileInfo fileinfo = {0};
|
||||
|
||||
if(storage_common_stat(storage, SUBGHZ_REGION_FILENAME, &fileinfo) != FSE_OK ||
|
||||
fileinfo.size == 0) {
|
||||
FURI_LOG_W(TAG, "Region file missing or empty");
|
||||
break;
|
||||
|
||||
} else if(!storage_file_open(file, SUBGHZ_REGION_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Failed to open region file");
|
||||
break;
|
||||
}
|
||||
|
||||
pb_istream_t istream = {
|
||||
.callback = region_istream_read,
|
||||
.state = file,
|
||||
.errmsg = NULL,
|
||||
.bytes_left = fileinfo.size,
|
||||
};
|
||||
|
||||
pb_region.bands.arg = malloc(sizeof(FuriHalRegion));
|
||||
|
||||
if(!pb_decode(&istream, PB_Region_fields, &pb_region)) {
|
||||
FURI_LOG_E(TAG, "Failed to decode region file");
|
||||
free(pb_region.bands.arg);
|
||||
break;
|
||||
}
|
||||
|
||||
FuriHalRegion* region = pb_region.bands.arg;
|
||||
|
||||
memcpy(
|
||||
region->country_code,
|
||||
pb_region.country_code->bytes,
|
||||
MIN(pb_region.country_code->size, sizeof(region->country_code) - 1));
|
||||
|
||||
furi_hal_region_set(region);
|
||||
|
||||
FURI_LOG_I(TAG, "Dynamic region set: %s", region->country_code);
|
||||
} while(0);
|
||||
|
||||
pb_release(PB_Region_fields, &pb_region);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void region_loader_pending_callback(void* context, uint32_t arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
FuriThread* loader = context;
|
||||
furi_thread_join(loader);
|
||||
furi_thread_free(loader);
|
||||
}
|
||||
|
||||
static void region_loader_state_callback(FuriThreadState state, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
if(state == FuriThreadStateStopped) {
|
||||
furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void region_storage_callback(const void* message, void* context) {
|
||||
UNUSED(context);
|
||||
const StorageEvent* event = message;
|
||||
|
||||
if(event->type == StorageEventTypeCardMount) {
|
||||
FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL);
|
||||
furi_thread_set_state_callback(loader, region_loader_state_callback);
|
||||
furi_thread_start(loader);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t region_on_system_start(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
furi_pubsub_subscribe(storage_get_pubsub(storage), region_storage_callback, NULL);
|
||||
|
||||
if(storage_sd_status(storage) != FSE_OK) {
|
||||
FURI_LOG_D(TAG, "SD Card not ready, skipping dynamic region");
|
||||
return 0;
|
||||
}
|
||||
|
||||
region_load_file(NULL);
|
||||
return 0;
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "storage_message.h"
|
||||
#include "storage_processing.h"
|
||||
#include "storage/storage_glue.h"
|
||||
#include "storages/storage_int.h"
|
||||
#include "storages/storage_ext.h"
|
||||
#include <assets_icons.h>
|
||||
|
||||
@@ -42,9 +41,6 @@ Storage* storage_app_alloc(void) {
|
||||
storage_data_timestamp(&app->storage[i]);
|
||||
}
|
||||
|
||||
#ifndef FURI_RAM_EXEC
|
||||
storage_int_init(&app->storage[ST_INT]);
|
||||
#endif
|
||||
storage_ext_init(&app->storage[ST_EXT]);
|
||||
|
||||
// sd icon gui
|
||||
@@ -106,6 +102,11 @@ int32_t storage_srv(void* p) {
|
||||
Storage* app = storage_app_alloc();
|
||||
furi_record_create(RECORD_STORAGE, app);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal)) {
|
||||
FURI_LOG_W(TAG, "Format Internal not supported, clearing flag");
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||
}
|
||||
|
||||
StorageMessage message;
|
||||
while(1) {
|
||||
if(furi_message_queue_get(app->message_queue, &message, STORAGE_TICK) == FuriStatusOk) {
|
||||
|
||||
@@ -506,7 +506,7 @@ FS_Error storage_sd_status(Storage* storage);
|
||||
|
||||
/******************* Internal LFS Functions *******************/
|
||||
|
||||
typedef void (*Storage_name_converter)(FuriString*);
|
||||
typedef void (*StorageNameConverter)(FuriString*);
|
||||
|
||||
/**
|
||||
* @brief Back up the internal storage contents to a *.tar archive.
|
||||
@@ -526,7 +526,7 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname);
|
||||
* @return FSE_OK if the storage was successfully restored, any other error code on failure.
|
||||
*/
|
||||
FS_Error
|
||||
storage_int_restore(Storage* storage, const char* dstname, Storage_name_converter converter);
|
||||
storage_int_restore(Storage* storage, const char* dstname, StorageNameConverter converter);
|
||||
|
||||
/***************** Simplified Functions ******************/
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
|
||||
storage_cli_print_error(error);
|
||||
} else {
|
||||
printf(
|
||||
"Label: %s\r\nType: LittleFS\r\n%luKiB total\r\n%luKiB free\r\n",
|
||||
"Label: %s\r\nType: Virtual\r\n%luKiB total\r\n%luKiB free\r\n",
|
||||
furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown",
|
||||
(uint32_t)(total_space / 1024),
|
||||
(uint32_t)(free_space / 1024));
|
||||
|
||||
@@ -14,7 +14,7 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname) {
|
||||
}
|
||||
|
||||
FS_Error
|
||||
storage_int_restore(Storage* storage, const char* srcname, Storage_name_converter converter) {
|
||||
storage_int_restore(Storage* storage, const char* srcname, StorageNameConverter converter) {
|
||||
furi_check(storage);
|
||||
|
||||
TarArchive* archive = tar_archive_alloc(storage);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <m-list.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#define TAG "Storage"
|
||||
|
||||
#define STORAGE_PATH_PREFIX_LEN 4u
|
||||
_Static_assert(
|
||||
sizeof(STORAGE_ANY_PATH_PREFIX) == STORAGE_PATH_PREFIX_LEN + 1,
|
||||
@@ -60,36 +62,27 @@ static StorageType storage_get_type_by_path(FuriString* path) {
|
||||
|
||||
return type;
|
||||
}
|
||||
static void storage_path_change_to_real_storage(FuriString* path, StorageType real_storage) {
|
||||
if(furi_string_search(path, STORAGE_ANY_PATH_PREFIX) == 0) {
|
||||
switch(real_storage) {
|
||||
case ST_EXT:
|
||||
furi_string_replace_at(
|
||||
path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX);
|
||||
break;
|
||||
case ST_INT:
|
||||
furi_string_replace_at(
|
||||
path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_INT_PATH_PREFIX);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) {
|
||||
StorageType type = storage_get_type_by_path(path);
|
||||
|
||||
if(storage_type_is_valid(type)) {
|
||||
// Any storage phase-out: redirect "/any" to "/ext"
|
||||
if(type == ST_ANY) {
|
||||
type = ST_INT;
|
||||
if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) {
|
||||
type = ST_EXT;
|
||||
}
|
||||
storage_path_change_to_real_storage(path, type);
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
STORAGE_ANY_PATH_PREFIX " is deprecated, use " STORAGE_EXT_PATH_PREFIX " instead");
|
||||
furi_string_replace_at(
|
||||
path, 0, strlen(STORAGE_EXT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX);
|
||||
type = ST_EXT;
|
||||
}
|
||||
|
||||
furi_assert(type == ST_EXT);
|
||||
|
||||
if(storage_data_status(&app->storage[type]) != StorageStatusOK) {
|
||||
return FSE_NOT_READY;
|
||||
}
|
||||
|
||||
furi_assert(type == ST_EXT || type == ST_INT);
|
||||
*storage = &app->storage[type];
|
||||
|
||||
return FSE_OK;
|
||||
@@ -559,6 +552,16 @@ void storage_process_alias(
|
||||
furi_string_get_cstr(apps_assets_path_with_appsid));
|
||||
|
||||
furi_string_free(apps_assets_path_with_appsid);
|
||||
|
||||
} else if(furi_string_start_with(path, STORAGE_INT_PATH_PREFIX)) {
|
||||
furi_string_replace_at(
|
||||
path, 0, strlen(STORAGE_INT_PATH_PREFIX), STORAGE_EXT_PATH_PREFIX "/.int");
|
||||
|
||||
FuriString* int_on_ext_path = furi_string_alloc_set(STORAGE_EXT_PATH_PREFIX "/.int");
|
||||
if(storage_process_common_stat(app, int_on_ext_path, NULL) != FSE_OK) {
|
||||
storage_process_common_mkdir(app, int_on_ext_path);
|
||||
}
|
||||
furi_string_free(int_on_ext_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,744 +0,0 @@
|
||||
#include "storage_int.h"
|
||||
#include <lfs.h>
|
||||
#include <furi_hal.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
#define TAG "StorageInt"
|
||||
|
||||
#define STORAGE_PATH STORAGE_INT_PATH_PREFIX
|
||||
#define LFS_CLEAN_FINGERPRINT 0
|
||||
|
||||
/* When less than LFS_RESERVED_PAGES_COUNT are left free, creation &
|
||||
* modification of non-dot files is restricted */
|
||||
#define LFS_RESERVED_PAGES_COUNT 3
|
||||
|
||||
typedef struct {
|
||||
const size_t start_address;
|
||||
const size_t start_page;
|
||||
struct lfs_config config;
|
||||
lfs_t lfs;
|
||||
} LFSData;
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
bool open;
|
||||
} LFSHandle;
|
||||
|
||||
static LFSHandle* lfs_handle_alloc_file(void) {
|
||||
LFSHandle* handle = malloc(sizeof(LFSHandle));
|
||||
handle->data = malloc(sizeof(lfs_file_t));
|
||||
return handle;
|
||||
}
|
||||
|
||||
static LFSHandle* lfs_handle_alloc_dir(void) {
|
||||
LFSHandle* handle = malloc(sizeof(LFSHandle));
|
||||
handle->data = malloc(sizeof(lfs_dir_t));
|
||||
return handle;
|
||||
}
|
||||
|
||||
/* INTERNALS */
|
||||
|
||||
static lfs_dir_t* lfs_handle_get_dir(LFSHandle* handle) {
|
||||
return handle->data;
|
||||
}
|
||||
|
||||
static lfs_file_t* lfs_handle_get_file(LFSHandle* handle) {
|
||||
return handle->data;
|
||||
}
|
||||
|
||||
static void lfs_handle_free(LFSHandle* handle) {
|
||||
free(handle->data);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void lfs_handle_set_open(LFSHandle* handle) {
|
||||
handle->open = true;
|
||||
}
|
||||
|
||||
static bool lfs_handle_is_open(LFSHandle* handle) {
|
||||
return handle->open;
|
||||
}
|
||||
|
||||
static lfs_t* lfs_get_from_storage(StorageData* storage) {
|
||||
return &((LFSData*)storage->data)->lfs;
|
||||
}
|
||||
|
||||
static LFSData* lfs_data_get_from_storage(StorageData* storage) {
|
||||
return (LFSData*)storage->data;
|
||||
}
|
||||
|
||||
static int storage_int_device_read(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
void* buffer,
|
||||
lfs_size_t size) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t address = lfs_data->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"Device read: block %lu, off %lu, buffer: %p, size %lu, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
(void*)address);
|
||||
|
||||
memcpy(buffer, (void*)address, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int storage_int_device_prog(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
const void* buffer,
|
||||
lfs_size_t size) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t address = lfs_data->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"Device prog: block %lu, off %lu, buffer: %p, size %lu, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
(void*)address);
|
||||
|
||||
int ret = 0;
|
||||
while(size > 0) {
|
||||
furi_hal_flash_write_dword(address, *(uint64_t*)buffer);
|
||||
address += c->prog_size;
|
||||
buffer += c->prog_size;
|
||||
size -= c->prog_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t block) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t page = lfs_data->start_page + block;
|
||||
|
||||
FURI_LOG_D(TAG, "Device erase: page %lu, translated page: %zx", block, page);
|
||||
|
||||
furi_hal_flash_erase(page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int storage_int_device_sync(const struct lfs_config* c) {
|
||||
UNUSED(c);
|
||||
FURI_LOG_D(TAG, "Device sync: skipping");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LFSData* storage_int_lfs_data_alloc(void) {
|
||||
LFSData* lfs_data = malloc(sizeof(LFSData));
|
||||
|
||||
// Internal storage start address
|
||||
*(size_t*)(&lfs_data->start_address) = furi_hal_flash_get_free_page_start_address();
|
||||
*(size_t*)(&lfs_data->start_page) =
|
||||
(lfs_data->start_address - furi_hal_flash_get_base()) / furi_hal_flash_get_page_size();
|
||||
|
||||
// LFS configuration
|
||||
// Glue and context
|
||||
lfs_data->config.context = lfs_data;
|
||||
lfs_data->config.read = storage_int_device_read;
|
||||
lfs_data->config.prog = storage_int_device_prog;
|
||||
lfs_data->config.erase = storage_int_device_erase;
|
||||
lfs_data->config.sync = storage_int_device_sync;
|
||||
|
||||
// Block device description
|
||||
lfs_data->config.read_size = furi_hal_flash_get_read_block_size();
|
||||
lfs_data->config.prog_size = furi_hal_flash_get_write_block_size();
|
||||
lfs_data->config.block_size = furi_hal_flash_get_page_size();
|
||||
lfs_data->config.block_count = furi_hal_flash_get_free_page_count();
|
||||
lfs_data->config.block_cycles = furi_hal_flash_get_cycles_count();
|
||||
lfs_data->config.cache_size = 16;
|
||||
lfs_data->config.lookahead_size = 16;
|
||||
|
||||
return lfs_data;
|
||||
}
|
||||
|
||||
// Returns true if fingerprint was invalid and LFS reformatting is needed
|
||||
static bool storage_int_check_and_set_fingerprint(LFSData* lfs_data) {
|
||||
bool value = false;
|
||||
|
||||
uint32_t os_fingerprint = 0;
|
||||
os_fingerprint |= ((lfs_data->start_page & 0xFF) << 0);
|
||||
os_fingerprint |= ((lfs_data->config.block_count & 0xFF) << 8);
|
||||
os_fingerprint |= ((LFS_DISK_VERSION_MAJOR & 0xFFFF) << 16);
|
||||
|
||||
uint32_t rtc_fingerprint = furi_hal_rtc_get_register(FuriHalRtcRegisterLfsFingerprint);
|
||||
if(rtc_fingerprint == LFS_CLEAN_FINGERPRINT) {
|
||||
FURI_LOG_I(TAG, "Storing LFS fingerprint in RTC");
|
||||
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
||||
} else if(rtc_fingerprint != os_fingerprint) {
|
||||
FURI_LOG_E(TAG, "LFS fingerprint mismatch");
|
||||
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
||||
value = true;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
|
||||
int err;
|
||||
lfs_t* lfs = &lfs_data->lfs;
|
||||
|
||||
bool was_fingerprint_outdated = storage_int_check_and_set_fingerprint(lfs_data);
|
||||
bool need_format = furi_hal_rtc_is_flag_set(FuriHalRtcFlagStorageFormatInternal) ||
|
||||
was_fingerprint_outdated;
|
||||
|
||||
if(need_format) {
|
||||
// Format storage
|
||||
err = lfs_format(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount");
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Factory reset: Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Factory reset: Mount after format failed");
|
||||
storage->status = StorageStatusNotMounted;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Factory reset: Format failed");
|
||||
storage->status = StorageStatusNoFS;
|
||||
}
|
||||
} else {
|
||||
// Normal
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Mount failed, formatting");
|
||||
err = lfs_format(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Format successful, trying to mount");
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Mount after format failed");
|
||||
storage->status = StorageStatusNotMounted;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Format failed");
|
||||
storage->status = StorageStatusNoFS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************** Common Functions ******************/
|
||||
|
||||
static FS_Error storage_int_parse_error(int error) {
|
||||
FS_Error result;
|
||||
|
||||
if(error >= LFS_ERR_OK) {
|
||||
result = FSE_OK;
|
||||
} else {
|
||||
switch(error) {
|
||||
case LFS_ERR_NOENT:
|
||||
result = FSE_NOT_EXIST;
|
||||
break;
|
||||
case LFS_ERR_EXIST:
|
||||
result = FSE_EXIST;
|
||||
break;
|
||||
case LFS_ERR_NOTEMPTY:
|
||||
result = FSE_DENIED;
|
||||
break;
|
||||
case LFS_ERR_INVAL:
|
||||
case LFS_ERR_NOATTR:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case LFS_ERR_BADF:
|
||||
case LFS_ERR_ISDIR:
|
||||
case LFS_ERR_NOTDIR:
|
||||
case LFS_ERR_NAMETOOLONG:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case LFS_ERR_IO:
|
||||
case LFS_ERR_FBIG:
|
||||
case LFS_ERR_NOSPC:
|
||||
case LFS_ERR_NOMEM:
|
||||
case LFS_ERR_CORRUPT:
|
||||
default:
|
||||
result = FSE_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Returns false if less than reserved space is left free */
|
||||
static bool storage_int_check_for_free_space(StorageData* storage) {
|
||||
LFSData* lfs_data = lfs_data_get_from_storage(storage);
|
||||
|
||||
lfs_ssize_t result = lfs_fs_size(lfs_get_from_storage(storage));
|
||||
if(result >= 0) {
|
||||
lfs_size_t free_space =
|
||||
(lfs_data->config.block_count - result) * lfs_data->config.block_size;
|
||||
|
||||
return free_space > LFS_RESERVED_PAGES_COUNT * furi_hal_flash_get_page_size();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/******************* File Functions *******************/
|
||||
|
||||
static bool storage_int_file_open(
|
||||
void* ctx,
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
|
||||
bool enough_free_space = storage_int_check_for_free_space(storage);
|
||||
|
||||
int flags = 0;
|
||||
|
||||
if(access_mode & FSAM_READ) flags |= LFS_O_RDONLY;
|
||||
if(access_mode & FSAM_WRITE) flags |= LFS_O_WRONLY;
|
||||
|
||||
if(open_mode & FSOM_OPEN_EXISTING) flags |= 0;
|
||||
if(open_mode & FSOM_OPEN_ALWAYS) flags |= LFS_O_CREAT;
|
||||
if(open_mode & FSOM_OPEN_APPEND) flags |= LFS_O_CREAT | LFS_O_APPEND;
|
||||
if(open_mode & FSOM_CREATE_NEW) flags |= LFS_O_CREAT | LFS_O_EXCL;
|
||||
if(open_mode & FSOM_CREATE_ALWAYS) flags |= LFS_O_CREAT | LFS_O_TRUNC;
|
||||
|
||||
LFSHandle* handle = lfs_handle_alloc_file();
|
||||
storage_set_storage_file_data(file, handle, storage);
|
||||
|
||||
if(!enough_free_space) {
|
||||
FuriString* filename;
|
||||
filename = furi_string_alloc();
|
||||
path_extract_basename(path, filename);
|
||||
bool is_dot_file =
|
||||
(!furi_string_empty(filename) && (furi_string_get_char(filename, 0) == '.'));
|
||||
furi_string_free(filename);
|
||||
|
||||
/* Restrict write & creation access to all non-dot files */
|
||||
if(!is_dot_file && (flags & (LFS_O_CREAT | LFS_O_WRONLY))) {
|
||||
file->internal_error_id = LFS_ERR_NOSPC;
|
||||
file->error_id = FSE_DENIED;
|
||||
FURI_LOG_W(TAG, "Denied access to '%s': no free space", path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
file->internal_error_id = lfs_file_open(lfs, lfs_handle_get_file(handle), path, flags);
|
||||
|
||||
if(file->internal_error_id >= LFS_ERR_OK) {
|
||||
lfs_handle_set_open(handle);
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static bool storage_int_file_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_close(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
lfs_handle_free(handle);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_int_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint16_t bytes_read = 0;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id =
|
||||
lfs_file_read(lfs, lfs_handle_get_file(handle), buff, bytes_to_read);
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
bytes_read = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_int_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint16_t bytes_written = 0;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id =
|
||||
lfs_file_write(lfs, lfs_handle_get_file(handle), buff, bytes_to_write);
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
bytes_written = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_int_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
if(from_start) {
|
||||
file->internal_error_id =
|
||||
lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_SET);
|
||||
} else {
|
||||
file->internal_error_id =
|
||||
lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_CUR);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static uint64_t storage_int_file_tell(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
int32_t position = 0;
|
||||
if(file->error_id == FSE_OK) {
|
||||
position = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
static bool storage_int_file_truncate(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
uint32_t position = file->internal_error_id;
|
||||
file->internal_error_id =
|
||||
lfs_file_truncate(lfs, lfs_handle_get_file(handle), position);
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static bool storage_int_file_sync(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_sync(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static uint64_t storage_int_file_size(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_size(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
uint32_t size = 0;
|
||||
if(file->error_id == FSE_OK) {
|
||||
size = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool storage_int_file_eof(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
bool eof = true;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
int32_t position = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
int32_t size = lfs_file_size(lfs, lfs_handle_get_file(handle));
|
||||
|
||||
if(position < 0) {
|
||||
file->internal_error_id = position;
|
||||
} else if(size < 0) {
|
||||
file->internal_error_id = size;
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_OK;
|
||||
eof = (position >= size);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return eof;
|
||||
}
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
static bool storage_int_dir_open(void* ctx, File* file, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
|
||||
LFSHandle* handle = lfs_handle_alloc_dir();
|
||||
storage_set_storage_file_data(file, handle, storage);
|
||||
|
||||
file->internal_error_id = lfs_dir_open(lfs, lfs_handle_get_dir(handle), path);
|
||||
if(file->internal_error_id >= LFS_ERR_OK) {
|
||||
lfs_handle_set_open(handle);
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static bool storage_int_dir_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_dir_close(lfs, lfs_handle_get_dir(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
lfs_handle_free(handle);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static bool storage_int_dir_read(
|
||||
void* ctx,
|
||||
File* file,
|
||||
FileInfo* fileinfo,
|
||||
char* name,
|
||||
const uint16_t name_length) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
struct lfs_info _fileinfo;
|
||||
|
||||
// LFS returns virtual directories "." and "..", so we read until we get something meaningful or an empty string
|
||||
do {
|
||||
file->internal_error_id = lfs_dir_read(lfs, lfs_handle_get_dir(handle), &_fileinfo);
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
} while(strcmp(_fileinfo.name, ".") == 0 || strcmp(_fileinfo.name, "..") == 0);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.size;
|
||||
fileinfo->flags = 0;
|
||||
if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
if(name != NULL) {
|
||||
snprintf(name, name_length, "%s", _fileinfo.name);
|
||||
}
|
||||
|
||||
// set FSE_NOT_EXIST error on end of directory
|
||||
if(file->internal_error_id == 0) {
|
||||
file->error_id = FSE_NOT_EXIST;
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
static bool storage_int_dir_rewind(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_dir_rewind(lfs, lfs_handle_get_dir(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return file->error_id == FSE_OK;
|
||||
}
|
||||
|
||||
/******************* Common FS Functions *******************/
|
||||
|
||||
static FS_Error storage_int_common_stat(void* ctx, const char* path, FileInfo* fileinfo) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
struct lfs_info _fileinfo;
|
||||
int result = lfs_stat(lfs, path, &_fileinfo);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.size;
|
||||
fileinfo->flags = 0;
|
||||
if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_remove(void* ctx, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
int result = lfs_remove(lfs, path);
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_mkdir(void* ctx, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
int result = lfs_mkdir(lfs, path);
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_fs_info(
|
||||
void* ctx,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space) {
|
||||
UNUSED(fs_path);
|
||||
StorageData* storage = ctx;
|
||||
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSData* lfs_data = lfs_data_get_from_storage(storage);
|
||||
|
||||
if(total_space) {
|
||||
*total_space = lfs_data->config.block_size * lfs_data->config.block_count;
|
||||
}
|
||||
|
||||
lfs_ssize_t result = lfs_fs_size(lfs);
|
||||
if(free_space && (result >= 0)) {
|
||||
*free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size;
|
||||
}
|
||||
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static bool storage_int_common_equivalent_path(const char* path1, const char* path2) {
|
||||
return strcmp(path1, path2) == 0;
|
||||
}
|
||||
|
||||
/******************* Init Storage *******************/
|
||||
static const FS_Api fs_api = {
|
||||
.file =
|
||||
{
|
||||
.open = storage_int_file_open,
|
||||
.close = storage_int_file_close,
|
||||
.read = storage_int_file_read,
|
||||
.write = storage_int_file_write,
|
||||
.seek = storage_int_file_seek,
|
||||
.tell = storage_int_file_tell,
|
||||
.truncate = storage_int_file_truncate,
|
||||
.size = storage_int_file_size,
|
||||
.sync = storage_int_file_sync,
|
||||
.eof = storage_int_file_eof,
|
||||
},
|
||||
.dir =
|
||||
{
|
||||
.open = storage_int_dir_open,
|
||||
.close = storage_int_dir_close,
|
||||
.read = storage_int_dir_read,
|
||||
.rewind = storage_int_dir_rewind,
|
||||
},
|
||||
.common =
|
||||
{
|
||||
.stat = storage_int_common_stat,
|
||||
.mkdir = storage_int_common_mkdir,
|
||||
.remove = storage_int_common_remove,
|
||||
.fs_info = storage_int_common_fs_info,
|
||||
.equivalent_path = storage_int_common_equivalent_path,
|
||||
},
|
||||
};
|
||||
|
||||
void storage_int_init(StorageData* storage) {
|
||||
FURI_LOG_I(TAG, "Starting");
|
||||
LFSData* lfs_data = storage_int_lfs_data_alloc();
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Config: start %p, read %lu, write %lu, page size: %lu, page count: %lu, cycles: %ld",
|
||||
(void*)lfs_data->start_address,
|
||||
lfs_data->config.read_size,
|
||||
lfs_data->config.prog_size,
|
||||
lfs_data->config.block_size,
|
||||
lfs_data->config.block_count,
|
||||
lfs_data->config.block_cycles);
|
||||
|
||||
storage_int_lfs_mount(lfs_data, storage);
|
||||
|
||||
storage->data = lfs_data;
|
||||
storage->api.tick = NULL;
|
||||
storage->fs_api = &fs_api;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "../storage_glue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void storage_int_init(StorageData* storage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user