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

Implement clipboard_get/has_image for X11 #81439

Merged
merged 1 commit into from
Oct 20, 2023
Merged
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
225 changes: 223 additions & 2 deletions platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "core/math/math_funcs.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
#include "scene/resources/atlas_texture.h"

Expand Down Expand Up @@ -519,7 +520,7 @@ Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *
}

Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) {
if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) {
if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) {
return True;
} else {
return False;
Expand Down Expand Up @@ -593,7 +594,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A

// Non-blocking wait for next event and remove it from the queue.
XEvent ev;
while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) {
while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) {
result = XGetWindowProperty(x11_display, x11_window,
selection, // selection type
0, LONG_MAX, // offset - len
Expand Down Expand Up @@ -664,6 +665,74 @@ 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 *valid_targets = nullptr;
unsigned long atom_count = 0;

Window selection_owner = XGetSelectionOwner(x11_display, p_source);
if (selection_owner != None) {
// Block events polling while processing selection events.
MutexLock mutex_lock(events_mutex);

Atom selection = XA_PRIMARY;
XConvertSelection(x11_display, p_source, target, selection, 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;
XGetWindowProperty(x11_display, x11_window,
selection, // Tricky..
0, 0, // offset - len
0, // Delete 0==FALSE
XA_ATOM, // flag
&type, // return type
&format, // return format
&len, &bytes_left, // data length
(unsigned char **)&valid_targets);

if (valid_targets) {
XFree(valid_targets);
valid_targets = nullptr;
}

if (type == XA_ATOM && bytes_left > 0) {
// Data is ready and can be processed all at once.
result = XGetWindowProperty(x11_display, x11_window,
selection, 0, bytes_left / 4, 0,
XA_ATOM, &type, &format,
&len, &dummy, (unsigned char **)&valid_targets);
if (result == Success) {
atom_count = len;
} else {
print_verbose("Failed to get selection data.");
return None;
}
} else {
return None;
}
} else {
return None;
}
Setadokalo marked this conversation as resolved.
Show resolved Hide resolved
for (unsigned long i = 0; i < atom_count; i++) {
Atom atom = valid_targets[i];
if (atom == png) {
XFree(valid_targets);
return png;
}
}

XFree(valid_targets);
Setadokalo marked this conversation as resolved.
Show resolved Hide resolved
return None;
}

String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {
String ret;
Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
Expand Down Expand Up @@ -702,6 +771,158 @@ String DisplayServerX11::clipboard_get_primary() const {
return ret;
}

Ref<Image> DisplayServerX11::clipboard_get_image() const {
_THREAD_SAFE_METHOD_
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);
if (target == None) {
return ret;
}

Window selection_owner = XGetSelectionOwner(x11_display, clipboard);

if (selection_owner != None) {
// 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.instantiate();
PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret);
}
} 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.instantiate();
PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret);
} else {
print_verbose("Failed to get selection data.");
}

if (data) {
XFree(data);
}
}
}

return ret;
}

bool DisplayServerX11::clipboard_has_image() const {
Atom target = _clipboard_get_image_target(
XInternAtom(x11_display, "CLIPBOARD", 0),
windows[MAIN_WINDOW_ID].x11_window);
return target != None;
}

Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) {
if (event->xany.window == *(Window *)arg) {
return (event->type == SelectionRequest) ||
Expand Down
3 changes: 3 additions & 0 deletions platform/linuxbsd/x11/display_server_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,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;
void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const;

bool do_mouse_warp = false;
Expand Down Expand Up @@ -406,6 +407,8 @@ class DisplayServerX11 : public DisplayServer {

virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual Ref<Image> clipboard_get_image() const override;
virtual bool clipboard_has_image() const override;
virtual void clipboard_set_primary(const String &p_text) override;
virtual String clipboard_get_primary() const override;

Expand Down
Loading