Browse files

Merge pull request #120 from thefiddler/xcursorfix

[X11] Improve mouse input
  • Loading branch information...
2 parents 1419c1e + 1690cf8 commit 687fc90c95e7670d738be216c6fe94e1c3006807 @thefiddler thefiddler committed May 15, 2014
View
40 Source/OpenTK/Input/MouseScrollWheel.cs → Source/OpenTK/Input/MouseScroll.cs
@@ -34,7 +34,7 @@ namespace OpenTK.Input
/// <summary>
/// Represents the state of a mouse wheel.
/// </summary>
- public struct MouseScrollWheel : IEquatable<MouseScrollWheel>
+ public struct MouseScroll : IEquatable<MouseScroll>
{
#region Public Members
@@ -52,31 +52,31 @@ public struct MouseScrollWheel : IEquatable<MouseScrollWheel>
/// <value>The y.</value>
public float Y { get; internal set; }
- /// <param name="left">A <see cref="MouseScrollWheel"/> instance to test for equality.</param>
- /// <param name="right">A <see cref="MouseScrollWheel"/> instance to test for equality.</param>
- public static bool operator ==(MouseScrollWheel left, MouseScrollWheel right)
+ /// <param name="left">A <see cref="MouseScroll"/> instance to test for equality.</param>
+ /// <param name="right">A <see cref="MouseScroll"/> instance to test for equality.</param>
+ public static bool operator ==(MouseScroll left, MouseScroll right)
{
return left.Equals(right);
}
- /// <param name="left">A <see cref="MouseScrollWheel"/> instance to test for inequality.</param>
- /// <param name="right">A <see cref="MouseScrollWheel"/> instance to test for inequality.</param>
- public static bool operator !=(MouseScrollWheel left, MouseScrollWheel right)
+ /// <param name="left">A <see cref="MouseScroll"/> instance to test for inequality.</param>
+ /// <param name="right">A <see cref="MouseScroll"/> instance to test for inequality.</param>
+ public static bool operator !=(MouseScroll left, MouseScroll right)
{
return !left.Equals(right);
}
/// <summary>
- /// Returns a <see cref="System.String"/> that represents the current <see cref="OpenTK.Input.MouseScrollWheel"/>.
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="OpenTK.Input.MouseScroll"/>.
/// </summary>
- /// <returns>A <see cref="System.String"/> that represents the current <see cref="OpenTK.Input.MouseScrollWheel"/>.</returns>
+ /// <returns>A <see cref="System.String"/> that represents the current <see cref="OpenTK.Input.MouseScroll"/>.</returns>
public override string ToString()
{
return string.Format("[X={0:0.00}, Y={1:0.00}]", X, Y);
}
/// <summary>
- /// Serves as a hash function for a <see cref="OpenTK.Input.MouseScrollWheel"/> object.
+ /// Serves as a hash function for a <see cref="OpenTK.Input.MouseScroll"/> object.
/// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.</returns>
@@ -86,29 +86,29 @@ public override int GetHashCode()
}
/// <summary>
- /// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="OpenTK.Input.MouseScrollWheel"/>.
+ /// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="OpenTK.Input.MouseScroll"/>.
/// </summary>
- /// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="OpenTK.Input.MouseScrollWheel"/>.</param>
+ /// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="OpenTK.Input.MouseScroll"/>.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current
- /// <see cref="OpenTK.Input.MouseScrollWheel"/>; otherwise, <c>false</c>.</returns>
+ /// <see cref="OpenTK.Input.MouseScroll"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
return
- obj is MouseScrollWheel &&
- Equals((MouseScrollWheel)obj);
+ obj is MouseScroll &&
+ Equals((MouseScroll)obj);
}
#endregion
#region IEquatable Members
/// <summary>
- /// Determines whether the specified <see cref="OpenTK.Input.MouseScrollWheel"/> is equal to the current <see cref="OpenTK.Input.MouseScrollWheel"/>.
+ /// Determines whether the specified <see cref="OpenTK.Input.MouseScroll"/> is equal to the current <see cref="OpenTK.Input.MouseScroll"/>.
/// </summary>
- /// <param name="other">The <see cref="OpenTK.Input.MouseScrollWheel"/> to compare with the current <see cref="OpenTK.Input.MouseScrollWheel"/>.</param>
- /// <returns><c>true</c> if the specified <see cref="OpenTK.Input.MouseScrollWheel"/> is equal to the current
- /// <see cref="OpenTK.Input.MouseScrollWheel"/>; otherwise, <c>false</c>.</returns>
- public bool Equals(MouseScrollWheel other)
+ /// <param name="other">The <see cref="OpenTK.Input.MouseScroll"/> to compare with the current <see cref="OpenTK.Input.MouseScroll"/>.</param>
+ /// <returns><c>true</c> if the specified <see cref="OpenTK.Input.MouseScroll"/> is equal to the current
+ /// <see cref="OpenTK.Input.MouseScroll"/>; otherwise, <c>false</c>.</returns>
+ public bool Equals(MouseScroll other)
{
return X == other.X && Y == other.Y;
}
View
4 Source/OpenTK/Input/MouseState.cs
@@ -39,7 +39,7 @@ public struct MouseState : IEquatable<MouseState>
#region Fields
int x, y;
- MouseScrollWheel scroll;
+ MouseScroll scroll;
ushort buttons;
bool is_connected;
@@ -104,7 +104,7 @@ public float WheelPrecise
/// Gets a <see cref="OpenTK.Input.MouseScrollWheel"/> instance,
/// representing the current state of the mouse scroll wheel.
/// </summary>
- public MouseScrollWheel Scroll
+ public MouseScroll Scroll
{
get { return scroll; }
}
View
2 Source/OpenTK/OpenTK.csproj
@@ -790,7 +790,6 @@
<Compile Include="WindowIcon.cs" />
<Compile Include="Platform\MacOS\Cocoa\NSBitmapFormat.cs" />
<Compile Include="Platform\NativeWindowBase.cs" />
- <Compile Include="Input\MouseScrollWheel.cs" />
<Compile Include="Input\MouseEventArgs.cs" />
<Compile Include="Platform\MacOS\Quartz\EventServices.cs" />
<Compile Include="Platform\MacOS\Quartz\DisplayServices.cs">
@@ -799,6 +798,7 @@
<Compile Include="Platform\MacOS\Quartz\CoreFoundation.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Input\MouseScroll.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
View
4 Source/OpenTK/Platform/NativeWindowBase.cs
@@ -232,6 +232,8 @@ protected void OnMouseDown(MouseButton button)
MouseState[button] = true;
var e = MouseDownArgs;
+ e.Button = button;
+ e.IsPressed = true;
e.Mouse = MouseState;
MouseDown(this, e);
@@ -242,6 +244,8 @@ protected void OnMouseUp(MouseButton button)
MouseState[button] = false;
var e = MouseUpArgs;
+ e.Button = button;
+ e.IsPressed = false;
e.Mouse = MouseState;
MouseUp(this, e);
View
124 Source/OpenTK/Platform/X11/X11GLNative.cs
@@ -91,11 +91,13 @@ internal sealed class X11GLNative : NativeWindowBase
//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,18 +123,16 @@ internal sealed class X11GLNative : NativeWindowBase
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];
readonly char[] chars = new char[16];
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 @@ internal sealed class X11GLNative : NativeWindowBase
if (xi2_supported)
{
xi2_opcode = XI2Mouse.XIOpCode;
+ xi2_version = XI2Mouse.XIVersion;
}
exists = true;
@@ -905,73 +906,52 @@ public override void ProcessEvents()
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);
}
}
break;
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 @@ public override void ProcessEvents()
has_focus = true;
if (has_focus != previous_focus)
OnFocusedChanged(EventArgs.Empty);
+
+ if (Focused && !CursorVisible)
+ {
+ GrabMouse();
+ }
}
break;
@@ -1000,6 +985,12 @@ public override void ProcessEvents()
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 @@ public override void ProcessEvents()
// RefreshWindowBorders();
//}
break;
-
+
default:
//Debug.WriteLine(String.Format("{0} event was not handled", e.type));
break;
@@ -1441,11 +1432,18 @@ public override MouseCursor Cursor
{
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 @@ public override MouseCursor Cursor
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 @@ public override bool CursorVisible
{
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 @@ public override bool CursorVisible
{
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
View
2 Source/OpenTK/Platform/X11/X11KeyMap.cs
@@ -388,7 +388,7 @@ internal static bool TranslateKey(ref XKeyEvent e, out Key key)
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;
View
95 Source/OpenTK/Platform/X11/XI2Mouse.cs
@@ -34,8 +34,6 @@
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 @@ class XIMouse
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<MouseWarp>
- {
- 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 @@ public XI2Mouse()
XIEventMasks.RawButtonPressMask |
XIEventMasks.RawButtonReleaseMask |
XIEventMasks.RawMotionMask |
+ XIEventMasks.MotionMask |
XIEventMasks.DeviceChangedMask |
(XIEventMasks)(1 << (int)ExitEvent)))
{
@@ -155,29 +145,38 @@ public XI2Mouse()
// 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 @@ public void SetPosition(double x, double y)
{
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 @@ void ProcessEvents()
{
switch ((XIEventType)cookie.evtype)
{
+ case XIEventType.Motion:
+ // Nothing to do
+ break;
+
case XIEventType.RawMotion:
case XIEventType.RawButtonPress:
case XIEventType.RawButtonRelease:
@@ -385,15 +384,13 @@ void ProcessRawEvent(ref XGenericEventCookie cookie)
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 @@ void ProcessRawEvent(ref XGenericEventCookie cookie)
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;
}

0 comments on commit 687fc90

Please sign in to comment.