/* * actions.cpp * Clipboard, rename, delete, and context-menu actions for the embedded desktop file manager * Copyright (c) 2026 Daniel Hammer */ #include "filemanager_internal.hpp" namespace filemanager { static void filemanager_load_clipboard(FileManagerState* fm, bool is_cut) { if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) return; if (fm->entry_count <= 0) return; int* sel = (int*)montauk::malloc((uint64_t)fm->entry_count * sizeof(int)); if (!sel) return; int n = filemanager_collect_selection(fm, sel, fm->entry_count); if (n <= 0) { montauk::mfree(sel); return; } if (!filemanager_ensure_clipboard_capacity(fm, n)) { montauk::mfree(sel); return; } int out = 0; for (int i = 0; i < n && out < fm->clipboard_cap; i++) { int idx = sel[i]; if (fm->entry_types[idx] == FM_ENTRY_DRIVE) continue; filemanager_build_fullpath(fm->clipboard_paths[out], 256, fm->current_path, fm->entry_names[idx]); out++; } montauk::mfree(sel); if (out == 0) return; fm->clipboard_count = out; fm->clipboard_is_cut = is_cut; } void filemanager_do_copy(FileManagerState* fm) { filemanager_load_clipboard(fm, false); } void filemanager_do_cut(FileManagerState* fm) { filemanager_load_clipboard(fm, true); } void filemanager_do_paste(FileManagerState* fm) { // Copy/move runs on a worker thread with a progress dialog (a small single // file pastes inline). Same-volume moves use rename (instant, no copy). filemanager_start_paste_job(fm); } void filemanager_start_rename(FileManagerState* fm) { if (fm->selected < 0 || fm->selected >= fm->entry_count) return; // Rename is single-target; with several entries selected, `selected` is // only the focus and renaming it would be a surprise. if (fm->multi_count > 1) return; if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm) || fm->entry_types[fm->selected] == FM_ENTRY_DRIVE) return; fm->rename_active = true; fm->rename_idx = fm->selected; montauk::strcpy(fm->rename_buf, fm->entry_names[fm->selected]); fm->rename_len = montauk::slen(fm->rename_buf); fm->rename_cursor = fm->rename_len; } void filemanager_finish_rename(FileManagerState* fm) { if (!fm->rename_active) return; fm->rename_active = false; // If name unchanged, skip if (montauk::streq(fm->rename_buf, fm->entry_names[fm->rename_idx])) return; if (fm->rename_len == 0) return; char old_path[512], new_path[512]; filemanager_build_fullpath(old_path, 512, fm->current_path, fm->entry_names[fm->rename_idx]); filemanager_build_fullpath(new_path, 512, fm->current_path, fm->rename_buf); montauk::StatusCode status = filemanager_status_from_result(montauk::frename(old_path, new_path)); if (status != montauk::Success && !filemanager_status_is_access_denied(status)) { // Fall back to copy + delete for drivers that don't support rename if (fm->is_dir[fm->rename_idx]) { status = filemanager_copy_dir_recursive(old_path, new_path); if (status == montauk::Success) status = filemanager_delete_recursive(old_path); } else { status = filemanager_copy_file(old_path, new_path); if (status == montauk::Success) status = filemanager_status_from_result(montauk::fdelete(old_path)); } } filemanager_read_dir(fm); if (status != montauk::Success) { char heading[192]; snprintf(heading, sizeof(heading), "Couldn't rename \"%s\"", path_basename(old_path)); filemanager_show_error(fm->desktop, fm, heading, old_path, status); } } void filemanager_cancel_rename(FileManagerState* fm) { fm->rename_active = false; } void filemanager_new_folder(FileManagerState* fm) { if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) return; // Find a unique name char name[64] = "New Folder"; char path[512]; filemanager_build_fullpath(path, 512, fm->current_path, name); // Try creating; if it fails (most likely the name is taken), append a // number. An access error fails the same way for every name, so stop. montauk::StatusCode status = filemanager_status_from_result(montauk::fmkdir(path)); for (int attempt = 2; status != montauk::Success && !filemanager_status_is_access_denied(status) && attempt <= 99; attempt++) { snprintf(name, 64, "New Folder %d", attempt); filemanager_build_fullpath(path, 512, fm->current_path, name); status = filemanager_status_from_result(montauk::fmkdir(path)); } filemanager_read_dir(fm); if (status != montauk::Success) { filemanager_show_error(fm->desktop, fm, "Couldn't create a new folder", fm->current_path, status); return; } // Select the new folder and start rename for (int i = 0; i < fm->entry_count; i++) { if (montauk::streq(fm->entry_names[i], name)) { fm->selected = i; filemanager_start_rename(fm); break; } } } void filemanager_delete_selected(FileManagerState* fm) { if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) return; if (fm->entry_count <= 0) return; int* sel = (int*)montauk::malloc((uint64_t)fm->entry_count * sizeof(int)); if (!sel) return; int n = filemanager_collect_selection(fm, sel, fm->entry_count); if (n <= 0) { montauk::mfree(sel); return; } // Filter out drives. int* filtered = (int*)montauk::malloc((uint64_t)n * sizeof(int)); if (!filtered) { montauk::mfree(sel); return; } int fn = 0; bool any_dir = false; for (int i = 0; i < n; i++) { int idx = sel[i]; if (fm->entry_types[idx] == FM_ENTRY_DRIVE) continue; filtered[fn++] = idx; if (fm->is_dir[idx]) any_dir = true; } montauk::mfree(sel); if (fn == 0) { montauk::mfree(filtered); return; } // Anything with a directory or multi-item selection: confirm first. if (any_dir || fn > 1) { if (fn == 1 && any_dir) { filemanager_show_directory_delete_confirmation(fm, filtered[0]); } else { filemanager_show_bulk_delete_confirmation(fm, filtered, fn); } montauk::mfree(filtered); return; } // Single plain file. Freeing a large file's blocks is a slow syscall // (the fs driver rewrites allocation bitmaps per block), so deleting it // inline would stall the desktop -- Files runs inside desktop.elf, so a // blocked main thread freezes the compositor and cursor. Hand anything // past the inline threshold to the background worker + progress dialog; // small files still delete inline so there's no dialog flash. int only = filtered[0]; char fullpath[512]; filemanager_build_fullpath(fullpath, 512, fm->current_path, fm->entry_names[only]); if (fm->entry_sizes[only] > FILEMANAGER_DELETE_INLINE_MAX_BYTES) { const char* one = fullpath; if (filemanager_start_delete_job(fm, &one, 1)) { montauk::mfree(filtered); return; } if (fm->active_fileop) { // Another operation owns the worker. Deleting a big file inline // here would stall the desktop (the exact bug this path avoids), // so refuse; its progress dialog tells the user we're busy. montauk::mfree(filtered); return; } // Job allocation failed (OOM): fall through to the inline delete. } montauk::mfree(filtered); montauk::StatusCode status = filemanager_status_from_result(montauk::fdelete(fullpath)); filemanager_clear_multi_selection(fm); filemanager_read_dir(fm); if (status != montauk::Success) { char heading[192]; snprintf(heading, sizeof(heading), "Couldn't delete \"%s\"", path_basename(fullpath)); filemanager_show_error(fm->desktop, fm, heading, fullpath, status); } } void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, int target_idx) { fm->ctx_open = true; fm->ctx_x = local_x; fm->ctx_y = local_y; fm->ctx_target_idx = target_idx; fm->ctx_hover = -1; fm->ctx_item_count = 0; if (target_idx >= 0 && target_idx < fm->entry_count) { int tt = fm->entry_types[target_idx]; if ((filemanager_is_computer_view(fm) && (tt == FM_ENTRY_DRIVE || tt == FM_ENTRY_HOME || tt == FM_ENTRY_APPS_ROOT || tt == FM_ENTRY_SPECIAL_DIR || tt == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT)) || (filemanager_is_virtual_view(fm) && tt == FM_ENTRY_VIRTUAL_ITEM)) { // Drive, Home, Apps shortcut, or app entry: only Open fm->ctx_items[fm->ctx_item_count++] = CTX_OPEN; fm->ctx_items[fm->ctx_item_count++] = CTX_PROPERTIES; } else { bool several = fm->multi_count > 1; fm->ctx_items[fm->ctx_item_count++] = CTX_OPEN; if (!several && !fm->is_dir[target_idx]) fm->ctx_items[fm->ctx_item_count++] = CTX_OPEN_WITH; fm->ctx_items[fm->ctx_item_count++] = CTX_COPY; fm->ctx_items[fm->ctx_item_count++] = CTX_CUT; if (!several) fm->ctx_items[fm->ctx_item_count++] = CTX_RENAME; fm->ctx_items[fm->ctx_item_count++] = CTX_DELETE; fm->ctx_items[fm->ctx_item_count++] = CTX_PROPERTIES; } } else { // Background click if (filemanager_is_virtual_view(fm)) { fm->ctx_open = false; return; } if (filemanager_is_computer_view(fm)) { fm->ctx_items[fm->ctx_item_count++] = CTX_REFRESH; } else { if (fm->clipboard_count > 0) fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE; fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER; if (fm->entry_count > 0) fm->ctx_items[fm->ctx_item_count++] = CTX_SELECT_ALL; fm->ctx_items[fm->ctx_item_count++] = CTX_SORT_NAME; fm->ctx_items[fm->ctx_item_count++] = CTX_SORT_SIZE; fm->ctx_items[fm->ctx_item_count++] = CTX_SORT_TYPE; fm->ctx_items[fm->ctx_item_count++] = CTX_SORT_MODIFIED; fm->ctx_items[fm->ctx_item_count++] = CTX_REFRESH; } } } void filemanager_close_ctx_menu(FileManagerState* fm) { fm->ctx_open = false; fm->ctx_hover = -1; } } // namespace filemanager