178 lines
5.9 KiB
C#
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);
|
|
}
|
|
}
|
|
} |