diff --git a/ESCPOS_NET.ConsoleTest/Program.cs b/ESCPOS_NET.ConsoleTest/Program.cs index 4e6c60c..6d4bb06 100644 --- a/ESCPOS_NET.ConsoleTest/Program.cs +++ b/ESCPOS_NET.ConsoleTest/Program.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using System.Threading; +using ESCPOS_NET.Printers; namespace ESCPOS_NET.ConsoleTest { @@ -32,13 +33,15 @@ static void Main(string[] args) Console.WriteLine("1 ) Test Serial Port"); Console.WriteLine("2 ) Test Network Printer"); Console.WriteLine("3 ) Test Samba-Shared Printer"); + Console.WriteLine("4 ) Test USB Printer"); Console.Write("Choice: "); string comPort = ""; string ip; string networkPort; string smbPath; + string usbPort = string.Empty; response = Console.ReadLine(); - var valid = new List { "1", "2", "3" }; + var valid = new List { "1", "2", "3", "4" }; if (!valid.Contains(response)) { response = "1"; @@ -58,8 +61,8 @@ static void Main(string[] args) { comPort = "COM5"; } - } - Console.Write("Baud Rate (enter for default 115200): "); + } + Console.Write("Baud Rate (enter for default 115200): "); if (!int.TryParse(Console.ReadLine(), out var baudRate)) { baudRate = 115200; @@ -100,6 +103,43 @@ static void Main(string[] args) printer = new SambaPrinter(tempFileBasePath: @"C:\Temp", filePath: smbPath); } + else if (choice == 4) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var usboptions = DeviceFinder.GetDevices();//gets the usb devices connected to the pc + if (usboptions.Count > 0) + { + int i = 0; + int num = 1; + while (i < usboptions.Count) + { + + Console.WriteLine($"{i + num}. Name: {usboptions[i].BusName} S/N: {usboptions[i].SerialNum}"); + i++; + //serial number and name for printer. Name reported might just be USB Printing Support or something generic + //the property necessary for printing is Device Path this is just for UI + } + Console.Write("Choose Printer (eg. 1): "); + string c = Console.ReadLine(); + if (int.TryParse(c, out int chosen) && chosen > 0) + { + usbPort = usboptions[chosen - 1].DevicePath; + } + } + printer = new USBPrinter(usbPort); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Console.Write("File / usb path (eg. /dev/usb/lp0): "); + usbPort = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(usbPort)) + { + comPort = "/dev/usb/lp0"; + } + printer = new FilePrinter(filePath: usbPort, false); + } + } bool monitor = false; Thread.Sleep(500); diff --git a/ESCPOS_NET/Printers/USBPrinter.cs b/ESCPOS_NET/Printers/USBPrinter.cs new file mode 100644 index 0000000..ccd8f13 --- /dev/null +++ b/ESCPOS_NET/Printers/USBPrinter.cs @@ -0,0 +1,34 @@ +using System.IO; + +namespace ESCPOS_NET.Printers +{ + public class USBPrinter : BasePrinter + { + private readonly FileStream _rfile; + private readonly FileStream _wfile; + public USBPrinter(string usbPath) + : base() + { + //keeping separate file streams performs better + //while using 1 file stream printers were having intermittent issues while printing + //your milege may vary + _rfile = File.Open(usbPath, FileMode.Open, FileAccess.Read); + _wfile = File.Open(usbPath, FileMode.Open, FileAccess.Write); + Writer = new BinaryWriter(_wfile); + Reader = new BinaryReader(_rfile); + } + + ~USBPrinter() + { + Dispose(false); + } + + protected override void OverridableDispose() + { + _rfile?.Close(); + _rfile?.Dispose(); + _wfile?.Close(); + _wfile?.Dispose(); + } + } +} diff --git a/ESCPOS_NET/Utils/DeviceDetails.cs b/ESCPOS_NET/Utils/DeviceDetails.cs new file mode 100644 index 0000000..84762f0 --- /dev/null +++ b/ESCPOS_NET/Utils/DeviceDetails.cs @@ -0,0 +1,18 @@ + +namespace ESCPOS_NET +{ + public class DeviceDetails + { + public string DisplayName { get; set; } + /// + /// DEVPKEY_Device_BusReportedDeviceDesc see reference + /// + public string BusName { get; set; } + public string SerialNum { get; set; } + public string DeviceDescription { get; set; } + public string DevicePath { get; set; } + public string Manufacturer { get; set; } + public ushort VID { get; set; } + public ushort PID { get; set; } + } +} diff --git a/ESCPOS_NET/Utils/DeviceFinder.cs b/ESCPOS_NET/Utils/DeviceFinder.cs new file mode 100644 index 0000000..97cd48a --- /dev/null +++ b/ESCPOS_NET/Utils/DeviceFinder.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; + +namespace ESCPOS_NET +{ + public class DeviceFinder + { + #region Win API + private struct SP_DEVICE_INTERFACE_DATA + { + internal int cbSize; + + internal Guid InterfaceClassGuid; + + internal int Flags; + + internal IntPtr Reserved; + } + private struct SP_DEVINFO_DATA + { + internal int cbSize; + + internal Guid ClassGuid; + + internal int DevInst; + + internal IntPtr Reserved; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct DEVPROPKEY + { + public Guid fmtid; + public uint pid; + } + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags); + [DllImport("setupapi.dll", SetLastError = true)] + private static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, int MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, IntPtr DeviceInfoData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out uint propertyType, IntPtr propertyBuffer, uint propertyBufferSize, out uint requiredSize, uint flags); + [DllImport("setupapi.dll", SetLastError = true)] + private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + private static void DEFINE_DEVPROPKEY(out DEVPROPKEY key, uint l, ushort w1, ushort w2, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8, uint pid) + { + key.fmtid = new Guid(l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8); + key.pid = pid; + } + #endregion + #region Device Methods + public static List GetDevices() + { + //https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-device + //USB devices: a5dcbf10-6530-11d2-901f-00c04fb951ed + //Bluetooth devices: 00f40965-e89d-4487-9890-87c3abb211f4 + var devices = GetDevicesbyClassID("a5dcbf10-6530-11d2-901f-00c04fb951ed"); + return devices; + } + private static List GetDevicesbyClassID(string classguid) + { + IntPtr intPtr = IntPtr.Zero; + var devices = new List(); + try + { + Guid guid = new(classguid); + intPtr = SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, 18); + if (intPtr == INVALID_HANDLE_VALUE) + { + Win32Exception("Failed to enumerate devices."); + } + + int num = 0; + while (true) + { + SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = default; + DeviceInterfaceData.cbSize = Marshal.SizeOf((object)DeviceInterfaceData); + if (!SetupDiEnumDeviceInterfaces(intPtr, IntPtr.Zero, ref guid, num, ref DeviceInterfaceData)) + { + break; + } + + int RequiredSize = 0; + if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, IntPtr.Zero, 0, ref RequiredSize, IntPtr.Zero) && Marshal.GetLastWin32Error() != 122) + { + Win32Exception("Failed to get interface details buffer size."); + } + + IntPtr intPtr2 = IntPtr.Zero; + try + { + intPtr2 = Marshal.AllocHGlobal(RequiredSize); + Marshal.WriteInt32(intPtr2, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); + SP_DEVINFO_DATA DeviceInfoData = default; + DeviceInfoData.cbSize = Marshal.SizeOf((object)DeviceInfoData); + if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, intPtr2, RequiredSize, ref RequiredSize, ref DeviceInfoData)) + { + Win32Exception("Failed to get device interface details."); + } + string path = Marshal.PtrToStringUni(new IntPtr(intPtr2.ToInt64() + 4)); + + DeviceDetails deviceDetails = GetDeviceDetails(path, intPtr, DeviceInfoData); + devices.Add(deviceDetails); + } + finally + { + if (intPtr2 != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr2); + intPtr2 = IntPtr.Zero; + } + } + num++; + } + if (Marshal.GetLastWin32Error() != 259) + { + Win32Exception("Failed to get device interface."); + } + } + finally + { + if (intPtr != IntPtr.Zero && intPtr != INVALID_HANDLE_VALUE) + { + SetupDiDestroyDeviceInfoList(intPtr); + } + } + return devices; + } + private static DeviceDetails GetDeviceDetails(string devicePath, IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData) + { + DeviceDetails result = new DeviceDetails + { + DevicePath = devicePath + }; + if (!string.IsNullOrWhiteSpace(devicePath) && devicePath.Contains("#")) + { + var spserial = devicePath.Split('#'); + result.SerialNum = spserial[spserial.Length - 2]; + //last in array is guid and last second is the serial number + //serial number might not be the actual serial number for the device it may be system generated + } + DEFINE_DEVPROPKEY(out DEVPROPKEY key, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); + result.BusName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); + result.DisplayName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); + result.DeviceDescription = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); + result.Manufacturer = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); + string[] multiStringProperty = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key); + Regex regex = new Regex("VID_([0-9A-F]{4})&PID_([0-9A-F]{4})", RegexOptions.IgnoreCase); + bool flag = false; + string[] array = multiStringProperty; + foreach (string input in array) + { + Match match = regex.Match(input); + if (match.Success) + { + result.VID = ushort.Parse(match.Groups[1].Value, NumberStyles.AllowHexSpecifier); + result.PID = ushort.Parse(match.Groups[2].Value, NumberStyles.AllowHexSpecifier); + flag = true; + break; + } + } + + if (!flag) + { + Win32Exception("Failed to find VID and PID for USB device. No hardware ID could be parsed."); + } + + return result; + } + private static string[] GetPropertyForDevice(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, DEVPROPKEY key) + { + IntPtr buffer = IntPtr.Zero; + try + { + uint buflen = 512; + buffer = Marshal.AllocHGlobal((int)buflen); + if (!SetupDiGetDeviceProperty(deviceInfoSet, ref deviceInfoData, ref key, out uint proptype, buffer, buflen, out uint outsize, 0)) + { + Win32Exception("Failed to get property for device"); + } + byte[] lbuffer = new byte[outsize]; + Marshal.Copy(buffer, lbuffer, 0, (int)outsize); + var val = Encoding.Unicode.GetString(lbuffer); + var aval = val.Split('\0'); + return aval; + } + finally + { + if (buffer != IntPtr.Zero) + Marshal.FreeHGlobal(buffer); + } + } + private static void Win32Exception(string message) + { + throw new Exception(message, new Win32Exception(Marshal.GetLastWin32Error())); + } + #endregion + } +}