Added image viewer app (#73)

This commit is contained in:
Ken Van Hoeylandt 2024-11-10 12:33:38 +01:00 committed by GitHub
parent 369180cb5a
commit 18e4383bcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 128 additions and 20 deletions

View File

@ -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. */

View File

@ -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 <dirent.h>
#include <unistd.h>
#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();
// 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);
}

View File

@ -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);

View File

@ -0,0 +1,44 @@
#include "image_viewer.h"
#include "log.h"
#include "lvgl.h"
#include "ui/style.h"
#include "ui/toolbar.h"
#include <tactility_core.h>
#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
};

View File

@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define IMAGE_VIEWER_FILE_ARGUMENT "file"
#ifdef __cplusplus
}
#endif

View File

@ -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,