10

I have successfully managed to trigger a callback when a print job is initially requested on the local machine during spooling. However is there anyway with win32print or something similar that may allow me to handle the event in which a print job is transferred to a print server or USB printer?

################################################################################
# Imports ######################################################################
################################################################################

from os.path import *
from printer import *
from watcher import *
from statvar import *

################################################################################
# Event Callback ###############################################################
################################################################################

def callback(code, event):

    num = splitext(event)[0]
    ext = splitext(event)[1]

    if code == 1 and ext == '.SPL':
        main(num.lstrip('0'))

################################################################################
# wx Event Handler #############################################################
################################################################################

def handling(*args):

    wx.CallAfter(callback, *args)

################################################################################
# Create Listener ##############################################################
################################################################################

# listens to the spool directory for files

watch = Watcher(SPOOL_DIRECTORY, handling)

# set the appropriate flags for a listener

watch.flags = FILE_NOTIFY_CHANGE_FILE_NAME

################################################################################
# Start Listener ###############################################################
################################################################################

watch.start()

################################################################################
# Start wx App #################################################################
################################################################################

app = wx.App()
wx.Frame(None)
app.MainLoop()

################################################################################
################################################################################
################################################################################
Malik Brahimi
  • 16,341
  • 7
  • 39
  • 70
  • https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.printing.printmanager.printtaskrequested.aspx seems like the callback you want for "initially requested" – Dima Tisnek Jan 04 '16 at 10:57
  • All these APIs are mappable to Python IIRC; definitely anything accessible over ActiveX and COM is. It's just a matter of specifying the argument and return types correctly :) – Dima Tisnek Jan 04 '16 at 11:07
  • Perhaps this API is more relevant though: https://msdn.microsoft.com/en-us/library/windows/desktop/dd162723(v=vs.85).aspx – Dima Tisnek Jan 04 '16 at 11:09
  • Here's how it's used: https://github.com/RavuAlHemio/StopPrintJobs/blob/45bf114e4679db474eb24c5df8ab557b3d468037/SpoolerAccessPI/Spooler.cs you see pyprint/pyprinting/win32print only expose a subset of Api's. But you can get [almost] any API if you do the hard work. Personally I doubt it's worth it, way too much work, win APIs are both tricky and change over time -- you may have to recode in 2 years, and some cheaper printers take shortcuts too, no guarantee your code works with customer's printer – Dima Tisnek Jan 04 '16 at 11:18
  • 1
    @qarma Yeah, I've seen many jump to `ctypes` because Windows is a pain in the rear. – Malik Brahimi Jan 04 '16 at 11:24
  • It seems you are pretty proficient with the library. Can you add the code you use for the local printer case, so that we can see what you want for the print server case (which I assume is the same functionality, but when sending the job to a server / USB printer)? – J Richard Snape Jan 04 '16 at 13:46
  • @JRichardSnape Like I said, I'm using a file system watcher with some third party package I found online. There's some `wxPython` in there and the `main` function simply handles the job given the id of the spool file. – Malik Brahimi Jan 04 '16 at 21:44
  • OK - got it now. It wasn't clear that the way you were triggering your callback was by watching for file change in the spool directory using `watcher`. – J Richard Snape Jan 05 '16 at 13:26
  • @JRichardSnape Yeah exactly. At the moment, I am incorrectly checking the spool directory instead of responding to events on the print server. Do you have any ideas as to how I could correct that? – Malik Brahimi Jan 06 '16 at 01:35
  • Step 1 is for you to work out what the Win32 mechanism is for doing what you need. Do that first. Forget about Python for now. Once you know how to do this in Win32, then think about accessing that from your Python code. – David Heffernan Jan 06 '16 at 17:02
  • @DavidHeffernan Yes, I understand what I need to do conceptually. While programming, however, I have found no such technique with any third party packages. With Python at least. I know people can do it in C, but that's beside the point. – Malik Brahimi Jan 06 '16 at 23:03
  • If you know how to do it in C, against Win32 then it's going to be a piece of cake to do it in Python with interop. – David Heffernan Jan 06 '16 at 23:07
  • @DavidHeffernan Problem is I don't know C, forget hardware programming. I've implemented the entirety of my printer project for school in Python but I'm hitting a roadblock here. I put 500 reputation on the line for this and no one seems to have an idea. – Malik Brahimi Jan 06 '16 at 23:10
  • Tell me which C api you know that does the job, please. – David Heffernan Jan 06 '16 at 23:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99993/discussion-between-malik-brahimi-and-david-heffernan). – Malik Brahimi Jan 06 '16 at 23:30
  • @DavidHeffernan I know that I'm going to need `ReplyPrinterChangeNotification` but I can't get it working in `ctypes` – Malik Brahimi Feb 05 '16 at 17:37

1 Answers1

3

Here's an idea that worked on my computer (Windows 8). It's hardly fully fledged code, but it might get you going. You need to utilise the functions FindFirstPrinterChangeNotification and FindNextPrinterChangeNotification These are contained within winspool.drv on the client side (irritatingly you can find them documented as being in spoolSS.dll but this is server side - this diagram can clarify).

The list of events that can be listened for (and, importantly, their flag settings) are available from MSDN here. Initially I thought you wanted PRINTER_CHANGE_ADD_JOB (0x00000100), but I think you may actually want PRINTER_CHANGE_WRITE_JOB (0x00000800). This doesn't trigger as soon as the job starts spooling, but unfortunately it does seem to be triggered multiple times in the example where you send one document to a network printer.

Unfortunately, these APIs aren't exposed in the win32print library. I think, therefore you have to dive into ctypes. Here I haven't registered a callback as such, rather I listen for the notification and when triggered I call the function and start listening again in an infinite loop. The process is stalled while listening. If you need conventional callback functionality, you could either run this script in its own thread, or maybe this answer will not suit your needs.

Note - this simply listens for the print job being requested and then calls a function. If you want to extract information about the job being triggered, the code will become horrendous. Further note - it will trigger for a print job that is started and subsequently cancelled, but I imagine that's fine.

from ctypes import *
from ctypes.wintypes import HANDLE, LPSTR

def add_job_callback():
    print('A job has just been sent to the printer this script is monitoring')

spl = windll.LoadLibrary('winspool.drv')

printer_name = 'KONICA MINOLTA PS Color Laser Class Driver'
# Put the name of your printer here - can be networked or any installed on your computer.  Alternatively, set it to None to use the local printer server
#printer_name = None

hPrinter = HANDLE()

if printer_name:
    spl.OpenPrinterA(c_char_p(printer_name), byref(hPrinter),None)
else:
    spl.OpenPrinterA(None, byref(hPrinter),None)

print(hPrinter)


hjob = spl.FindFirstPrinterChangeNotification(hPrinter,0x00000100,0, None)
# 0x00000100 is a flags setting to set watch for only PRINTER_CHANGE_ADD_JOB
while True:
    windll.kernel32.WaitForSingleObject(hjob,-1)
    #When this function returns, the change that you're monitoring for has been observed, trigger the function
    add_job_callback()
    spl.FindNextPrinterChangeNotification(hjob, None, None, None)

Note there are some small differences between Python 2.7 and Python 3 here - e.g. the initialisation of the c_char_p ctype from a string. I've presented the simplest version I could here - it works in 2.7.


Postscript

I did all the heavy lifting and then found this answer, that is something of a duplicate. It has rather nicer code that handles unicode printer names and the like, but only looks at the default local print server.

Community
  • 1
  • 1
J Richard Snape
  • 20,116
  • 5
  • 51
  • 79
  • Yeah, I found that other answer too, but I hardly understood it, nor does it handle the right event I believe. – Malik Brahimi Jan 07 '16 at 11:14
  • I don't know but is `PRINTER_CHANGE_ADD_JOB` the correct event I described? – Malik Brahimi Jan 07 '16 at 11:16
  • And is there a way to get the id of the print job after waiting for it? – Malik Brahimi Jan 07 '16 at 11:17
  • OK - one by one - You believe this is not the right event. Fair enough - maybe it is not. is PRINTER_CHANGE_ADD_JOB the correct event I described? Re-reading your question.... maybe ... maybe not. I'm not sure. You use the word "transferred" to the printer. What do you mean by that? Added to the spool queue for that printer, or actually sent irretrievably? This event is the former, do you want the latter? Is there a way to get the id of the print job after waiting for it? As I say - that is more involved but you said what the callback did was irrelevant. – J Richard Snape Jan 07 '16 at 11:25
  • I've never done this, but you could listen for all events on the printer and then check `pdwChange` from FindNextPrinterChangeNotification see [docs here](https://msdn.microsoft.com/en-us/library/windows/desktop/dd162723%28v=vs.85%29.aspx) to see the type of event. Maybe you really want `PRINTER_CHANGE_WRITE_JOB` the list of available events in those docs. To get info on the job, you'd need to use the [`PRINTER_NOTIFY_OPTIONS`](https://msdn.microsoft.com/en-us/library/windows/desktop/dd162855%28v=vs.85%29.aspx), I think - which I think would be much harder. – J Richard Snape Jan 07 '16 at 11:31
  • I have upvoted because I commend your efforts to provide a satisfactory answer but the truth is I have already implemented this with a file system watcher. Instead of handling the local spooling event when a print job is initially created, I want to handle the event when the printer receives that print job. – Malik Brahimi Jan 08 '16 at 00:26
  • Sure - the edit I added (WRITE_JOB) is a different event to it being added to the spool directory, but ADD_JOB will be effectively the same. I'm not sure whether you can get a message from the server on the client end to say "I've received the job" - that seems to be what you want? It's possible that [ReplyPrinterChangeNotification](https://msdn.microsoft.com/en-us/library/windows/hardware/ff561959%28v=vs.85%29.aspx) might provide that functionality, but I'm not familiar with it, so would have to do some research there... – J Richard Snape Jan 08 '16 at 14:20
  • Yes that's exactly what I need the printer to do. – Malik Brahimi Jan 09 '16 at 01:29
  • If am going to grant you the bounty as you are the only one who has made an attempt to help me, which I really appreciate. But if possible, could you look into that other API and edit your answer? – Malik Brahimi Jan 09 '16 at 19:20
  • @MalikBrahimi Sure - I'll do what I can to investigate if the other API is of use and can be used in your case. I'd recommend not awarding the bounty until the last minute (although I very much appreciate your sentiment) Someone might come along who just knows the answer off the top of their head and can really help you out. You get 24 hours after it expires to award it iiuc. – J Richard Snape Jan 09 '16 at 23:27
  • Hmm - I can't get much further on this. I had a look at the strings in `winspool.drv` and discovered [`RegisterForPrintAsyncNotifications`](https://msdn.microsoft.com/en-us/library/windows/desktop/dd162919%28v=vs.85%29.aspx) which I think is what you need. I can't translate the [examples](http://www.getcodesamples.com/src/6B9B2EFE) I found in c++ into Python ctypes right now. I'll keep plugging away at it... – J Richard Snape Jan 11 '16 at 13:23
  • Thank you so much. I really appreciate the help but do not sacrifice your own time for my project, especially while you are working. – Malik Brahimi Jan 11 '16 at 15:38
  • No - I thought I had the RegisterForPrintAsyncNotification working, but it returns a null handle for the event registration handler. I'm missing something in how to use that API, but I think it's the one you want. – J Richard Snape Jan 13 '16 at 15:46
  • Well, if you do manage to reach somewhere with that API please let me know as soon as possible. Thanks again – Malik Brahimi Jan 18 '16 at 17:43
  • Hi - I'm sorry - I've tried a few more times and I still can't get the callback to register. If you're willing to change languages - there is an example [here](http://www.getcodesamples.com/src/6B9B2EFE) - see particularly `client.cpp`, but I guess that's a bridge too far. – J Richard Snape Jan 20 '16 at 10:06
  • Do you know of anyone else with any Windows expertise that I may contact? Or someone who is fairly knowledgeable with hardware programming? Then again, I could always use direct help from Microsoft, but that seems far fetched ... – Malik Brahimi Jan 20 '16 at 10:30
  • I can't even find the right DLL to open with the reply API functions. – Malik Brahimi Jan 31 '16 at 00:03
  • Hi, sorry I haven't been able to help any further. The `RegisterForPrintAsyncNotifications` is in `winspool.drv`. I can't get it working, but when I'm on my other machine I'll post the code as faras I got on a pastebin or something - maybe you can fix it up. – J Richard Snape Jan 31 '16 at 08:06
  • `ReplyPrinterChangeNotification` doesn't seem to exist in `winspool.drv` at all. Any ideas? – Malik Brahimi Feb 05 '16 at 17:35
  • @malikbrahimi This makes me think that I can't help at all. It is in that file on my system (Windows 8). Perhaps it is a recent additon to the Windows API and you are on an older version - in which case it will be no use to you. You could get a definitive list of all strings in the binary (including function names) by going to a command prompt and typing `strings winspool.drv`. If it's not there, it's not available. With this and the fact I can't get the callback working even on my install - I'm really sorry but I don't think I can help any further. – J Richard Snape Feb 06 '16 at 16:35
  • It's just a matter of accessing it with `ctypes` that is the problem. Thanks for trying though. – Malik Brahimi Feb 06 '16 at 16:38