diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index fea38aad55c8..79869a3a5c56 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -37,6 +37,13 @@ [b]Note:[/b] This method is only implemented on Linux (X11/Wayland). + + + + + Returns the contents of the user's clipboard as the MIME type [param type]. + + @@ -49,6 +56,13 @@ Returns [code]true[/code] if there is an image content on the user's clipboard. + + + + + Returns [code]true[/code] if the user's clipboard data can be retrieved as the MIME type [param type]. + + diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 90bd3d57d4b7..d17a6897357d 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -719,9 +719,9 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A return ret; } -Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const { - Atom target = XInternAtom(x11_display, "TARGETS", 0); - Atom png = XInternAtom(x11_display, "image/png", 0); +Atom DisplayServerX11::_clipboard_get_type_target(Atom p_source, Window x11_window, const String &p_type) const { + Atom targets_atom = XInternAtom(x11_display, "TARGETS", 0); + Atom target = XInternAtom(x11_display, p_type.ascii().get_data(), 0); Atom *valid_targets = nullptr; unsigned long atom_count = 0; @@ -731,7 +731,7 @@ Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_win MutexLock mutex_lock(events_mutex); Atom selection = XA_PRIMARY; - XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime); + XConvertSelection(x11_display, p_source, targets_atom, selection, x11_window, CurrentTime); XFlush(x11_display); @@ -777,9 +777,9 @@ Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_win } for (unsigned long i = 0; i < atom_count; i++) { Atom atom = valid_targets[i]; - if (atom == png) { + if (atom == target) { XFree(valid_targets); - return png; + return target; } } @@ -830,7 +830,7 @@ Ref DisplayServerX11::clipboard_get_image() const { Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0); Window x11_window = windows[MAIN_WINDOW_ID].x11_window; Ref ret; - Atom target = _clipboard_get_image_target(clipboard, x11_window); + Atom target = _clipboard_get_type_target(clipboard, x11_window, String("image/png")); if (target == None) { return ret; } @@ -971,12 +971,166 @@ Ref DisplayServerX11::clipboard_get_image() const { } bool DisplayServerX11::clipboard_has_image() const { - Atom target = _clipboard_get_image_target( + Atom target = _clipboard_get_type_target( XInternAtom(x11_display, "CLIPBOARD", 0), - windows[MAIN_WINDOW_ID].x11_window); + windows[MAIN_WINDOW_ID].x11_window, + String("image/png")); return target != None; } +bool DisplayServerX11::clipboard_has_type(const String &p_type) const { + Atom target = _clipboard_get_type_target( + XInternAtom(x11_display, "CLIPBOARD", 0), + windows[MAIN_WINDOW_ID].x11_window, + p_type); + return target != None; +} + +Vector DisplayServerX11::clipboard_get_type(const String &p_type) const { + _THREAD_SAFE_METHOD_ + Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0); + Window x11_window = windows[MAIN_WINDOW_ID].x11_window; + Vector ret; + Atom target = _clipboard_get_type_target(clipboard, x11_window, p_type); + if (target == None) { + return ret; + } + + Window selection_owner = XGetSelectionOwner(x11_display, clipboard); + + if (selection_owner != None && selection_owner != x11_window) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + // Identifier for the property the other window + // will send the converted data to. + Atom transfer_prop = XA_PRIMARY; + XConvertSelection(x11_display, + clipboard, // source selection + target, // format to convert to + transfer_prop, // output property + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; + XGetWindowProperty(x11_display, x11_window, + transfer_prop, // Property data is transferred through + 0, 1, // offset, len (4 so we can get the size if INCR is used) + 0, // Delete 0==FALSE + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + if (type == XInternAtom(x11_display, "INCR", 0)) { + ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length."); + + // Data is going to be received incrementally. + DEBUG_LOG_X11("INCR selection started.\n"); + + LocalVector incr_data; + uint32_t data_size = 0; + bool success = false; + + // Initial response is the lower bound of the length of the transferred data. + incr_data.resize(*(unsigned long *)data); + XFree(data); + data = nullptr; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, transfer_prop); + + // Process events from the queue. + bool done = false; + while (!done) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) { + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, // output property + 0, LONG_MAX, // offset - len + True, // delete property to notify the owner + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + // New chunk, resize to be safe and append data. + incr_data.resize(MAX(data_size + len, prev_size)); + memcpy(incr_data.ptr() + data_size, data, len); + data_size += len; + } else if (!(format == 0 && len == 0)) { + // For unclear reasons the first GetWindowProperty always returns a length and format of 0. + // Otherwise, last chunk, process finished. + done = true; + success = true; + } + } else { + print_verbose("Failed to get selection data chunk."); + done = true; + } + + if (data) { + XFree(data); + data = nullptr; + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.resize(incr_data.size()); + memcpy(ret.ptrw(), incr_data.ptr(), incr_data.size()); + } + } else if (bytes_left > 0) { + if (data) { + XFree(data); + data = nullptr; + } + // Data is ready and can be processed all at once. + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, 0, bytes_left + 4, 0, + AnyPropertyType, &type, &format, + &len, &dummy, &data); + if (result == Success) { + ret.resize(bytes_left); + memcpy(ret.ptrw(), data, bytes_left); + } else { + print_verbose("Failed to get selection data."); + } + + if (data) { + XFree(data); + } + } + } + + return ret; +} + Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { if (event->xany.window == *(Window *)arg) { return (event->type == SelectionRequest) || diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 31e4d548b9ab..744c5863e0b2 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -305,7 +305,7 @@ class DisplayServerX11 : public DisplayServer { String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; String _clipboard_get(Atom p_source, Window x11_window) const; - Atom _clipboard_get_image_target(Atom p_source, Window x11_window) const; + Atom _clipboard_get_type_target(Atom p_source, Window x11_window, const String &p_type) const; void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; bool do_mouse_warp = false; @@ -422,6 +422,9 @@ class DisplayServerX11 : public DisplayServer { virtual void clipboard_set_primary(const String &p_text) override; virtual String clipboard_get_primary() const override; + virtual bool clipboard_has_type(const String &p_type) const override; + virtual Vector clipboard_get_type(const String &p_type) const override; + virtual int get_screen_count() const override; virtual int get_primary_screen() const override; virtual int get_keyboard_focus_screen() const override; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index ff9a07f3970e..d42dc4de57a0 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -30,6 +30,7 @@ #include "display_server.h" +#include "core/error/error_list.h" #include "core/input/input.h" #include "scene/resources/texture.h" #include "servers/display_server_headless.h" @@ -540,6 +541,14 @@ String DisplayServer::clipboard_get_primary() const { ERR_FAIL_V_MSG(String(), "Primary clipboard is not supported by this display server."); } +bool DisplayServer::clipboard_has_type(const String &p_type) const { + ERR_FAIL_V_MSG(false, "Dynamic Clipboard is not implemented yet by this display server."); +} + +Vector DisplayServer::clipboard_get_type(const String &p_type) const { + ERR_FAIL_V_MSG(Vector(), "Dynamic Clipboard is not implemented yet by this display server."); +} + void DisplayServer::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) { WARN_PRINT("Orientation not supported by this display server."); } @@ -886,6 +895,9 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("clipboard_set_primary", "clipboard_primary"), &DisplayServer::clipboard_set_primary); ClassDB::bind_method(D_METHOD("clipboard_get_primary"), &DisplayServer::clipboard_get_primary); + ClassDB::bind_method(D_METHOD("clipboard_has_type", "type"), &DisplayServer::clipboard_has_type); + ClassDB::bind_method(D_METHOD("clipboard_get_type", "type"), &DisplayServer::clipboard_get_type); + ClassDB::bind_method(D_METHOD("get_display_cutouts"), &DisplayServer::get_display_cutouts); ClassDB::bind_method(D_METHOD("get_display_safe_area"), &DisplayServer::get_display_safe_area); @@ -1305,6 +1317,13 @@ bool DisplayServer::can_create_rendering_device() { return false; } +Ref _get_png_from_buffer(Vector p_buffer) { + Ref image; + image.instantiate(); + image->load_png_from_buffer(p_buffer); + return image; +} + DisplayServer::DisplayServer() { singleton = this; Input::set_mouse_mode_func = _input_set_mouse_mode; diff --git a/servers/display_server.h b/servers/display_server.h index 805d2374ab01..cc838156bba8 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -35,7 +35,11 @@ #include "core/io/image.h" #include "core/io/resource.h" #include "core/os/os.h" +#include "core/string/string_name.h" +#include "core/templates/hash_map.h" #include "core/variant/callable.h" +#include "core/variant/dictionary.h" +#include "core/variant/variant.h" #include "display/native_menu.h" @@ -267,6 +271,7 @@ class DisplayServer : public Object { protected: static bool _get_window_early_clear_override(Color &r_color); + public: static void set_early_window_clear_color_override(bool p_enabled, Color p_color = Color(0, 0, 0, 0)); @@ -293,6 +298,9 @@ class DisplayServer : public Object { virtual void clipboard_set_primary(const String &p_text); virtual String clipboard_get_primary() const; + virtual bool clipboard_has_type(const String &p_type) const; + virtual PackedByteArray clipboard_get_type(const String &p_type) const; + virtual TypedArray get_display_cutouts() const { return TypedArray(); } virtual Rect2i get_display_safe_area() const { return screen_get_usable_rect(); }