mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
280 lines
11 KiB
C#
280 lines
11 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Timers;
|
|
using System.Windows.Forms;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
using Artemis.Core;
|
|
using Artemis.Core.Services;
|
|
using Artemis.UI.Utilities;
|
|
using Linearstar.Windows.RawInput;
|
|
using Linearstar.Windows.RawInput.Native;
|
|
using Serilog;
|
|
using MouseButton = Artemis.Core.Services.MouseButton;
|
|
|
|
namespace Artemis.UI.InputProviders
|
|
{
|
|
public class NativeWindowInputProvider : InputProvider
|
|
{
|
|
private const int WM_INPUT = 0x00FF;
|
|
|
|
private readonly IInputService _inputService;
|
|
private readonly ILogger _logger;
|
|
private DateTime _lastMouseUpdate;
|
|
private SpongeWindow _sponge;
|
|
private System.Timers.Timer _taskManagerTimer;
|
|
|
|
public NativeWindowInputProvider(ILogger logger, IInputService inputService)
|
|
{
|
|
_logger = logger;
|
|
_inputService = inputService;
|
|
|
|
_sponge = new SpongeWindow();
|
|
_sponge.WndProcCalled += SpongeOnWndProcCalled;
|
|
|
|
_taskManagerTimer = new System.Timers.Timer(500);
|
|
_taskManagerTimer.Elapsed += TaskManagerTimerOnElapsed;
|
|
_taskManagerTimer.Start();
|
|
|
|
RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, _sponge.Handle);
|
|
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _sponge.Handle);
|
|
}
|
|
|
|
|
|
#region Overrides of InputProvider
|
|
|
|
/// <inheritdoc />
|
|
public override void OnKeyboardToggleStatusRequested()
|
|
{
|
|
UpdateToggleStatus();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
/// <inheritdoc />
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_sponge?.DestroyHandle();
|
|
_sponge = null;
|
|
_taskManagerTimer?.Dispose();
|
|
_taskManagerTimer = null;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void SpongeOnWndProcCalled(object sender, Message message)
|
|
{
|
|
if (message.Msg != WM_INPUT)
|
|
return;
|
|
|
|
RawInputData data = RawInputData.FromHandle(message.LParam);
|
|
switch (data)
|
|
{
|
|
case RawInputMouseData mouse:
|
|
HandleMouseData(data, mouse);
|
|
break;
|
|
case RawInputKeyboardData keyboard:
|
|
HandleKeyboardData(data, keyboard);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void TaskManagerTimerOnElapsed(object sender, ElapsedEventArgs e)
|
|
{
|
|
// If task manager has focus then we can't track keys properly, release everything to avoid them getting stuck
|
|
// Same goes for Idle which is what you get when you press Ctrl+Alt+Del
|
|
Process active = Process.GetProcessById(WindowUtilities.GetActiveProcessId());
|
|
if (active?.ProcessName == "Taskmgr" || active?.ProcessName == "Idle")
|
|
_inputService.ReleaseAll();
|
|
}
|
|
|
|
#region Keyboard
|
|
|
|
private void HandleKeyboardData(RawInputData data, RawInputKeyboardData keyboardData)
|
|
{
|
|
KeyboardKey key = (KeyboardKey) KeyInterop.KeyFromVirtualKey(keyboardData.Keyboard.VirutalKey);
|
|
// Debug.WriteLine($"VK: {key} ({keyboardData.Keyboard.VirutalKey}), Flags: {keyboardData.Keyboard.Flags}, Scan code: {keyboardData.Keyboard.ScanCode}");
|
|
|
|
// Sometimes we get double hits and they resolve to None, ignore those
|
|
if (key == KeyboardKey.None)
|
|
return;
|
|
|
|
// Right alt triggers LeftCtrl with a different scan code for some reason, ignore those
|
|
if (key == KeyboardKey.LeftCtrl && keyboardData.Keyboard.ScanCode == 56)
|
|
return;
|
|
|
|
string identifier = data.Device?.DevicePath;
|
|
|
|
// Let the core know there is an identifier so it can store new identifications if applicable
|
|
if (identifier != null)
|
|
OnIdentifierReceived(identifier, InputDeviceType.Keyboard);
|
|
|
|
ArtemisDevice device = null;
|
|
if (identifier != null)
|
|
try
|
|
{
|
|
device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Warning(e, "Failed to retrieve input device by its identifier");
|
|
}
|
|
|
|
// Duplicate keys with different positions can be identified by the LeftKey flag (even though its set of the key that's physically on the right)
|
|
if (keyboardData.Keyboard.Flags == RawKeyboardFlags.LeftKey || keyboardData.Keyboard.Flags == (RawKeyboardFlags.LeftKey | RawKeyboardFlags.Up))
|
|
{
|
|
if (key == KeyboardKey.Enter)
|
|
key = KeyboardKey.NumPadEnter;
|
|
if (key == KeyboardKey.LeftCtrl)
|
|
key = KeyboardKey.RightCtrl;
|
|
if (key == KeyboardKey.LeftAlt)
|
|
key = KeyboardKey.RightAlt;
|
|
}
|
|
|
|
if (key == KeyboardKey.LeftShift && keyboardData.Keyboard.ScanCode == 54)
|
|
key = KeyboardKey.RightShift;
|
|
|
|
bool isDown = keyboardData.Keyboard.Flags != RawKeyboardFlags.Up &&
|
|
keyboardData.Keyboard.Flags != (RawKeyboardFlags.Up | RawKeyboardFlags.LeftKey) &&
|
|
keyboardData.Keyboard.Flags != (RawKeyboardFlags.Up | RawKeyboardFlags.RightKey);
|
|
|
|
OnKeyboardDataReceived(device, key, isDown);
|
|
UpdateToggleStatus();
|
|
}
|
|
|
|
private void UpdateToggleStatus()
|
|
{
|
|
OnKeyboardToggleStatusReceived(new KeyboardToggleStatus(
|
|
Keyboard.IsKeyToggled(Key.NumLock),
|
|
Keyboard.IsKeyToggled(Key.CapsLock),
|
|
Keyboard.IsKeyToggled(Key.Scroll)
|
|
));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mouse
|
|
|
|
private int _mouseDeltaX;
|
|
private int _mouseDeltaY;
|
|
|
|
private void HandleMouseData(RawInputData data, RawInputMouseData mouseData)
|
|
{
|
|
// Only submit mouse movement 25 times per second but increment the delta
|
|
// This can create a small inaccuracy of course, but Artemis is not a shooter :')
|
|
if (mouseData.Mouse.Buttons == RawMouseButtonFlags.None)
|
|
{
|
|
_mouseDeltaX += mouseData.Mouse.LastX;
|
|
_mouseDeltaY += mouseData.Mouse.LastY;
|
|
if (DateTime.Now - _lastMouseUpdate < TimeSpan.FromMilliseconds(40))
|
|
return;
|
|
}
|
|
|
|
ArtemisDevice device = null;
|
|
string identifier = data.Device?.DevicePath;
|
|
if (identifier != null)
|
|
try
|
|
{
|
|
device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Warning(e, "Failed to retrieve input device by its identifier");
|
|
}
|
|
|
|
// Debug.WriteLine($"Buttons: {data.Mouse.Buttons}, Data: {data.Mouse.ButtonData}, Flags: {data.Mouse.Flags}, XY: {data.Mouse.LastX},{data.Mouse.LastY}");
|
|
|
|
// Movement
|
|
if (mouseData.Mouse.Buttons == RawMouseButtonFlags.None)
|
|
{
|
|
Win32Point cursorPosition = GetCursorPosition();
|
|
OnMouseMoveDataReceived(device, cursorPosition.X, cursorPosition.Y, _mouseDeltaX, _mouseDeltaY);
|
|
_mouseDeltaX = 0;
|
|
_mouseDeltaY = 0;
|
|
_lastMouseUpdate = DateTime.Now;
|
|
return;
|
|
}
|
|
|
|
// Now we know its not movement, let the core know there is an identifier so it can store new identifications if applicable
|
|
if (identifier != null)
|
|
OnIdentifierReceived(identifier, InputDeviceType.Mouse);
|
|
|
|
// Scrolling
|
|
if (mouseData.Mouse.ButtonData != 0)
|
|
{
|
|
if (mouseData.Mouse.Buttons == RawMouseButtonFlags.MouseWheel)
|
|
OnMouseScrollDataReceived(device, MouseScrollDirection.Vertical, mouseData.Mouse.ButtonData);
|
|
else if (mouseData.Mouse.Buttons == RawMouseButtonFlags.MouseHorizontalWheel)
|
|
OnMouseScrollDataReceived(device, MouseScrollDirection.Horizontal, mouseData.Mouse.ButtonData);
|
|
return;
|
|
}
|
|
|
|
// Button presses
|
|
MouseButton button = MouseButton.Left;
|
|
bool isDown = false;
|
|
|
|
// Left
|
|
if (DetermineMouseButton(mouseData, RawMouseButtonFlags.LeftButtonDown, RawMouseButtonFlags.LeftButtonUp, ref isDown))
|
|
button = MouseButton.Left;
|
|
// Middle
|
|
else if (DetermineMouseButton(mouseData, RawMouseButtonFlags.MiddleButtonDown, RawMouseButtonFlags.MiddleButtonUp, ref isDown))
|
|
button = MouseButton.Middle;
|
|
// Right
|
|
else if (DetermineMouseButton(mouseData, RawMouseButtonFlags.RightButtonDown, RawMouseButtonFlags.RightButtonUp, ref isDown))
|
|
button = MouseButton.Right;
|
|
// Button 4
|
|
else if (DetermineMouseButton(mouseData, RawMouseButtonFlags.Button4Down, RawMouseButtonFlags.Button4Up, ref isDown))
|
|
button = MouseButton.Button4;
|
|
else if (DetermineMouseButton(mouseData, RawMouseButtonFlags.Button5Down, RawMouseButtonFlags.Button5Up, ref isDown))
|
|
button = MouseButton.Button5;
|
|
|
|
OnMouseButtonDataReceived(device, button, isDown);
|
|
}
|
|
|
|
private bool DetermineMouseButton(RawInputMouseData data, RawMouseButtonFlags downButton, RawMouseButtonFlags upButton, ref bool isDown)
|
|
{
|
|
if (data.Mouse.Buttons == downButton || data.Mouse.Buttons == upButton)
|
|
{
|
|
isDown = data.Mouse.Buttons == downButton;
|
|
return true;
|
|
}
|
|
|
|
isDown = false;
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Native
|
|
|
|
[DllImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool GetCursorPos(ref Win32Point pt);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct Win32Point
|
|
{
|
|
public readonly int X;
|
|
public readonly int Y;
|
|
}
|
|
|
|
private static Win32Point GetCursorPosition()
|
|
{
|
|
Win32Point w32Mouse = new();
|
|
GetCursorPos(ref w32Mouse);
|
|
|
|
return w32Mouse;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |