Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Extensible Clipboard Data Loading #86021

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
[b]Note:[/b] This method is only implemented on Linux (X11/Wayland).
</description>
</method>
<method name="clipboard_get_type" qualifiers="const">
<return type="PackedByteArray" />
<param index="0" name="type" type="String" />
<description>
Returns the contents of the user's clipboard as the MIME type [param type].
</description>
</method>
<method name="clipboard_has" qualifiers="const">
<return type="bool" />
<description>
Expand All @@ -49,6 +56,13 @@
Returns [code]true[/code] if there is an image content on the user's clipboard.
</description>
</method>
<method name="clipboard_has_type" qualifiers="const">
<return type="bool" />
<param index="0" name="type" type="String" />
<description>
Returns [code]true[/code] if the user's clipboard data can be retrieved as the MIME type [param type].
</description>
</method>
<method name="clipboard_set">
<return type="void" />
<param index="0" name="clipboard" type="String" />
Expand Down
172 changes: 163 additions & 9 deletions platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);

Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -830,7 +830,7 @@ Ref<Image> DisplayServerX11::clipboard_get_image() const {
Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0);
Window x11_window = windows[MAIN_WINDOW_ID].x11_window;
Ref<Image> 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;
}
Expand Down Expand Up @@ -971,12 +971,166 @@ Ref<Image> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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) ||
Expand Down
5 changes: 4 additions & 1 deletion platform/linuxbsd/x11/display_server_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<uint8_t> 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;
Expand Down
19 changes: 19 additions & 0 deletions servers/display_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<uint8_t> DisplayServer::clipboard_get_type(const String &p_type) const {
ERR_FAIL_V_MSG(Vector<uint8_t>(), "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.");
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -1305,6 +1317,13 @@ bool DisplayServer::can_create_rendering_device() {
return false;
}

Ref<Image> _get_png_from_buffer(Vector<uint8_t> p_buffer) {
Ref<Image> 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;
Expand Down
8 changes: 8 additions & 0 deletions servers/display_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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));

Expand All @@ -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<Rect2> get_display_cutouts() const { return TypedArray<Rect2>(); }
virtual Rect2i get_display_safe_area() const { return screen_get_usable_rect(); }

Expand Down
Loading