mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
375 lines
15 KiB
C#
375 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RGB.NET.Core;
|
|
using Serilog;
|
|
|
|
namespace Artemis.Core.Services
|
|
{
|
|
internal class InputService : IInputService
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly ISurfaceService _surfaceService;
|
|
|
|
public InputService(ILogger logger, ISurfaceService surfaceService)
|
|
{
|
|
_logger = logger;
|
|
_surfaceService = surfaceService;
|
|
|
|
_surfaceService.ActiveSurfaceConfigurationSelected += SurfaceConfigurationChanged;
|
|
_surfaceService.SurfaceConfigurationUpdated += SurfaceConfigurationChanged;
|
|
BustIdentifierCache();
|
|
}
|
|
|
|
#region IDisposable
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
while (_inputProviders.Any())
|
|
RemoveInputProvider(_inputProviders.First());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Providers
|
|
|
|
private readonly List<InputProvider> _inputProviders = new();
|
|
|
|
public void AddInputProvider(InputProvider inputProvider)
|
|
{
|
|
inputProvider.IdentifierReceived += InputProviderOnIdentifierReceived;
|
|
inputProvider.KeyboardDataReceived += InputProviderOnKeyboardDataReceived;
|
|
inputProvider.MouseButtonDataReceived += InputProviderOnMouseButtonDataReceived;
|
|
inputProvider.MouseScrollDataReceived += InputProviderOnMouseScrollDataReceived;
|
|
inputProvider.MouseMoveDataReceived += InputProviderOnMouseMoveDataReceived;
|
|
_inputProviders.Add(inputProvider);
|
|
}
|
|
|
|
public void RemoveInputProvider(InputProvider inputProvider)
|
|
{
|
|
if (!_inputProviders.Contains(inputProvider))
|
|
return;
|
|
|
|
_inputProviders.Remove(inputProvider);
|
|
inputProvider.IdentifierReceived -= InputProviderOnIdentifierReceived;
|
|
inputProvider.KeyboardDataReceived -= InputProviderOnKeyboardDataReceived;
|
|
inputProvider.MouseButtonDataReceived -= InputProviderOnMouseButtonDataReceived;
|
|
inputProvider.MouseScrollDataReceived -= InputProviderOnMouseScrollDataReceived;
|
|
inputProvider.MouseMoveDataReceived -= InputProviderOnMouseMoveDataReceived;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Identification
|
|
|
|
private readonly Dictionary<Tuple<InputProvider, object>, ArtemisDevice> _deviceCache = new();
|
|
private List<ArtemisDevice> _devices = new();
|
|
private ArtemisDevice? _cachedFallbackKeyboard;
|
|
private ArtemisDevice? _cachedFallbackMouse;
|
|
private ArtemisDevice? _identifyingDevice;
|
|
|
|
public void IdentifyDevice(ArtemisDevice device)
|
|
{
|
|
if (device.RgbDevice.DeviceInfo.DeviceType != RGBDeviceType.Keyboard && device.RgbDevice.DeviceInfo.DeviceType != RGBDeviceType.Mouse)
|
|
throw new ArtemisCoreException($"Cannot initialize input-identification for a device of type {device.RgbDevice.DeviceInfo.DeviceType}. \r\n" +
|
|
"Only keyboard and mouse is supported.");
|
|
|
|
_identifyingDevice = device;
|
|
_logger.Debug("Start identifying device {device}", device);
|
|
}
|
|
|
|
public void StopIdentify()
|
|
{
|
|
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
|
|
|
|
_identifyingDevice = null;
|
|
_surfaceService.UpdateSurfaceConfiguration(_surfaceService.ActiveSurface, true);
|
|
|
|
BustIdentifierCache();
|
|
}
|
|
|
|
public ArtemisDevice? GetDeviceByIdentifier(InputProvider provider, object identifier, InputDeviceType type)
|
|
{
|
|
if (provider == null) throw new ArgumentNullException(nameof(provider));
|
|
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
|
|
|
|
// Try cache first
|
|
ArtemisDevice? cacheMatch = GetDeviceFromCache(provider, identifier);
|
|
if (cacheMatch != null)
|
|
return cacheMatch;
|
|
|
|
string providerName = provider.GetType().FullName!;
|
|
ArtemisDevice? match = _devices.FirstOrDefault(m => m.InputIdentifiers.Any(i => Equals(i.InputProvider, providerName) && Equals(i.Identifier, identifier)));
|
|
|
|
// If a match was found cache it to speed up the next event and return the match
|
|
if (match != null)
|
|
{
|
|
AddDeviceToCache(match, provider, identifier);
|
|
return match;
|
|
}
|
|
|
|
// If there is no match, apply our fallback type
|
|
if (type == InputDeviceType.None)
|
|
return null;
|
|
if (type == InputDeviceType.Keyboard)
|
|
{
|
|
if (_cachedFallbackKeyboard != null)
|
|
return _cachedFallbackKeyboard;
|
|
_cachedFallbackKeyboard = _surfaceService.ActiveSurface.Devices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard);
|
|
return _cachedFallbackKeyboard;
|
|
}
|
|
|
|
if (type == InputDeviceType.Mouse)
|
|
{
|
|
if (_cachedFallbackMouse != null)
|
|
return _cachedFallbackMouse;
|
|
_cachedFallbackMouse = _surfaceService.ActiveSurface.Devices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse);
|
|
return _cachedFallbackMouse;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void BustIdentifierCache()
|
|
{
|
|
_deviceCache.Clear();
|
|
_cachedFallbackKeyboard = null;
|
|
_cachedFallbackMouse = null;
|
|
|
|
_devices = _surfaceService.ActiveSurface.Devices.Where(d => d.InputIdentifiers.Any()).ToList();
|
|
}
|
|
|
|
private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier)
|
|
{
|
|
_deviceCache.TryAdd(new Tuple<InputProvider, object>(provider, identifier), match);
|
|
}
|
|
|
|
private ArtemisDevice? GetDeviceFromCache(InputProvider provider, object identifier)
|
|
{
|
|
_deviceCache.TryGetValue(new Tuple<InputProvider, object>(provider, identifier), out ArtemisDevice? device);
|
|
return device;
|
|
}
|
|
|
|
private void SurfaceConfigurationChanged(object? sender, SurfaceConfigurationEventArgs e)
|
|
{
|
|
BustIdentifierCache();
|
|
}
|
|
|
|
private void InputProviderOnIdentifierReceived(object? sender, InputProviderIdentifierEventArgs e)
|
|
{
|
|
// Don't match if there is no device or if the device type differs from the event device type
|
|
if (_identifyingDevice == null ||
|
|
_identifyingDevice.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard && e.DeviceType == InputDeviceType.Mouse ||
|
|
_identifyingDevice.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse && e.DeviceType == InputDeviceType.Keyboard)
|
|
return;
|
|
if (!(sender is InputProvider inputProvider))
|
|
return;
|
|
|
|
string providerName = inputProvider.GetType().FullName!;
|
|
|
|
// Remove existing identification
|
|
_identifyingDevice.InputIdentifiers.RemoveAll(i => i.InputProvider == providerName);
|
|
_identifyingDevice.InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(providerName, e.Identifier));
|
|
|
|
StopIdentify();
|
|
OnDeviceIdentified();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Keyboard
|
|
|
|
private readonly Dictionary<ArtemisDevice, HashSet<KeyboardKey>> _pressedKeys = new();
|
|
private readonly Dictionary<ArtemisDevice, KeyboardModifierKey> _keyboardModifier = new();
|
|
private KeyboardModifierKey _globalModifiers;
|
|
|
|
private void InputProviderOnKeyboardDataReceived(object? sender, InputProviderKeyboardEventArgs e)
|
|
{
|
|
KeyboardModifierKey keyboardModifierKey = UpdateModifierKeys(e.Device, e.Key, e.IsDown);
|
|
|
|
// if UpdatePressedKeys is true, the key is already pressed, then we skip this event
|
|
if (UpdatePressedKeys(e.Device, e.Key, e.IsDown))
|
|
return;
|
|
|
|
// Get the LED
|
|
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
|
|
ArtemisLed? led = null;
|
|
if (foundLedId && e.Device != null)
|
|
led = e.Device.GetLed(ledId);
|
|
|
|
// Create the UpDown event args because it can be used for every event
|
|
ArtemisKeyboardKeyUpDownEventArgs eventArgs = new(e.Device, led, e.Key, keyboardModifierKey, e.IsDown);
|
|
OnKeyboardKeyUpDown(eventArgs);
|
|
if (e.IsDown)
|
|
OnKeyboardKeyDown(eventArgs);
|
|
else
|
|
OnKeyboardKeyUp(eventArgs);
|
|
|
|
// _logger.Verbose("Keyboard data: LED ID: {ledId}, key: {key}, is down: {isDown}, modifiers: {modifiers}, device: {device} ", ledId, e.Key, e.IsDown, keyboardModifierKey, e.Device);
|
|
}
|
|
|
|
private bool UpdatePressedKeys(ArtemisDevice? device, KeyboardKey key, bool isDown)
|
|
{
|
|
if (device != null)
|
|
{
|
|
// Ensure the device is in the dictionary
|
|
_pressedKeys.TryAdd(device, new HashSet<KeyboardKey>());
|
|
// Get the hash set of the device
|
|
HashSet<KeyboardKey> pressedDeviceKeys = _pressedKeys[device];
|
|
// See if the key is already pressed
|
|
bool alreadyPressed = pressedDeviceKeys.Contains(key);
|
|
|
|
// Prevent triggering already down keys again. When a key is held, we don't want to spam events like Windows does
|
|
if (isDown && alreadyPressed)
|
|
return true;
|
|
|
|
// Either add or remove the key depending on its status
|
|
if (isDown && !alreadyPressed)
|
|
pressedDeviceKeys.Add(key);
|
|
else if (!isDown && alreadyPressed)
|
|
pressedDeviceKeys.Remove(key);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private KeyboardModifierKey UpdateModifierKeys(ArtemisDevice? device, KeyboardKey key, in bool isDown)
|
|
{
|
|
KeyboardModifierKey modifiers = _globalModifiers;
|
|
if (device != null)
|
|
_keyboardModifier.TryGetValue(device, out modifiers);
|
|
|
|
if (key == KeyboardKey.LeftAlt || key == KeyboardKey.RightAlt)
|
|
{
|
|
if (isDown)
|
|
modifiers |= KeyboardModifierKey.Alt;
|
|
else
|
|
modifiers &= ~KeyboardModifierKey.Alt;
|
|
}
|
|
else if (key == KeyboardKey.LeftCtrl || key == KeyboardKey.RightCtrl)
|
|
{
|
|
if (isDown)
|
|
modifiers |= KeyboardModifierKey.Control;
|
|
else
|
|
modifiers &= ~KeyboardModifierKey.Control;
|
|
}
|
|
else if (key == KeyboardKey.LeftShift || key == KeyboardKey.RightShift)
|
|
{
|
|
if (isDown)
|
|
modifiers |= KeyboardModifierKey.Shift;
|
|
else
|
|
modifiers &= ~KeyboardModifierKey.Shift;
|
|
}
|
|
else if (key == KeyboardKey.LWin || key == KeyboardKey.RWin)
|
|
{
|
|
if (isDown)
|
|
modifiers |= KeyboardModifierKey.Windows;
|
|
else
|
|
modifiers &= ~KeyboardModifierKey.Windows;
|
|
}
|
|
|
|
if (device != null)
|
|
_keyboardModifier[device] = modifiers;
|
|
else
|
|
_globalModifiers = modifiers;
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mouse
|
|
|
|
private void InputProviderOnMouseButtonDataReceived(object? sender, InputProviderMouseButtonEventArgs e)
|
|
{
|
|
bool foundLedId = InputKeyUtilities.MouseButtonLedIdMap.TryGetValue(e.Button, out LedId ledId);
|
|
ArtemisLed? led = null;
|
|
if (foundLedId && e.Device != null)
|
|
led = e.Device.Leds.FirstOrDefault(l => l.RgbLed.Id == ledId);
|
|
|
|
// Create the UpDown event args because it can be used for every event
|
|
ArtemisMouseButtonUpDownEventArgs eventArgs = new(e.Device, led, e.Button, e.IsDown);
|
|
OnMouseButtonUpDown(eventArgs);
|
|
if (e.IsDown)
|
|
OnMouseButtonDown(eventArgs);
|
|
else
|
|
OnMouseButtonUp(eventArgs);
|
|
|
|
// _logger.Verbose("Mouse button data: LED ID: {ledId}, button: {button}, is down: {isDown}, device: {device} ", ledId, e.Button, e.IsDown, e.Device);
|
|
}
|
|
|
|
private void InputProviderOnMouseScrollDataReceived(object? sender, InputProviderMouseScrollEventArgs e)
|
|
{
|
|
OnMouseScroll(new ArtemisMouseScrollEventArgs(e.Device, e.Direction, e.Delta));
|
|
// _logger.Verbose("Mouse scroll data: Direction: {direction}, delta: {delta}, device: {device} ", e.Direction, e.Delta, e.Device);
|
|
}
|
|
|
|
private void InputProviderOnMouseMoveDataReceived(object? sender, InputProviderMouseMoveEventArgs e)
|
|
{
|
|
OnMouseMove(new ArtemisMouseMoveEventArgs(e.Device, e.CursorX, e.CursorY, e.DeltaX, e.DeltaY));
|
|
// _logger.Verbose("Mouse move data: XY: {X},{Y} - delta XY: {deltaX},{deltaY} - device: {device} ", e.CursorX, e.CursorY, e.DeltaX, e.DeltaY, e.Device);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler<ArtemisKeyboardKeyUpDownEventArgs>? KeyboardKeyUpDown;
|
|
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyDown;
|
|
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyUp;
|
|
public event EventHandler<ArtemisMouseButtonUpDownEventArgs>? MouseButtonUpDown;
|
|
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonDown;
|
|
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonUp;
|
|
public event EventHandler<ArtemisMouseScrollEventArgs>? MouseScroll;
|
|
public event EventHandler<ArtemisMouseMoveEventArgs>? MouseMove;
|
|
public event EventHandler? DeviceIdentified;
|
|
|
|
protected virtual void OnKeyboardKeyUpDown(ArtemisKeyboardKeyUpDownEventArgs e)
|
|
{
|
|
KeyboardKeyUpDown?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnKeyboardKeyDown(ArtemisKeyboardKeyEventArgs e)
|
|
{
|
|
KeyboardKeyDown?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnKeyboardKeyUp(ArtemisKeyboardKeyEventArgs e)
|
|
{
|
|
KeyboardKeyUp?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnMouseButtonUpDown(ArtemisMouseButtonUpDownEventArgs e)
|
|
{
|
|
MouseButtonUpDown?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnMouseButtonDown(ArtemisMouseButtonEventArgs e)
|
|
{
|
|
MouseButtonDown?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnMouseButtonUp(ArtemisMouseButtonEventArgs e)
|
|
{
|
|
MouseButtonUp?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnMouseScroll(ArtemisMouseScrollEventArgs e)
|
|
{
|
|
MouseScroll?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnMouseMove(ArtemisMouseMoveEventArgs e)
|
|
{
|
|
MouseMove?.Invoke(this, e);
|
|
}
|
|
|
|
protected virtual void OnDeviceIdentified()
|
|
{
|
|
DeviceIdentified?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |