Fix delay to process clipboard content from Godot in other programs

When pasting clipboard content from Godot to other applications,
multiple SelectionRequest events are sent to Godot in order to access
the data. It could take a long time before the data is ready for the
other app because events were processed one by one on the main thread,
especially when Godot is unfocused and runs at low frequency.

With this change, SelectionRequest events are directly handled on the
separate event polling thread to minimize this delay.

This change also replaces clipboard_get() calls in SelectionRequest with
a direct access to internal_clipboard, since in this case we know Godot
is the owner of the clipboard content and it's not necessary to query
the x server for it.
This commit is contained in:
PouleyKetchoupp 2020-09-25 16:40:04 +02:00
parent 2c4d64102a
commit 5a0376f969
2 changed files with 80 additions and 65 deletions

View file

@ -447,7 +447,12 @@ int DisplayServerX11::mouse_get_button_state() const {
void DisplayServerX11::clipboard_set(const String &p_text) {
_THREAD_SAFE_METHOD_
internal_clipboard = p_text;
{
// The clipboard content can be accessed while polling for events.
MutexLock mutex_lock(events_mutex);
internal_clipboard = p_text;
}
XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);
}
@ -468,13 +473,13 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A
int format, result;
unsigned long len, bytes_left, dummy;
unsigned char *data;
Window Sown = XGetSelectionOwner(x11_display, p_source);
Window selection_owner = XGetSelectionOwner(x11_display, p_source);
if (Sown == x11_window) {
if (selection_owner == x11_window) {
return internal_clipboard;
};
}
if (Sown != None) {
if (selection_owner != None) {
{
// Block events polling while processing selection events.
MutexLock mutex_lock(events_mutex);
@ -2266,6 +2271,66 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
Input::get_singleton()->accumulate_input_event(k);
}
void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) {
XEvent respond;
if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
p_event->target == XInternAtom(x11_display, "TEXT", 0) ||
p_event->target == XA_STRING ||
p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
p_event->target == XInternAtom(x11_display, "text/plain", 0)) {
// Directly using internal clipboard because we know our window
// is the owner during a selection request.
CharString clip = internal_clipboard.utf8();
XChangeProperty(x11_display,
p_event->requestor,
p_event->property,
p_event->target,
8,
PropModeReplace,
(unsigned char *)clip.get_data(),
clip.length());
respond.xselection.property = p_event->property;
} else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) {
Atom data[7];
data[0] = XInternAtom(x11_display, "TARGETS", 0);
data[1] = XInternAtom(x11_display, "UTF8_STRING", 0);
data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
data[3] = XInternAtom(x11_display, "TEXT", 0);
data[4] = XA_STRING;
data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
data[6] = XInternAtom(x11_display, "text/plain", 0);
XChangeProperty(x11_display,
p_event->requestor,
p_event->property,
XA_ATOM,
32,
PropModeReplace,
(unsigned char *)&data,
sizeof(data) / sizeof(data[0]));
respond.xselection.property = p_event->property;
} else {
char *targetname = XGetAtomName(x11_display, p_event->target);
printf("No Target '%s'\n", targetname);
if (targetname) {
XFree(targetname);
}
respond.xselection.property = None;
}
respond.xselection.type = SelectionNotify;
respond.xselection.display = p_event->display;
respond.xselection.requestor = p_event->requestor;
respond.xselection.selection = p_event->selection;
respond.xselection.target = p_event->target;
respond.xselection.time = p_event->time;
XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond);
XFlush(x11_display);
}
void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data) {
WARN_PRINT("Input method stopped");
@ -2422,6 +2487,15 @@ void DisplayServerX11::_poll_events() {
// it has to be ignored and a new one will be received.
continue;
}
// Handle selection request events directly in the event thread, because
// communication through the x server takes several events sent back and forth
// and we don't want to block other programs while processing only one each frame.
if (ev.type == SelectionRequest) {
_handle_selection_request_event(&(ev.xselectionrequest));
continue;
}
polled_events.push_back(ev);
}
}
@ -3050,66 +3124,6 @@ void DisplayServerX11::process_events() {
// it will be handled in its own function.
_handle_key_event(window_id, (XKeyEvent *)&event, events, event_index);
} break;
case SelectionRequest: {
XSelectionRequestEvent *req;
XEvent e, respond;
e = event;
req = &(e.xselectionrequest);
if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) ||
req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
req->target == XInternAtom(x11_display, "TEXT", 0) ||
req->target == XA_STRING ||
req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) ||
req->target == XInternAtom(x11_display, "text/plain", 0)) {
CharString clip = clipboard_get().utf8();
XChangeProperty(x11_display,
req->requestor,
req->property,
req->target,
8,
PropModeReplace,
(unsigned char *)clip.get_data(),
clip.length());
respond.xselection.property = req->property;
} else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {
Atom data[7];
data[0] = XInternAtom(x11_display, "TARGETS", 0);
data[1] = XInternAtom(x11_display, "UTF8_STRING", 0);
data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0);
data[3] = XInternAtom(x11_display, "TEXT", 0);
data[4] = XA_STRING;
data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0);
data[6] = XInternAtom(x11_display, "text/plain", 0);
XChangeProperty(x11_display,
req->requestor,
req->property,
XA_ATOM,
32,
PropModeReplace,
(unsigned char *)&data,
sizeof(data) / sizeof(data[0]));
respond.xselection.property = req->property;
} else {
char *targetname = XGetAtomName(x11_display, req->target);
printf("No Target '%s'\n", targetname);
if (targetname) {
XFree(targetname);
}
respond.xselection.property = None;
}
respond.xselection.type = SelectionNotify;
respond.xselection.display = req->display;
respond.xselection.requestor = req->requestor;
respond.xselection.selection = req->selection;
respond.xselection.target = req->target;
respond.xselection.time = req->time;
XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond);
XFlush(x11_display);
} break;
case SelectionNotify:

View file

@ -204,6 +204,7 @@ class DisplayServerX11 : public DisplayServer {
Point2i center;
void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false);
void _handle_selection_request_event(XSelectionRequestEvent *p_event);
String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const;
String _clipboard_get(Atom p_source, Window x11_window) const;