From f4a3cab0e05f6ab94b726f939f3bc0a757a4e696 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Sat, 10 May 2014 01:30:46 +0200 Subject: [PATCH] [Input] Added Mouse.GetCursorPos() (WIP) --- .../Examples/OpenTK/Test/GameWindowStates.cs | 1 + Source/OpenTK/Input/IMouseDriver2.cs | 1 + Source/OpenTK/Input/Mouse.cs | 33 ++- Source/OpenTK/OpenTK.csproj | 3 +- Source/OpenTK/Platform/MacOS/HIDInput.cs | 8 + Source/OpenTK/Platform/SDL2/Sdl2Mouse.cs | 18 ++ Source/OpenTK/Platform/Windows/WinRawMouse.cs | 24 ++ Source/OpenTK/Platform/X11/Bindings/XI.cs | 127 ++++++++ Source/OpenTK/Platform/X11/Functions.cs | 28 -- Source/OpenTK/Platform/X11/Structs.cs | 32 ++- Source/OpenTK/Platform/X11/X11Mouse.cs | 31 +- Source/OpenTK/Platform/X11/XI2Mouse.cs | 270 +++++++++++++----- 12 files changed, 452 insertions(+), 124 deletions(-) create mode 100644 Source/OpenTK/Platform/X11/Bindings/XI.cs diff --git a/Source/Examples/OpenTK/Test/GameWindowStates.cs b/Source/Examples/OpenTK/Test/GameWindowStates.cs index 1977b12c..f0248f41 100644 --- a/Source/Examples/OpenTK/Test/GameWindowStates.cs +++ b/Source/Examples/OpenTK/Test/GameWindowStates.cs @@ -301,6 +301,7 @@ namespace Examples.Tests static int DrawMice(Graphics gfx, int line) { line++; + DrawString(gfx, String.Format("Cursor: {0}", OpenTK.Input.Mouse.GetCursorState()), line++); DrawString(gfx, "Mouse:", line++); for (int i = 0; i < 4; i++) { diff --git a/Source/OpenTK/Input/IMouseDriver2.cs b/Source/OpenTK/Input/IMouseDriver2.cs index cc8ba5b4..45a85fa6 100644 --- a/Source/OpenTK/Input/IMouseDriver2.cs +++ b/Source/OpenTK/Input/IMouseDriver2.cs @@ -36,5 +36,6 @@ namespace OpenTK.Input MouseState GetState(); MouseState GetState(int index); void SetPosition(double x, double y); + MouseState GetCursorState(); } } diff --git a/Source/OpenTK/Input/Mouse.cs b/Source/OpenTK/Input/Mouse.cs index f68d5ff2..6caeb835 100644 --- a/Source/OpenTK/Input/Mouse.cs +++ b/Source/OpenTK/Input/Mouse.cs @@ -48,8 +48,14 @@ namespace OpenTK.Input /// /// Retrieves the combined for all specified mouse devices. + /// The X, Y and wheel values are defined in a hardware-specific coordinate system. + /// Pointer ballistics (acceleration) are NOT applied. Resolution is hardware-specific, + /// typically between 200 and 2000 DPI. + /// Use to retrieve the state of a specific mouse device. + /// Use to retrieve the absolute coordinates of the mouse cursor. + /// Use for event-based mouse input. /// - /// A structure containing the combined state of all mouse devices. + /// A structure representing the combined state of all mouse devices. public static MouseState GetState() { lock (SyncRoot) @@ -60,9 +66,15 @@ namespace OpenTK.Input /// /// Retrieves the for the specified mouse device. + /// The X, Y and wheel values are defined in a hardware-specific coordinate system. + /// Pointer ballistics (acceleration) are NOT applied. Resolution is hardware-specific, + /// typically between 200 and 2000 DPI. + /// Use to retrieve the combined state of all mouse devices. + /// Use to retrieve the absolute coordinates of the mouse cursor. + /// Use for event-based mouse input. /// /// The index of the mouse device. - /// A structure containing the state for the specified mouse device. + /// A structure representing the state for the specified mouse device. public static MouseState GetState(int index) { if (index < 0) @@ -74,6 +86,23 @@ namespace OpenTK.Input } } + /// + /// Retreves the for the mouse cursor. + /// The X and Y coordinates are defined in absolute desktop points, with the origin + /// placed at the top-left corner of . + /// Pointer ballistics (acceleration) are applied. Resolution is limited to the + /// resolution of the containing the cursor, + /// typically between 96 and 120 DPI. + /// + /// A structure representing the state of the mouse cursor. + public static MouseState GetCursorState() + { + lock (SyncRoot) + { + return driver.GetCursorState(); + } + } + /// ///Moves the mouse cursor to the specified screen position. /// diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 2d033d82..eab2002c 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -1,4 +1,4 @@ - + Local @@ -167,6 +167,7 @@ + Code diff --git a/Source/OpenTK/Platform/MacOS/HIDInput.cs b/Source/OpenTK/Platform/MacOS/HIDInput.cs index 28a061c5..ecfa3e3d 100755 --- a/Source/OpenTK/Platform/MacOS/HIDInput.cs +++ b/Source/OpenTK/Platform/MacOS/HIDInput.cs @@ -834,6 +834,14 @@ namespace OpenTK.Platform.MacOS return new MouseState(); } + MouseState IMouseDriver2.GetCursorState() + { + var state = new MouseState(); + state.SetIsConnected(true); + + return state; + } + void IMouseDriver2.SetPosition(double x, double y) { CG.SetLocalEventsSuppressionInterval(0.0); diff --git a/Source/OpenTK/Platform/SDL2/Sdl2Mouse.cs b/Source/OpenTK/Platform/SDL2/Sdl2Mouse.cs index 934096ef..4913c22d 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2Mouse.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2Mouse.cs @@ -119,6 +119,24 @@ namespace OpenTK.Platform.SDL2 return new MouseState(); } + public MouseState GetCursorState() + { + int x, y; + var buttons = SDL.GetMouseState(out x, out y); + + var state = new MouseState(); + state.SetIsConnected(true); + state.X = x; + state.Y = y; + state[MouseButton.Left] = (buttons & ButtonFlags.Left) != 0; + state[MouseButton.Middle] = (buttons & ButtonFlags.Middle) != 0; + state[MouseButton.Right] = (buttons & ButtonFlags.Right) != 0; + state[MouseButton.Button1] = (buttons & ButtonFlags.X1) != 0; + state[MouseButton.Button2] = (buttons & ButtonFlags.X2) != 0; + + return state; + } + public void SetPosition(double x, double y) { SDL.WarpMouseInWindow(IntPtr.Zero, (int)x, (int)y); diff --git a/Source/OpenTK/Platform/Windows/WinRawMouse.cs b/Source/OpenTK/Platform/Windows/WinRawMouse.cs index c904ea1f..3b1fd9d4 100644 --- a/Source/OpenTK/Platform/Windows/WinRawMouse.cs +++ b/Source/OpenTK/Platform/Windows/WinRawMouse.cs @@ -348,6 +348,30 @@ namespace OpenTK.Platform.Windows Functions.SetCursorPos((int)x, (int)y); } + public MouseState GetCursorState() + { + var state = new MouseState(); + state.SetIsConnected(true); + + POINT p = new POINT(); + Functions.GetCursorPos(ref p); + bool left = Functions.GetKeyState(VirtualKeys.LBUTTON) != 0; + bool right = Functions.GetKeyState(VirtualKeys.RBUTTON) != 0; + bool middle = Functions.GetKeyState(VirtualKeys.MBUTTON) != 0; + bool x1 = Functions.GetKeyState(VirtualKeys.XBUTTON1) != 0; + bool x2 = Functions.GetKeyState(VirtualKeys.XBUTTON2) != 0; + + state.X = p.X; + state.Y = p.Y; + state[MouseButton.Left] = left; + state[MouseButton.Right] = right; + state[MouseButton.Middle] = middle; + state[MouseButton.Button1] = x1; + state[MouseButton.Button2] = x2; + + return state; + } + #endregion } } diff --git a/Source/OpenTK/Platform/X11/Bindings/XI.cs b/Source/OpenTK/Platform/X11/Bindings/XI.cs new file mode 100644 index 00000000..418acfea --- /dev/null +++ b/Source/OpenTK/Platform/X11/Bindings/XI.cs @@ -0,0 +1,127 @@ +#region License +// +// The Open Toolkit Library License +// +// Copyright (c) 2006 - 2014 Stefanos Apostolopoulos for 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.Runtime.InteropServices; +using System.Text; + +namespace OpenTK.Platform.X11 +{ + using Bool = Boolean; + using Cursor = IntPtr; + using Display = IntPtr; + using Status = ErrorCodes; + using Time = IntPtr; + using Window = IntPtr; + + // Bindings for the XInput2 extension + class XI + { + const string lib = "libXi"; + + // mouse + internal static readonly IntPtr ButtonLeft = Functions.XInternAtom(API.DefaultDisplay, "Button Left", false); + internal static readonly IntPtr ButtonMiddle = Functions.XInternAtom(API.DefaultDisplay, "Button Middle", false); + internal static readonly IntPtr ButtonRight = Functions.XInternAtom(API.DefaultDisplay, "Button Right", false); + internal static readonly IntPtr ButtonWheelUp = Functions.XInternAtom(API.DefaultDisplay, "Button Wheel Up", false); + internal static readonly IntPtr ButtonWheelDown = Functions.XInternAtom(API.DefaultDisplay, "Button Wheel Down", false); + internal static readonly IntPtr ButtonWheelLeft = Functions.XInternAtom(API.DefaultDisplay, "Button Horiz Wheel Left", false); + internal static readonly IntPtr ButtonWheelRight = Functions.XInternAtom(API.DefaultDisplay, "Button Horiz Wheel Right", false); + internal static readonly IntPtr RelativeX = Functions.XInternAtom(API.DefaultDisplay, "Rel X", false); + internal static readonly IntPtr RelativeY = Functions.XInternAtom(API.DefaultDisplay, "Rel Y", false); + internal static readonly IntPtr RelativeHWheel = Functions.XInternAtom(API.DefaultDisplay, "Rel Horiz Wheel", false); + internal static readonly IntPtr RelativeVWheel = Functions.XInternAtom(API.DefaultDisplay, "Rel Vert Wheel", false); + internal static readonly IntPtr RelativeHScroll = Functions.XInternAtom(API.DefaultDisplay, "Rel Horiz Scroll", false); + internal static readonly IntPtr RelativeVScroll = Functions.XInternAtom(API.DefaultDisplay, "Rel Vert Scroll", false); + + // multitouch + internal static readonly IntPtr TouchX = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Position X", false); + internal static readonly IntPtr TouchY = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Position Y", false); + internal static readonly IntPtr TouchMajor = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Touch Major", false); + internal static readonly IntPtr TouchMinor = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Touch Minor", false); + internal static readonly IntPtr TouchPressure = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Pressure", false); + internal static readonly IntPtr TouchId = Functions.XInternAtom(API.DefaultDisplay, "Abs MT Tracking ID", false); + internal static readonly IntPtr TouchMaxContacts = Functions.XInternAtom(API.DefaultDisplay, "Max Contacts", false); + + // tablet + internal static readonly IntPtr AbsoluteX = Functions.XInternAtom(API.DefaultDisplay, "Abs X", false); + internal static readonly IntPtr AbsoluteY = Functions.XInternAtom(API.DefaultDisplay, "Abs Y", false); + internal static readonly IntPtr AbsolutePressure = Functions.XInternAtom(API.DefaultDisplay, "Abs Pressure", false); + internal static readonly IntPtr AbsoluteTiltX = Functions.XInternAtom(API.DefaultDisplay, "Abs Tilt X", false); + internal static readonly IntPtr AbsoluteTiltY = Functions.XInternAtom(API.DefaultDisplay, "Abs Tilt Y", false); + internal static readonly IntPtr AbsoluteWheel = Functions.XInternAtom(API.DefaultDisplay, "Abs Wheel", false); + internal static readonly IntPtr AbsoluteDistance = Functions.XInternAtom(API.DefaultDisplay, "Abs Distance", false); + + [DllImport(lib, EntryPoint = "XISelectEvents")] + static extern int SelectEvents(IntPtr dpy, Window win, [In] XIEventMask[] masks, int num_masks); + + [DllImport(lib, EntryPoint = "XISelectEvents")] + static extern int SelectEvents(IntPtr dpy, Window win, [In] ref XIEventMask masks, int num_masks); + + public static int SelectEvents(IntPtr dpy, Window win, XIEventMask[] masks) + { + return SelectEvents(dpy, win, masks, masks.Length); + } + + public static int SelectEvents(IntPtr dpy, Window win, XIEventMask mask) + { + return SelectEvents(dpy, win, ref mask, 1); + } + + [DllImport(lib, EntryPoint = "XIGrabDevice")] + static extern Status GrabDevice(IntPtr display, int deviceid, Window grab_window, Time time, + Cursor cursor, int grab_mode, int paired_device_mode, Bool owner_events, XIEventMask[] mask); + + [DllImport(lib, EntryPoint = "XIUngrabDevice")] + static extern Status UngrabDevice(IntPtr display, int deviceid, Time time); + + [DllImport(lib, EntryPoint = "XIWarpPointer")] + public static extern Bool XIWarpPointer(Display display, + int deviceid, Window src_w, Window dest_w, + double src_x, double src_y, int src_width, int src_height, + double dest_x, double dest_y); + + [DllImport(lib, EntryPoint = "XIQueryDevice")] + public static extern IntPtr QueryDevice(Display display, int id, out int count); + + [DllImport(lib, EntryPoint = "XIFreeDeviceInfo")] + public static extern void FreeDeviceInfo(IntPtr devices); + + [DllImport(lib, EntryPoint = "XIQueryPointer")] + public static extern Bool QueryPointer(Display display, + int deviceid, Window win, + out Window root_return, out Window child_return, + out double root_x_return, out double root_y_return, + out double win_x_return, out double win_y_return, + out XIButtonState buttons_return, out XIModifierState modifiers_return, + out XIGroupState group_return); + + [DllImport(lib, EntryPoint = "XIQueryVersion")] + internal static extern Status QueryVersion(Display display, ref int major, ref int minor); + } +} diff --git a/Source/OpenTK/Platform/X11/Functions.cs b/Source/OpenTK/Platform/X11/Functions.cs index 6fae04fa..4198a87a 100644 --- a/Source/OpenTK/Platform/X11/Functions.cs +++ b/Source/OpenTK/Platform/X11/Functions.cs @@ -516,34 +516,6 @@ namespace OpenTK.Platform.X11 [DllImport("libX11")] public static extern void XSetClassHint(IntPtr display, IntPtr window, ref XClassHint hint); - [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); - } - - [DllImport("libXi")] - static extern Status XIGrabDevice(IntPtr display, int deviceid, Window grab_window, Time time, - Cursor cursor, int grab_mode, int paired_device_mode, Bool owner_events, XIEventMask[] mask); - - [DllImport("libXi")] - static extern Status XIUngrabDevice(IntPtr display, int deviceid, Time time); - - [DllImport("libXi")] - public static extern Bool XIWarpPointer(Display display, - int deviceid, Window src_w, Window dest_w, - double src_x, double src_y, int src_width, int src_height, - double dest_x, double dest_y); - 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 68622733..91b25de8 100644 --- a/Source/OpenTK/Platform/X11/Structs.cs +++ b/Source/OpenTK/Platform/X11/Structs.cs @@ -1671,6 +1671,13 @@ namespace OpenTK.Platform.X11 // XInput2 structures + enum XIClassType + { + Button = 1, + Valuator = 2, + Scroll = 3, + } + struct XIDeviceInfo { public int deviceid; @@ -1684,10 +1691,33 @@ namespace OpenTK.Platform.X11 struct XIAnyClassInfo { - public int type; + public XIClassType type; public int sourceid; } + struct XIButtonClassInfo + { + } + + [StructLayout(LayoutKind.Sequential)] + struct XIValuatorClassInfo + { + public Atom label; + public double max; + public double min; + public int mode; + public int number; + public int resolution; + public int sourceid; + public int type; + public double value; + } + + struct XIScrollClassInfo + { + + } + struct XIDeviceEvent { public int type; /* GenericEvent */ diff --git a/Source/OpenTK/Platform/X11/X11Mouse.cs b/Source/OpenTK/Platform/X11/X11Mouse.cs index 0dc5879d..6a137b26 100644 --- a/Source/OpenTK/Platform/X11/X11Mouse.cs +++ b/Source/OpenTK/Platform/X11/X11Mouse.cs @@ -48,6 +48,7 @@ namespace OpenTK.Platform.X11 readonly IntPtr display; readonly IntPtr root_window; MouseState mouse = new MouseState(); + MouseState cursor = new MouseState(); // When the mouse warps, "detach" the current location // from the pointer. @@ -58,6 +59,7 @@ namespace OpenTK.Platform.X11 { Debug.WriteLine("Using X11Mouse."); mouse.IsConnected = true; + cursor.IsConnected = true; display = API.DefaultDisplay; root_window = Functions.XRootWindow(display, Functions.XDefaultScreen(display)); } @@ -78,6 +80,12 @@ namespace OpenTK.Platform.X11 return new MouseState(); } + public MouseState GetCursorState() + { + ProcessEvents(); + return cursor; + } + public void SetPosition(double x, double y) { // Update the current location, otherwise the pointer @@ -97,14 +105,6 @@ namespace OpenTK.Platform.X11 } } - void WriteBit(MouseButton offset, int enabled) - { - if (enabled != 0) - mouse.EnableBit((int)offset); - else - mouse.DisableBit((int)offset); - } - void ProcessEvents() { IntPtr root, child; @@ -117,6 +117,9 @@ namespace OpenTK.Platform.X11 Functions.XQueryPointer(display, window, out root, out child, out root_x, out root_y, out win_x, out win_y, out buttons); + cursor.X = root_x; + cursor.Y = root_y; + if (!mouse_detached) { mouse.X = root_x; @@ -129,9 +132,9 @@ namespace OpenTK.Platform.X11 mouse_detached_x = root_x; mouse_detached_y = root_y; } - WriteBit(MouseButton.Left, buttons & (int)MouseMask.Button1Mask); - WriteBit(MouseButton.Middle, buttons & (int)MouseMask.Button2Mask); - WriteBit(MouseButton.Right, buttons & (int)MouseMask.Button3Mask); + cursor[MouseButton.Left] = mouse[MouseButton.Left] = (buttons & (int)MouseMask.Button1Mask) != 0; + cursor[MouseButton.Middle] = mouse[MouseButton.Middle] = (buttons & (int)MouseMask.Button2Mask) != 0; + cursor[MouseButton.Right] = mouse[MouseButton.Right] = (buttons & (int)MouseMask.Button3Mask) != 0; // Note: this will never work right, wheel events have a duration of 0 // (yes, zero). They are impposible to catch via polling. // After spending a week on this, I simply don't care anymore. @@ -142,9 +145,9 @@ namespace OpenTK.Platform.X11 // mouse.WheelPrecise++; //if ((buttons & (int)MouseMask.Button5Mask) != 0) // mouse.WheelPrecise--; - WriteBit(MouseButton.Button1, buttons & (int)MouseMask.Button6Mask); - WriteBit(MouseButton.Button2, buttons & (int)MouseMask.Button7Mask); - WriteBit(MouseButton.Button3, buttons & (int)MouseMask.Button8Mask); + cursor[MouseButton.Button1] = mouse[MouseButton.Button1] = (buttons & (int)MouseMask.Button6Mask) != 0; + cursor[MouseButton.Button2] = mouse[MouseButton.Button2] = (buttons & (int)MouseMask.Button7Mask) != 0; + cursor[MouseButton.Button3] = mouse[MouseButton.Button3] = (buttons & (int)MouseMask.Button8Mask) != 0; } } } diff --git a/Source/OpenTK/Platform/X11/XI2Mouse.cs b/Source/OpenTK/Platform/X11/XI2Mouse.cs index 893783b0..9b860ea0 100644 --- a/Source/OpenTK/Platform/X11/XI2Mouse.cs +++ b/Source/OpenTK/Platform/X11/XI2Mouse.cs @@ -37,8 +37,15 @@ namespace OpenTK.Platform.X11 // This should be easy: just read the device id and route the data to the correct device. sealed class XI2Mouse : IMouseDriver2 { - List mice = new List(); - Dictionary rawids = new Dictionary(); // maps raw ids to mouse ids + class XIMouse + { + public MouseState State; + public XIDeviceInfo Info; + } + XIMouse master; // XIMouse for the mouse cursor + List devices = new List(); // List of connected mice + Dictionary rawids = new Dictionary(); // maps hardware device ids to XIMouse ids + internal readonly X11WindowInfo window; internal static int XIOpCode { get; private set; } static bool supported; @@ -72,12 +79,20 @@ namespace OpenTK.Platform.X11 if (!IsSupported(window.Display)) throw new NotSupportedException("XInput2 not supported."); - using (XIEventMask mask = new XIEventMask(1, XIEventMasks.RawButtonPressMask | - XIEventMasks.RawButtonReleaseMask | XIEventMasks.RawMotionMask)) + using (new XLock(API.DefaultDisplay)) + using (XIEventMask mask = new XIEventMask(1, + XIEventMasks.RawButtonPressMask | + XIEventMasks.RawButtonReleaseMask | + XIEventMasks.RawMotionMask | + XIEventMasks.MotionMask | + XIEventMasks.ButtonPressMask | + XIEventMasks.ButtonReleaseMask | + XIEventMasks.DeviceChangedMask)) { - Functions.XISelectEvents(window.Display, window.Handle, mask); - Functions.XISelectEvents(window.Display, window.RootWindow, mask); + XI.SelectEvents(window.Display, window.Handle, mask); } + + UpdateDevices(); } // Checks whether XInput2 is supported on the specified display. @@ -90,14 +105,51 @@ namespace OpenTK.Platform.X11 using (new XLock(display)) { int major, ev, error; - if (Functions.XQueryExtension(display, "XInputExtension", out major, out ev, out error) == 0) + if (Functions.XQueryExtension(display, "XInputExtension", out major, out ev, out error) != 0) { - return false; + XIOpCode = major; + + int minor = 2; + while (minor >= 0) + { + if (XI.QueryVersion(display, ref major, ref minor) == ErrorCodes.Success) + { + return true; + } + minor--; + } } - XIOpCode = major; } - return true; + return false; + } + + void UpdateDevices() + { + int count; + unsafe + { + XIDeviceInfo* list = (XIDeviceInfo*)XI.QueryDevice(window.Display, 1, out count); + for (int i = 0; i < count; i++) + { + if (devices.Count < i) + { + devices.Add(new XIMouse()); + } + XIMouse d = devices[i]; + d.State.SetIsConnected(true); + d.Info = *(list + i); + + // Map the hardware device id to the current XIMouse id + if (!rawids.ContainsKey(d.Info.deviceid)) + { + rawids.Add(d.Info.deviceid, 0); + } + rawids[d.Info.deviceid] = i; + } + XI.FreeDeviceInfo((IntPtr)list); + } + } #region IMouseDriver2 Members @@ -106,9 +158,9 @@ namespace OpenTK.Platform.X11 { ProcessEvents(); MouseState master = new MouseState(); - foreach (MouseState ms in mice) + foreach (var d in devices) { - master.MergeBits(ms); + master.MergeBits(d.State); } return master; } @@ -116,12 +168,17 @@ namespace OpenTK.Platform.X11 public MouseState GetState(int index) { ProcessEvents(); - if (mice.Count > index) - return mice[index]; + if (devices.Count > index) + return devices[index].State; else return new MouseState(); } + public MouseState GetCursorState() + { + return master.State; + } + public void SetPosition(double x, double y) { using (new XLock(window.Display)) @@ -169,79 +226,133 @@ namespace OpenTK.Platform.X11 cookie = e.GenericEventCookie; if (Functions.XGetEventData(window.Display, ref cookie) != 0) { - XIRawEvent raw = (XIRawEvent) - Marshal.PtrToStructure(cookie.data, typeof(XIRawEvent)); - - if (!rawids.ContainsKey(raw.deviceid)) - { - mice.Add(new MouseState()); - rawids.Add(raw.deviceid, mice.Count - 1); - } - MouseState state = mice[rawids[raw.deviceid]]; - - switch (raw.evtype) + switch ((XIEventType)cookie.evtype) { case XIEventType.RawMotion: - double x = 0, y = 0; - if (IsBitSet(raw.valuators.mask, 0)) - { - x = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 0)); - } - if (IsBitSet(raw.valuators.mask, 1)) - { - y = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 8)); - } - - if (!CheckMouseWarp(x, y)) - { - state.X += (int)x; - state.Y += (int)y; - } - 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.SetScrollRelative(0, 1); break; - case 5: state.SetScrollRelative(0, -1); 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; - } + case XIEventType.RawButtonRelease: + // Delivered to all XIMouse instances + ProcessRawEvent(ref cookie); 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; - } + case XIEventType.Motion: + case XIEventType.ButtonPress: + case XIEventType.ButtonRelease: + // Delivered only to the actual mouse cursor XIMouse instance + ProcessEvent(ref cookie); + break; + + case XIEventType.DeviceChanged: + UpdateDevices(); break; } - mice[rawids[raw.deviceid]] = state; } Functions.XFreeEventData(window.Display, ref cookie); } - } + } + } + + void ProcessEvent(ref XGenericEventCookie cookie) + { + XIDeviceEvent e = (XIDeviceEvent) + Marshal.PtrToStructure(cookie.data, typeof(XIDeviceEvent)); + + master.State.SetIsConnected(true); + master.State.X = (int)Math.Round(e.root_x); + master.State.Y = (int)Math.Round(e.root_y); + + } + + void ProcessRawEvent(ref XGenericEventCookie cookie) + { + XIRawEvent raw = (XIRawEvent) + Marshal.PtrToStructure(cookie.data, typeof(XIRawEvent)); + + if (!rawids.ContainsKey(raw.deviceid)) + { + Debug.Print("Unknown mouse device {0} encountered, ignoring.", raw.deviceid); + return; + } + + var d = devices[rawids[raw.deviceid]]; + + switch (raw.evtype) + { + case XIEventType.RawMotion: + double x = 0, y = 0; + double h = 0, v = 0; + for (int i = 0; i < d.Info.num_classes; i++) + { + unsafe + { + XIAnyClassInfo* info = (XIAnyClassInfo*)d.Info.classes + i; + switch (info->type) + { + case XIClassType.Valuator: + { + XIValuatorClassInfo* n = (XIValuatorClassInfo*)info; + } + break; + } + } + } + + if (IsBitSet(raw.valuators.mask, 0)) + { + x = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 0)); + } + if (IsBitSet(raw.valuators.mask, 1)) + { + y = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 8)); + } + break; + /* + if (!CheckMouseWarp(x, y)) + { + state.X += (int)x; + state.Y += (int)y; + } + 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.SetScrollRelative(0, 1); break; + case 5: state.SetScrollRelative(0, -1); 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;*/ + } + //mice[rawids[raw.deviceid]] = state; } static bool IsEventValid(IntPtr display, ref XEvent e, IntPtr arg) @@ -249,7 +360,10 @@ namespace OpenTK.Platform.X11 return e.GenericEventCookie.extension == arg.ToInt32() && (e.GenericEventCookie.evtype == (int)XIEventType.RawMotion || e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress || - e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease); + e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease || + e.GenericEventCookie.evtype == (int)XIEventType.Motion || + e.GenericEventCookie.evtype == (int)XIEventType.ButtonPress || + e.GenericEventCookie.evtype == (int)XIEventType.ButtonRelease); } static bool IsBitSet(IntPtr mask, int bit)