[X11] Fixed XI2Mouse smooth scrolling

The scrolling valuator offsets are now calculated correctly, based on
the number of set bits in the valuator mask.
Additionally XI2Mouse now runs on a background thread, using blocking
XIfEvent. This should improve resonsiveness and reduce CPU usage, due
to reduced polling.
This commit is contained in:
thefiddler 2014-05-12 01:48:06 +02:00
parent bba9c8c26f
commit 1cf7386748
2 changed files with 145 additions and 98 deletions

View file

@ -34,6 +34,8 @@ namespace OpenTK.Platform.X11
{
class X11Factory : PlatformFactoryBase
{
static IMouseDriver2 MouseDriver;
#region Constructors
public X11Factory()
@ -86,10 +88,15 @@ namespace OpenTK.Platform.X11
public override IMouseDriver2 CreateMouseDriver()
{
if (XI2Mouse.IsSupported(IntPtr.Zero))
return new XI2Mouse(); // Requires xorg 1.7 or higher.
else
return new X11Mouse(); // Always supported.
lock (this)
{
MouseDriver =
MouseDriver ??
(XI2Mouse.IsSupported(IntPtr.Zero) ?
(IMouseDriver2)new XI2Mouse() : // Requires xorg 1.7 or higher.
(IMouseDriver2)new X11Mouse()); // Always supported.
return MouseDriver;
}
}
public override IJoystickDriver2 CreateJoystickDriver()
@ -98,5 +105,18 @@ namespace OpenTK.Platform.X11
}
#endregion
protected override void Dispose(bool manual)
{
base.Dispose(manual);
if (manual && MouseDriver != null)
{
if (MouseDriver is IDisposable)
{
(MouseDriver as IDisposable).Dispose();
MouseDriver = null;
}
}
}
}
}

View file

@ -29,14 +29,19 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
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
sealed class XI2Mouse : IMouseDriver2, IDisposable
{
const XEventName ExitEvent = XEventName.LASTEvent + 1;
readonly Thread ProcessingThread;
bool disposed;
class XIMouse
{
public MouseState State;
@ -62,6 +67,7 @@ namespace OpenTK.Platform.X11
//static readonly IntPtr RelHorizWheel;
//static readonly IntPtr RelVertWheel;
long cursor_x, cursor_y; // For GetCursorState()
List<XIMouse> devices = new List<XIMouse>(); // List of connected mice
Dictionary<int, int> rawids = new Dictionary<int, int>(); // maps hardware device ids to XIMouse ids
@ -114,11 +120,11 @@ namespace OpenTK.Platform.X11
public XI2Mouse()
{
Debug.WriteLine("Using XI2Mouse.");
window = new X11WindowInfo();
using (new XLock(API.DefaultDisplay))
window.Display = Functions.XOpenDisplay(IntPtr.Zero);
using (new XLock(window.Display))
{
window = new X11WindowInfo();
window.Display = API.DefaultDisplay;
window.Screen = Functions.XDefaultScreen(window.Display);
window.RootWindow = Functions.XRootWindow(window.Display, window.Screen);
window.Handle = window.RootWindow;
@ -127,17 +133,22 @@ namespace OpenTK.Platform.X11
if (!IsSupported(window.Display))
throw new NotSupportedException("XInput2 not supported.");
using (new XLock(API.DefaultDisplay))
using (new XLock(window.Display))
using (XIEventMask mask = new XIEventMask(1,
XIEventMasks.RawButtonPressMask |
XIEventMasks.RawButtonReleaseMask |
XIEventMasks.RawMotionMask |
XIEventMasks.DeviceChangedMask))
XIEventMasks.DeviceChangedMask |
(XIEventMasks)(1 << (int)ExitEvent)))
{
XI.SelectEvents(window.Display, window.Handle, mask);
}
UpdateDevices();
ProcessingThread = new Thread(ProcessEvents);
ProcessingThread.IsBackground = true;
ProcessingThread.Start();
}
// Checks whether XInput2 is supported on the specified display.
@ -145,7 +156,9 @@ namespace OpenTK.Platform.X11
internal static bool IsSupported(IntPtr display)
{
if (display == IntPtr.Zero)
{
display = API.DefaultDisplay;
}
using (new XLock(display))
{
@ -260,7 +273,6 @@ namespace OpenTK.Platform.X11
public MouseState GetState()
{
ProcessEvents();
MouseState master = new MouseState();
foreach (var d in devices)
{
@ -271,7 +283,6 @@ namespace OpenTK.Platform.X11
public MouseState GetState(int index)
{
ProcessEvents();
if (devices.Count > index)
return devices[index].State;
else
@ -280,26 +291,17 @@ namespace OpenTK.Platform.X11
public MouseState GetCursorState()
{
IntPtr dummy;
int x, y, dummy2;
using (new XLock(window.Display))
{
Functions.XQueryPointer(window.Display, window.RootWindow,
out dummy, out dummy, out x, out y,
out dummy2, out dummy2, out dummy2);
}
MouseState master = GetState();
master.X = x;
master.Y = y;
master.X = (int)Interlocked.Read(ref cursor_x);
master.Y = (int)Interlocked.Read(ref cursor_y);
return master;
}
public void SetPosition(double x, double y)
{
using (new XLock(window.Display))
using (new XLock(API.DefaultDisplay))
{
Functions.XWarpPointer(window.Display,
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.
@ -308,36 +310,32 @@ namespace OpenTK.Platform.X11
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)
while (!disposed)
{
XEvent e = new XEvent();
XGenericEventCookie cookie;
using (new XLock(window.Display))
{
if (!Functions.XCheckIfEvent(window.Display, ref e, Predicate, new IntPtr(XIOpCode)))
Functions.XIfEvent(window.Display, ref e, Predicate, new IntPtr(XIOpCode));
if (e.type == ExitEvent)
{
return;
}
IntPtr dummy;
int x, y, dummy2;
Functions.XQueryPointer(window.Display, window.RootWindow,
out dummy, out dummy, out x, out y,
out dummy2, out dummy2, out dummy2);
Interlocked.Exchange(ref cursor_x, (long)x);
Interlocked.Exchange(ref cursor_y, (long)y);
cookie = e.GenericEventCookie;
if (Functions.XGetEventData(window.Display, ref cookie) != 0)
@ -365,66 +363,24 @@ namespace OpenTK.Platform.X11
{
unsafe
{
XIRawEvent* raw = (XIRawEvent*)cookie.data;
XIRawEvent raw = *(XIRawEvent*)cookie.data;
if (!rawids.ContainsKey(raw->deviceid))
if (!rawids.ContainsKey(raw.deviceid))
{
Debug.Print("Unknown mouse device {0} encountered, ignoring.", raw->deviceid);
Debug.Print("Unknown mouse device {0} encountered, ignoring.", raw.deviceid);
return;
}
var d = devices[rawids[raw->deviceid]];
var d = devices[rawids[raw.deviceid]];
switch (raw->evtype)
switch (raw.evtype)
{
case XIEventType.RawMotion:
{
// Check if this event contains position information
// Note: we use the raw values here, without pointer
// ballistics and any other modification.
double x = 0, y = 0;
if (IsBitSet(raw->valuators.mask, d.MotionX.number))
{
x = *((double*)raw->raw_values + d.MotionX.number);
}
if (IsBitSet(raw->valuators.mask, d.MotionY.number))
{
y = *((double*)raw->raw_values + d.MotionY.number);
}
d.State.X += (int)Math.Round(x);
d.State.Y += (int)Math.Round(y);
// Check if this event contains scrolling information
// Note: we use transformed (valuator) values here,
// because they seem to make more sense compared to
// the raw values (the XI2 docs are pretty scarce on
// how to use the raw values for scrolling.)
double h = 0, v = 0;
bool is_emulated = (raw->flags & XIEventFlags.PointerEmulated) != 0;
if (!is_emulated && IsBitSet(raw->valuators.mask, d.ScrollX.number))
{
h = *((double*)raw->valuators.values + d.ScrollX.number) / d.ScrollX.increment;
if (Double.IsInfinity(h))
{
Debug.WriteLine("[XI2] Mouse horizontal scroll was infinity.");
h = 0;
}
}
if (!is_emulated && IsBitSet(raw->valuators.mask, d.ScrollY.number))
{
v = *((double*)raw->valuators.values + d.ScrollY.number) / d.ScrollY.increment;
if (Double.IsInfinity(v))
{
Debug.WriteLine("[XI2] Mouse horizontal scroll was infinity.");
v = 0;
}
}
d.State.SetScrollRelative((float)h, (float)v);
}
ProcessRawMotion(d, ref raw);
break;
case XIEventType.RawButtonPress:
switch (raw->detail)
switch (raw.detail)
{
case 1: d.State.EnableBit((int)MouseButton.Left); break;
case 2: d.State.EnableBit((int)MouseButton.Middle); break;
@ -442,7 +398,7 @@ namespace OpenTK.Platform.X11
break;
case XIEventType.RawButtonRelease:
switch (raw->detail)
switch (raw.detail)
{
case 1: d.State.DisableBit((int)MouseButton.Left); break;
case 2: d.State.DisableBit((int)MouseButton.Middle); break;
@ -462,14 +418,53 @@ namespace OpenTK.Platform.X11
}
}
unsafe static void ProcessRawMotion(XIMouse d, ref XIRawEvent raw)
{
// Note: we use the raw values here, without pointer
// ballistics and any other modification.
double x = ReadRawValue(ref raw, d.MotionX.number);
double y = ReadRawValue(ref raw, d.MotionY.number);
double h = ReadRawValue(ref raw, d.ScrollX.number) / d.ScrollX.increment;
double v = ReadRawValue(ref raw, d.ScrollY.number) / d.ScrollY.increment;
d.State.X += (int)Math.Round(x);
d.State.Y += (int)Math.Round(y);
d.State.SetScrollRelative((float)h, (float)v);
}
unsafe static double ReadRawValue(ref XIRawEvent raw, int bit)
{
double value = 0;
if (IsBitSet(raw.valuators.mask, bit))
{
// Find the offset where this value is stored.
// The offset is equal to the number of bits
// set in raw.valuators.mask between [0, bit)
int offset = 0;
for (int i = 0; i < bit; i++)
{
if (IsBitSet(raw.valuators.mask, i))
{
offset++;
}
}
value = *((double*)raw.raw_values + offset);
}
return value;
}
static bool IsEventValid(IntPtr display, ref XEvent e, IntPtr arg)
{
return
(long)e.GenericEventCookie.extension == arg.ToInt64() &&
e.GenericEventCookie.evtype == (int)XIEventType.RawMotion ||
e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress ||
e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease ||
e.GenericEventCookie.evtype == (int)XIEventType.DeviceChanged;
bool valid = false;
if ((long)e.GenericEventCookie.extension == arg.ToInt64())
{
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawMotion;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.DeviceChanged;
}
valid |= e.AnyEvent.type == ExitEvent;
return valid;
}
static bool IsBitSet(IntPtr mask, int bit)
@ -479,6 +474,38 @@ namespace OpenTK.Platform.X11
return bit >= 0 && (*((byte*)mask + (bit >> 3)) & (1 << (bit & 7))) != 0;
}
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
using (new XLock(API.DefaultDisplay))
{
XEvent e = new XEvent();
e.type = ExitEvent;
Functions.XSendEvent(API.DefaultDisplay, window.Handle, false, IntPtr.Zero, ref e);
}
}
disposed = true;
}
}
~XI2Mouse()
{
Dispose(false);
}
#endregion
}
}