2
0
Эх сурвалжийг харах

Merge pull request #41910 from nekomatata/x11-inputs-lag-fix

Fix issues related to delay when processing events on X11 display server
Rémi Verschelde 5 жил өмнө
parent
commit
cc62eaf7be

+ 229 - 125
platform/linuxbsd/display_server_x11.cpp

@@ -322,23 +322,19 @@ bool DisplayServerX11::_refresh_device_info() {
 }
 
 void DisplayServerX11::_flush_mouse_motion() {
-	while (true) {
-		if (XPending(x11_display) > 0) {
-			XEvent event;
-			XPeekEvent(x11_display, &event);
-
-			if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
-				XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
-
-				if (event_data->evtype == XI_RawMotion) {
-					XNextEvent(x11_display, &event);
-				} else {
-					break;
-				}
-			} else {
-				break;
+	// Block events polling while flushing motion events.
+	MutexLock mutex_lock(events_mutex);
+
+	for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) {
+		XEvent &event = polled_events[event_index];
+		if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {
+			XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
+			if (event_data->evtype == XI_RawMotion) {
+				XFreeEventData(x11_display, &event.xcookie);
+				polled_events.remove(event_index--);
+				continue;
 			}
-		} else {
+			XFreeEventData(x11_display, &event.xcookie);
 			break;
 		}
 	}
@@ -451,12 +447,25 @@ 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);
 }
 
-static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) {
+Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) {
+	if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) {
+		return True;
+	} else {
+		return False;
+	}
+}
+
+String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {
 	String ret;
 
 	Atom type;
@@ -464,23 +473,27 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x
 	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) {
-		return p_internal_clipboard;
-	};
+	if (selection_owner == x11_window) {
+		return internal_clipboard;
+	}
 
-	if (Sown != None) {
-		XConvertSelection(x11_display, p_source, target, selection,
-				x11_window, CurrentTime);
-		XFlush(x11_display);
-		while (true) {
+	if (selection_owner != None) {
+		{
+			// 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);
+
+			// Blocking wait for predicate to be True
+			// and remove the event from the queue.
 			XEvent event;
-			XNextEvent(x11_display, &event);
-			if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
-				break;
-			};
-		};
+			XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window);
+		}
 
 		//
 		// Do not get any data, see how much data is there
@@ -514,14 +527,14 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x
 	return ret;
 }
 
-static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) {
+String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {
 	String ret;
 	Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
 	if (utf8_atom != None) {
-		ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom);
+		ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);
 	}
-	if (ret == "") {
-		ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING);
+	if (ret.empty()) {
+		ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);
 	}
 	return ret;
 }
@@ -530,11 +543,11 @@ String DisplayServerX11::clipboard_get() const {
 	_THREAD_SAFE_METHOD_
 
 	String ret;
-	ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard);
+	ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window);
 
-	if (ret == "") {
-		ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard);
-	};
+	if (ret.empty()) {
+		ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window);
+	}
 
 	return ret;
 }
@@ -1649,10 +1662,16 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win
 		return;
 	}
 
+	// Block events polling while changing input focus
+	// because it triggers some event polling internally.
 	if (p_active) {
-		XSetICFocus(wd.xic);
+		{
+			MutexLock mutex_lock(events_mutex);
+			XSetICFocus(wd.xic);
+		}
 		window_set_ime_position(wd.im_position, p_window);
 	} else {
+		MutexLock mutex_lock(events_mutex);
 		XUnsetICFocus(wd.xic);
 	}
 }
@@ -1673,7 +1692,14 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_
 	spot.x = short(p_pos.x);
 	spot.y = short(p_pos.y);
 	XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr);
-	XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr);
+
+	{
+		// Block events polling during this call
+		// because it triggers some event polling internally.
+		MutexLock mutex_lock(events_mutex);
+		XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr);
+	}
+
 	XFree(preedit_attr);
 }
 
@@ -1993,7 +2019,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button
 	return last_button_state;
 }
 
-void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) {
+void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {
 	WindowData wd = windows[p_window];
 	// X11 functions don't know what const is
 	XKeyEvent *xkeyevent = p_event;
@@ -2130,7 +2156,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
 	/* Phase 4, determine if event must be filtered */
 
 	// This seems to be a side-effect of using XIM.
-	// XEventFilter looks like a core X11 function,
+	// XFilterEvent looks like a core X11 function,
 	// but it's actually just used to see if we must
 	// ignore a deadkey, or events XIM determines
 	// must not reach the actual gui.
@@ -2164,17 +2190,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
 
 	// Echo characters in X11 are a keyrelease and a keypress
 	// one after the other with the (almot) same timestamp.
-	// To detect them, i use XPeekEvent and check that their
-	// difference in time is below a threshold.
+	// To detect them, i compare to the next event in list and
+	// check that their difference in time is below a threshold.
 
 	if (xkeyevent->type != KeyPress) {
 		p_echo = false;
 
 		// make sure there are events pending,
 		// so this call won't block.
-		if (XPending(x11_display) > 0) {
-			XEvent peek_event;
-			XPeekEvent(x11_display, &peek_event);
+		if (p_event_index + 1 < p_events.size()) {
+			XEvent &peek_event = p_events[p_event_index + 1];
 
 			// I'm using a threshold of 5 msecs,
 			// since sometimes there seems to be a little
@@ -2189,9 +2214,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
 				KeySym rk;
 				XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);
 				if (rk == keysym_keycode) {
-					XEvent event;
-					XNextEvent(x11_display, &event); //erase next event
-					_handle_key_event(p_window, (XKeyEvent *)&event, true);
+					// Consume to next event.
+					++p_event_index;
+					_handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);
 					return; //ignore current, echo next
 				}
 			}
@@ -2246,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");
@@ -2358,6 +2443,65 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev
 	}
 }
 
+void DisplayServerX11::_poll_events_thread(void *ud) {
+	DisplayServerX11 *display_server = (DisplayServerX11 *)ud;
+	display_server->_poll_events();
+}
+
+Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) {
+	// Just accept all events.
+	return True;
+}
+
+void DisplayServerX11::_poll_events() {
+	int x11_fd = ConnectionNumber(x11_display);
+	fd_set in_fds;
+
+	while (!events_thread_done) {
+		XFlush(x11_display);
+
+		FD_ZERO(&in_fds);
+		FD_SET(x11_fd, &in_fds);
+
+		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);
+		if (num_ready_fds < 0) {
+			ERR_PRINT("_poll_events: select error: " + itos(errno));
+		}
+
+		// Process events from the queue.
+		{
+			MutexLock mutex_lock(events_mutex);
+
+			// Non-blocking wait for next event
+			// and remove it from the queue.
+			XEvent ev;
+			while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) {
+				// Check if the input manager wants to process the event.
+				if (XFilterEvent(&ev, None)) {
+					// Event has been filtered by the Input Manager,
+					// 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);
+			}
+		}
+	}
+}
+
 void DisplayServerX11::process_events() {
 	_THREAD_SAFE_METHOD_
 
@@ -2401,13 +2545,16 @@ void DisplayServerX11::process_events() {
 	xi.tilt = Vector2();
 	xi.pressure_supported = false;
 
-	while (XPending(x11_display) > 0) {
-		XEvent event;
-		XNextEvent(x11_display, &event);
+	LocalVector<XEvent> events;
+	{
+		// Block events polling while flushing events.
+		MutexLock mutex_lock(events_mutex);
+		events = polled_events;
+		polled_events.clear();
+	}
 
-		if (XFilterEvent(&event, None)) {
-			continue;
-		}
+	for (uint32_t event_index = 0; event_index < events.size(); ++event_index) {
+		XEvent &event = events[event_index];
 
 		WindowID window_id = MAIN_WINDOW_ID;
 
@@ -2666,6 +2813,9 @@ void DisplayServerX11::process_events() {
 				}*/
 #endif
 				if (wd.xic) {
+					// Block events polling while changing input focus
+					// because it triggers some event polling internally.
+					MutexLock mutex_lock(events_mutex);
 					XSetICFocus(wd.xic);
 				}
 
@@ -2715,7 +2865,10 @@ void DisplayServerX11::process_events() {
 				xi.state.clear();
 #endif
 				if (wd.xic) {
-					XSetICFocus(wd.xic);
+					// Block events polling while changing input focus
+					// because it triggers some event polling internally.
+					MutexLock mutex_lock(events_mutex);
+					XUnsetICFocus(wd.xic);
 				}
 			} break;
 
@@ -2834,11 +2987,11 @@ void DisplayServerX11::process_events() {
 						break;
 					}
 
-					if (XPending(x11_display) > 0) {
-						XEvent tevent;
-						XPeekEvent(x11_display, &tevent);
-						if (tevent.type == MotionNotify) {
-							XNextEvent(x11_display, &event);
+					if (event_index + 1 < events.size()) {
+						const XEvent &next_event = events[event_index + 1];
+						if (next_event.type == MotionNotify) {
+							++event_index;
+							event = next_event;
 						} else {
 							break;
 						}
@@ -2969,67 +3122,7 @@ void DisplayServerX11::process_events() {
 
 				// key event is a little complex, so
 				// it will be handled in its own function.
-				_handle_key_event(window_id, (XKeyEvent *)&event);
-			} 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);
+				_handle_key_event(window_id, (XKeyEvent *)&event, events, event_index);
 			} break;
 
 			case SelectionNotify:
@@ -3424,6 +3517,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
 		XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
 
 		if (xim && xim_style) {
+			// Block events polling while changing input focus
+			// because it triggers some event polling internally.
+			MutexLock mutex_lock(events_mutex);
+
 			wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr);
 			if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {
 				WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value");
@@ -3926,12 +4023,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
 		}
 	}
 
+	events_thread = Thread::create(_poll_events_thread, this);
+
 	_update_real_mouse_position(windows[MAIN_WINDOW_ID]);
 
 	r_error = OK;
 }
 
 DisplayServerX11::~DisplayServerX11() {
+	events_thread_done = true;
+	Thread::wait_to_finish(events_thread);
+	memdelete(events_thread);
+	events_thread = nullptr;
+
 	//destroy all windows
 	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
 #ifdef VULKAN_ENABLED

+ 16 - 1
platform/linuxbsd/display_server_x11.h

@@ -36,6 +36,7 @@
 #include "servers/display_server.h"
 
 #include "core/input/input.h"
+#include "core/local_vector.h"
 #include "drivers/alsa/audio_driver_alsa.h"
 #include "drivers/alsamidi/midi_driver_alsamidi.h"
 #include "drivers/pulseaudio/audio_driver_pulseaudio.h"
@@ -202,7 +203,11 @@ class DisplayServerX11 : public DisplayServer {
 	MouseMode mouse_mode;
 	Point2i center;
 
-	void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false);
+	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;
 
 	//bool minimized;
 	//bool window_has_focus;
@@ -252,6 +257,16 @@ class DisplayServerX11 : public DisplayServer {
 	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
 	void _dispatch_input_event(const Ref<InputEvent> &p_event);
 
+	mutable Mutex events_mutex;
+	Thread *events_thread = nullptr;
+	bool events_thread_done = false;
+	LocalVector<XEvent> polled_events;
+	static void _poll_events_thread(void *ud);
+	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);
+
 protected:
 	void _window_changed(XEvent *event);