Skip to content

Commit

Permalink
Merge pull request #138 from thefiddler/xi2exitfix
Browse files Browse the repository at this point in the history
[X11] Fix hang when exiting XI2 input thread
  • Loading branch information
thefiddler committed Jun 18, 2014
2 parents f889835 + ea5f1ad commit f0fd13a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 77 deletions.
40 changes: 19 additions & 21 deletions Source/OpenTK/Platform/X11/API.cs
Expand Up @@ -117,24 +117,6 @@ static void CurrentDomain_ProcessExit(object sender, EventArgs e)

#region Window handling

[Obsolete("Use XCreateWindow instead")]
[DllImport(_dll_name, EntryPoint = "XCreateWindow")]
public extern static Window CreateWindow(
Display display,
Window parent,
int x, int y,
//uint width, uint height,
int width, int height,
//uint border_width,
int border_width,
int depth,
//uint @class,
int @class,
IntPtr visual,
[MarshalAs(UnmanagedType.SysUInt)] CreateWindowMask valuemask,
SetWindowAttributes attributes
);

[DllImport(_dll_name, EntryPoint = "XCreateSimpleWindow")]
public extern static Window CreateSimpleWindow(
Display display,
Expand Down Expand Up @@ -1462,10 +1444,26 @@ internal static partial class Functions
/// <para>The XCreateSimpleWindow function creates an unmapped InputOutput subwindow for a specified parent window, returns the window ID of the created window, and causes the X server to generate a CreateNotify event. The created window is placed on top in the stacking order with respect to siblings. Any part of the window that extends outside its parent window is clipped. The border_width for an InputOnly window must be zero, or a BadMatch error results. XCreateSimpleWindow inherits its depth, class, and visual from its parent. All other window attributes, except background and border, have their default values. </para>
/// <para>XCreateSimpleWindow can generate BadAlloc, BadMatch, BadValue, and BadWindow errors.</para>
/// </remarks>
[DllImport(X11Library, EntryPoint = "XCreateWindow")]//, CLSCompliant(false)]
public extern static Window XCreateWindow(Display display, Window parent,
public static Window XCreateWindow(Display display, Window parent,
int x, int y, int width, int height, int border_width, int depth,
int @class, IntPtr visual, UIntPtr valuemask, ref XSetWindowAttributes attributes);
CreateWindowArgs @class, IntPtr visual, SetWindowValuemask valuemask,
XSetWindowAttributes? attributes)
{
unsafe
{
if (attributes.HasValue)
{
XSetWindowAttributes attr = attributes.Value;
return XCreateWindow(display, parent, x, y, width, height, border_width, depth,
(int)@class, visual, (IntPtr)valuemask, &attr);
}
else
{
return XCreateWindow(display, parent, x, y, width, height, border_width, depth,
(int)@class, visual, (IntPtr)valuemask, null);
}
}
}

#endregion

Expand Down
7 changes: 3 additions & 4 deletions Source/OpenTK/Platform/X11/Functions.cs
Expand Up @@ -71,10 +71,8 @@ public static IntPtr XOpenDisplay(IntPtr display)
[DllImport("libX11", EntryPoint = "XSynchronize")]
public extern static IntPtr XSynchronize(IntPtr display, bool onoff);

//[DllImport("libX11", EntryPoint = "XCreateWindow"), CLSCompliant(false)]
//public extern static IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, int border_width, int depth, int xclass, IntPtr visual, UIntPtr valuemask, ref XSetWindowAttributes attributes);
[DllImport("libX11", EntryPoint = "XCreateWindow")]
public extern static IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, int border_width, int depth, int xclass, IntPtr visual, IntPtr valuemask, ref XSetWindowAttributes attributes);
public unsafe extern static IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, int border_width, int depth, int xclass, IntPtr visual, IntPtr valuemask, XSetWindowAttributes* attributes);

[DllImport("libX11", EntryPoint = "XCreateSimpleWindow")]//, CLSCompliant(false)]
public extern static IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, int border_width, UIntPtr border, UIntPtr background);
Expand Down Expand Up @@ -103,7 +101,8 @@ public static IntPtr XOpenDisplay(IntPtr display)
[DllImport("libX11")]
public extern static Bool XCheckTypedEvent(Display display, XEventName event_type, out XEvent event_return);


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public delegate Bool EventPredicate(IntPtr display, ref XEvent e, IntPtr arg);
[DllImport("libX11")]
public extern static Bool XIfEvent(Display display, ref XEvent e, IntPtr predicate, IntPtr arg );
Expand Down
7 changes: 4 additions & 3 deletions Source/OpenTK/Platform/X11/X11GLNative.cs
Expand Up @@ -181,12 +181,13 @@ internal sealed class X11GLNative : NativeWindowBase
EventMask.PropertyChangeMask;
attributes.event_mask = (IntPtr)window.EventMask;

uint mask = (uint)SetWindowValuemask.ColorMap | (uint)SetWindowValuemask.EventMask |
(uint)SetWindowValuemask.BackPixel | (uint)SetWindowValuemask.BorderPixel;
SetWindowValuemask mask =
SetWindowValuemask.ColorMap | SetWindowValuemask.EventMask |
SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel;

window.Handle = Functions.XCreateWindow(window.Display, window.RootWindow,
x, y, width, height, 0, window.VisualInfo.Depth/*(int)CreateWindowArgs.CopyFromParent*/,
(int)CreateWindowArgs.InputOutput, window.VisualInfo.Visual, (UIntPtr)mask, ref attributes);
CreateWindowArgs.InputOutput, window.VisualInfo.Visual, mask, attributes);

if (window.Handle == IntPtr.Zero)
throw new ApplicationException("XCreateWindow call failed (returned 0).");
Expand Down
137 changes: 88 additions & 49 deletions Source/OpenTK/Platform/X11/XI2MouseKeyboard.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -116,6 +116,9 @@ static XI2MouseKeyboard()
//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);
}
}

Expand All @@ -126,16 +129,25 @@ public XI2MouseKeyboard()
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);
}

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 |
Expand All @@ -144,10 +156,9 @@ public XI2MouseKeyboard()
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();
}

Expand Down Expand Up @@ -423,45 +434,52 @@ void ProcessEvents()
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)
Expand Down Expand Up @@ -575,16 +593,29 @@ unsafe static double ReadRawValue(ref XIRawEvent raw, int bit)
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;
}

Expand All @@ -596,6 +627,23 @@ static bool IsBitSet(IntPtr mask, int bit)
}
}

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
Expand All @@ -610,17 +658,8 @@ void Dispose(bool disposing)
{
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();
}
}

Expand Down

0 comments on commit f0fd13a

Please sign in to comment.