En este artículo te voy a enseñar a monitorear la cola de impresión de una impresora de Windows para saber cuando un trabajo ha sido agregado, básicamente vas a escuchar el evento de cuando alguien envía un trabajo de impresión a una impresora.
Esto va a servir para obtener una notificación cada vez que un documento es enviado para ser impreso en cualquier impresora, y a partir de ahí poder realizar otras acciones, sin importar el lugar desde donde el trabajo de impresión fue enviado.
Vamos a usar las APIs de Windows, específicamente OpenPrinter
, FindFirstPrinterChangeNotification
y FindNextPrinterChangeNotification
usando C++, pero me imagino que esto que haremos puede hacerse desde cualquier lenguaje de programación como C#, Python, etcétera.
Abrir impresora
Comencemos viendo el modo de obtener un handler para la impresora, para ello invocamos a la función OpenPrinter. Toma en cuenta que en este caso el nombre de la impresora se está leyendo desde la línea de comandos.
char nombreDeImpresora[MAX_PATH];
std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
std::cin.getline(nombreDeImpresora, MAX_PATH);
HANDLE manejadorDeImpresora = NULL;
PRINTER_DEFAULTS pd;
ZeroMemory(&pd, sizeof(pd));
pd.DesiredAccess = PRINTER_ACCESS_USE;
if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
{
std::cerr << "Error al abrir la impresora." << std::endl;
return 1;
}
Debido a que solo vamos a revisar la cola de impresión desde C++, solo necesitamos el permiso PRINTER_ACCESS_USE
, si quieres puedes revisar la documentación oficial para revisar otro tipo de derechos.
Obtener identificador de objeto para monitoreo
Una vez que tenemos el handle de la impresora podemos invocar a FindFirstPrinterChangeNotification, aquí pasamos una bandera indicando las condiciones bajo las cuales queremos ser notificados.
En este caso solo se necesita saber cuando alguien envía un trabajo de impresión a una impresora, así que con PRINTER_CHANGE_ADD_JOB
basta.
HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);
if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
{
std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
return 1;
}
std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";
Es importante mencionar que FindFirstPrinterChangeNotification
puede devolver INVALID_HANDLE_VALUE
en caso de no poder obtener el acceso al manejador, esto puede ocurrir por permisos o cosas similares, por ello es que debemos manejar esa opción.
Monitorear cola de impresión
Llegamos al punto más importante: saber cuando alguien envía un trabajo de impresión a una impresora en Windows.
Para ello encerramos el comportamiento en un ciclo infinito y esperamos con WaitForSingleObject para esperar la señal, que en otras palabras será la notificación de que se ha agregado un trabajo de impresión a la cola.
while (true)
{
if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
{
std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;
if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
{
std::cerr << "Error al esperar mas notificaciones" << std::endl;
break;
}
}
else
{
std::cerr << "Error al esperar la notificacion" << std::endl;
break;
}
}
La línea más importante está dentro del if
, que es en donde imprimimos que se ha agregado un trabajo a la cola de impresión. Aquí puedes hacer más cosas además de imprimir, pues puedes estar seguro de que alguien envió una impresión a la impresora previamente indicada.
Finalmente basta con cerrar los manejadores y objetos:
if (identificadorDeObjetoParaMonitoreo != NULL)
{
FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
}
if (manejadorDeImpresora != NULL)
{
ClosePrinter(manejadorDeImpresora);
}
Código completo
Ya he explicado cómo ser notificado cuando alguien envía una impresión a una impresora, ahora veremos el código completo. Recuerda que estamos trabajando con la Windows API así que debes incluir el encabezado windows.h
.
El código completo queda así:
#include <windows.h>
#include <iostream>
int main()
{
char nombreDeImpresora[MAX_PATH];
std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
std::cin.getline(nombreDeImpresora, MAX_PATH);
HANDLE manejadorDeImpresora = NULL;
PRINTER_DEFAULTS pd;
ZeroMemory(&pd, sizeof(pd));
pd.DesiredAccess = PRINTER_ACCESS_USE;
if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
{
std::cerr << "Error al abrir la impresora." << std::endl;
return 1;
}
HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);
if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
{
std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
return 1;
}
std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";
while (true)
{
if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
{
std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;
if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
{
std::cerr << "Error al esperar mas notificaciones" << std::endl;
break;
}
}
else
{
std::cerr << "Error al esperar la notificacion" << std::endl;
break;
}
}
if (identificadorDeObjetoParaMonitoreo != NULL)
{
FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
}
if (manejadorDeImpresora != NULL)
{
ClosePrinter(manejadorDeImpresora);
}
return 0;
}
Personalmente lo he compilado con g++ así: g++ monitorear.cpp -lwinspool -o monitorear.exe
Me parece que también se puede compilar con Visual Studio, y también recuerdo que puedes invocar a estas API desde otros lenguajes de programación.
El código es sencillo, pero me ha llevado un tiempo el investigar, hacer pruebas y colocar todo en una prueba de concepto.