diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index cc77ec68219..5c3a5933660 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -813,7 +813,7 @@ void OS_X11::finalize() { events_thread_done = true; Thread::wait_to_finish(events_thread); memdelete(events_thread); - events_thread = nullptr; + events_thread = NULL; if (main_loop) memdelete(main_loop); @@ -2028,53 +2028,106 @@ void OS_X11::_handle_key_event(XKeyEvent *p_event, LocalVector &p_events input->accumulate_input_event(k); } -void OS_X11::_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 = OS::get_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]; +Atom OS_X11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { + if (p_target == XInternAtom(x11_display, "TARGETS", 0)) { + // Request to list all supported targets. + Atom data[9]; 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); + data[1] = XInternAtom(x11_display, "SAVE_TARGETS", 0); + data[2] = XInternAtom(x11_display, "MULTIPLE", 0); + data[3] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[4] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[5] = XInternAtom(x11_display, "TEXT", 0); + data[6] = XA_STRING; + data[7] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[8] = XInternAtom(x11_display, "text/plain", 0); XChangeProperty(x11_display, - p_event->requestor, - p_event->property, + p_requestor, + p_property, XA_ATOM, 32, PropModeReplace, (unsigned char *)&data, sizeof(data) / sizeof(data[0])); - respond.xselection.property = p_event->property; + return p_property; + } else if (p_target == XInternAtom(x11_display, "SAVE_TARGETS", 0)) { + // Request to check if SAVE_TARGETS is supported, nothing special to do. + XChangeProperty(x11_display, + p_requestor, + p_property, + XInternAtom(x11_display, "NULL", False), + 32, + PropModeReplace, + NULL, + 0); + return p_property; + } else if (p_target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_target == XInternAtom(x11_display, "TEXT", 0) || + p_target == XA_STRING || + p_target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_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 = OS::get_clipboard().utf8(); + XChangeProperty(x11_display, + p_requestor, + p_property, + p_target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + return p_property; } else { - char *targetname = XGetAtomName(x11_display, p_event->target); - printf("No Target '%s'\n", targetname); - if (targetname) { - XFree(targetname); + char *target_name = XGetAtomName(x11_display, p_target); + printf("Target '%s' not supported.\n", target_name); + if (target_name) { + XFree(target_name); } + return None; + } +} + +void OS_X11::_handle_selection_request_event(XSelectionRequestEvent *p_event) const { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "MULTIPLE", 0)) { + // Request for multiple target conversions at once. + Atom atom_pair = XInternAtom(x11_display, "ATOM_PAIR", False); respond.xselection.property = None; + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + if (XGetWindowProperty(x11_display, p_event->requestor, p_event->property, 0, LONG_MAX, False, atom_pair, &type, &format, &len, &remaining, &data) == Success) { + if ((len >= 2) && data) { + Atom *targets = (Atom *)data; + for (uint64_t i = 0; i < len; i += 2) { + Atom target = targets[i]; + Atom &property = targets[i + 1]; + property = _process_selection_request_target(target, p_event->requestor, property); + } + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + atom_pair, + 32, + PropModeReplace, + (unsigned char *)targets, + len); + + respond.xselection.property = p_event->property; + } + XFree(data); + } + } else { + // Request for target conversion. + respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property); } respond.xselection.type = SelectionNotify; @@ -2176,32 +2229,43 @@ Bool OS_X11::_predicate_all_events(Display *display, XEvent *event, XPointer arg return True; } -void OS_X11::_poll_events() { +bool OS_X11::_wait_for_events() const { int x11_fd = ConnectionNumber(x11_display); fd_set in_fds; - while (!events_thread_done) { - XFlush(x11_display); + XFlush(x11_display); - FD_ZERO(&in_fds); - FD_SET(x11_fd, &in_fds); + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); - struct timeval tv; - tv.tv_usec = 0; - tv.tv_sec = 1; + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; - // Wait for next event or timeout. - int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + + if (num_ready_fds > 0) { + // Event received. + return true; + } else { + // Error or timeout. if (num_ready_fds < 0) { - ERR_PRINT("_poll_events: select error: " + itos(errno)); + ERR_PRINT("_wait_for_events: select error: " + itos(errno)); } + return false; + } +} + +void OS_X11::_poll_events() { + while (!events_thread_done) { + _wait_for_events(); // Process events from the queue. { MutexLock mutex_lock(events_mutex); - // Non-blocking wait for next event - // and remove it from the queue. + // Non-blocking wait for next event and remove it from the queue. XEvent ev; while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, NULL)) { // Check if the input manager wants to process the event. @@ -2806,6 +2870,10 @@ MainLoop *OS_X11::get_main_loop() const { } void OS_X11::delete_main_loop() { + // Send owned clipboard data to clipboard manager before exit. + // This has to be done here because the clipboard data is cleared before finalize(). + _clipboard_transfer_ownership(XA_PRIMARY, x11_window); + _clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window); if (main_loop) memdelete(main_loop); @@ -2842,60 +2910,137 @@ Bool OS_X11::_predicate_clipboard_selection(Display *display, XEvent *event, XPo } } -String OS_X11::_get_clipboard_impl(Atom p_source, Window x11_window, Atom target) const { +Bool OS_X11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + return True; + } else { + return False; + } +} +String OS_X11::_get_clipboard_impl(Atom p_source, Window x11_window, Atom target) const { String ret; - Atom type; - Atom selection = XA_PRIMARY; - int format, result; - unsigned long len, bytes_left, dummy; - unsigned char *data; Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (selection_owner == x11_window) { - return OS::get_clipboard(); } if (selection_owner != None) { - { - // Block events polling while processing selection events. - MutexLock mutex_lock(events_mutex); + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - XFlush(x11_display); + Atom selection = XA_PRIMARY; + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); - // 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); - } + XFlush(x11_display); - // - // Do not get any data, see how much data is there - // + // 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, selection, // Tricky.. 0, 0, // offset - len 0, // Delete 0==FALSE - AnyPropertyType, //flag + AnyPropertyType, // flag &type, // return type &format, // return format - &len, &bytes_left, //that + &len, &bytes_left, // data length &data); - // DATA is There - if (bytes_left > 0) { + + if (data) { + XFree(data); + } + + if (type == XInternAtom(x11_display, "INCR", 0)) { + // Data is going to be received incrementally. + LocalVector incr_data; + uint32_t data_size = 0; + bool success = false; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, type); + + // 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, NULL)) { + result = XGetWindowProperty(x11_display, x11_window, + selection, // selection type + 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); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + if (prev_size == 0) { + // First property contains initial data size. + unsigned long initial_size = *(unsigned long *)data; + incr_data.resize(initial_size); + } else { + // 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 { + // Last chunk, process finished. + done = true; + success = true; + } + } else { + printf("Failed to get selection data chunk.\n"); + done = true; + } + + if (data) { + XFree(data); + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.parse_utf8((const char *)incr_data.ptr(), data_size); + } + } else if (bytes_left > 0) { + // Data is ready and can be processed all at once. result = XGetWindowProperty(x11_display, x11_window, selection, 0, bytes_left, 0, AnyPropertyType, &type, &format, &len, &dummy, &data); + if (result == Success) { ret.parse_utf8((const char *)data); - } else - printf("FAIL\n"); - XFree(data); + } else { + printf("Failed to get selection data.\n"); + } + + if (data) { + XFree(data); + } } } @@ -2926,6 +3071,58 @@ String OS_X11::get_clipboard() const { return ret; } +Bool OS_X11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { + if (event->xany.window == *(Window *)arg) { + return (event->type == SelectionRequest) || + (event->type == SelectionNotify); + } else { + return False; + } +} + +void OS_X11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) const { + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + + if (selection_owner != x11_window) { + return; + } + + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom clipboard_manager = XInternAtom(x11_display, "CLIPBOARD_MANAGER", False); + Atom save_targets = XInternAtom(x11_display, "SAVE_TARGETS", False); + XConvertSelection(x11_display, clipboard_manager, save_targets, None, + x11_window, CurrentTime); + + // Process events from the queue. + while (true) { + 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_save_targets, (XPointer)&x11_window)) { + switch (ev.type) { + case SelectionRequest: + _handle_selection_request_event(&(ev.xselectionrequest)); + break; + + case SelectionNotify: { + if (ev.xselection.target == save_targets) { + // Once SelectionNotify is received, we're done whether it succeeded or not. + return; + } + + break; + } + } + } + } +} + String OS_X11::get_name() const { return "X11"; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index fe9c9feae31..b13aca3b00f 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -157,20 +157,26 @@ class OS_X11 : public OS_Unix { Point2i center; void _handle_key_event(XKeyEvent *p_event, LocalVector &p_events, uint32_t &p_event_index, bool p_echo = false); - void _handle_selection_request_event(XSelectionRequestEvent *p_event); + + Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const; + void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; String _get_clipboard_impl(Atom p_source, Window x11_window, Atom target) const; String _get_clipboard(Atom p_source, Window x11_window) const; + void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; mutable Mutex *events_mutex; Thread *events_thread = nullptr; bool events_thread_done = false; LocalVector polled_events; static void _poll_events_thread(void *ud); + bool _wait_for_events() const; void _poll_events(); static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg); void process_xevents(); virtual void delete_main_loop();