|
52 | 52 | #define BUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" |
53 | 53 | #define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings" |
54 | 54 | #define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser" |
| 55 | +#define BUS_INTERFACE_SCREENSHOT "org.freedesktop.portal.Screenshot" |
55 | 56 |
|
56 | 57 | bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { |
57 | 58 | DBusMessageIter iter[3]; |
@@ -295,6 +296,61 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co |
295 | 296 | dbus_message_iter_close_container(p_iter, &dict_iter); |
296 | 297 | } |
297 | 298 |
|
| 299 | +bool FreeDesktopPortalDesktop::color_picker_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Color &r_color) { |
| 300 | + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); |
| 301 | + |
| 302 | + dbus_uint32_t resp_code; |
| 303 | + dbus_message_iter_get_basic(p_iter, &resp_code); |
| 304 | + if (resp_code != 0) { |
| 305 | + r_cancel = true; |
| 306 | + } else { |
| 307 | + r_cancel = false; |
| 308 | + ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false); |
| 309 | + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false); |
| 310 | + |
| 311 | + DBusMessageIter dict_iter; |
| 312 | + dbus_message_iter_recurse(p_iter, &dict_iter); |
| 313 | + while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) { |
| 314 | + DBusMessageIter iter; |
| 315 | + dbus_message_iter_recurse(&dict_iter, &iter); |
| 316 | + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { |
| 317 | + const char *key; |
| 318 | + dbus_message_iter_get_basic(&iter, &key); |
| 319 | + dbus_message_iter_next(&iter); |
| 320 | + |
| 321 | + DBusMessageIter var_iter; |
| 322 | + dbus_message_iter_recurse(&iter, &var_iter); |
| 323 | + if (strcmp(key, "color") == 0) { // (ddd) |
| 324 | + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) { |
| 325 | + DBusMessageIter struct_iter; |
| 326 | + dbus_message_iter_recurse(&var_iter, &struct_iter); |
| 327 | + int idx = 0; |
| 328 | + while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) { |
| 329 | + double value = 0.0; |
| 330 | + dbus_message_iter_get_basic(&struct_iter, &value); |
| 331 | + if (idx == 0) { |
| 332 | + r_color.r = value; |
| 333 | + } else if (idx == 1) { |
| 334 | + r_color.g = value; |
| 335 | + } else if (idx == 2) { |
| 336 | + r_color.b = value; |
| 337 | + } |
| 338 | + idx++; |
| 339 | + if (!dbus_message_iter_next(&struct_iter)) { |
| 340 | + break; |
| 341 | + } |
| 342 | + } |
| 343 | + } |
| 344 | + } |
| 345 | + } |
| 346 | + if (!dbus_message_iter_next(&dict_iter)) { |
| 347 | + break; |
| 348 | + } |
| 349 | + } |
| 350 | + } |
| 351 | + return true; |
| 352 | +} |
| 353 | + |
298 | 354 | bool 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) { |
299 | 355 | ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); |
300 | 356 |
|
@@ -388,6 +444,92 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it |
388 | 444 | return true; |
389 | 445 | } |
390 | 446 |
|
| 447 | +bool FreeDesktopPortalDesktop::color_picker(const String &p_xid, const Callable &p_callback) { |
| 448 | + if (unsupported) { |
| 449 | + return false; |
| 450 | + } |
| 451 | + |
| 452 | + DBusError err; |
| 453 | + dbus_error_init(&err); |
| 454 | + |
| 455 | + // Open connection and add signal handler. |
| 456 | + ColorPickerData cd; |
| 457 | + cd.callback = p_callback; |
| 458 | + |
| 459 | + CryptoCore::RandomGenerator rng; |
| 460 | + ERR_FAIL_COND_V_MSG(rng.init(), false, "Failed to initialize random number generator."); |
| 461 | + uint8_t uuid[64]; |
| 462 | + Error rng_err = rng.get_random_bytes(uuid, 64); |
| 463 | + ERR_FAIL_COND_V_MSG(rng_err, false, "Failed to generate unique token."); |
| 464 | + |
| 465 | + String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection)); |
| 466 | + String token = String::hex_encode_buffer(uuid, 64); |
| 467 | + String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token); |
| 468 | + |
| 469 | + cd.path = path; |
| 470 | + cd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name); |
| 471 | + dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 472 | + if (dbus_error_is_set(&err)) { |
| 473 | + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); |
| 474 | + dbus_error_free(&err); |
| 475 | + return false; |
| 476 | + } |
| 477 | + |
| 478 | + DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SCREENSHOT, "PickColor"); |
| 479 | + { |
| 480 | + DBusMessageIter iter; |
| 481 | + dbus_message_iter_init_append(message, &iter); |
| 482 | + |
| 483 | + append_dbus_string(&iter, p_xid); |
| 484 | + |
| 485 | + DBusMessageIter arr_iter; |
| 486 | + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter); |
| 487 | + append_dbus_dict_string(&arr_iter, "handle_token", token); |
| 488 | + dbus_message_iter_close_container(&iter, &arr_iter); |
| 489 | + } |
| 490 | + DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err); |
| 491 | + dbus_message_unref(message); |
| 492 | + |
| 493 | + if (!reply || dbus_error_is_set(&err)) { |
| 494 | + ERR_PRINT(vformat("Failed to send DBus message: %s", err.message)); |
| 495 | + dbus_error_free(&err); |
| 496 | + dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 497 | + return false; |
| 498 | + } |
| 499 | + |
| 500 | + // Update signal path. |
| 501 | + { |
| 502 | + DBusMessageIter iter; |
| 503 | + if (dbus_message_iter_init(reply, &iter)) { |
| 504 | + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) { |
| 505 | + const char *new_path = nullptr; |
| 506 | + dbus_message_iter_get_basic(&iter, &new_path); |
| 507 | + if (String::utf8(new_path) != path) { |
| 508 | + dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 509 | + if (dbus_error_is_set(&err)) { |
| 510 | + ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message)); |
| 511 | + dbus_error_free(&err); |
| 512 | + return false; |
| 513 | + } |
| 514 | + cd.filter = String::utf8(new_path); |
| 515 | + dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 516 | + if (dbus_error_is_set(&err)) { |
| 517 | + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); |
| 518 | + dbus_error_free(&err); |
| 519 | + return false; |
| 520 | + } |
| 521 | + } |
| 522 | + } |
| 523 | + } |
| 524 | + } |
| 525 | + dbus_message_unref(reply); |
| 526 | + |
| 527 | + MutexLock lock(color_picker_mutex); |
| 528 | + color_pickers.push_back(cd); |
| 529 | + |
| 530 | + return true; |
| 531 | +} |
| 532 | + |
391 | 533 | bool FreeDesktopPortalDesktop::_is_interface_supported(const char *p_iface) { |
392 | 534 | bool supported = false; |
393 | 535 | DBusError err; |
@@ -443,6 +585,14 @@ bool FreeDesktopPortalDesktop::is_settings_supported() { |
443 | 585 | return supported; |
444 | 586 | } |
445 | 587 |
|
| 588 | +bool FreeDesktopPortalDesktop::is_screenshot_supported() { |
| 589 | + static int supported = -1; |
| 590 | + if (supported == -1) { |
| 591 | + supported = _is_interface_supported(BUS_INTERFACE_SCREENSHOT); |
| 592 | + } |
| 593 | + return supported; |
| 594 | +} |
| 595 | + |
446 | 596 | Error 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) { |
447 | 597 | if (unsupported) { |
448 | 598 | return FAILED; |
@@ -592,29 +742,47 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo |
592 | 742 | return OK; |
593 | 743 | } |
594 | 744 |
|
595 | | -void FreeDesktopPortalDesktop::process_file_dialog_callbacks() { |
596 | | - MutexLock lock(file_dialog_mutex); |
597 | | - while (!pending_cbs.is_empty()) { |
598 | | - FileDialogCallback cb = pending_cbs.front()->get(); |
599 | | - pending_cbs.pop_front(); |
600 | | - |
601 | | - if (cb.opt_in_cb) { |
602 | | - Variant ret; |
603 | | - Callable::CallError ce; |
604 | | - const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options }; |
| 745 | +void FreeDesktopPortalDesktop::process_callbacks() { |
| 746 | + { |
| 747 | + MutexLock lock(file_dialog_mutex); |
| 748 | + while (!pending_file_cbs.is_empty()) { |
| 749 | + FileDialogCallback cb = pending_file_cbs.front()->get(); |
| 750 | + pending_file_cbs.pop_front(); |
| 751 | + |
| 752 | + if (cb.opt_in_cb) { |
| 753 | + Variant ret; |
| 754 | + Callable::CallError ce; |
| 755 | + const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options }; |
| 756 | + |
| 757 | + cb.callback.callp(args, 4, ret, ce); |
| 758 | + if (ce.error != Callable::CallError::CALL_OK) { |
| 759 | + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce))); |
| 760 | + } |
| 761 | + } else { |
| 762 | + Variant ret; |
| 763 | + Callable::CallError ce; |
| 764 | + const Variant *args[3] = { &cb.status, &cb.files, &cb.index }; |
605 | 765 |
|
606 | | - cb.callback.callp(args, 4, ret, ce); |
607 | | - if (ce.error != Callable::CallError::CALL_OK) { |
608 | | - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce))); |
| 766 | + cb.callback.callp(args, 3, ret, ce); |
| 767 | + if (ce.error != Callable::CallError::CALL_OK) { |
| 768 | + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce))); |
| 769 | + } |
609 | 770 | } |
610 | | - } else { |
| 771 | + } |
| 772 | + } |
| 773 | + { |
| 774 | + MutexLock lock(color_picker_mutex); |
| 775 | + while (!pending_color_cbs.is_empty()) { |
| 776 | + ColorPickerCallback cb = pending_color_cbs.front()->get(); |
| 777 | + pending_color_cbs.pop_front(); |
| 778 | + |
611 | 779 | Variant ret; |
612 | 780 | Callable::CallError ce; |
613 | | - const Variant *args[3] = { &cb.status, &cb.files, &cb.index }; |
| 781 | + const Variant *args[2] = { &cb.status, &cb.color }; |
614 | 782 |
|
615 | | - cb.callback.callp(args, 3, ret, ce); |
| 783 | + cb.callback.callp(args, 2, ret, ce); |
616 | 784 | if (ce.error != Callable::CallError::CALL_OK) { |
617 | | - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce))); |
| 785 | + ERR_PRINT(vformat("Failed to execute color picker callback: %s.", Variant::get_callable_error_text(cb.callback, args, 2, ce))); |
618 | 786 | } |
619 | 787 | } |
620 | 788 | } |
@@ -645,40 +813,72 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) { |
645 | 813 | } |
646 | 814 | } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { |
647 | 815 | String path = String::utf8(dbus_message_get_path(msg)); |
648 | | - MutexLock lock(portal->file_dialog_mutex); |
649 | | - for (int i = 0; i < portal->file_dialogs.size(); i++) { |
650 | | - FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; |
651 | | - if (fd.path == path) { |
652 | | - DBusMessageIter iter; |
653 | | - if (dbus_message_iter_init(msg, &iter)) { |
654 | | - bool cancel = false; |
655 | | - Vector<String> uris; |
656 | | - Dictionary options; |
657 | | - int index = 0; |
658 | | - file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options); |
659 | | - |
660 | | - if (fd.callback.is_valid()) { |
661 | | - FileDialogCallback cb; |
662 | | - cb.callback = fd.callback; |
663 | | - cb.status = !cancel; |
664 | | - cb.files = uris; |
665 | | - cb.index = index; |
666 | | - cb.options = options; |
667 | | - cb.opt_in_cb = fd.opt_in_cb; |
668 | | - portal->pending_cbs.push_back(cb); |
669 | | - } |
670 | | - if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { |
671 | | - callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); |
| 816 | + { |
| 817 | + MutexLock lock(portal->file_dialog_mutex); |
| 818 | + for (int i = 0; i < portal->file_dialogs.size(); i++) { |
| 819 | + FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; |
| 820 | + if (fd.path == path) { |
| 821 | + DBusMessageIter iter; |
| 822 | + if (dbus_message_iter_init(msg, &iter)) { |
| 823 | + bool cancel = false; |
| 824 | + Vector<String> uris; |
| 825 | + Dictionary options; |
| 826 | + int index = 0; |
| 827 | + file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options); |
| 828 | + |
| 829 | + if (fd.callback.is_valid()) { |
| 830 | + FileDialogCallback cb; |
| 831 | + cb.callback = fd.callback; |
| 832 | + cb.status = !cancel; |
| 833 | + cb.files = uris; |
| 834 | + cb.index = index; |
| 835 | + cb.options = options; |
| 836 | + cb.opt_in_cb = fd.opt_in_cb; |
| 837 | + portal->pending_file_cbs.push_back(cb); |
| 838 | + } |
| 839 | + if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { |
| 840 | + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); |
| 841 | + } |
672 | 842 | } |
| 843 | + |
| 844 | + DBusError err; |
| 845 | + dbus_error_init(&err); |
| 846 | + dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err); |
| 847 | + dbus_error_free(&err); |
| 848 | + |
| 849 | + portal->file_dialogs.remove_at(i); |
| 850 | + break; |
673 | 851 | } |
| 852 | + } |
| 853 | + } |
| 854 | + { |
| 855 | + MutexLock lock(portal->color_picker_mutex); |
| 856 | + for (int i = 0; i < portal->color_pickers.size(); i++) { |
| 857 | + FreeDesktopPortalDesktop::ColorPickerData &cd = portal->color_pickers.write[i]; |
| 858 | + if (cd.path == path) { |
| 859 | + DBusMessageIter iter; |
| 860 | + if (dbus_message_iter_init(msg, &iter)) { |
| 861 | + bool cancel = false; |
| 862 | + Color c; |
| 863 | + color_picker_parse_response(&iter, cancel, c); |
| 864 | + |
| 865 | + if (cd.callback.is_valid()) { |
| 866 | + ColorPickerCallback cb; |
| 867 | + cb.callback = cd.callback; |
| 868 | + cb.color = c; |
| 869 | + cb.status = !cancel; |
| 870 | + portal->pending_color_cbs.push_back(cb); |
| 871 | + } |
| 872 | + } |
674 | 873 |
|
675 | | - DBusError err; |
676 | | - dbus_error_init(&err); |
677 | | - dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err); |
678 | | - dbus_error_free(&err); |
| 874 | + DBusError err; |
| 875 | + dbus_error_init(&err); |
| 876 | + dbus_bus_remove_match(portal->monitor_connection, cd.filter.utf8().get_data(), &err); |
| 877 | + dbus_error_free(&err); |
679 | 878 |
|
680 | | - portal->file_dialogs.remove_at(i); |
681 | | - break; |
| 879 | + portal->color_pickers.remove_at(i); |
| 880 | + break; |
| 881 | + } |
682 | 882 | } |
683 | 883 | } |
684 | 884 | } |
|
0 commit comments