Atlas - SDL_portaldialog.c
Home / ext / SDL / src / dialog / unix Lines: 1 | Size: 23366 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22#include "../SDL_dialog_utils.h" 23 24#include "../../core/linux/SDL_dbus.h" 25 26#ifdef SDL_USE_LIBDBUS 27 28#include <errno.h> 29#include <libgen.h> 30#include <sys/stat.h> 31#include <sys/types.h> 32#include <sys/wait.h> 33#include <unistd.h> 34 35#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop" 36#define PORTAL_PATH "/org/freedesktop/portal/desktop" 37#define PORTAL_INTERFACE "org.freedesktop.portal.FileChooser" 38 39#define SIGNAL_SENDER "org.freedesktop.portal.Desktop" 40#define SIGNAL_INTERFACE "org.freedesktop.portal.Request" 41#define SIGNAL_NAME "Response" 42#define SIGNAL_FILTER "type='signal', sender='"SIGNAL_SENDER"', interface='"SIGNAL_INTERFACE"', member='"SIGNAL_NAME"', path='" 43 44#define HANDLE_LEN 10 45 46#define WAYLAND_HANDLE_PREFIX "wayland:" 47#define X11_HANDLE_PREFIX "x11:" 48 49typedef struct { 50 SDL_DialogFileCallback callback; 51 void *userdata; 52 const char *path; 53} SignalCallback; 54 55static void DBus_AppendStringOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value) 56{ 57 DBusMessageIter options_pair, options_value; 58 59 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair); 60 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key); 61 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "s", &options_value); 62 dbus->message_iter_append_basic(&options_value, DBUS_TYPE_STRING, &value); 63 dbus->message_iter_close_container(&options_pair, &options_value); 64 dbus->message_iter_close_container(options, &options_pair); 65} 66 67static void DBus_AppendBoolOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, int value) 68{ 69 DBusMessageIter options_pair, options_value; 70 71 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair); 72 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key); 73 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "b", &options_value); 74 dbus->message_iter_append_basic(&options_value, DBUS_TYPE_BOOLEAN, &value); 75 dbus->message_iter_close_container(&options_pair, &options_value); 76 dbus->message_iter_close_container(options, &options_pair); 77} 78 79static void DBus_AppendFilter(SDL_DBusContext *dbus, DBusMessageIter *parent, const SDL_DialogFileFilter filter) 80{ 81 DBusMessageIter filter_entry, filter_array, filter_array_entry; 82 char *state = NULL, *patterns, *pattern, *glob_pattern; 83 int zero = 0; 84 85 dbus->message_iter_open_container(parent, DBUS_TYPE_STRUCT, NULL, &filter_entry); 86 dbus->message_iter_append_basic(&filter_entry, DBUS_TYPE_STRING, &filter.name); 87 dbus->message_iter_open_container(&filter_entry, DBUS_TYPE_ARRAY, "(us)", &filter_array); 88 89 /* Copy the filter string, converting to a case-insensitive version. 90 * For example, for case-insensitive matching of '*.png', the pattern '*.[pP][nN][gG]' is used. 91 */ 92 const size_t len = SDL_strlen(filter.pattern) + 1; 93 patterns = SDL_malloc(len * 4); // Single characters may be expanded to 4 characters. 94 if (!patterns) { 95 goto cleanup; 96 } 97 for (size_t i = 0, p = 0; i < len; ++i) { 98 if ((filter.pattern[i] >= 'a' && filter.pattern[i] <= 'z') || 99 (filter.pattern[i] >= 'A' && filter.pattern[i] <= 'Z')) { 100 patterns[p++] = '['; 101 patterns[p++] = SDL_tolower(filter.pattern[i]); 102 patterns[p++] = SDL_toupper(filter.pattern[i]); 103 patterns[p++] = ']'; 104 } else { 105 patterns[p++] = filter.pattern[i]; 106 } 107 } 108 109 pattern = SDL_strtok_r(patterns, ";", &state); 110 while (pattern) { 111 size_t max_len = SDL_strlen(pattern) + 3; 112 113 dbus->message_iter_open_container(&filter_array, DBUS_TYPE_STRUCT, NULL, &filter_array_entry); 114 dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_UINT32, &zero); 115 116 glob_pattern = SDL_calloc(max_len, sizeof(char)); 117 if (!glob_pattern) { 118 goto cleanup; 119 } 120 glob_pattern[0] = '*'; 121 /* Special case: The '*' filter doesn't need to be rewritten */ 122 if (pattern[0] != '*' || pattern[1]) { 123 glob_pattern[1] = '.'; 124 SDL_strlcat(glob_pattern + 2, pattern, max_len); 125 } 126 dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_STRING, &glob_pattern); 127 SDL_free(glob_pattern); 128 129 dbus->message_iter_close_container(&filter_array, &filter_array_entry); 130 pattern = SDL_strtok_r(NULL, ";", &state); 131 } 132 133cleanup: 134 SDL_free(patterns); 135 136 dbus->message_iter_close_container(&filter_entry, &filter_array); 137 dbus->message_iter_close_container(parent, &filter_entry); 138} 139 140static void DBus_AppendFilters(SDL_DBusContext *dbus, DBusMessageIter *options, const SDL_DialogFileFilter *filters, int nfilters) 141{ 142 DBusMessageIter options_pair, options_value, options_value_array; 143 static const char *filters_name = "filters"; 144 145 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair); 146 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &filters_name); 147 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "a(sa(us))", &options_value); 148 dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "(sa(us))", &options_value_array); 149 for (int i = 0; i < nfilters; i++) { 150 DBus_AppendFilter(dbus, &options_value_array, filters[i]); 151 } 152 dbus->message_iter_close_container(&options_value, &options_value_array); 153 dbus->message_iter_close_container(&options_pair, &options_value); 154 dbus->message_iter_close_container(options, &options_pair); 155} 156 157static void DBus_AppendByteArray(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value) 158{ 159 DBusMessageIter options_pair, options_value, options_array; 160 161 dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair); 162 dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key); 163 dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "ay", &options_value); 164 dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "y", &options_array); 165 do { 166 dbus->message_iter_append_basic(&options_array, DBUS_TYPE_BYTE, value); 167 } while (*value++); 168 dbus->message_iter_close_container(&options_value, &options_array); 169 dbus->message_iter_close_container(&options_pair, &options_value); 170 dbus->message_iter_close_container(options, &options_pair); 171} 172 173static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) 174{ 175 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 176 SignalCallback *signal_data = (SignalCallback *)data; 177 178 if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME) && 179 dbus->message_has_path(msg, signal_data->path)) { 180 DBusMessageIter signal_iter, result_array, array_entry, value_entry, uri_entry; 181 uint32_t result; 182 size_t length = 2, current = 0; 183 const char **path = NULL; 184 185 dbus->message_iter_init(msg, &signal_iter); 186 // Check if the parameters are what we expect 187 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_UINT32) { 188 goto not_our_signal; 189 } 190 dbus->message_iter_get_basic(&signal_iter, &result); 191 192 if (result == 1 || result == 2) { 193 // cancelled 194 const char *result_data[] = { NULL }; 195 signal_data->callback(signal_data->userdata, result_data, -1); // TODO: Set this to the last selected filter 196 goto done; 197 198 } else if (result) { 199 // some error occurred 200 signal_data->callback(signal_data->userdata, NULL, -1); 201 goto done; 202 } 203 204 if (!dbus->message_iter_next(&signal_iter)) { 205 goto not_our_signal; 206 } 207 208 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_ARRAY) { 209 goto not_our_signal; 210 } 211 212 dbus->message_iter_recurse(&signal_iter, &result_array); 213 214 while (dbus->message_iter_get_arg_type(&result_array) == DBUS_TYPE_DICT_ENTRY) { 215 const char *method; 216 217 dbus->message_iter_recurse(&result_array, &array_entry); 218 if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_STRING) { 219 goto not_our_signal; 220 } 221 222 dbus->message_iter_get_basic(&array_entry, &method); 223 if (!SDL_strcmp(method, "uris")) { 224 // we only care about the selected file paths 225 break; 226 } 227 228 if (!dbus->message_iter_next(&result_array)) { 229 goto not_our_signal; 230 } 231 } 232 233 if (!dbus->message_iter_next(&array_entry)) { 234 goto not_our_signal; 235 } 236 237 if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_VARIANT) { 238 goto not_our_signal; 239 } 240 dbus->message_iter_recurse(&array_entry, &value_entry); 241 242 if (dbus->message_iter_get_arg_type(&value_entry) != DBUS_TYPE_ARRAY) { 243 goto not_our_signal; 244 } 245 dbus->message_iter_recurse(&value_entry, &uri_entry); 246 247 path = SDL_malloc(length * sizeof(const char *)); 248 if (!path) { 249 signal_data->callback(signal_data->userdata, NULL, -1); 250 goto done; 251 } 252 253 while (dbus->message_iter_get_arg_type(&uri_entry) == DBUS_TYPE_STRING) { 254 const char *uri = NULL; 255 256 if (current >= length - 1) { 257 ++length; 258 const char **newpath = SDL_realloc(path, length * sizeof(const char *)); 259 if (!newpath) { 260 signal_data->callback(signal_data->userdata, NULL, -1); 261 goto done; 262 } 263 path = newpath; 264 } 265 266 dbus->message_iter_get_basic(&uri_entry, &uri); 267 268 // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html 269 // Returned paths will always start with 'file://'; SDL_URIToLocal() truncates it. 270 char *decoded_uri = SDL_malloc(SDL_strlen(uri) + 1); 271 if (SDL_URIToLocal(uri, decoded_uri)) { 272 path[current] = decoded_uri; 273 } else { 274 SDL_free(decoded_uri); 275 SDL_SetError("Portal dialogs: Unsupported protocol: %s", uri); 276 signal_data->callback(signal_data->userdata, NULL, -1); 277 goto done; 278 } 279 280 dbus->message_iter_next(&uri_entry); 281 ++current; 282 } 283 path[current] = NULL; 284 signal_data->callback(signal_data->userdata, path, -1); // TODO: Fetch the index of the filter that was used 285done: 286 dbus->connection_remove_filter(conn, &DBus_MessageFilter, signal_data); 287 288 if (path) { 289 for (size_t i = 0; i < current; ++i) { 290 SDL_free((char *)path[i]); 291 } 292 SDL_free(path); 293 } 294 SDL_free((void *)signal_data->path); 295 SDL_free(signal_data); 296 return DBUS_HANDLER_RESULT_HANDLED; 297 } 298 299not_our_signal: 300 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 301} 302 303void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 304{ 305 const char *method; 306 const char *method_title; 307 308 SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); 309 SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); 310 int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); 311 bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); 312 const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); 313 const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); 314 char *location_name = NULL; 315 char *location_folder = NULL; 316 struct stat statbuf; 317 bool open_folders = false; 318 bool save_file_existing = false; 319 bool save_file_new_named = false; 320 321 switch (type) { 322 case SDL_FILEDIALOG_OPENFILE: 323 method = "OpenFile"; 324 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open File"); 325 break; 326 327 case SDL_FILEDIALOG_SAVEFILE: 328 method = "SaveFile"; 329 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File"); 330 if (default_location) { 331 if (stat(default_location, &statbuf) == 0) { 332 save_file_existing = S_ISREG(statbuf.st_mode); 333 } else if (errno == ENOENT) { 334 char *dirc = SDL_strdup(default_location); 335 if (dirc) { 336 location_folder = SDL_strdup(dirname(dirc)); 337 SDL_free(dirc); 338 if (location_folder) { 339 save_file_new_named = (stat(location_folder, &statbuf) == 0) && S_ISDIR(statbuf.st_mode); 340 } 341 } 342 } 343 344 if (save_file_existing || save_file_new_named) { 345 char *basec = SDL_strdup(default_location); 346 if (basec) { 347 location_name = SDL_strdup(basename(basec)); 348 SDL_free(basec); 349 } 350 } 351 } 352 break; 353 354 case SDL_FILEDIALOG_OPENFOLDER: 355 method = "OpenFile"; 356 method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Open Folder"); 357 open_folders = true; 358 break; 359 360 default: 361 /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */ 362 SDL_SetError("Invalid file dialog type: %d", type); 363 callback(userdata, NULL, -1); 364 goto cleanup; 365 } 366 367 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 368 DBusError error; 369 DBusMessage *msg; 370 DBusMessageIter params, options; 371 const char *signal_id = NULL; 372 char *handle_str, *filter; 373 int filter_len; 374 static uint32_t handle_id = 0; 375 static char *default_parent_window = ""; 376 SDL_PropertiesID window_props = SDL_GetWindowProperties(window); 377 378 const char *err_msg = validate_filters(filters, nfilters); 379 380 dbus->error_init(&error); 381 382 if (err_msg) { 383 SDL_SetError("%s", err_msg); 384 callback(userdata, NULL, -1); 385 goto cleanup; 386 } 387 388 if (dbus == NULL) { 389 SDL_SetError("Failed to connect to DBus"); 390 callback(userdata, NULL, -1); 391 goto cleanup; 392 } 393 394 msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method); 395 if (msg == NULL) { 396 SDL_SetError("Failed to send message to portal"); 397 callback(userdata, NULL, -1); 398 goto cleanup; 399 } 400 401 dbus->message_iter_init_append(msg, ¶ms); 402 403 handle_str = default_parent_window; 404 if (window_props) { 405 const char *parent_handle = SDL_GetStringProperty(window_props, SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING, NULL); 406 if (parent_handle) { 407 size_t len = SDL_strlen(parent_handle); 408 len += sizeof(WAYLAND_HANDLE_PREFIX) + 1; 409 handle_str = SDL_malloc(len * sizeof(char)); 410 if (!handle_str) { 411 callback(userdata, NULL, -1); 412 goto cleanup; 413 } 414 415 SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle); 416 } else { 417 const Uint64 xid = (Uint64)SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); 418 if (xid) { 419 const size_t len = sizeof(X11_HANDLE_PREFIX) + 24; // A 64-bit number can be 20 characters max. 420 handle_str = SDL_malloc(len * sizeof(char)); 421 if (!handle_str) { 422 callback(userdata, NULL, -1); 423 goto cleanup; 424 } 425 426 // The portal wants X11 window ID numbers in hex. 427 SDL_snprintf(handle_str, len, "%s%" SDL_PRIx64, X11_HANDLE_PREFIX, xid); 428 } 429 } 430 } 431 432 dbus->message_iter_append_basic(¶ms, DBUS_TYPE_STRING, &handle_str); 433 if (handle_str != default_parent_window) { 434 SDL_free(handle_str); 435 } 436 437 dbus->message_iter_append_basic(¶ms, DBUS_TYPE_STRING, &method_title); 438 dbus->message_iter_open_container(¶ms, DBUS_TYPE_ARRAY, "{sv}", &options); 439 440 handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1)); 441 if (!handle_str) { 442 callback(userdata, NULL, -1); 443 goto cleanup; 444 } 445 SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id); 446 DBus_AppendStringOption(dbus, &options, "handle_token", handle_str); 447 SDL_free(handle_str); 448 449 DBus_AppendBoolOption(dbus, &options, "modal", !!window); 450 if (allow_many) { 451 DBus_AppendBoolOption(dbus, &options, "multiple", 1); 452 } 453 if (open_folders) { 454 DBus_AppendBoolOption(dbus, &options, "directory", 1); 455 } 456 if (filters) { 457 DBus_AppendFilters(dbus, &options, filters, nfilters); 458 } 459 if (default_location) { 460 if (save_file_existing && location_name) { 461 /* Open a save dialog at an existing file */ 462 DBus_AppendByteArray(dbus, &options, "current_file", default_location); 463 /* Setting "current_name" should not be necessary however the kde-desktop-portal sets the filename without an extension. 464 * An alternative would be to match the extension to a filter and set "current_filter". 465 */ 466 DBus_AppendStringOption(dbus, &options, "current_name", location_name); 467 } else if (save_file_new_named && location_folder && location_name) { 468 /* Open a save dialog at a location with a suggested name */ 469 DBus_AppendByteArray(dbus, &options, "current_folder", location_folder); 470 DBus_AppendStringOption(dbus, &options, "current_name", location_name); 471 } else { 472 DBus_AppendByteArray(dbus, &options, "current_folder", default_location); 473 } 474 } 475 if (accept) { 476 DBus_AppendStringOption(dbus, &options, "accept_label", accept); 477 } 478 dbus->message_iter_close_container(¶ms, &options); 479 480 DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, &error); 481 if (dbus->error_is_set(&error)) { 482 SDL_SetError("Failed to open dialog via DBus, %s: %s", error.name, error.message); 483 dbus->error_free(&error); 484 callback(userdata, NULL, -1); 485 goto cleanup; 486 } 487 488 if (reply) { 489 DBusMessageIter reply_iter; 490 dbus->message_iter_init(reply, &reply_iter); 491 492 if (dbus->message_iter_get_arg_type(&reply_iter) == DBUS_TYPE_OBJECT_PATH) { 493 dbus->message_iter_get_basic(&reply_iter, &signal_id); 494 } 495 } 496 497 if (!signal_id) { 498 SDL_SetError("Invalid response received by DBus"); 499 callback(userdata, NULL, -1); 500 goto incorrect_type; 501 } 502 503 dbus->message_unref(msg); 504 505 filter_len = SDL_strlen(SIGNAL_FILTER) + SDL_strlen(signal_id) + 2; 506 filter = SDL_malloc(sizeof(char) * filter_len); 507 if (!filter) { 508 callback(userdata, NULL, -1); 509 goto incorrect_type; 510 } 511 512 SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id); 513 dbus->bus_add_match(dbus->session_conn, filter, &error); 514 SDL_free(filter); 515 516 if (dbus->error_is_set(&error)) { 517 SDL_SetError("Failed to set up DBus listener for dialog, %s: %s", error.name, error.message); 518 dbus->error_free(&error); 519 callback(userdata, NULL, -1); 520 goto cleanup; 521 } 522 523 SignalCallback *data = SDL_malloc(sizeof(SignalCallback)); 524 if (!data) { 525 callback(userdata, NULL, -1); 526 goto incorrect_type; 527 } 528 data->callback = callback; 529 data->userdata = userdata; 530 data->path = SDL_strdup(signal_id); 531 if (!data->path) { 532 SDL_free(data); 533 callback(userdata, NULL, -1); 534 goto incorrect_type; 535 } 536 537 /* TODO: This should be registered before opening the portal, or the filter will not catch 538 the message if it is sent before we register the filter. 539 */ 540 dbus->connection_add_filter(dbus->session_conn, 541 &DBus_MessageFilter, data, NULL); 542 dbus->connection_flush(dbus->session_conn); 543 544incorrect_type: 545 dbus->message_unref(reply); 546 547cleanup: 548 SDL_free(location_name); 549 SDL_free(location_folder); 550} 551 552bool SDL_Portal_detect(void) 553{ 554 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 555 DBusMessage *msg = NULL, *reply = NULL; 556 char *reply_str = NULL; 557 DBusMessageIter reply_iter; 558 static int portal_present = -1; 559 560 // No need for this if the result is cached. 561 if (portal_present != -1) { 562 return (portal_present > 0); 563 } 564 565 portal_present = 0; 566 567 if (!dbus) { 568 SDL_SetError("%s", "Failed to connect to DBus!"); 569 return false; 570 } 571 572 // Use introspection to get the available services. 573 msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, "org.freedesktop.DBus.Introspectable", "Introspect"); 574 if (!msg) { 575 goto done; 576 } 577 578 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL); 579 dbus->message_unref(msg); 580 if (!reply) { 581 goto done; 582 } 583 584 if (!dbus->message_iter_init(reply, &reply_iter)) { 585 goto done; 586 } 587 588 if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_STRING) { 589 goto done; 590 } 591 592 /* Introspection gives us a dump of all the services on the destination in XML format, so search the 593 * giant string for the file chooser protocol. 594 */ 595 dbus->message_iter_get_basic(&reply_iter, &reply_str); 596 if (SDL_strstr(reply_str, PORTAL_INTERFACE)) { 597 portal_present = 1; // Found it! 598 } 599 600done: 601 if (reply) { 602 dbus->message_unref(reply); 603 } 604 605 return (portal_present > 0); 606} 607 608#else 609 610// Dummy implementation to avoid compilation problems 611 612void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) 613{ 614 SDL_Unsupported(); 615 callback(userdata, NULL, -1); 616} 617 618bool SDL_Portal_detect(void) 619{ 620 return false; 621} 622 623#endif // SDL_USE_LIBDBUS 624[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.