Skip to content
This repository was archived by the owner on Jun 17, 2022. It is now read-only.

Commit 7f96e58

Browse files
pastarmovjjay0lee
authored andcommitted
Listen for printer changes and refresh the printers list when it happens (#361)
* Listen for printer changes and refresh the printers list when such happen. This reduces the delay after new printer is added until it shows up as a printer avaiable for printing in Goolge Cloud Print on Windows. Timer based refreshes still need to happen due to other sources of printers eg Privet etc. * Adjust the code to the commited version of golang.org/x/sys/windows/svc.
1 parent cd1943f commit 7f96e58

File tree

6 files changed

+109
-34
lines changed

6 files changed

+109
-34
lines changed

gcp-windows-connector/gcp-windows-connector.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha
171171
}
172172
defer pm.Quit()
173173

174+
statusHandle := svc.StatusHandle()
175+
if statusHandle != 0 {
176+
err = ws.StartPrinterNotifications(statusHandle)
177+
if err != nil {
178+
log.Error(err)
179+
} else {
180+
log.Info("Successfully registered for device notifications.")
181+
}
182+
}
183+
174184
if config.CloudPrintingEnable {
175185
if config.LocalPrintingEnable {
176186
log.Infof("Ready to rock as proxy '%s' and in local mode", config.ProxyName)
@@ -198,6 +208,15 @@ func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s cha
198208

199209
return false, 0
200210

211+
case svc.DeviceEvent:
212+
log.Infof("Printers change notification received %d.", request.EventType)
213+
// Delay the action to let the OS finish the process or we might
214+
// not see the new printer. Even if we miss it eventually the timed updates
215+
// will pick it up.
216+
time.AfterFunc(time.Second*5, func() {
217+
pm.SyncPrinters(false)
218+
})
219+
201220
default:
202221
log.Errorf("Received unsupported service command from service control manager: %d", request.Cmd)
203222
}

lib/printer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func FilterBlacklistPrinters(printers []Printer, list map[string]interface{}) []
239239

240240
func FilterWhitelistPrinters(printers []Printer, list map[string]interface{}) []Printer {
241241
if len(list) == 0 {
242-
return printers; // Empty whitelist means don't use whitelist
242+
return printers // Empty whitelist means don't use whitelist
243243
}
244244

245245
return filterPrinters(printers, list, true)

lib/printer_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ https://developers.google.com/open-source/licenses/bsd
99
package lib
1010

1111
import (
12-
"testing"
1312
"reflect"
13+
"testing"
1414
)
1515

1616
func TestFilterBlacklistPrinters(t *testing.T) {
17-
printers := []Printer {
17+
printers := []Printer{
1818
{Name: "Stay1"},
1919
{Name: "Go1"},
2020
{Name: "Go2"},
2121
{Name: "Stay2"},
2222
}
23-
blacklist := map[string]interface{} {
23+
blacklist := map[string]interface{}{
2424
"Go1": "",
2525
"Go2": "",
2626
}
27-
correctFilteredPrinters := []Printer {
27+
correctFilteredPrinters := []Printer{
2828
{Name: "Stay1"},
2929
{Name: "Stay2"},
3030
}
@@ -37,17 +37,17 @@ func TestFilterBlacklistPrinters(t *testing.T) {
3737
}
3838

3939
func TestFilterWhitelistPrinters(t *testing.T) {
40-
printers := []Printer {
40+
printers := []Printer{
4141
{Name: "Stay1"},
4242
{Name: "Go1"},
4343
{Name: "Go2"},
4444
{Name: "Stay2"},
4545
}
46-
whitelist := map[string]interface{} {
46+
whitelist := map[string]interface{}{
4747
"Stay1": "",
4848
"Stay2": "",
4949
}
50-
correctFilteredPrinters := []Printer {
50+
correctFilteredPrinters := []Printer{
5151
{Name: "Stay1"},
5252
{Name: "Stay2"},
5353
}

manager/printermanager.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func NewPrinterManager(native NativePrintSystem, gcp *gcp.GoogleCloudPrint, priv
105105
// Sync once before returning, to make sure things are working.
106106
// Ignore privet updates this first time because Privet always starts
107107
// with zero printers.
108-
if err = pm.syncPrinters(true); err != nil {
108+
if err = pm.SyncPrinters(true); err != nil {
109109
return nil, err
110110
}
111111

@@ -146,7 +146,7 @@ func (pm *PrinterManager) syncPrintersPeriodically(interval time.Duration) {
146146
for {
147147
select {
148148
case <-t.C:
149-
if err := pm.syncPrinters(false); err != nil {
149+
if err := pm.SyncPrinters(false); err != nil {
150150
log.Error(err)
151151
}
152152
t.Reset(interval)
@@ -158,7 +158,7 @@ func (pm *PrinterManager) syncPrintersPeriodically(interval time.Duration) {
158158
}()
159159
}
160160

161-
func (pm *PrinterManager) syncPrinters(ignorePrivet bool) error {
161+
func (pm *PrinterManager) SyncPrinters(ignorePrivet bool) error {
162162
log.Info("Synchronizing printers, stand by")
163163

164164
// Get current snapshot of native printers.
@@ -442,7 +442,7 @@ func (pm *PrinterManager) printJob(nativePrinterName, filename, title, user, job
442442
}
443443
}
444444

445-
func (pm *PrinterManager)releaseJob(printerName string, nativeJobID uint32, jobID string) {
445+
func (pm *PrinterManager) releaseJob(printerName string, nativeJobID uint32, jobID string) {
446446
if err := pm.native.ReleaseJob(printerName, nativeJobID); err != nil {
447447
log.ErrorJob(jobID, err)
448448
}

winspool/win32.go

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,37 @@ import (
1515
"strings"
1616
"syscall"
1717
"unsafe"
18+
19+
"golang.org/x/sys/windows"
1820
)
1921

2022
var (
2123
gdi32 = syscall.MustLoadDLL("gdi32.dll")
2224
kernel32 = syscall.MustLoadDLL("kernel32.dll")
2325
ntoskrnl = syscall.MustLoadDLL("ntoskrnl.exe")
2426
winspool = syscall.MustLoadDLL("winspool.drv")
25-
26-
abortDocProc = gdi32.MustFindProc("AbortDoc")
27-
closePrinterProc = winspool.MustFindProc("ClosePrinter")
28-
createDCProc = gdi32.MustFindProc("CreateDCW")
29-
deleteDCProc = gdi32.MustFindProc("DeleteDC")
30-
deviceCapabilitiesProc = winspool.MustFindProc("DeviceCapabilitiesW")
31-
documentPropertiesProc = winspool.MustFindProc("DocumentPropertiesW")
32-
endDocProc = gdi32.MustFindProc("EndDoc")
33-
endPageProc = gdi32.MustFindProc("EndPage")
34-
enumPrintersProc = winspool.MustFindProc("EnumPrintersW")
35-
getDeviceCapsProc = gdi32.MustFindProc("GetDeviceCaps")
36-
getJobProc = winspool.MustFindProc("GetJobW")
37-
openPrinterProc = winspool.MustFindProc("OpenPrinterW")
38-
resetDCProc = gdi32.MustFindProc("ResetDCW")
39-
rtlGetVersionProc = ntoskrnl.MustFindProc("RtlGetVersion")
40-
setGraphicsModeProc = gdi32.MustFindProc("SetGraphicsMode")
41-
setJobProc = winspool.MustFindProc("SetJobW")
42-
setWorldTransformProc = gdi32.MustFindProc("SetWorldTransform")
43-
startDocProc = gdi32.MustFindProc("StartDocW")
44-
startPageProc = gdi32.MustFindProc("StartPage")
27+
user32 = syscall.MustLoadDLL("user32.dll")
28+
29+
abortDocProc = gdi32.MustFindProc("AbortDoc")
30+
closePrinterProc = winspool.MustFindProc("ClosePrinter")
31+
createDCProc = gdi32.MustFindProc("CreateDCW")
32+
deleteDCProc = gdi32.MustFindProc("DeleteDC")
33+
deviceCapabilitiesProc = winspool.MustFindProc("DeviceCapabilitiesW")
34+
documentPropertiesProc = winspool.MustFindProc("DocumentPropertiesW")
35+
endDocProc = gdi32.MustFindProc("EndDoc")
36+
endPageProc = gdi32.MustFindProc("EndPage")
37+
enumPrintersProc = winspool.MustFindProc("EnumPrintersW")
38+
getDeviceCapsProc = gdi32.MustFindProc("GetDeviceCaps")
39+
getJobProc = winspool.MustFindProc("GetJobW")
40+
openPrinterProc = winspool.MustFindProc("OpenPrinterW")
41+
resetDCProc = gdi32.MustFindProc("ResetDCW")
42+
rtlGetVersionProc = ntoskrnl.MustFindProc("RtlGetVersion")
43+
setGraphicsModeProc = gdi32.MustFindProc("SetGraphicsMode")
44+
setJobProc = winspool.MustFindProc("SetJobW")
45+
setWorldTransformProc = gdi32.MustFindProc("SetWorldTransform")
46+
startDocProc = gdi32.MustFindProc("StartDocW")
47+
startPageProc = gdi32.MustFindProc("StartPage")
48+
registerDeviceNotificationProc = user32.MustFindProc("RegisterDeviceNotificationW")
4549
)
4650

4751
// System error codes.
@@ -798,8 +802,8 @@ func (hPrinter HANDLE) SetJobUserName(jobID int32, userName string) error {
798802
return err
799803
}
800804

801-
ji1.pUserName = pUserName;
802-
ji1.position = 0; // To prevent a possible access denied error (0 is JOB_POSITION_UNSPECIFIED)
805+
ji1.pUserName = pUserName
806+
ji1.position = 0 // To prevent a possible access denied error (0 is JOB_POSITION_UNSPECIFIED)
803807
err = hPrinter.SetJobInfo1(jobID, ji1)
804808
if err != nil {
805809
return err
@@ -1196,3 +1200,49 @@ func GetWindowsVersion() string {
11961200

11971201
return version
11981202
}
1203+
1204+
type GUID struct {
1205+
Data1 uint32
1206+
Data2 uint16
1207+
Data3 uint16
1208+
Data4 [8]byte
1209+
}
1210+
1211+
var PRINTERS_DEVICE_CLASS = GUID{
1212+
0x4d36e979,
1213+
0xe325,
1214+
0x11ce,
1215+
[8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18},
1216+
}
1217+
1218+
type DevBroadcastDevinterface struct {
1219+
dwSize uint32
1220+
dwDeviceType uint32
1221+
dwReserved uint32
1222+
classGuid GUID
1223+
szName uint16
1224+
}
1225+
1226+
const (
1227+
DEVICE_NOTIFY_SERVICE_HANDLE = 1
1228+
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4
1229+
1230+
DBT_DEVTYP_DEVICEINTERFACE = 5
1231+
)
1232+
1233+
func RegisterDeviceNotification(handle windows.Handle) error {
1234+
1235+
var notificationFilter DevBroadcastDevinterface
1236+
notificationFilter.dwSize = uint32(unsafe.Sizeof(notificationFilter))
1237+
notificationFilter.dwDeviceType = DBT_DEVTYP_DEVICEINTERFACE
1238+
notificationFilter.dwReserved = 0
1239+
// BUG(pastarmovj): This class is ignored for now. Figure out what the right GUID is.
1240+
notificationFilter.classGuid = PRINTERS_DEVICE_CLASS
1241+
notificationFilter.szName = 0
1242+
1243+
r1, _, err := registerDeviceNotificationProc.Call(uintptr(handle), uintptr(unsafe.Pointer(&notificationFilter)), DEVICE_NOTIFY_SERVICE_HANDLE|DEVICE_NOTIFY_ALL_INTERFACE_CLASSES)
1244+
if r1 == 0 {
1245+
return err
1246+
}
1247+
return nil
1248+
}

winspool/winspool.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/google/cloud-print-connector/cdd"
2020
"github.com/google/cloud-print-connector/lib"
21+
"golang.org/x/sys/windows"
2122
)
2223

2324
// winspoolPDS represents capabilities that WinSpool always provides.
@@ -918,6 +919,11 @@ func (ws *WinSpool) ReleaseJob(printerName string, jobID uint32) error {
918919
return nil
919920
}
920921

922+
func (ws *WinSpool) StartPrinterNotifications(handle windows.Handle) error {
923+
err := RegisterDeviceNotification(handle)
924+
return err
925+
}
926+
921927
// The following functions are not relevant to Windows printing, but are required by the NativePrintSystem interface.
922928

923929
func (ws *WinSpool) RemoveCachedPPD(printerName string) {}

0 commit comments

Comments
 (0)