mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-13 00:07:12 +00:00
[FL-3841] FuriEventLoop Pt.2 (#3703)
* Abstract primitive type from main logic in FuriEventLoop * Remove message_queue_i.h * Add stream buffer support for event loop * Add semaphore support for event loop * Add temporary unit test workaround * Make the linter happy * Add mutex support for event loop * Implement event subscription and unsubscription while the event loop is running * Implement edge events * Fix leftover logical errors * Add event loop timer example application * Implement flag-based edge trigger and one-shot mode * Add event loop mutex example application * Only notify the event loop if stream buffer is at or above its trigger level * Reformat comments * Add event loop stream buffer example application * Add event loop multiple elements example application * Improve event loop flag names * Remove redundant signal handler as it is already handled by the event loop * Refactor Power service, improve ViewHolder * Use ViewHolder instead of ViewDispatcher in About app * Enable ViewDispatcher queue on construction, deprecate view_dispatcher_enable_queue() * Remove all invocations of view_dispatcher_enable_queue() * Remove app-scened-template * Remove missing library from target.json * Port Accessor app to ViewHolder * Make the linter happy * Add example_view_holder application, update ViewHolder docs * Add example_view_dispatcher application, update ViewDispatcher docs * Replace FuriSemaphore with FuriApiLock, remove workaround delay * Fix logical error * Fix another logical error * Use the sources directive to speed up compilation * Use constant define macro * Improve FuriEventLoop documentation * Improve FuriEventLoop documentation once more * Bump API Version * Gui: remove redundant checks from ViewDispatcher * Gui: remove dead ifs from ViewDispatcher Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com> Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
parent
1e5dd001fe
commit
bf6c6c231f
104 changed files with 2099 additions and 1757 deletions
|
@ -5,45 +5,49 @@
|
||||||
AccessorAppViewManager::AccessorAppViewManager() {
|
AccessorAppViewManager::AccessorAppViewManager() {
|
||||||
event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent));
|
event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent));
|
||||||
|
|
||||||
view_dispatcher = view_dispatcher_alloc();
|
view_holder = view_holder_alloc();
|
||||||
auto callback = cbc::obtain_connector(this, &AccessorAppViewManager::previous_view_callback);
|
auto callback =
|
||||||
|
cbc::obtain_connector(this, &AccessorAppViewManager::view_holder_back_callback);
|
||||||
|
|
||||||
// allocate views
|
// allocate views
|
||||||
submenu = submenu_alloc();
|
submenu = submenu_alloc();
|
||||||
add_view(ViewType::Submenu, submenu_get_view(submenu));
|
|
||||||
|
|
||||||
popup = popup_alloc();
|
popup = popup_alloc();
|
||||||
add_view(ViewType::Popup, popup_get_view(popup));
|
|
||||||
|
// set back callback
|
||||||
|
view_holder_set_back_callback(view_holder, callback, NULL);
|
||||||
|
|
||||||
gui = static_cast<Gui*>(furi_record_open(RECORD_GUI));
|
gui = static_cast<Gui*>(furi_record_open(RECORD_GUI));
|
||||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
view_holder_attach_to_gui(view_holder, gui);
|
||||||
|
|
||||||
// set previous view callback for all views
|
|
||||||
view_set_previous_callback(submenu_get_view(submenu), callback);
|
|
||||||
view_set_previous_callback(popup_get_view(popup), callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessorAppViewManager::~AccessorAppViewManager() {
|
AccessorAppViewManager::~AccessorAppViewManager() {
|
||||||
// remove views
|
// remove current view
|
||||||
view_dispatcher_remove_view(
|
view_holder_set_view(view_holder, NULL);
|
||||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Submenu));
|
|
||||||
view_dispatcher_remove_view(
|
|
||||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Popup));
|
|
||||||
|
|
||||||
// free view modules
|
// free view modules
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
submenu_free(submenu);
|
submenu_free(submenu);
|
||||||
popup_free(popup);
|
popup_free(popup);
|
||||||
|
// free view holder
|
||||||
// free dispatcher
|
view_holder_free(view_holder);
|
||||||
view_dispatcher_free(view_dispatcher);
|
|
||||||
|
|
||||||
// free event queue
|
// free event queue
|
||||||
furi_message_queue_free(event_queue);
|
furi_message_queue_free(event_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessorAppViewManager::switch_to(ViewType type) {
|
void AccessorAppViewManager::switch_to(ViewType type) {
|
||||||
view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
|
View* view;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case ViewType::Submenu:
|
||||||
|
view = submenu_get_view(submenu);
|
||||||
|
break;
|
||||||
|
case ViewType::Popup:
|
||||||
|
view = popup_get_view(popup);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
view_holder_set_view(view_holder, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
Submenu* AccessorAppViewManager::get_submenu() {
|
Submenu* AccessorAppViewManager::get_submenu() {
|
||||||
|
@ -65,16 +69,10 @@ void AccessorAppViewManager::send_event(AccessorEvent* event) {
|
||||||
furi_check(result == FuriStatusOk);
|
furi_check(result == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t AccessorAppViewManager::previous_view_callback(void*) {
|
void AccessorAppViewManager::view_holder_back_callback(void*) {
|
||||||
if(event_queue != NULL) {
|
if(event_queue != NULL) {
|
||||||
AccessorEvent event;
|
AccessorEvent event;
|
||||||
event.type = AccessorEvent::Type::Back;
|
event.type = AccessorEvent::Type::Back;
|
||||||
send_event(&event);
|
send_event(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return VIEW_IGNORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccessorAppViewManager::add_view(ViewType view_type, View* view) {
|
|
||||||
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <gui/view_dispatcher.h>
|
#include <gui/view_holder.h>
|
||||||
#include <gui/modules/submenu.h>
|
#include <gui/modules/submenu.h>
|
||||||
#include <gui/modules/popup.h>
|
#include <gui/modules/popup.h>
|
||||||
#include "accessor_event.h"
|
#include "accessor_event.h"
|
||||||
|
@ -10,7 +10,6 @@ public:
|
||||||
enum class ViewType : uint8_t {
|
enum class ViewType : uint8_t {
|
||||||
Submenu,
|
Submenu,
|
||||||
Popup,
|
Popup,
|
||||||
Tune,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FuriMessageQueue* event_queue;
|
FuriMessageQueue* event_queue;
|
||||||
|
@ -27,11 +26,10 @@ public:
|
||||||
Popup* get_popup(void);
|
Popup* get_popup(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ViewDispatcher* view_dispatcher;
|
|
||||||
Gui* gui;
|
Gui* gui;
|
||||||
|
ViewHolder* view_holder;
|
||||||
|
|
||||||
uint32_t previous_view_callback(void* context);
|
void view_holder_back_callback(void* context);
|
||||||
void add_view(ViewType view_type, View* view);
|
|
||||||
|
|
||||||
// view elements
|
// view elements
|
||||||
Submenu* submenu;
|
Submenu* submenu;
|
||||||
|
|
|
@ -42,7 +42,6 @@ BatteryTestApp* battery_test_alloc(void) {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_tick_event_callback(
|
view_dispatcher_set_tick_event_callback(
|
||||||
app->view_dispatcher, battery_test_battery_info_update_model, 500);
|
app->view_dispatcher, battery_test_battery_info_update_model, 500);
|
||||||
|
|
|
@ -36,7 +36,6 @@ BtDebugApp* bt_debug_app_alloc(void) {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
|
|
@ -66,7 +66,6 @@ CrashTest* crash_test_alloc(void) {
|
||||||
|
|
||||||
instance->gui = furi_record_open(RECORD_GUI);
|
instance->gui = furi_record_open(RECORD_GUI);
|
||||||
instance->view_dispatcher = view_dispatcher_alloc();
|
instance->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(
|
view_dispatcher_attach_to_gui(
|
||||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,6 @@ DisplayTest* display_test_alloc(void) {
|
||||||
|
|
||||||
instance->gui = furi_record_open(RECORD_GUI);
|
instance->gui = furi_record_open(RECORD_GUI);
|
||||||
instance->view_dispatcher = view_dispatcher_alloc();
|
instance->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(
|
view_dispatcher_attach_to_gui(
|
||||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,8 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
|
||||||
furi_message_queue_put(app->input_queue, input_event, 0);
|
furi_message_queue_put(app->input_queue, input_event, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool input_queue_callback(FuriMessageQueue* queue, void* context) {
|
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
FuriMessageQueue* queue = object;
|
||||||
EventLoopBlinkTestApp* app = context;
|
EventLoopBlinkTestApp* app = context;
|
||||||
|
|
||||||
InputEvent event;
|
InputEvent event;
|
||||||
|
@ -144,7 +145,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app);
|
furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app);
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
|
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
|
||||||
|
|
||||||
furi_event_loop_run(app.event_loop);
|
furi_event_loop_run(app.event_loop);
|
||||||
|
@ -154,7 +155,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||||
|
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue);
|
furi_event_loop_unsubscribe(app.event_loop, app.input_queue);
|
||||||
furi_message_queue_free(app.input_queue);
|
furi_message_queue_free(app.input_queue);
|
||||||
|
|
||||||
for(size_t i = 0; i < TIMER_COUNT; ++i) {
|
for(size_t i = 0; i < TIMER_COUNT; ++i) {
|
||||||
|
|
|
@ -33,8 +33,6 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
|
||||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
|
|
||||||
app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app);
|
||||||
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
|
@ -17,7 +17,6 @@ static LfRfidDebug* lfrfid_debug_alloc(void) {
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
app->view_dispatcher, lfrfid_debug_custom_event_callback);
|
app->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||||
|
|
|
@ -61,7 +61,6 @@ static LocaleTestApp* locale_test_alloc(void) {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
|
|
@ -99,7 +99,6 @@ static RpcDebugApp* rpc_debug_app_alloc(void) {
|
||||||
view_dispatcher_set_tick_event_callback(
|
view_dispatcher_set_tick_event_callback(
|
||||||
app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
|
app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
|
|
||||||
app->widget = widget_alloc();
|
app->widget = widget_alloc();
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
|
|
|
@ -30,7 +30,6 @@ SubGhzTestApp* subghz_test_app_alloc(void) {
|
||||||
// View Dispatcher
|
// View Dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
|
|
@ -126,7 +126,6 @@ int32_t text_box_view_test_app(void* p) {
|
||||||
Gui* gui = furi_record_open(RECORD_GUI);
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||||
view_dispatcher_enable_queue(view_dispatcher);
|
|
||||||
|
|
||||||
TextBoxViewTest instance = {
|
TextBoxViewTest instance = {
|
||||||
.text_box = text_box_alloc(),
|
.text_box = text_box_alloc(),
|
||||||
|
|
|
@ -242,7 +242,6 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
|
|
@ -19,25 +19,24 @@ typedef struct {
|
||||||
uint32_t consumer_counter;
|
uint32_t consumer_counter;
|
||||||
} TestFuriData;
|
} TestFuriData;
|
||||||
|
|
||||||
bool test_furi_event_loop_producer_mq_callback(FuriMessageQueue* queue, void* context) {
|
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
|
|
||||||
TestFuriData* data = context;
|
TestFuriData* data = context;
|
||||||
furi_check(data->mq == queue, "Invalid queue");
|
furi_check(data->mq == object, "Invalid queue");
|
||||||
|
|
||||||
FURI_LOG_I(
|
FURI_LOG_I(
|
||||||
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||||
|
|
||||||
// Remove and add should not cause crash
|
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||||
// furi_event_loop_message_queue_remove(data->producer_event_loop, data->mq);
|
furi_event_loop_subscribe_message_queue(
|
||||||
// furi_event_loop_message_queue_add(
|
data->producer_event_loop,
|
||||||
// data->producer_event_loop,
|
data->mq,
|
||||||
// data->mq,
|
FuriEventLoopEventOut,
|
||||||
// FuriEventLoopEventOut,
|
test_furi_event_loop_producer_mq_callback,
|
||||||
// test_furi_event_loop_producer_mq_callback,
|
data);
|
||||||
// data);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||||
furi_event_loop_stop(data->producer_event_loop);
|
furi_event_loop_stop(data->producer_event_loop);
|
||||||
|
@ -61,7 +60,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||||
FURI_LOG_I(TAG, "producer start 1st run");
|
FURI_LOG_I(TAG, "producer start 1st run");
|
||||||
|
|
||||||
data->producer_event_loop = furi_event_loop_alloc();
|
data->producer_event_loop = furi_event_loop_alloc();
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
data->producer_event_loop,
|
data->producer_event_loop,
|
||||||
data->mq,
|
data->mq,
|
||||||
FuriEventLoopEventOut,
|
FuriEventLoopEventOut,
|
||||||
|
@ -73,7 +72,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||||
|
|
||||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||||
furi_event_loop_free(data->producer_event_loop);
|
furi_event_loop_free(data->producer_event_loop);
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "producer start 2nd run");
|
FURI_LOG_I(TAG, "producer start 2nd run");
|
||||||
|
@ -81,7 +80,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||||
data->producer_counter = 0;
|
data->producer_counter = 0;
|
||||||
data->producer_event_loop = furi_event_loop_alloc();
|
data->producer_event_loop = furi_event_loop_alloc();
|
||||||
|
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
data->producer_event_loop,
|
data->producer_event_loop,
|
||||||
data->mq,
|
data->mq,
|
||||||
FuriEventLoopEventOut,
|
FuriEventLoopEventOut,
|
||||||
|
@ -90,7 +89,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||||
|
|
||||||
furi_event_loop_run(data->producer_event_loop);
|
furi_event_loop_run(data->producer_event_loop);
|
||||||
|
|
||||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||||
furi_event_loop_free(data->producer_event_loop);
|
furi_event_loop_free(data->producer_event_loop);
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "producer end");
|
FURI_LOG_I(TAG, "producer end");
|
||||||
|
@ -98,11 +97,11 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* context) {
|
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
|
|
||||||
TestFuriData* data = context;
|
TestFuriData* data = context;
|
||||||
furi_check(data->mq == queue);
|
furi_check(data->mq == object);
|
||||||
|
|
||||||
furi_delay_us(furi_hal_random_get() % 1000);
|
furi_delay_us(furi_hal_random_get() % 1000);
|
||||||
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
||||||
|
@ -110,16 +109,15 @@ bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* co
|
||||||
FURI_LOG_I(
|
FURI_LOG_I(
|
||||||
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||||
|
|
||||||
// Remove and add should not cause crash
|
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||||
// furi_event_loop_message_queue_remove(data->consumer_event_loop, data->mq);
|
furi_event_loop_subscribe_message_queue(
|
||||||
// furi_event_loop_message_queue_add(
|
data->consumer_event_loop,
|
||||||
// data->consumer_event_loop,
|
data->mq,
|
||||||
// data->mq,
|
FuriEventLoopEventIn,
|
||||||
// FuriEventLoopEventIn,
|
test_furi_event_loop_consumer_mq_callback,
|
||||||
// test_furi_event_loop_producer_mq_callback,
|
data);
|
||||||
// data);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||||
furi_event_loop_stop(data->consumer_event_loop);
|
furi_event_loop_stop(data->consumer_event_loop);
|
||||||
|
@ -137,7 +135,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||||
FURI_LOG_I(TAG, "consumer start 1st run");
|
FURI_LOG_I(TAG, "consumer start 1st run");
|
||||||
|
|
||||||
data->consumer_event_loop = furi_event_loop_alloc();
|
data->consumer_event_loop = furi_event_loop_alloc();
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
data->consumer_event_loop,
|
data->consumer_event_loop,
|
||||||
data->mq,
|
data->mq,
|
||||||
FuriEventLoopEventIn,
|
FuriEventLoopEventIn,
|
||||||
|
@ -149,14 +147,14 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||||
|
|
||||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||||
furi_event_loop_free(data->consumer_event_loop);
|
furi_event_loop_free(data->consumer_event_loop);
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "consumer start 2nd run");
|
FURI_LOG_I(TAG, "consumer start 2nd run");
|
||||||
|
|
||||||
data->consumer_counter = 0;
|
data->consumer_counter = 0;
|
||||||
data->consumer_event_loop = furi_event_loop_alloc();
|
data->consumer_event_loop = furi_event_loop_alloc();
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
data->consumer_event_loop,
|
data->consumer_event_loop,
|
||||||
data->mq,
|
data->mq,
|
||||||
FuriEventLoopEventIn,
|
FuriEventLoopEventIn,
|
||||||
|
@ -165,7 +163,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||||
|
|
||||||
furi_event_loop_run(data->consumer_event_loop);
|
furi_event_loop_run(data->consumer_event_loop);
|
||||||
|
|
||||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||||
furi_event_loop_free(data->consumer_event_loop);
|
furi_event_loop_free(data->consumer_event_loop);
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "consumer end");
|
FURI_LOG_I(TAG, "consumer end");
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <loader/loader.h>
|
#include <loader/loader.h>
|
||||||
#include <storage/filesystem_api_defines.h>
|
#include <storage/filesystem_api_defines.h>
|
||||||
|
|
||||||
|
#include <lib/toolbox/api_lock.h>
|
||||||
#include <lib/toolbox/md5_calc.h>
|
#include <lib/toolbox/md5_calc.h>
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
|
|
||||||
|
@ -35,8 +36,8 @@ static uint32_t command_id = 0;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
RpcSession* session;
|
RpcSession* session;
|
||||||
FuriStreamBuffer* output_stream;
|
FuriStreamBuffer* output_stream;
|
||||||
FuriSemaphore* close_session_semaphore;
|
FuriApiLock session_close_lock;
|
||||||
FuriSemaphore* terminate_semaphore;
|
FuriApiLock session_terminate_lock;
|
||||||
uint32_t timeout;
|
uint32_t timeout;
|
||||||
} RpcSessionContext;
|
} RpcSessionContext;
|
||||||
|
|
||||||
|
@ -92,8 +93,8 @@ static void test_rpc_setup(void) {
|
||||||
|
|
||||||
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
|
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
|
||||||
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
|
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
|
||||||
rpc_session[0].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
rpc_session[0].session_close_lock = api_lock_alloc_locked();
|
||||||
rpc_session[0].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
rpc_session[0].session_terminate_lock = api_lock_alloc_locked();
|
||||||
rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback);
|
rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback);
|
||||||
rpc_session_set_terminated_callback(
|
rpc_session_set_terminated_callback(
|
||||||
rpc_session[0].session, test_rpc_session_terminated_callback);
|
rpc_session[0].session, test_rpc_session_terminated_callback);
|
||||||
|
@ -112,8 +113,8 @@ static void test_rpc_setup_second_session(void) {
|
||||||
|
|
||||||
rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1);
|
rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1);
|
||||||
rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback);
|
rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback);
|
||||||
rpc_session[1].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
rpc_session[1].session_close_lock = api_lock_alloc_locked();
|
||||||
rpc_session[1].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
rpc_session[1].session_terminate_lock = api_lock_alloc_locked();
|
||||||
rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback);
|
rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback);
|
||||||
rpc_session_set_terminated_callback(
|
rpc_session_set_terminated_callback(
|
||||||
rpc_session[1].session, test_rpc_session_terminated_callback);
|
rpc_session[1].session, test_rpc_session_terminated_callback);
|
||||||
|
@ -121,36 +122,32 @@ static void test_rpc_setup_second_session(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_rpc_teardown(void) {
|
static void test_rpc_teardown(void) {
|
||||||
furi_check(rpc_session[0].close_session_semaphore);
|
furi_check(rpc_session[0].session_close_lock);
|
||||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, 0);
|
api_lock_relock(rpc_session[0].session_terminate_lock);
|
||||||
rpc_session_close(rpc_session[0].session);
|
rpc_session_close(rpc_session[0].session);
|
||||||
furi_check(
|
api_lock_wait_unlock(rpc_session[0].session_terminate_lock);
|
||||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, FuriWaitForever) ==
|
|
||||||
FuriStatusOk);
|
|
||||||
furi_record_close(RECORD_RPC);
|
furi_record_close(RECORD_RPC);
|
||||||
furi_stream_buffer_free(rpc_session[0].output_stream);
|
furi_stream_buffer_free(rpc_session[0].output_stream);
|
||||||
furi_semaphore_free(rpc_session[0].close_session_semaphore);
|
api_lock_free(rpc_session[0].session_close_lock);
|
||||||
furi_semaphore_free(rpc_session[0].terminate_semaphore);
|
api_lock_free(rpc_session[0].session_terminate_lock);
|
||||||
++command_id;
|
++command_id;
|
||||||
rpc_session[0].output_stream = NULL;
|
rpc_session[0].output_stream = NULL;
|
||||||
rpc_session[0].close_session_semaphore = NULL;
|
rpc_session[0].session_close_lock = NULL;
|
||||||
rpc = NULL;
|
rpc = NULL;
|
||||||
rpc_session[0].session = NULL;
|
rpc_session[0].session = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_rpc_teardown_second_session(void) {
|
static void test_rpc_teardown_second_session(void) {
|
||||||
furi_check(rpc_session[1].close_session_semaphore);
|
furi_check(rpc_session[1].session_close_lock);
|
||||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, 0);
|
api_lock_relock(rpc_session[1].session_terminate_lock);
|
||||||
rpc_session_close(rpc_session[1].session);
|
rpc_session_close(rpc_session[1].session);
|
||||||
furi_check(
|
api_lock_wait_unlock(rpc_session[1].session_terminate_lock);
|
||||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, FuriWaitForever) ==
|
|
||||||
FuriStatusOk);
|
|
||||||
furi_stream_buffer_free(rpc_session[1].output_stream);
|
furi_stream_buffer_free(rpc_session[1].output_stream);
|
||||||
furi_semaphore_free(rpc_session[1].close_session_semaphore);
|
api_lock_free(rpc_session[1].session_close_lock);
|
||||||
furi_semaphore_free(rpc_session[1].terminate_semaphore);
|
api_lock_free(rpc_session[1].session_terminate_lock);
|
||||||
++command_id;
|
++command_id;
|
||||||
rpc_session[1].output_stream = NULL;
|
rpc_session[1].output_stream = NULL;
|
||||||
rpc_session[1].close_session_semaphore = NULL;
|
rpc_session[1].session_close_lock = NULL;
|
||||||
rpc_session[1].session = NULL;
|
rpc_session[1].session = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,14 +201,14 @@ static void test_rpc_session_close_callback(void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
RpcSessionContext* callbacks_context = context;
|
RpcSessionContext* callbacks_context = context;
|
||||||
|
|
||||||
furi_check(furi_semaphore_release(callbacks_context->close_session_semaphore) == FuriStatusOk);
|
api_lock_unlock(callbacks_context->session_close_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_rpc_session_terminated_callback(void* context) {
|
static void test_rpc_session_terminated_callback(void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
RpcSessionContext* callbacks_context = context;
|
RpcSessionContext* callbacks_context = context;
|
||||||
|
|
||||||
furi_check(furi_semaphore_release(callbacks_context->terminate_semaphore) == FuriStatusOk);
|
api_lock_unlock(callbacks_context->session_terminate_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_rpc_print_message_list(MsgList_t msg_list) {
|
static void test_rpc_print_message_list(MsgList_t msg_list) {
|
||||||
|
@ -1645,7 +1642,7 @@ static void test_rpc_feed_rubbish_run(
|
||||||
|
|
||||||
test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0);
|
test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0);
|
||||||
|
|
||||||
furi_check(furi_semaphore_acquire(rpc_session[0].close_session_semaphore, 0) != FuriStatusOk);
|
furi_check(api_lock_is_locked(rpc_session[0].session_close_lock));
|
||||||
test_rpc_encode_and_feed(input_before, 0);
|
test_rpc_encode_and_feed(input_before, 0);
|
||||||
test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size);
|
test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size);
|
||||||
test_rpc_encode_and_feed(input_after, 0);
|
test_rpc_encode_and_feed(input_after, 0);
|
||||||
|
|
|
@ -36,14 +36,10 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||||
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
||||||
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
||||||
API_METHOD(
|
API_METHOD(
|
||||||
furi_event_loop_message_queue_subscribe,
|
furi_event_loop_subscribe_message_queue,
|
||||||
void,
|
void,
|
||||||
(FuriEventLoop*,
|
(FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
|
||||||
FuriMessageQueue*,
|
API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
|
||||||
FuriEventLoopEvent,
|
|
||||||
FuriEventLoopMessageQueueCallback,
|
|
||||||
void*)),
|
|
||||||
API_METHOD(furi_event_loop_message_queue_unsubscribe, void, (FuriEventLoop*, FuriMessageQueue*)),
|
|
||||||
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
||||||
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
||||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||||
|
|
|
@ -63,7 +63,6 @@ UsbTestApp* usb_test_app_alloc(void) {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
|
|
@ -75,7 +75,6 @@ static BleBeaconApp* ble_beacon_app_alloc(void) {
|
||||||
view_dispatcher_set_tick_event_callback(
|
view_dispatcher_set_tick_event_callback(
|
||||||
app->view_dispatcher, ble_beacon_app_tick_event_callback, 100);
|
app->view_dispatcher, ble_beacon_app_tick_event_callback, 100);
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
|
|
||||||
app->submenu = submenu_alloc();
|
app->submenu = submenu_alloc();
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
|
|
36
applications/examples/example_event_loop/application.fam
Normal file
36
applications/examples/example_event_loop/application.fam
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
App(
|
||||||
|
appid="example_event_loop_timer",
|
||||||
|
name="Example: Event Loop Timer",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
sources=["example_event_loop_timer.c"],
|
||||||
|
entry_point="example_event_loop_timer_app",
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="example_event_loop_mutex",
|
||||||
|
name="Example: Event Loop Mutex",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
sources=["example_event_loop_mutex.c"],
|
||||||
|
entry_point="example_event_loop_mutex_app",
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="example_event_loop_stream_buffer",
|
||||||
|
name="Example: Event Loop Stream Buffer",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
sources=["example_event_loop_stream_buffer.c"],
|
||||||
|
entry_point="example_event_loop_stream_buffer_app",
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="example_event_loop_multi",
|
||||||
|
name="Example: Event Loop Multi",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
sources=["example_event_loop_multi.c"],
|
||||||
|
entry_point="example_event_loop_multi_app",
|
||||||
|
requires=["gui"],
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
|
@ -0,0 +1,342 @@
|
||||||
|
/**
|
||||||
|
* @file example_event_loop_multi.c
|
||||||
|
* @brief Example application that demonstrates multiple primitives used with two FuriEventLoop instances.
|
||||||
|
*
|
||||||
|
* This application simulates a complex use case of having two concurrent event loops (each one executing in
|
||||||
|
* its own thread) using a stream buffer for communication and additional timers and message passing to handle
|
||||||
|
* the keypad input. Additionally, it shows how to use thread signals to stop an event loop in another thread.
|
||||||
|
* The GUI functionality is there only for the purpose of exclusive access to the input events.
|
||||||
|
*
|
||||||
|
* The application's functionality consists of the following:
|
||||||
|
* - Print keypad key names and types when pressed,
|
||||||
|
* - If the Back key is long-pressed, a countdown starts upon completion of which the app exits,
|
||||||
|
* - The countdown can be cancelled by long-pressing the Ok button, it also resets the counter,
|
||||||
|
* - Blocks of random data are periodically generated in a separate thread,
|
||||||
|
* - When ready, the main application thread gets notified and prints the data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_port.h>
|
||||||
|
|
||||||
|
#include <furi_hal_random.h>
|
||||||
|
|
||||||
|
#define TAG "ExampleEventLoopMulti"
|
||||||
|
|
||||||
|
#define COUNTDOWN_START_VALUE (5UL)
|
||||||
|
#define COUNTDOWN_INTERVAL_MS (1000UL)
|
||||||
|
#define WORKER_DATA_INTERVAL_MS (1500UL)
|
||||||
|
|
||||||
|
#define INPUT_QUEUE_SIZE (8)
|
||||||
|
#define STREAM_BUFFER_SIZE (16)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriEventLoopTimer* timer;
|
||||||
|
FuriStreamBuffer* stream_buffer;
|
||||||
|
} EventLoopMultiAppWorker;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Gui* gui;
|
||||||
|
ViewPort* view_port;
|
||||||
|
FuriThread* worker_thread;
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
FuriEventLoopTimer* exit_timer;
|
||||||
|
FuriStreamBuffer* stream_buffer;
|
||||||
|
uint32_t exit_countdown_value;
|
||||||
|
} EventLoopMultiApp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Worker functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
||||||
|
static bool
|
||||||
|
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiAppWorker* worker = context;
|
||||||
|
|
||||||
|
furi_assert(object == worker->stream_buffer);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Data was removed from buffer");
|
||||||
|
// Restart the timer to generate another block of random data.
|
||||||
|
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
||||||
|
// since it is of one-shot type.
|
||||||
|
static void event_loop_multi_app_worker_timer_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiAppWorker* worker = context;
|
||||||
|
|
||||||
|
// Generate a block of random data.
|
||||||
|
uint8_t data[STREAM_BUFFER_SIZE];
|
||||||
|
furi_hal_random_fill_buf(data, sizeof(data));
|
||||||
|
// Put the generated data in the stream buffer.
|
||||||
|
// IMPORTANT: No waiting in the event handlers!
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_send(worker->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopMultiAppWorker*
|
||||||
|
event_loop_multi_app_worker_alloc(FuriStreamBuffer* stream_buffer) {
|
||||||
|
EventLoopMultiAppWorker* worker = malloc(sizeof(EventLoopMultiAppWorker));
|
||||||
|
// Create the worker event loop.
|
||||||
|
worker->event_loop = furi_event_loop_alloc();
|
||||||
|
// Create the timer governing the data generation.
|
||||||
|
// It is of one-shot type, i.e. it will not restart automatically upon expiration.
|
||||||
|
worker->timer = furi_event_loop_timer_alloc(
|
||||||
|
worker->event_loop,
|
||||||
|
event_loop_multi_app_worker_timer_callback,
|
||||||
|
FuriEventLoopTimerTypeOnce,
|
||||||
|
worker);
|
||||||
|
|
||||||
|
// Using the same stream buffer as the main thread (it was already created beforehand).
|
||||||
|
worker->stream_buffer = stream_buffer;
|
||||||
|
// Notify the worker event loop about data being taken out of the stream buffer.
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
worker->event_loop,
|
||||||
|
worker->stream_buffer,
|
||||||
|
FuriEventLoopEventOut | FuriEventLoopEventFlagEdge,
|
||||||
|
event_loop_multi_app_stream_buffer_worker_callback,
|
||||||
|
worker);
|
||||||
|
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_multi_app_worker_free(EventLoopMultiAppWorker* worker) {
|
||||||
|
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_unsubscribe(worker->event_loop, worker->stream_buffer);
|
||||||
|
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_timer_free(worker->timer);
|
||||||
|
// Now it is okay to delete the event loop.
|
||||||
|
furi_event_loop_free(worker->event_loop);
|
||||||
|
|
||||||
|
free(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_multi_app_worker_run(EventLoopMultiAppWorker* worker) {
|
||||||
|
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||||
|
furi_event_loop_run(worker->event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is the worker thread body and (obviously) is executed in the worker thread.
|
||||||
|
static int32_t event_loop_multi_app_worker_thread(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiApp* app = context;
|
||||||
|
|
||||||
|
// Because an event loop is used, it MUST be created in the thread it will be run in.
|
||||||
|
// Therefore, the worker creation and deletion is handled in the worker thread.
|
||||||
|
EventLoopMultiAppWorker* worker = event_loop_multi_app_worker_alloc(app->stream_buffer);
|
||||||
|
event_loop_multi_app_worker_run(worker);
|
||||||
|
event_loop_multi_app_worker_free(worker);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main application functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
|
||||||
|
static void event_loop_multi_app_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiApp* app = context;
|
||||||
|
// Pass the event to the the application's input queue
|
||||||
|
furi_check(furi_message_queue_put(app->input_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is executed each time new data is available in the stream buffer.
|
||||||
|
static bool
|
||||||
|
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiApp* app = context;
|
||||||
|
|
||||||
|
furi_assert(object == app->stream_buffer);
|
||||||
|
// Get the data from the stream buffer
|
||||||
|
uint8_t data[STREAM_BUFFER_SIZE];
|
||||||
|
// IMPORTANT: No waiting in the event handlers!
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_receive(app->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||||
|
|
||||||
|
// Format the data for printing and print it to the debug output.
|
||||||
|
FuriString* tmp_str = furi_string_alloc();
|
||||||
|
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||||
|
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||||
|
furi_string_free(tmp_str);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is executed each time a new message is inserted in the input queue.
|
||||||
|
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiApp* app = context;
|
||||||
|
|
||||||
|
furi_assert(object == app->input_queue);
|
||||||
|
|
||||||
|
InputEvent event;
|
||||||
|
// IMPORTANT: No waiting in the event handlers!
|
||||||
|
furi_check(furi_message_queue_get(app->input_queue, &event, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(event.type == InputTypeLong) {
|
||||||
|
// The user has long-pressed the Back key, try starting the countdown.
|
||||||
|
if(event.key == InputKeyBack) {
|
||||||
|
if(!furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||||
|
// Actually start the countdown
|
||||||
|
FURI_LOG_I(TAG, "Starting exit countdown!");
|
||||||
|
furi_event_loop_timer_start(app->exit_timer, COUNTDOWN_INTERVAL_MS);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// The countdown is already in progress, print a warning message
|
||||||
|
FURI_LOG_W(TAG, "Countdown has already been started");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user has long-pressed the Ok key, try stopping the countdown.
|
||||||
|
} else if(event.key == InputKeyOk) {
|
||||||
|
if(furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||||
|
// Actually cancel the countdown
|
||||||
|
FURI_LOG_I(TAG, "Exit countdown cancelled!");
|
||||||
|
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||||
|
furi_event_loop_timer_stop(app->exit_timer);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// The countdown is not running, print a warning message
|
||||||
|
FURI_LOG_W(TAG, "Countdown has not been started yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Not a Back or Ok key, just print its name.
|
||||||
|
FURI_LOG_I(TAG, "Long press: %s", input_get_key_name(event.key));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(event.type == InputTypeShort) {
|
||||||
|
// Not a long press, just print the key's name.
|
||||||
|
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is executed each time the countdown timer expires.
|
||||||
|
static void event_loop_multi_app_exit_timer_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMultiApp* app = context;
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Exiting in %lu ...", app->exit_countdown_value);
|
||||||
|
|
||||||
|
// If the coundown value has reached 0, exit the application
|
||||||
|
if(app->exit_countdown_value == 0) {
|
||||||
|
FURI_LOG_I(TAG, "Exiting NOW!");
|
||||||
|
|
||||||
|
// Send a signal to the worker thread to exit.
|
||||||
|
// A signal handler that handles FuriSignalExit is already set by default.
|
||||||
|
furi_thread_signal(app->worker_thread, FuriSignalExit, NULL);
|
||||||
|
// Request the application event loop to stop.
|
||||||
|
furi_event_loop_stop(app->event_loop);
|
||||||
|
|
||||||
|
// Otherwise just decrement it and wait for the next time the timer expires.
|
||||||
|
} else {
|
||||||
|
app->exit_countdown_value -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopMultiApp* event_loop_multi_app_alloc(void) {
|
||||||
|
EventLoopMultiApp* app = malloc(sizeof(EventLoopMultiApp));
|
||||||
|
// Create event loop instances.
|
||||||
|
app->event_loop = furi_event_loop_alloc();
|
||||||
|
|
||||||
|
// Create a worker thread instance. The worker event loop will execute inside it.
|
||||||
|
app->worker_thread = furi_thread_alloc_ex(
|
||||||
|
"EventLoopMultiWorker", 1024, event_loop_multi_app_worker_thread, app);
|
||||||
|
// Create a message queue to receive the input events.
|
||||||
|
app->input_queue = furi_message_queue_alloc(INPUT_QUEUE_SIZE, sizeof(InputEvent));
|
||||||
|
// Create a stream buffer to receive the generated data.
|
||||||
|
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE);
|
||||||
|
// Create a timer to run the countdown.
|
||||||
|
app->exit_timer = furi_event_loop_timer_alloc(
|
||||||
|
app->event_loop,
|
||||||
|
event_loop_multi_app_exit_timer_callback,
|
||||||
|
FuriEventLoopTimerTypePeriodic,
|
||||||
|
app);
|
||||||
|
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->view_port = view_port_alloc();
|
||||||
|
// Start the countdown from this value
|
||||||
|
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||||
|
// Gain exclusive access to the input events
|
||||||
|
view_port_input_callback_set(app->view_port, event_loop_multi_app_input_callback, app);
|
||||||
|
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||||
|
// Notify the event loop about incoming messages in the queue
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
app->event_loop,
|
||||||
|
app->input_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
event_loop_multi_app_input_queue_callback,
|
||||||
|
app);
|
||||||
|
// Notify the event loop about new data in the stream buffer
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
app->event_loop,
|
||||||
|
app->stream_buffer,
|
||||||
|
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
|
||||||
|
event_loop_multi_app_stream_buffer_callback,
|
||||||
|
app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_multi_app_free(EventLoopMultiApp* app) {
|
||||||
|
gui_remove_view_port(app->gui, app->view_port);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_unsubscribe(app->event_loop, app->input_queue);
|
||||||
|
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||||
|
// Delete all instances
|
||||||
|
view_port_free(app->view_port);
|
||||||
|
furi_message_queue_free(app->input_queue);
|
||||||
|
furi_stream_buffer_free(app->stream_buffer);
|
||||||
|
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_timer_free(app->exit_timer);
|
||||||
|
furi_thread_free(app->worker_thread);
|
||||||
|
furi_event_loop_free(app->event_loop);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_multi_app_run(EventLoopMultiApp* app) {
|
||||||
|
FURI_LOG_I(TAG, "Press keys to see them printed here.");
|
||||||
|
FURI_LOG_I(TAG, "Long press \"Back\" to exit after %lu seconds.", COUNTDOWN_START_VALUE);
|
||||||
|
FURI_LOG_I(TAG, "Long press \"Ok\" to cancel the countdown.");
|
||||||
|
|
||||||
|
// Start the worker thread
|
||||||
|
furi_thread_start(app->worker_thread);
|
||||||
|
// Run the application event loop. This call will block until the application is about to exit.
|
||||||
|
furi_event_loop_run(app->event_loop);
|
||||||
|
// Wait for the worker thread to finish.
|
||||||
|
furi_thread_join(app->worker_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************
|
||||||
|
* vvv START HERE vvv
|
||||||
|
*
|
||||||
|
* The application's entry point - referenced in application.fam
|
||||||
|
*******************************************************************/
|
||||||
|
int32_t example_event_loop_multi_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
EventLoopMultiApp* app = event_loop_multi_app_alloc();
|
||||||
|
event_loop_multi_app_run(app);
|
||||||
|
event_loop_multi_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/**
|
||||||
|
* @file example_event_loop_mutex.c
|
||||||
|
* @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
|
||||||
|
*
|
||||||
|
* This application simulates a use case where a time-consuming blocking operation is executed
|
||||||
|
* in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
|
||||||
|
* of the above mentioned simulated work and prints the results to the debug output each time, then exits.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_random.h>
|
||||||
|
|
||||||
|
#define TAG "ExampleEventLoopMutex"
|
||||||
|
|
||||||
|
#define WORKER_ITERATION_COUNT (10)
|
||||||
|
// We are interested in IN events (for the mutex, that means that the mutex has been released),
|
||||||
|
// using edge trigger mode (reacting only to changes in mutex state) and
|
||||||
|
// employing one-shot mode to automatically unsubscribe before the event is processed.
|
||||||
|
#define MUTEX_EVENT_AND_FLAGS \
|
||||||
|
(FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriThread* worker_thread;
|
||||||
|
FuriMutex* worker_mutex;
|
||||||
|
uint8_t worker_result;
|
||||||
|
} EventLoopMutexApp;
|
||||||
|
|
||||||
|
// This funciton is being run in a separate thread to simulate lenghty blocking operations
|
||||||
|
static int32_t event_loop_mutex_app_worker_thread(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopMutexApp* app = context;
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Worker thread started");
|
||||||
|
|
||||||
|
// Run 10 iterations of simulated work
|
||||||
|
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||||
|
FURI_LOG_I(TAG, "Doing work ...");
|
||||||
|
// Take the mutex so that no-one can access the worker_result variable
|
||||||
|
furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
// Simulate a blocking operation with a random delay between 900 and 1100 ms
|
||||||
|
const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
|
||||||
|
furi_delay_ms(work_time_ms);
|
||||||
|
// Simulate a result with a random number between 0 and 255
|
||||||
|
app->worker_result = furi_hal_random_get() % 0xFF;
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
|
||||||
|
// Release the mutex, which will notify the event loop that the result is ready
|
||||||
|
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||||
|
// Return control to the scheduler so that the event loop can take the mutex in its turn
|
||||||
|
furi_thread_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||||
|
// Request the event loop to stop
|
||||||
|
furi_event_loop_stop(app->event_loop);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is being run each time when the mutex gets released
|
||||||
|
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
|
||||||
|
EventLoopMutexApp* app = context;
|
||||||
|
furi_assert(object == app->worker_mutex);
|
||||||
|
|
||||||
|
// Take the mutex so that no-one can access the worker_result variable
|
||||||
|
// IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
|
||||||
|
// must NOT ever block. If it is possible that the mutex will be taken by
|
||||||
|
// others, then the event callback code must take it into account.
|
||||||
|
furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
|
||||||
|
// Access the worker_result variable and print it.
|
||||||
|
FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
|
||||||
|
// Release the mutex, enabling the worker thread to continue when it's ready
|
||||||
|
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||||
|
// Subscribe for the mutex release events again, since we were unsubscribed automatically
|
||||||
|
// before processing the event.
|
||||||
|
furi_event_loop_subscribe_mutex(
|
||||||
|
app->event_loop,
|
||||||
|
app->worker_mutex,
|
||||||
|
MUTEX_EVENT_AND_FLAGS,
|
||||||
|
event_loop_mutex_app_event_callback,
|
||||||
|
app);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
||||||
|
EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
|
||||||
|
|
||||||
|
// Create an event loop instance.
|
||||||
|
app->event_loop = furi_event_loop_alloc();
|
||||||
|
// Create a worker thread instance.
|
||||||
|
app->worker_thread = furi_thread_alloc_ex(
|
||||||
|
"EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
|
||||||
|
// Create a mutex instance.
|
||||||
|
app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
|
// Subscribe for the mutex release events.
|
||||||
|
// Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
|
||||||
|
// from events before entering the event processing callback. This is necessary in order to not
|
||||||
|
// trigger on events caused by releasing the mutex in the callback.
|
||||||
|
furi_event_loop_subscribe_mutex(
|
||||||
|
app->event_loop,
|
||||||
|
app->worker_mutex,
|
||||||
|
MUTEX_EVENT_AND_FLAGS,
|
||||||
|
event_loop_mutex_app_event_callback,
|
||||||
|
app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
|
||||||
|
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
|
||||||
|
// Delete all instances
|
||||||
|
furi_thread_free(app->worker_thread);
|
||||||
|
furi_mutex_free(app->worker_mutex);
|
||||||
|
furi_event_loop_free(app->event_loop);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
|
||||||
|
furi_thread_start(app->worker_thread);
|
||||||
|
furi_event_loop_run(app->event_loop);
|
||||||
|
furi_thread_join(app->worker_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The application's entry point - referenced in application.fam
|
||||||
|
int32_t example_event_loop_mutex_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
EventLoopMutexApp* app = event_loop_mutex_app_alloc();
|
||||||
|
event_loop_mutex_app_run(app);
|
||||||
|
event_loop_mutex_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* @file example_event_loop_stream_buffer.c
|
||||||
|
* @brief Example application that demonstrates the FuriEventLoop and FuriStreamBuffer integration.
|
||||||
|
*
|
||||||
|
* This application simulates a use case where some data data stream comes from a separate thread (or hardware)
|
||||||
|
* and a stream buffer is used to act as an intermediate buffer. The worker thread produces 10 iterations of 32
|
||||||
|
* bytes of simulated data, and each time when the buffer is half-filled, the data is taken out of it and printed
|
||||||
|
* to the debug output. After completing all iterations, the application exits.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_random.h>
|
||||||
|
|
||||||
|
#define TAG "ExampleEventLoopStreamBuffer"
|
||||||
|
|
||||||
|
#define WORKER_ITERATION_COUNT (10)
|
||||||
|
|
||||||
|
#define STREAM_BUFFER_SIZE (32)
|
||||||
|
#define STREAM_BUFFER_TRIG_LEVEL (STREAM_BUFFER_SIZE / 2)
|
||||||
|
#define STREAM_BUFFER_EVENT_AND_FLAGS (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriThread* worker_thread;
|
||||||
|
FuriStreamBuffer* stream_buffer;
|
||||||
|
} EventLoopStreamBufferApp;
|
||||||
|
|
||||||
|
// This funciton is being run in a separate thread to simulate data coming from a producer thread or some device.
|
||||||
|
static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopStreamBufferApp* app = context;
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Worker thread started");
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||||
|
// Produce 32 bytes of simulated data.
|
||||||
|
for(uint32_t j = 0; j < STREAM_BUFFER_SIZE; ++j) {
|
||||||
|
// Simulate incoming data by generating a random byte.
|
||||||
|
uint8_t data = furi_hal_random_get() % 0xFF;
|
||||||
|
// Put the byte in the buffer. Depending on the use case, it may or may be not acceptable
|
||||||
|
// to wait for free space to become available.
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_send(app->stream_buffer, &data, 1, FuriWaitForever) == 1);
|
||||||
|
// Delay between 30 and 50 ms to slow down the output for clarity.
|
||||||
|
furi_delay_ms(30 + furi_hal_random_get() % 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||||
|
// Request the event loop to stop
|
||||||
|
furi_event_loop_stop(app->event_loop);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
||||||
|
static bool
|
||||||
|
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopStreamBufferApp* app = context;
|
||||||
|
|
||||||
|
furi_assert(object == app->stream_buffer);
|
||||||
|
|
||||||
|
// Temporary buffer that can hold at most half of the stream buffer's capacity.
|
||||||
|
uint8_t data[STREAM_BUFFER_TRIG_LEVEL];
|
||||||
|
// Receive the data. It is guaranteed that the amount of data in the buffer will be equal to
|
||||||
|
// or greater than the trigger level, therefore, no waiting delay is necessary.
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_receive(app->stream_buffer, data, sizeof(data), 0) == sizeof(data));
|
||||||
|
|
||||||
|
// Format the data for printing and print it to the debug output.
|
||||||
|
FuriString* tmp_str = furi_string_alloc();
|
||||||
|
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||||
|
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||||
|
furi_string_free(tmp_str);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
||||||
|
EventLoopStreamBufferApp* app = malloc(sizeof(EventLoopStreamBufferApp));
|
||||||
|
|
||||||
|
// Create an event loop instance.
|
||||||
|
app->event_loop = furi_event_loop_alloc();
|
||||||
|
// Create a worker thread instance.
|
||||||
|
app->worker_thread = furi_thread_alloc_ex(
|
||||||
|
"EventLoopStreamBufferWorker", 1024, event_loop_stream_buffer_app_worker_thread, app);
|
||||||
|
// Create a stream_buffer instance.
|
||||||
|
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRIG_LEVEL);
|
||||||
|
// Subscribe for the stream buffer IN events in edge triggered mode.
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
app->event_loop,
|
||||||
|
app->stream_buffer,
|
||||||
|
STREAM_BUFFER_EVENT_AND_FLAGS,
|
||||||
|
event_loop_stream_buffer_app_event_callback,
|
||||||
|
app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_stream_buffer_app_free(EventLoopStreamBufferApp* app) {
|
||||||
|
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||||
|
// Delete all instances
|
||||||
|
furi_thread_free(app->worker_thread);
|
||||||
|
furi_stream_buffer_free(app->stream_buffer);
|
||||||
|
furi_event_loop_free(app->event_loop);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_stream_buffer_app_run(EventLoopStreamBufferApp* app) {
|
||||||
|
furi_thread_start(app->worker_thread);
|
||||||
|
furi_event_loop_run(app->event_loop);
|
||||||
|
furi_thread_join(app->worker_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The application's entry point - referenced in application.fam
|
||||||
|
int32_t example_event_loop_stream_buffer_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
EventLoopStreamBufferApp* app = event_loop_stream_buffer_app_alloc();
|
||||||
|
event_loop_stream_buffer_app_run(app);
|
||||||
|
event_loop_stream_buffer_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* @file example_event_loop_timer.c
|
||||||
|
* @brief Example application that demonstrates FuriEventLoop's software timer capability.
|
||||||
|
*
|
||||||
|
* This application prints a countdown from 10 to 0 to the debug output and then exits.
|
||||||
|
* Despite only one timer being used in this example for clarity, an event loop instance can have
|
||||||
|
* an arbitrary number of independent timers of any type (periodic or one-shot).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
#define TAG "ExampleEventLoopTimer"
|
||||||
|
|
||||||
|
#define COUNTDOWN_START_VALUE (10)
|
||||||
|
#define COUNTDOWN_INTERVAL_MS (1000)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriEventLoopTimer* timer;
|
||||||
|
uint32_t countdown_value;
|
||||||
|
} EventLoopTimerApp;
|
||||||
|
|
||||||
|
// This function is called each time the timer expires (i.e. once per 1000 ms (1s) in this example)
|
||||||
|
static void event_loop_timer_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopTimerApp* app = context;
|
||||||
|
|
||||||
|
// Print the countdown value
|
||||||
|
FURI_LOG_I(TAG, "T-00:00:%02lu", app->countdown_value);
|
||||||
|
|
||||||
|
if(app->countdown_value == 0) {
|
||||||
|
// If the countdown reached 0, print the final line and stop the event loop
|
||||||
|
FURI_LOG_I(TAG, "Blast off to adventure!");
|
||||||
|
// After this call, the control will be returned back to event_loop_timers_app_run()
|
||||||
|
furi_event_loop_stop(app->event_loop);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Decrement the countdown value
|
||||||
|
app->countdown_value -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopTimerApp* event_loop_timer_app_alloc(void) {
|
||||||
|
EventLoopTimerApp* app = malloc(sizeof(EventLoopTimerApp));
|
||||||
|
|
||||||
|
// Create an event loop instance.
|
||||||
|
app->event_loop = furi_event_loop_alloc();
|
||||||
|
// Create a software timer instance.
|
||||||
|
// The timer is bound to the event loop instance and will execute in its context.
|
||||||
|
// Here, the timer type is periodic, i.e. it will restart automatically after expiring.
|
||||||
|
app->timer = furi_event_loop_timer_alloc(
|
||||||
|
app->event_loop, event_loop_timer_callback, FuriEventLoopTimerTypePeriodic, app);
|
||||||
|
// The countdown value will be tracked in this variable.
|
||||||
|
app->countdown_value = COUNTDOWN_START_VALUE;
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_timer_app_free(EventLoopTimerApp* app) {
|
||||||
|
// IMPORTANT: All event loop timers MUST be deleted BEFORE deleting the event loop itself.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_timer_free(app->timer);
|
||||||
|
// With all timers deleted, it's safe to delete the event loop.
|
||||||
|
furi_event_loop_free(app->event_loop);
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_timer_app_run(EventLoopTimerApp* app) {
|
||||||
|
FURI_LOG_I(TAG, "All systems go! Prepare for countdown!");
|
||||||
|
|
||||||
|
// Timers can be started either before the event loop is run, or in any
|
||||||
|
// callback function called by a running event loop.
|
||||||
|
furi_event_loop_timer_start(app->timer, COUNTDOWN_INTERVAL_MS);
|
||||||
|
// This call will block until furi_event_loop_stop() is called.
|
||||||
|
furi_event_loop_run(app->event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The application's entry point - referenced in application.fam
|
||||||
|
int32_t example_event_loop_timer_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
EventLoopTimerApp* app = event_loop_timer_app_alloc();
|
||||||
|
event_loop_timer_app_run(app);
|
||||||
|
event_loop_timer_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
App(
|
||||||
|
appid="example_view_dispatcher",
|
||||||
|
name="Example: ViewDispatcher",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="example_view_dispatcher_app",
|
||||||
|
requires=["gui"],
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
|
@ -0,0 +1,173 @@
|
||||||
|
/**
|
||||||
|
* @file example_view_dispatcher.c
|
||||||
|
* @brief Example application demonstrating the usage of the ViewDispatcher library.
|
||||||
|
*
|
||||||
|
* This application can display one of two views: either a Widget or a Submenu.
|
||||||
|
* Each view has its own way of switching to another one:
|
||||||
|
*
|
||||||
|
* - A center button in the Widget view.
|
||||||
|
* - A submenu item in the Submenu view
|
||||||
|
*
|
||||||
|
* Press either to switch to a different view. Press Back to exit the application.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
|
||||||
|
// Enumeration of the view indexes.
|
||||||
|
typedef enum {
|
||||||
|
ViewIndexWidget,
|
||||||
|
ViewIndexSubmenu,
|
||||||
|
ViewIndexCount,
|
||||||
|
} ViewIndex;
|
||||||
|
|
||||||
|
// Enumeration of submenu items.
|
||||||
|
typedef enum {
|
||||||
|
SubmenuIndexNothing,
|
||||||
|
SubmenuIndexSwitchView,
|
||||||
|
} SubmenuIndex;
|
||||||
|
|
||||||
|
// Main application structure.
|
||||||
|
typedef struct {
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Widget* widget;
|
||||||
|
Submenu* submenu;
|
||||||
|
} ExampleViewDispatcherApp;
|
||||||
|
|
||||||
|
// This function is called when the user has pressed the Back key.
|
||||||
|
static bool example_view_dispatcher_app_navigation_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleViewDispatcherApp* app = context;
|
||||||
|
// Back means exit the application, which can be done by stopping the ViewDispatcher.
|
||||||
|
view_dispatcher_stop(app->view_dispatcher);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called when there are custom events to process.
|
||||||
|
static bool example_view_dispatcher_app_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleViewDispatcherApp* app = context;
|
||||||
|
// The event numerical value can mean different things (the application is responsible to uphold its chosen convention)
|
||||||
|
// In this example, the only possible meaning is the view index to switch to.
|
||||||
|
furi_assert(event < ViewIndexCount);
|
||||||
|
// Switch to the requested view.
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called when the user presses the "Switch View" button on the Widget view.
|
||||||
|
static void example_view_dispatcher_app_button_callback(
|
||||||
|
GuiButtonType button_type,
|
||||||
|
InputType input_type,
|
||||||
|
void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleViewDispatcherApp* app = context;
|
||||||
|
// Only request the view switch if the user short-presses the Center button.
|
||||||
|
if(button_type == GuiButtonTypeCenter && input_type == InputTypeShort) {
|
||||||
|
// Request switch to the Submenu view via the custom event queue.
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexSubmenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called when the user activates the "Switch View" submenu item.
|
||||||
|
static void example_view_dispatcher_app_submenu_callback(void* context, uint32_t index) {
|
||||||
|
furi_assert(context);
|
||||||
|
ExampleViewDispatcherApp* app = context;
|
||||||
|
// Only request the view switch if the user activates the "Switch View" item.
|
||||||
|
if(index == SubmenuIndexSwitchView) {
|
||||||
|
// Request switch to the Widget view via the custom event queue.
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application constructor function.
|
||||||
|
static ExampleViewDispatcherApp* example_view_dispatcher_app_alloc() {
|
||||||
|
ExampleViewDispatcherApp* app = malloc(sizeof(ExampleViewDispatcherApp));
|
||||||
|
// Access the GUI API instance.
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
// Create and initialize the Widget view.
|
||||||
|
app->widget = widget_alloc();
|
||||||
|
widget_add_string_multiline_element(
|
||||||
|
app->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, "Press the Button below");
|
||||||
|
widget_add_button_element(
|
||||||
|
app->widget,
|
||||||
|
GuiButtonTypeCenter,
|
||||||
|
"Switch View",
|
||||||
|
example_view_dispatcher_app_button_callback,
|
||||||
|
app);
|
||||||
|
// Create and initialize the Submenu view.
|
||||||
|
app->submenu = submenu_alloc();
|
||||||
|
submenu_add_item(app->submenu, "Do Nothing", SubmenuIndexNothing, NULL, NULL);
|
||||||
|
submenu_add_item(
|
||||||
|
app->submenu,
|
||||||
|
"Switch View",
|
||||||
|
SubmenuIndexSwitchView,
|
||||||
|
example_view_dispatcher_app_submenu_callback,
|
||||||
|
app);
|
||||||
|
// Create the ViewDispatcher instance.
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
// Let the GUI know about this ViewDispatcher instance.
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||||
|
// Register the views within the ViewDispatcher instance. This alone will not show any of them on the screen.
|
||||||
|
// Each view must have its own index to refer to it later (it is best done via an enumeration as shown here).
|
||||||
|
view_dispatcher_add_view(app->view_dispatcher, ViewIndexWidget, widget_get_view(app->widget));
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, ViewIndexSubmenu, submenu_get_view(app->submenu));
|
||||||
|
// Set the custom event callback. It will be called each time a custom event is scheduled
|
||||||
|
// using the view_dispatcher_send_custom_callback() function.
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
app->view_dispatcher, example_view_dispatcher_app_custom_event_callback);
|
||||||
|
// Set the navigation, or back button callback. It will be called if the user pressed the Back button
|
||||||
|
// and the event was not handled in the currently displayed view.
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, example_view_dispatcher_app_navigation_callback);
|
||||||
|
// The context will be passed to the callbacks as a parameter, so we have access to our application object.
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application destructor function.
|
||||||
|
static void example_view_dispatcher_app_free(ExampleViewDispatcherApp* app) {
|
||||||
|
// All views must be un-registered (removed) from a ViewDispatcher instance
|
||||||
|
// before deleting it. Failure to do so will result in a crash.
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexWidget);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexSubmenu);
|
||||||
|
// Now it is safe to delete the ViewDispatcher instance.
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
// Delete the views
|
||||||
|
widget_free(app->widget);
|
||||||
|
submenu_free(app->submenu);
|
||||||
|
// End access to hte the GUI API.
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
// Free the remaining memory.
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void example_view_dispatcher_app_run(ExampleViewDispatcherApp* app) {
|
||||||
|
// Display the Widget view on the screen.
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ViewIndexWidget);
|
||||||
|
// This function will block until view_dispatcher_stop() is called.
|
||||||
|
// Internally, it uses a FuriEventLoop (see FuriEventLoop examples for more info on this).
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************
|
||||||
|
* vvv START HERE vvv
|
||||||
|
*
|
||||||
|
* The application's entry point - referenced in application.fam
|
||||||
|
*******************************************************************/
|
||||||
|
int32_t example_view_dispatcher_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
ExampleViewDispatcherApp* app = example_view_dispatcher_app_alloc();
|
||||||
|
example_view_dispatcher_app_run(app);
|
||||||
|
example_view_dispatcher_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
App(
|
||||||
|
appid="example_view_holder",
|
||||||
|
name="Example: ViewHolder",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="example_view_holder_app",
|
||||||
|
requires=["gui"],
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
|
@ -0,0 +1,78 @@
|
||||||
|
/**
|
||||||
|
* @file example_view_holder.c
|
||||||
|
* @brief Example application demonstrating the usage of the ViewHolder library.
|
||||||
|
*
|
||||||
|
* This application will display a text box with some scrollable text in it.
|
||||||
|
* Press the Back key to exit the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_holder.h>
|
||||||
|
#include <gui/modules/text_box.h>
|
||||||
|
|
||||||
|
#include <api_lock.h>
|
||||||
|
|
||||||
|
// This function will be called when the user presses the Back button.
|
||||||
|
static void example_view_holder_back_callback(void* context) {
|
||||||
|
FuriApiLock exit_lock = context;
|
||||||
|
// Unlock the exit lock, thus enabling the app to exit.
|
||||||
|
api_lock_unlock(exit_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t example_view_holder_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
// Access the GUI API instance.
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
// Create a TextBox view. The Gui object only accepts
|
||||||
|
// ViewPort instances, so we will need to address that later.
|
||||||
|
TextBox* text_box = text_box_alloc();
|
||||||
|
// Set some text so that the text box is not empty.
|
||||||
|
text_box_set_text(
|
||||||
|
text_box,
|
||||||
|
"ViewHolder is being used\n"
|
||||||
|
"to show this TextBox view.\n\n"
|
||||||
|
"Scroll down to see more.\n\n\n"
|
||||||
|
"Press \"Back\" to exit.");
|
||||||
|
|
||||||
|
// Create a ViewHolder instance. It will serve as an adapter to convert
|
||||||
|
// between the View type provided by the TextBox view and the ViewPort type
|
||||||
|
// that the GUI can actually display.
|
||||||
|
ViewHolder* view_holder = view_holder_alloc();
|
||||||
|
// Let the GUI know about this ViewHolder instance.
|
||||||
|
view_holder_attach_to_gui(view_holder, gui);
|
||||||
|
// Set the view that we want to display.
|
||||||
|
view_holder_set_view(view_holder, text_box_get_view(text_box));
|
||||||
|
|
||||||
|
// The part below is not really related to this example, but is necessary for it to function.
|
||||||
|
// We need to somehow stall the application thread so that the view stays on the screen (otherwise
|
||||||
|
// the app will just exit and won't display anything) and at the same time we need a way to quit out
|
||||||
|
// of the application.
|
||||||
|
|
||||||
|
// In this example, a simple FuriApiLock instance is used. A real-world application is likely to have some
|
||||||
|
// kind of event handling loop here instead. (see the ViewDispatcher example or one of FuriEventLoop
|
||||||
|
// examples for that).
|
||||||
|
|
||||||
|
// Create a pre-locked FuriApiLock instance.
|
||||||
|
FuriApiLock exit_lock = api_lock_alloc_locked();
|
||||||
|
// Set a Back event callback for the ViewHolder instance. It will be called when the user
|
||||||
|
// presses the Back button. We pass the exit lock instance as the context to be able to access
|
||||||
|
// it inside the callback function.
|
||||||
|
view_holder_set_back_callback(view_holder, example_view_holder_back_callback, exit_lock);
|
||||||
|
|
||||||
|
// This call will block the application thread from running until the exit lock gets unlocked somehow
|
||||||
|
// (the only way it can happen in this example is via the back callback).
|
||||||
|
api_lock_wait_unlock_and_free(exit_lock);
|
||||||
|
|
||||||
|
// The back key has been pressed, which unlocked the exit lock. The application is about to exit.
|
||||||
|
|
||||||
|
// The view must be removed from a ViewHolder instance before deleting it.
|
||||||
|
view_holder_set_view(view_holder, NULL);
|
||||||
|
// Delete everything to prevent memory leaks.
|
||||||
|
view_holder_free(view_holder);
|
||||||
|
text_box_free(text_box);
|
||||||
|
// End access to the GUI API.
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -30,7 +30,6 @@ ArchiveApp* archive_alloc(void) {
|
||||||
archive->view_dispatcher = view_dispatcher_alloc();
|
archive->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
|
||||||
ViewDispatcher* view_dispatcher = archive->view_dispatcher;
|
ViewDispatcher* view_dispatcher = archive->view_dispatcher;
|
||||||
view_dispatcher_enable_queue(view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(view_dispatcher, archive);
|
view_dispatcher_set_event_callback_context(view_dispatcher, archive);
|
||||||
view_dispatcher_set_custom_event_callback(view_dispatcher, archive_custom_event_callback);
|
view_dispatcher_set_custom_event_callback(view_dispatcher, archive_custom_event_callback);
|
||||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, archive_back_event_callback);
|
view_dispatcher_set_navigation_event_callback(view_dispatcher, archive_back_event_callback);
|
||||||
|
|
|
@ -112,8 +112,6 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
||||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
|
|
||||||
app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
|
||||||
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
|
@ -32,7 +32,6 @@ GpioApp* gpio_app_alloc(void) {
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
|
|
@ -85,7 +85,6 @@ iButton* ibutton_alloc(void) {
|
||||||
ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton);
|
ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton);
|
||||||
|
|
||||||
ibutton->view_dispatcher = view_dispatcher_alloc();
|
ibutton->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(ibutton->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(ibutton->view_dispatcher, ibutton);
|
view_dispatcher_set_event_callback_context(ibutton->view_dispatcher, ibutton);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
ibutton->view_dispatcher, ibutton_custom_event_callback);
|
ibutton->view_dispatcher, ibutton_custom_event_callback);
|
||||||
|
|
|
@ -150,7 +150,6 @@ static InfraredApp* infrared_alloc(void) {
|
||||||
infrared->gui = furi_record_open(RECORD_GUI);
|
infrared->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
||||||
view_dispatcher_enable_queue(view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
|
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
|
||||||
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
|
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
|
||||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
|
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
|
||||||
|
|
|
@ -77,7 +77,6 @@ static LfRfid* lfrfid_alloc(void) {
|
||||||
|
|
||||||
lfrfid->view_dispatcher = view_dispatcher_alloc();
|
lfrfid->view_dispatcher = view_dispatcher_alloc();
|
||||||
lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid);
|
lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid);
|
||||||
view_dispatcher_enable_queue(lfrfid->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid);
|
view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback);
|
lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||||
|
|
|
@ -41,7 +41,6 @@ NfcApp* nfc_app_alloc(void) {
|
||||||
|
|
||||||
instance->view_dispatcher = view_dispatcher_alloc();
|
instance->view_dispatcher = view_dispatcher_alloc();
|
||||||
instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance);
|
instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance);
|
||||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
|
view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
instance->view_dispatcher, nfc_custom_event_callback);
|
instance->view_dispatcher, nfc_custom_event_callback);
|
||||||
|
|
|
@ -97,7 +97,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||||
|
|
||||||
// View Dispatcher
|
// View Dispatcher
|
||||||
subghz->view_dispatcher = view_dispatcher_alloc();
|
subghz->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(subghz->view_dispatcher);
|
|
||||||
|
|
||||||
subghz->scene_manager = scene_manager_alloc(&subghz_scene_handlers, subghz);
|
subghz->scene_manager = scene_manager_alloc(&subghz_scene_handlers, subghz);
|
||||||
view_dispatcher_set_event_callback_context(subghz->view_dispatcher, subghz);
|
view_dispatcher_set_event_callback_context(subghz->view_dispatcher, subghz);
|
||||||
|
|
|
@ -29,7 +29,6 @@ U2fApp* u2f_app_alloc(void) {
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&u2f_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&u2f_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_tick_event_callback(
|
view_dispatcher_set_tick_event_callback(
|
||||||
app->view_dispatcher, u2f_app_tick_event_callback, 500);
|
app->view_dispatcher, u2f_app_tick_event_callback, 500);
|
||||||
|
|
|
@ -265,7 +265,6 @@ static Desktop* desktop_alloc(void) {
|
||||||
desktop->view_dispatcher = view_dispatcher_alloc();
|
desktop->view_dispatcher = view_dispatcher_alloc();
|
||||||
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
|
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
|
||||||
|
|
||||||
view_dispatcher_enable_queue(desktop->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(
|
view_dispatcher_attach_to_gui(
|
||||||
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
|
desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
|
||||||
view_dispatcher_set_tick_event_callback(
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
|
|
@ -49,12 +49,11 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
|
||||||
file_browser_start(file_browser, data->preselected_filename);
|
file_browser_start(file_browser, data->preselected_filename);
|
||||||
|
|
||||||
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
|
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
|
||||||
view_holder_start(view_holder);
|
|
||||||
api_lock_wait_unlock(file_browser_context->lock);
|
api_lock_wait_unlock(file_browser_context->lock);
|
||||||
|
|
||||||
ret = file_browser_context->result;
|
ret = file_browser_context->result;
|
||||||
|
|
||||||
view_holder_stop(view_holder);
|
view_holder_set_view(view_holder, NULL);
|
||||||
view_holder_free(view_holder);
|
view_holder_free(view_holder);
|
||||||
file_browser_stop(file_browser);
|
file_browser_stop(file_browser);
|
||||||
file_browser_free(file_browser);
|
file_browser_free(file_browser);
|
||||||
|
|
|
@ -88,12 +88,11 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa
|
||||||
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
|
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
|
||||||
|
|
||||||
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
|
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
|
||||||
view_holder_start(view_holder);
|
|
||||||
api_lock_wait_unlock(message_context->lock);
|
api_lock_wait_unlock(message_context->lock);
|
||||||
|
|
||||||
ret = message_context->result;
|
ret = message_context->result;
|
||||||
|
|
||||||
view_holder_stop(view_holder);
|
view_holder_set_view(view_holder, NULL);
|
||||||
view_holder_free(view_holder);
|
view_holder_free(view_holder);
|
||||||
dialog_ex_free(dialog_ex);
|
dialog_ex_free(dialog_ex);
|
||||||
api_lock_free(message_context->lock);
|
api_lock_free(message_context->lock);
|
||||||
|
|
|
@ -192,8 +192,8 @@ static void dolphin_update_clear_limits_timer_period(void* context) {
|
||||||
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
|
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dolphin_process_event(FuriMessageQueue* queue, void* context) {
|
static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
|
||||||
UNUSED(queue);
|
UNUSED(object);
|
||||||
|
|
||||||
Dolphin* dolphin = context;
|
Dolphin* dolphin = context;
|
||||||
DolphinEvent event;
|
DolphinEvent event;
|
||||||
|
@ -280,7 +280,7 @@ int32_t dolphin_srv(void* p) {
|
||||||
|
|
||||||
dolphin_init_state(dolphin);
|
dolphin_init_state(dolphin);
|
||||||
|
|
||||||
furi_event_loop_message_queue_subscribe(
|
furi_event_loop_subscribe_message_queue(
|
||||||
dolphin->event_loop,
|
dolphin->event_loop,
|
||||||
dolphin->event_queue,
|
dolphin->event_queue,
|
||||||
FuriEventLoopEventIn,
|
FuriEventLoopEventIn,
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#define TAG "ViewDispatcher"
|
#define TAG "ViewDispatcher"
|
||||||
|
|
||||||
|
#define VIEW_DISPATCHER_QUEUE_LEN (16U)
|
||||||
|
|
||||||
ViewDispatcher* view_dispatcher_alloc(void) {
|
ViewDispatcher* view_dispatcher_alloc(void) {
|
||||||
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
|
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
|
||||||
|
|
||||||
|
@ -14,6 +16,26 @@ ViewDispatcher* view_dispatcher_alloc(void) {
|
||||||
|
|
||||||
ViewDict_init(view_dispatcher->views);
|
ViewDict_init(view_dispatcher->views);
|
||||||
|
|
||||||
|
view_dispatcher->event_loop = furi_event_loop_alloc();
|
||||||
|
|
||||||
|
view_dispatcher->input_queue =
|
||||||
|
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent));
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
view_dispatcher->event_loop,
|
||||||
|
view_dispatcher->input_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
view_dispatcher_run_input_callback,
|
||||||
|
view_dispatcher);
|
||||||
|
|
||||||
|
view_dispatcher->event_queue =
|
||||||
|
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(uint32_t));
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
view_dispatcher->event_loop,
|
||||||
|
view_dispatcher->event_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
view_dispatcher_run_event_callback,
|
||||||
|
view_dispatcher);
|
||||||
|
|
||||||
return view_dispatcher;
|
return view_dispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,44 +51,19 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
|
||||||
// Free ViewPort
|
// Free ViewPort
|
||||||
view_port_free(view_dispatcher->view_port);
|
view_port_free(view_dispatcher->view_port);
|
||||||
// Free internal queue
|
// Free internal queue
|
||||||
if(view_dispatcher->input_queue) {
|
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->input_queue);
|
||||||
furi_event_loop_message_queue_unsubscribe(
|
furi_event_loop_unsubscribe(view_dispatcher->event_loop, view_dispatcher->event_queue);
|
||||||
view_dispatcher->event_loop, view_dispatcher->input_queue);
|
|
||||||
furi_message_queue_free(view_dispatcher->input_queue);
|
furi_message_queue_free(view_dispatcher->input_queue);
|
||||||
}
|
furi_message_queue_free(view_dispatcher->event_queue);
|
||||||
if(view_dispatcher->event_queue) {
|
|
||||||
furi_event_loop_message_queue_unsubscribe(
|
furi_event_loop_free(view_dispatcher->event_loop);
|
||||||
view_dispatcher->event_loop, view_dispatcher->event_queue);
|
|
||||||
furi_message_queue_free(view_dispatcher->event_queue);
|
|
||||||
}
|
|
||||||
if(view_dispatcher->event_loop) {
|
|
||||||
furi_event_loop_free(view_dispatcher->event_loop);
|
|
||||||
}
|
|
||||||
// Free dispatcher
|
// Free dispatcher
|
||||||
free(view_dispatcher);
|
free(view_dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
|
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher) {
|
||||||
furi_check(view_dispatcher);
|
UNUSED(view_dispatcher);
|
||||||
furi_check(view_dispatcher->event_loop == NULL);
|
|
||||||
|
|
||||||
view_dispatcher->event_loop = furi_event_loop_alloc();
|
|
||||||
|
|
||||||
view_dispatcher->input_queue = furi_message_queue_alloc(16, sizeof(InputEvent));
|
|
||||||
furi_event_loop_message_queue_subscribe(
|
|
||||||
view_dispatcher->event_loop,
|
|
||||||
view_dispatcher->input_queue,
|
|
||||||
FuriEventLoopEventIn,
|
|
||||||
view_dispatcher_run_input_callback,
|
|
||||||
view_dispatcher);
|
|
||||||
|
|
||||||
view_dispatcher->event_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
|
|
||||||
furi_event_loop_message_queue_subscribe(
|
|
||||||
view_dispatcher->event_loop,
|
|
||||||
view_dispatcher->event_queue,
|
|
||||||
FuriEventLoopEventIn,
|
|
||||||
view_dispatcher_run_event_callback,
|
|
||||||
view_dispatcher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_dispatcher_set_navigation_event_callback(
|
void view_dispatcher_set_navigation_event_callback(
|
||||||
|
@ -99,14 +96,12 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
|
||||||
|
|
||||||
FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) {
|
FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher) {
|
||||||
furi_check(view_dispatcher);
|
furi_check(view_dispatcher);
|
||||||
furi_check(view_dispatcher->event_loop);
|
|
||||||
|
|
||||||
return view_dispatcher->event_loop;
|
return view_dispatcher->event_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||||
furi_check(view_dispatcher);
|
furi_check(view_dispatcher);
|
||||||
furi_check(view_dispatcher->event_loop);
|
|
||||||
|
|
||||||
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
|
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
|
||||||
view_dispatcher->tick_period;
|
view_dispatcher->tick_period;
|
||||||
|
@ -134,7 +129,6 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||||
|
|
||||||
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
|
void view_dispatcher_stop(ViewDispatcher* view_dispatcher) {
|
||||||
furi_check(view_dispatcher);
|
furi_check(view_dispatcher);
|
||||||
furi_check(view_dispatcher->event_loop);
|
|
||||||
furi_event_loop_stop(view_dispatcher->event_loop);
|
furi_event_loop_stop(view_dispatcher->event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,13 +236,9 @@ void view_dispatcher_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
|
||||||
void view_dispatcher_input_callback(InputEvent* event, void* context) {
|
void view_dispatcher_input_callback(InputEvent* event, void* context) {
|
||||||
ViewDispatcher* view_dispatcher = context;
|
ViewDispatcher* view_dispatcher = context;
|
||||||
if(view_dispatcher->input_queue) {
|
furi_check(
|
||||||
furi_check(
|
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
|
||||||
furi_message_queue_put(view_dispatcher->input_queue, event, FuriWaitForever) ==
|
FuriStatusOk);
|
||||||
FuriStatusOk);
|
|
||||||
} else {
|
|
||||||
view_dispatcher_handle_input(view_dispatcher, event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
|
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
|
||||||
|
@ -328,7 +318,6 @@ void view_dispatcher_handle_custom_event(ViewDispatcher* view_dispatcher, uint32
|
||||||
|
|
||||||
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
|
void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t event) {
|
||||||
furi_check(view_dispatcher);
|
furi_check(view_dispatcher);
|
||||||
furi_check(view_dispatcher->event_loop);
|
|
||||||
|
|
||||||
furi_check(
|
furi_check(
|
||||||
furi_message_queue_put(view_dispatcher->event_queue, &event, FuriWaitForever) ==
|
furi_message_queue_put(view_dispatcher->event_queue, &event, FuriWaitForever) ==
|
||||||
|
@ -364,9 +353,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
|
||||||
view_port_update(view_dispatcher->view_port);
|
view_port_update(view_dispatcher->view_port);
|
||||||
} else {
|
} else {
|
||||||
view_port_enabled_set(view_dispatcher->view_port, false);
|
view_port_enabled_set(view_dispatcher->view_port, false);
|
||||||
if(view_dispatcher->event_loop) {
|
view_dispatcher_stop(view_dispatcher);
|
||||||
view_dispatcher_stop(view_dispatcher);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,10 +368,10 @@ void view_dispatcher_update(View* view, void* context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context) {
|
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
ViewDispatcher* instance = context;
|
ViewDispatcher* instance = context;
|
||||||
furi_assert(instance->event_queue == queue);
|
furi_assert(instance->event_queue == object);
|
||||||
|
|
||||||
uint32_t event;
|
uint32_t event;
|
||||||
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
|
||||||
|
@ -393,10 +380,10 @@ bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context) {
|
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
ViewDispatcher* instance = context;
|
ViewDispatcher* instance = context;
|
||||||
furi_assert(instance->input_queue == queue);
|
furi_assert(instance->input_queue == object);
|
||||||
|
|
||||||
InputEvent input;
|
InputEvent input;
|
||||||
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
* @file view_dispatcher.h
|
* @file view_dispatcher.h
|
||||||
* @brief GUI: ViewDispatcher API
|
* @brief GUI: ViewDispatcher API
|
||||||
*
|
*
|
||||||
|
* ViewDispatcher is used to connect several Views to a Gui instance, switch between them and handle various events.
|
||||||
|
* This is useful in applications featuring an advanced graphical user interface.
|
||||||
|
*
|
||||||
|
* Internally, ViewDispatcher employs a FuriEventLoop instance together with two separate
|
||||||
|
* message queues for input and custom event handling. See FuriEventLoop for more information.
|
||||||
|
*
|
||||||
|
* If no multi-view or complex event handling capabilities are required, consider using ViewHolder instead.
|
||||||
|
*
|
||||||
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
|
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -40,6 +48,9 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context);
|
||||||
ViewDispatcher* view_dispatcher_alloc(void);
|
ViewDispatcher* view_dispatcher_alloc(void);
|
||||||
|
|
||||||
/** Free ViewDispatcher instance
|
/** Free ViewDispatcher instance
|
||||||
|
*
|
||||||
|
* @warning All added views MUST be removed using view_dispatcher_remove_view()
|
||||||
|
* before calling this function.
|
||||||
*
|
*
|
||||||
* @param view_dispatcher pointer to ViewDispatcher
|
* @param view_dispatcher pointer to ViewDispatcher
|
||||||
*/
|
*/
|
||||||
|
@ -47,12 +58,13 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher);
|
||||||
|
|
||||||
/** Enable queue support
|
/** Enable queue support
|
||||||
*
|
*
|
||||||
* Allocates event_loop, input and event message queues. Must be used with
|
* @deprecated Do NOT use in new code and remove all calls to it from existing code.
|
||||||
* `view_dispatcher_run`
|
* The queue support is now always enabled during construction. If no queue support
|
||||||
|
* is required, consider using ViewHolder instead.
|
||||||
*
|
*
|
||||||
* @param view_dispatcher ViewDispatcher instance
|
* @param view_dispatcher ViewDispatcher instance
|
||||||
*/
|
*/
|
||||||
void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
|
FURI_DEPRECATED void view_dispatcher_enable_queue(ViewDispatcher* view_dispatcher);
|
||||||
|
|
||||||
/** Send custom event
|
/** Send custom event
|
||||||
*
|
*
|
||||||
|
@ -103,11 +115,11 @@ void view_dispatcher_set_event_callback_context(ViewDispatcher* view_dispatcher,
|
||||||
|
|
||||||
/** Get event_loop instance
|
/** Get event_loop instance
|
||||||
*
|
*
|
||||||
* event_loop instance is allocated on `view_dispatcher_enable_queue` and used
|
* Use the return value to connect additional supported primitives (message queues, timers, etc)
|
||||||
* in view_dispatcher_run.
|
* to this ViewDispatcher instance's event loop.
|
||||||
*
|
*
|
||||||
* You can add your objects into event_loop instance, but don't run the loop on
|
* @warning Do NOT call furi_event_loop_run() on the returned instance, it is done internally
|
||||||
* your side as it will cause issues with input processing on dispatcher stop.
|
* in the view_dispatcher_run() call.
|
||||||
*
|
*
|
||||||
* @param view_dispatcher ViewDispatcher instance
|
* @param view_dispatcher ViewDispatcher instance
|
||||||
*
|
*
|
||||||
|
@ -117,15 +129,14 @@ FuriEventLoop* view_dispatcher_get_event_loop(ViewDispatcher* view_dispatcher);
|
||||||
|
|
||||||
/** Run ViewDispatcher
|
/** Run ViewDispatcher
|
||||||
*
|
*
|
||||||
* Use only after queue enabled
|
* This function will start the event loop and block until view_dispatcher_stop() is called
|
||||||
|
* or the current thread receives a FuriSignalExit signal.
|
||||||
*
|
*
|
||||||
* @param view_dispatcher ViewDispatcher instance
|
* @param view_dispatcher ViewDispatcher instance
|
||||||
*/
|
*/
|
||||||
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
|
void view_dispatcher_run(ViewDispatcher* view_dispatcher);
|
||||||
|
|
||||||
/** Stop ViewDispatcher
|
/** Stop ViewDispatcher
|
||||||
*
|
|
||||||
* Use only after queue enabled
|
|
||||||
*
|
*
|
||||||
* @param view_dispatcher ViewDispatcher instance
|
* @param view_dispatcher ViewDispatcher instance
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -56,7 +56,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
|
||||||
void view_dispatcher_update(View* view, void* context);
|
void view_dispatcher_update(View* view, void* context);
|
||||||
|
|
||||||
/** ViewDispatcher run event loop event callback */
|
/** ViewDispatcher run event loop event callback */
|
||||||
bool view_dispatcher_run_event_callback(FuriMessageQueue* queue, void* context);
|
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
|
||||||
|
|
||||||
/** ViewDispatcher run event loop input callback */
|
/** ViewDispatcher run event loop input callback */
|
||||||
bool view_dispatcher_run_input_callback(FuriMessageQueue* queue, void* context);
|
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
|
||||||
|
|
|
@ -32,7 +32,8 @@ ViewHolder* view_holder_alloc(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_holder_free(ViewHolder* view_holder) {
|
void view_holder_free(ViewHolder* view_holder) {
|
||||||
furi_assert(view_holder);
|
furi_check(view_holder);
|
||||||
|
furi_check(view_holder->view == NULL);
|
||||||
|
|
||||||
if(view_holder->gui) {
|
if(view_holder->gui) {
|
||||||
gui_remove_view_port(view_holder->gui, view_holder->view_port);
|
gui_remove_view_port(view_holder->gui, view_holder->view_port);
|
||||||
|
@ -48,12 +49,14 @@ void view_holder_free(ViewHolder* view_holder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||||
furi_assert(view_holder);
|
furi_check(view_holder);
|
||||||
|
|
||||||
if(view_holder->view) {
|
if(view_holder->view) {
|
||||||
if(view_holder->view->exit_callback) {
|
while(view_holder->ongoing_input) {
|
||||||
view_holder->view->exit_callback(view_holder->view->context);
|
furi_delay_tick(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view_exit(view_holder->view);
|
||||||
view_set_update_callback(view_holder->view, NULL);
|
view_set_update_callback(view_holder->view, NULL);
|
||||||
view_set_update_callback_context(view_holder->view, NULL);
|
view_set_update_callback_context(view_holder->view, NULL);
|
||||||
}
|
}
|
||||||
|
@ -61,12 +64,23 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||||
view_holder->view = view;
|
view_holder->view = view;
|
||||||
|
|
||||||
if(view_holder->view) {
|
if(view_holder->view) {
|
||||||
|
const ViewPortOrientation orientation = (ViewPortOrientation)view->orientation;
|
||||||
|
furi_assert(orientation < ViewPortOrientationMAX);
|
||||||
|
if(view_port_get_orientation(view_holder->view_port) != orientation) {
|
||||||
|
view_port_set_orientation(view_holder->view_port, orientation);
|
||||||
|
// we just rotated input keys, now it's time to sacrifice some input
|
||||||
|
view_holder->ongoing_input = 0;
|
||||||
|
}
|
||||||
|
|
||||||
view_set_update_callback(view_holder->view, view_holder_update);
|
view_set_update_callback(view_holder->view, view_holder_update);
|
||||||
view_set_update_callback_context(view_holder->view, view_holder);
|
view_set_update_callback_context(view_holder->view, view_holder);
|
||||||
|
|
||||||
if(view_holder->view->enter_callback) {
|
view_enter(view_holder->view);
|
||||||
view_holder->view->enter_callback(view_holder->view->context);
|
view_port_enabled_set(view_holder->view_port, true);
|
||||||
}
|
view_port_update(view_holder->view_port);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
view_port_enabled_set(view_holder->view_port, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +88,7 @@ void view_holder_set_free_callback(
|
||||||
ViewHolder* view_holder,
|
ViewHolder* view_holder,
|
||||||
FreeCallback free_callback,
|
FreeCallback free_callback,
|
||||||
void* free_context) {
|
void* free_context) {
|
||||||
furi_assert(view_holder);
|
furi_check(view_holder);
|
||||||
view_holder->free_callback = free_callback;
|
view_holder->free_callback = free_callback;
|
||||||
view_holder->free_context = free_context;
|
view_holder->free_context = free_context;
|
||||||
}
|
}
|
||||||
|
@ -87,31 +101,22 @@ void view_holder_set_back_callback(
|
||||||
ViewHolder* view_holder,
|
ViewHolder* view_holder,
|
||||||
BackCallback back_callback,
|
BackCallback back_callback,
|
||||||
void* back_context) {
|
void* back_context) {
|
||||||
furi_assert(view_holder);
|
furi_check(view_holder);
|
||||||
view_holder->back_callback = back_callback;
|
view_holder->back_callback = back_callback;
|
||||||
view_holder->back_context = back_context;
|
view_holder->back_context = back_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
|
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
|
||||||
furi_assert(gui);
|
furi_check(view_holder);
|
||||||
furi_assert(view_holder);
|
furi_check(view_holder->gui == NULL);
|
||||||
view_holder->gui = gui;
|
furi_check(gui);
|
||||||
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
|
gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
|
||||||
}
|
view_holder->gui = gui;
|
||||||
|
|
||||||
void view_holder_start(ViewHolder* view_holder) {
|
|
||||||
view_port_enabled_set(view_holder->view_port, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void view_holder_stop(ViewHolder* view_holder) {
|
|
||||||
while(view_holder->ongoing_input)
|
|
||||||
furi_delay_tick(1);
|
|
||||||
view_port_enabled_set(view_holder->view_port, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void view_holder_update(View* view, void* context) {
|
void view_holder_update(View* view, void* context) {
|
||||||
furi_assert(view);
|
furi_check(view);
|
||||||
furi_assert(context);
|
furi_check(context);
|
||||||
|
|
||||||
ViewHolder* view_holder = context;
|
ViewHolder* view_holder = context;
|
||||||
if(view == view_holder->view) {
|
if(view == view_holder->view) {
|
||||||
|
@ -119,6 +124,18 @@ void view_holder_update(View* view, void* context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void view_holder_send_to_front(ViewHolder* view_holder) {
|
||||||
|
furi_check(view_holder);
|
||||||
|
furi_check(view_holder->gui);
|
||||||
|
gui_view_port_send_to_front(view_holder->gui, view_holder->view_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void view_holder_send_to_back(ViewHolder* view_holder) {
|
||||||
|
furi_check(view_holder);
|
||||||
|
furi_check(view_holder->gui);
|
||||||
|
gui_view_port_send_to_back(view_holder->gui, view_holder->view_port);
|
||||||
|
}
|
||||||
|
|
||||||
static void view_holder_draw_callback(Canvas* canvas, void* context) {
|
static void view_holder_draw_callback(Canvas* canvas, void* context) {
|
||||||
ViewHolder* view_holder = context;
|
ViewHolder* view_holder = context;
|
||||||
if(view_holder->view) {
|
if(view_holder->view) {
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
* @file view_holder.h
|
* @file view_holder.h
|
||||||
* @brief GUI: ViewHolder API
|
* @brief GUI: ViewHolder API
|
||||||
*
|
*
|
||||||
* @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time.
|
* ViewHolder is used to connect a single View to a Gui instance. This is useful in smaller applications
|
||||||
|
* with a simple user interface. If advanced view switching capabilites are required, consider using ViewDispatcher instead.
|
||||||
|
*
|
||||||
|
* @warning Views added to a ViewHolder MUST NOT be in a ViewStack at the same time.
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -22,7 +25,8 @@ typedef void (*FreeCallback)(void* free_context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Back callback type
|
* @brief Back callback type
|
||||||
* @warning comes from GUI thread
|
*
|
||||||
|
* @warning Will be called from the GUI thread
|
||||||
*/
|
*/
|
||||||
typedef void (*BackCallback)(void* back_context);
|
typedef void (*BackCallback)(void* back_context);
|
||||||
|
|
||||||
|
@ -34,12 +38,17 @@ ViewHolder* view_holder_alloc(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Free ViewHolder and call Free callback
|
* @brief Free ViewHolder and call Free callback
|
||||||
|
*
|
||||||
|
* @warning The current view must be unset prior to freeing a ViewHolder instance.
|
||||||
|
*
|
||||||
* @param view_holder pointer to ViewHolder
|
* @param view_holder pointer to ViewHolder
|
||||||
*/
|
*/
|
||||||
void view_holder_free(ViewHolder* view_holder);
|
void view_holder_free(ViewHolder* view_holder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set view for ViewHolder
|
* @brief Set view for ViewHolder
|
||||||
|
*
|
||||||
|
* Pass NULL as the view parameter to unset the current view.
|
||||||
*
|
*
|
||||||
* @param view_holder ViewHolder instance
|
* @param view_holder ViewHolder instance
|
||||||
* @param view View instance
|
* @param view View instance
|
||||||
|
@ -59,13 +68,25 @@ void view_holder_set_free_callback(
|
||||||
void* free_context);
|
void* free_context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
|
* @brief Free callback context getter.
|
||||||
|
*
|
||||||
|
* Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
|
||||||
*
|
*
|
||||||
* @param view_holder ViewHolder instance
|
* @param view_holder ViewHolder instance
|
||||||
* @return void* free callback context
|
* @return void* free callback context
|
||||||
*/
|
*/
|
||||||
void* view_holder_get_free_context(ViewHolder* view_holder);
|
void* view_holder_get_free_context(ViewHolder* view_holder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the back key callback.
|
||||||
|
*
|
||||||
|
* The callback function will be called if the user has pressed the Back key
|
||||||
|
* and the current view did not handle this event.
|
||||||
|
*
|
||||||
|
* @param view_holder ViewHolder instance
|
||||||
|
* @param back_callback pointer to the callback function
|
||||||
|
* @param back_context pointer to a user-specific object, can be NULL
|
||||||
|
*/
|
||||||
void view_holder_set_back_callback(
|
void view_holder_set_back_callback(
|
||||||
ViewHolder* view_holder,
|
ViewHolder* view_holder,
|
||||||
BackCallback back_callback,
|
BackCallback back_callback,
|
||||||
|
@ -80,26 +101,27 @@ void view_holder_set_back_callback(
|
||||||
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
|
void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enable view processing
|
* @brief View Update Handler
|
||||||
*
|
|
||||||
* @param view_holder
|
|
||||||
*/
|
|
||||||
void view_holder_start(ViewHolder* view_holder);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disable view processing
|
|
||||||
*
|
|
||||||
* @param view_holder
|
|
||||||
*/
|
|
||||||
void view_holder_stop(ViewHolder* view_holder);
|
|
||||||
|
|
||||||
/** View Update Handler
|
|
||||||
*
|
*
|
||||||
* @param view View Instance
|
* @param view View Instance
|
||||||
* @param context ViewHolder instance
|
* @param context ViewHolder instance
|
||||||
*/
|
*/
|
||||||
void view_holder_update(View* view, void* context);
|
void view_holder_update(View* view, void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send ViewPort of this ViewHolder instance to front
|
||||||
|
*
|
||||||
|
* @param view_holder ViewHolder instance
|
||||||
|
*/
|
||||||
|
void view_holder_send_to_front(ViewHolder* view_holder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send ViewPort of this ViewHolder instance to back
|
||||||
|
*
|
||||||
|
* @param view_holder ViewHolder instance
|
||||||
|
*/
|
||||||
|
void view_holder_send_to_back(ViewHolder* view_holder);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -61,7 +61,6 @@ static LoaderApplicationsApp* loader_applications_app_alloc(void) {
|
||||||
app->loading = loading_alloc();
|
app->loading = loading_alloc();
|
||||||
|
|
||||||
view_holder_attach_to_gui(app->view_holder, app->gui);
|
view_holder_attach_to_gui(app->view_holder, app->gui);
|
||||||
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
} //-V773
|
} //-V773
|
||||||
|
@ -149,7 +148,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||||
LoaderApplicationsApp* app = loader_applications_app_alloc();
|
LoaderApplicationsApp* app = loader_applications_app_alloc();
|
||||||
|
|
||||||
// start loading animation
|
// start loading animation
|
||||||
view_holder_start(app->view_holder);
|
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
||||||
|
|
||||||
while(loader_applications_select_app(app)) {
|
while(loader_applications_select_app(app)) {
|
||||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||||
|
@ -161,7 +160,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop loading animation
|
// stop loading animation
|
||||||
view_holder_stop(app->view_holder);
|
view_holder_set_view(app->view_holder, NULL);
|
||||||
|
|
||||||
loader_applications_app_free(app);
|
loader_applications_app_free(app);
|
||||||
|
|
||||||
|
|
|
@ -160,8 +160,6 @@ static LoaderMenuApp* loader_menu_app_alloc(LoaderMenu* loader_menu) {
|
||||||
view_set_context(settings_view, app->settings_menu);
|
view_set_context(settings_view, app->settings_menu);
|
||||||
view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
|
view_set_previous_callback(settings_view, loader_menu_switch_to_primary);
|
||||||
view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view);
|
view_dispatcher_add_view(app->view_dispatcher, LoaderMenuViewSettings, settings_view);
|
||||||
|
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary);
|
view_dispatcher_switch_to_view(app->view_dispatcher, LoaderMenuViewPrimary);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|
|
@ -17,13 +17,15 @@ void power_cli_off(Cli* cli, FuriString* args) {
|
||||||
void power_cli_reboot(Cli* cli, FuriString* args) {
|
void power_cli_reboot(Cli* cli, FuriString* args) {
|
||||||
UNUSED(cli);
|
UNUSED(cli);
|
||||||
UNUSED(args);
|
UNUSED(args);
|
||||||
power_reboot(PowerBootModeNormal);
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
power_reboot(power, PowerBootModeNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
|
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
|
||||||
UNUSED(cli);
|
UNUSED(cli);
|
||||||
UNUSED(args);
|
UNUSED(args);
|
||||||
power_reboot(PowerBootModeDfu);
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
power_reboot(power, PowerBootModeDfu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void power_cli_5v(Cli* cli, FuriString* args) {
|
void power_cli_5v(Cli* cli, FuriString* args) {
|
||||||
|
|
|
@ -4,10 +4,18 @@
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
|
|
||||||
#define POWER_OFF_TIMEOUT 90
|
#include <update_util/update_operation.h>
|
||||||
#define TAG "Power"
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
void power_draw_battery_callback(Canvas* canvas, void* context) {
|
#define TAG "Power"
|
||||||
|
|
||||||
|
#define POWER_OFF_TIMEOUT_S (90U)
|
||||||
|
#define POWER_POLL_PERIOD_MS (1000UL)
|
||||||
|
|
||||||
|
#define POWER_VBUS_LOW_THRESHOLD (4.0f)
|
||||||
|
#define POWER_HEALTH_LOW_THRESHOLD (70U)
|
||||||
|
|
||||||
|
static void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
Power* power = context;
|
Power* power = context;
|
||||||
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
|
canvas_draw_icon(canvas, 0, 0, &I_Battery_26x8);
|
||||||
|
@ -219,6 +227,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||||
}
|
}
|
||||||
canvas_set_bitmap_mode(canvas, 0);
|
canvas_set_bitmap_mode(canvas, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
canvas_draw_box(canvas, 8, 3, 8, 2);
|
canvas_draw_box(canvas, 8, 3, 8, 2);
|
||||||
}
|
}
|
||||||
|
@ -228,99 +237,61 @@ static ViewPort* power_battery_view_port_alloc(Power* power) {
|
||||||
ViewPort* battery_view_port = view_port_alloc();
|
ViewPort* battery_view_port = view_port_alloc();
|
||||||
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_26x8));
|
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_26x8));
|
||||||
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
|
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
|
||||||
gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
|
|
||||||
return battery_view_port;
|
return battery_view_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
Power* power_alloc(void) {
|
static bool power_update_info(Power* power) {
|
||||||
Power* power = malloc(sizeof(Power));
|
const PowerInfo info = {
|
||||||
|
.is_charging = furi_hal_power_is_charging(),
|
||||||
|
.gauge_is_ok = furi_hal_power_gauge_is_ok(),
|
||||||
|
.is_shutdown_requested = furi_hal_power_is_shutdown_requested(),
|
||||||
|
.charge = furi_hal_power_get_pct(),
|
||||||
|
.health = furi_hal_power_get_bat_health_pct(),
|
||||||
|
.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
|
||||||
|
.capacity_full = furi_hal_power_get_battery_full_capacity(),
|
||||||
|
.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger),
|
||||||
|
.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge),
|
||||||
|
.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(),
|
||||||
|
.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger),
|
||||||
|
.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge),
|
||||||
|
.voltage_vbus = furi_hal_power_get_usb_voltage(),
|
||||||
|
.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger),
|
||||||
|
.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge),
|
||||||
|
};
|
||||||
|
|
||||||
// Records
|
const bool need_refresh = (power->info.charge != info.charge) ||
|
||||||
power->notification = furi_record_open(RECORD_NOTIFICATION);
|
(power->info.is_charging != info.is_charging);
|
||||||
power->gui = furi_record_open(RECORD_GUI);
|
power->info = info;
|
||||||
|
return need_refresh;
|
||||||
// Pubsub
|
|
||||||
power->event_pubsub = furi_pubsub_alloc();
|
|
||||||
|
|
||||||
// State initialization
|
|
||||||
power->state = PowerStateNotCharging;
|
|
||||||
power->battery_low = false;
|
|
||||||
power->power_off_timeout = POWER_OFF_TIMEOUT;
|
|
||||||
power->api_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
||||||
|
|
||||||
// Gui
|
|
||||||
power->view_dispatcher = view_dispatcher_alloc();
|
|
||||||
power->power_off = power_off_alloc();
|
|
||||||
view_dispatcher_add_view(
|
|
||||||
power->view_dispatcher, PowerViewOff, power_off_get_view(power->power_off));
|
|
||||||
power->power_unplug_usb = power_unplug_usb_alloc();
|
|
||||||
view_dispatcher_add_view(
|
|
||||||
power->view_dispatcher,
|
|
||||||
PowerViewUnplugUsb,
|
|
||||||
power_unplug_usb_get_view(power->power_unplug_usb));
|
|
||||||
view_dispatcher_attach_to_gui(
|
|
||||||
power->view_dispatcher, power->gui, ViewDispatcherTypeFullscreen);
|
|
||||||
|
|
||||||
// Battery view port
|
|
||||||
power->battery_view_port = power_battery_view_port_alloc(power);
|
|
||||||
power->show_low_bat_level_message = true;
|
|
||||||
|
|
||||||
return power;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void power_check_charging_state(Power* power) {
|
static void power_check_charging_state(Power* power) {
|
||||||
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
if(furi_hal_power_is_charging()) {
|
if(furi_hal_power_is_charging()) {
|
||||||
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
|
if((power->info.charge == 100) || (furi_hal_power_is_charging_done())) {
|
||||||
if(power->state != PowerStateCharged) {
|
if(power->state != PowerStateCharged) {
|
||||||
notification_internal_message(power->notification, &sequence_charged);
|
notification_internal_message(notification, &sequence_charged);
|
||||||
power->state = PowerStateCharged;
|
power->state = PowerStateCharged;
|
||||||
power->event.type = PowerEventTypeFullyCharged;
|
power->event.type = PowerEventTypeFullyCharged;
|
||||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if(power->state != PowerStateCharging) {
|
} else if(power->state != PowerStateCharging) {
|
||||||
notification_internal_message(power->notification, &sequence_charging);
|
notification_internal_message(notification, &sequence_charging);
|
||||||
power->state = PowerStateCharging;
|
power->state = PowerStateCharging;
|
||||||
power->event.type = PowerEventTypeStartCharging;
|
power->event.type = PowerEventTypeStartCharging;
|
||||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(power->state != PowerStateNotCharging) {
|
|
||||||
notification_internal_message(power->notification, &sequence_not_charging);
|
|
||||||
power->state = PowerStateNotCharging;
|
|
||||||
power->event.type = PowerEventTypeStopCharging;
|
|
||||||
furi_pubsub_publish(power->event_pubsub, &power->event);
|
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if(power->state != PowerStateNotCharging) {
|
||||||
|
notification_internal_message(notification, &sequence_not_charging);
|
||||||
|
power->state = PowerStateNotCharging;
|
||||||
|
power->event.type = PowerEventTypeStopCharging;
|
||||||
|
furi_pubsub_publish(power->event_pubsub, &power->event);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static bool power_update_info(Power* power) {
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
PowerInfo info;
|
|
||||||
|
|
||||||
info.is_charging = furi_hal_power_is_charging();
|
|
||||||
info.gauge_is_ok = furi_hal_power_gauge_is_ok();
|
|
||||||
info.is_shutdown_requested = furi_hal_power_is_shutdown_requested();
|
|
||||||
info.charge = furi_hal_power_get_pct();
|
|
||||||
info.health = furi_hal_power_get_bat_health_pct();
|
|
||||||
info.capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
|
|
||||||
info.capacity_full = furi_hal_power_get_battery_full_capacity();
|
|
||||||
info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
|
|
||||||
info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
|
|
||||||
info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit();
|
|
||||||
info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
|
|
||||||
info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
|
|
||||||
info.voltage_vbus = furi_hal_power_get_usb_voltage();
|
|
||||||
info.temperature_charger = furi_hal_power_get_battery_temperature(FuriHalPowerICCharger);
|
|
||||||
info.temperature_gauge = furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
|
|
||||||
|
|
||||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
|
||||||
bool need_refresh = power->info.charge != info.charge;
|
|
||||||
need_refresh |= power->info.is_charging != info.is_charging;
|
|
||||||
power->info = info;
|
|
||||||
furi_mutex_release(power->api_mtx);
|
|
||||||
|
|
||||||
return need_refresh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void power_check_low_battery(Power* power) {
|
static void power_check_low_battery(Power* power) {
|
||||||
|
@ -329,40 +300,41 @@ static void power_check_low_battery(Power* power) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check battery charge and vbus voltage
|
// Check battery charge and vbus voltage
|
||||||
if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) &&
|
if((power->info.is_shutdown_requested) &&
|
||||||
power->show_low_bat_level_message) {
|
(power->info.voltage_vbus < POWER_VBUS_LOW_THRESHOLD) && power->show_battery_low_warning) {
|
||||||
if(!power->battery_low) {
|
if(!power->battery_low) {
|
||||||
view_dispatcher_send_to_front(power->view_dispatcher);
|
view_holder_send_to_front(power->view_holder);
|
||||||
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewOff);
|
view_holder_set_view(power->view_holder, power_off_get_view(power->view_power_off));
|
||||||
}
|
}
|
||||||
power->battery_low = true;
|
power->battery_low = true;
|
||||||
} else {
|
} else {
|
||||||
if(power->battery_low) {
|
if(power->battery_low) {
|
||||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
// view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
||||||
power->power_off_timeout = POWER_OFF_TIMEOUT;
|
view_holder_set_view(power->view_holder, NULL);
|
||||||
|
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
|
||||||
}
|
}
|
||||||
power->battery_low = false;
|
power->battery_low = false;
|
||||||
}
|
}
|
||||||
// If battery low, update view and switch off power after timeout
|
// If battery low, update view and switch off power after timeout
|
||||||
if(power->battery_low) {
|
if(power->battery_low) {
|
||||||
PowerOffResponse response = power_off_get_response(power->power_off);
|
PowerOffResponse response = power_off_get_response(power->view_power_off);
|
||||||
if(response == PowerOffResponseDefault) {
|
if(response == PowerOffResponseDefault) {
|
||||||
if(power->power_off_timeout) {
|
if(power->power_off_timeout) {
|
||||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
|
||||||
} else {
|
} else {
|
||||||
power_off(power);
|
power_off(power);
|
||||||
}
|
}
|
||||||
} else if(response == PowerOffResponseOk) {
|
} else if(response == PowerOffResponseOk) {
|
||||||
power_off(power);
|
power_off(power);
|
||||||
} else if(response == PowerOffResponseHide) {
|
} else if(response == PowerOffResponseHide) {
|
||||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
view_holder_set_view(power->view_holder, NULL);
|
||||||
if(power->power_off_timeout) {
|
if(power->power_off_timeout) {
|
||||||
power_off_set_time_left(power->power_off, power->power_off_timeout--);
|
power_off_set_time_left(power->view_power_off, power->power_off_timeout--);
|
||||||
} else {
|
} else {
|
||||||
power_off(power);
|
power_off(power);
|
||||||
}
|
}
|
||||||
} else if(response == PowerOffResponseCancel) {
|
} else if(response == PowerOffResponseCancel) {
|
||||||
view_dispatcher_switch_to_view(power->view_dispatcher, VIEW_NONE);
|
view_holder_set_view(power->view_holder, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,6 +356,121 @@ void power_trigger_ui_update(Power* power) {
|
||||||
view_port_update(power->battery_view_port);
|
view_port_update(power->battery_view_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void power_handle_shutdown(Power* power) {
|
||||||
|
furi_hal_power_off();
|
||||||
|
// Notify user if USB is plugged
|
||||||
|
view_holder_send_to_front(power->view_holder);
|
||||||
|
view_holder_set_view(
|
||||||
|
power->view_holder, power_unplug_usb_get_view(power->view_power_unplug_usb));
|
||||||
|
furi_delay_ms(100);
|
||||||
|
furi_halt("Disconnect USB for safe shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void power_handle_reboot(PowerBootMode mode) {
|
||||||
|
if(mode == PowerBootModeNormal) {
|
||||||
|
update_operation_disarm();
|
||||||
|
} else if(mode == PowerBootModeDfu) {
|
||||||
|
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
|
||||||
|
} else if(mode == PowerBootModeUpdateStart) {
|
||||||
|
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_power_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool power_message_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Power* power = context;
|
||||||
|
|
||||||
|
furi_assert(object == power->message_queue);
|
||||||
|
|
||||||
|
PowerMessage msg;
|
||||||
|
furi_check(furi_message_queue_get(power->message_queue, &msg, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
switch(msg.type) {
|
||||||
|
case PowerMessageTypeShutdown:
|
||||||
|
power_handle_shutdown(power);
|
||||||
|
break;
|
||||||
|
case PowerMessageTypeReboot:
|
||||||
|
power_handle_reboot(msg.boot_mode);
|
||||||
|
break;
|
||||||
|
case PowerMessageTypeGetInfo:
|
||||||
|
*msg.power_info = power->info;
|
||||||
|
break;
|
||||||
|
case PowerMessageTypeIsBatteryHealthy:
|
||||||
|
*msg.bool_param = power->info.health > POWER_HEALTH_LOW_THRESHOLD;
|
||||||
|
break;
|
||||||
|
case PowerMessageTypeShowBatteryLowWarning:
|
||||||
|
power->show_battery_low_warning = *msg.bool_param;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.lock) {
|
||||||
|
api_lock_unlock(msg.lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void power_tick_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Power* power = context;
|
||||||
|
|
||||||
|
// Update data from gauge and charger
|
||||||
|
const bool need_refresh = power_update_info(power);
|
||||||
|
// Check low battery level
|
||||||
|
power_check_low_battery(power);
|
||||||
|
// Check and notify about charging state
|
||||||
|
power_check_charging_state(power);
|
||||||
|
// Check and notify about battery level change
|
||||||
|
power_check_battery_level_change(power);
|
||||||
|
// Update battery view port
|
||||||
|
if(need_refresh) {
|
||||||
|
view_port_update(power->battery_view_port);
|
||||||
|
}
|
||||||
|
// Check OTG status and disable it in case of fault
|
||||||
|
if(furi_hal_power_is_otg_enabled()) {
|
||||||
|
furi_hal_power_check_otg_status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Power* power_alloc(void) {
|
||||||
|
Power* power = malloc(sizeof(Power));
|
||||||
|
// Pubsub
|
||||||
|
power->event_pubsub = furi_pubsub_alloc();
|
||||||
|
// State initialization
|
||||||
|
power->power_off_timeout = POWER_OFF_TIMEOUT_S;
|
||||||
|
power->show_battery_low_warning = true;
|
||||||
|
// Gui
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
|
power->view_holder = view_holder_alloc();
|
||||||
|
power->view_power_off = power_off_alloc();
|
||||||
|
power->view_power_unplug_usb = power_unplug_usb_alloc();
|
||||||
|
|
||||||
|
view_holder_attach_to_gui(power->view_holder, gui);
|
||||||
|
// Battery view port
|
||||||
|
power->battery_view_port = power_battery_view_port_alloc(power);
|
||||||
|
gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
|
||||||
|
// Event loop
|
||||||
|
power->event_loop = furi_event_loop_alloc();
|
||||||
|
power->message_queue = furi_message_queue_alloc(4, sizeof(PowerMessage));
|
||||||
|
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
power->event_loop,
|
||||||
|
power->message_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
power_message_callback,
|
||||||
|
power);
|
||||||
|
furi_event_loop_tick_set(power->event_loop, 1000, power_tick_callback, power);
|
||||||
|
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t power_srv(void* p) {
|
int32_t power_srv(void* p) {
|
||||||
UNUSED(p);
|
UNUSED(p);
|
||||||
|
|
||||||
|
@ -396,40 +483,9 @@ int32_t power_srv(void* p) {
|
||||||
|
|
||||||
Power* power = power_alloc();
|
Power* power = power_alloc();
|
||||||
power_update_info(power);
|
power_update_info(power);
|
||||||
|
|
||||||
furi_record_create(RECORD_POWER, power);
|
furi_record_create(RECORD_POWER, power);
|
||||||
|
furi_event_loop_run(power->event_loop);
|
||||||
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
|
|
||||||
desktop_settings_load(settings);
|
|
||||||
power->displayBatteryPercentage = settings->displayBatteryPercentage;
|
|
||||||
free(settings);
|
|
||||||
|
|
||||||
while(1) {
|
|
||||||
// Update data from gauge and charger
|
|
||||||
bool need_refresh = power_update_info(power);
|
|
||||||
|
|
||||||
// Check low battery level
|
|
||||||
power_check_low_battery(power);
|
|
||||||
|
|
||||||
// Check and notify about charging state
|
|
||||||
power_check_charging_state(power);
|
|
||||||
|
|
||||||
// Check and notify about battery level change
|
|
||||||
power_check_battery_level_change(power);
|
|
||||||
|
|
||||||
// Update battery view port
|
|
||||||
if(need_refresh) {
|
|
||||||
view_port_update(power->battery_view_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check OTG status and disable it in case of fault
|
|
||||||
if(furi_hal_power_is_otg_enabled()) {
|
|
||||||
furi_hal_power_check_otg_status();
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_delay_ms(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_crash("That was unexpected");
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <core/pubsub.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <core/pubsub.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -65,7 +66,7 @@ void power_off(Power* power);
|
||||||
*
|
*
|
||||||
* @param mode PowerBootMode
|
* @param mode PowerBootMode
|
||||||
*/
|
*/
|
||||||
void power_reboot(PowerBootMode mode);
|
void power_reboot(Power* power, PowerBootMode mode);
|
||||||
|
|
||||||
/** Get power info
|
/** Get power info
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,41 +1,39 @@
|
||||||
#include "power_i.h"
|
#include "power_i.h"
|
||||||
|
|
||||||
#include <furi.h>
|
|
||||||
#include <furi_hal.h>
|
|
||||||
#include <update_util/update_operation.h>
|
|
||||||
|
|
||||||
void power_off(Power* power) {
|
void power_off(Power* power) {
|
||||||
furi_check(power);
|
furi_check(power);
|
||||||
|
|
||||||
furi_hal_power_off();
|
PowerMessage msg = {
|
||||||
// Notify user if USB is plugged
|
.type = PowerMessageTypeShutdown,
|
||||||
view_dispatcher_send_to_front(power->view_dispatcher);
|
};
|
||||||
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewUnplugUsb);
|
|
||||||
furi_delay_ms(100);
|
furi_check(
|
||||||
furi_halt("Disconnect USB for safe shutdown");
|
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void power_reboot(PowerBootMode mode) {
|
void power_reboot(Power* power, PowerBootMode mode) {
|
||||||
if(mode == PowerBootModeNormal) {
|
PowerMessage msg = {
|
||||||
update_operation_disarm();
|
.type = PowerMessageTypeReboot,
|
||||||
} else if(mode == PowerBootModeDfu) {
|
.boot_mode = mode,
|
||||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeDfu);
|
};
|
||||||
} else if(mode == PowerBootModeUpdateStart) {
|
|
||||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
|
|
||||||
} else {
|
|
||||||
furi_crash();
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_hal_power_reset();
|
furi_check(
|
||||||
|
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void power_get_info(Power* power, PowerInfo* info) {
|
void power_get_info(Power* power, PowerInfo* info) {
|
||||||
furi_check(power);
|
furi_check(power);
|
||||||
furi_check(info);
|
furi_check(info);
|
||||||
|
|
||||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
PowerMessage msg = {
|
||||||
memcpy(info, &power->info, sizeof(power->info));
|
.type = PowerMessageTypeGetInfo,
|
||||||
furi_mutex_release(power->api_mtx);
|
.power_info = info,
|
||||||
|
.lock = api_lock_alloc_locked(),
|
||||||
|
};
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||||
|
api_lock_wait_unlock_and_free(msg.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
FuriPubSub* power_get_pubsub(Power* power) {
|
FuriPubSub* power_get_pubsub(Power* power) {
|
||||||
|
@ -45,16 +43,30 @@ FuriPubSub* power_get_pubsub(Power* power) {
|
||||||
|
|
||||||
bool power_is_battery_healthy(Power* power) {
|
bool power_is_battery_healthy(Power* power) {
|
||||||
furi_check(power);
|
furi_check(power);
|
||||||
bool is_healthy = false;
|
|
||||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
bool ret = false;
|
||||||
is_healthy = power->info.health > POWER_BATTERY_HEALTHY_LEVEL;
|
|
||||||
furi_mutex_release(power->api_mtx);
|
PowerMessage msg = {
|
||||||
return is_healthy;
|
.type = PowerMessageTypeIsBatteryHealthy,
|
||||||
|
.lock = api_lock_alloc_locked(),
|
||||||
|
.bool_param = &ret,
|
||||||
|
};
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||||
|
api_lock_wait_unlock_and_free(msg.lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void power_enable_low_battery_level_notification(Power* power, bool enable) {
|
void power_enable_low_battery_level_notification(Power* power, bool enable) {
|
||||||
furi_check(power);
|
furi_check(power);
|
||||||
furi_mutex_acquire(power->api_mtx, FuriWaitForever);
|
|
||||||
power->show_low_bat_level_message = enable;
|
PowerMessage msg = {
|
||||||
furi_mutex_release(power->api_mtx);
|
.type = PowerMessageTypeShowBatteryLowWarning,
|
||||||
|
.bool_param = &enable,
|
||||||
|
};
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,15 @@
|
||||||
|
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <gui/view_dispatcher.h>
|
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_holder.h>
|
||||||
|
|
||||||
|
#include <toolbox/api_lock.h>
|
||||||
#include <assets_icons.h>
|
#include <assets_icons.h>
|
||||||
|
|
||||||
#include <gui/modules/popup.h>
|
|
||||||
#include "views/power_off.h"
|
#include "views/power_off.h"
|
||||||
#include "views/power_unplug_usb.h"
|
#include "views/power_unplug_usb.h"
|
||||||
|
|
||||||
#include <notification/notification_messages.h>
|
|
||||||
|
|
||||||
#define POWER_BATTERY_HEALTHY_LEVEL 70
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PowerStateNotCharging,
|
PowerStateNotCharging,
|
||||||
PowerStateCharging,
|
PowerStateCharging,
|
||||||
|
@ -22,29 +18,45 @@ typedef enum {
|
||||||
} PowerState;
|
} PowerState;
|
||||||
|
|
||||||
struct Power {
|
struct Power {
|
||||||
ViewDispatcher* view_dispatcher;
|
ViewHolder* view_holder;
|
||||||
PowerOff* power_off;
|
FuriPubSub* event_pubsub;
|
||||||
PowerUnplugUsb* power_unplug_usb;
|
FuriEventLoop* event_loop;
|
||||||
|
FuriMessageQueue* message_queue;
|
||||||
|
|
||||||
ViewPort* battery_view_port;
|
ViewPort* battery_view_port;
|
||||||
Gui* gui;
|
PowerOff* view_power_off;
|
||||||
NotificationApp* notification;
|
PowerUnplugUsb* view_power_unplug_usb;
|
||||||
FuriPubSub* event_pubsub;
|
|
||||||
PowerEvent event;
|
|
||||||
|
|
||||||
|
PowerEvent event;
|
||||||
PowerState state;
|
PowerState state;
|
||||||
PowerInfo info;
|
PowerInfo info;
|
||||||
|
|
||||||
bool battery_low;
|
bool battery_low;
|
||||||
bool show_low_bat_level_message;
|
bool show_battery_low_warning;
|
||||||
uint8_t displayBatteryPercentage;
|
uint8_t displayBatteryPercentage;
|
||||||
uint8_t battery_level;
|
uint8_t battery_level;
|
||||||
uint8_t power_off_timeout;
|
uint8_t power_off_timeout;
|
||||||
|
|
||||||
FuriMutex* api_mtx;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PowerViewOff,
|
PowerViewOff,
|
||||||
PowerViewUnplugUsb,
|
PowerViewUnplugUsb,
|
||||||
} PowerView;
|
} PowerView;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PowerMessageTypeShutdown,
|
||||||
|
PowerMessageTypeReboot,
|
||||||
|
PowerMessageTypeGetInfo,
|
||||||
|
PowerMessageTypeIsBatteryHealthy,
|
||||||
|
PowerMessageTypeShowBatteryLowWarning,
|
||||||
|
} PowerMessageType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PowerMessageType type;
|
||||||
|
union {
|
||||||
|
PowerBootMode boot_mode;
|
||||||
|
PowerInfo* power_info;
|
||||||
|
bool* bool_param;
|
||||||
|
};
|
||||||
|
FuriApiLock lock;
|
||||||
|
} PowerMessage;
|
||||||
|
|
|
@ -54,18 +54,21 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte
|
||||||
RpcSession* session = (RpcSession*)context;
|
RpcSession* session = (RpcSession*)context;
|
||||||
furi_assert(session);
|
furi_assert(session);
|
||||||
|
|
||||||
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
const int mode = request->content.system_reboot_request.mode;
|
const int mode = request->content.system_reboot_request.mode;
|
||||||
|
|
||||||
if(mode == PB_System_RebootRequest_RebootMode_OS) {
|
if(mode == PB_System_RebootRequest_RebootMode_OS) {
|
||||||
power_reboot(PowerBootModeNormal);
|
power_reboot(power, PowerBootModeNormal);
|
||||||
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
|
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
|
||||||
power_reboot(PowerBootModeDfu);
|
power_reboot(power, PowerBootModeDfu);
|
||||||
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
|
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
|
||||||
power_reboot(PowerBootModeUpdateStart);
|
power_reboot(power, PowerBootModeUpdateStart);
|
||||||
} else {
|
} else {
|
||||||
rpc_send_and_release_empty(
|
rpc_send_and_release_empty(
|
||||||
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
|
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
furi_record_close(RECORD_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rpc_system_system_device_info_callback(
|
static void rpc_system_system_device_info_callback(
|
||||||
|
@ -181,9 +184,9 @@ static void rpc_system_system_factory_reset_process(const PB_Main* request, void
|
||||||
|
|
||||||
furi_hal_rtc_reset_registers();
|
furi_hal_rtc_reset_registers();
|
||||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||||
power_reboot(PowerBootModeNormal);
|
|
||||||
|
|
||||||
(void)session;
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
power_reboot(power, PowerBootModeNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
|
@ -675,9 +675,12 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
|
||||||
char c = cli_getc(cli);
|
char c = cli_getc(cli);
|
||||||
if(c == 'y' || c == 'Y') {
|
if(c == 'y' || c == 'Y') {
|
||||||
printf("Data will be wiped after reboot.\r\n");
|
printf("Data will be wiped after reboot.\r\n");
|
||||||
|
|
||||||
furi_hal_rtc_reset_registers();
|
furi_hal_rtc_reset_registers();
|
||||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||||
power_reboot(PowerBootModeNormal);
|
|
||||||
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
power_reboot(power, PowerBootModeNormal);
|
||||||
} else {
|
} else {
|
||||||
printf("Safe choice.\r\n");
|
printf("Safe choice.\r\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <dialogs/dialogs.h>
|
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <gui/view_dispatcher.h>
|
#include <gui/view_holder.h>
|
||||||
#include <gui/modules/empty_screen.h>
|
#include <gui/modules/empty_screen.h>
|
||||||
|
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
#include <assets_icons.h>
|
#include <assets_icons.h>
|
||||||
|
|
||||||
#include <furi_hal_version.h>
|
#include <furi_hal_version.h>
|
||||||
#include <furi_hal_region.h>
|
#include <furi_hal_region.h>
|
||||||
#include <furi_hal_bt.h>
|
#include <furi_hal_bt.h>
|
||||||
|
@ -202,18 +205,15 @@ int32_t about_settings_app(void* p) {
|
||||||
DialogMessage* message = dialog_message_alloc();
|
DialogMessage* message = dialog_message_alloc();
|
||||||
|
|
||||||
Gui* gui = furi_record_open(RECORD_GUI);
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
ViewHolder* view_holder = view_holder_alloc();
|
||||||
EmptyScreen* empty_screen = empty_screen_alloc();
|
EmptyScreen* empty_screen = empty_screen_alloc();
|
||||||
const uint32_t empty_screen_index = 0;
|
|
||||||
|
|
||||||
size_t screen_index = 0;
|
size_t screen_index = 0;
|
||||||
DialogMessageButton screen_result;
|
DialogMessageButton screen_result;
|
||||||
|
|
||||||
// draw empty screen to prevent menu flickering
|
// draw empty screen to prevent menu flickering
|
||||||
view_dispatcher_add_view(
|
view_holder_attach_to_gui(view_holder, gui);
|
||||||
view_dispatcher, empty_screen_index, empty_screen_get_view(empty_screen));
|
view_holder_set_view(view_holder, empty_screen_get_view(empty_screen));
|
||||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
|
||||||
view_dispatcher_switch_to_view(view_dispatcher, empty_screen_index);
|
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
if(screen_index >= COUNT_OF(about_screens) - 1) {
|
if(screen_index >= COUNT_OF(about_screens) - 1) {
|
||||||
|
@ -244,8 +244,8 @@ int32_t about_settings_app(void* p) {
|
||||||
dialog_message_free(message);
|
dialog_message_free(message);
|
||||||
furi_record_close(RECORD_DIALOGS);
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
|
||||||
view_dispatcher_remove_view(view_dispatcher, empty_screen_index);
|
view_holder_set_view(view_holder, NULL);
|
||||||
view_dispatcher_free(view_dispatcher);
|
view_holder_free(view_holder);
|
||||||
empty_screen_free(empty_screen);
|
empty_screen_free(empty_screen);
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ BtSettingsApp* bt_settings_app_alloc(void) {
|
||||||
// View Dispatcher and Scene Manager
|
// View Dispatcher and Scene Manager
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
|
|
@ -30,7 +30,6 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) {
|
||||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
|
|
@ -242,7 +242,6 @@ static NotificationAppSettings* alloc_settings(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
view_dispatcher_add_view(app->view_dispatcher, 0, view);
|
view_dispatcher_add_view(app->view_dispatcher, 0, view);
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||||
|
|
|
@ -28,7 +28,6 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
app->scene_manager = scene_manager_alloc(&power_settings_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&power_settings_scene_handlers, app);
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
app->view_dispatcher, power_settings_custom_event_callback);
|
app->view_dispatcher, power_settings_custom_event_callback);
|
||||||
|
|
|
@ -49,10 +49,12 @@ bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEve
|
||||||
if(event.event == DialogExResultLeft) {
|
if(event.event == DialogExResultLeft) {
|
||||||
scene_manager_previous_scene(app->scene_manager);
|
scene_manager_previous_scene(app->scene_manager);
|
||||||
} else if(event.event == DialogExResultRight) {
|
} else if(event.event == DialogExResultRight) {
|
||||||
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
|
||||||
if(reboot_type == RebootTypeDFU) {
|
if(reboot_type == RebootTypeDFU) {
|
||||||
power_reboot(PowerBootModeDfu);
|
power_reboot(power, PowerBootModeDfu);
|
||||||
} else {
|
} else {
|
||||||
power_reboot(PowerBootModeNormal);
|
power_reboot(power, PowerBootModeNormal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
|
|
@ -65,7 +65,9 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv
|
||||||
} else {
|
} else {
|
||||||
furi_hal_rtc_reset_registers();
|
furi_hal_rtc_reset_registers();
|
||||||
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal);
|
||||||
power_reboot(PowerBootModeNormal);
|
|
||||||
|
Power* power = furi_record_open(RECORD_POWER);
|
||||||
|
power_reboot(power, PowerBootModeNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
|
|
@ -23,7 +23,6 @@ static StorageSettings* storage_settings_alloc(void) {
|
||||||
app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app);
|
app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app);
|
||||||
app->text_string = furi_string_alloc();
|
app->text_string = furi_string_alloc();
|
||||||
|
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
|
|
@ -220,7 +220,6 @@ SystemSettings* system_settings_alloc(void) {
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
|
@ -72,7 +72,6 @@ Hid* hid_alloc() {
|
||||||
|
|
||||||
// View dispatcher
|
// View dispatcher
|
||||||
app->view_dispatcher = view_dispatcher_alloc();
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
view_dispatcher_set_custom_event_callback(app->view_dispatcher, hid_custom_event_callback);
|
view_dispatcher_set_custom_event_callback(app->view_dispatcher, hid_custom_event_callback);
|
||||||
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback);
|
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback);
|
||||||
|
|
|
@ -69,7 +69,6 @@ static JsApp* js_app_alloc(void) {
|
||||||
app->loading = loading_alloc();
|
app->loading = loading_alloc();
|
||||||
|
|
||||||
app->gui = furi_record_open("gui");
|
app->gui = furi_record_open("gui");
|
||||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
||||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading));
|
app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading));
|
||||||
|
|
|
@ -97,10 +97,9 @@ static void js_submenu_show(struct mjs* mjs) {
|
||||||
view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu);
|
view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu);
|
||||||
|
|
||||||
view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu));
|
view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu));
|
||||||
view_holder_start(submenu->view_holder);
|
|
||||||
api_lock_wait_unlock(submenu->lock);
|
api_lock_wait_unlock(submenu->lock);
|
||||||
|
|
||||||
view_holder_stop(submenu->view_holder);
|
view_holder_set_view(submenu->view_holder, NULL);
|
||||||
view_holder_free(submenu->view_holder);
|
view_holder_free(submenu->view_holder);
|
||||||
furi_record_close(RECORD_GUI);
|
furi_record_close(RECORD_GUI);
|
||||||
api_lock_free(submenu->lock);
|
api_lock_free(submenu->lock);
|
||||||
|
|
|
@ -125,7 +125,7 @@ static void js_textbox_is_open(struct mjs* mjs) {
|
||||||
static void textbox_callback(void* context, uint32_t arg) {
|
static void textbox_callback(void* context, uint32_t arg) {
|
||||||
UNUSED(arg);
|
UNUSED(arg);
|
||||||
JsTextboxInst* textbox = context;
|
JsTextboxInst* textbox = context;
|
||||||
view_holder_stop(textbox->view_holder);
|
view_holder_set_view(textbox->view_holder, NULL);
|
||||||
textbox->is_shown = false;
|
textbox->is_shown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ static void js_textbox_show(struct mjs* mjs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
view_holder_start(textbox->view_holder);
|
view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box));
|
||||||
textbox->is_shown = true;
|
textbox->is_shown = true;
|
||||||
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
@ -155,7 +155,7 @@ static void js_textbox_close(struct mjs* mjs) {
|
||||||
JsTextboxInst* textbox = get_this_ctx(mjs);
|
JsTextboxInst* textbox = get_this_ctx(mjs);
|
||||||
if(!check_arg_count(mjs, 0)) return;
|
if(!check_arg_count(mjs, 0)) return;
|
||||||
|
|
||||||
view_holder_stop(textbox->view_holder);
|
view_holder_set_view(textbox->view_holder, NULL);
|
||||||
textbox->is_shown = false;
|
textbox->is_shown = false;
|
||||||
|
|
||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
@ -180,7 +180,6 @@ static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) {
|
||||||
textbox->view_holder = view_holder_alloc();
|
textbox->view_holder = view_holder_alloc();
|
||||||
view_holder_attach_to_gui(textbox->view_holder, gui);
|
view_holder_attach_to_gui(textbox->view_holder, gui);
|
||||||
view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox);
|
view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox);
|
||||||
view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box));
|
|
||||||
|
|
||||||
*object = textbox_obj;
|
*object = textbox_obj;
|
||||||
return textbox;
|
return textbox;
|
||||||
|
@ -189,7 +188,7 @@ static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) {
|
||||||
static void js_textbox_destroy(void* inst) {
|
static void js_textbox_destroy(void* inst) {
|
||||||
JsTextboxInst* textbox = inst;
|
JsTextboxInst* textbox = inst;
|
||||||
|
|
||||||
view_holder_stop(textbox->view_holder);
|
view_holder_set_view(textbox->view_holder, NULL);
|
||||||
view_holder_free(textbox->view_holder);
|
view_holder_free(textbox->view_holder);
|
||||||
textbox->view_holder = NULL;
|
textbox->view_holder = NULL;
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,6 @@ Updater* updater_alloc(const char* arg) {
|
||||||
updater->view_dispatcher = view_dispatcher_alloc();
|
updater->view_dispatcher = view_dispatcher_alloc();
|
||||||
updater->scene_manager = scene_manager_alloc(&updater_scene_handlers, updater);
|
updater->scene_manager = scene_manager_alloc(&updater_scene_handlers, updater);
|
||||||
|
|
||||||
view_dispatcher_enable_queue(updater->view_dispatcher);
|
|
||||||
|
|
||||||
view_dispatcher_set_event_callback_context(updater->view_dispatcher, updater);
|
view_dispatcher_set_event_callback_context(updater->view_dispatcher, updater);
|
||||||
view_dispatcher_set_custom_event_callback(
|
view_dispatcher_set_custom_event_callback(
|
||||||
updater->view_dispatcher, updater_custom_event_callback);
|
updater->view_dispatcher, updater_custom_event_callback);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "event_loop_i.h"
|
#include "event_loop_i.h"
|
||||||
#include "message_queue_i.h"
|
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
|
@ -22,13 +21,17 @@ static FuriEventLoopItem* furi_event_loop_item_alloc(
|
||||||
|
|
||||||
static void furi_event_loop_item_free(FuriEventLoopItem* instance);
|
static void furi_event_loop_item_free(FuriEventLoopItem* instance);
|
||||||
|
|
||||||
|
static void furi_event_loop_item_free_later(FuriEventLoopItem* instance);
|
||||||
|
|
||||||
static void furi_event_loop_item_set_callback(
|
static void furi_event_loop_item_set_callback(
|
||||||
FuriEventLoopItem* instance,
|
FuriEventLoopItem* instance,
|
||||||
FuriEventLoopMessageQueueCallback callback,
|
FuriEventLoopEventCallback callback,
|
||||||
void* callback_context);
|
void* callback_context);
|
||||||
|
|
||||||
static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
|
static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
|
||||||
|
|
||||||
|
static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance);
|
||||||
|
|
||||||
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
||||||
for(; !PendingQueue_empty_p(instance->pending_queue);
|
for(; !PendingQueue_empty_p(instance->pending_queue);
|
||||||
PendingQueue_pop_back(NULL, instance->pending_queue)) {
|
PendingQueue_pop_back(NULL, instance->pending_queue)) {
|
||||||
|
@ -37,6 +40,21 @@ static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
FuriEventLoop* instance = context;
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
switch(signal) {
|
||||||
|
case FuriSignalExit:
|
||||||
|
furi_event_loop_stop(instance);
|
||||||
|
return true;
|
||||||
|
// Room for possible other standard signal handlers
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Main public API
|
* Main public API
|
||||||
*/
|
*/
|
||||||
|
@ -67,6 +85,7 @@ void furi_event_loop_free(FuriEventLoop* instance) {
|
||||||
|
|
||||||
furi_event_loop_process_timer_queue(instance);
|
furi_event_loop_process_timer_queue(instance);
|
||||||
furi_check(TimerList_empty_p(instance->timer_list));
|
furi_check(TimerList_empty_p(instance->timer_list));
|
||||||
|
furi_check(WaitingList_empty_p(instance->waiting_list));
|
||||||
|
|
||||||
FuriEventLoopTree_clear(instance->tree);
|
FuriEventLoopTree_clear(instance->tree);
|
||||||
PendingQueue_clear(instance->pending_queue);
|
PendingQueue_clear(instance->pending_queue);
|
||||||
|
@ -81,21 +100,81 @@ void furi_event_loop_free(FuriEventLoop* instance) {
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FuriEventLoopProcessStatus
|
static inline FuriEventLoopProcessStatus
|
||||||
furi_event_loop_poll_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) {
|
furi_event_loop_poll_process_level_event(FuriEventLoopItem* item) {
|
||||||
UNUSED(instance);
|
|
||||||
|
|
||||||
if(!item->contract->get_level(item->object, item->event)) {
|
if(!item->contract->get_level(item->object, item->event)) {
|
||||||
return FuriEventLoopProcessStatusComplete;
|
return FuriEventLoopProcessStatusComplete;
|
||||||
}
|
} else if(item->callback(item->object, item->callback_context)) {
|
||||||
|
|
||||||
if(item->callback(item->object, item->callback_context)) {
|
|
||||||
return FuriEventLoopProcessStatusIncomplete;
|
return FuriEventLoopProcessStatusIncomplete;
|
||||||
} else {
|
} else {
|
||||||
return FuriEventLoopProcessStatusAgain;
|
return FuriEventLoopProcessStatusAgain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline FuriEventLoopProcessStatus
|
||||||
|
furi_event_loop_poll_process_edge_event(FuriEventLoopItem* item) {
|
||||||
|
if(item->callback(item->object, item->callback_context)) {
|
||||||
|
return FuriEventLoopProcessStatusComplete;
|
||||||
|
} else {
|
||||||
|
return FuriEventLoopProcessStatusAgain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline FuriEventLoopProcessStatus
|
||||||
|
furi_event_loop_poll_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) {
|
||||||
|
FuriEventLoopProcessStatus status;
|
||||||
|
if(item->event & FuriEventLoopEventFlagOnce) {
|
||||||
|
furi_event_loop_unsubscribe(instance, item->object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item->event & FuriEventLoopEventFlagEdge) {
|
||||||
|
status = furi_event_loop_poll_process_edge_event(item);
|
||||||
|
} else {
|
||||||
|
status = furi_event_loop_poll_process_level_event(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item->owner == NULL) {
|
||||||
|
status = FuriEventLoopProcessStatusFreeLater;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) {
|
||||||
|
FuriEventLoopItem* item = NULL;
|
||||||
|
|
||||||
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
|
if(!WaitingList_empty_p(instance->waiting_list)) {
|
||||||
|
item = WaitingList_pop_front(instance->waiting_list);
|
||||||
|
WaitingList_init_field(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_CRITICAL_EXIT();
|
||||||
|
|
||||||
|
if(!item) return;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
FuriEventLoopProcessStatus ret = furi_event_loop_poll_process_event(instance, item);
|
||||||
|
|
||||||
|
if(ret == FuriEventLoopProcessStatusComplete) {
|
||||||
|
// Event processing complete, break from loop
|
||||||
|
break;
|
||||||
|
} else if(ret == FuriEventLoopProcessStatusIncomplete) {
|
||||||
|
// Event processing incomplete more processing needed
|
||||||
|
} else if(ret == FuriEventLoopProcessStatusAgain) { //-V547
|
||||||
|
furi_event_loop_item_notify(item);
|
||||||
|
break;
|
||||||
|
// Unsubscribed from inside the callback, delete item
|
||||||
|
} else if(ret == FuriEventLoopProcessStatusFreeLater) { //-V547
|
||||||
|
furi_event_loop_item_free(item);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) {
|
static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) {
|
||||||
if(flags) {
|
if(flags) {
|
||||||
xTaskNotifyIndexed(
|
xTaskNotifyIndexed(
|
||||||
|
@ -134,34 +213,7 @@ void furi_event_loop_run(FuriEventLoop* instance) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if(flags & FuriEventLoopFlagEvent) {
|
} else if(flags & FuriEventLoopFlagEvent) {
|
||||||
FuriEventLoopItem* item = NULL;
|
furi_event_loop_process_waiting_list(instance);
|
||||||
FURI_CRITICAL_ENTER();
|
|
||||||
|
|
||||||
if(!WaitingList_empty_p(instance->waiting_list)) {
|
|
||||||
item = WaitingList_pop_front(instance->waiting_list);
|
|
||||||
WaitingList_init_field(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
|
||||||
|
|
||||||
if(item) {
|
|
||||||
while(true) {
|
|
||||||
FuriEventLoopProcessStatus ret =
|
|
||||||
furi_event_loop_poll_process_event(instance, item);
|
|
||||||
if(ret == FuriEventLoopProcessStatusComplete) {
|
|
||||||
// Event processing complete, break from loop
|
|
||||||
break;
|
|
||||||
} else if(ret == FuriEventLoopProcessStatusIncomplete) {
|
|
||||||
// Event processing incomplete more processing needed
|
|
||||||
} else if(ret == FuriEventLoopProcessStatusAgain) { //-V547
|
|
||||||
furi_event_loop_item_notify(item);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
furi_crash();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagEvent);
|
furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagEvent);
|
||||||
|
|
||||||
} else if(flags & FuriEventLoopFlagTimer) {
|
} else if(flags & FuriEventLoopFlagTimer) {
|
||||||
|
@ -217,87 +269,150 @@ void furi_event_loop_pend_callback(
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message queue API
|
* Private generic susbscription API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void furi_event_loop_message_queue_subscribe(
|
static void furi_event_loop_object_subscribe(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue,
|
FuriEventLoopObject* object,
|
||||||
|
const FuriEventLoopContract* contract,
|
||||||
FuriEventLoopEvent event,
|
FuriEventLoopEvent event,
|
||||||
FuriEventLoopMessageQueueCallback callback,
|
FuriEventLoopEventCallback callback,
|
||||||
void* context) {
|
void* context) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||||
furi_check(instance->state == FuriEventLoopStateStopped);
|
furi_check(object);
|
||||||
furi_check(message_queue);
|
furi_assert(contract);
|
||||||
|
furi_check(callback);
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
furi_check(FuriEventLoopTree_get(instance->tree, message_queue) == NULL);
|
furi_check(FuriEventLoopTree_get(instance->tree, object) == NULL);
|
||||||
|
|
||||||
// Allocate and setup item
|
// Allocate and setup item
|
||||||
FuriEventLoopItem* item = furi_event_loop_item_alloc(
|
FuriEventLoopItem* item = furi_event_loop_item_alloc(instance, contract, object, event);
|
||||||
instance, &furi_message_queue_event_loop_contract, message_queue, event);
|
|
||||||
furi_event_loop_item_set_callback(item, callback, context);
|
furi_event_loop_item_set_callback(item, callback, context);
|
||||||
|
|
||||||
FuriEventLoopTree_set_at(instance->tree, message_queue, item);
|
FuriEventLoopTree_set_at(instance->tree, object, item);
|
||||||
|
|
||||||
FuriEventLoopLink* link = item->contract->get_link(message_queue);
|
FuriEventLoopLink* link = item->contract->get_link(object);
|
||||||
|
FuriEventLoopEvent event_noflags = item->event & FuriEventLoopEventMask;
|
||||||
|
|
||||||
if(item->event == FuriEventLoopEventIn) {
|
if(event_noflags == FuriEventLoopEventIn) {
|
||||||
furi_check(link->item_in == NULL);
|
furi_check(link->item_in == NULL);
|
||||||
link->item_in = item;
|
link->item_in = item;
|
||||||
} else if(item->event == FuriEventLoopEventOut) {
|
} else if(event_noflags == FuriEventLoopEventOut) {
|
||||||
furi_check(link->item_out == NULL);
|
furi_check(link->item_out == NULL);
|
||||||
link->item_out = item;
|
link->item_out = item;
|
||||||
} else {
|
} else {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(item->contract->get_level(item->object, item->event)) {
|
if(!(item->event & FuriEventLoopEventFlagEdge)) {
|
||||||
furi_event_loop_item_notify(item);
|
if(item->contract->get_level(item->object, event_noflags)) {
|
||||||
|
furi_event_loop_item_notify(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
}
|
}
|
||||||
|
|
||||||
void furi_event_loop_message_queue_unsubscribe(
|
/**
|
||||||
|
* Public specialized subscription API
|
||||||
|
*/
|
||||||
|
|
||||||
|
void furi_event_loop_subscribe_message_queue(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue) {
|
FuriMessageQueue* message_queue,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context) {
|
||||||
|
extern const FuriEventLoopContract furi_message_queue_event_loop_contract;
|
||||||
|
|
||||||
|
furi_event_loop_object_subscribe(
|
||||||
|
instance, message_queue, &furi_message_queue_event_loop_contract, event, callback, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_event_loop_subscribe_stream_buffer(
|
||||||
|
FuriEventLoop* instance,
|
||||||
|
FuriStreamBuffer* stream_buffer,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context) {
|
||||||
|
extern const FuriEventLoopContract furi_stream_buffer_event_loop_contract;
|
||||||
|
|
||||||
|
furi_event_loop_object_subscribe(
|
||||||
|
instance, stream_buffer, &furi_stream_buffer_event_loop_contract, event, callback, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_event_loop_subscribe_semaphore(
|
||||||
|
FuriEventLoop* instance,
|
||||||
|
FuriSemaphore* semaphore,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context) {
|
||||||
|
extern const FuriEventLoopContract furi_semaphore_event_loop_contract;
|
||||||
|
|
||||||
|
furi_event_loop_object_subscribe(
|
||||||
|
instance, semaphore, &furi_semaphore_event_loop_contract, event, callback, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_event_loop_subscribe_mutex(
|
||||||
|
FuriEventLoop* instance,
|
||||||
|
FuriMutex* mutex,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context) {
|
||||||
|
extern const FuriEventLoopContract furi_mutex_event_loop_contract;
|
||||||
|
|
||||||
|
furi_event_loop_object_subscribe(
|
||||||
|
instance, mutex, &furi_mutex_event_loop_contract, event, callback, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public generic unsubscription API
|
||||||
|
*/
|
||||||
|
|
||||||
|
void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(instance->state == FuriEventLoopStateStopped);
|
|
||||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
FuriEventLoopItem** item_ptr = FuriEventLoopTree_get(instance->tree, message_queue);
|
FuriEventLoopItem* item = NULL;
|
||||||
furi_check(item_ptr);
|
furi_check(FuriEventLoopTree_pop_at(&item, instance->tree, object));
|
||||||
|
|
||||||
FuriEventLoopItem* item = *item_ptr;
|
|
||||||
furi_check(item);
|
furi_check(item);
|
||||||
furi_check(item->owner == instance);
|
furi_check(item->owner == instance);
|
||||||
|
|
||||||
FuriEventLoopLink* link = item->contract->get_link(message_queue);
|
FuriEventLoopLink* link = item->contract->get_link(object);
|
||||||
|
FuriEventLoopEvent event_noflags = item->event & FuriEventLoopEventMask;
|
||||||
|
|
||||||
if(item->event == FuriEventLoopEventIn) {
|
if(event_noflags == FuriEventLoopEventIn) {
|
||||||
furi_check(link->item_in == item);
|
furi_check(link->item_in == item);
|
||||||
link->item_in = NULL;
|
link->item_in = NULL;
|
||||||
} else if(item->event == FuriEventLoopEventOut) {
|
} else if(event_noflags == FuriEventLoopEventOut) {
|
||||||
furi_check(link->item_out == item);
|
furi_check(link->item_out == item);
|
||||||
link->item_out = NULL;
|
link->item_out = NULL;
|
||||||
} else {
|
} else {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_event_loop_item_free(item);
|
if(furi_event_loop_item_is_waiting(item)) {
|
||||||
|
WaitingList_unlink(item);
|
||||||
|
}
|
||||||
|
|
||||||
FuriEventLoopTree_erase(instance->tree, message_queue);
|
if(instance->state == FuriEventLoopStateProcessing) {
|
||||||
|
furi_event_loop_item_free_later(item);
|
||||||
|
} else {
|
||||||
|
furi_event_loop_item_free(item);
|
||||||
|
}
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Event Loop Item API, used internally
|
* Private Event Loop Item functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static FuriEventLoopItem* furi_event_loop_item_alloc(
|
static FuriEventLoopItem* furi_event_loop_item_alloc(
|
||||||
|
@ -322,12 +437,19 @@ static FuriEventLoopItem* furi_event_loop_item_alloc(
|
||||||
|
|
||||||
static void furi_event_loop_item_free(FuriEventLoopItem* instance) {
|
static void furi_event_loop_item_free(FuriEventLoopItem* instance) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
furi_assert(!furi_event_loop_item_is_waiting(instance));
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void furi_event_loop_item_free_later(FuriEventLoopItem* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
furi_assert(!furi_event_loop_item_is_waiting(instance));
|
||||||
|
instance->owner = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void furi_event_loop_item_set_callback(
|
static void furi_event_loop_item_set_callback(
|
||||||
FuriEventLoopItem* instance,
|
FuriEventLoopItem* instance,
|
||||||
FuriEventLoopMessageQueueCallback callback,
|
FuriEventLoopEventCallback callback,
|
||||||
void* callback_context) {
|
void* callback_context) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
furi_assert(!instance->callback);
|
furi_assert(!instance->callback);
|
||||||
|
@ -341,27 +463,35 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance) {
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
if(!instance->WaitingList.prev && !instance->WaitingList.next) {
|
FuriEventLoop* owner = instance->owner;
|
||||||
WaitingList_push_back(instance->owner->waiting_list, instance);
|
furi_assert(owner);
|
||||||
|
|
||||||
|
if(!furi_event_loop_item_is_waiting(instance)) {
|
||||||
|
WaitingList_push_back(owner->waiting_list, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
|
|
||||||
xTaskNotifyIndexed(
|
xTaskNotifyIndexed(
|
||||||
instance->owner->thread_id,
|
owner->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagEvent, eSetBits);
|
||||||
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX,
|
|
||||||
FuriEventLoopFlagEvent,
|
|
||||||
eSetBits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) {
|
||||||
|
return instance->WaitingList.prev || instance->WaitingList.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal event loop link API, used by supported primitives
|
||||||
|
*/
|
||||||
|
|
||||||
void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event) {
|
void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event) {
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
FURI_CRITICAL_ENTER();
|
FURI_CRITICAL_ENTER();
|
||||||
|
|
||||||
if(event == FuriEventLoopEventIn) {
|
if(event & FuriEventLoopEventIn) {
|
||||||
if(instance->item_in) furi_event_loop_item_notify(instance->item_in);
|
if(instance->item_in) furi_event_loop_item_notify(instance->item_in);
|
||||||
} else if(event == FuriEventLoopEventOut) {
|
} else if(event & FuriEventLoopEventOut) {
|
||||||
if(instance->item_out) furi_event_loop_item_notify(instance->item_out);
|
if(instance->item_out) furi_event_loop_item_notify(instance->item_out);
|
||||||
} else {
|
} else {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
|
@ -369,18 +499,3 @@ void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent
|
||||||
|
|
||||||
FURI_CRITICAL_EXIT();
|
FURI_CRITICAL_EXIT();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) {
|
|
||||||
furi_assert(context);
|
|
||||||
FuriEventLoop* instance = context;
|
|
||||||
UNUSED(arg);
|
|
||||||
|
|
||||||
switch(signal) {
|
|
||||||
case FuriSignalExit:
|
|
||||||
furi_event_loop_stop(instance);
|
|
||||||
return true;
|
|
||||||
// Room for possible other standard signal handlers
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,10 +20,83 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Event Loop events */
|
/**
|
||||||
|
* @brief Enumeration of event types, flags and masks.
|
||||||
|
*
|
||||||
|
* Only one event direction (In or Out) can be used per subscription.
|
||||||
|
* An object can have no more than one subscription for each direction.
|
||||||
|
*
|
||||||
|
* Additional flags that modify the behaviour can be
|
||||||
|
* set using the bitwise OR operation (see flag description).
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FuriEventLoopEventOut, /**< On departure: item was retrieved from container, flag reset, etc... */
|
/**
|
||||||
FuriEventLoopEventIn, /**< On arrival: item was inserted into container, flag set, etc... */
|
* @brief Subscribe to In events.
|
||||||
|
*
|
||||||
|
* In events occur on the following conditions:
|
||||||
|
* - One or more items were inserted into a FuriMessageQueue,
|
||||||
|
* - Enough data has been written to a FuriStreamBuffer,
|
||||||
|
* - A FuriSemaphore has been released at least once,
|
||||||
|
* - A FuriMutex has been released.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventIn = 0x00000001U,
|
||||||
|
/**
|
||||||
|
* @brief Subscribe to Out events.
|
||||||
|
*
|
||||||
|
* Out events occur on the following conditions:
|
||||||
|
* - One or more items were removed from a FuriMessageQueue,
|
||||||
|
* - Any amount of data has been read out of a FuriStreamBuffer,
|
||||||
|
* - A FuriSemaphore has been acquired at least once,
|
||||||
|
* - A FuriMutex has been acquired.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventOut = 0x00000002U,
|
||||||
|
/**
|
||||||
|
* @brief Special value containing the event direction bits, used internally.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventMask = 0x00000003U,
|
||||||
|
/**
|
||||||
|
* @brief Use edge triggered events.
|
||||||
|
*
|
||||||
|
* By default, level triggered events are used. A level above zero
|
||||||
|
* is reported based on the following conditions:
|
||||||
|
*
|
||||||
|
* In events:
|
||||||
|
* - a FuriMessageQueue contains one or more items,
|
||||||
|
* - a FuriStreamBuffer contains one or more bytes,
|
||||||
|
* - a FuriSemaphore can be acquired at least once,
|
||||||
|
* - a FuriMutex can be acquired.
|
||||||
|
*
|
||||||
|
* Out events:
|
||||||
|
* - a FuriMessageQueue has at least one item of free space,
|
||||||
|
* - a FuriStreamBuffer has at least one byte of free space,
|
||||||
|
* - a FuriSemaphore has been acquired at least once,
|
||||||
|
* - a FuriMutex has been acquired.
|
||||||
|
*
|
||||||
|
* If this flag is NOT set, the event will be generated repeatedly until
|
||||||
|
* the level becomes zero (e.g. all items have been removed from
|
||||||
|
* a FuriMessageQueue in case of the "In" event, etc.)
|
||||||
|
*
|
||||||
|
* If this flag IS set, then the above check is skipped and the event
|
||||||
|
* is generated ONLY when a change occurs, with the event direction
|
||||||
|
* (In or Out) taken into account.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventFlagEdge = 0x00000004U,
|
||||||
|
/**
|
||||||
|
* @brief Automatically unsubscribe from events after one time.
|
||||||
|
*
|
||||||
|
* By default, events will be generated each time the specified conditions
|
||||||
|
* have been met. If this flag IS set, the event subscription will be cancelled
|
||||||
|
* upon the first occurred event and no further events will be generated.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventFlagOnce = 0x00000008U,
|
||||||
|
/**
|
||||||
|
* @brief Special value containing the event flag bits, used internally.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventFlagMask = 0xFFFFFFFCU,
|
||||||
|
/**
|
||||||
|
* @brief Special value to force the enum to 32-bit values.
|
||||||
|
*/
|
||||||
|
FuriEventLoopEventReserved = UINT32_MAX,
|
||||||
} FuriEventLoopEvent;
|
} FuriEventLoopEvent;
|
||||||
|
|
||||||
/** Anonymous message queue type */
|
/** Anonymous message queue type */
|
||||||
|
@ -115,21 +188,22 @@ void furi_event_loop_pend_callback(
|
||||||
void* context);
|
void* context);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message queue related APIs
|
* Event subscription/notification APIs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Anonymous message queue type */
|
typedef void FuriEventLoopObject;
|
||||||
typedef struct FuriMessageQueue FuriMessageQueue;
|
|
||||||
|
|
||||||
/** Callback type for message queue
|
/** Callback type for event loop events
|
||||||
*
|
*
|
||||||
* @param queue The queue that triggered event
|
* @param object The object that triggered the event
|
||||||
* @param context The context that was provided on
|
* @param context The context that was provided upon subscription
|
||||||
* furi_event_loop_message_queue_subscribe call
|
|
||||||
*
|
*
|
||||||
* @return true if event was processed, false if we need to delay processing
|
* @return true if event was processed, false if we need to delay processing
|
||||||
*/
|
*/
|
||||||
typedef bool (*FuriEventLoopMessageQueueCallback)(FuriMessageQueue* queue, void* context);
|
typedef bool (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context);
|
||||||
|
|
||||||
|
/** Opaque message queue type */
|
||||||
|
typedef struct FuriMessageQueue FuriMessageQueue;
|
||||||
|
|
||||||
/** Subscribe to message queue events
|
/** Subscribe to message queue events
|
||||||
*
|
*
|
||||||
|
@ -141,21 +215,79 @@ typedef bool (*FuriEventLoopMessageQueueCallback)(FuriMessageQueue* queue, void*
|
||||||
* @param[in] callback The callback to call on event
|
* @param[in] callback The callback to call on event
|
||||||
* @param context The context for callback
|
* @param context The context for callback
|
||||||
*/
|
*/
|
||||||
void furi_event_loop_message_queue_subscribe(
|
void furi_event_loop_subscribe_message_queue(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue,
|
FuriMessageQueue* message_queue,
|
||||||
FuriEventLoopEvent event,
|
FuriEventLoopEvent event,
|
||||||
FuriEventLoopMessageQueueCallback callback,
|
FuriEventLoopEventCallback callback,
|
||||||
void* context);
|
void* context);
|
||||||
|
|
||||||
/** Unsubscribe from message queue
|
/** Opaque stream buffer type */
|
||||||
|
typedef struct FuriStreamBuffer FuriStreamBuffer;
|
||||||
|
|
||||||
|
/** Subscribe to stream buffer events
|
||||||
|
*
|
||||||
|
* @warning you can only have one subscription for one event type.
|
||||||
*
|
*
|
||||||
* @param instance The Event Loop instance
|
* @param instance The Event Loop instance
|
||||||
* @param message_queue The message queue
|
* @param stream_buffer The stream buffer to add
|
||||||
|
* @param[in] event The Event Loop event to trigger on
|
||||||
|
* @param[in] callback The callback to call on event
|
||||||
|
* @param context The context for callback
|
||||||
*/
|
*/
|
||||||
void furi_event_loop_message_queue_unsubscribe(
|
void furi_event_loop_subscribe_stream_buffer(
|
||||||
FuriEventLoop* instance,
|
FuriEventLoop* instance,
|
||||||
FuriMessageQueue* message_queue);
|
FuriStreamBuffer* stream_buffer,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
/** Opaque semaphore type */
|
||||||
|
typedef struct FuriSemaphore FuriSemaphore;
|
||||||
|
|
||||||
|
/** Subscribe to semaphore events
|
||||||
|
*
|
||||||
|
* @warning you can only have one subscription for one event type.
|
||||||
|
*
|
||||||
|
* @param instance The Event Loop instance
|
||||||
|
* @param semaphore The semaphore to add
|
||||||
|
* @param[in] event The Event Loop event to trigger on
|
||||||
|
* @param[in] callback The callback to call on event
|
||||||
|
* @param context The context for callback
|
||||||
|
*/
|
||||||
|
void furi_event_loop_subscribe_semaphore(
|
||||||
|
FuriEventLoop* instance,
|
||||||
|
FuriSemaphore* semaphore,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
/** Opaque mutex type */
|
||||||
|
typedef struct FuriMutex FuriMutex;
|
||||||
|
|
||||||
|
/** Subscribe to mutex events
|
||||||
|
*
|
||||||
|
* @warning you can only have one subscription for one event type.
|
||||||
|
*
|
||||||
|
* @param instance The Event Loop instance
|
||||||
|
* @param mutex The mutex to add
|
||||||
|
* @param[in] event The Event Loop event to trigger on
|
||||||
|
* @param[in] callback The callback to call on event
|
||||||
|
* @param context The context for callback
|
||||||
|
*/
|
||||||
|
void furi_event_loop_subscribe_mutex(
|
||||||
|
FuriEventLoop* instance,
|
||||||
|
FuriMutex* mutex,
|
||||||
|
FuriEventLoopEvent event,
|
||||||
|
FuriEventLoopEventCallback callback,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
/** Unsubscribe from events (common)
|
||||||
|
*
|
||||||
|
* @param instance The Event Loop instance
|
||||||
|
* @param object The object to unsubscribe from
|
||||||
|
*/
|
||||||
|
void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,16 @@ struct FuriEventLoopItem {
|
||||||
FuriEventLoop* owner;
|
FuriEventLoop* owner;
|
||||||
|
|
||||||
// Tracking item
|
// Tracking item
|
||||||
const FuriEventLoopContract* contract;
|
|
||||||
void* object;
|
|
||||||
FuriEventLoopEvent event;
|
FuriEventLoopEvent event;
|
||||||
|
FuriEventLoopObject* object;
|
||||||
|
const FuriEventLoopContract* contract;
|
||||||
|
|
||||||
// Callback and context
|
// Callback and context
|
||||||
FuriEventLoopMessageQueueCallback callback;
|
FuriEventLoopEventCallback callback;
|
||||||
void* callback_context;
|
void* callback_context;
|
||||||
|
|
||||||
// Waiting list
|
// Waiting list
|
||||||
ILIST_INTERFACE(WaitingList, struct FuriEventLoopItem);
|
ILIST_INTERFACE(WaitingList, FuriEventLoopItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST)
|
ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST)
|
||||||
|
@ -36,7 +36,7 @@ ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST)
|
||||||
BPTREE_DEF2( // NOLINT
|
BPTREE_DEF2( // NOLINT
|
||||||
FuriEventLoopTree,
|
FuriEventLoopTree,
|
||||||
FURI_EVENT_LOOP_TREE_RANK,
|
FURI_EVENT_LOOP_TREE_RANK,
|
||||||
void*, /* pointer to object we track */
|
FuriEventLoopObject*, /* pointer to object we track */
|
||||||
M_PTR_OPLIST,
|
M_PTR_OPLIST,
|
||||||
FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */
|
FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */
|
||||||
M_PTR_OPLIST)
|
M_PTR_OPLIST)
|
||||||
|
@ -60,6 +60,7 @@ typedef enum {
|
||||||
FuriEventLoopProcessStatusComplete,
|
FuriEventLoopProcessStatusComplete,
|
||||||
FuriEventLoopProcessStatusIncomplete,
|
FuriEventLoopProcessStatusIncomplete,
|
||||||
FuriEventLoopProcessStatusAgain,
|
FuriEventLoopProcessStatusAgain,
|
||||||
|
FuriEventLoopProcessStatusFreeLater,
|
||||||
} FuriEventLoopProcessStatus;
|
} FuriEventLoopProcessStatus;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
|
@ -19,17 +19,16 @@ void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent
|
||||||
|
|
||||||
/* Contract between event loop and an object */
|
/* Contract between event loop and an object */
|
||||||
|
|
||||||
typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(void* object);
|
typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(FuriEventLoopObject* object);
|
||||||
|
|
||||||
typedef uint32_t (*FuriEventLoopContractGetLevel)(void* object, FuriEventLoopEvent event);
|
typedef uint32_t (
|
||||||
|
*FuriEventLoopContractGetLevel)(FuriEventLoopObject* object, FuriEventLoopEvent event);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const FuriEventLoopContractGetLink get_link;
|
const FuriEventLoopContractGetLink get_link;
|
||||||
const FuriEventLoopContractGetLevel get_level;
|
const FuriEventLoopContractGetLevel get_level;
|
||||||
} FuriEventLoopContract;
|
} FuriEventLoopContract;
|
||||||
|
|
||||||
bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "message_queue_i.h"
|
#include "message_queue.h"
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
|
@ -6,6 +6,8 @@
|
||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
|
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
|
|
||||||
// Internal FreeRTOS member names
|
// Internal FreeRTOS member names
|
||||||
#define uxMessagesWaiting uxDummy4[0]
|
#define uxMessagesWaiting uxDummy4[0]
|
||||||
#define uxLength uxDummy4[1]
|
#define uxLength uxDummy4[1]
|
||||||
|
@ -13,10 +15,7 @@
|
||||||
|
|
||||||
struct FuriMessageQueue {
|
struct FuriMessageQueue {
|
||||||
StaticQueue_t container;
|
StaticQueue_t container;
|
||||||
|
|
||||||
// Event Loop Link
|
|
||||||
FuriEventLoopLink event_loop_link;
|
FuriEventLoopLink event_loop_link;
|
||||||
|
|
||||||
uint8_t buffer[];
|
uint8_t buffer[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -208,13 +207,14 @@ FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) {
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FuriEventLoopLink* furi_message_queue_event_loop_get_link(void* object) {
|
static FuriEventLoopLink* furi_message_queue_event_loop_get_link(FuriEventLoopObject* object) {
|
||||||
FuriMessageQueue* instance = object;
|
FuriMessageQueue* instance = object;
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
return &instance->event_loop_link;
|
return &instance->event_loop_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t furi_message_queue_event_loop_get_level(void* object, FuriEventLoopEvent event) {
|
static uint32_t
|
||||||
|
furi_message_queue_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) {
|
||||||
FuriMessageQueue* instance = object;
|
FuriMessageQueue* instance = object;
|
||||||
furi_assert(instance);
|
furi_assert(instance);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "message_queue.h"
|
|
||||||
#include "event_loop_link_i.h"
|
|
||||||
|
|
||||||
extern const FuriEventLoopContract furi_message_queue_event_loop_contract;
|
|
|
@ -1,15 +1,18 @@
|
||||||
#include "mutex.h"
|
#include "mutex.h"
|
||||||
#include "check.h"
|
|
||||||
#include "common_defines.h"
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <semphr.h>
|
#include <semphr.h>
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
|
|
||||||
// Internal FreeRTOS member names
|
// Internal FreeRTOS member names
|
||||||
#define ucQueueType ucDummy9
|
#define ucQueueType ucDummy9
|
||||||
|
|
||||||
struct FuriMutex {
|
struct FuriMutex {
|
||||||
StaticSemaphore_t container;
|
StaticSemaphore_t container;
|
||||||
|
FuriEventLoopLink event_loop_link;
|
||||||
};
|
};
|
||||||
|
|
||||||
// IMPORTANT: container MUST be the FIRST struct member
|
// IMPORTANT: container MUST be the FIRST struct member
|
||||||
|
@ -39,6 +42,10 @@ void furi_mutex_free(FuriMutex* instance) {
|
||||||
furi_check(!FURI_IS_IRQ_MODE());
|
furi_check(!FURI_IS_IRQ_MODE());
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
|
|
||||||
|
// Event Loop must be disconnected
|
||||||
|
furi_check(!instance->event_loop_link.item_in);
|
||||||
|
furi_check(!instance->event_loop_link.item_out);
|
||||||
|
|
||||||
vSemaphoreDelete((SemaphoreHandle_t)instance);
|
vSemaphoreDelete((SemaphoreHandle_t)instance);
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
@ -76,6 +83,10 @@ FuriStatus furi_mutex_acquire(FuriMutex* instance, uint32_t timeout) {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(stat == FuriStatusOk) {
|
||||||
|
furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventOut);
|
||||||
|
}
|
||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +115,10 @@ FuriStatus furi_mutex_release(FuriMutex* instance) {
|
||||||
furi_crash();
|
furi_crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(stat == FuriStatusOk) {
|
||||||
|
furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventIn);
|
||||||
|
}
|
||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,3 +137,26 @@ FuriThreadId furi_mutex_get_owner(FuriMutex* instance) {
|
||||||
|
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FuriEventLoopLink* furi_mutex_event_loop_get_link(FuriEventLoopObject* object) {
|
||||||
|
FuriMutex* instance = object;
|
||||||
|
furi_assert(instance);
|
||||||
|
return &instance->event_loop_link;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
furi_mutex_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) {
|
||||||
|
FuriMutex* instance = object;
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
if(event == FuriEventLoopEventIn || event == FuriEventLoopEventOut) {
|
||||||
|
return furi_mutex_get_owner(instance) ? 0 : 1;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FuriEventLoopContract furi_mutex_event_loop_contract = {
|
||||||
|
.get_link = furi_mutex_event_loop_get_link,
|
||||||
|
.get_level = furi_mutex_event_loop_get_level,
|
||||||
|
};
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
#include "semaphore.h"
|
#include "semaphore.h"
|
||||||
#include "check.h"
|
|
||||||
#include "common_defines.h"
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <semphr.h>
|
#include <semphr.h>
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
#include "kernel.h"
|
||||||
|
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
|
|
||||||
|
// Internal FreeRTOS member names
|
||||||
|
#define uxMessagesWaiting uxDummy4[0]
|
||||||
|
#define uxLength uxDummy4[1]
|
||||||
|
|
||||||
struct FuriSemaphore {
|
struct FuriSemaphore {
|
||||||
StaticSemaphore_t container;
|
StaticSemaphore_t container;
|
||||||
|
FuriEventLoopLink event_loop_link;
|
||||||
};
|
};
|
||||||
|
|
||||||
// IMPORTANT: container MUST be the FIRST struct member
|
// IMPORTANT: container MUST be the FIRST struct member
|
||||||
|
@ -40,6 +48,10 @@ void furi_semaphore_free(FuriSemaphore* instance) {
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
furi_check(!FURI_IS_IRQ_MODE());
|
furi_check(!FURI_IS_IRQ_MODE());
|
||||||
|
|
||||||
|
// Event Loop must be disconnected
|
||||||
|
furi_check(!instance->event_loop_link.item_in);
|
||||||
|
furi_check(!instance->event_loop_link.item_out);
|
||||||
|
|
||||||
vSemaphoreDelete((SemaphoreHandle_t)instance);
|
vSemaphoreDelete((SemaphoreHandle_t)instance);
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
@ -76,6 +88,10 @@ FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(stat == FuriStatusOk) {
|
||||||
|
furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventOut);
|
||||||
|
}
|
||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +119,10 @@ FuriStatus furi_semaphore_release(FuriSemaphore* instance) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(stat == FuriStatusOk) {
|
||||||
|
furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventIn);
|
||||||
|
}
|
||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,3 +140,46 @@ uint32_t furi_semaphore_get_count(FuriSemaphore* instance) {
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t furi_semaphore_get_space(FuriSemaphore* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
uint32_t space;
|
||||||
|
|
||||||
|
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||||
|
uint32_t isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||||
|
|
||||||
|
space = instance->container.uxLength - instance->container.uxMessagesWaiting;
|
||||||
|
|
||||||
|
taskEXIT_CRITICAL_FROM_ISR(isrm);
|
||||||
|
} else {
|
||||||
|
space = uxQueueSpacesAvailable((QueueHandle_t)instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FuriEventLoopLink* furi_semaphore_event_loop_get_link(FuriEventLoopObject* object) {
|
||||||
|
FuriSemaphore* instance = object;
|
||||||
|
furi_assert(instance);
|
||||||
|
return &instance->event_loop_link;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
furi_semaphore_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) {
|
||||||
|
FuriSemaphore* instance = object;
|
||||||
|
furi_assert(instance);
|
||||||
|
|
||||||
|
if(event == FuriEventLoopEventIn) {
|
||||||
|
return furi_semaphore_get_count(instance);
|
||||||
|
} else if(event == FuriEventLoopEventOut) {
|
||||||
|
return furi_semaphore_get_space(instance);
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FuriEventLoopContract furi_semaphore_event_loop_contract = {
|
||||||
|
.get_link = furi_semaphore_event_loop_get_link,
|
||||||
|
.get_level = furi_semaphore_event_loop_get_level,
|
||||||
|
};
|
||||||
|
|
|
@ -53,6 +53,14 @@ FuriStatus furi_semaphore_release(FuriSemaphore* instance);
|
||||||
*/
|
*/
|
||||||
uint32_t furi_semaphore_get_count(FuriSemaphore* instance);
|
uint32_t furi_semaphore_get_count(FuriSemaphore* instance);
|
||||||
|
|
||||||
|
/** Get available space
|
||||||
|
*
|
||||||
|
* @param instance The pointer to FuriSemaphore instance
|
||||||
|
*
|
||||||
|
* @return Semaphore available space
|
||||||
|
*/
|
||||||
|
uint32_t furi_semaphore_get_space(FuriSemaphore* instance);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
#include "stream_buffer.h"
|
#include "stream_buffer.h"
|
||||||
|
|
||||||
#include "check.h"
|
|
||||||
#include "common_defines.h"
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <FreeRTOS-Kernel/include/stream_buffer.h>
|
#include <FreeRTOS-Kernel/include/stream_buffer.h>
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
#include "common_defines.h"
|
||||||
|
|
||||||
|
#include "event_loop_link_i.h"
|
||||||
|
|
||||||
|
// Internal FreeRTOS member names
|
||||||
|
#define xTriggerLevelBytes uxDummy1[3]
|
||||||
|
|
||||||
struct FuriStreamBuffer {
|
struct FuriStreamBuffer {
|
||||||
StaticStreamBuffer_t container;
|
StaticStreamBuffer_t container;
|
||||||
|
FuriEventLoopLink event_loop_link;
|
||||||
uint8_t buffer[];
|
uint8_t buffer[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +40,10 @@ FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level) {
|
||||||
void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) {
|
void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) {
|
||||||
furi_check(stream_buffer);
|
furi_check(stream_buffer);
|
||||||
|
|
||||||
|
// Event Loop must be disconnected
|
||||||
|
furi_check(!stream_buffer->event_loop_link.item_in);
|
||||||
|
furi_check(!stream_buffer->event_loop_link.item_out);
|
||||||
|
|
||||||
vStreamBufferDelete((StreamBufferHandle_t)stream_buffer);
|
vStreamBufferDelete((StreamBufferHandle_t)stream_buffer);
|
||||||
free(stream_buffer);
|
free(stream_buffer);
|
||||||
}
|
}
|
||||||
|
@ -61,6 +71,16 @@ size_t furi_stream_buffer_send(
|
||||||
ret = xStreamBufferSend((StreamBufferHandle_t)stream_buffer, data, length, timeout);
|
ret = xStreamBufferSend((StreamBufferHandle_t)stream_buffer, data, length, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ret > 0) {
|
||||||
|
const size_t bytes_available =
|
||||||
|
xStreamBufferBytesAvailable((StreamBufferHandle_t)stream_buffer);
|
||||||
|
const size_t trigger_level = ((StaticStreamBuffer_t*)stream_buffer)->xTriggerLevelBytes;
|
||||||
|
|
||||||
|
if(bytes_available >= trigger_level) {
|
||||||
|
furi_event_loop_link_notify(&stream_buffer->event_loop_link, FuriEventLoopEventIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +102,10 @@ size_t furi_stream_buffer_receive(
|
||||||
ret = xStreamBufferReceive((StreamBufferHandle_t)stream_buffer, data, length, timeout);
|
ret = xStreamBufferReceive((StreamBufferHandle_t)stream_buffer, data, length, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ret > 0) {
|
||||||
|
furi_event_loop_link_notify(&stream_buffer->event_loop_link, FuriEventLoopEventOut);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +136,42 @@ bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer) {
|
||||||
FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer) {
|
FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer) {
|
||||||
furi_check(stream_buffer);
|
furi_check(stream_buffer);
|
||||||
|
|
||||||
|
FuriStatus status;
|
||||||
|
|
||||||
if(xStreamBufferReset((StreamBufferHandle_t)stream_buffer) == pdPASS) {
|
if(xStreamBufferReset((StreamBufferHandle_t)stream_buffer) == pdPASS) {
|
||||||
return FuriStatusOk;
|
status = FuriStatusOk;
|
||||||
} else {
|
} else {
|
||||||
return FuriStatusError;
|
status = FuriStatusError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status == FuriStatusOk) {
|
||||||
|
furi_event_loop_link_notify(&stream_buffer->event_loop_link, FuriEventLoopEventOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FuriEventLoopLink* furi_stream_buffer_event_loop_get_link(FuriEventLoopObject* object) {
|
||||||
|
FuriStreamBuffer* stream_buffer = object;
|
||||||
|
furi_assert(stream_buffer);
|
||||||
|
return &stream_buffer->event_loop_link;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
furi_stream_buffer_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) {
|
||||||
|
FuriStreamBuffer* stream_buffer = object;
|
||||||
|
furi_assert(stream_buffer);
|
||||||
|
|
||||||
|
if(event == FuriEventLoopEventIn) {
|
||||||
|
return xStreamBufferBytesAvailable((StreamBufferHandle_t)stream_buffer);
|
||||||
|
} else if(event == FuriEventLoopEventOut) {
|
||||||
|
return xStreamBufferSpacesAvailable((StreamBufferHandle_t)stream_buffer);
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FuriEventLoopContract furi_stream_buffer_event_loop_contract = {
|
||||||
|
.get_link = furi_stream_buffer_event_loop_get_link,
|
||||||
|
.get_level = furi_stream_buffer_event_loop_get_level,
|
||||||
|
};
|
||||||
|
|
|
@ -32,7 +32,6 @@ libs = env.BuildModules(
|
||||||
"digital_signal",
|
"digital_signal",
|
||||||
"pulse_reader",
|
"pulse_reader",
|
||||||
"signal_reader",
|
"signal_reader",
|
||||||
"appframe",
|
|
||||||
"u8g2",
|
"u8g2",
|
||||||
"lfrfid",
|
"lfrfid",
|
||||||
"flipper_application",
|
"flipper_application",
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
template <typename TApp>
|
|
||||||
class GenericScene {
|
|
||||||
public:
|
|
||||||
virtual void on_enter(TApp* app, bool need_restore) = 0;
|
|
||||||
virtual bool on_event(TApp* app, typename TApp::Event* event) = 0;
|
|
||||||
virtual void on_exit(TApp* app) = 0;
|
|
||||||
virtual ~GenericScene() {};
|
|
||||||
|
|
||||||
private:
|
|
||||||
};
|
|
|
@ -1,47 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <core/record.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Class for opening, casting, holding and closing records
|
|
||||||
*
|
|
||||||
* @tparam TRecordClass record class
|
|
||||||
*/
|
|
||||||
template <typename TRecordClass>
|
|
||||||
class RecordController {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new Record Controller object for record with record name
|
|
||||||
*
|
|
||||||
* @param record_name record name
|
|
||||||
*/
|
|
||||||
RecordController(const char* record_name) {
|
|
||||||
name = record_name;
|
|
||||||
value = static_cast<TRecordClass*>(furi_record_open(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
~RecordController() {
|
|
||||||
furi_record_close(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Record getter
|
|
||||||
*
|
|
||||||
* @return TRecordClass* record value
|
|
||||||
*/
|
|
||||||
TRecordClass* get() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Record getter (by cast)
|
|
||||||
*
|
|
||||||
* @return TRecordClass* record value
|
|
||||||
*/
|
|
||||||
operator TRecordClass*() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char* name;
|
|
||||||
TRecordClass* value;
|
|
||||||
};
|
|
|
@ -1,246 +0,0 @@
|
||||||
#include <map>
|
|
||||||
#include <forward_list>
|
|
||||||
#include <initializer_list>
|
|
||||||
|
|
||||||
#define GENERIC_SCENE_ENUM_VALUES Exit, Start
|
|
||||||
#define GENERIC_EVENT_ENUM_VALUES Tick, Back
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Controller for scene navigation in application
|
|
||||||
*
|
|
||||||
* @tparam TScene generic scene class
|
|
||||||
* @tparam TApp application class
|
|
||||||
*/
|
|
||||||
template <typename TScene, typename TApp>
|
|
||||||
class SceneController {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Add scene to scene container
|
|
||||||
*
|
|
||||||
* @param scene_index scene index
|
|
||||||
* @param scene_pointer scene object pointer
|
|
||||||
*/
|
|
||||||
void add_scene(typename TApp::SceneType scene_index, TScene* scene_pointer) {
|
|
||||||
furi_check(scenes.count(scene_index) == 0);
|
|
||||||
scenes[scene_index] = scene_pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Switch to next scene and store current scene in previous scenes list
|
|
||||||
*
|
|
||||||
* @param scene_index next scene index
|
|
||||||
* @param need_restore true, if we want the scene to restore its parameters
|
|
||||||
*/
|
|
||||||
void switch_to_next_scene(typename TApp::SceneType scene_index, bool need_restore = false) {
|
|
||||||
previous_scenes_list.push_front(current_scene_index);
|
|
||||||
switch_to_scene(scene_index, need_restore);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Switch to next scene without ability to return to current scene
|
|
||||||
*
|
|
||||||
* @param scene_index next scene index
|
|
||||||
* @param need_restore true, if we want the scene to restore its parameters
|
|
||||||
*/
|
|
||||||
void switch_to_scene(typename TApp::SceneType scene_index, bool need_restore = false) {
|
|
||||||
if(scene_index != TApp::SceneType::Exit) {
|
|
||||||
scenes[current_scene_index]->on_exit(app);
|
|
||||||
current_scene_index = scene_index;
|
|
||||||
scenes[current_scene_index]->on_enter(app, need_restore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Search the scene in the list of previous scenes and switch to it
|
|
||||||
*
|
|
||||||
* @param scene_index_list list of scene indexes to which you want to switch
|
|
||||||
*/
|
|
||||||
bool search_and_switch_to_previous_scene(
|
|
||||||
const std::initializer_list<typename TApp::SceneType>& scene_index_list) {
|
|
||||||
auto previous_scene_index = TApp::SceneType::Exit;
|
|
||||||
bool scene_found = false;
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
while(!scene_found) {
|
|
||||||
previous_scene_index = get_previous_scene_index();
|
|
||||||
for(const auto& element : scene_index_list) {
|
|
||||||
if(previous_scene_index == element) {
|
|
||||||
scene_found = true;
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(previous_scene_index == TApp::SceneType::Exit) {
|
|
||||||
scene_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result) {
|
|
||||||
switch_to_scene(previous_scene_index, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool search_and_switch_to_another_scene(
|
|
||||||
const std::initializer_list<typename TApp::SceneType>& scene_index_list,
|
|
||||||
typename TApp::SceneType scene_index) {
|
|
||||||
auto previous_scene_index = TApp::SceneType::Exit;
|
|
||||||
bool scene_found = false;
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
while(!scene_found) {
|
|
||||||
previous_scene_index = get_previous_scene_index();
|
|
||||||
for(const auto& element : scene_index_list) {
|
|
||||||
if(previous_scene_index == element) {
|
|
||||||
scene_found = true;
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(previous_scene_index == TApp::SceneType::Exit) {
|
|
||||||
scene_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result) {
|
|
||||||
switch_to_scene(scene_index, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_previous_scene(
|
|
||||||
const std::initializer_list<typename TApp::SceneType>& scene_index_list) {
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
for(auto const& previous_element : previous_scenes_list) {
|
|
||||||
for(const auto& element : scene_index_list) {
|
|
||||||
if(previous_element == element) {
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(previous_element == TApp::SceneType::Exit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Start application main cycle
|
|
||||||
*
|
|
||||||
* @param tick_length_ms tick event length in milliseconds
|
|
||||||
*/
|
|
||||||
void process(
|
|
||||||
uint32_t /* tick_length_ms */ = 100,
|
|
||||||
typename TApp::SceneType start_scene_index = TApp::SceneType::Start) {
|
|
||||||
typename TApp::Event event;
|
|
||||||
bool consumed;
|
|
||||||
bool exit = false;
|
|
||||||
|
|
||||||
current_scene_index = start_scene_index;
|
|
||||||
scenes[current_scene_index]->on_enter(app, false);
|
|
||||||
|
|
||||||
while(!exit) {
|
|
||||||
app->view_controller.receive_event(&event);
|
|
||||||
|
|
||||||
consumed = scenes[current_scene_index]->on_event(app, &event);
|
|
||||||
|
|
||||||
if(!consumed) {
|
|
||||||
if(event.type == TApp::EventType::Back) {
|
|
||||||
exit = switch_to_previous_scene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
scenes[current_scene_index]->on_exit(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Switch to previous scene
|
|
||||||
*
|
|
||||||
* @param count how many steps back
|
|
||||||
* @return true if app need to exit
|
|
||||||
*/
|
|
||||||
bool switch_to_previous_scene(uint8_t count = 1) {
|
|
||||||
auto previous_scene_index = TApp::SceneType::Start;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < count; i++)
|
|
||||||
previous_scene_index = get_previous_scene_index();
|
|
||||||
|
|
||||||
if(previous_scene_index == TApp::SceneType::Exit) return true;
|
|
||||||
|
|
||||||
switch_to_scene(previous_scene_index, true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Construct a new Scene Controller object
|
|
||||||
*
|
|
||||||
* @param app_pointer pointer to application class
|
|
||||||
*/
|
|
||||||
SceneController(TApp* app_pointer) {
|
|
||||||
app = app_pointer;
|
|
||||||
current_scene_index = TApp::SceneType::Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destroy the Scene Controller object
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
~SceneController() {
|
|
||||||
for(auto& it : scenes)
|
|
||||||
delete it.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief Scenes pointers container
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
std::map<typename TApp::SceneType, TScene*> scenes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief List of indexes of previous scenes
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
std::forward_list<typename TApp::SceneType> previous_scenes_list;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Current scene index holder
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typename TApp::SceneType current_scene_index;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Application pointer holder
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
TApp* app;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the previous scene index
|
|
||||||
*
|
|
||||||
* @return previous scene index
|
|
||||||
*/
|
|
||||||
typename TApp::SceneType get_previous_scene_index() {
|
|
||||||
auto scene_index = TApp::SceneType::Exit;
|
|
||||||
|
|
||||||
if(!previous_scenes_list.empty()) {
|
|
||||||
scene_index = previous_scenes_list.front();
|
|
||||||
previous_scenes_list.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
return scene_index;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,18 +0,0 @@
|
||||||
#include "text_store.h"
|
|
||||||
#include <furi.h>
|
|
||||||
|
|
||||||
TextStore::TextStore(uint8_t _text_size)
|
|
||||||
: text_size(_text_size) {
|
|
||||||
text = static_cast<char*>(malloc(text_size + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStore::~TextStore() {
|
|
||||||
free(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextStore::set(const char* _text...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, _text);
|
|
||||||
vsnprintf(text, text_size, _text, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
class TextStore {
|
|
||||||
public:
|
|
||||||
TextStore(uint8_t text_size);
|
|
||||||
~TextStore(void);
|
|
||||||
|
|
||||||
void set(const char* text...);
|
|
||||||
const uint8_t text_size;
|
|
||||||
char* text;
|
|
||||||
};
|
|
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
* type_index without RTTI
|
|
||||||
*
|
|
||||||
* Copyright frickiericker 2016.
|
|
||||||
* Distributed under the Boost Software License, Version 1.0.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
* obtaining a copy of the software and accompanying documentation covered by
|
|
||||||
* this license (the "Software") to use, reproduce, display, distribute,
|
|
||||||
* execute, and transmit the Software, and to prepare derivative works of the
|
|
||||||
* Software, and to permit third-parties to whom the Software is furnished to
|
|
||||||
* do so, all subject to the following:
|
|
||||||
*
|
|
||||||
* The copyright notices in the Software and this entire statement, including
|
|
||||||
* the above license grant, this restriction and the following disclaimer,
|
|
||||||
* must be included in all copies of the Software, in whole or in part, and
|
|
||||||
* all derivative works of the Software, unless such copies or derivative
|
|
||||||
* works are solely in the form of machine-executable object code generated by
|
|
||||||
* a source language processor.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace ext {
|
|
||||||
/**
|
|
||||||
* Dummy type for tag-dispatching.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
struct tag_type {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A value of tag_type<T>.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
constexpr tag_type<T> tag{};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type_index implementation without RTTI.
|
|
||||||
*/
|
|
||||||
struct type_index {
|
|
||||||
/**
|
|
||||||
* Creates a type_index object for the specified type.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
type_index(tag_type<T>) noexcept
|
|
||||||
: hash_code_{index<T>} {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the hash code.
|
|
||||||
*/
|
|
||||||
std::size_t hash_code() const noexcept {
|
|
||||||
return hash_code_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Unique integral index associated to template type argument.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
static std::size_t const index;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global counter for generating index values.
|
|
||||||
*/
|
|
||||||
static std::size_t& counter() noexcept {
|
|
||||||
static std::size_t counter_;
|
|
||||||
return counter_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::size_t hash_code_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename>
|
|
||||||
std::size_t const type_index::index = type_index::counter()++;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a type_index object for the specified type.
|
|
||||||
*
|
|
||||||
* Equivalent to `ext::type_index{ext::tag<T>}`.
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
type_index make_type_index() noexcept {
|
|
||||||
return tag<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator==(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return a.hash_code() == b.hash_code();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator!=(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator<(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return a.hash_code() < b.hash_code();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator<=(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return a.hash_code() <= b.hash_code();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator>(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return !(a <= b);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator>=(type_index const& a, type_index const& b) noexcept {
|
|
||||||
return !(a < b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct std::hash<ext::type_index> {
|
|
||||||
using argument_type = ext::type_index;
|
|
||||||
using result_type = std::size_t;
|
|
||||||
|
|
||||||
result_type operator()(argument_type const& t) const noexcept {
|
|
||||||
return t.hash_code();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,170 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "view_modules/generic_view_module.h"
|
|
||||||
#include <map>
|
|
||||||
#include <core/check.h>
|
|
||||||
#include <gui/view_dispatcher.h>
|
|
||||||
#include <callback-connector.h>
|
|
||||||
#include "typeindex_no_rtti.hpp"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Controller for switching application views and handling inputs and events
|
|
||||||
*
|
|
||||||
* @tparam TApp application class
|
|
||||||
* @tparam TViewModules variadic list of ViewModules
|
|
||||||
*/
|
|
||||||
template <typename TApp, typename... TViewModules>
|
|
||||||
class ViewController {
|
|
||||||
public:
|
|
||||||
ViewController() {
|
|
||||||
event_queue = furi_message_queue_alloc(10, sizeof(typename TApp::Event));
|
|
||||||
|
|
||||||
view_dispatcher = view_dispatcher_alloc();
|
|
||||||
previous_view_callback_pointer = cbc::obtain_connector(
|
|
||||||
this, &ViewController<TApp, TViewModules...>::previous_view_callback);
|
|
||||||
|
|
||||||
[](...) {
|
|
||||||
}((this->add_view(ext::make_type_index<TViewModules>().hash_code(), new TViewModules()),
|
|
||||||
0)...);
|
|
||||||
|
|
||||||
gui = static_cast<Gui*>(furi_record_open("gui"));
|
|
||||||
}
|
|
||||||
|
|
||||||
~ViewController() {
|
|
||||||
for(auto& it : holder) {
|
|
||||||
view_dispatcher_remove_view(view_dispatcher, static_cast<uint32_t>(it.first));
|
|
||||||
delete it.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
view_dispatcher_free(view_dispatcher);
|
|
||||||
furi_message_queue_free(event_queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get ViewModule pointer
|
|
||||||
*
|
|
||||||
* @tparam T Concrete ViewModule class
|
|
||||||
* @return T* ViewModule pointer
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
T* get() {
|
|
||||||
uint32_t view_index = ext::make_type_index<T>().hash_code();
|
|
||||||
furi_check(holder.count(view_index) != 0);
|
|
||||||
return static_cast<T*>(holder[view_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get ViewModule pointer by cast
|
|
||||||
*
|
|
||||||
* @tparam T Concrete ViewModule class
|
|
||||||
* @return T* ViewModule pointer
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
operator T*() {
|
|
||||||
uint32_t view_index = ext::make_type_index<T>().hash_code();
|
|
||||||
furi_check(holder.count(view_index) != 0);
|
|
||||||
return static_cast<T*>(holder[view_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Switch view to ViewModule
|
|
||||||
*
|
|
||||||
* @tparam T Concrete ViewModule class
|
|
||||||
* @return T* ViewModule pointer
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
void switch_to() {
|
|
||||||
uint32_t view_index = ext::make_type_index<T>().hash_code();
|
|
||||||
furi_check(holder.count(view_index) != 0);
|
|
||||||
view_dispatcher_switch_to_view(view_dispatcher, view_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Receive event from app event queue
|
|
||||||
*
|
|
||||||
* @param event event pointer
|
|
||||||
*/
|
|
||||||
void receive_event(typename TApp::Event* event) {
|
|
||||||
if(furi_message_queue_get(event_queue, event, 100) != FuriStatusOk) {
|
|
||||||
event->type = TApp::EventType::Tick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Send event to app event queue
|
|
||||||
*
|
|
||||||
* @param event event pointer
|
|
||||||
*/
|
|
||||||
void send_event(typename TApp::Event* event) {
|
|
||||||
FuriStatus result = furi_message_queue_put(event_queue, event, FuriWaitForever);
|
|
||||||
furi_check(result == FuriStatusOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void attach_to_gui(ViewDispatcherType type) {
|
|
||||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief ViewModulesHolder
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
std::map<size_t, GenericViewModule*> holder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief App event queue
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
FuriMessageQueue* event_queue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Main ViewDispatcher pointer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ViewDispatcher* view_dispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gui record pointer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Gui* gui;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Previous view callback fn pointer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ViewNavigationCallback previous_view_callback_pointer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Previous view callback fn
|
|
||||||
*
|
|
||||||
* @param context not used
|
|
||||||
* @return uint32_t VIEW_IGNORE
|
|
||||||
*/
|
|
||||||
uint32_t previous_view_callback(void* context) {
|
|
||||||
(void)context;
|
|
||||||
|
|
||||||
typename TApp::Event event;
|
|
||||||
event.type = TApp::EventType::Back;
|
|
||||||
|
|
||||||
if(event_queue != NULL) {
|
|
||||||
send_event(&event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return VIEW_IGNORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add ViewModule to holder
|
|
||||||
*
|
|
||||||
* @param view_index view index in holder
|
|
||||||
* @param view_module view module pointer
|
|
||||||
*/
|
|
||||||
void add_view(size_t view_index, GenericViewModule* view_module) {
|
|
||||||
furi_check(holder.count(view_index) == 0);
|
|
||||||
holder[view_index] = view_module;
|
|
||||||
|
|
||||||
View* view = view_module->get_view();
|
|
||||||
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_index), view);
|
|
||||||
view_set_previous_callback(view, previous_view_callback_pointer);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
#include "byte_input_vm.h"
|
|
||||||
|
|
||||||
ByteInputVM::ByteInputVM() {
|
|
||||||
byte_input = byte_input_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteInputVM::~ByteInputVM() {
|
|
||||||
byte_input_free(byte_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* ByteInputVM::get_view() {
|
|
||||||
return byte_input_get_view(byte_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ByteInputVM::clean() {
|
|
||||||
byte_input_set_header_text(byte_input, "");
|
|
||||||
byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ByteInputVM::set_result_callback(
|
|
||||||
ByteInputCallback input_callback,
|
|
||||||
ByteChangedCallback changed_callback,
|
|
||||||
void* callback_context,
|
|
||||||
uint8_t* bytes,
|
|
||||||
uint8_t bytes_count) {
|
|
||||||
byte_input_set_result_callback(
|
|
||||||
byte_input, input_callback, changed_callback, callback_context, bytes, bytes_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ByteInputVM::set_header_text(const char* text) {
|
|
||||||
byte_input_set_header_text(byte_input, text);
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "generic_view_module.h"
|
|
||||||
#include <gui/modules/byte_input.h>
|
|
||||||
|
|
||||||
class ByteInputVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
ByteInputVM(void);
|
|
||||||
~ByteInputVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set byte input result callback
|
|
||||||
*
|
|
||||||
* @param input_callback input callback fn
|
|
||||||
* @param changed_callback changed callback fn
|
|
||||||
* @param callback_context callback context
|
|
||||||
* @param bytes buffer to use
|
|
||||||
* @param bytes_count buffer length
|
|
||||||
*/
|
|
||||||
void set_result_callback(
|
|
||||||
ByteInputCallback input_callback,
|
|
||||||
ByteChangedCallback changed_callback,
|
|
||||||
void* callback_context,
|
|
||||||
uint8_t* bytes,
|
|
||||||
uint8_t bytes_count);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set byte input header text
|
|
||||||
*
|
|
||||||
* @param text text to be shown
|
|
||||||
*/
|
|
||||||
void set_header_text(const char* text);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ByteInput* byte_input;
|
|
||||||
};
|
|
|
@ -1,61 +0,0 @@
|
||||||
#include "dialog_ex_vm.h"
|
|
||||||
|
|
||||||
DialogExVM::DialogExVM() {
|
|
||||||
dialog_ex = dialog_ex_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
DialogExVM::~DialogExVM() {
|
|
||||||
dialog_ex_free(dialog_ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* DialogExVM::get_view() {
|
|
||||||
return dialog_ex_get_view(dialog_ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::clean() {
|
|
||||||
set_result_callback(NULL);
|
|
||||||
set_context(NULL);
|
|
||||||
set_header(NULL, 0, 0, AlignLeft, AlignBottom);
|
|
||||||
set_text(NULL, 0, 0, AlignLeft, AlignBottom);
|
|
||||||
set_icon(0, 0, NULL);
|
|
||||||
set_left_button_text(NULL);
|
|
||||||
set_center_button_text(NULL);
|
|
||||||
set_right_button_text(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_result_callback(DialogExResultCallback callback) {
|
|
||||||
dialog_ex_set_result_callback(dialog_ex, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_context(void* context) {
|
|
||||||
dialog_ex_set_context(dialog_ex, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_header(
|
|
||||||
const char* text,
|
|
||||||
uint8_t x,
|
|
||||||
uint8_t y,
|
|
||||||
Align horizontal,
|
|
||||||
Align vertical) {
|
|
||||||
dialog_ex_set_header(dialog_ex, text, x, y, horizontal, vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) {
|
|
||||||
dialog_ex_set_text(dialog_ex, text, x, y, horizontal, vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_icon(uint8_t x, uint8_t y, const Icon* icon) {
|
|
||||||
dialog_ex_set_icon(dialog_ex, x, y, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_left_button_text(const char* text) {
|
|
||||||
dialog_ex_set_left_button_text(dialog_ex, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_center_button_text(const char* text) {
|
|
||||||
dialog_ex_set_center_button_text(dialog_ex, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DialogExVM::set_right_button_text(const char* text) {
|
|
||||||
dialog_ex_set_right_button_text(dialog_ex, text);
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "generic_view_module.h"
|
|
||||||
#include <gui/modules/dialog_ex.h>
|
|
||||||
|
|
||||||
class DialogExVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
DialogExVM(void);
|
|
||||||
~DialogExVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dialog result callback
|
|
||||||
* @param callback - result callback function
|
|
||||||
*/
|
|
||||||
void set_result_callback(DialogExResultCallback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dialog context
|
|
||||||
* @param context - context pointer, will be passed to result callback
|
|
||||||
*/
|
|
||||||
void set_context(void* context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dialog header text
|
|
||||||
* If text is null, dialog header will not be rendered
|
|
||||||
* @param text - text to be shown, can be multiline
|
|
||||||
* @param x, y - text position
|
|
||||||
* @param horizontal, vertical - text aligment
|
|
||||||
*/
|
|
||||||
void set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dialog text
|
|
||||||
* If text is null, dialog text will not be rendered
|
|
||||||
* @param text - text to be shown, can be multiline
|
|
||||||
* @param x, y - text position
|
|
||||||
* @param horizontal, vertical - text aligment
|
|
||||||
*/
|
|
||||||
void set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dialog icon
|
|
||||||
* If x or y is negative, dialog icon will not be rendered
|
|
||||||
* @param x, y - icon position
|
|
||||||
* @param name - icon to be shown
|
|
||||||
*/
|
|
||||||
void set_icon(uint8_t x, uint8_t y, const Icon* icon);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set left button text
|
|
||||||
* If text is null, left button will not be rendered and processed
|
|
||||||
* @param text - text to be shown
|
|
||||||
*/
|
|
||||||
void set_left_button_text(const char* text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set center button text
|
|
||||||
* If text is null, center button will not be rendered and processed
|
|
||||||
* @param text - text to be shown
|
|
||||||
*/
|
|
||||||
void set_center_button_text(const char* text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set right button text
|
|
||||||
* If text is null, right button will not be rendered and processed
|
|
||||||
* @param text - text to be shown
|
|
||||||
*/
|
|
||||||
void set_right_button_text(const char* text);
|
|
||||||
|
|
||||||
private:
|
|
||||||
DialogEx* dialog_ex;
|
|
||||||
};
|
|
|
@ -1,10 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <gui/view.h>
|
|
||||||
|
|
||||||
class GenericViewModule {
|
|
||||||
public:
|
|
||||||
GenericViewModule() {};
|
|
||||||
virtual ~GenericViewModule() {};
|
|
||||||
virtual View* get_view() = 0;
|
|
||||||
virtual void clean() = 0;
|
|
||||||
};
|
|
|
@ -1,56 +0,0 @@
|
||||||
#include "popup_vm.h"
|
|
||||||
#include <gui/modules/popup.h>
|
|
||||||
|
|
||||||
PopupVM::PopupVM() {
|
|
||||||
popup = popup_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupVM::~PopupVM() {
|
|
||||||
popup_free(popup);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* PopupVM::get_view() {
|
|
||||||
return popup_get_view(popup);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::clean() {
|
|
||||||
set_callback(NULL);
|
|
||||||
set_context(NULL);
|
|
||||||
set_header(NULL, 0, 0, AlignLeft, AlignBottom);
|
|
||||||
set_text(NULL, 0, 0, AlignLeft, AlignBottom);
|
|
||||||
set_icon(0, 0, NULL);
|
|
||||||
disable_timeout();
|
|
||||||
set_timeout(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_callback(PopupCallback callback) {
|
|
||||||
popup_set_callback(popup, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_context(void* context) {
|
|
||||||
popup_set_context(popup, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) {
|
|
||||||
popup_set_header(popup, text, x, y, horizontal, vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) {
|
|
||||||
popup_set_text(popup, text, x, y, horizontal, vertical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_icon(int8_t x, int8_t y, const Icon* icon) {
|
|
||||||
popup_set_icon(popup, x, y, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::set_timeout(uint32_t timeout_in_ms) {
|
|
||||||
popup_set_timeout(popup, timeout_in_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::enable_timeout() {
|
|
||||||
popup_enable_timeout(popup);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupVM::disable_timeout() {
|
|
||||||
popup_disable_timeout(popup);
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "generic_view_module.h"
|
|
||||||
#include <gui/modules/popup.h>
|
|
||||||
|
|
||||||
class PopupVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
PopupVM(void);
|
|
||||||
~PopupVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup header text
|
|
||||||
* @param text - text to be shown
|
|
||||||
*/
|
|
||||||
void set_callback(PopupCallback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup context
|
|
||||||
* @param context - context pointer, will be passed to result callback
|
|
||||||
*/
|
|
||||||
void set_context(void* context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup header text
|
|
||||||
* If text is null, popup header will not be rendered
|
|
||||||
* @param text - text to be shown, can be multiline
|
|
||||||
* @param x, y - text position
|
|
||||||
* @param horizontal, vertical - text aligment
|
|
||||||
*/
|
|
||||||
void set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup text
|
|
||||||
* If text is null, popup text will not be rendered
|
|
||||||
* @param text - text to be shown, can be multiline
|
|
||||||
* @param x, y - text position
|
|
||||||
* @param horizontal, vertical - text aligment
|
|
||||||
*/
|
|
||||||
void set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup icon
|
|
||||||
* If icon position is negative, popup icon will not be rendered
|
|
||||||
* @param x, y - icon position
|
|
||||||
* @param name - icon to be shown
|
|
||||||
*/
|
|
||||||
void set_icon(int8_t x, int8_t y, const Icon* icon);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set popup timeout
|
|
||||||
* @param timeout_in_ms - popup timeout value in milliseconds
|
|
||||||
*/
|
|
||||||
void set_timeout(uint32_t timeout_in_ms);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable popup timeout
|
|
||||||
*/
|
|
||||||
void enable_timeout(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable popup timeout
|
|
||||||
*/
|
|
||||||
void disable_timeout(void);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Popup* popup;
|
|
||||||
};
|
|
|
@ -1,33 +0,0 @@
|
||||||
#include "submenu_vm.h"
|
|
||||||
|
|
||||||
SubmenuVM::SubmenuVM() {
|
|
||||||
submenu = submenu_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
SubmenuVM::~SubmenuVM() {
|
|
||||||
submenu_free(submenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* SubmenuVM::get_view() {
|
|
||||||
return submenu_get_view(submenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmenuVM::clean() {
|
|
||||||
submenu_reset(submenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmenuVM::add_item(
|
|
||||||
const char* label,
|
|
||||||
uint32_t index,
|
|
||||||
SubmenuItemCallback callback,
|
|
||||||
void* callback_context) {
|
|
||||||
submenu_add_item(submenu, label, index, callback, callback_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmenuVM::set_selected_item(uint32_t index) {
|
|
||||||
submenu_set_selected_item(submenu, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmenuVM::set_header(const char* header) {
|
|
||||||
submenu_set_header(submenu, header);
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "generic_view_module.h"
|
|
||||||
#include <gui/modules/submenu.h>
|
|
||||||
|
|
||||||
class SubmenuVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
SubmenuVM(void);
|
|
||||||
~SubmenuVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add item to submenu
|
|
||||||
*
|
|
||||||
* @param label - menu item label
|
|
||||||
* @param index - menu item index, used for callback, may be the same with other items
|
|
||||||
* @param callback - menu item callback
|
|
||||||
* @param callback_context - menu item callback context
|
|
||||||
*/
|
|
||||||
void add_item(
|
|
||||||
const char* label,
|
|
||||||
uint32_t index,
|
|
||||||
SubmenuItemCallback callback,
|
|
||||||
void* callback_context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set submenu item selector
|
|
||||||
*
|
|
||||||
* @param index index of the item to be selected
|
|
||||||
*/
|
|
||||||
void set_selected_item(uint32_t index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set optional header for submenu
|
|
||||||
*
|
|
||||||
* @param header header to set
|
|
||||||
*/
|
|
||||||
void set_header(const char* header);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Submenu* submenu;
|
|
||||||
};
|
|
|
@ -1,39 +0,0 @@
|
||||||
#include "text_input_vm.h"
|
|
||||||
|
|
||||||
TextInputVM::TextInputVM() {
|
|
||||||
text_input = text_input_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInputVM::~TextInputVM() {
|
|
||||||
text_input_free(text_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
View* TextInputVM::get_view() {
|
|
||||||
return text_input_get_view(text_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextInputVM::clean() {
|
|
||||||
text_input_reset(text_input);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextInputVM::set_result_callback(
|
|
||||||
TextInputCallback callback,
|
|
||||||
void* callback_context,
|
|
||||||
char* text,
|
|
||||||
uint8_t max_text_length,
|
|
||||||
bool clear_default_text) {
|
|
||||||
text_input_set_result_callback(
|
|
||||||
text_input, callback, callback_context, text, max_text_length, clear_default_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextInputVM::set_header_text(const char* text) {
|
|
||||||
text_input_set_header_text(text_input, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextInputVM::set_validator(TextInputValidatorCallback callback, void* callback_context) {
|
|
||||||
text_input_set_validator(text_input, callback, callback_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* TextInputVM::get_validator_callback_context() {
|
|
||||||
return text_input_get_validator_callback_context(text_input);
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "generic_view_module.h"
|
|
||||||
#include <gui/modules/text_input.h>
|
|
||||||
|
|
||||||
class TextInputVM : public GenericViewModule {
|
|
||||||
public:
|
|
||||||
TextInputVM(void);
|
|
||||||
~TextInputVM() final;
|
|
||||||
View* get_view() final;
|
|
||||||
void clean() final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set text input result callback
|
|
||||||
*
|
|
||||||
* @param callback - callback fn
|
|
||||||
* @param callback_context - callback context
|
|
||||||
* @param text - text buffer to use
|
|
||||||
* @param max_text_length - text buffer length
|
|
||||||
* @param clear_default_text - clears given buffer on OK event
|
|
||||||
*/
|
|
||||||
void set_result_callback(
|
|
||||||
TextInputCallback callback,
|
|
||||||
void* callback_context,
|
|
||||||
char* text,
|
|
||||||
uint8_t max_text_length,
|
|
||||||
bool clear_default_text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set text input header text
|
|
||||||
*
|
|
||||||
* @param text - text to be shown
|
|
||||||
*/
|
|
||||||
void set_header_text(const char* text);
|
|
||||||
|
|
||||||
void set_validator(TextInputValidatorCallback callback, void* callback_context);
|
|
||||||
|
|
||||||
void* get_validator_callback_context(void);
|
|
||||||
|
|
||||||
private:
|
|
||||||
TextInput* text_input;
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
Import("env")
|
|
||||||
|
|
||||||
env.Append(
|
|
||||||
CPPPATH=[
|
|
||||||
"#/lib/app-scened-template",
|
|
||||||
"#/lib/callback-connector",
|
|
||||||
],
|
|
||||||
LINT_SOURCES=[
|
|
||||||
Dir("app-scened-template"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
libenv = env.Clone(FW_LIB_NAME="appframe")
|
|
||||||
libenv.ApplyLibFlags()
|
|
||||||
|
|
||||||
sources = []
|
|
||||||
|
|
||||||
recurse_dirs = [
|
|
||||||
"app-scened-template",
|
|
||||||
"callback-connector",
|
|
||||||
]
|
|
||||||
|
|
||||||
for recurse_dir in recurse_dirs:
|
|
||||||
sources += libenv.GlobRecursive("*.c*", recurse_dir)
|
|
||||||
|
|
||||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
|
||||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
|
||||||
Return("lib")
|
|
|
@ -41,3 +41,7 @@ typedef FuriEventFlag* FuriApiLock;
|
||||||
#define api_lock_wait_unlock_and_free(_lock) \
|
#define api_lock_wait_unlock_and_free(_lock) \
|
||||||
api_lock_wait_unlock(_lock); \
|
api_lock_wait_unlock(_lock); \
|
||||||
api_lock_free(_lock);
|
api_lock_free(_lock);
|
||||||
|
|
||||||
|
#define api_lock_is_locked(_lock) (!(furi_event_flag_get(_lock) & API_LOCK_EVENT))
|
||||||
|
|
||||||
|
#define api_lock_relock(_lock) furi_event_flag_clear(_lock, API_LOCK_EVENT)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue