From ea5f1ad1e8f821ac582b22727f15d01d856084a1 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 17 Jun 2014 09:05:29 +0200 Subject: [PATCH] [X11] Send ClientMessage to exit input event loop To do that, we need a custom InputOnly window. This window is only used for receiving the exit ClientMessage - XI2 input is still received through the root window. Fixes issue https://github.com/mono/MonoGame/issues/2711 --- .../OpenTK/Platform/X11/XI2MouseKeyboard.cs | 137 +++++++++++------- 1 file changed, 88 insertions(+), 49 deletions(-) diff --git a/Source/OpenTK/Platform/X11/XI2MouseKeyboard.cs b/Source/OpenTK/Platform/X11/XI2MouseKeyboard.cs index 1ef31796..a8a1fd2d 100644 --- a/Source/OpenTK/Platform/X11/XI2MouseKeyboard.cs +++ b/Source/OpenTK/Platform/X11/XI2MouseKeyboard.cs @@ -36,7 +36,7 @@ namespace OpenTK.Platform.X11 { sealed class XI2MouseKeyboard : IKeyboardDriver2, IMouseDriver2, IDisposable { - const XEventName ExitEvent = XEventName.LASTEvent + 1; + static readonly IntPtr ExitAtom; readonly object Sync = new object(); readonly Thread ProcessingThread; readonly X11KeyMap KeyMap; @@ -116,6 +116,9 @@ namespace OpenTK.Platform.X11 //TouchPressure = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Pressure", false); //TouchId = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Tracking ID", false); //TouchMaxContacts = Functions.XInternAtom(API.DefaultDisplay, "Max Contacts", false); + + // Custom + ExitAtom = Functions.XInternAtom(API.DefaultDisplay, "Exit Input Thread Message", false); } } @@ -126,9 +129,14 @@ namespace OpenTK.Platform.X11 window.Display = Functions.XOpenDisplay(IntPtr.Zero); using (new XLock(window.Display)) { + XSetWindowAttributes attr = new XSetWindowAttributes(); + window.Screen = Functions.XDefaultScreen(window.Display); window.RootWindow = Functions.XRootWindow(window.Display, window.Screen); - window.Handle = window.RootWindow; + window.Handle = Functions.XCreateWindow(window.Display, window.RootWindow, + 0, 0, 1, 1, 0, 0, + CreateWindowArgs.InputOnly, IntPtr.Zero, + SetWindowValuemask.Nothing, attr); KeyMap = new X11KeyMap(window.Display); } @@ -136,6 +144,10 @@ namespace OpenTK.Platform.X11 if (!IsSupported(window.Display)) throw new NotSupportedException("XInput2 not supported."); + // Enable XI2 mouse/keyboard events + // Note: the input event loop blocks waiting for these events + // *or* a custom ClientMessage event that instructs us to exit. + // See SendExitEvent() below. using (new XLock(window.Display)) using (XIEventMask mask = new XIEventMask(1, XIEventMasks.RawKeyPressMask | @@ -144,10 +156,9 @@ namespace OpenTK.Platform.X11 XIEventMasks.RawButtonReleaseMask | XIEventMasks.RawMotionMask | XIEventMasks.MotionMask | - XIEventMasks.DeviceChangedMask | - (XIEventMasks)(1 << (int)ExitEvent))) + XIEventMasks.DeviceChangedMask)) { - XI.SelectEvents(window.Display, window.Handle, mask); + XI.SelectEvents(window.Display, window.RootWindow, mask); UpdateDevices(); } @@ -423,45 +434,52 @@ namespace OpenTK.Platform.X11 using (new XLock(window.Display)) { Functions.XIfEvent(window.Display, ref e, Predicate, new IntPtr(XIOpCode)); - if (e.type == ExitEvent) + + if (e.type == XEventName.ClientMessage && e.ClientMessageEvent.ptr1 == ExitAtom) { - return; + Functions.XDestroyWindow(window.Display, window.Handle); + window.Handle = IntPtr.Zero; + break; } - IntPtr dummy; - int x, y, dummy2; - Functions.XQueryPointer(window.Display, window.RootWindow, - out dummy, out dummy, out x, out y, - out dummy2, out dummy2, out dummy2); - Interlocked.Exchange(ref cursor_x, (long)x); - Interlocked.Exchange(ref cursor_y, (long)y); - - cookie = e.GenericEventCookie; - if (Functions.XGetEventData(window.Display, ref cookie) != 0) + if (e.type == XEventName.GenericEvent && e.GenericEvent.extension == XIOpCode) { - switch ((XIEventType)cookie.evtype) + IntPtr dummy; + int x, y, dummy2; + Functions.XQueryPointer(window.Display, window.RootWindow, + out dummy, out dummy, out x, out y, + out dummy2, out dummy2, out dummy2); + Interlocked.Exchange(ref cursor_x, (long)x); + Interlocked.Exchange(ref cursor_y, (long)y); + + cookie = e.GenericEventCookie; + if (Functions.XGetEventData(window.Display, ref cookie) != 0) { - case XIEventType.Motion: + switch ((XIEventType)cookie.evtype) + { + case XIEventType.Motion: // Nothing to do - break; + break; - case XIEventType.RawKeyPress: - case XIEventType.RawKeyRelease: - case XIEventType.RawMotion: - case XIEventType.RawButtonPress: - case XIEventType.RawButtonRelease: + case XIEventType.RawKeyPress: + case XIEventType.RawKeyRelease: + case XIEventType.RawMotion: + case XIEventType.RawButtonPress: + case XIEventType.RawButtonRelease: // Delivered to all XIMouse instances - ProcessRawEvent(ref cookie); - break; + ProcessRawEvent(ref cookie); + break; - case XIEventType.DeviceChanged: - UpdateDevices(); - break; + case XIEventType.DeviceChanged: + UpdateDevices(); + break; + } } + Functions.XFreeEventData(window.Display, ref cookie); } - Functions.XFreeEventData(window.Display, ref cookie); } } + Debug.WriteLine("[X11] Exiting input event loop."); } void ProcessRawEvent(ref XGenericEventCookie cookie) @@ -571,16 +589,29 @@ namespace OpenTK.Platform.X11 static bool IsEventValid(IntPtr display, ref XEvent e, IntPtr arg) { bool valid = false; - if ((long)e.GenericEventCookie.extension == arg.ToInt64()) + switch (e.type) { - valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawKeyPress; - valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawKeyRelease; - valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawMotion; - valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress; - valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease; - valid |= e.GenericEventCookie.evtype == (int)XIEventType.DeviceChanged; + case XEventName.GenericEvent: + { + long extension = (long)e.GenericEventCookie.extension; + valid = + extension == arg.ToInt64() && + (e.GenericEventCookie.evtype == (int)XIEventType.RawKeyPress || + e.GenericEventCookie.evtype == (int)XIEventType.RawKeyRelease || + e.GenericEventCookie.evtype == (int)XIEventType.RawMotion || + e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress || + e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease || + e.GenericEventCookie.evtype == (int)XIEventType.DeviceChanged); + valid |= extension == 0; + break; + } + + case XEventName.ClientMessage: + valid = + e.ClientMessageEvent.ptr1 == ExitAtom; + break; } - valid |= e.AnyEvent.type == ExitEvent; + return valid; } @@ -592,6 +623,23 @@ namespace OpenTK.Platform.X11 } } + void SendExitEvent() + { + // Send a ClientMessage event to unblock XIfEvent + // and exit the input event loop. + using (new XLock(API.DefaultDisplay)) + { + XEvent ev = new XEvent(); + ev.type = XEventName.ClientMessage; + ev.ClientMessageEvent.display = window.Display; + ev.ClientMessageEvent.window = window.Handle; + ev.ClientMessageEvent.format = 32; + ev.ClientMessageEvent.ptr1 = ExitAtom; + Functions.XSendEvent(API.DefaultDisplay, window.Handle, false, 0, ref ev); + Functions.XFlush(API.DefaultDisplay); + } + } + #endregion #region IDisposable Members @@ -606,17 +654,8 @@ namespace OpenTK.Platform.X11 { if (!disposed) { - if (disposing) - { - using (new XLock(API.DefaultDisplay)) - { - XEvent e = new XEvent(); - e.type = ExitEvent; - Functions.XSendEvent(API.DefaultDisplay, window.Handle, false, IntPtr.Zero, ref e); - Functions.XFlush(API.DefaultDisplay); - } - } disposed = true; + SendExitEvent(); } }