#include "file_browser.h" #include #include #include FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) { #ifdef PORTALS main_context = g_main_context_default(); main_loop = g_main_loop_new(main_context, true); portal = xdp_portal_new(); type = g_variant_type_new("a(sa(us))"); inner_filter_type = g_variant_type_new("a(us)"); #endif this->save = save; fallback = ImGui::FileBrowser((save ? ImGuiFileBrowserFlags_CreateNewDir|ImGuiFileBrowserFlags_EnterNewFilename : 0) + extra_fallback_flags); } void FileBrowser::SetTypeFilters(string name, vector filters) { filter_name = name; this->filters = filters; #ifdef PORTALS if (variant != NULL) { g_variant_unref(variant); } GVariantBuilder builder; GVariantBuilder inner_filter_builder; g_variant_builder_init(&builder, type); g_variant_builder_init(&inner_filter_builder, inner_filter_type); GVariant *name_variant = g_variant_new_string((const gchar*)name.c_str()); for (auto filter : filters) { GVariant *id = g_variant_new_uint32(0); GVariant *filter_variant = g_variant_new_string((const gchar*)(string("*") + filter).c_str()); GVariant **filter_children = new GVariant*[2] {id, filter_variant}; GVariant *filter_tuple = g_variant_new_tuple(filter_children, 2); g_variant_builder_add_value(&inner_filter_builder, filter_tuple); } GVariant *inner_filters = g_variant_builder_end(&inner_filter_builder); GVariant **outer_filter_children = new GVariant*[2] {name_variant, inner_filters}; GVariant *outer_filter_tuple = g_variant_new_tuple(outer_filter_children, 2); g_variant_builder_add_value(&builder, outer_filter_tuple); variant = g_variant_builder_end(&builder); g_variant_ref(variant); #endif fallback.SetTypeFilters(filters); } void FileBrowser::SetPwd(path path) { pwd = path; #ifndef PORTALS fallback.SetPwd(path); #endif } bool FileBrowser::HasSelected() { #ifdef PORTALS return selected.has_value(); #else return fallback.HasSelected(); #endif } path FileBrowser::GetSelected() { #ifdef PORTALS return selected.value_or(path()); #else return fallback.GetSelected(); #endif } void FileBrowser::SetWindowSize(int w, int h) { window_size = ImVec2(w, h); fallback.SetWindowSize(w, h); } void FileBrowser::SetWindowPos(int x, int y) { window_pos = ImVec2(x, y); fallback.SetWindowPos(x, y); } void FileBrowser::Open() { #ifdef PORTALS open = true; if (save) { xdp_portal_save_file(portal, NULL, title.c_str(), NULL, pwd.c_str(), NULL, variant, NULL, NULL, XDP_SAVE_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserSaveCallback, this); } else { xdp_portal_open_file(portal, NULL, title.c_str(), variant, NULL, NULL, XDP_OPEN_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserOpenCallback, this); } #else fallback.Open(); #endif } #ifdef PORTALS path url_decode(string str) { static const string file_proto = "file://"; if (str.starts_with(file_proto)) { str = str.substr(file_proto.length()); } string ret; int len = str.length(); for (int i = 0; i < len; i++) { if (str[i] != '%') { // Don't decode '+' as a space as libportal doesn't encode space as '+', and encodes '+' in a file path verbatim ret += str[i]; } else { unsigned int ch_int; sscanf(str.substr(i + 1, 2).c_str(), "%x", &ch_int); ret += static_cast(ch_int); i = i + 2; } } return ret; } void FileBrowser::FileBrowserOpenCallback(GObject *src, GAsyncResult *res, gpointer data) { (void)src; FileBrowser *self = (FileBrowser*)data; GVariant *variant = xdp_portal_open_file_finish(self->portal, res, NULL); if (variant == NULL) { printf("Cancelled.\n"); return; } GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY); GVariant *first_uri = g_variant_get_child_value(uris, 0); gsize length; const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length); vector c_str_vec; c_str_vec.reserve(length + 1); for (gsize i = 0; i < length; i++) { c_str_vec.push_back(c_str_without_terminater[i]); } c_str_vec.push_back('\0'); const char *c_str = c_str_vec.data(); printf("Selected file %s\n", c_str); path path = url_decode(c_str); self->open = false; self->selected.emplace(path); } void FileBrowser::FileBrowserSaveCallback(GObject *src, GAsyncResult *res, gpointer data) { (void)src; FileBrowser *self = (FileBrowser*)data; GVariant *variant = xdp_portal_save_file_finish(self->portal, res, NULL); if (variant == NULL) { printf("Cancelled.\n"); return; } GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY); GVariant *first_uri = g_variant_get_child_value(uris, 0); gsize length; const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length); vector c_str_vec; c_str_vec.reserve(length + 1); for (gsize i = 0; i < length; i++) { c_str_vec.push_back(c_str_without_terminater[i]); } c_str_vec.push_back('\0'); const char *c_str = c_str_vec.data(); printf("Selected file %s\n", c_str); path path = url_decode(c_str); self->open = false; self->selected.emplace(path); } #endif void FileBrowser::Display() { #ifdef PORTALS g_main_context_iteration(main_context, false); #else fallback.Display(); #endif } void FileBrowser::ClearSelected() { selected = optional(); #ifndef PORTALS fallback.ClearSelected(); #endif } void FileBrowser::SetTitle(string title) { this->title = title; #ifndef PORTALS fallback.SetTitle(title); #endif } bool FileBrowser::IsOpened() { #ifdef PORTALS return open; #else return fallback.IsOpened(); #endif } FileBrowser::~FileBrowser() { #ifdef PORTALS if (variant != NULL) { g_variant_unref(variant); } if (type != NULL) { g_variant_type_free(type); } if (inner_filter_type != NULL) { g_variant_type_free(inner_filter_type); } g_main_loop_quit(main_loop); #endif }