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