From 671457b60af302862528f0ebc2c3d50b6f3ce42d Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Thu, 21 Oct 2010 12:32:00 +0000 Subject: [PATCH] * OpenTK.csproj: * Input/MouseState.cs: * Platform/X11/Structs.cs: * Platform/X11/XI2Mouse.cs: * Platform/X11/Functions.cs: * Platform/X11/X11Factory.cs: Added XInput2 driver for mice. Multi-mouse support pending. * Platform/X11/X11Mouse.cs: Log the driver type for debugging purposes. --- Source/OpenTK/Input/MouseState.cs | 38 +++++ Source/OpenTK/OpenTK.csproj | 1 + Source/OpenTK/Platform/X11/Functions.cs | 34 +++- Source/OpenTK/Platform/X11/Structs.cs | 192 +++++++++++++++++++++++ Source/OpenTK/Platform/X11/X11Factory.cs | 8 + Source/OpenTK/Platform/X11/X11Mouse.cs | 3 + Source/OpenTK/Platform/X11/XI2Mouse.cs | 188 ++++++++++++++++++++++ 7 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 Source/OpenTK/Platform/X11/XI2Mouse.cs diff --git a/Source/OpenTK/Input/MouseState.cs b/Source/OpenTK/Input/MouseState.cs index feae30d5..937833df 100644 --- a/Source/OpenTK/Input/MouseState.cs +++ b/Source/OpenTK/Input/MouseState.cs @@ -207,6 +207,44 @@ namespace OpenTK.Input return !left.Equals(right); } + /// + /// Compares to an object instance for equality. + /// + /// + /// The to compare to. + /// + /// + /// True if this instance is equal to obj; false otherwise. + /// + public override bool Equals(object obj) + { + if (obj is MouseState) + { + return this == (MouseState)obj; + } + else + { + return false; + } + } + + /// + /// Generates a hashcode for the current instance. + /// + /// + /// A represting the hashcode for this instance. + /// + public override int GetHashCode() + { + unsafe + { + fixed (int* b = Buttons) + { + return b->GetHashCode() ^ X.GetHashCode() ^ Y.GetHashCode() ^ WheelPrecise.GetHashCode(); + } + } + } + #endregion #region Internal Members diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 693d87bb..8719568a 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -762,6 +762,7 @@ + diff --git a/Source/OpenTK/Platform/X11/Functions.cs b/Source/OpenTK/Platform/X11/Functions.cs index 88a53e40..68a93386 100644 --- a/Source/OpenTK/Platform/X11/Functions.cs +++ b/Source/OpenTK/Platform/X11/Functions.cs @@ -94,7 +94,16 @@ namespace OpenTK.Platform.X11 public extern static Bool XCheckWindowEvent(Display display, Window w, EventMask event_mask, ref XEvent event_return); [DllImport("libX11")] public extern static Bool XCheckTypedWindowEvent(Display display, Window w, XEventName event_type, ref XEvent event_return); - + [DllImport("libX11")] + public extern static Bool XCheckTypedEvent(Display display, XEventName event_type, out XEvent event_return); + + + 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 ); + [DllImport("libX11")] + public extern static Bool XCheckIfEvent(Display display, ref XEvent e, IntPtr predicate, IntPtr arg ); + [DllImport("libX11")] public extern static int XConnectionNumber(IntPtr diplay); [DllImport("libX11")] @@ -325,7 +334,7 @@ namespace OpenTK.Platform.X11 public extern static int XQueryBestCursor(IntPtr display, IntPtr drawable, int width, int height, out int best_width, out int best_height); [DllImport("libX11", EntryPoint = "XQueryExtension")] - public extern static int XQueryExtension(IntPtr display, string extension_name, ref int major, ref int first_event, ref int first_error); + public extern static int XQueryExtension(IntPtr display, string extension_name, out int major, out int first_event, out int first_error); [DllImport("libX11", EntryPoint = "XWhitePixel")] public extern static IntPtr XWhitePixel(IntPtr display, int screen_no); @@ -462,6 +471,27 @@ namespace OpenTK.Platform.X11 [DllImport("libX11")] public static extern int XRefreshKeyboardMapping(ref XMappingEvent event_map); + [DllImport("libX11")] + public static extern int XGetEventData(IntPtr display, ref XGenericEventCookie cookie); + + [DllImport("libX11")] + public static extern void XFreeEventData(IntPtr display, ref XGenericEventCookie cookie); + + [DllImport("libXi")] + static extern int XISelectEvents(IntPtr dpy, Window win, [In] XIEventMask[] masks, int num_masks); + [DllImport("libXi")] + static extern int XISelectEvents(IntPtr dpy, Window win, [In] ref XIEventMask masks, int num_masks); + + public static int XISelectEvents(IntPtr dpy, Window win, XIEventMask[] masks) + { + return XISelectEvents(dpy, win, masks, masks.Length); + } + + public static int XISelectEvents(IntPtr dpy, Window win, XIEventMask mask) + { + return XISelectEvents(dpy, win, ref mask, 1); + } + static readonly IntPtr CopyFromParent = IntPtr.Zero; public static void SendNetWMMessage(X11WindowInfo window, IntPtr message_type, IntPtr l0, IntPtr l1, IntPtr l2) diff --git a/Source/OpenTK/Platform/X11/Structs.cs b/Source/OpenTK/Platform/X11/Structs.cs index bc7e84a7..180f455d 100644 --- a/Source/OpenTK/Platform/X11/Structs.cs +++ b/Source/OpenTK/Platform/X11/Structs.cs @@ -36,6 +36,10 @@ using System.Runtime.InteropServices; // X11 Version namespace OpenTK.Platform.X11 { + using Bool = System.Boolean; + using Time = System.IntPtr; + using Window = System.IntPtr; + // // In the structures below, fields of type long are mapped to IntPtr. // This will work on all platforms where sizeof(long)==sizeof(void*), which @@ -545,6 +549,29 @@ namespace OpenTK.Platform.X11 public IntPtr pad23; } + [StructLayout(LayoutKind.Sequential)] + internal struct XGenericEvent + { + public int type; // of event. Always GenericEvent + public IntPtr serial; // # of last request processed + public bool send_event; // true if from SendEvent request + public IntPtr display; // Display the event was read from + public int extension; // major opcode of extension that caused the event + public int evtype; // actual event type. + } + + internal struct XGenericEventCookie + { + public int type; // of event. Always GenericEvent + public IntPtr serial; // # of last request processed + public bool send_event; // true if from SendEvent request + public IntPtr display; // Display the event was read from + public int extension; // major opcode of extension that caused the event + public int evtype; // actual event type. + public uint cookie; // unique event cookie + public IntPtr data; // actual event data + } + [StructLayout(LayoutKind.Explicit)] internal struct XEvent { @@ -612,6 +639,10 @@ namespace OpenTK.Platform.X11 public XErrorEvent ErrorEvent; [FieldOffset(0)] public XKeymapEvent KeymapEvent; + [FieldOffset(0)] + public XGenericEvent GenericEvent; + [FieldOffset(0)] + public XGenericEventCookie GenericEventCookie; //[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=24)] //[ FieldOffset(0) ] public int[] pad; @@ -753,6 +784,7 @@ namespace OpenTK.Platform.X11 ColormapNotify = 32, ClientMessage = 33, MappingNotify = 34, + GenericEvent = 35, LASTEvent } @@ -1623,4 +1655,164 @@ namespace OpenTK.Platform.X11 XYPixmap = 1, ZPixmap } + + // XInput2 structures + + struct XIDeviceInfo + { + public int deviceid; + public IntPtr name; // byte* + public int use; + public int attachment; + public Bool enabled; + public int num_classes; + public IntPtr classes; // XIAnyClassInfo ** + } + + struct XIAnyClassInfo + { + public int type; + public int sourceid; + } + + struct XIDeviceEvent + { + public int type; /* GenericEvent */ + public IntPtr serial; /* # of last request processed by server */ + public bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public int extension; /* XI extension offset */ + public XIEventType evtype; + public Time time; + public int deviceid; + public int sourceid; + public int detail; + public Window root; + public Window @event; + public Window child; + public double root_x; + public double root_y; + public double event_x; + public double event_y; + public int flags; + public XIButtonState buttons; + public XIValuatorState valuators; + public XIModifierState mods; + public XIGroupState @group; + } + + struct XIRawEvent + { + public int type; /* GenericEvent */ + public IntPtr serial; /* # of last request processed by server */ + public Bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public int extension; /* XI extension offset */ + public XIEventType evtype; /* XI_RawKeyPress, XI_RawKeyRelease, etc. */ + public Time time; + public int deviceid; + public int sourceid; + public int detail; + public int flags; + public XIValuatorState valuators; + public IntPtr raw_values; // double * + } + + struct XIButtonState + { + public int mask_len; + public IntPtr mask; // byte* + } + + struct XIModifierState + { + public int @base; + public int latched; + public int locked; + public int effective; + } + + struct XIGroupState + { + public int @base; + public int latched; + public int locked; + public int effective; + } + + struct XIValuatorState + { + public int mask_len; + public IntPtr mask; // byte* + public IntPtr values; // double* + } + + struct XIEventMask : IDisposable + { + public int deviceid; // 0 = XIAllDevices, 1 = XIAllMasterDevices + int mask_len; + unsafe XIEventMasks* mask; + + public XIEventMask(int id, XIEventMasks m) + { + deviceid = id; + mask_len = sizeof(XIEventMasks); + unsafe + { + mask = (XIEventMasks*)Marshal.AllocHGlobal(mask_len); + *mask = m; + } + } + + public void Dispose() + { + unsafe + { + Marshal.FreeHGlobal(new IntPtr((void*)mask)); + } + } + } + + enum XIEventType + { + DeviceChanged = 1, + KeyPress, + KeyRelease, + ButtonPress, + ButtonRelease, + Motion, + Enter, + Leave, + FocusIn, + FocusOut, + HierarchyChanged, + PropertyEvent, + RawKeyPress, + RawKeyRelease, + RawButtonPress, + RawButtonRelease, + RawMotion, + LastEvent = RawMotion + } + + enum XIEventMasks + { + DeviceChangedMask = (1 << (int)XIEventType.DeviceChanged), + KeyPressMask = (1 << (int)XIEventType.KeyPress), + KeyReleaseMask = (1 << (int)XIEventType.KeyRelease), + ButtonPressMask = (1 << (int)XIEventType.ButtonPress), + ButtonReleaseMask = (1 << (int)XIEventType.ButtonRelease), + MotionMask = (1 << (int)XIEventType.Motion), + EnterMask = (1 << (int)XIEventType.Enter), + LeaveMask = (1 << (int)XIEventType.Leave), + FocusInMask = (1 << (int)XIEventType.FocusIn), + FocusOutMask = (1 << (int)XIEventType.FocusOut), + HierarchyChangedMask = (1 << (int)XIEventType.HierarchyChanged), + PropertyEventMask = (1 << (int)XIEventType.PropertyEvent), + RawKeyPressMask = (1 << (int)XIEventType.RawKeyPress), + RawKeyReleaseMask = (1 << (int)XIEventType.RawKeyRelease), + RawButtonPressMask = (1 << (int)XIEventType.RawButtonPress), + RawButtonReleaseMask = (1 << (int)XIEventType.RawButtonRelease), + RawMotionMask = (1 << (int)XIEventType.RawMotion), + } } diff --git a/Source/OpenTK/Platform/X11/X11Factory.cs b/Source/OpenTK/Platform/X11/X11Factory.cs index b1212377..96dd6162 100644 --- a/Source/OpenTK/Platform/X11/X11Factory.cs +++ b/Source/OpenTK/Platform/X11/X11Factory.cs @@ -85,6 +85,14 @@ namespace OpenTK.Platform.X11 public virtual OpenTK.Input.IMouseDriver CreateMouseDriver() { + try + { + // Only supported on xorg 1.7 or higher. + return new XI2Mouse(null); + } + catch (NotSupportedException) { } + + // Should always be supported. return new X11Mouse(null); } diff --git a/Source/OpenTK/Platform/X11/X11Mouse.cs b/Source/OpenTK/Platform/X11/X11Mouse.cs index 49c8bedc..4b636a99 100644 --- a/Source/OpenTK/Platform/X11/X11Mouse.cs +++ b/Source/OpenTK/Platform/X11/X11Mouse.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using OpenTK.Input; namespace OpenTK.Platform.X11 @@ -60,6 +61,8 @@ namespace OpenTK.Platform.X11 // IntPtr.Zero, IntPtr.Zero); //Functions.XSelectInput(window.Display, window.RootWindow, new IntPtr((int)window.EventMask)); } + + Debug.WriteLine("Using X11Mouse."); } } diff --git a/Source/OpenTK/Platform/X11/XI2Mouse.cs b/Source/OpenTK/Platform/X11/XI2Mouse.cs new file mode 100644 index 00000000..0451aa27 --- /dev/null +++ b/Source/OpenTK/Platform/X11/XI2Mouse.cs @@ -0,0 +1,188 @@ + #region License + // + // The Open Toolkit Library License + // + // Copyright (c) 2006 - 2010 the Open Toolkit library. + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights to + // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + // the Software, and to permit persons to whom the Software is furnished to do + // so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + // OTHER DEALINGS IN THE SOFTWARE. + // + #endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using OpenTK.Input; + +namespace OpenTK.Platform.X11 +{ + // Todo: multi-mouse support. Right now we aggregate all data into a single mouse device. + // This should be easy: just read the device id and route the data to the correct device. + sealed class XI2Mouse : IMouseDriver + { + MouseState state = new MouseState(); + X11WindowInfo window; + readonly int XIOpCode; + + static readonly Functions.EventPredicate PredicateImpl = IsEventValid; + readonly IntPtr Predicate = Marshal.GetFunctionPointerForDelegate(PredicateImpl); + + // Can either attach itself to the specified window or can hook the root window. + public XI2Mouse(X11WindowInfo win) + { + if (win != null) + { + window = win; + } + else + { + using (new XLock(API.DefaultDisplay)) + { + window = new X11WindowInfo(); + window.Display = API.DefaultDisplay; + window.Screen = Functions.XDefaultScreen(window.Display); + window.RootWindow = Functions.XRootWindow(window.Display, window.Screen); + window.WindowHandle = window.RootWindow; + } + } + + using (new XLock(window.Display)) + { + int major, ev, error; + if (Functions.XQueryExtension(window.Display, "XInputExtension", out major, out ev, out error) == 0) + { + Debug.WriteLine("XInput2 not supported."); + throw new NotSupportedException(); + } + XIOpCode = major; + + using (XIEventMask mask = new XIEventMask(1, XIEventMasks.RawButtonPressMask | + XIEventMasks.RawButtonReleaseMask | XIEventMasks.RawMotionMask)) + { + Functions.XISelectEvents(window.Display, window.WindowHandle, mask); + } + } + Debug.WriteLine("Using XI2Mouse."); + } + + // Todo: remove this + public IList Mouse { get { throw new NotSupportedException(); } } + + public MouseState GetState() + { + ProcessEvents(); + return state; + } + + public MouseState GetState(int index) + { + ProcessEvents(); + return state; + } + + void ProcessEvents() + { + while (true) + { + XEvent e = new XEvent(); + XGenericEventCookie cookie; + + using (new XLock(window.Display)) + { + if (!Functions.XCheckIfEvent(window.Display, ref e, Predicate, new IntPtr(XIOpCode))) + return; + + cookie = e.GenericEventCookie; + if (Functions.XGetEventData(window.Display, ref cookie) != 0) + { + XIRawEvent raw = (XIRawEvent) + Marshal.PtrToStructure(cookie.data, typeof(XIRawEvent)); + + switch (raw.evtype) + { + case XIEventType.RawMotion: + if (IsBitSet(raw.valuators.mask, 0)) + state.X += (int)BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 0)); + if (IsBitSet(raw.valuators.mask, 1)) + state.Y += (int)BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 8)); + break; + + case XIEventType.RawButtonPress: + switch (raw.detail) + { + case 1: state.EnableBit((int)MouseButton.Left); break; + case 2: state.EnableBit((int)MouseButton.Middle); break; + case 3: state.EnableBit((int)MouseButton.Right); break; + case 4: state.WheelPrecise++; break; + case 5: state.WheelPrecise--; break; + case 6: state.EnableBit((int)MouseButton.Button1); break; + case 7: state.EnableBit((int)MouseButton.Button2); break; + case 8: state.EnableBit((int)MouseButton.Button3); break; + case 9: state.EnableBit((int)MouseButton.Button4); break; + case 10: state.EnableBit((int)MouseButton.Button5); break; + case 11: state.EnableBit((int)MouseButton.Button6); break; + case 12: state.EnableBit((int)MouseButton.Button7); break; + case 13: state.EnableBit((int)MouseButton.Button8); break; + case 14: state.EnableBit((int)MouseButton.Button9); break; + } + break; + + case XIEventType.RawButtonRelease: + switch (raw.detail) + { + case 1: state.DisableBit((int)MouseButton.Left); break; + case 2: state.DisableBit((int)MouseButton.Middle); break; + case 3: state.DisableBit((int)MouseButton.Right); break; + case 6: state.DisableBit((int)MouseButton.Button1); break; + case 7: state.DisableBit((int)MouseButton.Button2); break; + case 8: state.DisableBit((int)MouseButton.Button3); break; + case 9: state.DisableBit((int)MouseButton.Button4); break; + case 10: state.DisableBit((int)MouseButton.Button5); break; + case 11: state.DisableBit((int)MouseButton.Button6); break; + case 12: state.DisableBit((int)MouseButton.Button7); break; + case 13: state.DisableBit((int)MouseButton.Button8); break; + case 14: state.DisableBit((int)MouseButton.Button9); break; + } + break; + } + } + Functions.XFreeEventData(window.Display, ref cookie); + } + } + } + + static bool IsEventValid(IntPtr display, ref XEvent e, IntPtr arg) + { + return e.GenericEventCookie.extension == arg.ToInt32() && + (e.GenericEventCookie.evtype == (int)XIEventType.RawMotion || + e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress || + e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease); + } + + static bool IsBitSet(IntPtr mask, int bit) + { + unsafe + { + return (*((byte*)mask + (bit >> 3)) & (1 << (bit & 7))) != 0; + } + } + } +} +