diff --git a/src/Artemis.UI.Linux/Providers/Input/ArtemisLinuxInputProviderException.cs b/src/Artemis.UI.Linux/Providers/Input/ArtemisLinuxInputProviderException.cs new file mode 100644 index 000000000..6642c17de --- /dev/null +++ b/src/Artemis.UI.Linux/Providers/Input/ArtemisLinuxInputProviderException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.UI.Linux.Providers.Input +{ + public class ArtemisLinuxInputProviderException : Exception + { + public ArtemisLinuxInputProviderException() : base() + { + } + + public ArtemisLinuxInputProviderException(string? message) : base(message) + { + } + + public ArtemisLinuxInputProviderException(string? message, Exception? innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxAbsoluteAxis.cs b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxAbsoluteAxis.cs similarity index 100% rename from src/Artemis.UI.Linux/Providers/Input/LinuxAbsoluteAxis.cs rename to src/Artemis.UI.Linux/Providers/Input/Enums/LinuxAbsoluteAxis.cs diff --git a/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxDeviceType.cs b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxDeviceType.cs new file mode 100644 index 000000000..22e956420 --- /dev/null +++ b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxDeviceType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.UI.Linux.Providers.Input +{ + public enum LinuxDeviceType + { + Keyboard, + Mouse, + Gamepad + } +} diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputEventType.cs b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxInputEventType.cs similarity index 93% rename from src/Artemis.UI.Linux/Providers/Input/LinuxInputEventType.cs rename to src/Artemis.UI.Linux/Providers/Input/Enums/LinuxInputEventType.cs index dadeb76aa..9701e1717 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputEventType.cs +++ b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxInputEventType.cs @@ -9,57 +9,57 @@ namespace Artemis.UI.Linux.Providers.Input /// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol. /// SYN = 0x00, - + /// /// Used to describe state changes of keyboards, buttons, or other key-like devices. /// KEY = 0x01, - + /// /// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left. /// REL = 0x02, - + /// /// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen. /// ABS = 0x03, - + /// /// Used to describe miscellaneous input data that do not fit into other types. /// MSC = 0x04, - + /// /// Used to describe binary state input switches. /// SW = 0x05, - + /// /// Used to turn LEDs on devices on and off. /// LED = 0x11, - + /// /// Used to output sound to devices. /// SND = 0x12, - + /// /// Used for autorepeating devices. /// REP = 0x14, - + /// /// Used to send force feedback commands to an input device. /// FF = 0x15, - + /// /// A special type for power button and switch input. /// PWR = 0x16, - + /// /// Used to receive force feedback device status. /// diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxKeyboardKeyCodes.cs b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxKeyboardKeyCodes.cs similarity index 100% rename from src/Artemis.UI.Linux/Providers/Input/LinuxKeyboardKeyCodes.cs rename to src/Artemis.UI.Linux/Providers/Input/Enums/LinuxKeyboardKeyCodes.cs diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxRelativeAxis.cs b/src/Artemis.UI.Linux/Providers/Input/Enums/LinuxRelativeAxis.cs similarity index 100% rename from src/Artemis.UI.Linux/Providers/Input/LinuxRelativeAxis.cs rename to src/Artemis.UI.Linux/Providers/Input/Enums/LinuxRelativeAxis.cs diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDevice.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDevice.cs index b944da4d4..9fae6eee2 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDevice.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDevice.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections; +using Artemis.Core; using System.Collections.Generic; using System.Linq; -using System.Reflection.Metadata; -using Humanizer; namespace Artemis.UI.Linux.Providers.Input { @@ -12,82 +9,81 @@ namespace Artemis.UI.Linux.Providers.Input /// public class LinuxInputDevice { - public string InputId { get; } - public string? Bus { get; } - public string? Vendor { get; } - public string? Product { get; } - public string? Version { get; } - public string? Name { get; } - public string? Phys { get; } - public string? Sysfs { get; } - public string? Uniq { get; } - public string[]? Handlers { get; } - public bool IsMouse => Handlers.Any(h => h.Contains("mouse")); - public bool IsKeyboard => Handlers.Any(h => h.Contains("kbd")); - public bool IsGamePad => Handlers.Any(h => h.Contains("js")); - public string EventPath => $"/dev/input/{Handlers.First(h => h.Contains("event"))}"; + public LinuxInputId InputId { get; } + public string Name { get; } + public string[] Handlers { get; } + public string EventPath { get; } + public LinuxDeviceType DeviceType { get; } public LinuxInputDevice(IEnumerable lines) { foreach (string line in lines) { - char dataType = line.First(); - string data = line.Substring(3); //get the first character in each line and set the according property with relevant data + + char dataType = line[0]; + string data = line[3..]; switch (dataType) { case 'I': - InputId = data; - foreach (string component in data.Split(" ")) - { - string?[] parts = component.Split('='); - switch (parts[0]) - { - case "Bus": - Bus = parts[1]; - break; - case "Vendor": - Vendor = parts[1]; - break; - case "Product": - Product = parts[1]; - break; - case "Version": - Version = parts[1]; - break; - default: - break; - } - } + InputId = new LinuxInputId(data); break; case 'N': - Name = data.Replace("\"", "") - .Replace("Name=", ""); - break; - case 'P': - Phys = data.Replace("Phys=", ""); - break; - case 'S': - Sysfs = data.Replace("Sysfs=", ""); + Name = data.Replace("\"", "").Replace("Name=", ""); break; case 'H': Handlers = data.Replace("Handlers=", "").Split(" "); - break; - case 'U': - Uniq = data.Replace("Uniq=", ""); + + if (Handlers?.Any(h => h.Contains("mouse")) == true) + DeviceType = LinuxDeviceType.Mouse; + else if (Handlers?.Any(h => h.Contains("kbd")) == true) + DeviceType = LinuxDeviceType.Keyboard; + else if (Handlers?.Any(h => h.Contains("js")) == true) + DeviceType = LinuxDeviceType.Gamepad; + + var evt = Handlers!.First(h => h.Contains("event")); + + EventPath = $"/dev/input/{evt}"; break; default: //do we need any more of this data? break; } } + + if (InputId is null || Name is null || Handlers is null || EventPath is null) + { + throw new ArtemisLinuxInputProviderException("Linux device definition did not contain necessary data"); + } } - + #region Overrides of Object /// public override string ToString() => $"{Name} - {EventPath}"; #endregion + + public class LinuxInputId + { + public string Bus { get; } + public string Vendor { get; } + public string Product { get; } + public string Version { get; } + + public LinuxInputId(string line) + { + var components = line.Split(" ") + .Select(c => c.Split('=')) + .ToDictionary(c => c[0], c => c[1]); + + Bus = components["Bus"]; + Vendor = components["Vendor"]; + Product = components["Product"]; + Version = components["Version"]; + } + + public override string ToString() => $"Bus={Bus} Vendor={Vendor} Product={Product} Version={Version}"; + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceFinder.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceFinder.cs index 7268b1ee4..60082f2f3 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceFinder.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceFinder.cs @@ -8,11 +8,11 @@ namespace Artemis.UI.Linux.Providers.Input public static class LinuxInputDeviceFinder { private const string DEVICES_FILE = "/proc/bus/input/devices"; - + public static IEnumerable Find() { return File.ReadAllLines(DEVICES_FILE) - .PartitionBy(s => s == "") //split on empty lines + .PartitionBy(s => s?.Length == 0) //split on empty lines .Select(lineGroup => new LinuxInputDevice(lineGroup)); } @@ -33,10 +33,10 @@ namespace Artemis.UI.Linux.Providers.Input return groupNumber; }; return a - .Select(x => new { Value = x, GroupNumber = getGroupNumber(predicate(x))} ) + .Select(x => new { Value = x, GroupNumber = getGroupNumber(predicate(x)) }) .Where(x => x.GroupNumber != null) .GroupBy(x => x.GroupNumber) - .Select(g => g.Select(x => x.Value)); + .Select(g => g.Select(x => x.Value)); } } } \ No newline at end of file diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceReader.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceReader.cs index 81e4bd6c2..827f49510 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceReader.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputDeviceReader.cs @@ -3,7 +3,6 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Artemis.UI.Linux.Utilities; namespace Artemis.UI.Linux.Providers.Input { diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs index ea8baa816..65422712d 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Linux.Utilities; using Serilog; @@ -20,7 +21,7 @@ namespace Artemis.UI.Linux.Providers.Input foreach (LinuxInputDevice deviceDefinition in LinuxInputDeviceFinder.Find()) { - LinuxInputDeviceReader? reader = new LinuxInputDeviceReader(deviceDefinition); + LinuxInputDeviceReader? reader = new(deviceDefinition); reader.InputEvent += OnInputEvent; _readers.Add(reader); } @@ -30,68 +31,103 @@ namespace Artemis.UI.Linux.Providers.Input { if (sender is not LinuxInputDeviceReader reader) return; - - if (reader.InputDevice.IsKeyboard) + switch (reader.InputDevice.DeviceType) { - HandleKeyboardData(reader.InputDevice, e); - } - else if (reader.InputDevice.IsMouse) - { - HandleMouseData(reader.InputDevice, e); - } - else if (reader.InputDevice.IsGamePad) - { - //TODO: handle game pad input? + case LinuxDeviceType.Keyboard: + HandleKeyboardData(reader.InputDevice, e); + break; + case LinuxDeviceType.Mouse: + HandleMouseData(reader.InputDevice, e); + break; + case LinuxDeviceType.Gamepad: + break; } } - private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs e) + private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs args) { - switch (e.Type) + switch (args.Type) { case LinuxInputEventType.KEY: - KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) e.Code); - bool isDown = e.Value != 0; + KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes)args.Code); + bool isDown = args.Value != 0; - _logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes) e.Code} | Down: {isDown}"); - - //TODO: identify + _logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}"); - OnKeyboardDataReceived(null, key, isDown); + var identifier = keyboard.InputId; + + OnIdentifierReceived(identifier, InputDeviceType.Keyboard); + + ArtemisDevice? device = null; + + try + { + device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard); + } + catch (Exception e) + { + _logger.Warning(e, "Failed to retrieve input device by its identifier"); + } + + OnKeyboardDataReceived(device, key, isDown); break; default: - _logger.Verbose($"Unknown keyboard event type: {e.Type}"); + _logger.Verbose($"Unknown keyboard event type: {args.Type}"); break; } } - private void HandleMouseData(LinuxInputDevice mouse, LinuxInputEventArgs e) + private void HandleMouseData(LinuxInputDevice mouse, LinuxInputEventArgs args) { - switch (e.Type) + var identifier = mouse.InputId; + + OnIdentifierReceived(identifier, InputDeviceType.Mouse); + + ArtemisDevice? device = null; + + try + { + device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Mouse); + } + catch (Exception e) + { + _logger.Warning(e, "Failed to retrieve input device by its identifier"); + } + + switch (args.Type) { case LinuxInputEventType.KEY: - bool isDown = e.Value != 0; - MouseButton button = InputUtilities.MouseButtonFromButtonCode((LinuxKeyboardKeyCodes)e.Code); + bool isDown = args.Value != 0; + MouseButton button = InputUtilities.MouseButtonFromButtonCode((LinuxKeyboardKeyCodes)args.Code); - _logger.Verbose($"Mouse Button: {(LinuxKeyboardKeyCodes) e.Code} | Down: {isDown}"); + _logger.Verbose($"Mouse Button: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}"); - //TODO: identify - - OnMouseButtonDataReceived(null, button, isDown); + OnMouseButtonDataReceived(device, button, isDown); break; - case LinuxInputEventType.ABS: - LinuxAbsoluteAxis absoluteAxis = (LinuxAbsoluteAxis) e.Code; - _logger.Verbose($"Absolute mouse: axis={absoluteAxis} | value={e.Value}"); - break; case LinuxInputEventType.REL: - LinuxRelativeAxis relativeAxis = (LinuxRelativeAxis) e.Code; - _logger.Verbose($"Relative mouse: axis={relativeAxis} | value={e.Value}"); + LinuxRelativeAxis relativeAxis = (LinuxRelativeAxis)args.Code; - //TODO: handle mouse movement + _logger.Verbose($"Relative mouse: axis={relativeAxis} | value={args.Value}"); + + switch (relativeAxis) + { + case LinuxRelativeAxis.REL_WHEEL: + OnMouseScrollDataReceived(device, MouseScrollDirection.Vertical, args.Value); + break; + case LinuxRelativeAxis.REL_HWHEEL: + OnMouseScrollDataReceived(device, MouseScrollDirection.Horizontal, args.Value); + break; + case LinuxRelativeAxis.REL_X: + OnMouseMoveDataReceived(device, 0, 0, args.Value, 0); + break; + case LinuxRelativeAxis.REL_Y: + OnMouseMoveDataReceived(device, 0, 0, 0, args.Value); + break; + } break; default: - _logger.Verbose($"Unknown mouse event type: {e.Type}"); + _logger.Verbose($"Unknown mouse event type: {args.Type}"); break; } }