diff --git a/applications/external/text_viewer/LICENSE b/applications/external/text_viewer/LICENSE deleted file mode 100644 index 69004dc62..000000000 --- a/applications/external/text_viewer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Roman Shchekin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/applications/external/text_viewer/application.fam b/applications/external/text_viewer/application.fam index 6222dc643..65100d9d8 100644 --- a/applications/external/text_viewer/application.fam +++ b/applications/external/text_viewer/application.fam @@ -2,17 +2,17 @@ App( appid="text_viewer", name="Text Viewer", apptype=FlipperAppType.EXTERNAL, - entry_point="text_viewer_app", + entry_point="text_viewer", requires=[ "gui", "dialogs", ], - stack_size=2 * 1024, + stack_size=10 * 1024, order=20, fap_icon="icons/text_10px.png", fap_category="Tools", fap_icon_assets="icons", - fap_author="@kowalski7cc & @kyhwana", + fap_author="@Willy-JL", # Original by @kowalski7cc & @kyhwana, new has code borrowed from archive > show fap_version="1.0", fap_description="Text viewer application", ) diff --git a/applications/external/text_viewer/scenes/text_viewer_scene.c b/applications/external/text_viewer/scenes/text_viewer_scene.c new file mode 100644 index 000000000..a507da4af --- /dev/null +++ b/applications/external/text_viewer/scenes/text_viewer_scene.c @@ -0,0 +1,30 @@ +#include "text_viewer_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const text_viewer_on_enter_handlers[])(void*) = { +#include "text_viewer_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const text_viewer_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "text_viewer_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const text_viewer_on_exit_handlers[])(void* context) = { +#include "text_viewer_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers text_viewer_scene_handlers = { + .on_enter_handlers = text_viewer_on_enter_handlers, + .on_event_handlers = text_viewer_on_event_handlers, + .on_exit_handlers = text_viewer_on_exit_handlers, + .scene_num = TextViewerSceneNum, +}; diff --git a/applications/external/text_viewer/scenes/text_viewer_scene.h b/applications/external/text_viewer/scenes/text_viewer_scene.h new file mode 100644 index 000000000..a8770db3b --- /dev/null +++ b/applications/external/text_viewer/scenes/text_viewer_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) TextViewerScene##id, +typedef enum { +#include "text_viewer_scene_config.h" + TextViewerSceneNum, +} TextViewerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers text_viewer_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "text_viewer_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "text_viewer_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "text_viewer_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/text_viewer/scenes/text_viewer_scene_config.h b/applications/external/text_viewer/scenes/text_viewer_scene_config.h new file mode 100644 index 000000000..86c69441e --- /dev/null +++ b/applications/external/text_viewer/scenes/text_viewer_scene_config.h @@ -0,0 +1 @@ +ADD_SCENE(text_viewer, show, Show) diff --git a/applications/external/text_viewer/scenes/text_viewer_scene_show.c b/applications/external/text_viewer/scenes/text_viewer_scene_show.c new file mode 100644 index 000000000..3d5aa077a --- /dev/null +++ b/applications/external/text_viewer/scenes/text_viewer_scene_show.c @@ -0,0 +1,132 @@ +#include "../text_viewer.h" + +#define SHOW_MAX_FILE_SIZE 8000 + +void text_viewer_scene_show_widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + TextViewer* app = (TextViewer*)context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +static bool text_show_read_lines(File* file, FuriString* str_result) { + //furi_string_reset(str_result); + uint8_t buffer[SHOW_MAX_FILE_SIZE]; + + uint16_t read_count = storage_file_read(file, buffer, SHOW_MAX_FILE_SIZE); + if(storage_file_get_error(file) != FSE_OK) { + return false; + } + + for(uint16_t i = 0; i < read_count; i++) { + furi_string_push_back(str_result, buffer[i]); + } + + return true; +} + +void text_viewer_scene_show_on_enter(void* context) { + furi_assert(context); + TextViewer* app = context; + + FuriString* buffer; + buffer = furi_string_alloc(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + FileInfo fileinfo; + FS_Error error = storage_common_stat(storage, furi_string_get_cstr(app->path), &fileinfo); + if(error == FSE_OK) { + if((fileinfo.size < SHOW_MAX_FILE_SIZE) && (fileinfo.size > 2)) { + bool ok = storage_file_open( + file, furi_string_get_cstr(app->path), FSAM_READ, FSOM_OPEN_EXISTING); + if(ok) { + if(!text_show_read_lines(file, buffer)) { + goto text_file_read_err; + } + if(!furi_string_size(buffer)) { + goto text_file_read_err; + } + + storage_file_seek(file, 0, true); + + widget_add_text_scroll_element( + app->widget, 0, 0, 128, 64, furi_string_get_cstr(buffer)); + + } else { + text_file_read_err: + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 64, + AlignLeft, + AlignCenter, + "\e#Error:\nStorage file open error\e#", + false); + } + storage_file_close(file); + } else if(fileinfo.size < 2) { + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 64, + AlignLeft, + AlignCenter, + "\e#Error:\nFile is too small\e#", + false); + } else { + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 64, + AlignLeft, + AlignCenter, + "\e#Error:\nFile is too large to show\e#", + false); + } + } else { + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 64, + AlignLeft, + AlignCenter, + "\e#Error:\nFile system error\e#", + false); + } + + furi_string_free(buffer); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + view_dispatcher_switch_to_view(app->view_dispatcher, TextViewerViewWidget); +} + +bool text_viewer_scene_show_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + TextViewer* app = (TextViewer*)context; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_previous_scene(app->scene_manager); + return true; + } + return false; +} + +void text_viewer_scene_show_on_exit(void* context) { + furi_assert(context); + TextViewer* app = (TextViewer*)context; + + widget_reset(app->widget); +} diff --git a/applications/external/text_viewer/text_viewer.c b/applications/external/text_viewer/text_viewer.c index 6313175c2..782b44c6f 100644 --- a/applications/external/text_viewer/text_viewer.c +++ b/applications/external/text_viewer/text_viewer.c @@ -1,287 +1,84 @@ -#include -#include +#include "text_viewer.h" -#include -#include -#include -#include - -#include -#include -#include -#include - -#define TAG "TextViewer" - -#define TEXT_VIEWER_APP_PATH_FOLDER STORAGE_EXT_PATH_PREFIX -#define TEXT_VIEWER_APP_EXTENSION "*" - -#define TEXT_VIEWER_BYTES_PER_LINE 20u -#define TEXT_VIEWER_LINES_ON_SCREEN 5u -#define TEXT_VIEWER_BUF_SIZE (TEXT_VIEWER_LINES_ON_SCREEN * TEXT_VIEWER_BYTES_PER_LINE) - -typedef struct { - uint8_t file_bytes[TEXT_VIEWER_LINES_ON_SCREEN][TEXT_VIEWER_BYTES_PER_LINE]; - uint32_t file_offset; - uint32_t file_read_bytes; - uint32_t file_size; - Stream* stream; - bool mode; // Print address or content -} TextViewerModel; - -typedef struct { - TextViewerModel* model; - FuriMutex** mutex; - - FuriMessageQueue* input_queue; - - ViewPort* view_port; - Gui* gui; - Storage* storage; -} TextViewer; - -static void render_callback(Canvas* canvas, void* ctx) { - TextViewer* text_viewer = ctx; - furi_check(furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); - - canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - - //elements_button_left(canvas, text_viewer->model->mode ? "Addr" : "Text"); - text_viewer->model->mode = 1; //text mode - //elements_button_right(canvas, "Info"); - - int ROW_HEIGHT = 12; - int TOP_OFFSET = 10; - int LEFT_OFFSET = 3; - - uint32_t line_count = text_viewer->model->file_size / TEXT_VIEWER_BYTES_PER_LINE; - if(text_viewer->model->file_size % TEXT_VIEWER_BYTES_PER_LINE != 0) line_count += 1; - uint32_t first_line_on_screen = text_viewer->model->file_offset / TEXT_VIEWER_BYTES_PER_LINE; - if(line_count > TEXT_VIEWER_LINES_ON_SCREEN) { - uint8_t width = canvas_width(canvas); - elements_scrollbar_pos( - canvas, - width, - 0, - ROW_HEIGHT * TEXT_VIEWER_LINES_ON_SCREEN, - first_line_on_screen, // TODO - line_count - (TEXT_VIEWER_LINES_ON_SCREEN - 1)); - } - - char temp_buf[32]; - uint32_t row_iters = text_viewer->model->file_read_bytes / TEXT_VIEWER_BYTES_PER_LINE; - if(text_viewer->model->file_read_bytes % TEXT_VIEWER_BYTES_PER_LINE != 0) row_iters += 1; - - for(uint32_t i = 0; i < row_iters; ++i) { - uint32_t bytes_left_per_row = - text_viewer->model->file_read_bytes - i * TEXT_VIEWER_BYTES_PER_LINE; - bytes_left_per_row = MIN(bytes_left_per_row, TEXT_VIEWER_BYTES_PER_LINE); - - if(text_viewer->model->mode) { - memcpy(temp_buf, text_viewer->model->file_bytes[i], bytes_left_per_row); - temp_buf[bytes_left_per_row] = '\0'; - for(uint32_t j = 0; j < bytes_left_per_row; ++j) - if(!isprint((int)temp_buf[j])) temp_buf[j] = ' '; - - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); - } else { - uint32_t addr = text_viewer->model->file_offset + i * TEXT_VIEWER_BYTES_PER_LINE; - snprintf(temp_buf, 32, "%04lX", addr); - - canvas_set_font(canvas, FontKeyboard); - canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf); - } - } - - furi_mutex_release(text_viewer->mutex); +static bool text_viewer_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + TextViewer* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -static void input_callback(InputEvent* input_event, void* ctx) { - TextViewer* text_viewer = ctx; - if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) { - furi_message_queue_put(text_viewer->input_queue, input_event, 0); - } +static bool text_viewer_back_event_callback(void* context) { + furi_assert(context); + TextViewer* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static TextViewer* text_viewer_alloc() { - TextViewer* instance = malloc(sizeof(TextViewer)); +TextViewer* text_viewer_alloc() { + TextViewer* app = malloc(sizeof(TextViewer)); + app->gui = furi_record_open(RECORD_GUI); - instance->model = malloc(sizeof(TextViewerModel)); - memset(instance->model, 0x0, sizeof(TextViewerModel)); + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&text_viewer_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, text_viewer_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, text_viewer_back_event_callback); - instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - instance->view_port = view_port_alloc(); - view_port_draw_callback_set(instance->view_port, render_callback, instance); - view_port_input_callback_set(instance->view_port, input_callback, instance); + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, TextViewerViewWidget, widget_get_view(app->widget)); - instance->gui = furi_record_open(RECORD_GUI); - gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + app->path = furi_string_alloc(); - instance->storage = furi_record_open(RECORD_STORAGE); - - return instance; + return app; } -static void text_viewer_free(TextViewer* instance) { - furi_record_close(RECORD_STORAGE); +void text_viewer_free(TextViewer* app) { + furi_assert(app); + + view_dispatcher_remove_view(app->view_dispatcher, TextViewerViewWidget); + widget_free(app->widget); + + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + furi_string_free(app->path); - gui_remove_view_port(instance->gui, instance->view_port); furi_record_close(RECORD_GUI); - view_port_free(instance->view_port); - - furi_message_queue_free(instance->input_queue); - - furi_mutex_free(instance->mutex); - - if(instance->model->stream) buffered_file_stream_close(instance->model->stream); - - free(instance->model); - free(instance); + free(app); } -static bool text_viewer_open_file(TextViewer* text_viewer, const char* file_path) { - furi_assert(text_viewer); - furi_assert(file_path); - - text_viewer->model->stream = buffered_file_stream_alloc(text_viewer->storage); - bool isOk = true; - - do { - if(!buffered_file_stream_open( - text_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Unable to open stream: %s", file_path); - isOk = false; - break; - } - - text_viewer->model->file_size = stream_size(text_viewer->model->stream); - } while(false); - - return isOk; -} - -static bool text_viewer_read_file(TextViewer* text_viewer) { - furi_assert(text_viewer); - furi_assert(text_viewer->model->stream); - furi_assert(text_viewer->model->file_offset % TEXT_VIEWER_BYTES_PER_LINE == 0); - - memset(text_viewer->model->file_bytes, 0x0, TEXT_VIEWER_BUF_SIZE); - bool isOk = true; - - do { - uint32_t offset = text_viewer->model->file_offset; - if(!stream_seek(text_viewer->model->stream, offset, true)) { - FURI_LOG_E(TAG, "Unable to seek stream"); - isOk = false; - break; - } - - text_viewer->model->file_read_bytes = stream_read( - text_viewer->model->stream, - (uint8_t*)text_viewer->model->file_bytes, - TEXT_VIEWER_BUF_SIZE); - } while(false); - - return isOk; -} - -int32_t text_viewer_app(void* p) { - TextViewer* text_viewer = text_viewer_alloc(); - - FuriString* file_path; - file_path = furi_string_alloc(); +extern int32_t text_viewer(void* p) { + TextViewer* app = text_viewer_alloc(); do { if(p && strlen(p)) { - furi_string_set(file_path, (const char*)p); + furi_string_set(app->path, (const char*)p); } else { - furi_string_set(file_path, TEXT_VIEWER_APP_PATH_FOLDER); + furi_string_set(app->path, TEXT_VIEWER_PATH); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options( - &browser_options, TEXT_VIEWER_APP_EXTENSION, &I_text_10px); + &browser_options, TEXT_VIEWER_EXTENSION, &I_text_10px); browser_options.hide_ext = false; DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); + bool res = dialog_file_browser_show(dialogs, app->path, app->path, &browser_options); furi_record_close(RECORD_DIALOGS); if(!res) { - FURI_LOG_I(TAG, "No file selected"); break; } } - FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path)); - - if(!text_viewer_open_file(text_viewer, furi_string_get_cstr(file_path))) break; - text_viewer_read_file(text_viewer); - - InputEvent input; - while(1) { - if(furi_message_queue_get(text_viewer->input_queue, &input, 100) == FuriStatusOk) { - if(input.key == InputKeyBack) { - break; - } else if(input.key == InputKeyUp) { - furi_check( - furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); - if(text_viewer->model->file_offset > 0) { - text_viewer->model->file_offset -= TEXT_VIEWER_BYTES_PER_LINE; - if(!text_viewer_read_file(text_viewer)) break; - } - furi_mutex_release(text_viewer->mutex); - } else if(input.key == InputKeyDown) { - furi_check( - furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); - uint32_t last_byte_on_screen = - text_viewer->model->file_offset + text_viewer->model->file_read_bytes; - - if(text_viewer->model->file_size > last_byte_on_screen) { - text_viewer->model->file_offset += TEXT_VIEWER_BYTES_PER_LINE; - if(!text_viewer_read_file(text_viewer)) break; - } - furi_mutex_release(text_viewer->mutex); - } else if(input.key == InputKeyLeft) { - furi_check( - furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); - text_viewer->model->mode = !text_viewer->model->mode; - furi_mutex_release(text_viewer->mutex); - } else if(input.key == InputKeyRight) { - FuriString* buffer; - buffer = furi_string_alloc(); - furi_string_printf( - buffer, - "File path: %s\nFile size: %lu (0x%lX)", - furi_string_get_cstr(file_path), - text_viewer->model->file_size, - text_viewer->model->file_size); - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_header( - message, "Text Viewer v1.1", 16, 2, AlignLeft, AlignTop); - dialog_message_set_icon(message, &I_text_10px, 3, 2); - dialog_message_set_text( - message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop); - dialog_message_set_buttons(message, NULL, NULL, "Back"); - dialog_message_show(dialogs, message); - - furi_string_free(buffer); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - } - } - view_port_update(text_viewer->view_port); - } + scene_manager_next_scene(app->scene_manager, TextViewerSceneShow); + view_dispatcher_run(app->view_dispatcher); } while(false); - furi_string_free(file_path); - text_viewer_free(text_viewer); - + text_viewer_free(app); return 0; } diff --git a/applications/external/text_viewer/text_viewer.h b/applications/external/text_viewer/text_viewer.h new file mode 100644 index 000000000..f0427ca28 --- /dev/null +++ b/applications/external/text_viewer/text_viewer.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "text_viewer_icons.h" +#include "scenes/text_viewer_scene.h" + +#define TEXT_VIEWER_PATH STORAGE_EXT_PATH_PREFIX +#define TEXT_VIEWER_EXTENSION "*" + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Widget* widget; + + FuriString* path; +} TextViewer; + +typedef enum { + TextViewerViewWidget, +} TextViewerView;