/* * associations.cpp * File type associations for the Files file manager * Copyright (c) 2026 Daniel Hammer */ #include "filemanager_internal.hpp" #include #include namespace filemanager { // Resolution is shared with the desktop (gui/app_catalog.hpp). static bool assoc_item_launchable(const DesktopState* ds, const DesktopItem& item) { return gui::app_catalog_launchable(ds, item); } static bool assoc_item_claims(const DesktopItem& item, const char* ext) { return gui::app_catalog_claims(item, ext); } int filemanager_assoc_default(DesktopState* ds, const char* ext) { return gui::app_catalog_default_for(ds, ext); } int filemanager_assoc_candidates(DesktopState* ds, const char* ext, int* out, int max, int* recommended) { if (recommended) *recommended = 0; if (!ds || !out || max <= 0 || ds->desktop_item_count <= 0) return 0; int* sorted = (int*)montauk::malloc((uint64_t)ds->desktop_item_count * sizeof(int)); if (!sorted) return 0; int n = 0; for (int i = 0; i < ds->desktop_item_count; i++) { if (assoc_item_launchable(ds, ds->desktop_items[i])) sorted[n++] = i; } for (int i = 1; i < n; i++) { int tmp = sorted[i]; int j = i - 1; while (j >= 0 && str_compare_ci(ds->desktop_items[tmp].name, ds->desktop_items[sorted[j]].name) < 0) { sorted[j + 1] = sorted[j]; j--; } sorted[j + 1] = tmp; } // Apps that declare the type lead; any other regular app follows (an app // that declares nothing may still open the file). Hidden and settings // items only appear when they claim the type. int count = 0; for (int i = 0; i < n && count < max; i++) { if (assoc_item_claims(ds->desktop_items[sorted[i]], ext)) out[count++] = sorted[i]; } if (recommended) *recommended = count; for (int i = 0; i < n && count < max; i++) { const DesktopItem& item = ds->desktop_items[sorted[i]]; if (item.section == DESKTOP_ITEM_SECTION_APPS && !assoc_item_claims(item, ext)) out[count++] = sorted[i]; } montauk::mfree(sorted); return count; } bool filemanager_assoc_set_default(DesktopState* ds, const char* ext, int item_index) { if (!ds || !ds->current_user[0] || !gui::app_catalog_ext_storable(ext)) return false; if (item_index < 0 || item_index >= ds->desktop_item_count) return false; auto doc = montauk::config::load_user(ds->current_user, gui::kAssociationsConfig); char key[48]; gui::app_catalog_assoc_key(key, sizeof(key), ext); montauk::config::set_string(&doc, key, ds->desktop_items[item_index].id); int rc = montauk::config::save_user(ds->current_user, gui::kAssociationsConfig, &doc); doc.destroy(); return rc == 0; } bool filemanager_file_looks_textual(const char* path) { return gui::app_catalog_file_looks_textual(path); } bool filemanager_launch_with(DesktopState* ds, int item_index, const char* path) { if (!ds || item_index < 0 || item_index >= ds->desktop_item_count) return false; const DesktopItem& item = ds->desktop_items[item_index]; if (!assoc_item_launchable(ds, item)) return false; // Apps that declare file types are started by the desktop, with the // authority they get from the launcher. The desktop forwards a document // path only to those, so anything else is started here. if (item.file_types[0]) return files_host::launch_item(item.id, path); return desktop_spawn_app(ds, item.binary_path, path) >= 0; } } // namespace filemanager