From 07cbb9dd8bd2852330b186def3f67947deb58ab1 Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Tue, 23 Nov 2010 17:17:13 +0000 Subject: [PATCH] * Input/Mouse.cs: * Input/IMouseDriver2.cs: * Platform/X11/X11Mouse.cs: * Platform/X11/XI2Mouse.cs: * Platform/X11/Functions.cs: * Platform/Windows/WMInput.cs: * Platform/X11/X11GLNative.cs: * Platform/Windows/WinRawMouse.cs: Added ability to set the position of the mouse cursor. [X11] Avoid grabbing the pointer, as this causes unexpected side-effects (XInput2 stops working, debugging becomes difficult). We now use XWarpPointer and try to discard the spurious MouseMove events it generates. [X11] Make cursor visible when window loses focus, to make debugging easier. Restore previous state when it regains focus. --- Source/OpenTK/Input/IMouseDriver2.cs | 40 +++++++--- Source/OpenTK/Input/Mouse.cs | 17 +++++ Source/OpenTK/Platform/Windows/WMInput.cs | 5 ++ Source/OpenTK/Platform/Windows/WinRawMouse.cs | 5 ++ Source/OpenTK/Platform/X11/Functions.cs | 6 ++ Source/OpenTK/Platform/X11/X11GLNative.cs | 73 +++++++++++++++++-- Source/OpenTK/Platform/X11/X11Mouse.cs | 5 ++ Source/OpenTK/Platform/X11/XI2Mouse.cs | 60 ++++++++++++++- 8 files changed, 189 insertions(+), 22 deletions(-) diff --git a/Source/OpenTK/Input/IMouseDriver2.cs b/Source/OpenTK/Input/IMouseDriver2.cs index 6b560e13..0d0335e7 100644 --- a/Source/OpenTK/Input/IMouseDriver2.cs +++ b/Source/OpenTK/Input/IMouseDriver2.cs @@ -1,4 +1,31 @@ -using System; + #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.Text; @@ -6,17 +33,8 @@ namespace OpenTK.Input { interface IMouseDriver2 { - /// - /// Retrieves the MouseState for the default keyboard device. - /// - /// A structure containing the state of the mouse device. MouseState GetState(); - - /// - /// Retrieves the MouseState for the specified keyboard device. - /// - /// The index of the keyboard device. - /// A structure containing the state of the mouse device. MouseState GetState(int index); + void SetPosition(double x, double y); } } diff --git a/Source/OpenTK/Input/Mouse.cs b/Source/OpenTK/Input/Mouse.cs index c3f42c17..f68d5ff2 100644 --- a/Source/OpenTK/Input/Mouse.cs +++ b/Source/OpenTK/Input/Mouse.cs @@ -74,6 +74,23 @@ namespace OpenTK.Input } } + /// + ///Moves the mouse cursor to the specified screen position. + /// + /// + /// A that represents the absolute x position of the cursor in screen coordinates. + /// + /// + /// A that represents the absolute y position of the cursor in screen coordinates. + /// + public static void SetPosition(double x, double y) + { + lock (SyncRoot) + { + driver.SetPosition(x, y); + } + } + #endregion } } diff --git a/Source/OpenTK/Platform/Windows/WMInput.cs b/Source/OpenTK/Platform/Windows/WMInput.cs index 8f42d3a7..692ef491 100644 --- a/Source/OpenTK/Platform/Windows/WMInput.cs +++ b/Source/OpenTK/Platform/Windows/WMInput.cs @@ -137,6 +137,11 @@ namespace OpenTK.Platform.Windows } } + public void SetPosition(double x, double y) + { + throw new NotImplementedException(); + } + #endregion #region IKeyboardDriver2 Members diff --git a/Source/OpenTK/Platform/Windows/WinRawMouse.cs b/Source/OpenTK/Platform/Windows/WinRawMouse.cs index 3f028bc6..4d6ade4f 100644 --- a/Source/OpenTK/Platform/Windows/WinRawMouse.cs +++ b/Source/OpenTK/Platform/Windows/WinRawMouse.cs @@ -278,6 +278,11 @@ namespace OpenTK.Platform.Windows } } + public void SetPosition(double x, double y) + { + throw new NotImplementedException(); + } + #endregion } } diff --git a/Source/OpenTK/Platform/X11/Functions.cs b/Source/OpenTK/Platform/X11/Functions.cs index 69e1e362..7d138d92 100644 --- a/Source/OpenTK/Platform/X11/Functions.cs +++ b/Source/OpenTK/Platform/X11/Functions.cs @@ -529,6 +529,12 @@ namespace OpenTK.Platform.X11 [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/X11GLNative.cs b/Source/OpenTK/Platform/X11/X11GLNative.cs index 4688ebd7..4be53acd 100644 --- a/Source/OpenTK/Platform/X11/X11GLNative.cs +++ b/Source/OpenTK/Platform/X11/X11GLNative.cs @@ -54,7 +54,10 @@ namespace OpenTK.Platform.X11 const int _min_width = 30, _min_height = 30; X11WindowInfo window = new X11WindowInfo(); + + // Legacy input support X11Input driver; + MouseDevice mouse; // Window manager hints for fullscreen windows. // Not used right now (the code is written, but is not 64bit-correct), but could be useful for older WMs which @@ -107,6 +110,8 @@ namespace OpenTK.Platform.X11 bool isExiting; bool _decorations_hidden = false; + bool cursor_visible = true; + int mouse_rel_x, mouse_rel_y; // Keyboard input readonly byte[] ascii = new byte[16]; @@ -115,6 +120,8 @@ namespace OpenTK.Platform.X11 readonly IntPtr EmptyCursor; + public static bool MouseWarpActive = false; + #endregion #region Constructors @@ -196,6 +203,7 @@ namespace OpenTK.Platform.X11 RefreshWindowBounds(ref e); driver = new X11Input(window); + mouse = driver.Mouse[0]; EmptyCursor = CreateEmptyCursor(window); @@ -692,6 +700,17 @@ namespace OpenTK.Platform.X11 return cursor; } + static void SetMouseClamped(MouseDevice mouse, int x, int y, + int left, int top, int width, int height) + { + // Clamp mouse to the specified rectangle. + x = Math.Max(x, left); + x = Math.Min(x, width); + y = Math.Max(y, top); + y = Math.Min(y, height); + mouse.Position = new Point(x, y); + } + #endregion #region INativeWindow Members @@ -794,6 +813,45 @@ namespace OpenTK.Platform.X11 break; 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; + int middle_x = (Bounds.Left + Bounds.Right) / 2; + int middle_y = (Bounds.Top + Bounds.Bottom) / 2; + 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) + { + SetMouseClamped(mouse, + mouse.X + x - mouse_rel_x, + mouse.Y + y - mouse_rel_y, + 0, 0, Width, Height); + mouse_rel_x = x; + mouse_rel_y = y; + + // Warp cursor to center of window. + MouseWarpActive = true; + Mouse.SetPosition(middle_x, middle_y); + } + else + { + SetMouseClamped(mouse, x, y, 0, 0, Width, Height); + mouse_rel_x = x; + mouse_rel_y = y; + } + break; + } + case XEventName.ButtonPress: case XEventName.ButtonRelease: driver.ProcessEvent(ref e); @@ -818,7 +876,10 @@ namespace OpenTK.Platform.X11 break; case XEventName.LeaveNotify: - MouseLeave(this, EventArgs.Empty); + if (CursorVisible) + { + MouseLeave(this, EventArgs.Empty); + } break; case XEventName.EnterNotify: @@ -1291,15 +1352,15 @@ namespace OpenTK.Platform.X11 public bool CursorVisible { - get { return true; } // Not used + get { return cursor_visible; } set { if (value) { using (new XLock(window.Display)) { - Functions.XUngrabPointer(window.Display, IntPtr.Zero); Functions.XUndefineCursor(window.Display, window.WindowHandle); + cursor_visible = true; } } else @@ -1307,11 +1368,7 @@ namespace OpenTK.Platform.X11 using (new XLock(window.Display)) { Functions.XDefineCursor(window.Display, window.WindowHandle, EmptyCursor); - Functions.XGrabPointer(window.Display, window.WindowHandle, true, - EventMask.PointerMotionMask | EventMask.ButtonPressMask | EventMask.ButtonReleaseMask, - GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, window.WindowHandle, IntPtr.Zero, - IntPtr.Zero); - + cursor_visible = false; } } } diff --git a/Source/OpenTK/Platform/X11/X11Mouse.cs b/Source/OpenTK/Platform/X11/X11Mouse.cs index 9dd523c6..0dc3f169 100644 --- a/Source/OpenTK/Platform/X11/X11Mouse.cs +++ b/Source/OpenTK/Platform/X11/X11Mouse.cs @@ -61,6 +61,11 @@ namespace OpenTK.Platform.X11 return new MouseState(); } + public void SetPosition(double x, double y) + { + throw new NotImplementedException(); + } + void WriteBit(MouseButton offset, int enabled) { if (enabled != 0) diff --git a/Source/OpenTK/Platform/X11/XI2Mouse.cs b/Source/OpenTK/Platform/X11/XI2Mouse.cs index 69825805..e1162ecd 100644 --- a/Source/OpenTK/Platform/X11/XI2Mouse.cs +++ b/Source/OpenTK/Platform/X11/XI2Mouse.cs @@ -39,12 +39,22 @@ namespace OpenTK.Platform.X11 { List mice = new List(); Dictionary rawids = new Dictionary(); // maps raw ids to mouse ids - readonly X11WindowInfo window; + internal readonly X11WindowInfo window; static int XIOpCode; 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; + public XI2Mouse() { Debug.WriteLine("Using XI2Mouse."); @@ -65,6 +75,7 @@ namespace OpenTK.Platform.X11 XIEventMasks.RawButtonReleaseMask | XIEventMasks.RawMotionMask)) { Functions.XISelectEvents(window.Display, window.WindowHandle, mask); + Functions.XISelectEvents(window.Display, window.RootWindow, mask); } } @@ -110,8 +121,40 @@ namespace OpenTK.Platform.X11 return new MouseState(); } + public void SetPosition(double x, double y) + { + using (new XLock(window.Display)) + { +// Functions.XIWarpPointer(window.Display, 0, +// IntPtr.Zero, window.RootWindow, 0, 0, 0, 0, x, y); + Functions.XWarpPointer(window.Display, + 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); + } + + ProcessEvents(); + } + #endregion + bool CheckMouseWarp(double x, double y) + { + // Check if a mouse warp with the specified destination exists. + bool is_warp = + mouse_warp_event.HasValue && + mouse_warp_event.Value.Equals(new MouseWarp((int)x, (int)y)); + + if (is_warp && --mouse_warp_event_count <= 0) + mouse_warp_event = null; + + return is_warp; + } + void ProcessEvents() { while (true) @@ -140,10 +183,21 @@ namespace OpenTK.Platform.X11 switch (raw.evtype) { case XIEventType.RawMotion: + double x = 0, y = 0; if (IsBitSet(raw.valuators.mask, 0)) - state.X += (int)BitConverter.Int64BitsToDouble(Marshal.ReadInt64(raw.raw_values, 0)); + { + x = 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)); + { + 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: