EonaCat.HID/EonaCat.HID/Managers/HidManagerLinux.cs

178 lines
5.9 KiB
C#

using EonaCat.HID.EventArguments;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace EonaCat.HID.Managers.Linux
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/license for full license details.
internal sealed class HidManagerLinux : IHidManager
{
public event EventHandler<HidEventArgs> OnDeviceInserted;
public event EventHandler<HidEventArgs> OnDeviceRemoved;
public event EventHandler<string> OnDeviceError;
public IEnumerable<IHid> Enumerate(ushort? vendorId = null, ushort? productId = null)
{
var hidrawDir = "/sys/class/hidraw/";
if (!Directory.Exists(hidrawDir))
{
yield break;
}
var hidrawEntries = Directory.GetDirectories(hidrawDir).Where(d => d.Contains("hidraw"));
foreach (var hidrawEntry in hidrawEntries)
{
var devName = Path.GetFileName(hidrawEntry);
var devPath = "/dev/" + devName;
if (!File.Exists(devPath))
{
continue;
}
var dev = new HidLinux(devPath);
dev.Setup();
if (vendorId.HasValue && dev.VendorId != vendorId)
{
continue;
}
if (productId.HasValue && dev.ProductId != productId)
{
continue;
}
yield return dev;
}
}
private void DeviceInserted(IHid device)
{
OnDeviceInserted?.Invoke(this, new HidEventArgs(device));
}
private void DeviceRemoved(IHid device)
{
OnDeviceRemoved?.Invoke(this, new HidEventArgs(device));
}
public HidManagerLinux()
{
Task.Run(() => MonitorDevices());
}
private void MonitorDevices()
{
var previousDevices = new Dictionary<string, IHid>();
const int maxErrors = 10;
TimeSpan errorWindow = TimeSpan.FromMinutes(5);
Queue<DateTime> errorTimestamps = new Queue<DateTime>();
while (true)
{
try
{
var currentDevices = Enumerate().ToList();
var currentDeviceDict = currentDevices.ToDictionary(d => d.DevicePath);
// Detect new devices
foreach (var kvp in currentDeviceDict)
{
if (!previousDevices.ContainsKey(kvp.Key))
{
DeviceInserted(kvp.Value);
}
}
// Detect removed devices
foreach (var kvp in previousDevices)
{
if (!currentDeviceDict.ContainsKey(kvp.Key))
{
DeviceRemoved(kvp.Value);
}
}
previousDevices = currentDeviceDict;
// Clear error log on success
errorTimestamps.Clear();
}
catch (Exception ex)
{
OnDeviceError?.Invoke(this, $"[MonitorDevices] Error: {ex.Message}");
var now = DateTime.UtcNow;
errorTimestamps.Enqueue(now);
// Remove timestamps outside the 5-minute window
while (errorTimestamps.Count > 0 && now - errorTimestamps.Peek() > errorWindow)
{
errorTimestamps.Dequeue();
}
if (errorTimestamps.Count >= maxErrors)
{
Console.Error.WriteLine($"[MonitorDevices] Too many errors ({errorTimestamps.Count}) in the last 5 minutes. Monitoring stopped.");
break;
}
}
Thread.Sleep(1000); // Poll every second
}
}
}
internal static class NativeMethods
{
public const int O_RDWR = 0x0002;
public const int O_NONBLOCK = 0x800;
[DllImport("libc", SetLastError = true)]
public static extern int open(string pathname, int flags);
[DllImport("libc", SetLastError = true)]
public static extern int close(int fd);
[DllImport("libc", SetLastError = true)]
public static extern int ioctl(int fd, int request, IntPtr data);
// _IOC macro emulation
public const int _IOC_NRBITS = 8;
public const int _IOC_TYPEBITS = 8;
public const int _IOC_SIZEBITS = 14;
public const int _IOC_DIRBITS = 2;
public const int _IOC_NRMASK = (1 << _IOC_NRBITS) - 1;
public const int _IOC_TYPEMASK = (1 << _IOC_TYPEBITS) - 1;
public const int _IOC_SIZEMASK = (1 << _IOC_SIZEBITS) - 1;
public const int _IOC_DIRMASK = (1 << _IOC_DIRBITS) - 1;
public const int _IOC_NRSHIFT = 0;
public const int _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS;
public const int _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS;
public const int _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS;
public const int _IOC_NONE = 0;
public const int _IOC_WRITE = 1;
public const int _IOC_READ = 2;
public static int _IOC(int dir, int type, int nr, int size)
{
return ((dir & _IOC_DIRMASK) << _IOC_DIRSHIFT) |
((type & _IOC_TYPEMASK) << _IOC_TYPESHIFT) |
((nr & _IOC_NRMASK) << _IOC_NRSHIFT) |
((size & _IOC_SIZEMASK) << _IOC_SIZESHIFT);
}
}
}