From 817c1f98f5c94d30b0d270d53a5f9ec5cbb59b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Mon, 28 Jun 2021 14:09:59 +0200 Subject: [PATCH] feat: implement basic directory browser window --- file-browser/include/file_access.h | 6 +- file-browser/include/window_browser.h | 11 ++ file-browser/src/file_access.c | 25 +++- file-browser/src/gui_list_commands.c | 2 + file-browser/src/local_file_access.c | 76 ++++++----- file-browser/src/local_file_utils.c | 6 +- file-browser/src/window_browser.c | 181 ++++++++++++++++++++++++++ file-browser/src/window_initial.c | 8 +- lib-gui/src/gui_list_container.c | 60 +++++---- lib-gui/src/gui_window_info.c | 1 + lib-pheripherals/src/nonblocking_io.c | 4 +- 11 files changed, 318 insertions(+), 62 deletions(-) create mode 100644 file-browser/include/window_browser.h create mode 100644 file-browser/src/window_browser.c diff --git a/file-browser/include/file_access.h b/file-browser/include/file_access.h index 3524481..20d86b0 100644 --- a/file-browser/include/file_access.h +++ b/file-browser/include/file_access.h @@ -5,6 +5,7 @@ #include #include #include "file_execute.h" +#include "logger.h" typedef struct directory_t directory_t; @@ -52,6 +53,8 @@ typedef enum { FILOPER_UNKNOWN, } file_operation_error_t; +extern const char *file_operation_error_strings[]; + typedef struct { bool error; union { @@ -156,9 +159,10 @@ extern const fileaccess_t extern const fileaccess_t temp_file_access; // state is /tmp directory descriptor -extern uint8_t fileaccess_connectors_count; extern fileaccess_connector_t fileaccess_connectors[(FA_COUNT-1)*FA_COUNT]; +void fileaccess_log_error(logger_t *logger, file_operation_error_t error); + fileaccess_state_t fileaccess_init(const fileaccess_t *fileaccess, void *data); bool fileaccess_deinit(fileaccess_state_t state); diff --git a/file-browser/include/window_browser.h b/file-browser/include/window_browser.h new file mode 100644 index 0000000..ef7b828 --- /dev/null +++ b/file-browser/include/window_browser.h @@ -0,0 +1,11 @@ +#ifndef __WINDOW_BROWSER_H__ +#define __WINDOW_BROWSER_H__ + +#include "gui.h" +#include +#include "file_access.h" + +bool window_browser_open_local(gui_t *gui, font_t *font); +bool window_browser_open(gui_t *gui, font_t *font, fileaccess_state_t state); + +#endif // __WINDOW_BROWSER_H__ diff --git a/file-browser/src/file_access.c b/file-browser/src/file_access.c index 9a288d5..48ff32b 100644 --- a/file-browser/src/file_access.c +++ b/file-browser/src/file_access.c @@ -1,8 +1,31 @@ #include "file_access.h" +#include "logger.h" #include #include +/* + FILOPER_SUCCESS, + FILOPER_PERMISSIONS, + FILOPER_DOES_NOT_EXIST, + FILOPER_USED, + FILOPER_ALREADY_EXISTS, + FILOPER_NOT_ENOUGH_SPACE, + FILOPER_UNKNOWN, + */ -uint8_t connectors_count = 1; +const char *file_operation_error_strings[] = { + "Success", + "No permissions", + "No such file or directory", + "File is in use", + "File already exists", + "Not enough space on device", + "Unknown error", +}; + +void fileaccess_log_error(logger_t *logger, file_operation_error_t error) { + logger_error(logger, __FILE__, __FUNCTION__, __LINE__, + "File operation error: %s", file_operation_error_strings[error]); +} fileaccess_state_t fileaccess_init(const fileaccess_t *fileaccess, void *data) { return fileaccess->init(data); diff --git a/file-browser/src/gui_list_commands.c b/file-browser/src/gui_list_commands.c index d250061..51d4df4 100644 --- a/file-browser/src/gui_list_commands.c +++ b/file-browser/src/gui_list_commands.c @@ -56,6 +56,8 @@ static void command_handler_move_up(void *state, int amount) { void gui_list_commands_register(commands_t *commands, gui_list_command_state_t *state) { commands_register(commands, IN_KEYBOARD, 13, command_handler_gui_list_clicked, state); + commands_register(commands, IN_KEYBOARD, 'v', command_handler_gui_list_clicked, + state); commands_register(commands, IN_KEYBOARD, KEYBOARD_DOWN, command_handler_move_down, state); diff --git a/file-browser/src/local_file_access.c b/file-browser/src/local_file_access.c index 29ebcf5..d203a4e 100644 --- a/file-browser/src/local_file_access.c +++ b/file-browser/src/local_file_access.c @@ -23,18 +23,28 @@ bool local_fileaccess_deinit_state(fileaccess_state_t data) { return true; } -static file_operation_error_t file_get_information(void **malloced, - uint64_t *offset, uint64_t *bytes_malloced, +static uint64_t directory_get_needed_bytes(char *name, uint32_t *dirs_count, DIR *dirptr) { + uint64_t size = sizeof(directory_t) + strlen(name) + 1; + + struct dirent *dir; + errno = 0; + *dirs_count = 0; + while ((dir = readdir(dirptr)) != NULL) { + size += sizeof(file_t); + size += strlen(dir->d_name) + 1; + (*dirs_count)++; + } + + rewinddir(dirptr); + return size; +} + +static file_operation_error_t file_get_information(void *malloced, + uint64_t *file_offset, + uint64_t *names_offset, fileaccess_state_t state, file_t file) { size_t name_len = strlen(file.name); - bytes_malloced += sizeof(file_t) + name_len + 1; - void *new = realloc(*malloced, *bytes_malloced); - if (new == NULL) { - free(*malloced); - return FILOPER_UNKNOWN; - } - *malloced = new; char full_path[file_get_full_path_memory_size(state, file.directory, &file)]; file_get_full_path(state, file.directory, &file, full_path); @@ -44,8 +54,8 @@ static file_operation_error_t file_get_information(void **malloced, int status = stat(full_path, &stats); if (status == -1) { - free(new); - return file_operation_error_from_errno(errno); + //free(new); + //return file_operation_error_from_errno(errno); } file.size = stats.st_size; @@ -53,13 +63,13 @@ static file_operation_error_t file_get_information(void **malloced, file.uid = stats.st_uid; file.permissions = stats.st_mode; - file_t *stored = new + *offset; + file_t *stored = malloced + *file_offset; *stored = file; - *offset += sizeof(file_t); + *file_offset += sizeof(file_t); - strcpy(new + *offset, file.name); - stored->name = new + *offset; - *offset += name_len + 1; + strcpy(malloced + *names_offset, file.name); + stored->name = malloced + *names_offset; + *names_offset += name_len + 1; return FILOPER_SUCCESS; } @@ -70,9 +80,18 @@ directory_or_error_t local_fileaccess_directory_list(fileaccess_state_t state, char full_path[path_join_memory_size(state.payload.local.path, path)]; path_join((char *)state.payload.local.path, path, full_path); - uint64_t malloc_offset = sizeof(directory_t) + strlen(path) + 1; - uint64_t bytes_malloced = sizeof(directory_t) + strlen(path) + 1; - directory_t *directory = malloc(malloc_offset); + DIR *dirptr = opendir(full_path); + if (dirptr == NULL) { + ret.error = true; + ret.payload.error = file_operation_error_from_errno(errno); + return ret; + } + + uint32_t files_count = 0; + uint64_t size = directory_get_needed_bytes(path, &files_count, dirptr); + uint64_t files_offset = sizeof(directory_t) + strlen(path) + 1; + uint64_t names_offset = files_count * sizeof(file_t) + files_offset; + directory_t *directory = malloc(size); void *malloced = directory; if (directory == NULL) { @@ -82,17 +101,10 @@ directory_or_error_t local_fileaccess_directory_list(fileaccess_state_t state, } directory->path = malloced + sizeof(directory_t); + directory->files = malloced + files_offset; + directory->files_count = 0; strcpy(directory->path, path); - - DIR *dirptr = opendir(full_path); - if (dirptr == NULL) { - ret.error = true; - ret.payload.error = file_operation_error_from_errno(errno); - free(malloced); - return ret; - } - struct dirent * dir; errno = 0; while ((dir = readdir(dirptr)) != NULL) { @@ -115,17 +127,19 @@ directory_or_error_t local_fileaccess_directory_list(fileaccess_state_t state, break; } - ret.payload.error = file_get_information(&malloced, &malloc_offset, - &bytes_malloced, state, file); + ret.payload.error = file_get_information(malloced, &files_offset, + &names_offset, state, file); + errno = 0; if (ret.payload.error != FILOPER_SUCCESS) { ret.error = true; - free(malloced); return ret; } directory->files_count++; } + closedir(dirptr); + if (errno != 0) { ret.error = true; ret.payload.error = file_operation_error_from_errno(errno); diff --git a/file-browser/src/local_file_utils.c b/file-browser/src/local_file_utils.c index 8545e76..21b2430 100644 --- a/file-browser/src/local_file_utils.c +++ b/file-browser/src/local_file_utils.c @@ -10,7 +10,11 @@ static int nfw_callback(const char *fpath, const struct stat *sb, int typeflag); size_t file_get_full_path_memory_size(fileaccess_state_t state, directory_t *directory, file_t *file) { - return strlen(file->name) + strlen(state.payload.local.path) + strlen(file->name) + 3; + size_t root = strlen(state.payload.local.path); + size_t dir = strlen(directory->path); + size_t file_name = strlen(file->name); + + return root + 1 + dir + 1 + file_name + 1; } bool file_get_full_path(fileaccess_state_t state, diff --git a/file-browser/src/window_browser.c b/file-browser/src/window_browser.c new file mode 100644 index 0000000..004758e --- /dev/null +++ b/file-browser/src/window_browser.c @@ -0,0 +1,181 @@ +#include "window_browser.h" +#include "display_utils.h" +#include "file_access.h" +#include "gui.h" +#include "gui_component_text.h" +#include "gui_container_info.h" +#include "gui_list_commands.h" +#include "gui_component_line.h" +#include "gui_window_info.h" +#include "input.h" +#include "logger.h" +#include "renderer.h" + +typedef struct { + bool running; + gui_t *gui; + + container_t *list_container; + window_t *browser_window; + + font_t *font; + + gui_list_command_state_t click_state; + text_t text_state; + + directory_t *current_directory; + fileaccess_state_t state; +} browser_window_state_t; + +static bool browser_window_list_render_item(void *state, uint32_t index, + renderer_t *renderer, int16_t beg_x, + int16_t beg_y, + display_pixel_t color); + +static bool browser_window_list_render_header(void *state, uint32_t index, + renderer_t *renderer, + int16_t beg_x, int16_t beg_y, + display_pixel_t color); + +static void browser_window_item_clicked(container_t *container, void *state, + uint32_t selected_index); + +static void *browser_window_construct(window_t *window, void *state); +static bool browser_window_running(void *state); +static void browser_window_job(void *state); + +gui_container_info_t window_browser_containers[] = { + {.type = CONT_TABLE, + .payload.list = {.render_item_fn = browser_window_list_render_item, + .render_header_fn = browser_window_list_render_header, + .item_height = 16}}, + {.type = CONT_GROUP, .payload.group.components_count = 2}, +}; + +window_info_t window_browser_info = { + .construct = browser_window_construct, + .containers_count = 2, + .containers = window_browser_containers, +}; + +bool window_browser_open_local(gui_t *gui, font_t *font) { + fileaccess_state_t state = fileaccess_init(&local_file_access, NULL); + return window_browser_open(gui, font, state); +} + +bool window_browser_open(gui_t *gui, font_t *font, fileaccess_state_t state) { + directory_or_error_t root = fileaccess_root_list(state); + + if (root.error) { + fileaccess_log_error(gui->logger, root.error); + // TODO: dialog + return false; + } + + browser_window_state_t bstate = { + .state = state, + .gui = gui, + .font = font, + .current_directory = root.payload.directory, + .running = true, + }; + + uint16_t commands_state = commands_save_state(gui->commands); + gui_window_init_and_loop(gui, &bstate, window_browser_info, browser_window_running, + browser_window_job); + commands_restore_state(gui->commands, commands_state); + + return true; +} + +static void command_handler_exit(void *state, int amount) { + browser_window_state_t *bstate = (browser_window_state_t *)state; + if (bstate->gui->active_window == bstate->browser_window) { + bstate->running = false; + } +} + +static void *browser_window_construct(window_t *window, void *state) { + browser_window_state_t *bstate = (browser_window_state_t *)state; + logger_t *logger = bstate->gui->logger; + logger_info(logger, __FILE__, __FUNCTION__, __LINE__, + "Constructing browser window"); + bstate->list_container = &window->containers[0]; + bstate->browser_window = window; + + bstate->click_state.container = bstate->list_container; + bstate->click_state.state = state; + bstate->click_state.clicked = browser_window_item_clicked; + bstate->click_state.font = bstate->font; + bstate->click_state.gui = bstate->gui; + bstate->click_state.window = window; + + bstate->text_state.font = bstate->font; + bstate->text_state.line = bstate->current_directory->path; + bstate->text_state.color = WHITE_PIXEL; + + // containers init + // group components init + component_t path_text = gui_text_create(&bstate->text_state, 3, 3, 0, 0); + component_t line_component = gui_line_create(&WHITE_PIXEL, 0, path_text.height + path_text.y + 3, 1000, 1); + + gui_group_container_add_component(&window->containers[1], path_text); + gui_group_container_add_component(&window->containers[1], line_component); + + // list init + gui_container_info_init(bstate->list_container, bstate, + bstate->current_directory->files_count, 5, + 5 + line_component.height + 5); + bstate->list_container->width = bstate->gui->size.x - 20; + bstate->list_container->height = bstate->gui->size.y - bstate->list_container->y - 20; + + // commands register + gui_list_commands_register(bstate->gui->commands, &bstate->click_state); + commands_register(bstate->gui->commands, IN_KEYBOARD, 'e', + command_handler_exit, state); + + return state; +} + +static void browser_window_item_clicked(container_t *container, void *state, + uint32_t selected_index) { + + browser_window_state_t *bstate = (browser_window_state_t *)state; + logger_t *logger = bstate->gui->logger; + + logger_info(logger, __FILE__, __FUNCTION__, __LINE__, "Item was clicked."); +} + +static bool browser_window_list_render_item(void *state, uint32_t index, + renderer_t *renderer, int16_t beg_x, + int16_t beg_y, + display_pixel_t color) { + browser_window_state_t *bstate = (browser_window_state_t *)state; + file_t file = bstate->current_directory->files[index]; + renderer_write_string(renderer, beg_x, beg_y, 0, bstate->font, file.name, + color); + return true; +} + +static bool browser_window_list_render_header(void *state, uint32_t index, + renderer_t *renderer, + int16_t beg_x, int16_t beg_y, + display_pixel_t color) { + + browser_window_state_t *bstate = (browser_window_state_t *)state; + renderer_write_string(renderer, beg_x, beg_y, 0, bstate->font, "This is header", color); + return true; +} + +static bool browser_window_running(void *state) { + browser_window_state_t *bstate = (browser_window_state_t*)state; + return bstate->running; +} + +static void browser_window_job(void *state) { + browser_window_state_t *bstate = (browser_window_state_t *)state; + if (!bstate->running) { + // cleanup + fileaccess_directory_close(bstate->state, bstate->current_directory); + } +} diff --git a/file-browser/src/window_initial.c b/file-browser/src/window_initial.c index ecbc222..79e3e75 100644 --- a/file-browser/src/window_initial.c +++ b/file-browser/src/window_initial.c @@ -9,6 +9,7 @@ #include "input.h" #include "logger.h" #include "renderer.h" +#include "window_browser.h" #include @@ -100,7 +101,9 @@ static bool initial_window_list_render_item(void *state, uint32_t index, static void command_handler_exit(void *state, int amount) { initial_window_state_t *istate = (initial_window_state_t *)state; - istate->running = false; + if (istate->gui->active_window == istate->initial_window) { + istate->running = false; + } } static void *initial_window_construct(window_t *window, void *state) { @@ -140,6 +143,8 @@ static void initial_window_job(void *state) { (initial_window_state_t *)state; initial_window_state->list_container->inner.list.scroll_x = 0; + gui_list_container_set_item_height(initial_window_state->list_container, + initial_window_state->font.size); // do nothing? } @@ -156,6 +161,7 @@ static void initial_window_item_clicked(container_t *container, void *state, case INITIAL_WINDOW_LOCAL_INDEX: logger_info(logger, __FILE__, __FUNCTION__, __LINE__, "Clicked local root filesystem"); + window_browser_open_local(istate->gui, &istate->font); break; case INITIAL_WINDOW_MOUNT_INDEX: logger_info(logger, __FILE__, __FUNCTION__, __LINE__, diff --git a/lib-gui/src/gui_list_container.c b/lib-gui/src/gui_list_container.c index e1c36fa..e784d23 100644 --- a/lib-gui/src/gui_list_container.c +++ b/lib-gui/src/gui_list_container.c @@ -7,30 +7,28 @@ container_t gui_list_container_create(void *state, uint32_t items_count, render_item render_it, render_item render_header) { list_container_t list = { - .item_height = item_height, - .items_count = items_count, - .state = state, - .scroll_x = 0, - .scroll_y = 0, - .selected_index = 0, - .render_header_fn = render_header, - .render_item_fn = render_it, - .regular_background = BLACK_PIXEL, - .regular_foreground = WHITE_PIXEL, - .selected_background = WHITE_PIXEL, - .selected_foreground = BLACK_PIXEL, - .item_padding = 3, + .item_height = item_height, + .items_count = items_count, + .state = state, + .scroll_x = 0, + .scroll_y = 0, + .selected_index = 0, + .render_header_fn = render_header, + .render_item_fn = render_it, + .regular_background = BLACK_PIXEL, + .regular_foreground = WHITE_PIXEL, + .selected_background = WHITE_PIXEL, + .selected_foreground = BLACK_PIXEL, + .item_padding = 3, }; - container_t container = { - .focusable = true, - .focused = false, - .height = item_height * items_count, - .width = 0, - .inner.list = list, - .x = 0, - .y = 0 - }; + container_t container = {.focusable = true, + .focused = false, + .height = item_height * items_count, + .width = 0, + .inner.list = list, + .x = 0, + .y = 0}; return container; } @@ -57,7 +55,8 @@ bool gui_list_container_set_item_height(container_t *container, return true; } -bool gui_list_container_set_render_function(container_t *container, render_item render_it, +bool gui_list_container_set_render_function(container_t *container, + render_item render_it, render_item render_header) { container->inner.list.render_item_fn = render_it; container->inner.list.render_header_fn = render_header; @@ -94,6 +93,14 @@ void gui_list_container_render(gui_t *gui, container_t *container) { uint32_t selected_index = gui_list_get_selected_index(container); + if (list.render_header_fn && + list.render_header_fn(list.state, 0, gui->renderer, 0 + list.item_padding, + 0 + list.item_padding, WHITE_PIXEL)) { + // if header was rendered, translate initial position + renderer_translate(gui->renderer, 0, item_full_height); + renderer_set_draw_area(gui->renderer, container->width, container->height - item_full_height); + } + for (uint32_t i = first_index; i < end_index && i < list.items_count; i++) { int32_t y = beg_y + (i - first_index) * item_full_height; display_pixel_t fgcolor = list.regular_foreground; @@ -104,8 +111,10 @@ void gui_list_container_render(gui_t *gui, container_t *container) { bgcolor = list.selected_background; } - renderer_render_rectangle(gui->renderer, beg_x, y, 1000, item_full_height, bgcolor); - list.render_item_fn(list.state, i, gui->renderer, beg_x + list.item_padding, y + list.item_padding, fgcolor); + renderer_render_rectangle(gui->renderer, beg_x, y, 1000, item_full_height, + bgcolor); + list.render_item_fn(list.state, i, gui->renderer, beg_x + list.item_padding, + y + list.item_padding, fgcolor); } } @@ -138,6 +147,5 @@ void gui_list_container_update(gui_t *gui, container_t *container) { list.scroll_y = (selected_index - items_count) * item_full_height; } - container->inner.list = list; } diff --git a/lib-gui/src/gui_window_info.c b/lib-gui/src/gui_window_info.c index 82c7e82..dda841d 100644 --- a/lib-gui/src/gui_window_info.c +++ b/lib-gui/src/gui_window_info.c @@ -1,6 +1,7 @@ #include "gui_window_info.h" #include "gui.h" #include "gui_container_info.h" +#include "logger.h" bool gui_window_init_and_loop(gui_t *gui, void *state, window_info_t info, gui_loop_running_fn loop_running, diff --git a/lib-pheripherals/src/nonblocking_io.c b/lib-pheripherals/src/nonblocking_io.c index 7cb5716..e1d7f98 100644 --- a/lib-pheripherals/src/nonblocking_io.c +++ b/lib-pheripherals/src/nonblocking_io.c @@ -35,7 +35,9 @@ int file_set_nonblocking(int file, struct termios *old) tcgetattr(file, old); } - cfmakeraw(&attrs); + attrs.c_lflag &= ~ICANON; + attrs.c_lflag &= ~ECHO; + //cfmakeraw(&attrs); tcsetattr(file, TCSANOW, &attrs); return 1; -- 2.48.1