diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2023-08-01 10:18:41 +0300 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2023-08-17 10:56:16 +0300 |
commit | 6cc314e0fa3f0070c1bbeef7f8dbabfff976e190 (patch) | |
tree | 035ab93617a1fff4e8ad94198f61849a3f6b104f /platform/linuxbsd/freedesktop_portal_desktop.cpp | |
parent | a278c1b98a81738a35b96a933a6e6cf771f9ab2d (diff) | |
download | redot-engine-6cc314e0fa3f0070c1bbeef7f8dbabfff976e190.tar.gz |
[Linux/Freedesktop] Implement native file selection dialog support.
Diffstat (limited to 'platform/linuxbsd/freedesktop_portal_desktop.cpp')
-rw-r--r-- | platform/linuxbsd/freedesktop_portal_desktop.cpp | 350 |
1 files changed, 343 insertions, 7 deletions
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index c3cb0f411d..91c14e0e91 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -32,6 +32,7 @@ #ifdef DBUS_ENABLED +#include "core/crypto/crypto_core.h" #include "core/error/error_macros.h" #include "core/os/os.h" #include "core/string/ustring.h" @@ -43,12 +44,15 @@ #include <dbus/dbus.h> #endif +#include <unistd.h> + #define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop" #define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop" #define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings" +#define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser" -static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { +bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { DBusMessageIter iter[3]; dbus_message_iter_init(p_reply_message, &iter[0]); @@ -80,11 +84,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { - dbus_error_free(&error); - unsupported = true; if (OS::get_singleton()->is_stdout_verbose()) { - ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message); + ERR_PRINT(vformat("Error opening D-Bus connection: %s", error.message)); } + dbus_error_free(&error); + unsupported = true; return false; } @@ -100,11 +104,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { - dbus_error_free(&error); - dbus_connection_unref(bus); if (OS::get_singleton()->is_stdout_verbose()) { - ERR_PRINT(String() + "Error on D-Bus communication: " + error.message); + ERR_PRINT(vformat("Error on D-Bus communication: %s", error.message)); } + dbus_error_free(&error); + dbus_connection_unref(bus); return false; } @@ -126,6 +130,317 @@ uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() { return value; } +static const char *cs_empty = ""; + +void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const String &p_string) { + CharString cs = p_string.utf8(); + const char *cs_ptr = cs.ptr(); + if (cs_ptr) { + dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_ptr); + } else { + dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_empty); + } +} + +void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + DBusMessageIter arr_iter; + const char *filters_key = "filters"; + + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key); + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter); + dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter); + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() == 2) { + DBusMessageIter struct_iter; + DBusMessageIter array_iter; + DBusMessageIter array_struct_iter; + dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter); + append_dbus_string(&struct_iter, tokens[0]); + + dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); + dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); + { + const unsigned nil = 0; + dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil); + } + append_dbus_string(&array_struct_iter, tokens[1]); + dbus_message_iter_close_container(&array_iter, &array_struct_iter); + dbus_message_iter_close_container(&struct_iter, &array_iter); + dbus_message_iter_close_container(&arr_iter, &struct_iter); + } + } + dbus_message_iter_close_container(&var_iter, &arr_iter); + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + +void FreeDesktopPortalDesktop::append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + append_dbus_string(&dict_iter, p_key); + + if (p_as_byte_array) { + DBusMessageIter arr_iter; + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "ay", &var_iter); + dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "y", &arr_iter); + CharString cs = p_value.utf8(); + const char *cs_ptr = cs.get_data(); + do { + dbus_message_iter_append_basic(&arr_iter, DBUS_TYPE_BYTE, cs_ptr); + } while (*cs_ptr++); + dbus_message_iter_close_container(&var_iter, &arr_iter); + } else { + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "s", &var_iter); + append_dbus_string(&var_iter, p_value); + } + + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + +void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + append_dbus_string(&dict_iter, p_key); + + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "b", &var_iter); + { + int val = p_value; + dbus_message_iter_append_basic(&var_iter, DBUS_TYPE_BOOLEAN, &val); + } + + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + +bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) { + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); + + dbus_uint32_t resp_code; + dbus_message_iter_get_basic(p_iter, &resp_code); + if (resp_code != 0) { + r_cancel = true; + } else { + r_cancel = false; + ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false); + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false); + + DBusMessageIter dict_iter; + dbus_message_iter_recurse(p_iter, &dict_iter); + while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter iter; + dbus_message_iter_recurse(&dict_iter, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + const char *key; + dbus_message_iter_get_basic(&iter, &key); + dbus_message_iter_next(&iter); + + DBusMessageIter var_iter; + dbus_message_iter_recurse(&iter, &var_iter); + if (strcmp(key, "uris") == 0) { + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) { + DBusMessageIter uri_iter; + dbus_message_iter_recurse(&var_iter, &uri_iter); + while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) { + const char *value; + dbus_message_iter_get_basic(&uri_iter, &value); + r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_decode()); + if (!dbus_message_iter_next(&uri_iter)) { + break; + } + } + } + } + } + if (!dbus_message_iter_next(&dict_iter)) { + break; + } + } + } + return true; +} + +Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + if (unsupported) { + return FAILED; + } + + DBusError err; + dbus_error_init(&err); + + // Open connection and add signal handler. + FileDialogData fd; + fd.callback = p_callback; + + CryptoCore::RandomGenerator rng; + ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); + uint8_t uuid[64]; + Error rng_err = rng.get_random_bytes(uuid, 64); + ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token."); + + fd.connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to open DBus connection: %s", err.message)); + dbus_error_free(&err); + unsupported = true; + return FAILED; + } + + String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(fd.connection)); + String token = String::hex_encode_buffer(uuid, 64); + String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token); + + fd.path = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name); + dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + + // Generate FileChooser message. + const char *method = nullptr; + switch (p_mode) { + case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: { + method = "SaveFile"; + } break; + case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY: + case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE: + case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR: + case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: { + method = "OpenFile"; + } break; + } + + DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method); + { + DBusMessageIter iter; + dbus_message_iter_init_append(message, &iter); + + append_dbus_string(&iter, p_xid); + append_dbus_string(&iter, p_title); + + DBusMessageIter arr_iter; + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter); + + append_dbus_dict_string(&arr_iter, "handle_token", token); + append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES); + append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR); + append_dbus_dict_filters(&arr_iter, p_filters); + append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true); + if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) { + append_dbus_dict_string(&arr_iter, "current_name", p_filename); + } + + dbus_message_iter_close_container(&iter, &arr_iter); + } + + DBusMessage *reply = dbus_connection_send_with_reply_and_block(fd.connection, message, DBUS_TIMEOUT_INFINITE, &err); + dbus_message_unref(message); + + if (!reply || dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to send DBus message: %s", err.message)); + dbus_error_free(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_connection_unref(fd.connection); + return FAILED; + } + + // Update signal path. + { + DBusMessageIter iter; + if (dbus_message_iter_init(reply, &iter)) { + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) { + const char *new_path = nullptr; + dbus_message_iter_get_basic(&iter, &new_path); + if (String::utf8(new_path) != path) { + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + fd.path = String::utf8(new_path); + dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + } + } + } + } + dbus_message_unref(reply); + + MutexLock lock(file_dialog_mutex); + file_dialogs.push_back(fd); + + return OK; +} + +void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { + FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud; + + while (!portal->file_dialog_thread_abort.is_set()) { + { + MutexLock lock(portal->file_dialog_mutex); + for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) { + bool remove = false; + { + FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; + if (fd.connection) { + while (true) { + DBusMessage *msg = dbus_connection_pop_message(fd.connection); + if (!msg) { + break; + } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + DBusMessageIter iter; + if (dbus_message_iter_init(msg, &iter)) { + bool cancel = false; + Vector<String> uris; + file_chooser_parse_response(&iter, cancel, uris); + + if (fd.callback.is_valid()) { + Variant v_status = !cancel; + Variant v_files = uris; + Variant *v_args[2] = { &v_status, &v_files }; + fd.callback.call_deferredp((const Variant **)&v_args, 2); + } + } + dbus_message_unref(msg); + + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + remove = true; + break; + } + dbus_message_unref(msg); + } + dbus_connection_read_write(fd.connection, 0); + } + } + if (remove) { + portal->file_dialogs.remove_at(i); + } + } + } + usleep(50000); + } +} + FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { #ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED @@ -153,6 +468,27 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { print_verbose("PortalDesktop: Unsupported DBus library version!"); unsupported = true; } + + if (!unsupported) { + file_dialog_thread_abort.clear(); + file_dialog_thread.start(FreeDesktopPortalDesktop::_thread_file_dialog_monitor, this); + } +} + +FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() { + file_dialog_thread_abort.set(); + if (file_dialog_thread.is_started()) { + file_dialog_thread.wait_to_finish(); + } + for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) { + if (fd.connection) { + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + } + } } #endif // DBUS_ENABLED |