diff --git a/cmd/print/print.go b/cmd/print/print.go index 078aa54..e3ba987 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -77,7 +77,7 @@ func printOneDocument(printerName, documentName string, lines []string) error { } defer p.Close() - err = p.StartDocument(documentName, "RAW") + err = p.StartRawDocument(documentName) if err != nil { return err } diff --git a/printer.go b/printer.go index e8accc5..4386369 100644 --- a/printer.go +++ b/printer.go @@ -26,9 +26,39 @@ type PRINTER_INFO_5 struct { TransmissionRetryTimeout uint32 } +type DRIVER_INFO_8 struct { + Version uint32 + Name *uint16 + Environment *uint16 + DriverPath *uint16 + DataFile *uint16 + ConfigFile *uint16 + HelpFile *uint16 + DependentFiles *uint16 + MonitorName *uint16 + DefaultDataType *uint16 + PreviousNames *uint16 + DriverDate syscall.Filetime + DriverVersion uint64 + MfgName *uint16 + OEMUrl *uint16 + HardwareID *uint16 + Provider *uint16 + PrintProcessor *uint16 + VendorSetup *uint16 + ColorProfiles *uint16 + InfPath *uint16 + PrinterDriverAttributes uint32 + CoreDriverDependencies *uint16 + MinInboxDriverVerDate syscall.Filetime + MinInboxDriverVerVersion uint32 +} + const ( PRINTER_ENUM_LOCAL = 2 PRINTER_ENUM_CONNECTIONS = 4 + + PRINTER_DRIVER_XPS = 0x00000002 ) //sys GetDefaultPrinter(buf *uint16, bufN *uint32) (err error) = winspool.GetDefaultPrinterW @@ -40,6 +70,7 @@ const ( //sys StartPagePrinter(h syscall.Handle) (err error) = winspool.StartPagePrinter //sys EndPagePrinter(h syscall.Handle) (err error) = winspool.EndPagePrinter //sys EnumPrinters(flags uint32, name *uint16, level uint32, buf *byte, bufN uint32, needed *uint32, returned *uint32) (err error) = winspool.EnumPrintersW +//sys GetPrinterDriver(h syscall.Handle, env *uint16, level uint32, di *byte, n uint32, needed *uint32) (err error) = winspool.GetPrinterDriverW func Default() (string, error) { b := make([]uint16, 3) @@ -97,6 +128,40 @@ func Open(name string) (*Printer, error) { return &p, nil } +// DriverInfo stores information about printer driver. +type DriverInfo struct { + Name string + Environment string + DriverPath string + Attributes uint32 +} + +// DriverInfo returns information about printer p driver. +func (p *Printer) DriverInfo() (*DriverInfo, error) { + var needed uint32 + b := make([]byte, 1024*10) + for { + err := GetPrinterDriver(p.h, nil, 8, &b[0], uint32(len(b)), &needed) + if err == nil { + break + } + if err != syscall.ERROR_INSUFFICIENT_BUFFER { + return nil, err + } + if needed <= uint32(len(b)) { + return nil, err + } + b = make([]byte, needed) + } + di := (*DRIVER_INFO_8)(unsafe.Pointer(&b[0])) + return &DriverInfo{ + Attributes: di.PrinterDriverAttributes, + Name: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.Name))[:]), + DriverPath: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.DriverPath))[:]), + Environment: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.Environment))[:]), + }, nil +} + func (p *Printer) StartDocument(name, datatype string) error { d := DOC_INFO_1{ DocName: &(syscall.StringToUTF16(name))[0], @@ -106,6 +171,22 @@ func (p *Printer) StartDocument(name, datatype string) error { return StartDocPrinter(p.h, 1, &d) } +// StartRawDocument calls StartDocument and passes either "RAW" or "XPS_PASS" +// as a document type, depending if printer driver is XPS-based or not. +func (p *Printer) StartRawDocument(name string) error { + di, err := p.DriverInfo() + if err != nil { + return err + } + // See https://support.microsoft.com/en-us/help/2779300/v4-print-drivers-using-raw-mode-to-send-pcl-postscript-directly-to-the + // for details. + datatype := "RAW" + if di.Attributes&PRINTER_DRIVER_XPS != 0 { + datatype = "XPS_PASS" + } + return p.StartDocument(name, datatype) +} + func (p *Printer) Write(b []byte) (int, error) { var written uint32 err := WritePrinter(p.h, &b[0], uint32(len(b)), &written) diff --git a/printer_test.go b/printer_test.go index 438af58..3f462b9 100644 --- a/printer_test.go +++ b/printer_test.go @@ -54,3 +54,22 @@ func TestReadNames(t *testing.T) { } t.Fatal("Default printed %q is not listed amongst printers returned by ReadNames %q", name, names) } + +func TestDriverInfo(t *testing.T) { + name, err := Default() + if err != nil { + t.Fatalf("Default failed: %v", err) + } + + p, err := Open(name) + if err != nil { + t.Fatalf("Open failed: %v", err) + } + defer p.Close() + + di, err := p.DriverInfo() + if err != nil { + t.Fatalf("DriverInfo failed: %v", err) + } + t.Logf("%+v", di) +} diff --git a/zapi.go b/zapi.go index 923c32e..6549c96 100644 --- a/zapi.go +++ b/zapi.go @@ -19,6 +19,7 @@ var ( procStartPagePrinter = modwinspool.NewProc("StartPagePrinter") procEndPagePrinter = modwinspool.NewProc("EndPagePrinter") procEnumPrintersW = modwinspool.NewProc("EnumPrintersW") + procGetPrinterDriverW = modwinspool.NewProc("GetPrinterDriverW") ) func GetDefaultPrinter(buf *uint16, bufN *uint32) (err error) { @@ -128,3 +129,15 @@ func EnumPrinters(flags uint32, name *uint16, level uint32, buf *byte, bufN uint } return } + +func GetPrinterDriver(h syscall.Handle, env *uint16, level uint32, di *byte, n uint32, needed *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetPrinterDriverW.Addr(), 6, uintptr(h), uintptr(unsafe.Pointer(env)), uintptr(level), uintptr(unsafe.Pointer(di)), uintptr(n), uintptr(unsafe.Pointer(needed))) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +}