|
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, ReadVariantType p_type, void *r_value) { |
57 | 58 | DBusMessageIter iter[3]; |
@@ -340,6 +341,61 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co |
340 | 341 | dbus_message_iter_close_container(p_iter, &dict_iter); |
341 | 342 | } |
342 | 343 |
|
| 344 | +bool FreeDesktopPortalDesktop::color_picker_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Color &r_color) { |
| 345 | + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); |
| 346 | + |
| 347 | + dbus_uint32_t resp_code; |
| 348 | + dbus_message_iter_get_basic(p_iter, &resp_code); |
| 349 | + if (resp_code != 0) { |
| 350 | + r_cancel = true; |
| 351 | + } else { |
| 352 | + r_cancel = false; |
| 353 | + ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false); |
| 354 | + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false); |
| 355 | + |
| 356 | + DBusMessageIter dict_iter; |
| 357 | + dbus_message_iter_recurse(p_iter, &dict_iter); |
| 358 | + while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) { |
| 359 | + DBusMessageIter iter; |
| 360 | + dbus_message_iter_recurse(&dict_iter, &iter); |
| 361 | + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { |
| 362 | + const char *key; |
| 363 | + dbus_message_iter_get_basic(&iter, &key); |
| 364 | + dbus_message_iter_next(&iter); |
| 365 | + |
| 366 | + DBusMessageIter var_iter; |
| 367 | + dbus_message_iter_recurse(&iter, &var_iter); |
| 368 | + if (strcmp(key, "color") == 0) { // (ddd) |
| 369 | + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) { |
| 370 | + DBusMessageIter struct_iter; |
| 371 | + dbus_message_iter_recurse(&var_iter, &struct_iter); |
| 372 | + int idx = 0; |
| 373 | + while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) { |
| 374 | + double value = 0.0; |
| 375 | + dbus_message_iter_get_basic(&struct_iter, &value); |
| 376 | + if (idx == 0) { |
| 377 | + r_color.r = value; |
| 378 | + } else if (idx == 1) { |
| 379 | + r_color.g = value; |
| 380 | + } else if (idx == 2) { |
| 381 | + r_color.b = value; |
| 382 | + } |
| 383 | + idx++; |
| 384 | + if (!dbus_message_iter_next(&struct_iter)) { |
| 385 | + break; |
| 386 | + } |
| 387 | + } |
| 388 | + } |
| 389 | + } |
| 390 | + } |
| 391 | + if (!dbus_message_iter_next(&dict_iter)) { |
| 392 | + break; |
| 393 | + } |
| 394 | + } |
| 395 | + } |
| 396 | + return true; |
| 397 | +} |
| 398 | + |
343 | 399 | 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) { |
344 | 400 | ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); |
345 | 401 |
|
@@ -433,6 +489,92 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it |
433 | 489 | return true; |
434 | 490 | } |
435 | 491 |
|
| 492 | +bool FreeDesktopPortalDesktop::color_picker(const String &p_xid, const Callable &p_callback) { |
| 493 | + if (unsupported) { |
| 494 | + return false; |
| 495 | + } |
| 496 | + |
| 497 | + DBusError err; |
| 498 | + dbus_error_init(&err); |
| 499 | + |
| 500 | + // Open connection and add signal handler. |
| 501 | + ColorPickerData cd; |
| 502 | + cd.callback = p_callback; |
| 503 | + |
| 504 | + CryptoCore::RandomGenerator rng; |
| 505 | + ERR_FAIL_COND_V_MSG(rng.init(), false, "Failed to initialize random number generator."); |
| 506 | + uint8_t uuid[64]; |
| 507 | + Error rng_err = rng.get_random_bytes(uuid, 64); |
| 508 | + ERR_FAIL_COND_V_MSG(rng_err, false, "Failed to generate unique token."); |
| 509 | + |
| 510 | + String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection)); |
| 511 | + String token = String::hex_encode_buffer(uuid, 64); |
| 512 | + String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token); |
| 513 | + |
| 514 | + cd.path = path; |
| 515 | + 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); |
| 516 | + dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 517 | + if (dbus_error_is_set(&err)) { |
| 518 | + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); |
| 519 | + dbus_error_free(&err); |
| 520 | + return false; |
| 521 | + } |
| 522 | + |
| 523 | + DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SCREENSHOT, "PickColor"); |
| 524 | + { |
| 525 | + DBusMessageIter iter; |
| 526 | + dbus_message_iter_init_append(message, &iter); |
| 527 | + |
| 528 | + append_dbus_string(&iter, p_xid); |
| 529 | + |
| 530 | + DBusMessageIter arr_iter; |
| 531 | + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter); |
| 532 | + append_dbus_dict_string(&arr_iter, "handle_token", token); |
| 533 | + dbus_message_iter_close_container(&iter, &arr_iter); |
| 534 | + } |
| 535 | + DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err); |
| 536 | + dbus_message_unref(message); |
| 537 | + |
| 538 | + if (!reply || dbus_error_is_set(&err)) { |
| 539 | + ERR_PRINT(vformat("Failed to send DBus message: %s", err.message)); |
| 540 | + dbus_error_free(&err); |
| 541 | + dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 542 | + return false; |
| 543 | + } |
| 544 | + |
| 545 | + // Update signal path. |
| 546 | + { |
| 547 | + DBusMessageIter iter; |
| 548 | + if (dbus_message_iter_init(reply, &iter)) { |
| 549 | + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) { |
| 550 | + const char *new_path = nullptr; |
| 551 | + dbus_message_iter_get_basic(&iter, &new_path); |
| 552 | + if (String::utf8(new_path) != path) { |
| 553 | + dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 554 | + if (dbus_error_is_set(&err)) { |
| 555 | + ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message)); |
| 556 | + dbus_error_free(&err); |
| 557 | + return false; |
| 558 | + } |
| 559 | + cd.filter = String::utf8(new_path); |
| 560 | + dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err); |
| 561 | + if (dbus_error_is_set(&err)) { |
| 562 | + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); |
| 563 | + dbus_error_free(&err); |
| 564 | + return false; |
| 565 | + } |
| 566 | + } |
| 567 | + } |
| 568 | + } |
| 569 | + } |
| 570 | + dbus_message_unref(reply); |
| 571 | + |
| 572 | + MutexLock lock(color_picker_mutex); |
| 573 | + color_pickers.push_back(cd); |
| 574 | + |
| 575 | + return true; |
| 576 | +} |
| 577 | + |
436 | 578 | bool FreeDesktopPortalDesktop::_is_interface_supported(const char *p_iface) { |
437 | 579 | bool supported = false; |
438 | 580 | DBusError err; |
@@ -488,6 +630,14 @@ bool FreeDesktopPortalDesktop::is_settings_supported() { |
488 | 630 | return supported; |
489 | 631 | } |
490 | 632 |
|
| 633 | +bool FreeDesktopPortalDesktop::is_screenshot_supported() { |
| 634 | + static int supported = -1; |
| 635 | + if (supported == -1) { |
| 636 | + supported = _is_interface_supported(BUS_INTERFACE_SCREENSHOT); |
| 637 | + } |
| 638 | + return supported; |
| 639 | +} |
| 640 | + |
491 | 641 | 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) { |
492 | 642 | if (unsupported) { |
493 | 643 | return FAILED; |
@@ -637,29 +787,47 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo |
637 | 787 | return OK; |
638 | 788 | } |
639 | 789 |
|
640 | | -void FreeDesktopPortalDesktop::process_file_dialog_callbacks() { |
641 | | - MutexLock lock(file_dialog_mutex); |
642 | | - while (!pending_cbs.is_empty()) { |
643 | | - FileDialogCallback cb = pending_cbs.front()->get(); |
644 | | - pending_cbs.pop_front(); |
645 | | - |
646 | | - if (cb.opt_in_cb) { |
647 | | - Variant ret; |
648 | | - Callable::CallError ce; |
649 | | - const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options }; |
| 790 | +void FreeDesktopPortalDesktop::process_callbacks() { |
| 791 | + { |
| 792 | + MutexLock lock(file_dialog_mutex); |
| 793 | + while (!pending_file_cbs.is_empty()) { |
| 794 | + FileDialogCallback cb = pending_file_cbs.front()->get(); |
| 795 | + pending_file_cbs.pop_front(); |
| 796 | + |
| 797 | + if (cb.opt_in_cb) { |
| 798 | + Variant ret; |
| 799 | + Callable::CallError ce; |
| 800 | + const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options }; |
| 801 | + |
| 802 | + cb.callback.callp(args, 4, ret, ce); |
| 803 | + if (ce.error != Callable::CallError::CALL_OK) { |
| 804 | + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce))); |
| 805 | + } |
| 806 | + } else { |
| 807 | + Variant ret; |
| 808 | + Callable::CallError ce; |
| 809 | + const Variant *args[3] = { &cb.status, &cb.files, &cb.index }; |
650 | 810 |
|
651 | | - cb.callback.callp(args, 4, ret, ce); |
652 | | - if (ce.error != Callable::CallError::CALL_OK) { |
653 | | - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce))); |
| 811 | + cb.callback.callp(args, 3, ret, ce); |
| 812 | + if (ce.error != Callable::CallError::CALL_OK) { |
| 813 | + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce))); |
| 814 | + } |
654 | 815 | } |
655 | | - } else { |
| 816 | + } |
| 817 | + } |
| 818 | + { |
| 819 | + MutexLock lock(color_picker_mutex); |
| 820 | + while (!pending_color_cbs.is_empty()) { |
| 821 | + ColorPickerCallback cb = pending_color_cbs.front()->get(); |
| 822 | + pending_color_cbs.pop_front(); |
| 823 | + |
656 | 824 | Variant ret; |
657 | 825 | Callable::CallError ce; |
658 | | - const Variant *args[3] = { &cb.status, &cb.files, &cb.index }; |
| 826 | + const Variant *args[2] = { &cb.status, &cb.color }; |
659 | 827 |
|
660 | | - cb.callback.callp(args, 3, ret, ce); |
| 828 | + cb.callback.callp(args, 2, ret, ce); |
661 | 829 | if (ce.error != Callable::CallError::CALL_OK) { |
662 | | - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce))); |
| 830 | + ERR_PRINT(vformat("Failed to execute color picker callback: %s.", Variant::get_callable_error_text(cb.callback, args, 2, ce))); |
663 | 831 | } |
664 | 832 | } |
665 | 833 | } |
@@ -690,40 +858,72 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) { |
690 | 858 | } |
691 | 859 | } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { |
692 | 860 | String path = String::utf8(dbus_message_get_path(msg)); |
693 | | - MutexLock lock(portal->file_dialog_mutex); |
694 | | - for (int i = 0; i < portal->file_dialogs.size(); i++) { |
695 | | - FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; |
696 | | - if (fd.path == path) { |
697 | | - DBusMessageIter iter; |
698 | | - if (dbus_message_iter_init(msg, &iter)) { |
699 | | - bool cancel = false; |
700 | | - Vector<String> uris; |
701 | | - Dictionary options; |
702 | | - int index = 0; |
703 | | - file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options); |
704 | | - |
705 | | - if (fd.callback.is_valid()) { |
706 | | - FileDialogCallback cb; |
707 | | - cb.callback = fd.callback; |
708 | | - cb.status = !cancel; |
709 | | - cb.files = uris; |
710 | | - cb.index = index; |
711 | | - cb.options = options; |
712 | | - cb.opt_in_cb = fd.opt_in_cb; |
713 | | - portal->pending_cbs.push_back(cb); |
714 | | - } |
715 | | - if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { |
716 | | - callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); |
| 861 | + { |
| 862 | + MutexLock lock(portal->file_dialog_mutex); |
| 863 | + for (int i = 0; i < portal->file_dialogs.size(); i++) { |
| 864 | + FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; |
| 865 | + if (fd.path == path) { |
| 866 | + DBusMessageIter iter; |
| 867 | + if (dbus_message_iter_init(msg, &iter)) { |
| 868 | + bool cancel = false; |
| 869 | + Vector<String> uris; |
| 870 | + Dictionary options; |
| 871 | + int index = 0; |
| 872 | + file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options); |
| 873 | + |
| 874 | + if (fd.callback.is_valid()) { |
| 875 | + FileDialogCallback cb; |
| 876 | + cb.callback = fd.callback; |
| 877 | + cb.status = !cancel; |
| 878 | + cb.files = uris; |
| 879 | + cb.index = index; |
| 880 | + cb.options = options; |
| 881 | + cb.opt_in_cb = fd.opt_in_cb; |
| 882 | + portal->pending_file_cbs.push_back(cb); |
| 883 | + } |
| 884 | + if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { |
| 885 | + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); |
| 886 | + } |
717 | 887 | } |
| 888 | + |
| 889 | + DBusError err; |
| 890 | + dbus_error_init(&err); |
| 891 | + dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err); |
| 892 | + dbus_error_free(&err); |
| 893 | + |
| 894 | + portal->file_dialogs.remove_at(i); |
| 895 | + break; |
718 | 896 | } |
| 897 | + } |
| 898 | + } |
| 899 | + { |
| 900 | + MutexLock lock(portal->color_picker_mutex); |
| 901 | + for (int i = 0; i < portal->color_pickers.size(); i++) { |
| 902 | + FreeDesktopPortalDesktop::ColorPickerData &cd = portal->color_pickers.write[i]; |
| 903 | + if (cd.path == path) { |
| 904 | + DBusMessageIter iter; |
| 905 | + if (dbus_message_iter_init(msg, &iter)) { |
| 906 | + bool cancel = false; |
| 907 | + Color c; |
| 908 | + color_picker_parse_response(&iter, cancel, c); |
| 909 | + |
| 910 | + if (cd.callback.is_valid()) { |
| 911 | + ColorPickerCallback cb; |
| 912 | + cb.callback = cd.callback; |
| 913 | + cb.color = c; |
| 914 | + cb.status = !cancel; |
| 915 | + portal->pending_color_cbs.push_back(cb); |
| 916 | + } |
| 917 | + } |
719 | 918 |
|
720 | | - DBusError err; |
721 | | - dbus_error_init(&err); |
722 | | - dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err); |
723 | | - dbus_error_free(&err); |
| 919 | + DBusError err; |
| 920 | + dbus_error_init(&err); |
| 921 | + dbus_bus_remove_match(portal->monitor_connection, cd.filter.utf8().get_data(), &err); |
| 922 | + dbus_error_free(&err); |
724 | 923 |
|
725 | | - portal->file_dialogs.remove_at(i); |
726 | | - break; |
| 924 | + portal->color_pickers.remove_at(i); |
| 925 | + break; |
| 926 | + } |
727 | 927 | } |
728 | 928 | } |
729 | 929 | } |
|
0 commit comments