Initial Commit

This commit is contained in:
2023-06-01 12:48:01 -07:00
commit 1f5deedc73
35 changed files with 2262 additions and 0 deletions

View File

@ -0,0 +1,6 @@
namespace Barcode.Host.Shared.KeyboardMouse.Abstract;
public interface IAggregateInputReader
{
event InputReader.RaiseKeyPress OnKeyPress;
}

View File

@ -0,0 +1,72 @@
using Barcode.Host.Shared.KeyboardMouse.Abstract;
using Microsoft.Extensions.Logging;
namespace Barcode.Host.Shared.KeyboardMouse;
public class AggregateInputReader : IDisposable, IAggregateInputReader
{
private readonly ILogger<InputReader> _InputReaderLogger;
private Dictionary<string, InputReader>? _Readers = new();
public event InputReader.RaiseKeyPress? OnKeyPress;
public AggregateInputReader(ILogger<InputReader> inputReaderLogger)
{
_InputReaderLogger = inputReaderLogger;
System.Timers.Timer timer = new()
{
Interval = 10 * 1000,
Enabled = true
};
timer.Elapsed += (_, _) => Scan();
timer.Start();
}
private void ReaderOnOnKeyPress(KeyPressEvent e) => OnKeyPress?.Invoke(e);
private void Scan()
{
string[] files = Directory.GetFiles("/dev/input/", "event*");
foreach (string file in files)
{
if (_Readers is not null && _Readers.ContainsKey(file))
{
continue;
}
InputReader reader = new(file, _InputReaderLogger);
reader.OnKeyPress += ReaderOnOnKeyPress;
_Readers?.Add(file, reader);
}
IEnumerable<InputReader>? deadReaders = _Readers?.Values.Where(r => r.Faulted);
if (deadReaders is not null)
{
foreach (InputReader? dr in deadReaders)
{
_ = _Readers?.Remove(dr.Path);
dr.OnKeyPress -= ReaderOnOnKeyPress;
dr.Dispose();
}
}
}
public void Dispose()
{
if (_Readers is not null)
{
foreach (InputReader d in _Readers.Values)
{
d.OnKeyPress -= ReaderOnOnKeyPress;
d.Dispose();
}
}
_Readers = null;
}
}

View File

@ -0,0 +1,82 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public static class DeviceReader
{
public static IEnumerable<LinuxDevice> Get(string path = "/proc/bus/input/devices")
{
List<LinuxDevice> devices = new();
using FileStream filestream = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(filestream);
LinuxDevice linuxDevice = new();
while (!reader.EndOfStream)
{
string? line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
{
if (!string.IsNullOrWhiteSpace(linuxDevice.Name))
{
devices.Add(linuxDevice);
linuxDevice = new LinuxDevice();
}
continue;
}
if (line.StartsWith("I"))
ApplyIdentifier(line, linuxDevice);
else if (line.StartsWith("N"))
linuxDevice.Name = line.Substring(9, line.Length - 9 - 1);
else if (line.StartsWith("P"))
linuxDevice.PhysicalPath = line[8..];
else if (line.StartsWith("S"))
linuxDevice.SysFsPath = line[9..];
else if (line.StartsWith("U"))
linuxDevice.UniqueIdentificationCode = line[8..];
else if (line.StartsWith("H"))
linuxDevice.Handlers = line[12..]
.Split(" ")
.Where(h => !string.IsNullOrWhiteSpace(h))
.ToList();
else if (line.StartsWith("B"))
linuxDevice.Bitmaps.Add(line[3..]);
}
return devices;
}
private static void ApplyIdentifier(string line, LinuxDevice linuxDevice)
{
string[] values = line[3..]
.Split(" ");
foreach (string v in values)
{
string[] kvp = v.Split("=");
switch (kvp[0])
{
case "Bus":
linuxDevice.Identifier.Bus = kvp[1];
break;
case "Vendor":
linuxDevice.Identifier.Vendor = kvp[1];
break;
case "Product":
linuxDevice.Identifier.Product = kvp[1];
break;
case "Version":
linuxDevice.Identifier.Version = kvp[1];
break;
}
}
}
}

View File

@ -0,0 +1,277 @@
namespace Barcode.Host.Shared.KeyboardMouse;
/// <summary>
/// Mapping for this can be found here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
/// </summary>
public enum EventCode
{
Reserved = 0,
Esc = 1,
Num1 = 2,
Num2 = 3,
Num3 = 4,
Num4 = 5,
Num5 = 6,
Num6 = 7,
Num7 = 8,
Num8 = 9,
Num9 = 10,
Num0 = 11,
Minus = 12,
Equal = 13,
Backspace = 14,
Tab = 15,
Q = 16,
W = 17,
E = 18,
R = 19,
T = 20,
Y = 21,
U = 22,
I = 23,
O = 24,
P = 25,
LeftBrace = 26,
RightBrace = 27,
Enter = 28,
LeftCtrl = 29,
A = 30,
S = 31,
D = 32,
F = 33,
G = 34,
H = 35,
J = 36,
K = 37,
L = 38,
Semicolon = 39,
Apostrophe = 40,
Grave = 41,
LeftShift = 42,
Backslash = 43,
Z = 44,
X = 45,
C = 46,
V = 47,
B = 48,
N = 49,
M = 50,
Comma = 51,
Dot = 52,
Slash = 53,
RightShift = 54,
KpAsterisk = 55,
LeftAlt = 56,
Space = 57,
Capslock = 58,
F1 = 59,
Pf2 = 60,
F3 = 61,
F4 = 62,
F5 = 63,
F6 = 64,
F7 = 65,
F8 = 66,
Pf9 = 67,
F10 = 68,
Numlock = 69,
ScrollLock = 70,
Kp7 = 71,
Kp8 = 72,
Kp9 = 73,
PkpMinus = 74,
Kp4 = 75,
Kp5 = 76,
Kp6 = 77,
KpPlus = 78,
Kp1 = 79,
Kp2 = 80,
Kp3 = 81,
Kp0 = 82,
KpDot = 83,
Zenkakuhankaku = 85,
//102ND = 86,
F11 = 87,
F12 = 88,
Ro = 89,
Katakana = 90,
Hiragana = 91,
Henkan = 92,
Katakanahiragana = 93,
Muhenkan = 94,
KpJpComma = 95,
KpEnter = 96,
RightCtrl = 97,
KpSlash = 98,
SysRq = 99,
RightAlt = 100,
LineFeed = 101,
Home = 102,
Up = 103,
Pageup = 104,
Left = 105,
Right = 106,
End = 107,
Down = 108,
Pagedown = 109,
Insert = 110,
Delete = 111,
Macro = 112,
Mute = 113,
VolumeDown = 114,
VolumeUp = 115,
Power = 116, // SC System Power Down
KpEqual = 117,
KpPlusMinus = 118,
Pause = 119,
Scale = 120, // AL Compiz Scale (Expose)
KpComma = 121,
Hangeul = 122,
Hanja = 123,
Yen = 124,
LeftMeta = 125,
RightMeta = 126,
Compose = 127,
Stop = 128, // AC Stop
Again = 129,
Props = 130, // AC Properties
Undo = 131, // AC Undo
Front = 132,
Copy = 133, // AC Copy
Open = 134, // AC Open
Paste = 135, // AC Paste
Find = 136, // AC Search
Cut = 137, // AC Cut
Help = 138, // AL Integrated Help Center
Menu = 139, // Menu (show menu)
Calc = 140, // AL Calculator
Setup = 141,
Sleep = 142, // SC System Sleep
Wakeup = 143, // System Wake Up
File = 144, // AL Local Machine Browser
Sendfile = 145,
DeleteFile = 146,
Xfer = 147,
Prog1 = 148,
Prog2 = 149,
Www = 150, // AL Internet Browser
MsDos = 151,
Coffee = 152, // AL Terminal Lock/Screensaver
RotateDisplay = 153, // Display orientation for e.g. tablets
CycleWindows = 154,
Mail = 155,
Bookmarks = 156, // AC Bookmarks
Computer = 157,
Back = 158, // AC Back
Forward = 159, // AC Forward
CloseCd = 160,
EjectCd = 161,
EjectCloseCd = 162,
NextSong = 163,
PlayPause = 164,
PreviousSong = 165,
StopCd = 166,
Record = 167,
Rewind = 168,
Phone = 169, // Media Select Telephone
Iso = 170,
Config = 171, // AL Consumer Control Configuration
Homepage = 172, // AC Home
Refresh = 173, // AC Refresh
Exit = 174, // AC Exit
Move = 175,
Edit = 176,
ScrollUp = 177,
ScrollDown = 178,
KpLeftParen = 179,
KpRightParen = 180,
New = 181, // AC New
Redo = 182, // AC Redo/Repeat
F13 = 183,
F14 = 184,
F15 = 185,
F16 = 186,
F17 = 187,
F18 = 188,
F19 = 189,
F20 = 190,
F21 = 191,
F22 = 192,
F23 = 193,
F24 = 194,
PlayCd = 200,
PauseCd = 201,
Prog3 = 202,
Prog4 = 203,
Dashboard = 204, // AL Dashboard
Suspend = 205,
Close = 206, // AC Close
Play = 207,
FastForward = 208,
BassBoost = 209,
Print = 210, // AC Print
Hp = 211,
Camera = 212,
Sound = 213,
Question = 214,
Email = 215,
Chat = 216,
Search = 217,
Connect = 218,
Finance = 219, // AL Checkbook/Finance
Sport = 220,
Shop = 221,
AltErase = 222,
Cancel = 223, // AC Cancel
BrightnessDown = 224,
BrightnessUp = 225,
Media = 226,
SwitchVideoMode = 227, // Cycle between available video outputs (Monitor/LCD/TV-out/etc)
KbdIllumToggle = 228,
KbdIllumDown = 229,
KbdIllumUp = 230,
Send = 231, // AC Send
Reply = 232, // AC Reply
ForwardMail = 233, // AC Forward Msg
Save = 234, // AC Save
Documents = 235,
Battery = 236,
Bluetooth = 237,
Wlan = 238,
Uwb = 239,
Unknown = 240,
VideoNext = 241, // drive next video source
VideoPrev = 242, // drive previous video source
BrightnessCycle = 243, // brightness up, after max is min
BrightnessAuto = 244, // Set Auto Brightness: manual brightness control is off, rely on ambient
DisplayOff = 245, // display device to off state
Wwan = 246, // Wireless WAN (LTE, UMTS, GSM, etc.)
RfKill = 247, // Key that controls all radios
MicMute = 248, // Mute / unmute the microphone
LeftMouse = 272,
RightMouse = 273,
MiddleMouse = 274,
MouseBack = 275,
MouseForward = 276,
ToolFinger = 325,
ToolQuintTap = 328,
Touch = 330,
ToolDoubleTap = 333,
ToolTripleTap = 334,
ToolQuadTap = 335,
Mic = 582
}

View File

@ -0,0 +1,64 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public enum EventType
{
/// <summary>
/// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
/// </summary>
EV_SYN,
/// <summary>
/// Used to describe state changes of keyboards, buttons, or other key-like devices.
/// </summary>
EV_KEY,
/// <summary>
/// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
/// </summary>
EV_REL,
/// <summary>
/// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
/// </summary>
EV_ABS,
/// <summary>
/// Used to describe miscellaneous input data that do not fit into other types.
/// </summary>
EV_MSC,
/// <summary>
/// Used to describe binary state input switches.
/// </summary>
EV_SW,
/// <summary>
/// Used to turn LEDs on devices on and off.
/// </summary>
EV_LED,
/// <summary>
/// Used to output sound to devices.
/// </summary>
EV_SND,
/// <summary>
/// Used for autorepeating devices.
/// </summary>
EV_REP,
/// <summary>
/// Used to send force feedback commands to an input device.
/// </summary>
EV_FF,
/// <summary>
/// A special type for power button and switch input.
/// </summary>
EV_PWR,
/// <summary>
/// Used to receive force feedback device status.
/// </summary>
EV_FF_STATUS,
}

View File

@ -0,0 +1,166 @@
using Microsoft.Extensions.Logging;
namespace Barcode.Host.Shared.KeyboardMouse;
public class InputReader : IDisposable
{
private readonly ILogger<InputReader> _Logger;
private const int _BufferLength = 24;
private static readonly int _PiOffset;
private readonly byte[] _Buffer = new byte[_BufferLength];
private FileStream? _Stream;
private bool _Disposing;
public delegate void RaiseKeyPress(KeyPressEvent e);
public delegate void RaiseMouseMove(MouseMoveEvent e);
public event RaiseKeyPress? OnKeyPress;
public event RaiseMouseMove? OnMouseMove;
public string Path { get; }
public bool Faulted { get; private set; }
static InputReader()
{
if (RunningOnRaspberryPi())
{
_PiOffset = -8;
}
}
public InputReader(
string path,
ILogger<InputReader> logger)
{
_Logger = logger;
Path = path;
try
{
_Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
catch (UnauthorizedAccessException ex)
{
_Logger.LogError(ex, "Current user doesn't have permissions to access input data. Add user to input group to correct this error");
Faulted = true;
}
catch (IOException ex)
{
_Logger.LogWarning(ex, $"Error occurred while trying to build stream for {path}");
Faulted = true;
}
_ = Task.Run(Run);
}
private void Run()
{
while (true)
{
if (_Disposing)
break;
try
{
if (!Faulted && _Stream is not null)
{
_ = _Stream.Read(_Buffer, 0, _BufferLength);
}
}
catch (IOException ex)
{
_Logger.LogInformation(ex, $"Error occured while trying to read from the stream for {Path}");
Faulted = true;
}
EventType type = GetEventType();
short code = GetCode();
int value = GetValue();
switch (type)
{
case EventType.EV_KEY:
HandleKeyPressEvent(code, value);
break;
case EventType.EV_REL:
MouseAxis axis = (MouseAxis)code;
MouseMoveEvent e = new(axis, value);
OnMouseMove?.Invoke(e);
break;
}
}
}
private int GetValue()
{
byte[] valueBits = new[]
{
_Buffer[20 + _PiOffset],
_Buffer[21 + _PiOffset],
_Buffer[22 + _PiOffset],
_Buffer[23 + _PiOffset]
};
int value = BitConverter.ToInt32(valueBits, 0);
return value;
}
private short GetCode()
{
byte[] codeBits = new[]
{
_Buffer[18 + _PiOffset],
_Buffer[19 + _PiOffset]
};
short code = BitConverter.ToInt16(codeBits, 0);
return code;
}
private EventType GetEventType()
{
byte[] typeBits = new[]
{
_Buffer[16 + _PiOffset],
_Buffer[17 + _PiOffset]
};
short type = BitConverter.ToInt16(typeBits, 0);
EventType eventType = (EventType)type;
return eventType;
}
private void HandleKeyPressEvent(short code, int value)
{
EventCode c = (EventCode)code;
KeyState s = (KeyState)value;
KeyPressEvent e = new(c, s);
OnKeyPress?.Invoke(e);
}
public void Dispose()
{
_Disposing = true;
_Stream?.Dispose();
_Stream = null;
}
private static bool RunningOnRaspberryPi()
{
string path = "/proc/cpuinfo";
string[] text = File.ReadAllLines(path);
bool runningOnPi = text.Any(l => l.Contains("Raspberry Pi"));
return runningOnPi;
}
}

View File

@ -0,0 +1,14 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public readonly struct KeyPressEvent
{
public KeyPressEvent(EventCode code, KeyState state)
{
Code = code;
State = state;
}
public EventCode Code { get; }
public KeyState State { get; }
}

View File

@ -0,0 +1,8 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public enum KeyState
{
KeyUp,
KeyDown,
KeyHold
}

View File

@ -0,0 +1,18 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public class LinuxDevice
{
public LinuxDeviceIdentifier Identifier { get; set; } = new();
public string? Name { get; set; }
public string? PhysicalPath { get; set; }
public string? SysFsPath { get; set; }
public string? UniqueIdentificationCode { get; set; }
public List<string> Handlers { get; set; } = new();
public List<string> Bitmaps { get; set; } = new();
}

View File

@ -0,0 +1,12 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public class LinuxDeviceIdentifier
{
public string? Bus { get; set; }
public string? Vendor { get; set; }
public string? Product { get; set; }
public string? Version { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public enum MouseAxis
{
X,
Y
}

View File

@ -0,0 +1,14 @@
namespace Barcode.Host.Shared.KeyboardMouse;
public readonly struct MouseMoveEvent
{
public MouseMoveEvent(MouseAxis axis, int amount)
{
Axis = axis;
Amount = amount;
}
public MouseAxis Axis { get; }
public int Amount { get; }
}