From 18e4383bcf9d42b91434cb3f3676d818529b89d4 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 10 Nov 2024 12:33:38 +0100 Subject: [PATCH] Added image viewer app (#73) --- tactility/src/app_manifest.h | 2 + tactility/src/apps/system/files/files.c | 77 +++++++++++++++---- tactility/src/apps/system/files/files_data.c | 12 ++- .../apps/system/image_viewer/image_viewer.c | 44 +++++++++++ .../apps/system/image_viewer/image_viewer.h | 11 +++ tactility/src/tactility.c | 2 + 6 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 tactility/src/apps/system/image_viewer/image_viewer.c create mode 100644 tactility/src/apps/system/image_viewer/image_viewer.h diff --git a/tactility/src/app_manifest.h b/tactility/src/app_manifest.h index ee030b71..c4e7701a 100644 --- a/tactility/src/app_manifest.h +++ b/tactility/src/app_manifest.h @@ -13,6 +13,8 @@ typedef void* App; typedef enum { /** A desktop app sits at the root of the app stack managed by the Loader service */ AppTypeDesktop, + /** Apps that generally aren't started from the desktop (e.g. image viewer) */ + AppTypeHidden, /** Standard apps, provided by the system. */ AppTypeSystem, /** The apps that are launched/shown by the Settings app. The Settings app itself is of type AppTypeSystem. */ diff --git a/tactility/src/apps/system/files/files.c b/tactility/src/apps/system/files/files.c index 17abebc2..b269352c 100644 --- a/tactility/src/apps/system/files/files.c +++ b/tactility/src/apps/system/files/files.c @@ -1,6 +1,7 @@ #include "files_data.h" #include "app.h" +#include "apps/system/image_viewer/image_viewer.h" #include "assets.h" #include "check.h" #include "file_utils.h" @@ -9,13 +10,14 @@ #include "string_utils.h" #include "ui/toolbar.h" #include +#include #define TAG "files_app" /** - * Lower case check to see if the given file matches the provided file extension + * Case-insensitive check to see if the given file matches the provided file extension. * @param path the full path to the file - * @param extension the extension to look for, including the period symbol + * @param extension the extension to look for, including the period symbol, in lower case * @return true on match */ static bool has_file_extension(const char* path, const char* extension) { @@ -26,7 +28,7 @@ static bool has_file_extension(const char* path, const char* extension) { } for (int i = (int)postfix_len - 1; i >= 0; i--) { - if (tolower(path[base_len - postfix_len + i]) != extension[i]) { + if (tolower(path[base_len - postfix_len + i]) != tolower(extension[i])) { return false; } } @@ -34,12 +36,9 @@ static bool has_file_extension(const char* path, const char* extension) { return true; } -static bool is_image_file(const char* filename) { - return has_file_extension(filename, ".jpg") || - has_file_extension(filename, ".png") || - has_file_extension(filename, ".jpeg") || - has_file_extension(filename, ".svg") || - has_file_extension(filename, ".bmp"); +static bool is_supported_image_file(const char* filename) { + // Currently only the PNG library is built into Tactility + return has_file_extension(filename, ".png"); } // region Views @@ -62,6 +61,44 @@ static void on_exit_app_pressed(TT_UNUSED lv_event_t* event) { loader_stop_app(); } +static void view_file(const char* path, const char* filename) { + size_t path_len = strlen(path); + size_t filename_len = strlen(filename); + char* filepath = malloc(path_len + filename_len + 2); + sprintf(filepath, "%s/%s", path, filename); + + // For PC we need to make the path relative to the current work directory, + // because that's how LVGL maps its 'drive letter' to the file system. + char* processed_filepath; + if (tt_get_platform() == PlatformPc) { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + TT_LOG_E(TAG, "Failed to get current working directory"); + return; + } + if (!strstr(filepath, cwd)) { + TT_LOG_E(TAG, "Can only work with files in working directory %s", cwd); + return; + } + char* substr = filepath + strlen(cwd); + processed_filepath = substr; + } else { + processed_filepath = filepath; + } + + TT_LOG_I(TAG, "Clicked %s", filepath); + + if (is_supported_image_file(filename)) { + Bundle bundle = tt_bundle_alloc(); + tt_bundle_put_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath); + loader_start_app("image_viewer", false, bundle); + } else { + TT_LOG_W(TAG, "opening files of this type is not supported"); + } + + free(filepath); +} + static void on_file_pressed(lv_event_t* event) { lv_event_code_t code = lv_event_get_code(event); if (code == LV_EVENT_CLICKED) { @@ -80,10 +117,12 @@ static void on_file_pressed(lv_event_t* event) { TT_LOG_W(TAG, "opening links is not supported"); break; case TT_DT_REG: - TT_LOG_W(TAG, "opening files is not supported"); + view_file(files_data->current_path, dir_entry->d_name); break; default: - TT_LOG_W(TAG, "file type %d is not supported", dir_entry->d_type); + // Assume it's a file + // TODO: Find a better way to identify a file + view_file(files_data->current_path, dir_entry->d_name); break; } } @@ -95,7 +134,7 @@ static void create_file_widget(FilesData* files_data, lv_obj_t* parent, struct d const char* symbol; if (dir_entry->d_type == TT_DT_DIR) { symbol = LV_SYMBOL_DIRECTORY; - } else if (is_image_file(dir_entry->d_name)) { + } else if (is_supported_image_file(dir_entry->d_name)) { symbol = LV_SYMBOL_IMAGE; } else if (dir_entry->d_type == TT_DT_LNK) { symbol = LV_SYMBOL_LOOP; @@ -136,7 +175,19 @@ static void on_show(App app, lv_obj_t* parent) { static void on_start(App app) { FilesData* data = files_data_alloc(); - files_data_set_entries_for_path(data, "/"); + // PC platform is bound to current work directory because of the LVGL file system mapping + if (tt_get_platform() == PlatformPc) { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + files_data_set_entries_for_path(data, cwd); + } else { + TT_LOG_E(TAG, "Failed to get current work directory files"); + files_data_set_entries_for_path(data, "/"); + } + } else { + files_data_set_entries_for_path(data, "/"); + } + tt_app_set_data(app, data); } diff --git a/tactility/src/apps/system/files/files_data.c b/tactility/src/apps/system/files/files_data.c index 368f4da0..e0a6d279 100644 --- a/tactility/src/apps/system/files/files_data.c +++ b/tactility/src/apps/system/files/files_data.c @@ -5,22 +5,20 @@ #define TAG "files_app" -static bool get_child_path(char* base_path, const char* child_path, char* out_path, size_t out_size) { +static bool get_child_path(char* base_path, const char* child_path, char* out_path, size_t max_chars) { size_t current_path_length = strlen(base_path); size_t added_path_length = strlen(child_path); size_t total_path_length = current_path_length + added_path_length + 1; // two paths with `/` - if (total_path_length >= out_size) { + if (total_path_length >= max_chars) { TT_LOG_E(TAG, "Path limit reached (%d chars)", MAX_PATH_LENGTH); return false; } else { - memcpy(out_path, base_path, current_path_length); // Postfix with "/" when the current path isn't "/" if (current_path_length != 1) { - out_path[current_path_length] = '/'; - strcpy(&out_path[current_path_length + 1], child_path); + sprintf(out_path, "%s/%s", base_path, child_path); } else { - strcpy(&out_path[current_path_length], child_path); + sprintf(out_path, "/%s", child_path); } return true; } @@ -101,7 +99,7 @@ bool files_data_set_entries_for_path(FilesData* data, const char* path) { } bool files_data_set_entries_for_child_path(FilesData* data, const char* child_path) { - char new_absolute_path[MAX_PATH_LENGTH]; + char new_absolute_path[MAX_PATH_LENGTH + 1]; if (get_child_path(data->current_path, child_path, new_absolute_path, MAX_PATH_LENGTH)) { TT_LOG_I(TAG, "Navigating from %s to %s", data->current_path, new_absolute_path); return files_data_set_entries_for_path(data, new_absolute_path); diff --git a/tactility/src/apps/system/image_viewer/image_viewer.c b/tactility/src/apps/system/image_viewer/image_viewer.c new file mode 100644 index 00000000..8ece8b58 --- /dev/null +++ b/tactility/src/apps/system/image_viewer/image_viewer.c @@ -0,0 +1,44 @@ +#include "image_viewer.h" +#include "log.h" +#include "lvgl.h" +#include "ui/style.h" +#include "ui/toolbar.h" +#include + +#define TAG "image_viewer" + +static void app_show(App app, lv_obj_t* parent) { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + tt_toolbar_create_for_app(parent, app); + + lv_obj_t* wrapper = lv_obj_create(parent); + lv_obj_set_width(wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(wrapper, 1); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); + tt_lv_obj_set_style_no_padding(wrapper); + tt_lv_obj_set_style_bg_invisible(wrapper); + + lv_obj_t* image = lv_img_create(wrapper); + lv_obj_align(image, LV_ALIGN_CENTER, 0, 0); + Bundle bundle = tt_app_get_parameters(app); + if (tt_bundle_has_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT)) { + const char* file = tt_bundle_get_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT); + char* prefixed_path = malloc(strlen(file) + 3); + tt_assert(prefixed_path != NULL); + sprintf(prefixed_path, "A:%s", file); + TT_LOG_I(TAG, "Opening %s", prefixed_path); + lv_img_set_src(image, prefixed_path); + free(prefixed_path); + } +} + +const AppManifest image_viewer_app = { + .id = "image_viewer", + .name = "Image Viewer", + .icon = NULL, + .type = AppTypeDesktop, + .on_start = NULL, + .on_stop = NULL, + .on_show = &app_show, + .on_hide = NULL +}; diff --git a/tactility/src/apps/system/image_viewer/image_viewer.h b/tactility/src/apps/system/image_viewer/image_viewer.h new file mode 100644 index 00000000..888770e5 --- /dev/null +++ b/tactility/src/apps/system/image_viewer/image_viewer.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define IMAGE_VIEWER_FILE_ARGUMENT "file" + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/tactility/src/tactility.c b/tactility/src/tactility.c index df3e348a..942aac88 100644 --- a/tactility/src/tactility.c +++ b/tactility/src/tactility.c @@ -35,6 +35,7 @@ static const ServiceManifest* const system_services[] = { extern const AppManifest desktop_app; extern const AppManifest display_app; extern const AppManifest files_app; +extern const AppManifest image_viewer_app; extern const AppManifest power_app; extern const AppManifest settings_app; extern const AppManifest system_info_app; @@ -51,6 +52,7 @@ static const AppManifest* const system_apps[] = { &desktop_app, &display_app, &files_app, + &image_viewer_app, &settings_app, &system_info_app, &wifi_connect_app,