diff --git a/Analyzer/MainForm.cs b/Analyzer/MainForm.cs
index a964828..5933258 100644
--- a/Analyzer/MainForm.cs
+++ b/Analyzer/MainForm.cs
@@ -1,5 +1,6 @@
using EonaCat.HID.EventArguments;
using EonaCat.HID.Helpers;
+using EonaCat.HID.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -106,6 +107,8 @@ namespace EonaCat.HID.Analyzer
deviceName = device.ProductName;
deviceManufacturer = device.Manufacturer;
deviceSerialNumber = device.SerialNumber;
+ var isWritingSupported = device.IsWritingSupport;
+ var isReadingSupported = device.IsReadingSupport;
var row = new string[]
{
@@ -113,6 +116,8 @@ namespace EonaCat.HID.Analyzer
deviceName,
deviceManufacturer,
deviceSerialNumber,
+ isReadingSupported.ToString(),
+ isWritingSupported.ToString(),
device.InputReportByteLength.ToString(),
device.OutputReportByteLength.ToString(),
device.FeatureReportByteLength.ToString(),
@@ -256,7 +261,7 @@ namespace EonaCat.HID.Analyzer
{
try
{
- var str = $"Rx Input Report from device {e.Device.ProductName} => {BitConverter.ToString(e.Data)}";
+ var str = $"Rx Input Report from device {e.Device.ProductName} => {BitConverter.ToString(e.Report.Data)}";
AppendEventLog(str, Color.Blue);
}
catch (Exception ex)
@@ -269,7 +274,7 @@ namespace EonaCat.HID.Analyzer
{
try
{
- var str = string.Format("Removed Device --> VID {0:X4}, PID {0:X4}", e.Device.VendorId, e.Device.ProductId);
+ var str = string.Format("Removed Device {0} --> VID {1:X4}, PID {2:X4}", e.Device.ProductName, e.Device.VendorId, e.Device.ProductId);
AppendEventLog(str);
}
catch (Exception ex)
@@ -282,7 +287,7 @@ namespace EonaCat.HID.Analyzer
{
try
{
- var str = string.Format("Inserted Device --> VID {0:X4}, PID {0:X4}", e.Device.VendorId, e.Device.ProductId);
+ var str = string.Format("Inserted Device {0} --> VID {1:X4}, PID {2:X4}", e.Device.ProductName, e.Device.VendorId, e.Device.ProductId);
AppendEventLog(str, Color.Orange);
}
catch (Exception ex)
@@ -332,14 +337,14 @@ namespace EonaCat.HID.Analyzer
throw new Exception("This device has no Input Report support!");
}
- var buffer = await _device.ReadInputReportAsync();
- if (buffer.Length < 2)
+ var report = await _device.ReadInputReportAsync();
+ if (report == null || report.Data.Length < 2)
{
- AppendEventLog("Received report is too short to contain a valid Report ID.", Color.Red);
+ AppendEventLog("Received report is null or is too short to contain a valid Report ID.", Color.Red);
return;
}
- var str = string.Format("Rx Input Report [{0}] <-- {1}", buffer.Length, BitConverter.ToString(buffer));
+ var str = string.Format("Rx Input Report [{0}] <-- {1}", report.Data.Length, BitConverter.ToString(report.Data));
AppendEventLog(str, Color.Blue);
}
catch (Exception ex)
@@ -352,27 +357,33 @@ namespace EonaCat.HID.Analyzer
{
try
{
+ if (_device == null)
+ {
+ AppendEventLog("No device connected. Please select a device and click 'Connect'.", Color.Red);
+ return;
+ }
+
byte hidReportId = byte.Parse(comboBoxReportId.Text.Trim());
- byte[] buf = ByteHelper.HexStringToByteArray(textBoxWriteData.Text.Trim());
+ byte[] dataBuffer = ByteHelper.HexStringToByteArray(textBoxWriteData.Text.Trim());
- // Combine report ID and buffer
- byte[] outputReport = new byte[buf.Length + 1];
- outputReport[0] = hidReportId;
- Array.Copy(buf, 0, outputReport, 1, buf.Length);
+ if (_device.OutputReportByteLength <= 0)
+ {
+ throw new Exception("This device has no Output Report support!");
+ }
+ if (dataBuffer.Length > _device.OutputReportByteLength - 1)
+ {
+ throw new Exception("Output Report Length Exceeds allowed size.");
+ }
- try
- {
- await _device.WriteOutputReportAsync(outputReport);
- AppendEventLog($"Output report sent: {BitConverter.ToString(outputReport)}", Color.DarkGreen);
- }
- catch (Exception ex)
- {
- AppendEventLog("Write failed: " + ex.Message, Color.DarkRed);
- }
+ var outputReport = new HidReport(hidReportId, dataBuffer);
+
+ await _device.WriteOutputReportAsync(outputReport);
+
+ AppendEventLog($"Output report sent (Report ID: 0x{hidReportId:X2}): {ByteHelper.ByteArrayToHexString(dataBuffer)}", Color.DarkGreen);
}
catch (Exception ex)
{
- PopupException("Error preparing output report: " + ex.Message);
+ PopupException("Error sending output report: " + ex.Message);
}
}
@@ -387,8 +398,15 @@ namespace EonaCat.HID.Analyzer
throw new Exception("This device has no Feature Report support!");
}
- var buffer = await _device.GetFeatureReportAsync(hidReportId);
- var str = string.Format("Rx Feature Report [{0}] <-- {1}", buffer.Length, ByteHelper.ByteArrayToHexString(buffer));
+ HidReport report = await _device.GetFeatureReportAsync(hidReportId);
+ if (report == null || report.Data == null || report.Data.Length < 1)
+ {
+ AppendEventLog("Received feature report is null or too short.", Color.Red);
+ return;
+ }
+
+ string hexString = $"{report.ReportId:X2} - {ByteHelper.ByteArrayToHexString(report.Data)}";
+ var str = $"Rx Feature Report [{report.Data.Length + 1}] <-- {hexString}";
AppendEventLog(str, Color.Blue);
}
catch (Exception ex)
@@ -397,23 +415,33 @@ namespace EonaCat.HID.Analyzer
}
}
+
private async void ButtonWriteFeature_Click(object sender, EventArgs e)
{
try
{
- var hidReportId = byte.Parse(comboBoxReportId.Text);
- var buf = ByteHelper.HexStringToByteArray(textBoxWriteData.Text);
-
- var len = _device.FeatureReportByteLength;
- if (buf.Length > len)
+ if (!byte.TryParse(comboBoxReportId.Text, out var hidReportId))
{
- throw new Exception("Write Feature Report Length Exceed");
+ throw new FormatException("Invalid Report ID format.");
}
- Array.Resize(ref buf, len);
- await _device.SendFeatureReportAsync(buf);
- var str = string.Format("Tx Feature Report [{0}] --> {1}", buf.Length, ByteHelper.ByteArrayToHexString(buf));
- AppendEventLog(str, Color.DarkGreen);
+ var data = ByteHelper.HexStringToByteArray(textBoxWriteData.Text);
+
+ int maxLen = _device.FeatureReportByteLength - 1;
+ if (data.Length > maxLen)
+ {
+ throw new InvalidOperationException($"Feature report data length exceeds max allowed ({maxLen}).");
+ }
+
+ var reportData = new byte[data.Length];
+ Array.Copy(data, reportData, data.Length);
+
+ var hidReport = new HidReport(hidReportId, reportData);
+
+ await _device.SendFeatureReportAsync(hidReport);
+
+ string logMsg = $"Tx Feature Report [{data.Length + 1}] --> {ByteHelper.ByteArrayToHexString(new[] { hidReportId }.Concat(reportData).ToArray())}";
+ AppendEventLog(logMsg, Color.DarkGreen);
}
catch (Exception ex)
{
diff --git a/Analyzer/MainForm.resx b/Analyzer/MainForm.resx
index 173f685..e39c655 100644
--- a/Analyzer/MainForm.resx
+++ b/Analyzer/MainForm.resx
@@ -129,6 +129,12 @@
True
+
+ True
+
+
+ True
+
True
@@ -151,59 +157,59 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
- YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIqSURBVDhPfZJNaBNBFMcXSknBKKkg9FKL7SKSQ1tpMSEm
- mZ0NhFzirXdPftyEHjxueohsNiDUj0IC7UW95CIq5NDLpkQo7U401hatBkKys5kG0/Qe0JW3ycZsWvuH
- P8u89+bHe2+W4/4jdWrKo3q97r1gcBw8nD9XOY4b2Zy+flK4xsfL/sCDz7duf4N4XRDQcK0lebcZSGns
- MVgmxlWIqTMzfJ7nXT9iMdfXxdBkHSGeCmKHIvyaLCyMOgCSWh1TdhqGQpiZ1pjgSA6IIjRPBZFRAb9y
- JBTClpLbRkfZYUUAtH5m19oHCbNdyXat3T2s7T209tCDdCjGd/qAFGG7Mmk863bSnDiuZFQHoPzIPC4v
- b9r1OsIvKBK3rEPyU+NKcrv+RyZHfrugDyjd7/ogYULsHwD5qwj9rkejl7m0xnwwu7TfdDsAvYs2ZBDQ
- isUuUUE0aThyk1NKR7MASH38dfEUYAAyCKgi5AGAjvEcly6zCzACdHImYH/l1AgU4wCMULN/MIWwgkIa
- zx0Ae4GHT832l2ULUEVooorQmC6IazQsFux6Ll1i8e4z0nk4tyrZl31AzxDTUUTQBVy0njEsxvsAkKKx
- TJJQZkPOEgCs5YVCFDpxJDPEHFVIYx06eVJq8osbW5Ox1byLX827vNI7Hmpqkcg0RVgGGxhHHQBbMjGC
- 8PVtFL771ov3Zlfex28kPpws5XIjw7XnKvimOA72Sjn3nPTWM5y39RdV/noYsphYLAAAAABJRU5ErkJg
- gg==
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIrSURBVDhPfZJPaBNBFMYXSknBKKkg9NIW20Uhh7bSYkJM
+ MjsbCLnEW++e/HMTevC4KUTZbEBo1UIC7UW95CIqBPSyKZFCm4nG2qLVYEh2NtNgmt4DuvI22ZhNaz/4
+ WOa9Nz/ee7Mc9x+pk5Mu1e127vr9o+DB/JnKcNzQu6krx7nLfLTk9d39dP3GV4jXBAEN1pqSdxq+RIE9
+ AMtEn4CYOj3NZ3ne8T0ScXxZCIzXEOKpILYpwi/I/PywDSCplRFlu64rhBnJAhNsyT5RhOaoIDIq4Oe2
+ hELYYnxLbyvbLA+A5o/0Wms/ZrTK6Y4Ltw6qu/fMPXQhbYrxzR4gQdiOTOqrnU4aY0fllGoDlO4bR6Wl
+ 91a9hvBTisRN8/DwY/1SfKv6RyaHXqugByje6Xg/ZkDsHwB5fyL0uxYOX+SSBeaB2aW9htMG6F60IP2A
+ ZiRygQqiQYOha5xSPJwBQOLDr/MnAH2QfkAFIRcANIxnuWSJnYMRoJNTAXvLJ0agGPtghKr1gymE5RRS
+ f2IDWAs8eGy0Pi+ZgApCYxWERjRBXKNBMWfVc8kii3aekc7BuVlOP+sBuoaYhkKCJuC8+YxBMdoDgJQC
+ S8UJZRbkNAHAXF4gQKETWzJFjGGF1Nehk0fFBr+wsTkeWck6+JWswy295qGmGgpNUYRlsI5x2AawJBPd
+ D1/PRu6bZz1/e2b5TfRq7O3xYiYzNFh7pvwv86Ngt5RxzkqvXIN5S38BNmd6B/1xTKkAAAAASUVORK5C
+ YII=
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
- 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
+ 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
- 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
+ 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
- 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
+ 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
- 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
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJOSURBVDhPdZHtS1NRHMdHCpkFRa/6H+pNbyLcjYFZVES+
+ Mh1RFukeNNPm3DTZTs57fdoWgyiIiBa9syBYtMzd6170QBASNsVa03kv2k5QC6qt3T2cX9wLu9vu1hd+
+ cM73/L6f86TR1BC1gA0Uh/Vqv6YM6G6jye51G0duaosexX3doubxp8rO/8ho9z41D3vhsnXmd4/dQ0me
+ lsUzFIud6t6aMts9fglQDjn8Vthxb867975vNOmedmZeB6zH1TlFCKF6k937uAjp6EPZ7sGxq7d9jnTR
+ o2lEsh+MyhWrJEEMNveT8300nDU6ocs6JQelGnJOwfpzPWRWLd+JwBxQZxV1ItSg70WbXYOV4S8PWyC/
+ MgAgMCDG0B/Cj7ers7KWwz27bvmcyrGHHJOw/qwdch+vAImPyYBS0X7Cu/ZXAHye4VT5zmuBNkgFmoHE
+ b6jCpSI8fUEBWBzTpWP7j0JuuR8K0VEorNqUgDTPR66VASZKvxN4ZAvZ0KT8YLmlXih8HqnaEXgaxFfn
+ 4G+oFVJzp5OwhRoVgKQfbwwP0ksWUhUUGMivWEF8d0keizEmVfNHdOGfewg/0SnGmIIS3nABibvkcfZ9
+ N6SDJyEf6Tcfe5nYqQtDvRJuCSV3U1xiUxf+tg94+mIRQtYQpF+cgkL0OogCI5LoqE3q17KJO1QIWxQA
+ xSY6KA5DE4tN0pxsjDfnBWZRFFA2E0e/0vNnIiTuOiitnQhGtx8K4WQThxcVQNss1FEcDurC8QbF1Gg0
+ AGgbwGxduSdJy+KBIwu49R+1rskaRvlY/wAAAABJRU5ErkJggg==
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
- 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
- HF2OqgAAizrlNjwLhaIAAAAASUVORK5CYII=
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAETSURBVDhPY2CgBphQKSYe5qL21dTU9D8xGKR2WrG4GNyA
+ iZUK9h5Ohv+/7uL8//8gO178dRfXf5DaKeUKdiiuyI9Rv1qVqoqhARn/O8D+vyJZ9X9erMYlFM0gMLNe
+ kivCX+fTilZpDI0wvKRJ5n+gr+qXiblCfOj6wWBimbyFvb3h3/MLBDE0X1ok+B8kh+F0dNCVp1Dm5Wzw
+ //Vmbrjmt1u5/4PE2nMU2tDVYwUlSSqHC+LU4QbkxWj8L4lXOYyuDieYUi/KY25uCjcAxAaFEbo6nKC+
+ noEJFN8wA0BsdDV4weAy4NMOrv+WFiakGwDStGey+H8/N4P/ZYkqR9DVEAQViar7Y3y0PnTmK5Sjy1EV
+ AAB6heUx9GPlMgAAAABJRU5ErkJggg==
diff --git a/Analyzer/mainForm.Designer.cs b/Analyzer/mainForm.Designer.cs
index 5f02e70..0589a22 100644
--- a/Analyzer/mainForm.Designer.cs
+++ b/Analyzer/mainForm.Designer.cs
@@ -31,15 +31,6 @@
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.textBoxWriteData = new System.Windows.Forms.TextBox();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
- this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column9 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column10 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column11 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column7 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column5 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
- this.Column8 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator();
@@ -67,6 +58,17 @@
this.rtbEventLog = new System.Windows.Forms.RichTextBox();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
+ this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column9 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column10 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column11 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column6 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column7 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column5 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.Column8 = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
this.tableLayoutPanel1.SuspendLayout();
this.toolStrip1.SuspendLayout();
@@ -96,6 +98,8 @@
this.Column9,
this.Column10,
this.Column11,
+ this.Column6,
+ this.Column3,
this.Column7,
this.Column4,
this.Column5,
@@ -112,69 +116,6 @@
this.dataGridView1.SelectionChanged += new System.EventHandler(this.DataGridView1_SelectionChanged);
this.dataGridView1.DoubleClick += new System.EventHandler(this.dataGridView1_DoubleClick);
//
- // Column1
- //
- this.Column1.HeaderText = "No";
- this.Column1.Name = "Column1";
- this.Column1.ReadOnly = true;
- this.Column1.Width = 87;
- //
- // Column9
- //
- this.Column9.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
- this.Column9.HeaderText = "Name";
- this.Column9.Name = "Column9";
- this.Column9.ReadOnly = true;
- //
- // Column10
- //
- this.Column10.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
- this.Column10.HeaderText = "Manufacturer";
- this.Column10.Name = "Column10";
- this.Column10.ReadOnly = true;
- //
- // Column11
- //
- this.Column11.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
- this.Column11.HeaderText = "SerialNo";
- this.Column11.Name = "Column11";
- this.Column11.ReadOnly = true;
- //
- // Column7
- //
- this.Column7.HeaderText = "InputReport";
- this.Column7.Name = "Column7";
- this.Column7.ReadOnly = true;
- this.Column7.Width = 86;
- //
- // Column4
- //
- this.Column4.HeaderText = "OutputReport";
- this.Column4.Name = "Column4";
- this.Column4.ReadOnly = true;
- this.Column4.Width = 87;
- //
- // Column5
- //
- this.Column5.HeaderText = "FeatureReport";
- this.Column5.Name = "Column5";
- this.Column5.ReadOnly = true;
- this.Column5.Width = 86;
- //
- // Column2
- //
- this.Column2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
- this.Column2.HeaderText = "Info";
- this.Column2.Name = "Column2";
- this.Column2.ReadOnly = true;
- //
- // Column8
- //
- this.Column8.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
- this.Column8.HeaderText = "DevicePath";
- this.Column8.Name = "Column8";
- this.Column8.ReadOnly = true;
- //
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 1;
@@ -473,6 +414,81 @@
this.toolStripStatusLabel1.Size = new System.Drawing.Size(118, 17);
this.toolStripStatusLabel1.Text = "toolStripStatusLabel1";
//
+ // Column1
+ //
+ this.Column1.HeaderText = "No";
+ this.Column1.Name = "Column1";
+ this.Column1.ReadOnly = true;
+ this.Column1.Width = 87;
+ //
+ // Column9
+ //
+ this.Column9.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.Column9.HeaderText = "Name";
+ this.Column9.Name = "Column9";
+ this.Column9.ReadOnly = true;
+ //
+ // Column10
+ //
+ this.Column10.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.Column10.HeaderText = "Manufacturer";
+ this.Column10.Name = "Column10";
+ this.Column10.ReadOnly = true;
+ //
+ // Column11
+ //
+ this.Column11.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.Column11.HeaderText = "SerialNo";
+ this.Column11.Name = "Column11";
+ this.Column11.ReadOnly = true;
+ //
+ // Column6
+ //
+ this.Column6.HeaderText = "CanRead";
+ this.Column6.Name = "Column6";
+ this.Column6.ReadOnly = true;
+ //
+ // Column3
+ //
+ this.Column3.HeaderText = "CanWrite";
+ this.Column3.Name = "Column3";
+ this.Column3.ReadOnly = true;
+ //
+ // Column7
+ //
+ this.Column7.HeaderText = "InputReport";
+ this.Column7.Name = "Column7";
+ this.Column7.ReadOnly = true;
+ this.Column7.Width = 86;
+ //
+ // Column4
+ //
+ this.Column4.HeaderText = "OutputReport";
+ this.Column4.Name = "Column4";
+ this.Column4.ReadOnly = true;
+ this.Column4.Width = 87;
+ //
+ // Column5
+ //
+ this.Column5.HeaderText = "FeatureReport";
+ this.Column5.Name = "Column5";
+ this.Column5.ReadOnly = true;
+ this.Column5.Width = 86;
+ //
+ // Column2
+ //
+ this.Column2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.Column2.HeaderText = "Info";
+ this.Column2.Name = "Column2";
+ this.Column2.ReadOnly = true;
+ //
+ // Column8
+ //
+ this.Column8.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
+ this.Column8.HeaderText = "DevicePath";
+ this.Column8.Name = "Column8";
+ this.Column8.ReadOnly = true;
+ //
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -537,6 +553,8 @@
private System.Windows.Forms.DataGridViewTextBoxColumn Column9;
private System.Windows.Forms.DataGridViewTextBoxColumn Column10;
private System.Windows.Forms.DataGridViewTextBoxColumn Column11;
+ private System.Windows.Forms.DataGridViewTextBoxColumn Column6;
+ private System.Windows.Forms.DataGridViewTextBoxColumn Column3;
private System.Windows.Forms.DataGridViewTextBoxColumn Column7;
private System.Windows.Forms.DataGridViewTextBoxColumn Column4;
private System.Windows.Forms.DataGridViewTextBoxColumn Column5;
diff --git a/EonaCat.HID.Console/Program.cs b/EonaCat.HID.Console/Program.cs
index 2182867..a7bc3fd 100644
--- a/EonaCat.HID.Console/Program.cs
+++ b/EonaCat.HID.Console/Program.cs
@@ -1,4 +1,5 @@
using EonaCat.HID;
+using EonaCat.HID.Models;
using System.Globalization;
namespace EonaCat.HID.Example
@@ -46,7 +47,7 @@ namespace EonaCat.HID.Example
_device.OnDataReceived += (s, e) =>
{
- Console.WriteLine($"Rx Data: {BitConverter.ToString(e.Data)}");
+ Console.WriteLine($"Rx Data: {BitConverter.ToString(e.Report.Data)}");
};
_device.OnError += (s, e) =>
@@ -62,14 +63,13 @@ namespace EonaCat.HID.Example
Console.WriteLine("Listening... Press [Enter] to send test output report.");
Console.ReadLine();
- // Example: Send output report
+ // Example: Send output report using HidReport
var data = ByteHelper.HexStringToByteArray("01-02-03");
- byte[] outputReport = new byte[data.Length + 1];
- outputReport[0] = 0x00; // Report ID
- Array.Copy(data, 0, outputReport, 1, data.Length);
+ var reportId = (byte)0x00; // Report ID
+ var outputReport = new HidReport(reportId, data);
await _device.WriteOutputReportAsync(outputReport);
- Console.WriteLine($"Sent output report: {BitConverter.ToString(outputReport)}");
+ Console.WriteLine($"Sent output report: Report ID: {reportId}, Data: {BitConverter.ToString(data)}");
Console.WriteLine("Press [Enter] to exit...");
Console.ReadLine();
diff --git a/EonaCat.HID/EventArguments/HidDataReceivedEventArgs.cs b/EonaCat.HID/EventArguments/HidDataReceivedEventArgs.cs
index fa18444..b0dd846 100644
--- a/EonaCat.HID/EventArguments/HidDataReceivedEventArgs.cs
+++ b/EonaCat.HID/EventArguments/HidDataReceivedEventArgs.cs
@@ -1,4 +1,5 @@
-using System;
+using EonaCat.HID.Models;
+using System;
namespace EonaCat.HID.EventArguments
{
@@ -11,12 +12,12 @@ namespace EonaCat.HID.EventArguments
public class HidDataReceivedEventArgs : EventArgs
{
public IHid Device { get; }
- public byte[] Data { get; }
+ public HidReport Report { get; }
- public HidDataReceivedEventArgs(IHid device, byte[] data)
+ public HidDataReceivedEventArgs(IHid device, HidReport report)
{
Device = device;
- Data = data;
+ Report = report;
}
}
}
\ No newline at end of file
diff --git a/EonaCat.HID/EventArguments/HidEventArgs.cs b/EonaCat.HID/EventArguments/HidEventArgs.cs
index 125968c..89c67fe 100644
--- a/EonaCat.HID/EventArguments/HidEventArgs.cs
+++ b/EonaCat.HID/EventArguments/HidEventArgs.cs
@@ -11,6 +11,8 @@ namespace EonaCat.HID.EventArguments
public class HidEventArgs : EventArgs
{
public IHid Device { get; }
+ public bool HasDevice => Device != null;
+ public bool IsConnected => HasDevice && Device.IsConnected;
public HidEventArgs(IHid device)
{
diff --git a/EonaCat.HID/HidLinux.cs b/EonaCat.HID/HidLinux.cs
index 3b8d04e..96ae9ae 100644
--- a/EonaCat.HID/HidLinux.cs
+++ b/EonaCat.HID/HidLinux.cs
@@ -1,6 +1,8 @@
using EonaCat.HID.EventArguments;
using EonaCat.HID.Managers;
using EonaCat.HID.Managers.Linux;
+using EonaCat.HID.Models;
+using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.IO;
@@ -17,6 +19,7 @@ namespace EonaCat.HID
{
private readonly string _devicePath;
private FileStream _stream;
+ private bool _isOpen;
private readonly object _lock = new object();
private CancellationTokenSource _cts;
@@ -29,6 +32,11 @@ namespace EonaCat.HID
public int InputReportByteLength { get; private set; }
public int OutputReportByteLength { get; private set; }
public int FeatureReportByteLength { get; private set; }
+
+ public bool IsReadingSupport { get; private set; }
+ public bool IsWritingSupport { get; private set; }
+
+ public bool IsConnected => _isOpen;
public IDictionary Capabilities { get; private set; } = new Dictionary();
public event EventHandler OnDataReceived;
@@ -51,21 +59,33 @@ namespace EonaCat.HID
{
lock (_lock)
{
- if (_stream != null)
- {
+ if (_stream != null || _isOpen)
return;
- }
- // Open device for read/write
- var fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR | NativeMethods.O_NONBLOCK);
+ // Open HID device in non-blocking read/write mode
+ int fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR | NativeMethods.O_NONBLOCK);
if (fd < 0)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException($"Failed to open device: {_devicePath}")));
+ OnError?.Invoke(this, new HidErrorEventArgs(this,
+ new IOException($"Failed to open device: {_devicePath}")));
return;
}
var safeHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(new IntPtr(fd), ownsHandle: true);
- _stream = new FileStream(safeHandle, FileAccess.ReadWrite, bufferSize: 64, isAsync: false);
+
+ try
+ {
+ _stream = new FileStream(safeHandle, FileAccess.ReadWrite, bufferSize: 64, isAsync: false);
+ _isOpen = true;
+
+ IsReadingSupport = true;
+ IsWritingSupport = true;
+ }
+ catch (Exception ex)
+ {
+ safeHandle.Dispose();
+ OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
+ }
}
}
@@ -162,20 +182,24 @@ namespace EonaCat.HID
return null;
}
- public async Task WriteOutputReportAsync(byte[] data)
+ public async Task WriteOutputReportAsync(HidReport report)
{
- if (data == null)
+ if (report == null)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentNullException(nameof(data))));
+ var ex = new ArgumentNullException(nameof(report));
+ OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
return;
}
if (_stream == null)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open")));
+ var ex = new InvalidOperationException("Device not open");
+ OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
return;
}
+ var data = report.Data;
+
await Task.Run(() =>
{
lock (_lock)
@@ -188,17 +212,19 @@ namespace EonaCat.HID
catch (Exception ex)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
+ throw;
}
}
});
}
- public async Task ReadInputReportAsync()
+ public async Task ReadInputReportAsync()
{
if (_stream == null)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open")));
- return Array.Empty();
+ var ex = new InvalidOperationException("Device not open");
+ OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
+ return null;
}
return await Task.Run(() =>
@@ -215,91 +241,119 @@ namespace EonaCat.HID
catch (Exception ex)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
+ return null;
}
}
if (bytesRead <= 0)
{
- return Array.Empty();
+ return null;
}
- byte[] result = new byte[bytesRead];
- Array.Copy(buffer, result, bytesRead);
- return result;
+ // First byte is report ID, rest is data
+ byte reportId = buffer[0];
+ byte[] data = new byte[bytesRead - 1];
+ Array.Copy(buffer, 1, data, 0, data.Length);
+
+ return new HidReport(reportId, data);
});
}
- public async Task SendFeatureReportAsync(byte[] data)
- {
- if (data == null || data.Length == 0)
- throw new ArgumentException("Feature report data must not be null or empty.");
- int fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR);
- if (fd < 0)
+ public async Task SendFeatureReportAsync(HidReport report)
+ {
+ if (report == null)
+ throw new ArgumentNullException(nameof(report));
+
+ // Prepare full report buffer: [reportId][reportData...]
+ int size = 1 + (report.Data?.Length ?? 0);
+ byte[] buffer = new byte[size];
+ buffer[0] = report.ReportId;
+ if (report.Data != null && report.Data.Length > 0)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException($"Failed to open device: {_devicePath}")));
- return;
+ Array.Copy(report.Data, 0, buffer, 1, report.Data.Length);
}
- try
+ await Task.Run(() =>
{
- int size = data.Length;
- IntPtr buffer = Marshal.AllocHGlobal(size);
- Marshal.Copy(data, 0, buffer, size);
-
- int request = NativeMethods._IOC(NativeMethods._IOC_WRITE, 'H', 0x06, size); // HIDIOCSFEATURE
- int result = NativeMethods.ioctl(fd, request, buffer);
- if (result < 0)
+ int fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR);
+ if (fd < 0)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException("ioctl HIDIOCSFEATURE failed")));
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException($"Failed to open device: {_devicePath}")));
return;
}
- Marshal.FreeHGlobal(buffer);
- }
- finally
- {
- NativeMethods.close(fd);
- }
+ IntPtr unmanagedBuffer = IntPtr.Zero;
+ try
+ {
+ unmanagedBuffer = Marshal.AllocHGlobal(size);
+ Marshal.Copy(buffer, 0, unmanagedBuffer, size);
- await Task.CompletedTask;
+ int request = NativeMethods._IOC(NativeMethods._IOC_WRITE, 'H', 0x06, size); // HIDIOCSFEATURE
+ int result = NativeMethods.ioctl(fd, request, unmanagedBuffer);
+ if (result < 0)
+ {
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException("ioctl HIDIOCSFEATURE failed")));
+ }
+ }
+ finally
+ {
+ if (unmanagedBuffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(unmanagedBuffer);
+ NativeMethods.close(fd);
+ }
+ });
}
- public async Task GetFeatureReportAsync(byte reportId)
+
+
+
+ public async Task GetFeatureReportAsync(byte reportId)
{
const int maxFeatureSize = 256;
byte[] buffer = new byte[maxFeatureSize];
buffer[0] = reportId;
- int fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR);
- if (fd < 0)
+ return await Task.Run(() =>
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException($"Failed to open device: {_devicePath}")));
- return Array.Empty();
- }
-
- try
- {
- IntPtr bufPtr = Marshal.AllocHGlobal(buffer.Length);
- Marshal.Copy(buffer, 0, bufPtr, buffer.Length);
-
- int request = NativeMethods._IOC(NativeMethods._IOC_READ, 'H', 0x07, buffer.Length); // HIDIOCGFEATURE
- int result = NativeMethods.ioctl(fd, request, bufPtr);
- if (result < 0)
+ int fd = NativeMethods.open(_devicePath, NativeMethods.O_RDWR);
+ if (fd < 0)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException("ioctl HIDIOCGFEATURE failed")));
- return Array.Empty();
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException($"Failed to open device: {_devicePath}")));
+ return new HidReport(0, Array.Empty());
}
- Marshal.Copy(bufPtr, buffer, 0, buffer.Length);
- Marshal.FreeHGlobal(bufPtr);
+ IntPtr bufPtr = IntPtr.Zero;
+ try
+ {
+ bufPtr = Marshal.AllocHGlobal(buffer.Length);
+ Marshal.Copy(buffer, 0, bufPtr, buffer.Length);
- return await Task.FromResult(buffer);
- }
- finally
- {
- NativeMethods.close(fd);
- }
+ int request = NativeMethods._IOC(NativeMethods._IOC_READ, 'H', 0x07, buffer.Length); // HIDIOCGFEATURE
+ int result = NativeMethods.ioctl(fd, request, bufPtr);
+ if (result < 0)
+ {
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException("ioctl HIDIOCGFEATURE failed")));
+ return new HidReport(0, Array.Empty());
+ }
+
+ byte[] actualBuffer = new byte[result];
+ Marshal.Copy(bufPtr, actualBuffer, 0, result);
+
+ byte actualReportId = actualBuffer.Length > 0 ? actualBuffer[0] : (byte)0;
+ byte[] reportData = actualBuffer.Length > 1 ? new byte[actualBuffer.Length - 1] : Array.Empty();
+ if (reportData.Length > 0)
+ Array.Copy(actualBuffer, 1, reportData, 0, reportData.Length);
+
+ return new HidReport(actualReportId, reportData);
+ }
+ finally
+ {
+ if (bufPtr != IntPtr.Zero)
+ Marshal.FreeHGlobal(bufPtr);
+ NativeMethods.close(fd);
+ }
+ });
}
public async Task StartListeningAsync(CancellationToken cancellationToken)
@@ -341,7 +395,7 @@ namespace EonaCat.HID
catch (NotSupportedException)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new NotSupportedException("Reading input reports is not supported on this device.")));
- break; // Exit if reading is not supported
+ break;
}
catch (Exception ex)
{
@@ -354,7 +408,22 @@ namespace EonaCat.HID
var data = new byte[bytesRead];
Array.Copy(buffer, data, bytesRead);
- OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, data));
+ // Extract reportId and report data
+ byte reportId = data.Length > 0 ? data[0] : (byte)0;
+
+ byte[] reportData;
+ if (data.Length > 1)
+ {
+ reportData = new byte[data.Length - 1];
+ Array.Copy(data, 1, reportData, 0, data.Length - 1);
+ }
+ else
+ {
+ reportData = Array.Empty();
+ }
+
+ var hidReport = new HidReport(reportId, reportData);
+ OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, hidReport));
}
else
{
diff --git a/EonaCat.HID/HidMac.cs b/EonaCat.HID/HidMac.cs
index d80e032..da68b5a 100644
--- a/EonaCat.HID/HidMac.cs
+++ b/EonaCat.HID/HidMac.cs
@@ -1,6 +1,10 @@
using EonaCat.HID.EventArguments;
+using EonaCat.HID.Models;
+using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -36,19 +40,49 @@ namespace EonaCat.HID
public int OutputReportByteLength { get; private set; } = 64;
public int FeatureReportByteLength { get; private set; } = 64;
+ public bool IsReadingSupport { get; private set; }
+ public bool IsWritingSupport { get; private set; }
+
+ public bool IsConnected => _isOpen;
+
public IDictionary Capabilities { get; private set; } = new Dictionary();
public event EventHandler OnDataReceived;
public event EventHandler OnError;
+ private const int KERN_SUCCESS = 0;
+ private const int kIOHIDOptionsTypeNone = 0;
+
public void Open()
{
- IOHIDDeviceOpen(_deviceHandle, 0);
+ if (_isOpen)
+ {
+ return;
+ }
+
+ var result = IOHIDDeviceOpen(_deviceHandle, kIOHIDOptionsTypeNone);
+ if (result != KERN_SUCCESS)
+ {
+ throw new InvalidOperationException($"Failed to open HID device. IOHIDDeviceOpen returned: {result}");
+ }
+
+ IsReadingSupport = true;
+ IsWritingSupport = CheckOutputReportSupport();
+
+ _isOpen = true;
+ }
+
+ private bool CheckOutputReportSupport()
+ {
+ // On macOS, there's no simple API to test writing directly.
+ // You typically assume support based on report length.
+ return OutputReportByteLength > 0;
}
public void Close()
{
IOHIDDeviceClose(_deviceHandle, 0);
+ _isOpen = false;
}
public void Dispose()
@@ -64,26 +98,35 @@ namespace EonaCat.HID
_listeningCts?.Cancel();
}
- public async Task WriteOutputReportAsync(byte[] data)
+ public async Task WriteOutputReportAsync(HidReport report)
{
- if (data is null)
+ if (report == null)
{
- throw new ArgumentNullException(nameof(data));
+ throw new ArgumentNullException(nameof(report));
+ }
+
+ if (report.Data == null || report.Data.Length == 0)
+ {
+ throw new ArgumentException("Data cannot be null or empty", nameof(report));
}
await Task.Run(() =>
{
- byte reportId = data.Length > 0 ? data[0] : (byte)0;
- IntPtr buffer = Marshal.AllocHGlobal(data.Length);
+ // Total length includes reportId + data length
+ int length = 1 + report.Data.Length;
+ IntPtr buffer = Marshal.AllocHGlobal(length);
try
{
- Marshal.Copy(data, 0, buffer, data.Length);
- int res = IOHIDDeviceSetReport(_deviceHandle, 1, reportId, buffer, data.Length);
+ // First byte is reportId
+ Marshal.WriteByte(buffer, report.ReportId);
+ // Copy the rest of data after the reportId byte
+ Marshal.Copy(report.Data, 0, buffer + 1, report.Data.Length);
+
+ int res = IOHIDDeviceSetReport(_deviceHandle, 1 /* kIOHIDReportTypeOutput */, report.ReportId, buffer, length);
if (res != 0)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new Exception($"IOHIDDeviceSetReport (Output) failed: {res}")));
- return;
}
}
finally
@@ -93,26 +136,32 @@ namespace EonaCat.HID
});
}
- public async Task SendFeatureReportAsync(byte[] data)
+ public async Task SendFeatureReportAsync(HidReport report)
{
- if (data is null)
+ if (report == null)
{
- throw new ArgumentNullException(nameof(data));
+ throw new ArgumentNullException(nameof(report));
+ }
+
+ if (report.Data == null || report.Data.Length == 0)
+ {
+ throw new ArgumentException("Data cannot be null or empty", nameof(report));
}
await Task.Run(() =>
{
- byte reportId = data.Length > 0 ? data[0] : (byte)0;
- IntPtr buffer = Marshal.AllocHGlobal(data.Length);
+ int length = 1 + report.Data.Length;
+ IntPtr buffer = Marshal.AllocHGlobal(length);
try
{
- Marshal.Copy(data, 0, buffer, data.Length);
- int res = IOHIDDeviceSetReport(_deviceHandle, 2, reportId, buffer, data.Length);
+ Marshal.WriteByte(buffer, report.ReportId);
+ Marshal.Copy(report.Data, 0, buffer + 1, report.Data.Length);
+
+ int res = IOHIDDeviceSetReport(_deviceHandle, 2 /* kIOHIDReportTypeFeature */, report.ReportId, buffer, length);
if (res != 0)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new Exception($"IOHIDDeviceSetReport (Feature) failed: {res}")));
- return;
}
}
finally
@@ -122,7 +171,7 @@ namespace EonaCat.HID
});
}
- public async Task GetFeatureReportAsync(byte reportId)
+ public async Task GetFeatureReportAsync(byte reportId)
{
return await Task.Run(() =>
{
@@ -131,16 +180,26 @@ namespace EonaCat.HID
try
{
- int res = IOHIDDeviceGetReport(_deviceHandle, 2, reportId, buffer, ref length);
+ int res = IOHIDDeviceGetReport(_deviceHandle, 2 /* kIOHIDReportTypeFeature */, reportId, buffer, ref length);
if (res != 0)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new Exception($"IOHIDDeviceGetReport (Feature) failed: {res}")));
- return Array.Empty();
+ return new HidReport(reportId, Array.Empty());
}
byte[] outBuf = new byte[length];
Marshal.Copy(buffer, outBuf, 0, length);
- return outBuf;
+
+ if (outBuf.Length > 0 && outBuf[0] == reportId)
+ {
+ // Data excludes report ID (first byte)
+ var dataOnly = outBuf.Skip(1).ToArray();
+ return new HidReport(reportId, dataOnly);
+ }
+ else
+ {
+ return new HidReport(reportId, outBuf);
+ }
}
finally
{
@@ -149,18 +208,40 @@ namespace EonaCat.HID
});
}
- public Task ReadInputReportAsync()
+ public Task ReadInputReportAsync()
{
- var tcs = new TaskCompletionSource();
+ var tcs = new TaskCompletionSource();
byte[] buffer = new byte[InputReportByteLength];
InputReportCallback callback = null;
callback = (ctx, result, sender, report, reportLength) =>
{
- byte[] output = new byte[reportLength.ToInt32()];
- Array.Copy(report, output, output.Length);
- tcs.TrySetResult(output);
+ int length = reportLength.ToInt32();
+ if (report == null || length == 0)
+ {
+ tcs.TrySetResult(new HidReport(0, Array.Empty()));
+ return;
+ }
+
+ byte[] output = new byte[length];
+ Array.Copy(report, output, length);
+
+ byte reportId = output.Length > 0 ? output[0] : (byte)0;
+
+ byte[] reportData;
+ if (output.Length > 1)
+ {
+ reportData = new byte[output.Length - 1];
+ Array.Copy(output, 1, reportData, 0, reportData.Length);
+ }
+ else
+ {
+ reportData = Array.Empty();
+ }
+
+ var hidReport = new HidReport(reportId, reportData);
+ tcs.TrySetResult(hidReport);
};
GCHandle.Alloc(callback);
@@ -169,13 +250,12 @@ namespace EonaCat.HID
return tcs.Task;
}
-
public Task StartListeningAsync(CancellationToken ct)
{
if (_listeningTask != null && !_listeningTask.IsCompleted)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new NotSupportedException("Already listening on this device.")));
- return null;
+ return Task.CompletedTask;
}
_listeningCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
@@ -185,14 +265,26 @@ namespace EonaCat.HID
try
{
byte[] buffer = new byte[InputReportByteLength];
- InputReportCallback callback = (ctx, result, sender, report, reportLength) =>
+ InputReportCallback callback = (context, result, sender, report, reportLength) =>
{
- byte[] data = new byte[reportLength.ToInt32()];
- Array.Copy(report, data, data.Length);
+ int len = reportLength.ToInt32();
+ if (report == null || report.Length < len)
+ {
+ return;
+ }
- OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, data));
+ // Extract reportId (first byte)
+ byte reportId = len > 0 ? report[0] : (byte)0;
+
+ // Extract the rest of the data after reportId
+ byte[] reportData = len > 1 ? report.Skip(1).Take(len - 1).ToArray() : Array.Empty();
+
+ var hidReport = new HidReport(reportId, reportData);
+
+ OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, hidReport));
};
+
GCHandle.Alloc(callback);
IOHIDDeviceRegisterInputReportCallback(_deviceHandle, buffer, (IntPtr)buffer.Length, callback, IntPtr.Zero);
IOHIDDeviceScheduleWithRunLoop(_deviceHandle, CFRunLoopGetCurrent(), IntPtr.Zero);
@@ -206,7 +298,7 @@ namespace EonaCat.HID
}
catch (OperationCanceledException)
{
- return; // Exit gracefully if cancellation was requested
+ return;
}
catch (NotSupportedException)
{
@@ -405,7 +497,7 @@ namespace EonaCat.HID
// For managing lifetime of listening loop
private CancellationTokenSource _listeningCts;
private Task _listeningTask;
-
+ private bool _isOpen;
private enum CFNumberType : int
{
diff --git a/EonaCat.HID/HidWindows.cs b/EonaCat.HID/HidWindows.cs
index 8d9688a..b9f7fe7 100644
--- a/EonaCat.HID/HidWindows.cs
+++ b/EonaCat.HID/HidWindows.cs
@@ -1,5 +1,6 @@
using EonaCat.HID.EventArguments;
using EonaCat.HID.Managers.Windows;
+using EonaCat.HID.Models;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
@@ -35,8 +36,11 @@ namespace EonaCat.HID
public int InputReportByteLength { get; private set; }
public int OutputReportByteLength { get; private set; }
public int FeatureReportByteLength { get; private set; }
+ public bool IsConnected => _isOpen;
public IDictionary Capabilities { get; } = new Dictionary();
+ public bool IsReadingSupport { get; private set; }
+ public bool IsWritingSupport { get; private set; }
public event EventHandler OnDataReceived;
public event EventHandler OnError;
@@ -95,22 +99,23 @@ namespace EonaCat.HID
if (handle == null || handle.IsInvalid)
{
int err = Marshal.GetLastWin32Error();
- OnError?.Invoke(this, new HidErrorEventArgs(this, new Win32Exception(err, $"Cannot open device {_devicePath} with any access mode")));
+ OnError?.Invoke(this, new HidErrorEventArgs(this,
+ new Win32Exception(err, $"Cannot open device {_devicePath} with any access mode")));
return;
}
_deviceHandle = handle;
- _deviceStream = new FileStream(_deviceHandle, access, 64, true);
+ _deviceStream = new FileStream(_deviceHandle, access, bufferSize: 64, isAsync: true);
_isOpen = true;
- // HID descriptor parsing
+ // HID descriptor
if (!HidD_GetPreparsedData(_deviceHandle.DangerousGetHandle(), out _preparsedData))
throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed HidD_GetPreparsedData");
HIDP_CAPS caps;
- int res = HidP_GetCaps(_preparsedData, out caps);
- if (res == 0)
- throw new Win32Exception(res, "Failed HidP_GetCaps");
+ int capsRes = HidP_GetCaps(_preparsedData, out caps);
+ if (capsRes != NativeMethods.HIDP_STATUS_SUCCESS)
+ throw new Win32Exception(capsRes, "Failed HidP_GetCaps");
InputReportByteLength = caps.InputReportByteLength;
OutputReportByteLength = caps.OutputReportByteLength;
@@ -126,12 +131,14 @@ namespace EonaCat.HID
ProductName = GetStringDescriptor(HidD_GetProductString);
SerialNumber = GetStringDescriptor(HidD_GetSerialNumberString);
+ IsReadingSupport = (access == FileAccess.Read || access == FileAccess.ReadWrite);
+ IsWritingSupport = (access == FileAccess.Write || access == FileAccess.ReadWrite);
+
HidDeviceAttributes attr = GetDeviceAttributes();
VendorId = attr.VendorID;
ProductId = attr.ProductID;
}
-
private SafeFileHandle TryOpenDevice(int access)
{
var handle = CreateFile(_devicePath,
@@ -145,34 +152,6 @@ namespace EonaCat.HID
return handle;
}
- private bool CanRead(SafeFileHandle handle)
- {
- try
- {
- using (var stream = new FileStream(handle, FileAccess.Read, 1, true))
- {
- byte[] test = new byte[1];
- stream.Read(test, 0, 1);
- return true;
- }
- }
- catch { return false; }
- }
-
- private bool CanWrite(SafeFileHandle handle)
- {
- try
- {
- using (var stream = new FileStream(handle, FileAccess.Write, 1, true))
- {
- byte[] test = new byte[1];
- stream.Write(test, 0, 1);
- return true;
- }
- }
- catch { return false; }
- }
-
///
/// Close the device
///
@@ -247,7 +226,7 @@ namespace EonaCat.HID
Close();
}
- public async Task WriteOutputReportAsync(byte[] data)
+ public async Task WriteOutputReportAsync(HidReport report)
{
if (!_isOpen)
{
@@ -255,15 +234,26 @@ namespace EonaCat.HID
return;
}
- if (data == null || data.Length == 0)
+ if (report == null)
{
- OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentNullException(nameof(data), "Data cannot be null or empty")));
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentNullException(nameof(report))));
+ return;
+ }
+
+ if (report.Data == null || report.Data.Length == 0)
+ {
+ OnError?.Invoke(this, new HidErrorEventArgs(this, new ArgumentException("Data cannot be null or empty", nameof(report))));
return;
}
try
{
- await _deviceStream.WriteAsync(data, 0, data.Length);
+ // Combine reportId and data into one buffer for sending
+ var buffer = new byte[1 + report.Data.Length];
+ buffer[0] = report.ReportId;
+ Array.Copy(report.Data, 0, buffer, 1, report.Data.Length);
+
+ await _deviceStream.WriteAsync(buffer, 0, buffer.Length);
await _deviceStream.FlushAsync();
}
catch (Exception ex)
@@ -273,12 +263,12 @@ namespace EonaCat.HID
}
}
- public async Task ReadInputReportAsync()
+ public async Task ReadInputReportAsync()
{
if (!_isOpen)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open")));
- return Array.Empty();
+ return new HidReport(0, Array.Empty());
}
return await Task.Run(async () =>
@@ -291,10 +281,13 @@ namespace EonaCat.HID
if (read == 0)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new IOException("No data read from device")));
- return Array.Empty();
+ return new HidReport(0, Array.Empty());
}
- return buffer.Take(read).ToArray();
+ byte reportId = buffer[0];
+ byte[] data = buffer.Skip(1).Take(read - 1).ToArray();
+
+ return new HidReport(reportId, data);
}
catch (Exception ex)
{
@@ -304,7 +297,8 @@ namespace EonaCat.HID
});
}
- public async Task SendFeatureReportAsync(byte[] data)
+
+ public async Task SendFeatureReportAsync(HidReport report)
{
if (!_isOpen)
{
@@ -312,10 +306,13 @@ namespace EonaCat.HID
return;
}
- if (data == null || data.Length == 0)
- {
- throw new ArgumentNullException(nameof(data));
- }
+ if (report == null)
+ throw new ArgumentNullException(nameof(report));
+
+ // Prepare buffer with ReportId + Data
+ var data = new byte[1 + report.Data.Length];
+ data[0] = report.ReportId;
+ Array.Copy(report.Data, 0, data, 1, report.Data.Length);
await Task.Run(() =>
{
@@ -330,12 +327,13 @@ namespace EonaCat.HID
});
}
- public async Task GetFeatureReportAsync(byte reportId)
+
+ public async Task GetFeatureReportAsync(byte reportId)
{
if (!_isOpen)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new InvalidOperationException("Device not open")));
- return Array.Empty();
+ return new HidReport(0, Array.Empty());
}
return await Task.Run(() =>
@@ -349,13 +347,15 @@ namespace EonaCat.HID
var err = Marshal.GetLastWin32Error();
var ex = new Win32Exception(err, "HidD_GetFeature failed");
OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
- return Array.Empty();
+ return new HidReport(0, Array.Empty());
}
- return buffer;
+ byte[] data = buffer.Skip(1).ToArray();
+ return new HidReport(reportId, data);
});
}
+
///
/// Begin async reading loop raising OnDataReceived events on data input
///
@@ -379,15 +379,17 @@ namespace EonaCat.HID
try
{
- while (!_listeningCts.Token.IsCancellationRequested)
+ var token = _listeningCts.Token;
+
+ while (!token.IsCancellationRequested)
{
try
{
- byte[] data = await ReadInputReportAsync(_listeningCts.Token);
+ var inputReport = await ReadInputReportAsync(token);
- if (data != null && data.Length > 0)
+ if (inputReport?.Data?.Length > 0)
{
- OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, data));
+ OnDataReceived?.Invoke(this, new HidDataReceivedEventArgs(this, inputReport));
}
}
catch (OperationCanceledException)
@@ -397,15 +399,15 @@ namespace EonaCat.HID
catch (NotSupportedException)
{
OnError?.Invoke(this, new HidErrorEventArgs(this, new NotSupportedException("Reading input reports is not supported on this device.")));
- break; // Exit if reading is not supported
+ break;
}
catch (Exception ex)
{
- // Handle exceptions during reading
- if (_listeningCts.IsCancellationRequested)
+ if (token.IsCancellationRequested)
{
- break; // Exit if cancellation was requested
+ break;
}
+
OnError?.Invoke(this, new HidErrorEventArgs(this, ex));
}
}
@@ -417,24 +419,30 @@ namespace EonaCat.HID
}
}
- private Task ReadInputReportAsync(CancellationToken cancellationToken)
+ private Task ReadInputReportAsync(CancellationToken cancellationToken)
{
- var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- // Use overlapped IO pattern from FileStream
var buffer = new byte[InputReportByteLength];
+
+ // Start async read
_deviceStream.BeginRead(buffer, 0, buffer.Length, ar =>
{
try
{
int bytesRead = _deviceStream.EndRead(ar);
+
if (bytesRead == 0)
{
- tcs.SetResult(Array.Empty());
+ // No data read, reportId 0 and empty data
+ tcs.SetResult(new HidReport(0, Array.Empty()));
}
else
{
- tcs.SetResult(buffer.Take(bytesRead).ToArray());
+ // First byte is reportId, rest is data
+ byte reportId = buffer[0];
+ byte[] data = bytesRead > 1 ? buffer.Skip(1).Take(bytesRead - 1).ToArray() : Array.Empty();
+ tcs.SetResult(new HidReport(reportId, data));
}
}
catch (Exception ex)
diff --git a/EonaCat.HID/IHid.cs b/EonaCat.HID/IHid.cs
index 21d7244..41ad017 100644
--- a/EonaCat.HID/IHid.cs
+++ b/EonaCat.HID/IHid.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
using EonaCat.HID.EventArguments;
+using EonaCat.HID.Models;
namespace EonaCat.HID
{
@@ -23,6 +24,10 @@ namespace EonaCat.HID
int InputReportByteLength { get; }
int OutputReportByteLength { get; }
int FeatureReportByteLength { get; }
+ bool IsConnected { get;}
+
+ bool IsReadingSupport { get; }
+ bool IsWritingSupport { get; }
IDictionary Capabilities { get; }
@@ -40,25 +45,25 @@ namespace EonaCat.HID
/// Writes an output report to the device
///
/// Complete report data including ReportID
- Task WriteOutputReportAsync(byte[] data);
+ Task WriteOutputReportAsync(HidReport report);
///
/// Reads an input report
///
/// Input report data
- Task ReadInputReportAsync();
+ Task ReadInputReportAsync();
///
/// Sends a feature report
///
/// Complete feature report data including ReportID
- Task SendFeatureReportAsync(byte[] data);
+ Task SendFeatureReportAsync(HidReport report);
///
/// Gets a feature report
///
/// Feature report data
- Task GetFeatureReportAsync(byte reportId);
+ Task GetFeatureReportAsync(byte reportId);
///
/// Asynchronously read input reports and raise OnDataReceived event
diff --git a/EonaCat.HID/Managers/HidManagerMac.cs b/EonaCat.HID/Managers/HidManagerMac.cs
index 2825319..7b291f3 100644
--- a/EonaCat.HID/Managers/HidManagerMac.cs
+++ b/EonaCat.HID/Managers/HidManagerMac.cs
@@ -14,6 +14,8 @@ namespace EonaCat.HID.Managers.Mac
private IntPtr _hidManager;
private readonly IOHIDDeviceCallback _deviceAddedCallback;
private readonly IOHIDDeviceCallback _deviceRemovedCallback;
+ private readonly Dictionary _knownDevices = new();
+
public event EventHandler OnDeviceInserted;
public event EventHandler OnDeviceRemoved;
@@ -78,27 +80,57 @@ namespace EonaCat.HID.Managers.Mac
return devices;
}
+ private void DeviceInsertedInternal(IHid device)
+ {
+ if (!_knownDevices.ContainsKey(device.DevicePath))
+ {
+ _knownDevices[device.DevicePath] = device;
+ OnDeviceInserted?.Invoke(this, new HidEventArgs(device));
+ }
+ }
+
+ private void DeviceRemovedInternal(IHid device)
+ {
+ if (_knownDevices.ContainsKey(device.DevicePath))
+ {
+ device = _knownDevices[device.DevicePath];
+ _knownDevices.Remove(device.DevicePath);
+ OnDeviceRemoved?.Invoke(this, new HidEventArgs(device));
+ }
+ }
+
private void DeviceAddedCallback(IntPtr context, IntPtr result, IntPtr sender, IntPtr devicePtr)
{
if (devicePtr == IntPtr.Zero)
- {
- return; // Ignore null devices
- }
+ return;
- // Create the device and invoke the event
- var device = new HidMac(devicePtr);
- device.Setup();
- OnDeviceInserted?.Invoke(this, new HidEventArgs(device));
+ try
+ {
+ var device = new HidMac(devicePtr);
+ device.Setup();
+ DeviceInsertedInternal(device);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"DeviceAddedCallback error: {ex.Message}");
+ }
}
+
private void DeviceRemovedCallback(IntPtr context, IntPtr result, IntPtr sender, IntPtr devicePtr)
{
if (devicePtr == IntPtr.Zero)
- {
- return; // Ignore null devices
- }
+ return;
- OnDeviceRemoved?.Invoke(this, new HidEventArgs(null));
+ try
+ {
+ var device = new HidMac(devicePtr);
+ DeviceRemovedInternal(device);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"DeviceRemovedCallback error: {ex.Message}");
+ }
}
public void Dispose()
diff --git a/EonaCat.HID/Managers/HidManagerWindows.cs b/EonaCat.HID/Managers/HidManagerWindows.cs
index 203c6e8..9cdb69a 100644
--- a/EonaCat.HID/Managers/HidManagerWindows.cs
+++ b/EonaCat.HID/Managers/HidManagerWindows.cs
@@ -27,6 +27,7 @@ namespace EonaCat.HID.Managers.Windows
private IntPtr _deviceNotificationHandle;
private WndProc _windowProcDelegate;
private IntPtr _messageWindowHandle;
+ private readonly Dictionary _knownDevices = new();
public event EventHandler OnDeviceInserted;
public event EventHandler OnDeviceRemoved;
@@ -104,29 +105,76 @@ namespace EonaCat.HID.Managers.Windows
{
if (msg == WM_DEVICECHANGE)
{
- var eventType = wParam.ToInt32();
- if (eventType == DBT_DEVICEARRIVAL)
+ int eventType = wParam.ToInt32();
+
+ if (eventType == DBT_DEVICEARRIVAL || eventType == DBT_DEVICEREMOVECOMPLETE)
{
- // Device inserted
- var devBroadcast = Marshal.PtrToStructure(lParam);
- if (devBroadcast.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+ var hdr = Marshal.PtrToStructure(lParam);
+ if (hdr.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
- // Treat all HID devices or filter if needed
- OnDeviceInserted?.Invoke(this, new HidEventArgs(null));
- }
- }
- else if (eventType == DBT_DEVICEREMOVECOMPLETE)
- {
- var devBroadcast = Marshal.PtrToStructure(lParam);
- if (devBroadcast.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
- {
- OnDeviceRemoved?.Invoke(this, new HidEventArgs(null));
+ var devInterface = Marshal.PtrToStructure(lParam);
+
+ // Calculate pointer to string
+ IntPtr stringPtr = IntPtr.Add(lParam, Marshal.SizeOf());
+
+ // Read null-terminated string from unmanaged memory
+ string devicePath = Marshal.PtrToStringUni(stringPtr);
+ if (!string.IsNullOrEmpty(devicePath))
+ {
+ try
+ {
+ if (eventType == DBT_DEVICEARRIVAL)
+ {
+ using (var testHandle = CreateFile(devicePath, 0,
+ FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
+ {
+ if (testHandle.IsInvalid)
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+
+ var device = new HidWindows(devicePath);
+ device.Setup();
+ DeviceInsertedInternal(device);
+ }
+ else if (eventType == DBT_DEVICEREMOVECOMPLETE)
+ {
+ var device = new HidWindows(devicePath);
+ DeviceRemovedInternal(device);
+ }
+
+ return IntPtr.Zero;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[WindowProc] HID device change error: {ex.Message}");
+ }
+ }
}
}
}
+
return DefWindowProc(hwnd, msg, wParam, lParam);
}
+ private void DeviceInsertedInternal(IHid device)
+ {
+ if (!_knownDevices.ContainsKey(device.DevicePath))
+ {
+ _knownDevices[device.DevicePath] = device;
+ OnDeviceInserted?.Invoke(this, new HidEventArgs(device));
+ }
+ }
+
+ private void DeviceRemovedInternal(IHid device)
+ {
+ if (_knownDevices.ContainsKey(device.DevicePath))
+ {
+ device = _knownDevices[device.DevicePath];
+ _knownDevices.Remove(device.DevicePath);
+ OnDeviceRemoved?.Invoke(this, new HidEventArgs(device));
+ }
+ }
+
public IEnumerable Enumerate(ushort? vendorId = null, ushort? productId = null)
{
var list = new List();
@@ -293,8 +341,6 @@ namespace EonaCat.HID.Managers.Windows
}
}
- #region Native Methods and structs
-
internal static class NativeMethods
{
public const int ERROR_INSUFFICIENT_BUFFER = 122;
@@ -308,6 +354,7 @@ namespace EonaCat.HID.Managers.Windows
public const int GENERIC_READ = unchecked((int)0x80000000);
public const int GENERIC_WRITE = 0x40000000;
public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
+ public const int HIDP_STATUS_SUCCESS = 0x110000;
public const int WM_DEVICECHANGE = 0x0219;
public const int DBT_DEVICEARRIVAL = 0x8000;
@@ -347,11 +394,9 @@ namespace EonaCat.HID.Managers.Windows
public int dbcc_devicetype;
public int dbcc_reserved;
public Guid dbcc_classguid;
-
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
- public string dbcc_name;
}
+
[StructLayout(LayoutKind.Sequential)]
public struct SP_DEVICE_INTERFACE_DATA
{
@@ -516,5 +561,4 @@ namespace EonaCat.HID.Managers.Windows
public ushort ProductID;
public ushort VersionNumber;
}
- #endregion
}
\ No newline at end of file
diff --git a/EonaCat.HID/Models/HidReport.cs b/EonaCat.HID/Models/HidReport.cs
new file mode 100644
index 0000000..a8fc471
--- /dev/null
+++ b/EonaCat.HID/Models/HidReport.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace EonaCat.HID.Models
+{
+ public class HidReport
+ {
+ public byte ReportId { get; }
+ public byte[] Data { get; }
+
+ public HidReport(byte reportId, byte[] data)
+ {
+ ReportId = reportId;
+ Data = data ?? Array.Empty();
+ }
+ }
+}