diff --git a/Source/OpenTK/Input/MouseScrollWheel.cs b/Source/OpenTK/Input/MouseScroll.cs similarity index 70% rename from Source/OpenTK/Input/MouseScrollWheel.cs rename to Source/OpenTK/Input/MouseScroll.cs index 2be06b67..c9f024f8 100644 --- a/Source/OpenTK/Input/MouseScrollWheel.cs +++ b/Source/OpenTK/Input/MouseScroll.cs @@ -34,7 +34,7 @@ namespace OpenTK.Input /// /// Represents the state of a mouse wheel. /// - public struct MouseScrollWheel : IEquatable + public struct MouseScroll : IEquatable { #region Public Members @@ -52,31 +52,31 @@ namespace OpenTK.Input /// The y. public float Y { get; internal set; } - /// A instance to test for equality. - /// A instance to test for equality. - public static bool operator ==(MouseScrollWheel left, MouseScrollWheel right) + /// A instance to test for equality. + /// A instance to test for equality. + public static bool operator ==(MouseScroll left, MouseScroll right) { return left.Equals(right); } - /// A instance to test for inequality. - /// A instance to test for inequality. - public static bool operator !=(MouseScrollWheel left, MouseScrollWheel right) + /// A instance to test for inequality. + /// A instance to test for inequality. + public static bool operator !=(MouseScroll left, MouseScroll right) { return !left.Equals(right); } /// - /// Returns a that represents the current . + /// Returns a that represents the current . /// - /// A that represents the current . + /// A that represents the current . public override string ToString() { return string.Format("[X={0:0.00}, Y={1:0.00}]", X, Y); } /// - /// Serves as a hash function for a object. + /// Serves as a hash function for a object. /// /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. @@ -86,16 +86,16 @@ namespace OpenTK.Input } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . + /// The to compare with the current . /// true if the specified is equal to the current - /// ; otherwise, false. + /// ; otherwise, false. public override bool Equals(object obj) { return - obj is MouseScrollWheel && - Equals((MouseScrollWheel)obj); + obj is MouseScroll && + Equals((MouseScroll)obj); } #endregion @@ -103,12 +103,12 @@ namespace OpenTK.Input #region IEquatable Members /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// true if the specified is equal to the current - /// ; otherwise, false. - public bool Equals(MouseScrollWheel other) + /// The to compare with the current . + /// true if the specified is equal to the current + /// ; otherwise, false. + public bool Equals(MouseScroll other) { return X == other.X && Y == other.Y; } diff --git a/Source/OpenTK/Input/MouseState.cs b/Source/OpenTK/Input/MouseState.cs index a07567c0..3a74a0f3 100644 --- a/Source/OpenTK/Input/MouseState.cs +++ b/Source/OpenTK/Input/MouseState.cs @@ -39,7 +39,7 @@ namespace OpenTK.Input #region Fields int x, y; - MouseScrollWheel scroll; + MouseScroll scroll; ushort buttons; bool is_connected; @@ -104,7 +104,7 @@ namespace OpenTK.Input /// Gets a instance, /// representing the current state of the mouse scroll wheel. /// - public MouseScrollWheel Scroll + public MouseScroll Scroll { get { return scroll; } } diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 333f516c..502877fb 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -790,7 +790,6 @@ - @@ -799,6 +798,7 @@ Code + diff --git a/Source/OpenTK/Platform/NativeWindowBase.cs b/Source/OpenTK/Platform/NativeWindowBase.cs index 97365f9b..90e53a81 100644 --- a/Source/OpenTK/Platform/NativeWindowBase.cs +++ b/Source/OpenTK/Platform/NativeWindowBase.cs @@ -232,6 +232,8 @@ namespace OpenTK.Platform MouseState[button] = true; var e = MouseDownArgs; + e.Button = button; + e.IsPressed = true; e.Mouse = MouseState; MouseDown(this, e); @@ -242,6 +244,8 @@ namespace OpenTK.Platform MouseState[button] = false; var e = MouseUpArgs; + e.Button = button; + e.IsPressed = false; e.Mouse = MouseState; MouseUp(this, e); diff --git a/Source/OpenTK/Platform/X11/X11GLNative.cs b/Source/OpenTK/Platform/X11/X11GLNative.cs index 13b429d7..e2cd6e24 100644 --- a/Source/OpenTK/Platform/X11/X11GLNative.cs +++ b/Source/OpenTK/Platform/X11/X11GLNative.cs @@ -91,11 +91,13 @@ namespace OpenTK.Platform.X11 //IntPtr _atom_motif_wm_hints; //IntPtr _atom_kde_wm_hints; //IntPtr _atom_kde_net_wm_hints; - + static readonly IntPtr _atom_remove = (IntPtr)0; static readonly IntPtr _atom_add = (IntPtr)1; static readonly IntPtr _atom_toggle = (IntPtr)2; - + + // Used by OpenTK to detect mouse warp events + Rectangle bounds, client_rectangle; int border_left, border_right, border_top, border_bottom; Icon icon; @@ -121,7 +123,6 @@ namespace OpenTK.Platform.X11 MouseCursor cursor = MouseCursor.Default; IntPtr cursorHandle; bool cursor_visible = true; - int mouse_rel_x, mouse_rel_y; // Keyboard input readonly byte[] ascii = new byte[16]; @@ -129,10 +130,9 @@ namespace OpenTK.Platform.X11 readonly IntPtr EmptyCursor; - public static bool MouseWarpActive = false; - readonly bool xi2_supported; readonly int xi2_opcode; + readonly int xi2_version; #endregion @@ -244,6 +244,7 @@ namespace OpenTK.Platform.X11 if (xi2_supported) { xi2_opcode = XI2Mouse.XIOpCode; + xi2_version = XI2Mouse.XIVersion; } exists = true; @@ -905,63 +906,43 @@ namespace OpenTK.Platform.X11 case XEventName.MotionNotify: { - // Try to detect and ignore events from XWarpPointer, below. - // This heuristic will fail if the user actually moves the pointer - // to the dead center of the window. Fortunately, this situation - // is very very uncommon. Todo: Can this be remedied? int x = e.MotionEvent.x; int y = e.MotionEvent.y; - // TODO: Have offset as a stored field, only update it when the window moves - // The middle point cannot be the average of the Bounds.left/right/top/bottom, - // because these fields take into account window decoration (borders, etc), - // which we do not want to account for. - Point offset = this.PointToClient(Point.Empty); - int middle_x = Width/2-offset.X; - int middle_y = Height/2-offset.Y; - Point screen_xy = PointToScreen(new Point(x, y)); - if (!CursorVisible && MouseWarpActive && - screen_xy.X == middle_x && screen_xy.Y == middle_y) - { - MouseWarpActive = false; - mouse_rel_x = x; - mouse_rel_y = y; - } - else if (!CursorVisible) - { - OnMouseMove( - MathHelper.Clamp(MouseState.X + x - mouse_rel_x, 0, Width), - MathHelper.Clamp(MouseState.Y + y - mouse_rel_y, 0, Height)); - mouse_rel_x = x; - mouse_rel_y = y; - - // Warp cursor to center of window. - MouseWarpActive = true; - Mouse.SetPosition(middle_x, middle_y); - } - else + if (x != 0 || y != 0) { OnMouseMove( MathHelper.Clamp(x, 0, Width), MathHelper.Clamp(y, 0, Height)); - mouse_rel_x = x; - mouse_rel_y = y; } - break; } case XEventName.ButtonPress: { - int dx, dy; + float dx, dy; MouseButton button = X11KeyMap.TranslateButton(e.ButtonEvent.button, out dx, out dy); if (button != MouseButton.LastButton) { OnMouseDown(button); } - else if (dx != 0 || dy != 0) + + if (xi2_version >= 210) { + // High resolution scroll events supported. + // This code is implemented in XI2Mouse.GetCursorState(). + // Instead of reimplementing this functionality, just + // use the values from there. + MouseState state = Mouse.GetCursorState(); + dx = state.Scroll.X - MouseState.Scroll.X; + dy = state.Scroll.Y - MouseState.Scroll.Y; + } + + if (dx != 0 || dy != 0) + { + // High resolution scroll events not supported + // fallback to the old Button4-7 scroll buttons OnMouseWheel(dx, dy); } } @@ -969,9 +950,8 @@ namespace OpenTK.Platform.X11 case XEventName.ButtonRelease: { - int dx, dy; + float dx, dy; MouseButton button = X11KeyMap.TranslateButton(e.ButtonEvent.button, out dx, out dy); - if (button != MouseButton.LastButton) { OnMouseUp(button); @@ -985,6 +965,11 @@ namespace OpenTK.Platform.X11 has_focus = true; if (has_focus != previous_focus) OnFocusedChanged(EventArgs.Empty); + + if (Focused && !CursorVisible) + { + GrabMouse(); + } } break; @@ -1000,6 +985,12 @@ namespace OpenTK.Platform.X11 case XEventName.LeaveNotify: if (CursorVisible) { + int x = MathHelper.Clamp(e.CrossingEvent.x, 0, Width); + int y = MathHelper.Clamp(e.CrossingEvent.y, 0, Height); + if (x != MouseState.X || y != MouseState.Y) + { + OnMouseMove(x, y); + } OnMouseLeave(EventArgs.Empty); } break; @@ -1028,7 +1019,7 @@ namespace OpenTK.Platform.X11 // RefreshWindowBorders(); //} break; - + default: //Debug.WriteLine(String.Format("{0} event was not handled", e.type)); break; @@ -1441,11 +1432,18 @@ namespace OpenTK.Platform.X11 { unsafe { + if (value == cursor) + return; + using (new XLock(window.Display)) { if (value == MouseCursor.Default) { - Functions.XUndefineCursor(window.Display, window.Handle); + cursorHandle = IntPtr.Zero; + } + else if (value == MouseCursor.Empty) + { + cursorHandle = EmptyCursor; } else { @@ -1457,10 +1455,17 @@ namespace OpenTK.Platform.X11 xcursorimage->pixels = (uint*)pixels; xcursorimage->delay = 0; cursorHandle = Functions.XcursorImageLoadCursor(window.Display, xcursorimage); - Functions.XDefineCursor(window.Display, window.Handle, cursorHandle); Functions.XcursorImageDestroy(xcursorimage); } } + + // If the cursor is visible set it now. + // Otherwise, it will be set in CursorVisible = true. + if (CursorVisible) + { + Functions.XDefineCursor(window.Display, window.Handle, cursorHandle); + } + cursor = value; } } @@ -1480,6 +1485,13 @@ namespace OpenTK.Platform.X11 { using (new XLock(window.Display)) { + UngrabMouse(); + + Point p = PointToScreen(new Point(MouseState.X, MouseState.Y)); + Mouse.SetPosition(p.X, p.Y); + + // Note: if cursorHandle = IntPtr.Zero, this restores the default cursor + // (equivalent to calling XUndefineCursor) Functions.XDefineCursor(window.Display, window.Handle, cursorHandle); cursor_visible = true; } @@ -1488,13 +1500,27 @@ namespace OpenTK.Platform.X11 { using (new XLock(window.Display)) { - Functions.XDefineCursor(window.Display, window.Handle, EmptyCursor); + GrabMouse(); cursor_visible = false; } } } } + void GrabMouse() + { + Functions.XGrabPointer(window.Display, window.Handle, false, + EventMask.PointerMotionMask | EventMask.ButtonPressMask | + EventMask.ButtonReleaseMask, + GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, + window.Handle, EmptyCursor, IntPtr.Zero); + } + + void UngrabMouse() + { + Functions.XUngrabPointer(window.Display, IntPtr.Zero); + } + #endregion #endregion diff --git a/Source/OpenTK/Platform/X11/X11KeyMap.cs b/Source/OpenTK/Platform/X11/X11KeyMap.cs index 29e96935..216671e8 100644 --- a/Source/OpenTK/Platform/X11/X11KeyMap.cs +++ b/Source/OpenTK/Platform/X11/X11KeyMap.cs @@ -388,7 +388,7 @@ namespace OpenTK.Platform.X11 return key != Key.Unknown; } - internal static MouseButton TranslateButton(int button, out int wheelx, out int wheely) + internal static MouseButton TranslateButton(int button, out float wheelx, out float wheely) { wheelx = 0; wheely = 0; diff --git a/Source/OpenTK/Platform/X11/XI2Mouse.cs b/Source/OpenTK/Platform/X11/XI2Mouse.cs index 90422273..ff6cffe7 100644 --- a/Source/OpenTK/Platform/X11/XI2Mouse.cs +++ b/Source/OpenTK/Platform/X11/XI2Mouse.cs @@ -34,8 +34,6 @@ 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 : IMouseDriver2, IDisposable { const XEventName ExitEvent = XEventName.LASTEvent + 1; @@ -73,20 +71,11 @@ namespace OpenTK.Platform.X11 internal readonly X11WindowInfo window; internal static int XIOpCode { get; private set; } + internal static int XIVersion { get; private set; } static readonly Functions.EventPredicate PredicateImpl = IsEventValid; readonly IntPtr Predicate = Marshal.GetFunctionPointerForDelegate(PredicateImpl); - // Store information on a mouse warp event, so it can be ignored. - struct MouseWarp : IEquatable - { - public MouseWarp(double x, double y) { X = x; Y = y; } - double X, Y; - public bool Equals(MouseWarp warp) { return X == warp.X && Y == warp.Y; } - } - MouseWarp? mouse_warp_event; - int mouse_warp_event_count; - static XI2Mouse() { using (new XLock(API.DefaultDisplay)) @@ -138,6 +127,7 @@ namespace OpenTK.Platform.X11 XIEventMasks.RawButtonPressMask | XIEventMasks.RawButtonReleaseMask | XIEventMasks.RawMotionMask | + XIEventMasks.MotionMask | XIEventMasks.DeviceChangedMask | (XIEventMasks)(1 << (int)ExitEvent))) { @@ -155,29 +145,38 @@ namespace OpenTK.Platform.X11 // If a display is not specified, the default display is used. internal static bool IsSupported(IntPtr display) { - if (display == IntPtr.Zero) + try { - display = API.DefaultDisplay; - } - - using (new XLock(display)) - { - int major, ev, error; - if (Functions.XQueryExtension(display, "XInputExtension", out major, out ev, out error) != 0) + if (display == IntPtr.Zero) { - XIOpCode = major; + display = API.DefaultDisplay; + } - int minor = 2; - while (minor >= 0) + using (new XLock(display)) + { + int major, ev, error; + if (Functions.XQueryExtension(display, "XInputExtension", out major, out ev, out error) != 0) { - if (XI.QueryVersion(display, ref major, ref minor) == ErrorCodes.Success) + XIOpCode = major; + + int minor = 2; + while (minor >= 0) { - return true; + if (XI.QueryVersion(display, ref major, ref minor) == ErrorCodes.Success) + { + XIVersion = major * 100 + minor * 10; + return true; + } + minor--; } - minor--; } } } + catch (DllNotFoundException e) + { + Debug.Print(e.ToString()); + Debug.Print("XInput2 extension not supported. Mouse support will suffer."); + } return false; } @@ -303,12 +302,8 @@ namespace OpenTK.Platform.X11 { Functions.XWarpPointer(API.DefaultDisplay, IntPtr.Zero, window.RootWindow, 0, 0, 0, 0, (int)x, (int)y); - - // Mark the expected warp-event so it can be ignored. - if (mouse_warp_event == null) - mouse_warp_event_count = 0; - mouse_warp_event_count++; - mouse_warp_event = new MouseWarp((int)x, (int)y); + Interlocked.Exchange(ref cursor_x, (long)x); + Interlocked.Exchange(ref cursor_y, (long)y); } } @@ -342,6 +337,10 @@ namespace OpenTK.Platform.X11 { switch ((XIEventType)cookie.evtype) { + case XIEventType.Motion: + // Nothing to do + break; + case XIEventType.RawMotion: case XIEventType.RawButtonPress: case XIEventType.RawButtonRelease: @@ -385,15 +384,13 @@ namespace OpenTK.Platform.X11 case 1: d.State.EnableBit((int)MouseButton.Left); break; case 2: d.State.EnableBit((int)MouseButton.Middle); break; case 3: d.State.EnableBit((int)MouseButton.Right); break; - case 6: d.State.EnableBit((int)MouseButton.Button1); break; - case 7: d.State.EnableBit((int)MouseButton.Button2); break; - case 8: d.State.EnableBit((int)MouseButton.Button3); break; - case 9: d.State.EnableBit((int)MouseButton.Button4); break; - case 10: d.State.EnableBit((int)MouseButton.Button5); break; - case 11: d.State.EnableBit((int)MouseButton.Button6); break; - case 12: d.State.EnableBit((int)MouseButton.Button7); break; - case 13: d.State.EnableBit((int)MouseButton.Button8); break; - case 14: d.State.EnableBit((int)MouseButton.Button9); break; + case 8: d.State.EnableBit((int)MouseButton.Button1); break; + case 9: d.State.EnableBit((int)MouseButton.Button2); break; + case 10: d.State.EnableBit((int)MouseButton.Button3); break; + case 11: d.State.EnableBit((int)MouseButton.Button4); break; + case 12: d.State.EnableBit((int)MouseButton.Button5); break; + case 13: d.State.EnableBit((int)MouseButton.Button6); break; + case 14: d.State.EnableBit((int)MouseButton.Button7); break; } break; @@ -403,15 +400,13 @@ namespace OpenTK.Platform.X11 case 1: d.State.DisableBit((int)MouseButton.Left); break; case 2: d.State.DisableBit((int)MouseButton.Middle); break; case 3: d.State.DisableBit((int)MouseButton.Right); break; - case 6: d.State.DisableBit((int)MouseButton.Button1); break; - case 7: d.State.DisableBit((int)MouseButton.Button2); break; - case 8: d.State.DisableBit((int)MouseButton.Button3); break; - case 9: d.State.DisableBit((int)MouseButton.Button4); break; - case 10: d.State.DisableBit((int)MouseButton.Button5); break; - case 11: d.State.DisableBit((int)MouseButton.Button6); break; - case 12: d.State.DisableBit((int)MouseButton.Button7); break; - case 13: d.State.DisableBit((int)MouseButton.Button8); break; - case 14: d.State.DisableBit((int)MouseButton.Button9); break; + case 8: d.State.DisableBit((int)MouseButton.Button1); break; + case 9: d.State.DisableBit((int)MouseButton.Button2); break; + case 10: d.State.DisableBit((int)MouseButton.Button3); break; + case 11: d.State.DisableBit((int)MouseButton.Button4); break; + case 12: d.State.DisableBit((int)MouseButton.Button5); break; + case 13: d.State.DisableBit((int)MouseButton.Button6); break; + case 14: d.State.DisableBit((int)MouseButton.Button7); break; } break; }