/* * navigation.cpp * Navigation and file opening logic for the embedded desktop file manager * Copyright (c) 2026 Daniel Hammer */ #include "filemanager_internal.hpp" namespace filemanager { static void filemanager_store_location(FileManagerLocation* location, const char* path, FileManagerVirtualViewKind virtual_view) { if (!location) return; montauk::strncpy(location->path, path ? path : "", sizeof(location->path)); location->virtual_view = virtual_view; } static bool filemanager_location_matches_current(const FileManagerState* fm, const FileManagerLocation& location) { return fm && location.virtual_view == fm->virtual_view && montauk::streq(location.path, fm->current_path); } static void filemanager_restore_location(FileManagerState* fm, const FileManagerLocation& location) { montauk::strncpy(fm->current_path, location.path, sizeof(fm->current_path)); if (location.virtual_view != FM_VIRTUAL_VIEW_NONE) { filemanager_read_virtual_view(fm, location.virtual_view); } else if (location.path[0] == '\0') { filemanager_read_drives(fm); } else { filemanager_read_dir(fm); } } void filemanager_push_history(FileManagerState* fm) { if (!fm) return; if (fm->history_count > 0 && fm->history_pos >= 0) { if (filemanager_location_matches_current(fm, fm->history[fm->history_pos])) return; } if (fm->history_pos < fm->history_count - 1) { fm->history_count = fm->history_pos + 1; } if (fm->history_count >= 16) { for (int i = 1; i < 16; i++) { fm->history[i - 1] = fm->history[i]; } fm->history_count = 15; fm->history_pos = 14; } fm->history_pos++; filemanager_store_location(&fm->history[fm->history_pos], fm->current_path, fm->virtual_view); fm->history_count = fm->history_pos + 1; } void filemanager_navigate(FileManagerState* fm, const char* name) { int path_len = montauk::slen(fm->current_path); if (path_len > 0 && fm->current_path[path_len - 1] != '/') { str_append(fm->current_path, "/", 256); } str_append(fm->current_path, name, 256); filemanager_read_dir(fm); filemanager_push_history(fm); } void filemanager_go_up(FileManagerState* fm) { if (filemanager_is_computer_view(fm)) return; if (filemanager_is_virtual_view(fm)) { filemanager_read_drives(fm); filemanager_push_history(fm); return; } int len = montauk::slen(fm->current_path); // At drive root (e.g. "0:/" or "10:/") -- go to drives view bool is_drive_root = false; if (len >= 3 && fm->current_path[len - 1] == '/') { int colon = -1; for (int i = 0; i < len; i++) { if (fm->current_path[i] == ':') { colon = i; break; } } if (colon >= 0 && colon + 2 == len) is_drive_root = true; } if (is_drive_root) { filemanager_read_drives(fm); filemanager_push_history(fm); return; } if (len > 0 && fm->current_path[len - 1] == '/') { fm->current_path[len - 1] = '\0'; len--; } int last_slash = -1; for (int i = len - 1; i >= 0; i--) { if (fm->current_path[i] == '/') { last_slash = i; break; } } if (last_slash >= 0) { if (last_slash > 0 && fm->current_path[last_slash - 1] == ':') { fm->current_path[last_slash + 1] = '\0'; } else { fm->current_path[last_slash] = '\0'; } } filemanager_read_dir(fm); filemanager_push_history(fm); } void filemanager_navigate_to_history(FileManagerState* fm) { if (!fm || fm->history_pos < 0 || fm->history_pos >= fm->history_count) return; filemanager_restore_location(fm, fm->history[fm->history_pos]); } void filemanager_go_back(FileManagerState* fm) { if (fm->history_pos <= 0) return; fm->history_pos--; filemanager_navigate_to_history(fm); } void filemanager_go_forward(FileManagerState* fm) { if (fm->history_pos >= fm->history_count - 1) return; fm->history_pos++; filemanager_navigate_to_history(fm); } void filemanager_go_home(FileManagerState* fm) { filemanager_read_drives(fm); filemanager_push_history(fm); } void filemanager_refresh(FileManagerState* fm) { if (!fm) return; if (filemanager_is_virtual_view(fm)) { filemanager_read_virtual_view(fm, fm->virtual_view); } else if (filemanager_is_computer_view(fm)) { filemanager_read_drives(fm); } else { filemanager_read_dir(fm); } } void filemanager_start_pathbar(FileManagerState* fm) { fm->pathbar_editing = true; if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) { fm->pathbar_buf[0] = '\0'; fm->pathbar_len = 0; } else { montauk::strcpy(fm->pathbar_buf, fm->current_path); fm->pathbar_len = montauk::slen(fm->pathbar_buf); } fm->pathbar_cursor = fm->pathbar_len; } void filemanager_cancel_pathbar(FileManagerState* fm) { fm->pathbar_editing = false; } // True if `path` begins with a "N:/" volume prefix (one or more digits then ":/"). static bool filemanager_has_volume_prefix(const char* path) { int i = 0; while (path[i] >= '0' && path[i] <= '9') i++; return i > 0 && path[i] == ':' && path[i + 1] == '/'; } // Resolve a user-typed path into a fully-qualified "N:/..." path. Paths without // a volume number (e.g. "/os" or "os") are drive-relative; the kernel resolves // them against drive 0, but filemanager_read_dir strips a drive-root-relative // prefix, so an unqualified path would leave the returned basenames with their // full sub-path. Qualify it here so the prefix stripping matches. The drive of // the current location is preferred, falling back to drive 0. static void filemanager_qualify_path(FileManagerState* fm, char* out, int out_max) { const char* in = fm->pathbar_buf; if (filemanager_has_volume_prefix(in)) { montauk::strcpy(out, in); return; } char drive[8] = "0"; if (filemanager_has_volume_prefix(fm->current_path)) { int i = 0; while (fm->current_path[i] >= '0' && fm->current_path[i] <= '9' && i < 6) { drive[i] = fm->current_path[i]; i++; } drive[i] = '\0'; } montauk::strcpy(out, drive); str_append(out, ":", out_max); if (in[0] != '/') str_append(out, "/", out_max); str_append(out, in, out_max); } void filemanager_commit_pathbar(FileManagerState* fm) { fm->pathbar_editing = false; if (fm->pathbar_len == 0) { filemanager_read_drives(fm); filemanager_push_history(fm); return; } filemanager_qualify_path(fm, fm->current_path, sizeof(fm->current_path)); filemanager_read_dir(fm); filemanager_push_history(fm); } void filemanager_open_entry(FileManagerState* fm, int idx) { if (idx < 0 || idx >= fm->entry_count) return; if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_SPECIAL_DIR) { DesktopState* ds = fm->desktop; if (ds && ds->home_dir[0] != '\0') { montauk::strcpy(fm->current_path, ds->home_dir); int plen = montauk::slen(fm->current_path); if (plen > 0 && fm->current_path[plen - 1] != '/') str_append(fm->current_path, "/", 256); str_append(fm->current_path, fm->entry_names[idx], 256); filemanager_read_dir(fm); filemanager_push_history(fm); } return; } if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_APPS_ROOT) { filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_APPS); filemanager_push_history(fm); return; } if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT) { filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION); filemanager_push_history(fm); return; } if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_ITEM) { DesktopState* ds = fm->desktop; const FileManagerVirtualEntry& entry = fm->virtual_entries[idx]; if (ds && entry.desktop_item_index >= 0 && entry.desktop_item_index < ds->desktop_item_count) { desktop_launch_item(ds, &ds->desktop_items[entry.desktop_item_index]); } return; } if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_HOME) { DesktopState* ds = fm->desktop; if (ds && ds->home_dir[0] != '\0') { montauk::strcpy(fm->current_path, ds->home_dir); filemanager_read_dir(fm); filemanager_push_history(fm); } return; } if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_DRIVE) { int d = fm->drive_indices[idx]; char dpath[8]; if (d < 10) { dpath[0] = '0' + d; dpath[1] = ':'; dpath[2] = '/'; dpath[3] = '\0'; } else { dpath[0] = '1'; dpath[1] = '0' + (d - 10); dpath[2] = ':'; dpath[3] = '/'; dpath[4] = '\0'; } montauk::strcpy(fm->current_path, dpath); filemanager_read_dir(fm); filemanager_push_history(fm); return; } if (fm->is_dir[idx]) { filemanager_navigate(fm, fm->entry_names[idx]); return; } // Open the file with its default app (see associations.cpp). DesktopState* ds = fm->desktop; const char* name = fm->entry_names[idx]; char fullpath[512]; filemanager_build_fullpath(fullpath, 512, fm->current_path, name); if (str_ends_with(name, ".elf")) { desktop_spawn_app(ds, fullpath); return; } char ext[16]; file_extension(name, ext, sizeof(ext)); int app = filemanager_assoc_default(ds, ext); if (app >= 0 && filemanager_launch_with(ds, app, fullpath)) return; // Nothing claims this type: text still goes straight to the Text Editor // (READMEs, Makefiles, odd extensions); anything else asks. if (filemanager_file_looks_textual(fullpath)) { desktop_spawn_app(ds, "0:/apps/texteditor/texteditor.elf", fullpath); return; } filemanager_show_open_with(fm, idx); } } // namespace filemanager