To be notified of new print jobs, you can use FindFirstPrinterChangeNotification
, FindNextPrinterChangeNotification
, and a wait function from kernel32 such as WaitForSingleObject
.
Here's an example to set a filter that waits for a new print job on the local print server. There's much more work to be done if you want the JOB_NOTIFY_FIELD_DOCUMENT
value out of a PRINTER_NOTIFY_INFO_DATA
structure.
Edit:
Malik Brahimi requested information about obtaining the print job ID, so I've decided to flesh this out as a more complete example. The new code adds a wait_for_print_job_info
function that waits for notifications specified using the PRINTER_NOTIFY_OPTIONS
structure. Windows returns the data in an array of the above-mentioned PRINTER_NOTIFY_INFO_DATA
structures, each of which has the print job Id
for the given data.
The data has to be freed by calling FreePrinterNotifyInfo
, which I handle automatically in the __del__
finalizer of the PPRINTER_NOTIFY_INFO_GC
class.
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32.dll', use_last_error=True)
winspool = ctypes.WinDLL('winspool.drv', use_last_error=True)
# define LPHANDLE, PDWORD, and PWORD for Python 2
if not hasattr(wintypes, 'LPHANDLE'):
setattr(wintypes, 'LPHANDLE', ctypes.POINTER(wintypes.HANDLE))
if not hasattr(wintypes, 'PDWORD'):
setattr(wintypes, 'PDWORD', ctypes.POINTER(wintypes.DWORD))
if not hasattr(wintypes, 'PWORD'):
setattr(wintypes, 'PWORD', ctypes.POINTER(wintypes.WORD))
INFINITE = -1
WAIT_OBJECT_0 = 0x00000000
WAIT_TIMEOUT = 0x00000102
WAIT_FAILED = 0xFFFFFFFF
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
PRINTER_CHANGE_ADD_PRINTER = 0x00000001
PRINTER_CHANGE_SET_PRINTER = 0x00000002
PRINTER_CHANGE_DELETE_PRINTER = 0x00000004
PRINTER_CHANGE_FAILED_CONNECTION_PRINTER = 0x00000008
PRINTER_CHANGE_PRINTER = 0x000000FF
PRINTER_CHANGE_ADD_JOB = 0x00000100
PRINTER_CHANGE_SET_JOB = 0x00000200
PRINTER_CHANGE_DELETE_JOB = 0x00000400
PRINTER_CHANGE_WRITE_JOB = 0x00000800
PRINTER_CHANGE_JOB = 0x0000FF00
PRINTER_CHANGE_ADD_FORM = 0x00010000
PRINTER_CHANGE_SET_FORM = 0x00020000
PRINTER_CHANGE_DELETE_FORM = 0x00040000
PRINTER_CHANGE_FORM = 0x00070000
PRINTER_CHANGE_ADD_PORT = 0x00100000
PRINTER_CHANGE_CONFIGURE_PORT = 0x00200000
PRINTER_CHANGE_DELETE_PORT = 0x00400000
PRINTER_CHANGE_PORT = 0x00700000
PRINTER_CHANGE_ADD_PRINT_PROCESSOR = 0x01000000
PRINTER_CHANGE_DELETE_PRINT_PROCESSOR = 0x04000000
PRINTER_CHANGE_PRINT_PROCESSOR = 0x07000000
PRINTER_CHANGE_SERVER = 0x08000000 # NT 6.1+
PRINTER_CHANGE_ADD_PRINTER_DRIVER = 0x10000000
PRINTER_CHANGE_SET_PRINTER_DRIVER = 0x20000000
PRINTER_CHANGE_DELETE_PRINTER_DRIVER = 0x40000000
PRINTER_CHANGE_PRINTER_DRIVER = 0x70000000
PRINTER_CHANGE_ALL = 0x7F77FFFF
PRINTER_CHANGE_TIMEOUT = 0x80000000
PRINTER_NOTIFY_CATEGORY_ALL = 0x00
PRINTER_NOTIFY_CATEGORY_3D = 0x01
PRINTER_NOTIFY_TYPE = 0x00
JOB_NOTIFY_TYPE = 0x01
PRINTER_NOTIFY_FIELD_SERVER_NAME = 0x00 # not supported
PRINTER_NOTIFY_FIELD_PRINTER_NAME = 0x01
PRINTER_NOTIFY_FIELD_SHARE_NAME = 0x02
PRINTER_NOTIFY_FIELD_PORT_NAME = 0x03
PRINTER_NOTIFY_FIELD_DRIVER_NAME = 0x04
PRINTER_NOTIFY_FIELD_COMMENT = 0x05
PRINTER_NOTIFY_FIELD_LOCATION = 0x06
PRINTER_NOTIFY_FIELD_DEVMODE = 0x07
PRINTER_NOTIFY_FIELD_SEPFILE = 0x08
PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR = 0x09
PRINTER_NOTIFY_FIELD_PARAMETERS = 0x0A
PRINTER_NOTIFY_FIELD_DATATYPE = 0x0B
PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR = 0x0C
PRINTER_NOTIFY_FIELD_ATTRIBUTES = 0x0D
PRINTER_NOTIFY_FIELD_PRIORITY = 0x0E
PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY = 0x0F
PRINTER_NOTIFY_FIELD_START_TIME = 0x10
PRINTER_NOTIFY_FIELD_UNTIL_TIME = 0x11
PRINTER_NOTIFY_FIELD_STATUS = 0x12
PRINTER_NOTIFY_FIELD_STATUS_STRING = 0x13 # not supported
PRINTER_NOTIFY_FIELD_CJOBS = 0x14
PRINTER_NOTIFY_FIELD_AVERAGE_PPM = 0x15
PRINTER_NOTIFY_FIELD_TOTAL_PAGES = 0x16 # not supported
PRINTER_NOTIFY_FIELD_PAGES_PRINTED = 0x17 # not supported
PRINTER_NOTIFY_FIELD_TOTAL_BYTES = 0x18 # not supported
PRINTER_NOTIFY_FIELD_BYTES_PRINTED = 0x19 # not supported
PRINTER_NOTIFY_FIELD_OBJECT_GUID = 0x1A
PRINTER_NOTIFY_FIELD_FRIENDLY_NAME = 0x1B # NT 6.0+
PRINTER_NOTIFY_FIELD_BRANCH_OFFICE_PRINTING = 0x1C # NT 6.2+
JOB_NOTIFY_FIELD_PRINTER_NAME = 0x00
JOB_NOTIFY_FIELD_MACHINE_NAME = 0x01
JOB_NOTIFY_FIELD_PORT_NAME = 0x02
JOB_NOTIFY_FIELD_USER_NAME = 0x03
JOB_NOTIFY_FIELD_NOTIFY_NAME = 0x04
JOB_NOTIFY_FIELD_DATATYPE = 0x05
JOB_NOTIFY_FIELD_PRINT_PROCESSOR = 0x06
JOB_NOTIFY_FIELD_PARAMETERS = 0x07
JOB_NOTIFY_FIELD_DRIVER_NAME = 0x08
JOB_NOTIFY_FIELD_DEVMODE = 0x09
JOB_NOTIFY_FIELD_STATUS = 0x0A
JOB_NOTIFY_FIELD_STATUS_STRING = 0x0B
JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR = 0x0C # not supported
JOB_NOTIFY_FIELD_DOCUMENT = 0x0D
JOB_NOTIFY_FIELD_PRIORITY = 0x0E
JOB_NOTIFY_FIELD_POSITION = 0x0F
JOB_NOTIFY_FIELD_SUBMITTED = 0x10
JOB_NOTIFY_FIELD_START_TIME = 0x11
JOB_NOTIFY_FIELD_UNTIL_TIME = 0x12
JOB_NOTIFY_FIELD_TIME = 0x13
JOB_NOTIFY_FIELD_TOTAL_PAGES = 0x14
JOB_NOTIFY_FIELD_PAGES_PRINTED = 0x15
JOB_NOTIFY_FIELD_TOTAL_BYTES = 0x16
JOB_NOTIFY_FIELD_BYTES_PRINTED = 0x17
JOB_NOTIFY_FIELD_REMOTE_JOB_ID = 0x18
PRINTER_NOTIFY_OPTIONS_REFRESH = 0x01
PRINTER_NOTIFY_INFO_DISCARDED = 0x01
JOB_STATUS_PAUSED = 0x00000001
JOB_STATUS_ERROR = 0x00000002
JOB_STATUS_DELETING = 0x00000004
JOB_STATUS_SPOOLING = 0x00000008
JOB_STATUS_PRINTING = 0x00000010
JOB_STATUS_OFFLINE = 0x00000020
JOB_STATUS_PAPEROUT = 0x00000040
JOB_STATUS_PRINTED = 0x00000080
JOB_STATUS_DELETED = 0x00000100
JOB_STATUS_BLOCKED_DEVQ = 0x00000200
JOB_STATUS_USER_INTERVENTION = 0x00000400
JOB_STATUS_RESTART = 0x00000800
JOB_STATUS_COMPLETE = 0x00001000
JOB_STATUS_RETAINED = 0x00002000
JOB_STATUS_RENDERING_LOCALLY = 0x00004000
JOB_STATUS_ALL = 0x00007FFF
JOB_STATUS_STRING = {
JOB_STATUS_PAUSED: 'PAUSED',
JOB_STATUS_ERROR: 'ERROR',
JOB_STATUS_DELETING: 'DELETING',
JOB_STATUS_SPOOLING: 'SPOOLING',
JOB_STATUS_PRINTING: 'PRINTING',
JOB_STATUS_OFFLINE: 'OFFLINE',
JOB_STATUS_PAPEROUT: 'PAPEROUT',
JOB_STATUS_PRINTED: 'PRINTED',
JOB_STATUS_DELETED: 'DELETED',
JOB_STATUS_BLOCKED_DEVQ: 'BLOCKED_DEVQ',
JOB_STATUS_USER_INTERVENTION: 'USER_INTERVENTION',
JOB_STATUS_RESTART: 'RESTART',
JOB_STATUS_COMPLETE: 'COMPLETE',
JOB_STATUS_RETAINED: 'RETAINED',
JOB_STATUS_RENDERING_LOCALLY: 'RENDERING_LOCALLY'}
class SYSTEMTIME(ctypes.Structure):
_fields_ = (('wYear', wintypes.WORD),
('wMonth', wintypes.WORD),
('wDayOfWeek', wintypes.WORD),
('wDay', wintypes.WORD),
('wHour', wintypes.WORD),
('wMinute', wintypes.WORD),
('wSecond', wintypes.WORD),
('wMilliseconds', wintypes.WORD))
@property
def as_datetime(self):
from datetime import datetime
return datetime(self.wYear, self.wMonth, self.wDay,
self.wHour, self.wMinute, self.wSecond,
self.wMilliseconds * 1000)
class PRINTER_NOTIFY_OPTIONS_TYPE(ctypes.Structure):
_fields_ = (('Type', wintypes.WORD),
('Reserved0', wintypes.WORD),
('Reserved1', wintypes.DWORD),
('Reserved2', wintypes.DWORD),
('Count', wintypes.DWORD),
('_pFields', wintypes.PWORD))
def __init__(self, Type=JOB_NOTIFY_TYPE, pFields=None):
super(PRINTER_NOTIFY_OPTIONS_TYPE, self).__init__(Type)
if pFields is not None:
self.pFields = pFields
@property
def pFields(self):
ptr_t = ctypes.POINTER(wintypes.WORD * self.Count)
return ptr_t(self._pFields.contents)[0]
@pFields.setter
def pFields(self, pFields):
self.Count = len(pFields)
self._pFields = pFields
PPRINTER_NOTIFY_OPTIONS_TYPE = ctypes.POINTER(PRINTER_NOTIFY_OPTIONS_TYPE)
class PRINTER_NOTIFY_OPTIONS(ctypes.Structure):
_fields_ = (('Version', wintypes.DWORD),
('Flags', wintypes.DWORD),
('Count', wintypes.DWORD),
('_pTypes', PPRINTER_NOTIFY_OPTIONS_TYPE))
def __init__(self, Flags=0, pTypes=None):
super(PRINTER_NOTIFY_OPTIONS, self).__init__(2, Flags)
if pTypes is not None:
self.pTypes = pTypes
@property
def pTypes(self):
ptr_t = ctypes.POINTER(PRINTER_NOTIFY_OPTIONS_TYPE * self.Count)
return ptr_t(self._pTypes.contents)[0]
@pTypes.setter
def pTypes(self, types):
if isinstance(types, PRINTER_NOTIFY_OPTIONS_TYPE):
self.Count = 1
self._pTypes = ctypes.pointer(types)
else:
self.Count = len(types)
self._pTypes = types
PPRINTER_NOTIFY_OPTIONS = ctypes.POINTER(PRINTER_NOTIFY_OPTIONS)
class PRINTER_NOTIFY_INFO_DATA(ctypes.Structure):
class _NOTIFY_DATA(ctypes.Union):
class _DATA(ctypes.Structure):
_fields_ = (('cbBuf', wintypes.DWORD),
('pBuf', wintypes.LPVOID))
_fields_ = (('adwData', wintypes.DWORD * 2),
('Data', _DATA))
_fields_ = (('Type', wintypes.WORD),
('Field', wintypes.WORD),
('Reserved', wintypes.DWORD),
('Id', wintypes.DWORD),
('_NotifyData', _NOTIFY_DATA))
@property
def _data_as_string(self):
if self._NotifyData.Data.pBuf:
return ctypes.c_wchar_p(self._NotifyData.Data.pBuf).value
return u""
@property
def _data_as_datetime(self):
if self._NotifyData.Data.pBuf:
t = SYSTEMTIME.from_address(self._NotifyData.Data.pBuf)
else:
t = SYSTEMTIME()
return t.as_datetime
@property
def NotifyData(self):
if self.Type == JOB_NOTIFY_TYPE:
if self.Field == JOB_NOTIFY_FIELD_PRINTER_NAME:
return 'job_printer_name', self._data_as_string
if self.Field == JOB_NOTIFY_FIELD_MACHINE_NAME:
return 'job_machine_name', self._data_as_string
if self.Field == JOB_NOTIFY_FIELD_USER_NAME:
return 'job_user_name', self._data_as_string
elif self.Field == JOB_NOTIFY