Skip to content

Commit

Permalink
Implemented native USB Printing support for Windows (should work for …
Browse files Browse the repository at this point in the history
…linux as well) with complete printer status report back.

No driver install or Virtual Serial Port needed.
Choose printer from list of usb devices and send data straight to the printer like Serial or Network printer.
Possible fix for Isuues : lukevp#186 lukevp#220 lukevp#223
  • Loading branch information
Gary-gr9 committed Jul 6, 2023
1 parent 7edaa3f commit 5907987
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 3 deletions.
46 changes: 43 additions & 3 deletions ESCPOS_NET.ConsoleTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using System.Threading;
using ESCPOS_NET.Printers;

namespace ESCPOS_NET.ConsoleTest
{
Expand Down Expand Up @@ -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<string> { "1", "2", "3" };
var valid = new List<string> { "1", "2", "3", "4" };
if (!valid.Contains(response))
{
response = "1";
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
34 changes: 34 additions & 0 deletions ESCPOS_NET/Printers/USBPrinter.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
18 changes: 18 additions & 0 deletions ESCPOS_NET/Utils/DeviceDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

namespace ESCPOS_NET
{
public class DeviceDetails
{
public string DisplayName { get; set; }
/// <summary>
/// DEVPKEY_Device_BusReportedDeviceDesc <see href="https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/devpkey.h">see reference</see>
/// </summary>
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; }
}
}
211 changes: 211 additions & 0 deletions ESCPOS_NET/Utils/DeviceFinder.cs
Original file line number Diff line number Diff line change
@@ -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<DeviceDetails> 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<DeviceDetails> GetDevicesbyClassID(string classguid)
{
IntPtr intPtr = IntPtr.Zero;
var devices = new List<DeviceDetails>();
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
}
}

0 comments on commit 5907987

Please sign in to comment.