[Linux] Implemented INativeWindow keyboard/mouse events
authorthefiddler <stapostol@gmail.com>
Wed, 16 Jul 2014 10:18:24 +0000 (12:18 +0200)
committerthefiddler <stapostol@gmail.com>
Wed, 16 Jul 2014 12:28:28 +0000 (14:28 +0200)
The mouse cursor is now confined to the display bounds.

Source/OpenTK/Platform/Linux/Bindings/Evdev.cs
Source/OpenTK/Platform/Linux/Bindings/LibInput.cs
Source/OpenTK/Platform/Linux/LinuxInput.cs
Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs

index 8674db3..11988eb 100644 (file)
@@ -28,6 +28,7 @@
 #endregion
 
 using System;
+using System.Diagnostics;
 using OpenTK.Input;
 
 namespace OpenTK.Platform.Linux
@@ -359,10 +360,9 @@ namespace OpenTK.Platform.Linux
                     return MouseButton.Button8;
                 case EvdevButton.BTN8:
                     return MouseButton.Button9;
-                case EvdevButton.BTN9:
-                    return MouseButton.LastButton;
                 default:
-                    return MouseButton.LastButton;
+                    Debug.Print("[Input] Unknown EvdevButton {0}", button);
+                    return MouseButton.Left;
             }
         }
     }
index 29864b9..15aa00d 100644 (file)
@@ -258,9 +258,8 @@ namespace OpenTK.Platform.Linux
         public Fixed24 DeltaY { get { return GetDY(@event); } }
         public Fixed24 X { get { return GetAbsX(@event); } }
         public Fixed24 Y { get { return GetAbsY(@event); } }
-        // Are the following useful?
-        //public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); }
-        //public Fixed24 TransformedY(int height) { return GetAbsXTransformed(@event, height); }
+        public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); }
+        public Fixed24 TransformedY(int height) { return GetAbsYTransformed(@event, height); }
 
         [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_time", CallingConvention = CallingConvention.Cdecl)]
         static extern uint GetTime(IntPtr @event);
index ddd3a8a..390f022 100644 (file)
@@ -28,7 +28,9 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
+using System.Drawing;
 using System.Runtime.InteropServices;
 using System.Threading;
 using OpenTK.Input;
@@ -108,6 +110,9 @@ namespace OpenTK.Platform.Linux
         DeviceCollection<KeyboardDevice> Keyboards = new DeviceCollection<KeyboardDevice>();
         DeviceCollection<MouseDevice> Mice = new DeviceCollection<MouseDevice>();
 
+        // Todo: do we need to maintain the geometry of each display separately?
+        Rectangle bounds;
+
         // Global mouse cursor state
         Vector2 CursorPosition = Vector2.Zero;
         // Global mouse cursor offset (used for emulating SetPosition)
@@ -210,6 +215,12 @@ namespace OpenTK.Platform.Linux
                     ret < 0 && !(error == ErrorNumber.Again || error == ErrorNumber.Interrupted) ||
                     (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0;
 
+                // We need to query the desktop bounds in order to position the mouse cursor correctly.
+                // This value will be used for the current bunch of input events. If a monitor changes
+                // resolution in the meantime, we might be slightly off in our calculations - this error
+                // will be corrected when the next bunch of input events arrives.
+                UpdateDisplayBounds();
+
                 if (ret > 0 && (poll_fd.revents & (PollFlags.In | PollFlags.Pri)) != 0)
                 {
                     ProcessEvents(input_context);
@@ -225,6 +236,19 @@ namespace OpenTK.Platform.Linux
             Debug.Print("[Input] Exited input loop.", poll_fd.fd, poll_fd.events);
         }
 
+        void UpdateDisplayBounds()
+        {
+            bounds = Rectangle.Empty;
+            for (DisplayIndex i = DisplayIndex.First; i < DisplayIndex.Sixth; i++)
+            {
+                DisplayDevice display = DisplayDevice.GetDisplay(i);
+                if (display != null)
+                {
+                    bounds = Rectangle.Union(bounds, display.Bounds);
+                }
+            }
+        }
+
         void Setup()
         {
             // Todo: add static path fallback when udev is not installed.
@@ -425,16 +449,22 @@ namespace OpenTK.Platform.Linux
             {
                 mouse.State.Position += delta;
             }
+
+            CursorPosition = new Vector2(
+                MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right),
+                MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom));
         }
 
         void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e)
         {
-            Vector2 position = new Vector2(e.X, e.Y);
             if (mouse != null)
             {
-                mouse.State.Position = position;
+                mouse.State.Position = new Vector2(e.X, e.Y);
             }
-            CursorPosition = position; // update global cursor position
+
+            CursorPosition = new Vector2(
+                e.TransformedX(bounds.Width),
+                e.TransformedY(bounds.Height));
         }
 
         static int GetId(IntPtr device)
index d0e32f7..ee6edf8 100644 (file)
@@ -32,6 +32,7 @@ using System.Diagnostics;
 using System.Drawing;
 using System.Runtime.InteropServices;
 using OpenTK.Graphics;
+using OpenTK.Input;
 using OpenTK.Platform.Egl;
 
 namespace OpenTK.Platform.Linux
@@ -43,9 +44,13 @@ namespace OpenTK.Platform.Linux
         LinuxWindowInfo window;
         string title;
         Icon icon;
-        bool exists;
         Rectangle bounds;
         Size client_size;
+        bool exists;
+        bool is_focused;
+
+        KeyboardState previous_keyboard;
+        MouseState previous_mouse;
 
         public LinuxNativeWindow(IntPtr display, IntPtr gbm, int fd,
             int x, int y, int width, int height, string title,
@@ -147,10 +152,83 @@ namespace OpenTK.Platform.Linux
             return SurfaceFormat.RGBA8888;
         }
 
+        KeyboardState ProcessKeyboard(KeyboardState keyboard)
+        {
+            for (Key i = 0; i < Key.LastKey; i++)
+            {
+                if (keyboard[i])
+                {
+                    OnKeyDown(i, previous_keyboard[i]);
+                    // Todo: implement libxkb-common binding for text input
+                }
+
+                if (!keyboard[i] && previous_keyboard[i])
+                {
+                    OnKeyUp(i);
+                }
+            }
+            return keyboard;
+        }
+
+        MouseState ProcessMouse(MouseState mouse)
+        {
+            for (MouseButton i = 0; i < MouseButton.LastButton; i++)
+            {
+                if (mouse[i] && !previous_mouse[i])
+                {
+                    OnMouseDown(i);
+                }
+
+                if (!mouse[i] && previous_mouse[i])
+                {
+                    OnMouseUp(i);
+                }
+
+                if (mouse.Position != previous_mouse.Position)
+                {
+                    OnMouseMove(mouse.X, mouse.Y);
+                }
+
+                if (mouse.Scroll != previous_mouse.Scroll)
+                {
+                    OnMouseWheel(mouse.Scroll.X, mouse.Scroll.Y);
+                }
+
+                // Note: focus follows mouse. Literally.
+                bool cursor_in = Bounds.Contains(new Point(mouse.X, mouse.Y));
+                if (!cursor_in && Focused)
+                {
+                    OnMouseLeave(EventArgs.Empty);
+                    SetFocus(false);
+                }
+                else if (cursor_in && !Focused)
+                {
+                    OnMouseEnter(EventArgs.Empty);
+                    SetFocus(true);
+                }
+            }
+
+            return mouse;
+        }
+
+        void SetFocus(bool focus)
+        {
+            if (is_focused != focus)
+            {
+                is_focused = focus;
+                OnFocusedChanged(EventArgs.Empty);
+            }
+        }
+
         #region INativeWindow Members
 
         public override void ProcessEvents()
         {
+            // Note: there is no event-based keyboard/mouse input available.
+            // We will fake that by polling OpenTK.Input.
+            previous_keyboard = ProcessKeyboard(Keyboard.GetState());
+            previous_mouse = ProcessMouse(Mouse.GetCursorState());
+
             base.ProcessEvents();
         }
 
@@ -216,7 +294,7 @@ namespace OpenTK.Platform.Linux
         {
             get
             {
-                return true;
+                return is_focused;
             }
         }