delphiwinapiprint-spooler-api

Receive notifications when the printer status changes


I would like to develop an application that displays a message when the printer prints.

I created a small test application for the console, but for some reason I don't get any notification of a change in the printer state. It should be noted that when I checked the job status in this way (with JOB_NOTIFY_TYPE and JOB_NOTIFY_FIELD_STATUS), notifications about the job status were indeed received.

Does the printer not send notifications? (And is it related to the printer type and/or driver?)

Here is the code to test:

program PrinterMonitor;

{$APPTYPE CONSOLE}

uses
  Winapi.Windows,
  Winapi.WinSpool;

const
  PrinterField: WORD = PRINTER_NOTIFY_FIELD_STATUS;

var
  PrinterName: string;
  hStopEvent: THandle;
  hPrinter: THandle;
  hPrintNotify: THandle;
  pno: TPrinterNotifyOptions;
  pnot: TPrinterNotifyOptionsType;
  pni: Pointer;
  dwChange: DWORD;
  WaitHandles: array[0..1] of THandle;

function CtrlHandler(fdwCtrlType: DWORD): BOOL; stdcall;
begin
  Result := False;
  case fdwCtrlType of
    CTRL_C_EVENT, CTRL_CLOSE_EVENT:
    begin
      SetEvent(hStopEvent);
      Result := True;
    end;
  end;
end;

procedure GetPrinterNotifyData(const Info: TPrinterNotifyInfo);
var
  I: Integer;
begin
  for I := 0 to Info.Count - 1 do
  begin
    with Info.aData[I] do
    begin
      case wType of
        PRINTER_NOTIFY_TYPE:
        begin
          case Field of
            PRINTER_NOTIFY_FIELD_STATUS: WriteLn('Printer status: ', NotifyData.adwData[0]);
          end;
        end;
      end;
    end;
  end;
end;

begin
  if ParamCount < 1 then
  begin
    WriteLn('No printer name specified.');
    Exit;
  end;
  PrinterName := ParamStr(1); //pass a printer name as argument

  OpenPrinter(PWideChar(PrinterName), hPrinter, nil);
  if hPrinter = 0 then
  begin
    WriteLn('Cannot open printer "', PrinterName, '". ErrorCode: ', GetLastError);
    Exit;
  end;

  pnot.wType := PRINTER_NOTIFY_TYPE;
  pnot.Count := 1;
  pnot.pFields := @PrinterField;

  pno.Version := 2;
  pno.Count := 1;
  pno.pTypes := @pnot;

  hStopEvent := CreateEvent(nil, True, False, nil);
  if hStopEvent = 0 then
  begin
    WriteLn('Cannot create stop event. ErrorCode: ', GetLastError);
    ClosePrinter(hPrinter);
    Exit;
  end;

  hPrintNotify := FindFirstPrinterChangeNotification(hPrinter, 0, 0, @pno);
  if hPrintNotify = 0 then
  begin
    WriteLn('Cannot start monitoring. ErrorCode: ', GetLastError);
    ClosePrinter(hPrinter);
    CloseHandle(hStopEvent);
    Exit;
  end;

  WaitHandles[0] := hStopEvent;
  WaitHandles[1] := hPrintNotify;

  SetConsoleCtrlHandler(@CtrlHandler, True); // handling CTRL+C & close console events for stop monitoring

  WriteLn('==== Monitor started ====');
  while True do
  begin
    case WaitForMultipleObjects(2, @WaitHandles, False, INFINITE) of
      WAIT_OBJECT_0: // hStopEvent signalled
      begin
        WriteLn('==== Monitor stopped ====');
        Break;
      end;
      WAIT_OBJECT_0+1: // hPrintNotify signalled
      begin
        if not FindNextPrinterChangeNotification(hPrintNotify, dwChange, nil, pni) then
        begin
          WriteLn('Cannot get notify changes data.');
          Break;
        end;
        GetPrinterNotifyData(PPrinterNotifyInfo(pni)^);
        FreePrinterNotifyInfo(PPrinterNotifyInfo(pni));
      end;
      else
      begin
        WriteLn('Wait failed');
        Break;
      end;
    end;
  end;
  FindClosePrinterChangeNotification(hPrintNotify);
  ClosePrinter(hPrinter);
  CloseHandle(hStopEvent);
end.

Solution

  • When I run your code and then pause and resume the print queue, it works fine:

    ==== Monitor started ====
    Printer status: 131073
    Printer status: 131072
    

    The value 131073 = 0x20001, which is comprised of the enums:

    PRINTER_STATUS_TONER_LOW | PRINTER_STATUS_PAUSED