using EonaCat.HID.EventArguments; using EonaCat.HID.Helpers; using EonaCat.HID.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; namespace EonaCat.HID.Analyzer { // 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. public partial class MainForm : Form { IHidManager _deviceManager; private IHid _device; private IEnumerable _deviceList; public MainForm() { InitializeComponent(); CreateDeviceManager(); } private void CreateDeviceManager() { _deviceManager = HidFactory.CreateDeviceManager(); if (_deviceManager == null) { throw new Exception("Failed to create HID manager."); } _deviceManager.OnDeviceInserted += Hid_Inserted; _deviceManager.OnDeviceRemoved += Hid_Removed; } public void RefreshDevices(ushort? vendorId = null, ushort? productId = null) { _deviceList = _deviceManager.Enumerate(vendorId, productId); } private void MainForm_Load(object sender, System.EventArgs e) { try { rtbEventLog.Font = new Font("Consolas", 9); } catch (Exception ex) { PopupException(ex.Message); } } private void MainForm_Shown(object sender, System.EventArgs e) { try { RefreshDevices(); UpdateDeviceList(); toolStripStatusLabel1.Text = "Please select device and click open to start."; } catch (Exception ex) { PopupException(ex.Message); } } private void AppendEventLog(string result, Color? color = null, bool appendNewLine = true) { var currentColor = color ?? Color.Black; if (appendNewLine) { result += Environment.NewLine; } // update from UI thread Invoke(new MethodInvoker(() => { rtbEventLog.SelectionStart = rtbEventLog.TextLength; rtbEventLog.SelectionLength = 0; rtbEventLog.SelectionColor = currentColor; rtbEventLog.AppendText(result); if (!rtbEventLog.Focused) { rtbEventLog.ScrollToCaret(); } })); } private void UpdateDeviceList() { dataGridView1.SelectionChanged -= DataGridView1_SelectionChanged; dataGridView1.Rows.Clear(); for (int i = 0; i < _deviceList.Count(); i++) { IHid device = _deviceList.ElementAt(i); var deviceName = ""; var deviceManufacturer = ""; var deviceSerialNumber = ""; deviceName = device.ProductName; deviceManufacturer = device.Manufacturer; deviceSerialNumber = device.SerialNumber; var isWritingSupported = device.IsWritingSupport; var isReadingSupported = device.IsReadingSupport; var row = new string[] { (i + 1).ToString(), deviceName, deviceManufacturer, deviceSerialNumber, isReadingSupported.ToString(), isWritingSupported.ToString(), device.InputReportByteLength.ToString(), device.OutputReportByteLength.ToString(), device.FeatureReportByteLength.ToString(), $"Vendor:{device.VendorId:X4} Product:{device.ProductId:X4} Revision:{device.VendorId:X4}", device.DevicePath }; dataGridView1.Rows.Add(row); } dataGridView1.SelectionChanged += DataGridView1_SelectionChanged; DataGridView1_SelectionChanged(this, null); } private void PopupException(string message, string caption = "Exception") { Invoke(new Action(() => { MessageBox.Show(message, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); })); } private void NewToolStripMenuItem_Click(object sender, System.EventArgs e) { try { Process.Start(Assembly.GetExecutingAssembly().Location); } catch (Exception ex) { PopupException(ex.Message); } } private void ExitToolStripMenuItem_Click(object sender, System.EventArgs e) { try { this.Close(); } catch (Exception ex) { PopupException(ex.Message); } } private void AboutToolStripMenuItem_Click(object sender, EventArgs e) { try { HelpToolStripButton_Click(sender, e); } catch (Exception ex) { PopupException(ex.Message); } } private void ToolStripButtonReload_Click(object sender, EventArgs e) { try { RefreshDevices(); UpdateDeviceList(); } catch (Exception ex) { PopupException(ex.Message); } } private void ToolStripButtonFilter_Click(object sender, EventArgs e) { try { ushort? vid = null; ushort? pid = null; var str = toolStripTextBoxVidPid.Text.Split(':'); if (!string.IsNullOrEmpty(toolStripTextBoxVidPid.Text)) { vid = ushort.Parse(str[0], NumberStyles.AllowHexSpecifier); pid = ushort.Parse(str[1], NumberStyles.AllowHexSpecifier); } RefreshDevices(vid, pid); UpdateDeviceList(); } catch (Exception ex) { PopupException(ex.Message); } } private void ToolStripButtonConnect_Click(object sender, EventArgs e) { ConnectToSelectedDeviceAsync().ConfigureAwait(false); } private async Task ConnectToSelectedDeviceAsync() { await Task.Run(async () => { try { _device = _deviceList.ElementAt(dataGridView1.SelectedRows[0].Index); if (_device == null) { throw new Exception("Could not find Hid USB Device with specified VID PID"); } var device = _device; device.OnDataReceived -= OnDataReceived; device.OnDataReceived += OnDataReceived; device.OnError -= OnError; device.OnError += OnError; device.Open(); AppendEventLog($"Connected to device {_device.ProductName}", Color.Green); AppendEventLog($"Started listening to device {_device.ProductName}", Color.Green); await device.StartListeningAsync(default).ConfigureAwait(false); } catch (Exception ex) { PopupException(ex.Message); } }); } private void OnError(object sender, HidErrorEventArgs e) { try { var str = $"Device error for {e.Device.ProductName} => {e.Exception.Message}"; AppendEventLog(str, Color.Red); } catch (Exception ex) { PopupException(ex.Message); } } private void OnDataReceived(object sender, HidDataReceivedEventArgs e) { try { var str = $"Rx Input Report from device {e.Device.ProductName} => {BitConverter.ToString(e.Report.Data)}"; AppendEventLog(str, Color.Blue); } catch (Exception ex) { PopupException(ex.Message); } } private void Hid_Removed(object sender, HidEventArgs e) { try { 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) { PopupException(ex.Message); } } private void Hid_Inserted(object sender, HidEventArgs e) { try { 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) { PopupException(ex.Message); } } private void ToolStripButtonClear_Click(object sender, EventArgs e) { try { rtbEventLog.Clear(); } catch (Exception ex) { PopupException(ex.Message); } } private void HelpToolStripButton_Click(object sender, EventArgs e) { try { var aboutbox = new AboutBox(); aboutbox.ShowDialog(); } catch (Exception ex) { PopupException(ex.Message); } } private async void ButtonReadInput_Click(object sender, EventArgs e) { try { if (_device == null) { AppendEventLog("No device connected. Please select a device and click 'Connect'.", Color.Red); return; } var len = (int)_device.InputReportByteLength; if (len <= 0) { throw new Exception("This device has no Input Report support!"); } var report = await _device.ReadInputReportAsync(); if (report == null || report.Data.Length < 2) { 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}", report.Data.Length, BitConverter.ToString(report.Data)); AppendEventLog(str, Color.Blue); } catch (Exception ex) { PopupException(ex.Message); } } private async void ButtonWriteOutput_Click(object sender, EventArgs e) { 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[] dataBuffer = ByteHelper.HexStringToByteArray(textBoxWriteData.Text.Trim()); 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."); } 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 sending output report: " + ex.Message); } } private async void ButtonReadFeature_Click(object sender, EventArgs e) { try { var hidReportId = byte.Parse(comboBoxReportId.Text); var len = _device.FeatureReportByteLength; if (len <= 0) { throw new Exception("This device has no Feature Report support!"); } 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) { PopupException(ex.Message); } } private async void ButtonWriteFeature_Click(object sender, EventArgs e) { try { if (!byte.TryParse(comboBoxReportId.Text, out var hidReportId)) { throw new FormatException("Invalid Report ID format."); } 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) { PopupException(ex.Message); } } private void DataGridView1_SelectionChanged(object sender, EventArgs e) { try { if (dataGridView1.SelectedRows.Count <= 0) { return; } var index = dataGridView1.SelectedRows[0].Index; var info = _deviceList.ElementAt(index); toolStripTextBoxVidPid.Text = string.Format("{0:X4}:{1:X4}", info.VendorId, info.ProductId); } catch (Exception ex) { PopupException(ex.Message); } } private void dataGridView1_DoubleClick(object sender, EventArgs e) { ConnectToSelectedDeviceAsync().ConfigureAwait(false); } private void toolStripTextBoxVidPid_Enter(object sender, EventArgs e) { ToolStripButtonFilter_Click(sender, e); } } }