Skip to content

Commit 4e97861

Browse files
committed
make package work with XPS-based printer drivers
Add Printer.DriverInfo that retrieves information about printer driver. Use DriverInfo to determine if printer driver is XPS-based. Pass "XPS_PASS" instead of "RAW", when using XPS-based driver. Thanks to Newton Rocha for original version of this code. Fixes #1 Fixes #3
1 parent cf858f4 commit 4e97861

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

cmd/print/print.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func printOneDocument(printerName, documentName string, lines []string) error {
7777
}
7878
defer p.Close()
7979

80-
err = p.StartDocument(documentName, "RAW")
80+
err = p.StartRawDocument(documentName)
8181
if err != nil {
8282
return err
8383
}

printer.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,39 @@ type PRINTER_INFO_5 struct {
2626
TransmissionRetryTimeout uint32
2727
}
2828

29+
type DRIVER_INFO_8 struct {
30+
Version uint32
31+
Name *uint16
32+
Environment *uint16
33+
DriverPath *uint16
34+
DataFile *uint16
35+
ConfigFile *uint16
36+
HelpFile *uint16
37+
DependentFiles *uint16
38+
MonitorName *uint16
39+
DefaultDataType *uint16
40+
PreviousNames *uint16
41+
DriverDate syscall.Filetime
42+
DriverVersion uint64
43+
MfgName *uint16
44+
OEMUrl *uint16
45+
HardwareID *uint16
46+
Provider *uint16
47+
PrintProcessor *uint16
48+
VendorSetup *uint16
49+
ColorProfiles *uint16
50+
InfPath *uint16
51+
PrinterDriverAttributes uint32
52+
CoreDriverDependencies *uint16
53+
MinInboxDriverVerDate syscall.Filetime
54+
MinInboxDriverVerVersion uint32
55+
}
56+
2957
const (
3058
PRINTER_ENUM_LOCAL = 2
3159
PRINTER_ENUM_CONNECTIONS = 4
60+
61+
PRINTER_DRIVER_XPS = 0x00000002
3262
)
3363

3464
//sys GetDefaultPrinter(buf *uint16, bufN *uint32) (err error) = winspool.GetDefaultPrinterW
@@ -40,6 +70,7 @@ const (
4070
//sys StartPagePrinter(h syscall.Handle) (err error) = winspool.StartPagePrinter
4171
//sys EndPagePrinter(h syscall.Handle) (err error) = winspool.EndPagePrinter
4272
//sys EnumPrinters(flags uint32, name *uint16, level uint32, buf *byte, bufN uint32, needed *uint32, returned *uint32) (err error) = winspool.EnumPrintersW
73+
//sys GetPrinterDriver(h syscall.Handle, env *uint16, level uint32, di *byte, n uint32, needed *uint32) (err error) = winspool.GetPrinterDriverW
4374

4475
func Default() (string, error) {
4576
b := make([]uint16, 3)
@@ -97,6 +128,40 @@ func Open(name string) (*Printer, error) {
97128
return &p, nil
98129
}
99130

131+
// DriverInfo stores information about printer driver.
132+
type DriverInfo struct {
133+
Name string
134+
Environment string
135+
DriverPath string
136+
Attributes uint32
137+
}
138+
139+
// DriverInfo returns information about printer p driver.
140+
func (p *Printer) DriverInfo() (*DriverInfo, error) {
141+
var needed uint32
142+
b := make([]byte, 1024*10)
143+
for {
144+
err := GetPrinterDriver(p.h, nil, 8, &b[0], uint32(len(b)), &needed)
145+
if err == nil {
146+
break
147+
}
148+
if err != syscall.ERROR_INSUFFICIENT_BUFFER {
149+
return nil, err
150+
}
151+
if needed <= uint32(len(b)) {
152+
return nil, err
153+
}
154+
b = make([]byte, needed)
155+
}
156+
di := (*DRIVER_INFO_8)(unsafe.Pointer(&b[0]))
157+
return &DriverInfo{
158+
Attributes: di.PrinterDriverAttributes,
159+
Name: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.Name))[:]),
160+
DriverPath: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.DriverPath))[:]),
161+
Environment: syscall.UTF16ToString((*[2048]uint16)(unsafe.Pointer(di.Environment))[:]),
162+
}, nil
163+
}
164+
100165
func (p *Printer) StartDocument(name, datatype string) error {
101166
d := DOC_INFO_1{
102167
DocName: &(syscall.StringToUTF16(name))[0],
@@ -106,6 +171,22 @@ func (p *Printer) StartDocument(name, datatype string) error {
106171
return StartDocPrinter(p.h, 1, &d)
107172
}
108173

174+
// StartRawDocument calls StartDocument and passes either "RAW" or "XPS_PASS"
175+
// as a document type, depending if printer driver is XPS-based or not.
176+
func (p *Printer) StartRawDocument(name string) error {
177+
di, err := p.DriverInfo()
178+
if err != nil {
179+
return err
180+
}
181+
// See https://support.microsoft.com/en-us/help/2779300/v4-print-drivers-using-raw-mode-to-send-pcl-postscript-directly-to-the
182+
// for details.
183+
datatype := "RAW"
184+
if di.Attributes&PRINTER_DRIVER_XPS != 0 {
185+
datatype = "XPS_PASS"
186+
}
187+
return p.StartDocument(name, datatype)
188+
}
189+
109190
func (p *Printer) Write(b []byte) (int, error) {
110191
var written uint32
111192
err := WritePrinter(p.h, &b[0], uint32(len(b)), &written)

printer_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,22 @@ func TestReadNames(t *testing.T) {
5454
}
5555
t.Fatal("Default printed %q is not listed amongst printers returned by ReadNames %q", name, names)
5656
}
57+
58+
func TestDriverInfo(t *testing.T) {
59+
name, err := Default()
60+
if err != nil {
61+
t.Fatalf("Default failed: %v", err)
62+
}
63+
64+
p, err := Open(name)
65+
if err != nil {
66+
t.Fatalf("Open failed: %v", err)
67+
}
68+
defer p.Close()
69+
70+
di, err := p.DriverInfo()
71+
if err != nil {
72+
t.Fatalf("DriverInfo failed: %v", err)
73+
}
74+
t.Logf("%+v", di)
75+
}

zapi.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var (
1919
procStartPagePrinter = modwinspool.NewProc("StartPagePrinter")
2020
procEndPagePrinter = modwinspool.NewProc("EndPagePrinter")
2121
procEnumPrintersW = modwinspool.NewProc("EnumPrintersW")
22+
procGetPrinterDriverW = modwinspool.NewProc("GetPrinterDriverW")
2223
)
2324

2425
func GetDefaultPrinter(buf *uint16, bufN *uint32) (err error) {
@@ -128,3 +129,15 @@ func EnumPrinters(flags uint32, name *uint16, level uint32, buf *byte, bufN uint
128129
}
129130
return
130131
}
132+
133+
func GetPrinterDriver(h syscall.Handle, env *uint16, level uint32, di *byte, n uint32, needed *uint32) (err error) {
134+
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)))
135+
if r1 == 0 {
136+
if e1 != 0 {
137+
err = error(e1)
138+
} else {
139+
err = syscall.EINVAL
140+
}
141+
}
142+
return
143+
}

0 commit comments

Comments
 (0)