Path: blob/master/platform/linuxbsd/freedesktop_portal_desktop.cpp
10277 views
/**************************************************************************/1/* freedesktop_portal_desktop.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "freedesktop_portal_desktop.h"3132#ifdef DBUS_ENABLED3334#include "core/crypto/crypto_core.h"35#include "core/error/error_macros.h"36#include "core/os/os.h"37#include "core/string/ustring.h"38#include "core/variant/variant.h"3940#ifdef SOWRAP_ENABLED41#include "dbus-so_wrap.h"42#else43#include <dbus/dbus.h>44#endif4546#include <unistd.h>4748#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop"49#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop"5051#define BUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties"52#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings"53#define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser"54#define BUS_INTERFACE_SCREENSHOT "org.freedesktop.portal.Screenshot"5556bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, ReadVariantType p_type, void *r_value) {57DBusMessageIter iter[3];5859dbus_message_iter_init(p_reply_message, &iter[0]);60if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {61return false;62}6364dbus_message_iter_recurse(&iter[0], &iter[1]);65if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {66return false;67}6869dbus_message_iter_recurse(&iter[1], &iter[2]);70if (p_type == VAR_TYPE_COLOR) {71if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_STRUCT) {72return false;73}74DBusMessageIter struct_iter;75dbus_message_iter_recurse(&iter[2], &struct_iter);76int idx = 0;77while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) {78double value = 0.0;79dbus_message_iter_get_basic(&struct_iter, &value);80if (value < 0.0 || value > 1.0) {81return false;82}83if (idx == 0) {84static_cast<Color *>(r_value)->r = value;85} else if (idx == 1) {86static_cast<Color *>(r_value)->g = value;87} else if (idx == 2) {88static_cast<Color *>(r_value)->b = value;89}90idx++;91if (!dbus_message_iter_next(&struct_iter)) {92break;93}94}95if (idx != 3) {96return false;97}98} else if (p_type == VAR_TYPE_UINT32) {99if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_UINT32) {100return false;101}102dbus_message_iter_get_basic(&iter[2], r_value);103} else if (p_type == VAR_TYPE_BOOL) {104if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_BOOLEAN) {105return false;106}107dbus_message_iter_get_basic(&iter[2], r_value);108}109return true;110}111112bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char *p_key, ReadVariantType p_type, void *r_value) {113if (unsupported) {114return false;115}116117DBusError error;118dbus_error_init(&error);119120DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error);121if (dbus_error_is_set(&error)) {122if (OS::get_singleton()->is_stdout_verbose()) {123ERR_PRINT(vformat("Error opening D-Bus connection: %s", error.message));124}125dbus_error_free(&error);126unsupported = true;127return false;128}129130DBusMessage *message = dbus_message_new_method_call(131BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SETTINGS,132"Read");133dbus_message_append_args(134message,135DBUS_TYPE_STRING, &p_namespace,136DBUS_TYPE_STRING, &p_key,137DBUS_TYPE_INVALID);138139DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error);140dbus_message_unref(message);141if (dbus_error_is_set(&error)) {142if (OS::get_singleton()->is_stdout_verbose()) {143ERR_PRINT(vformat("Error on D-Bus communication: %s", error.message));144}145dbus_error_free(&error);146dbus_connection_unref(bus);147return false;148}149150bool success = try_parse_variant(reply, p_type, r_value);151152dbus_message_unref(reply);153dbus_connection_unref(bus);154155return success;156}157158uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() {159if (unsupported) {160return 0;161}162163uint32_t value = 0;164if (read_setting("org.freedesktop.appearance", "color-scheme", VAR_TYPE_UINT32, &value)) {165return value;166} else {167return 0;168}169}170171Color FreeDesktopPortalDesktop::get_appearance_accent_color() {172if (unsupported) {173return Color(0, 0, 0, 0);174}175176Color value;177if (read_setting("org.freedesktop.appearance", "accent-color", VAR_TYPE_COLOR, &value)) {178return value;179} else {180return Color(0, 0, 0, 0);181}182}183184uint32_t FreeDesktopPortalDesktop::get_high_contrast() {185if (unsupported) {186return -1;187}188189dbus_bool_t value = false;190if (read_setting("org.gnome.desktop.a11y.interface", "high-contrast", VAR_TYPE_BOOL, &value)) {191return value;192}193return -1;194}195196static const char *cs_empty = "";197198void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const String &p_string) {199CharString cs = p_string.utf8();200const char *cs_ptr = cs.ptr();201if (cs_ptr) {202dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_ptr);203} else {204dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_empty);205}206}207208void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options, HashMap<String, String> &r_ids) {209DBusMessageIter dict_iter;210DBusMessageIter var_iter;211DBusMessageIter arr_iter;212const char *choices_key = "choices";213214dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);215dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key);216dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter);217dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter);218219r_ids.clear();220for (int i = 0; i < p_options.size(); i++) {221const Dictionary &item = p_options[i];222if (!item.has("name") || !item.has("values") || !item.has("default")) {223continue;224}225const String &name = item["name"];226const Vector<String> &options = item["values"];227int default_idx = item["default"];228229DBusMessageIter struct_iter;230DBusMessageIter array_iter;231DBusMessageIter array_struct_iter;232dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);233append_dbus_string(&struct_iter, "option_" + itos(i)); // ID.234append_dbus_string(&struct_iter, name); // User visible name.235r_ids["option_" + itos(i)] = name;236237dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter);238for (int j = 0; j < options.size(); j++) {239dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);240append_dbus_string(&array_struct_iter, itos(j));241append_dbus_string(&array_struct_iter, options[j]);242dbus_message_iter_close_container(&array_iter, &array_struct_iter);243}244dbus_message_iter_close_container(&struct_iter, &array_iter);245if (options.is_empty()) {246append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection.247} else {248append_dbus_string(&struct_iter, itos(default_idx)); // Default selection.249}250251dbus_message_iter_close_container(&arr_iter, &struct_iter);252}253dbus_message_iter_close_container(&var_iter, &arr_iter);254dbus_message_iter_close_container(&dict_iter, &var_iter);255dbus_message_iter_close_container(p_iter, &dict_iter);256}257258void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts, const Vector<String> &p_filter_mimes) {259DBusMessageIter dict_iter;260DBusMessageIter var_iter;261DBusMessageIter arr_iter;262const char *filters_key = "filters";263264ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size());265ERR_FAIL_COND(p_filter_names.size() != p_filter_mimes.size());266267dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);268dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);269dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);270dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);271for (int i = 0; i < p_filter_names.size(); i++) {272DBusMessageIter struct_iter;273DBusMessageIter array_iter;274DBusMessageIter array_struct_iter;275dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);276append_dbus_string(&struct_iter, p_filter_names[i]);277278dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);279const String &flt_orig = p_filter_exts[i];280String flt;281for (int j = 0; j < flt_orig.length(); j++) {282if (is_unicode_letter(flt_orig[j])) {283flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j]));284} else {285flt += flt_orig[j];286}287}288int filter_slice_count = flt.get_slice_count(",");289for (int j = 0; j < filter_slice_count; j++) {290dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);291String str = (flt.get_slicec(',', j).strip_edges());292{293const unsigned flt_type = 0;294dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &flt_type);295}296append_dbus_string(&array_struct_iter, str);297dbus_message_iter_close_container(&array_iter, &array_struct_iter);298}299const String &mime = p_filter_mimes[i];300filter_slice_count = mime.get_slice_count(",");301for (int j = 0; j < filter_slice_count; j++) {302dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);303String str = mime.get_slicec(',', j).strip_edges();304{305const unsigned flt_type = 1;306dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &flt_type);307}308append_dbus_string(&array_struct_iter, str);309dbus_message_iter_close_container(&array_iter, &array_struct_iter);310}311dbus_message_iter_close_container(&struct_iter, &array_iter);312dbus_message_iter_close_container(&arr_iter, &struct_iter);313}314dbus_message_iter_close_container(&var_iter, &arr_iter);315dbus_message_iter_close_container(&dict_iter, &var_iter);316dbus_message_iter_close_container(p_iter, &dict_iter);317}318319void FreeDesktopPortalDesktop::append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array) {320DBusMessageIter dict_iter;321DBusMessageIter var_iter;322dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);323append_dbus_string(&dict_iter, p_key);324325if (p_as_byte_array) {326DBusMessageIter arr_iter;327dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "ay", &var_iter);328dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "y", &arr_iter);329CharString cs = p_value.utf8();330const char *cs_ptr = cs.get_data();331do {332dbus_message_iter_append_basic(&arr_iter, DBUS_TYPE_BYTE, cs_ptr);333} while (*cs_ptr++);334dbus_message_iter_close_container(&var_iter, &arr_iter);335} else {336dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "s", &var_iter);337append_dbus_string(&var_iter, p_value);338}339340dbus_message_iter_close_container(&dict_iter, &var_iter);341dbus_message_iter_close_container(p_iter, &dict_iter);342}343344void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value) {345DBusMessageIter dict_iter;346DBusMessageIter var_iter;347dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);348append_dbus_string(&dict_iter, p_key);349350dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "b", &var_iter);351{352int val = p_value;353dbus_message_iter_append_basic(&var_iter, DBUS_TYPE_BOOLEAN, &val);354}355356dbus_message_iter_close_container(&dict_iter, &var_iter);357dbus_message_iter_close_container(p_iter, &dict_iter);358}359360bool FreeDesktopPortalDesktop::color_picker_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Color &r_color) {361ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);362363dbus_uint32_t resp_code;364dbus_message_iter_get_basic(p_iter, &resp_code);365if (resp_code != 0) {366r_cancel = true;367} else {368r_cancel = false;369ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false);370ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false);371372DBusMessageIter dict_iter;373dbus_message_iter_recurse(p_iter, &dict_iter);374while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {375DBusMessageIter iter;376dbus_message_iter_recurse(&dict_iter, &iter);377if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {378const char *key;379dbus_message_iter_get_basic(&iter, &key);380dbus_message_iter_next(&iter);381382DBusMessageIter var_iter;383dbus_message_iter_recurse(&iter, &var_iter);384if (strcmp(key, "color") == 0) { // (ddd)385if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {386DBusMessageIter struct_iter;387dbus_message_iter_recurse(&var_iter, &struct_iter);388int idx = 0;389while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) {390double value = 0.0;391dbus_message_iter_get_basic(&struct_iter, &value);392if (idx == 0) {393r_color.r = value;394} else if (idx == 1) {395r_color.g = value;396} else if (idx == 2) {397r_color.b = value;398}399idx++;400if (!dbus_message_iter_next(&struct_iter)) {401break;402}403}404}405}406}407if (!dbus_message_iter_next(&dict_iter)) {408break;409}410}411}412return true;413}414415bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, const HashMap<String, String> &p_ids, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) {416ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);417418dbus_uint32_t resp_code;419dbus_message_iter_get_basic(p_iter, &resp_code);420if (resp_code != 0) {421r_cancel = true;422} else {423r_cancel = false;424ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false);425ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false);426427DBusMessageIter dict_iter;428dbus_message_iter_recurse(p_iter, &dict_iter);429while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {430DBusMessageIter iter;431dbus_message_iter_recurse(&dict_iter, &iter);432if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {433const char *key;434dbus_message_iter_get_basic(&iter, &key);435dbus_message_iter_next(&iter);436437DBusMessageIter var_iter;438dbus_message_iter_recurse(&iter, &var_iter);439if (strcmp(key, "current_filter") == 0) { // (sa(us))440if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {441DBusMessageIter struct_iter;442dbus_message_iter_recurse(&var_iter, &struct_iter);443while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {444const char *value;445dbus_message_iter_get_basic(&struct_iter, &value);446String name = String::utf8(value);447448r_index = p_names.find(name);449if (!dbus_message_iter_next(&struct_iter)) {450break;451}452}453}454} else if (strcmp(key, "choices") == 0) { // a(ss) {455if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {456DBusMessageIter struct_iter;457dbus_message_iter_recurse(&var_iter, &struct_iter);458while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) {459DBusMessageIter opt_iter;460dbus_message_iter_recurse(&struct_iter, &opt_iter);461const char *opt_key = nullptr;462dbus_message_iter_get_basic(&opt_iter, &opt_key);463String opt_skey = String::utf8(opt_key);464dbus_message_iter_next(&opt_iter);465const char *opt_val = nullptr;466dbus_message_iter_get_basic(&opt_iter, &opt_val);467String opt_sval = String::utf8(opt_val);468469if (p_ids.has(opt_skey)) {470opt_skey = p_ids[opt_skey];471if (opt_sval == "true") {472r_options[opt_skey] = true;473} else if (opt_sval == "false") {474r_options[opt_skey] = false;475} else {476r_options[opt_skey] = opt_sval.to_int();477}478}479480if (!dbus_message_iter_next(&struct_iter)) {481break;482}483}484}485} else if (strcmp(key, "uris") == 0) { // as486if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {487DBusMessageIter uri_iter;488dbus_message_iter_recurse(&var_iter, &uri_iter);489while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) {490const char *value;491dbus_message_iter_get_basic(&uri_iter, &value);492r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_file_decode());493if (!dbus_message_iter_next(&uri_iter)) {494break;495}496}497}498}499}500if (!dbus_message_iter_next(&dict_iter)) {501break;502}503}504}505return true;506}507508bool FreeDesktopPortalDesktop::color_picker(const String &p_xid, const Callable &p_callback) {509if (unsupported) {510return false;511}512513DBusError err;514dbus_error_init(&err);515516// Open connection and add signal handler.517ColorPickerData cd;518cd.callback = p_callback;519520CryptoCore::RandomGenerator rng;521ERR_FAIL_COND_V_MSG(rng.init(), false, "Failed to initialize random number generator.");522uint8_t uuid[64];523Error rng_err = rng.get_random_bytes(uuid, 64);524ERR_FAIL_COND_V_MSG(rng_err, false, "Failed to generate unique token.");525526String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection));527String token = String::hex_encode_buffer(uuid, 64);528String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace_char('.', '_').remove_char(':'), token);529530cd.path = path;531cd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);532dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err);533if (dbus_error_is_set(&err)) {534ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));535dbus_error_free(&err);536return false;537}538539DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SCREENSHOT, "PickColor");540{541DBusMessageIter iter;542dbus_message_iter_init_append(message, &iter);543544append_dbus_string(&iter, p_xid);545546DBusMessageIter arr_iter;547dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter);548append_dbus_dict_string(&arr_iter, "handle_token", token);549dbus_message_iter_close_container(&iter, &arr_iter);550}551DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err);552dbus_message_unref(message);553554if (!reply || dbus_error_is_set(&err)) {555ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));556dbus_error_free(&err);557dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err);558return false;559}560561// Update signal path.562{563DBusMessageIter iter;564if (dbus_message_iter_init(reply, &iter)) {565if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {566const char *new_path = nullptr;567dbus_message_iter_get_basic(&iter, &new_path);568if (String::utf8(new_path) != path) {569dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err);570if (dbus_error_is_set(&err)) {571ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));572dbus_error_free(&err);573return false;574}575cd.filter = String::utf8(new_path);576dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err);577if (dbus_error_is_set(&err)) {578ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));579dbus_error_free(&err);580return false;581}582}583}584}585}586dbus_message_unref(reply);587588MutexLock lock(color_picker_mutex);589color_pickers.push_back(cd);590591return true;592}593594bool FreeDesktopPortalDesktop::_is_interface_supported(const char *p_iface) {595bool supported = false;596DBusError err;597dbus_error_init(&err);598DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &err);599if (dbus_error_is_set(&err)) {600dbus_error_free(&err);601} else {602DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_PROPERTIES, "Get");603if (message) {604const char *name_space = p_iface;605const char *key = "version";606dbus_message_append_args(607message,608DBUS_TYPE_STRING, &name_space,609DBUS_TYPE_STRING, &key,610DBUS_TYPE_INVALID);611DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 250, &err);612if (dbus_error_is_set(&err)) {613dbus_error_free(&err);614} else if (reply) {615DBusMessageIter iter;616if (dbus_message_iter_init(reply, &iter)) {617DBusMessageIter iter_ver;618dbus_message_iter_recurse(&iter, &iter_ver);619dbus_uint32_t ver_code;620dbus_message_iter_get_basic(&iter_ver, &ver_code);621print_verbose(vformat("PortalDesktop: %s version %d detected.", p_iface, ver_code));622supported = true;623}624dbus_message_unref(reply);625}626dbus_message_unref(message);627}628dbus_connection_unref(bus);629}630return supported;631}632633bool FreeDesktopPortalDesktop::is_file_chooser_supported() {634static int supported = -1;635if (supported == -1) {636supported = _is_interface_supported(BUS_INTERFACE_FILE_CHOOSER);637}638return supported;639}640641bool FreeDesktopPortalDesktop::is_settings_supported() {642static int supported = -1;643if (supported == -1) {644supported = _is_interface_supported(BUS_INTERFACE_SETTINGS);645}646return supported;647}648649bool FreeDesktopPortalDesktop::is_screenshot_supported() {650static int supported = -1;651if (supported == -1) {652supported = _is_interface_supported(BUS_INTERFACE_SCREENSHOT);653}654return supported;655}656657Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {658if (unsupported) {659return FAILED;660}661662ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);663ERR_FAIL_NULL_V(monitor_connection, FAILED);664665Vector<String> filter_names;666Vector<String> filter_exts;667Vector<String> filter_mimes;668for (int i = 0; i < p_filters.size(); i++) {669Vector<String> tokens = p_filters[i].split(";");670if (tokens.size() >= 1) {671String flt = tokens[0].strip_edges();672String mime = (tokens.size() >= 3) ? tokens[2].strip_edges() : String();673if (!flt.is_empty() || !mime.is_empty()) {674if (tokens.size() >= 2) {675if (flt == "*.*") {676filter_exts.push_back("*");677} else {678filter_exts.push_back(flt);679}680filter_mimes.push_back(mime);681filter_names.push_back(tokens[1]);682} else {683if (flt == "*.*") {684filter_exts.push_back("*");685filter_names.push_back(RTR("All Files") + " (*.*)");686} else {687filter_exts.push_back(flt);688filter_names.push_back(flt);689}690filter_mimes.push_back(mime);691}692}693}694}695if (filter_names.is_empty()) {696filter_exts.push_back("*");697filter_mimes.push_back("");698filter_names.push_back(RTR("All Files") + " (*.*)");699}700701DBusError err;702dbus_error_init(&err);703704// Open connection and add signal handler.705FileDialogData fd;706fd.callback = p_callback;707fd.prev_focus = p_window_id;708fd.filter_names = filter_names;709fd.opt_in_cb = p_options_in_cb;710711CryptoCore::RandomGenerator rng;712ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");713uint8_t uuid[64];714Error rng_err = rng.get_random_bytes(uuid, 64);715ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token.");716717String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection));718String token = String::hex_encode_buffer(uuid, 64);719String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace_char('.', '_').remove_char(':'), token);720721fd.path = path;722fd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);723dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);724if (dbus_error_is_set(&err)) {725ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));726dbus_error_free(&err);727return FAILED;728}729730// Generate FileChooser message.731const char *method = nullptr;732if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {733method = "SaveFile";734} else {735method = "OpenFile";736}737738DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);739{740DBusMessageIter iter;741dbus_message_iter_init_append(message, &iter);742743append_dbus_string(&iter, p_xid);744append_dbus_string(&iter, p_title);745746DBusMessageIter arr_iter;747dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter);748749append_dbus_dict_string(&arr_iter, "handle_token", token);750append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);751append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);752append_dbus_dict_filters(&arr_iter, filter_names, filter_exts, filter_mimes);753754append_dbus_dict_options(&arr_iter, p_options, fd.option_ids);755append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);756if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {757append_dbus_dict_string(&arr_iter, "current_name", p_filename);758}759760dbus_message_iter_close_container(&iter, &arr_iter);761}762763DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err);764dbus_message_unref(message);765766if (!reply || dbus_error_is_set(&err)) {767ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));768dbus_error_free(&err);769dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);770return FAILED;771}772773// Update signal path.774{775DBusMessageIter iter;776if (dbus_message_iter_init(reply, &iter)) {777if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {778const char *new_path = nullptr;779dbus_message_iter_get_basic(&iter, &new_path);780if (String::utf8(new_path) != path) {781dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);782if (dbus_error_is_set(&err)) {783ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));784dbus_error_free(&err);785return FAILED;786}787fd.filter = String::utf8(new_path);788dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);789if (dbus_error_is_set(&err)) {790ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));791dbus_error_free(&err);792return FAILED;793}794}795}796}797}798dbus_message_unref(reply);799800MutexLock lock(file_dialog_mutex);801file_dialogs.push_back(fd);802803return OK;804}805806void FreeDesktopPortalDesktop::process_callbacks() {807{808MutexLock lock(file_dialog_mutex);809while (!pending_file_cbs.is_empty()) {810FileDialogCallback cb = pending_file_cbs.front()->get();811pending_file_cbs.pop_front();812813if (cb.opt_in_cb) {814Variant ret;815Callable::CallError ce;816const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options };817818cb.callback.callp(args, 4, ret, ce);819if (ce.error != Callable::CallError::CALL_OK) {820ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce)));821}822} else {823Variant ret;824Callable::CallError ce;825const Variant *args[3] = { &cb.status, &cb.files, &cb.index };826827cb.callback.callp(args, 3, ret, ce);828if (ce.error != Callable::CallError::CALL_OK) {829ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce)));830}831}832}833}834{835MutexLock lock(color_picker_mutex);836while (!pending_color_cbs.is_empty()) {837ColorPickerCallback cb = pending_color_cbs.front()->get();838pending_color_cbs.pop_front();839840Variant ret;841Callable::CallError ce;842const Variant *args[2] = { &cb.status, &cb.color };843844cb.callback.callp(args, 2, ret, ce);845if (ce.error != Callable::CallError::CALL_OK) {846ERR_PRINT(vformat("Failed to execute color picker callback: %s.", Variant::get_callable_error_text(cb.callback, args, 2, ce)));847}848}849}850}851852void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) {853FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud;854855while (!portal->monitor_thread_abort.is_set()) {856if (portal->monitor_connection) {857while (true) {858DBusMessage *msg = dbus_connection_pop_message(portal->monitor_connection);859if (!msg) {860break;861} else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Settings", "SettingChanged")) {862DBusMessageIter iter;863if (dbus_message_iter_init(msg, &iter)) {864const char *value;865dbus_message_iter_get_basic(&iter, &value);866String name_space = String::utf8(value);867dbus_message_iter_next(&iter);868dbus_message_iter_get_basic(&iter, &value);869String key = String::utf8(value);870871if (name_space == "org.freedesktop.appearance" && (key == "color-scheme" || key == "accent-color")) {872callable_mp(portal, &FreeDesktopPortalDesktop::_system_theme_changed_callback).call_deferred();873}874}875} else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {876String path = String::utf8(dbus_message_get_path(msg));877{878MutexLock lock(portal->file_dialog_mutex);879for (int i = 0; i < portal->file_dialogs.size(); i++) {880FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i];881if (fd.path == path) {882DBusMessageIter iter;883if (dbus_message_iter_init(msg, &iter)) {884bool cancel = false;885Vector<String> uris;886Dictionary options;887int index = 0;888file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options);889890if (fd.callback.is_valid()) {891FileDialogCallback cb;892cb.callback = fd.callback;893cb.status = !cancel;894cb.files = uris;895cb.index = index;896cb.options = options;897cb.opt_in_cb = fd.opt_in_cb;898portal->pending_file_cbs.push_back(cb);899}900if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {901callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);902}903}904905DBusError err;906dbus_error_init(&err);907dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err);908dbus_error_free(&err);909910portal->file_dialogs.remove_at(i);911break;912}913}914}915{916MutexLock lock(portal->color_picker_mutex);917for (int i = 0; i < portal->color_pickers.size(); i++) {918FreeDesktopPortalDesktop::ColorPickerData &cd = portal->color_pickers.write[i];919if (cd.path == path) {920DBusMessageIter iter;921if (dbus_message_iter_init(msg, &iter)) {922bool cancel = false;923Color c;924color_picker_parse_response(&iter, cancel, c);925926if (cd.callback.is_valid()) {927ColorPickerCallback cb;928cb.callback = cd.callback;929cb.color = c;930cb.status = !cancel;931portal->pending_color_cbs.push_back(cb);932}933}934935DBusError err;936dbus_error_init(&err);937dbus_bus_remove_match(portal->monitor_connection, cd.filter.utf8().get_data(), &err);938dbus_error_free(&err);939940portal->color_pickers.remove_at(i);941break;942}943}944}945}946dbus_message_unref(msg);947}948dbus_connection_read_write(portal->monitor_connection, 0);949}950951OS::get_singleton()->delay_usec(50'000);952}953}954955void FreeDesktopPortalDesktop::_system_theme_changed_callback() {956if (system_theme_changed.is_valid()) {957Variant ret;958Callable::CallError ce;959system_theme_changed.callp(nullptr, 0, ret, ce);960if (ce.error != Callable::CallError::CALL_OK) {961ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));962}963}964}965966FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {967DBusError err;968dbus_error_init(&err);969monitor_connection = dbus_bus_get(DBUS_BUS_SESSION, &err);970if (dbus_error_is_set(&err)) {971dbus_error_free(&err);972} else {973theme_path = "type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged'";974dbus_bus_add_match(monitor_connection, theme_path.utf8().get_data(), &err);975if (dbus_error_is_set(&err)) {976dbus_error_free(&err);977dbus_connection_unref(monitor_connection);978monitor_connection = nullptr;979}980dbus_connection_read_write(monitor_connection, 0);981}982983if (!unsupported) {984monitor_thread_abort.clear();985monitor_thread.start(FreeDesktopPortalDesktop::_thread_monitor, this);986}987}988989FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() {990monitor_thread_abort.set();991if (monitor_thread.is_started()) {992monitor_thread.wait_to_finish();993}994995if (monitor_connection) {996DBusError err;997for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) {998dbus_error_init(&err);999dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);1000dbus_error_free(&err);1001}1002dbus_error_init(&err);1003dbus_bus_remove_match(monitor_connection, theme_path.utf8().get_data(), &err);1004dbus_error_free(&err);1005dbus_connection_unref(monitor_connection);1006}1007}10081009#endif // DBUS_ENABLED101010111012