-
Notifications
You must be signed in to change notification settings - Fork 399
/
WindowsMouseHandler.cs
141 lines (111 loc) · 5.53 KB
/
WindowsMouseHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Drawing;
using System.Runtime.Versioning;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.StateChanges;
using osu.Framework.Platform.Windows.Native;
using osuTK;
using SDL2;
// ReSharper disable UnusedParameter.Local (Class regularly handles native events where we don't consume all parameters)
namespace osu.Framework.Platform.Windows
{
/// <summary>
/// A windows specific mouse input handler which overrides the SDL2 implementation of raw input.
/// This is done to better handle quirks of some devices.
/// </summary>
[SupportedOSPlatform("windows")]
internal unsafe class WindowsMouseHandler : MouseHandler
{
private const int raw_input_coordinate_space = 65535;
private SDL.SDL_WindowsMessageHook callback = null!;
private WindowsWindow window = null!;
public override bool IsActive => Enabled.Value;
public override bool Initialize(GameHost host)
{
if (!(host.Window is WindowsWindow desktopWindow))
return false;
window = desktopWindow;
callback = (ptr, wnd, u, param, l) => onWndProc(ptr, wnd, u, param, l);
Enabled.BindValueChanged(enabled =>
{
host.InputThread.Scheduler.Add(() => SDL.SDL_SetWindowsMessageHook(enabled.NewValue ? callback : null, IntPtr.Zero));
}, true);
return base.Initialize(host);
}
public override void FeedbackMousePositionChange(Vector2 position, bool isSelfFeedback)
{
window.LastMousePosition = position;
base.FeedbackMousePositionChange(position, isSelfFeedback);
}
protected override void HandleMouseMoveRelative(Vector2 delta)
{
// handled via custom logic below.
}
private IntPtr onWndProc(IntPtr userData, IntPtr hWnd, uint message, ulong wParam, long lParam)
{
if (!Enabled.Value)
return IntPtr.Zero;
if (message != Native.Input.WM_INPUT)
return IntPtr.Zero;
if (Native.Input.IsTouchEvent(Native.Input.GetMessageExtraInfo()))
// sometimes GetMessageExtraInfo returns 0, so additionally, mouse.ExtraInformation is checked below.
// touch events are handled by TouchHandler
return IntPtr.Zero;
int payloadSize = sizeof(RawInputData);
Native.Input.GetRawInputData((IntPtr)lParam, RawInputCommand.Input, out var data, ref payloadSize, sizeof(RawInputHeader));
if (data.Header.Type != RawInputType.Mouse)
return IntPtr.Zero;
var mouse = data.Mouse;
// `ExtraInformation` doens't have the MI_WP_SIGNATURE set, so we have to rely solely on the touch flag.
if (Native.Input.HasTouchFlag(mouse.ExtraInformation))
return IntPtr.Zero;
//TODO: this isn't correct.
if (mouse.ExtraInformation > 0)
{
// i'm not sure if there is a valid case where we need to handle packets with this present
// but the osu!tablet fires noise events with non-zero values, which we want to ignore.
// return IntPtr.Zero;
}
var position = new Vector2(mouse.LastX, mouse.LastY);
float sensitivity = (float)Sensitivity.Value;
if (mouse.Flags.HasFlagFast(RawMouseFlags.MoveAbsolute))
{
var screenRect = mouse.Flags.HasFlagFast(RawMouseFlags.VirtualDesktop) ? Native.Input.VirtualScreenRect : new Rectangle(window.Position, window.ClientSize);
Vector2 screenSize = new Vector2(screenRect.Width, screenRect.Height);
if (mouse.LastX == 0 && mouse.LastY == 0)
{
// not sure if this is the case for all tablets, but on osu!tablet these can appear and are noise.
return IntPtr.Zero;
}
// i am not sure what this 64 flag is, but it's set on the osu!tablet at very least.
// using it here as a method of determining where the coordinate space is incorrect.
if (((int)mouse.Flags & 64) == 0)
{
position /= raw_input_coordinate_space;
position *= screenSize;
}
if (Sensitivity.Value != 1)
{
// apply absolute sensitivity adjustment from the centre of the screen area.
Vector2 halfScreenSize = (screenSize / 2);
position -= halfScreenSize;
position *= (float)Sensitivity.Value;
position += halfScreenSize;
}
// map from screen to client coordinate space.
// not using Window's PointToClient implementation to keep floating point precision here.
position -= new Vector2(window.Position.X, window.Position.Y);
position *= window.Scale;
PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = position });
}
else
{
PendingInputs.Enqueue(new MousePositionRelativeInput { Delta = new Vector2(mouse.LastX, mouse.LastY) * sensitivity });
}
return IntPtr.Zero;
}
}
}