485 lines
16 KiB
C#
485 lines
16 KiB
C#
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<IHid> _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);
|
|
}
|
|
}
|
|
} |