Patent Publication Number: US-2003231329-A1

Title: Method of print-time printer selection

Description:
CROSS REFERENCE TO RELATED APPLICATIONS  
     [0001] This application is related to co-pending U.S. application Ser. No. 10/082,980 filed Feb. 25, 2002, “System for Installing a Printer Driver on a Network” and U.S. application Ser. No. 10/083,209 filed Feb. 25, 2002, “System for Installing a Printer Driver on a Network”, the contents of which are incorporated herein by reference.  
     [0002] This application is related to applicants&#39; co-pending, co-assigned U.S. applications entitled Multiple Printer Driver (Docket No. D/A2200) and Mobile USB Printer Driver (Docket No. D/A2202), which are filed concurrently with this application the contents of which are incorporated herein by reference. 
    
    
     
       FIELD OF THE INVENTION  
       [0003] This invention relates generally to network printers and more particularly to a system for enabling a user to physically select a printer when printing a document.  
       BACKGROUND OF THE INVENTION  
       [0004] Mobile professionals have difficulty printing documents while away from known territories. A common problem lies in the lack of compatible printer drivers for the multitude of different printers they may have access to on the road. Even if the drivers are available, the mobile professional must install the drivers for all the possible printers and maintain a large list of print queues on the professional&#39;s personal computer (PC). This is cumbersome at best, and unnecessarily clutters up the user&#39;s printers folder and complicates printer selection during printing.  
       [0005] Another common problem mobile professionals have is finding a connection to a desired printer. If the printer is on a network, the professional must have network connectivity and know how to connect to a desired printer on the network. The mobile professional may be able to see the printer in the next room, but finding that printer&#39;s address on the network is often difficult. If the mobile professional cannot connect to a network or there is no network available, the user must find a printer with a parallel port or a USB port connection and connect directly.  
       [0006] Connecting to a network printer on the user&#39;s laptop is not always simple. Even when the user has successfully obtained network access, the user must install a printer driver and configure it to print over the network to the desired printer. This requires knowledge of the printer&#39;s IP Address or DNS name, which is not always understood by the user. Finding this information is equally challenging on many printers, often requiring the user to navigate complicated menu trees or printing confusing configuration pages.  
       SUMMARY OF THE INVENTION  
       [0007] A system for print-time printer selection, according to one aspect of the invention, includes a printer driver installed on a host device such as a personal computer or laptop computer. The printer driver has the capability to control a plurality of different printer types and also to listen for a printer&#39;s identification. The system also includes a beacon, located at the printer which, responsive to a user&#39;s activation of the beacon at the printer, broadcasts the printer&#39;s identification on the network. When the printer driver receives the user-selected printer identification identifying the printer as one of the plurality of printer types supported by the printer driver, the printer driver sends the user&#39;s print job to the identified printer. The beacon is located at the printer and may be implemented as software stored in the printer&#39;s firmware, such as the network code region of the firmware. The beacon broadcasts information on the network using whatever protocol is used by the network it is connected to, such as TCP/IP.  
       [0008] The system of the invention provides print-time printer selection by enabling a user to physically select the printer of choice when the user selects the print function. The user installs a printer driver that is capable of controlling several different printer types and to respond to a printer identification signal. Once installed on the user&#39;s host device, when the printer driver is selected as the default printer, the driver prompts the user to physically select the desired printer. The user walks over to the desired printer, and activates the printer&#39;s beacon. The printer&#39;s beacon broadcasts an identification signal on the network. The printer driver listens for the printer&#39;s identification signal. When it is received, the printer driver sends the user&#39;s print job to the identified printer. The document spools and prints to the printer the user picked. Optionally, information about that printer may be cached on the user&#39;s computer for future use.  
       [0009] A system for print-time printer selection removes the ambiguity of multiple printers on a network by allowing the user to make a physical connection to the desired printer. When a user decides to print a document from the user&#39;s host device (e.g., PC or laptop), the user is instructed to go to the printer the user wants to print from and activate the printer&#39;s beacon from the printer&#39;s front panel. The beacon, responsive to an input (such as a user pressing a button at the printer&#39;s control panel), broadcasts the printer&#39;s identification information. This identification information may include the printer&#39;s model name and distinguishing network information. When the printer driver receives the information, the user&#39;s print job is directed to that printer.  
       [0010] A USB printer driver, according to another aspect of the invention, includes a generic driver for generating a print job in a page description language for each of a plurality of different printer types; a detector for detecting any USB enabled printers connected to a USB port on a host device; means, responsive to detection of a USB enabled printer connected to the USB port on the host device, for retrieving the detected USB enabled printer&#39;s device name; means, responsive to the printer&#39;s device name, for generating a print path between the host device and the detected USB enabled printer; and a spooler for sending the print job to the detected USB enabled printer using the print path.  
       [0011] The USB printer driver combines a generic driver interface and automated USB printing device detection. The USB printer driver allows a user to automatically print to a USB connected printer at print time. A user can install the USB printer driver on a laptop. When a user travels, the user need only connect to a printer with a USB connection. Once connected, the user need only select a document to print. When the print window opens, the user selects the USB printer. This driver then automatically scans the USB port for USB enabled printers. When a USB enabled printer is found, the USB printer driver sends the print job to that printer without user intervention or subsequent driver installation.  
     
    
    
     BRIEF DESCRIPTION OF THE DRAWINGS  
     [0012]FIG. 1 is a block diagram of a system employing a method of print-time printer selection;  
     [0013]FIG. 2 is a block diagram illustrating operation of a smart USB printer driver; and  
     [0014]FIG. 3 is a drop down menu illustrating various printers installed on a particular application. 
    
    
     DETAILED DESCRIPTION OF THE EMBODIMENTS  
     [0015] Referring to FIG. 1, a system for print-time printer selection includes a printer driver installed on a host device  10 , which is shown as a personal computer connected to network  100 . Printers  20 ,  22 ,  24 ,  26  and  28  are also connected to network  100 . The printer driver is capable of controlling, in this example, all of the printers shown in FIG. 1. In order to control each printer  20 ,  22 ,  24 ,  26  and  28 , the printer driver must be capable of translating a print job from an application program into the appropriate page description language (PDL) for any supported printer. Each printer  20 ,  22 ,  24 ,  26  and  28  includes a menu or button or other user-accessible means to select that printer to print at that particular time.  
     [0016] The printer driver may be a standalone printer driver, which is installed like any other printer driver, with the distinction that this printer driver has additional functionality. Alternatively, the printer driver may be in the form of a plug-in to an existing application program. The printer driver must be capable of generating printer-independent PDL (such as PostScript) that will successfully print to any supported printer. The list of supported printers is not necessarily small, and could encompass a large variety of devices. The printer driver may support any number of printer features and page sizes, depending on the implementation and marketing goals of the end product. The printer driver may also include a port monitor, and a language monitor that, as part of the system, are capable of interrupting a print job in-progress, allowing the user to specify to which printer to print their document. The printer driver may optionally be capable of submitting a user password with the print job such that security is maintained when desired across the system of printers and PCs.  
     [0017] Each printer in this system must include some means by which a user may select the printer for “mobile” or “beacon” or print-time printing. The system of the invention is beneficial for mobile users as well as fixed users. This selection mechanism, or beacon, may include a menu item, a button, a switch or such other means available to the user on the printer. The printer in this system, responsive to user activation of the beacon, communicates with the user&#39;s PC through a network broadcast message such that the PC may become aware of the user&#39;s selection. The user&#39;s PC and printer may communicate directly if they are connected via a parallel port or serial port. The printer in this system may optionally require successful submission of the document&#39;s password (as provided through the printer driver) prior to printing the document.  
     [0018] The system of print-time printer selection may be used when a user opens a document on the user&#39;s PC. The user selects the print function from the application program used to open the document. Under printer properties, the user selects the “beacon” printer. The beacon printer dialog box opens and prompts the user to make a physical selection of a printer. The user walks to the desired printer (in this case printer  28 ) and selects the printer through some interface resident on that printer (such as pushing a print button).  
     [0019] Printer  28  includes beacon technology which broadcasts certain identification information on the network in response to appropriate selections on the printer&#39;s panel menu. When the user follows the instructions provided, this feature is activated, and the printer  28  will attempt to communicate with the user&#39;s PC  10 . In this case, the user  50  wants to print her document to printer  28 . User  50  highlights the “Printer Identification” selection on the printer  28 &#39;s menu and presses “OK” and highlight the “Select for Print” and presses “OK”. These steps activate the beacon on printer  28 .  
     [0020] When the beacon is activated, the beacon (which may be implemented in printer  28 &#39;s firmware) then broadcasts a communication packet containing whatever predetermined information has been stored for broadcasting. This communication packet contains a special packet of data that the PC  10  is listening for. This packet contains enough distinguishing information such that the host machine knows which printer sent it. The listening PC  10  receives this packet and then the printer driver uses this information to determine if printer  28  is supported. The data packet broadcast by the beacon may contain the IP address of the printer. Once the packet is received, the user&#39;s PC  10  may contact the printer  28  directly over the network using the network protocol, such as TCP/IP and sending the user&#39;s print job. A specific printer was chosen at this point by the user, removing any ambiguity there may have been in the list of possible printers to work with.  
     [0021] Printer  28  communicates via a broadcast message to all PCs on the printer&#39;s network. If the user&#39;s PC  10  is on the same network, the user&#39;s PC  10  replies to the printer&#39;s broadcast message with the print job information. Printer  28  may optionally broadcast a message prompting the user to enter password (if the print job is password enabled). User  50  enters password which is communicated to printer  28 . If the password is correct, printer  28  notifies the user&#39;s PC  10  to submit the print job. The printer driver sends the print job to the printer  28 . When the print job is completed, the printer  28  sends a message that the print job is complete to the user&#39;s PC  10 . The user retrieves the completed print job at printer  28 .  
     [0022] The following exemplary code checks to see if the selected printer is a beacon printer, i.e., one in which the user must activate the printer by physically walking to the selected printer, pushing a button on the printer. When the user pushes the button on the printer, the printer broadcasts its URL to the multiple printer driver.  
                                                  Example: localmon.cpp                 #include “precomp.h”       #pragma hdrstop       #include “Imon.h”       #include “..\common\general.h”       #include “dialogs.h”       #include &lt;tchar.h&gt;       #include “..\..\opb_spooler\common\opb_spooler_jobticket.h”       #include “..\..\opb_spooler\common\opb_spooler_launch.h”       HANDLE LcmhMonitor;       HANDLE LcmhInst;       CRITICAL_SECTION LcmSpoolerSection;       DWORD LocalmonDebug;       DWORD LcmPortInfo1Strings[]={FIELD_OFFSET(PORT_INFO_1, pName),                         (DWORD)−1};                 DWORD LcmPortInfo2Strings[]={FIELD_OFFSET(PORT_INFO_2, pPortName),                         FIELD_OFFSET(PORT_INFO_2, pMonitorName),           FIELD_OFFSET(PORT_INFO_2, pDescription),           (DWORD)-1};                 WCHAR szPorts[] = L“ports”;       WCHAR szPortsEx[] = L“portsex”;/* Extra ports values */       WCHAR szFILE[]  = L“FILE:”;       WCHAR szLcmCOM[]  = L“COM”;       WCHAR szLcmLPT[]  = L“LPT”;       TCHAR szJobDispName[MAX_PATH];       BOOL bNewStart = TRUE;       extern WCHAR szWindows[];       extern WCHAR szINIKey_TransmissionRetryTimeout[];       BOOL       LocalMonInit(HANDLE hModule)       {                         LcmhInst = hModule;           InitializeCriticalSection(&amp;LcmSpoolerSection);           return TRUE;                 }       /************************************************************************       ** LcmEnumPorts       ** Purpose: To enumerate all available ports for us to use       /************************************************************************/       BOOL       LcmEnumPorts(                         HANDLE hMonitor,           LPWSTR pName,           DWORD Level,           LPBYTE pPorts,           DWORD cbBuf,           LPD WORD pcbNeeded,           LPD WORD pcReturned           )                 {                         PINILOCALMON pIniLocalMon = (PINILOCALMON)hMonitor;           PINIPORT pIniPort;           DWORD cb;           LPBYTE pEnd;           DWORD LastError=0;                         OutputDebugString(_T(“LcmEnumPorts\n”));                         LcmEnterSplSem();           cb=0;           pIniPort = pIniLocalMon−&gt;pIniPort;           //CheckAndAddIrdaPort(pIniLocalMon);           while (pIniPort) {                         if ( !(pIniPort−&gt;Status &amp; PP_FILEPORT)) {                         cb+=GetPortSize(pIniPort, Level);                         }           pIniPort=pIniPort−&gt;pNext;                         }           *pcbNeeded=cb;           if (cb &lt;= cbBuD) {                         pEnd=pPorts+cbBuf;           *pcReturned=0;           pIniPort = pIniLocalMon−&gt;pIniPort;           while (pIniPort) {                         if (!(pIniPort−&gt;Status &amp; PP_FILEPORT)) {                         pEnd = CopyIniPortToPort(pIniPort, Level, pPorts, pEnd);           if( !pEnd ){                         LastError = GetLastError();           break;                         }           switch (Level) {           case 1:                         pPorts+=sizeof(PORT_INFO_1);           break;                         case 2:                         pPorts+=sizeof(PORT_INFO_2);           break;                         default:                         DBGMSG(DBG_ERROR,                         (“EnumPorts: invalid level %d”, Level));                         LastError = ERROR_INVALID_LEVEL;           goto Cleanup;                         }           (*pcReturned)++;                         }           pIniPort=pIniPort−&gt;pNext;                         }                         } else                         LastError = ERROR_INSUFFICIENT_BUFFER;                 Cleanup:                         LcmLeaveSplSem();           if (LastError) {                         SetLastError(LastError);           return FALSE;                         } else                     /*   LcmLeaveSplSem();                         (*pcReturned)=0;                 */                         return TRUE;                 }       BOOL       LcmxEnumPorts(                         LPWSTR pName,           DWORD Level,           LPBYTE pPorts,           DWORD cbBuf,           LPDWORD pcbNeeded,           LPDWORD pcReturned           )                 {                         OutputDebugString(_T(“LcmxEnumPorts\n”));                         return LcmEnumPorts(LcmhMonitor, pName, Level, pPorts, cbBuf, pcbNeeded,                 pcReturned);       }       /************************************************************************       ** LcmOpenPort       ** Purpose: To open the port that we need to use       ************************************************************************/       BOOL       LcmOpenPort(                         HANDLE hMonitor,           LPWSTR pName,           PHANDLE pHandle           )                 {                         PINILOCALMON pIniLocalMon = (PINILOCALMON)hMonitor;                             PINIPORT   pIniPort;                             BOOL   bRet = TRUE;                         LcmEnterSplSem();                         //get the port pointer                         pIniPort = FindPort(pIniLocalMon, pName);           if ( !pIniPort )                         bRet = FALSE;                         if ( bRet )                         *pHandle = pIniPort;                         LcmLeaveSplSem();           return bRet;                 }       BOOL       LcmxOpenPort(                         LPWSTR pName,           PHANDLE pHandle           )                 {                         OutputDebugString(_T(“LcmxOpenPort\n”));                         return LcmOpenPort(LcmhMonitor, pName, pHandle);                 }       /***********************************************************************       ** LcmStartDocPort       ** Purpose: This is where much of the interesting things take place. We need to determine                     **   if we are the walk-up printer driver, and if so, launch the applicable dialog.       **   Also, we need to create the appropriate spool file and job ticket.                 ***********************************************************************/       BOOL       LcmStartDocPort(                         HANDLE hPort,           LPWSTR pPrinterName,           DWORD JobId,           DWORD Level,           LPBYTE pDocInfo)                 {                             PINIPORT   pIniPort = (PINIPORT)hPort;           HANDLE   hToken;                         PDOC_INFO_1 pDocInfo1 = (PDOC_INFO_1)pDocInfo;           DWORD Error = 0;                         OutputDebugString(_T(“LcmStartDocPort\n”));                         DBGMSG(DBG_TRACE, (“StartDocPort(%08x, %ws, %d, %d, %08x)\n”,                         hPort, pPrinterName, JobId, Level, pDocInfo));                         if (pIniPort−&gt;Status &amp; PP_STARTDOC) {                         return TRUE;                         }           LcmEnterSplSem();           pIniPort−&gt;Status |= PP_STARTDOC;           LcmLeaveSplSem();           pIniPort−&gt;hPrinter = NULL;           pIniPort−&gt;pPrinterName = AllocSplStr(pPrinterName);           if (pIniPort−&gt;pPrinterName) {                         if (OpenPrinter(pPrinterName, &amp;pIniPort−&gt;hPrinter, NULL)) {                 //try to get the job name (cast it to a doc_info_1 structure, even though it might be       //doc_info_2_tcscpy(szJobDispName, ((DOC_INFO_1 *)pDocInfo)−&gt;pDocName);                         pIniPort−&gt;JobId = JobId;                         //create some file paths           //get the base path           TCHAR szBasePath[64];           TCHAR szJobPath[MAX_PATH];           //Get the Base path from the registry           if(!_SpoolerJobTicketClass::GetSpoolPath(szBasePath,                 MAX_PATH))                         _tcscpy(szBasePath,_T(“c:\\temp\\”));                         //the job will be called &lt;base_path&gt;\opb_spool&lt;job_id&gt;.prn           _stprintf(szJobPath,_T(“%sopb_spool_%d.prn”),szBasePath,JobId);           //right here, we need to know if we are the Walk-up Printer Driver.           //Only this driver will prompt the user at print //time for an IP           //address.           if (IsWalkupPrinter(pIniPort−&gt;hPrinter)) {                         //we know we need to launch our dialog           TCHAR szIPAddress[MAX_PATH];           TCHAR szPrinterModel[MAX_PATH];           _SpoolerJobTicketClass::PORT_TYPE PortType =                 _SpoolerJobTicketClass::_IP;                         //memset (szIPAddress, 0, sizeof(szIPAddress));           HWND hParent = GetForeground Window();           //see if they have printed recently           int rc = IDNO;                 //this is a way to always drop into the next line if we don&#39;t call this                         //update our timer value based on user&#39;s settings           global_timer_value = GetCountdownTimerValue();           //check for USB           if (IsUSBPrinterConnected (szPrinterModel, MAX_PATH,                 szIPAddress, MAX_PATH)) {                         rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE(DLG_NOTIFICATION), hParent, (int (_stdcall       *)(void))&amp;PrintNoticeProc, (LPARAM)szPrinterModel );                         if (rc == IDOK)                         PortType = _SpoolerJobTicketClass::_USB;                         }           else if (PrintToLast(pIniPort−&gt;hPrinter)) {                         GetLastCachedIP(pIniPort−&gt;hPrinter, szIPAddress);           rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE(DLG_NOTIFICATION), hParent, (int (_stdcall       *)(void))&amp;PrintNoticeProc, (LPARAM)szIPAddress );                         }                 //this will be called either if PrintToLast is false, or if the user wishes to choose another       printer                         if (rc == IDNO) {                 //we let them choose       //be kind of tricky, and pass the Printer name through this IP address variable                         _tcscpy(szIPAddress,pPrinterName);           rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE( DLG_WALKUP), hParent, (int (_stdcall       *)(void))&amp;WalkupProc (LPARAM)szIPAddress );                         }                 //this will be the value if the user either lets the print to last dialog run it&#39;s course, or       chooses a specific printer themselves                         if (rc == IDOK) {                 //we go ahead and print...       //the user has successfully selected something       //MessageBox(hParent, szIPAddress, NULL, MB_OK);                         TCHAR szJobTicketPath[MAX_PATH];                 //the ticket will be called &lt;base_path&gt;\opb_spool_&lt;job_id&gt;.tkt                         _stprintf(szJobTicketPath,_T(“%sopb_spool_%d.tkt”),szBasePath,JobId);           _SpoolerJobTicketClass NewJobTicket;                 //copy the job name       _tcscpy(NewJobTicket.pJobTicket−&gt;szJobName, ((DOC_INFO_1 *)pDocInfo)−       &gt;pDocName);       //we only support 1 printer for walk-up                         NewJobTicket.pJobTicket−&gt;dwTotalAvailablePrinters = 1;                 //initialize the printer (only one of them for walk-up)       NewJobTicket.pJobTicket−&gt;printerInfo[0].enumPortType=PortType;       _tcscpy(NewJobTicket.pJobTicket−&gt;printerInfo[0].szPortVal, szIPAddress);       //now, write it out                         LaunchOPBSpooler();           NewJobTicket.WriteJobTicketToDisk(szJobTicketPath);           _SpoolerJobTicketClass test;           test.ReadJobTicketFromDisk(szJobTicketPath);                 }       //this will be the case if the user cancels either the print to last dialog, or the choose printer       dialog                         else if (rc == IDCANCEL) {           //we cancel the job                         DWORD retVal = SetJob(pIniPort−&gt;hPrinter, JobId, 0, NULL,                 JOB_CONTROL_SENT_TO_PRINTER);       retVal = SetJob(pIniPort−&gt;hPrinter, JobId, 0, NULL, JOB_CONTROL_DELETE);                         }                         }                 // For non dosdevices CreateFile on the name of the port       //                         pIniPort−&gt;hFile = CreateFile(szJobPath,//pIniPort−&gt;pName,                         GENERIC_WRITE,           FILE_SHARE_READ,           NULL,           OPEN_ALWAYS,           FILE_ATTRIBUTE_NORMAL |           FILE_FLAG_SEQUENTIAL_SCAN,           NULL);                         if ( pIniPort−&gt;hFile != INVALID_HANDLE_VALUE)                         SetEndOfFile(pIniPort−&gt;hFile);                         }                         } // end of if (pIniPort−&gt;pPrinterName)           if (pIniPort−&gt;hFile == INVALID_HANDLE_VALUE)                         goto Fail;                         bNewStart = TRUE;                         return TRUE;                 Fail:                         SPLASSERT(pIniPort−&gt;hFile == INVALID_HANDLE_VALUE);           LcmEnterSplSem();           pIniPort−&gt;Status &amp;= ˜PP_STARTDOC;           LcmLeaveSplSem();           if (pIniPort−&gt;hPrinter) {                         ClosePrinter(pIniPort−&gt;hPrinter);                         }           if (pIniPort−&gt;pPrinterName) {                         FreeSplStr(pIniPort−&gt;pPrinterName);                         }           if (Error)                         SetLastError(Error);                         return FALSE;                 }       BOOL       LcmWritePort(                         HANDLE hPort,           LPBYTE pBuffer,           DWORD cbBuf,           LPDWORD pcb Written)                 {                         PINIPORT pIniPort = (PINIPORT)hPort;           BOOL rc;                         OutputDebugString(_T(“LcmWritePort\n”));                         DBGMSG(DBG_TRACE, (“WritePort(%08x, %08x, %d)\n”, hPort, pBuffer, cbBuf));           if ( !pIniPort−&gt;hFile ∥ pIniPort−&gt;hFile == INVALID_HANDLE_VALUE) {                         SetLastError(ERROR_IN_VALID_HANDLE);           return FALSE;                         } else {                 //only do this at the beginning, and only if it&#39;s not a walk-up printer                         if (bNewStart &amp;&amp; !IsWalkupPrinter(pIniPort−&gt;hPrinter)) {                         char szIPAddress[64], szIPAddress2[64];           memset(szIPAddress,0,64);                 //tricky code to get the IP Address and backup IP. If we don&#39;t do this, there&#39;s no NULL       //for our string manipulation routines, and they fail!       //first, grab the last byte                         BYTE lastOne = pBuffer[cbBuf−1];                 //then, we have to save the last byte of the buffer to NULL                         pBuffer[cbBuf−1]= 0; //null it off                 //now, do our searching       //search for the IP Address                         char *ptr = strstr((char *)pBuffer,“MPDIP=”);                         if (ptr) {                         int i = 0;           ptr+=strlen(“MPDIP=”);           while (*ptr != &#39;\r&#39;){                         szIPAddress[i++] = *ptr++;                         }                         }           ptr = strstr((char *)pBuffer,“MPDIP2=”);           if(ptr) {                         int i = 0;           ptr+= strlen(“MPDIP2=”);           while (*ptr != &#39;\r&#39;){                         szIPAddress2[i++] = *ptr++;                         }                         }                 //now, write out our job ticket       //the user has successfully selected something       //MessageBox(hParent, szIPAddress, NULL, MB_OK);                         TCHAR szJobTicketPath[MAX_PATH];           //create some file paths           //get the base path           TCHAR szBasePatht[64];           //Get the Base path from the registry           if(!_SpoolerJobTicketClass::GetSpoolPath(szBasePath MAX_PATH))                         _tcscpy(szBasePath,_T(“c:\\temp\\”));                 //the ticket will be called &lt;base_path&gt;\opb_spool&lt;job_id&gt;.tkt                         _stpfintf(szJobTicketPath,                 _T(“%sopb_spool_%d.tkt”),szBasePath,pIniPort−&gt;JobId);       //how many printers                         if(szIPAddress[0] !=0){                         _SpoolerJobTicketClass NewJobTicket;                 //copy the job name                         _tcscpy(NewJobTicket.pJobTicket−&gt;szJobName, szJobDispName);                         NewJobTicket.pJobTicket−&gt;dwTotalAvailablePrinters = 1;           NewJobTicket.pJobTicket-                 &gt;printerInfo[0].enumPortType=_SpoolerJobTicketClass::_IP                         TCHAR szBuffer[64];           MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,                 szIPAddress, −1, szBuffer, 64);                         _tcscpy(NewJobTicket.pJobTicket-                 &gt;printerInfo[0].szPortVal, szBuffer);                         if (szIPAddress2[0] !=0){                         NewJobTicket.pJobTicket-                 &gt;dwTotalAvailablePrinters = 2;                         NewJobTicket.pJobTicket                 &gt;printerInfo[1].enumPortType=_SpoolerJobTicketClass::_IP;                         MultiByteToWideChar(CP_ACP,                 MB_PRECOMPOSED, szIPAddress2, −1, szBuffer, 64);                         _tcscpy(NewJobTicket.pJobTicket-                 &gt;printerInfo[1].szPortVal, szBuffer);                         }                 //now, write it out       #ifdef_DEBUG                         DWORD needed = 0;           DWORD retVal = GetPrinterData(pIniPort−&gt;hPrinter,                 _T(“DontSpool”),NULL,NULL,NULL,&amp;needed);                         if (retVal == ERROR_SUCCESS) {                         HWND hWindow = GetForegroundWindow();           MessageBox(hWindow,szBuffer,_T(“Spooling                 to...”),MB_OK|MB_SETFOREGROUND|MB_APPLMODAL|MB_TOPMOST);                         }           else {                         LaunchOPBSpooler();                         NewJobTicket.WriteJobTicketToDisk(szJobTicketPath);                         }                 #else                         LaunchOPBSpooler();           NewJobTicket.WriteJobTicketToDisk(szJobTicketPath);                 #endif//_DEBUG                         }           else {                 //we have to cancel the job       DWORD retVal = SetJob(pIniPort−&gt;hPrinter, pIniPort−&gt;JobId, 0, NULL,       JOB_CONTROL_SENT_TO_PRINTER);       retVal = SetJob(pIniPort−&gt;hPrinter, pIniPort−&gt;JobId, 0, NULL,       JOB_CONTROL_DELETE);                         }                 //now, put it back                         pBuffer[cbBuf−1]= lastOne;           bNewStart = FALSE;                         }                         rc = WriteFile(pIniPort−&gt;hFile, pBuffer, cbBuf, pcbWritten, NULL);           if ( rc &amp;&amp; *pcbWritten == 0) {                         SetLastError(ERROR_TIMEOUT);           rc = FALSE;                         }                         }           DBGMSG(DBG_TRACE, (“WritePort returns %d; %d bytes written\n”, rc,                 *pcb Written));                         return rc;                 }       BOOL       LcmReadPort(                         HANDLE hPort,           LPBYTE pBuffer,           DWORD cbBuf,           LPDWORD pcbRead)                 {                         PINIPORT  pIniPort = (PNIPORT)hPort;           BOOL rc;                         OutputDebugString(_T(“LcmReadPort\n”));                         DBGMSG(DBG_TRACE, (“ReadPort(%08x, %08x, %d)\n”, hPort, pBuffer, cbBuf));                             if ( !pIniPort−&gt;hFile   ∥                             pIniPort−&gt;hFile == INVALID_HANDLE_VALUE   ∥                         !(pIniPort−&gt;Status &amp; PP_COMM_PORT)) {           SetLastError(ERROR_INVALID_HANDLE);           return FALSE;                         }           rc = ReadFile(pIniPort−&gt;hFile, pBuffer, cbBuf, pcbRead, NULL);           DBGMSG(DBG_TRACE, (“ReadPort returns %d; %d bytes read\n”, rc, *pcbRead));           return rc;                 }       BOOL       LcmEndDocPort(                         HANDLE hPort           )                 {                         PINIPORT  pIniPort = (PINIPORT)hPort;                         OutputDebugString(_T(“LcmEndDocPort\n”));                         DBGMSG(DBG_TRACE, (“EndDocPort(%08x)\n”, hPort));           if (!(pIniPort−&gt;Status &amp; PP_STARTDOC)) {                         return TRUE;                         }                 // The flush here is done to make sure any cached IO&#39;s get written       // before the handle is closed. This is particularly a problem       // for Intelligent buffered serial devices                         FlushFileBuffers(pIniPort−&gt;hFile);           // For any ports other than real LPT ports we open during StartDocPort           // and close it during EndDocPort           if ( !(pIniPort−&gt;Status &amp; PP_COMM_PORT) ∥ IS_COM_PORT(pIniPort−&gt;pName)) {                     //   if ( IS_RDA_PORT(pIniPort−&gt;pName)) {                     //   IrdaEndDocPort(pIniPort);                     //   } else {                         CloseHandle(pIniPort−&gt;hFile);           pIniPort−&gt;hFile = INVALID_HANDLE_VALUE;           if ( pIniPort−&gt;Status &amp; PP_DOSDEVPORT) {                         (VOID)RemoveDosDeviceDefinition(pIniPort);                         }           if ( IS_COM_PORT(pIniPort−&gt;pName)) {                         pIniPort−&gt;Status &amp;= ˜(PP_COMM_PORT|PP_DOSDEVPORT);           FreeSplStr(pIniPort−&gt;pDeviceName);           pIniPort−&gt;pDeviceName = NULL;                         }                     //   }                         }           SetJob(pIniPort−&gt;hPrinter, pIniPort−&gt;JobId, 0, NULL,                 JOB_CONTROL_SENT_TO_PRINTER);                         ClosePrinter(pIniPort−&gt;hPrinter);           FreeSplStr(pIniPort−&gt;pPrinterName);                 // Startdoc no longer active.       pIniPort−&gt;Status &amp;= ˜PP_STARTDOC;                         return TRUE;                 }       BOOL       LcmClosePort(                         HANDLE hPort           )                 {                         PINIPORT pIniPort = (PINIPORT)hPort;                         OutputDebugString(_T(“LcmClosePort\n”));                         FreeSplStr(pIniPort−&gt;pDeviceName);           pIniPort−&gt;pDeviceName = NULL;           if (pIniPort−&gt;Status &amp; PP_FILEPORT) {                         LcmEnterSplSem();           DeletePortNode(pIniPort−&gt;pIniLocalMon, pIniPort);           LcmLeaveSplSem();                         } else if ( pIniPort−&gt;Status &amp; PP_COMM_PORT ) {                         (VOID) RemoveDosDeviceDefinition(pIniPort);           if ( pIniPort−&gt;hFile != INVALID_HANDLE_VALUE) {                         CloseHandle(pIniPort−&gt;hFile);           pIniPort−&gt;hFile = INVALID_HANDLE_VALUE;                         }           pIniPort−&gt;Status &amp;= ˜(PP_COMM_PORT|PP_DOSDEVPORT);                         }           return TRUE;                 }       BOOL       LcmAddPortEx(                         HANDLE hMonitor,           LPWSTR pName,           DWORD Level,           LPBYTE pBuffer,           LPWSTR pMonitorName                 )       {                         PINILOCALMON pIniLocalMon = (PINILOCALMON)hMonitor;           LPWSTR pPortName;           DWORD Error;           LPPORT_INFO_1 pPortInfo1;           LPPORT_INFO_FF pPortInfoFF;                         OutputDebugString(_T(“LcmAddPortEx\n”));                 switch (Level) {       case (DWORD)−1:                         pPortInfoFF = (LPPORT_INFO_FF)pBuffer;           pPortName = pPortInfoFF−&gt;pName;           break;                         case 1:                         pPortInfo1 = (LPPORT_INFO_1)pBuffer;           pPortName = pPortInfo1−&gt;pName;           break;                         default:                         SetLastError(ERROR_INVALID_LEVEL);           return(FALSE);                         }           if (!pPortName) {                         SetLastError(ERROR_INVALID_PARAMETER);           return(FALSE);                         }           if (PortExists(pName, pPortName, &amp;Error)) {                         SetLastError(ERROR_INVALID_PARAMETER);           return(FALSE);                         }           if (Error != NO_ERROR) {                         SetLastError(Error);           return(FALSE);                         }           if (!LcmCreatePortEntry(pIniLocalMon, pPortName)) {                         return(FALSE);                         }           if (!WriteProfileString(szPorts, pPortName, L“”)) {                         LcmDeletePortEntry( pIniLocalMon, pPortName );           return(FALSE);                         }           return TRUE;                 }       BOOL       LcmxAddPortEx(                         LPWSTR pName,           DWORD Level,           LPBYTE pBuffer,           LPWSTR pMonitorName                 )       {                         OutputDebugString(_T(“LcmxAddPortEx\n”));                         return LcmAddPortEx(LcmhMonitor, pName, Level, pBuffer, pMonitorName);                 }       BOOL       LcmGetPrinterDataFromPort(                         HANDLE hPort,           DWORD ControlID,           LPWSTR pValueName,           LPWSTR lpInBuffer,           DWORD cbInBuffer,           LPWSTR lpOutBuffer,           DWORD cbOutBuffer,           IPDWORD lpcbReturned)                 {                         PINIPORT  pIniPort = (PINIPORT)hPort;           BOOL  rc;                         OutputDebugString(_T(“LcmGetPrinterDataFromPort\n”));                         DBGMSG(DBG_TRACE,                         (“GetPrinterDataFromPort(%08x, %d, %ws, %ws, %d, ”,           hPort, ControlID, pValueName, lpInBuffer, cbInBuffer));                             if ( !ControlID   ∥                             !pIniPort−&gt;hFile   ∥                             pIniPort−&gt;hFile == INVALID_HANDLE_VALUE   ∥           !(pIniPort−&gt;Status &amp; PP_DOSDEVPORT)) {           SetLastError(ERROR_INVALLD_PARAMETER);           return FALSE;                         }           rc = DeviceIoControl(pIniPort−&gt;hFile,                         ControlID,           lpInBuffer,           cbInBuffer,           lpOutBuffer,           cbOutBuffer,           lpcbReturned,           NULL);                         DBGMSG(DBG_TRACE,                         (“%ws, %d, %d)\n”, lpOutBuffer, cbOutBuffer, lpcbReturned));                         return rc;                 }       BOOL       LcmSetPortTimeOuts(                         HANDLE hPort,           LPCOMMTIMEOUTS lpCTO,           DWORD reserved)  // must be set to 0                 {                             PINIPORT   pIniPort = (PINIPORT)hPort;                             COMMTIMEOUTS   cto;                         OutputDebugString(_T(“LcmSetPortTimeOuts\n”));                         if (reserved !=0)                         return FALSE;                         if ( !(pIniPort−&gt;Status &amp; PP_DOSDEVPORT)) {                         SetLastError(ERROR_INVALID_PARAMETER);           return FALSE;                         }           if ( GetCommTimeouts(pIniPort−&gt;hFile, &amp;cto))           {                         cto.ReadTotalTimeoutConstant = lpCTO−&gt;ReadTotalTimeoutConstant;           cto.ReadIntervalTimeout = lpCTO−&gt;ReadIntervalTimeout;           return SetCommTimeouts(pIniPort−&gt;hFile, &amp;cto);                         }           return FALSE;                 }       VOID       LcmShutdown(                         HANDLE hMonitor           )                 {                         PINIPORT pIniPort;           PINIPORT pIniPortNext;           PINILOCALMON pIniLocalMon = (PINILOCALMON)hMonitor;                         OutputDebugString(_T(“LcmShutdown\n”));                         //           // Delete the ports, then delete the LOCALMONITOR.           //           for( pIniPort = pIniLocalMon−&gt;pIniPort; pIniPort; pIniPort = pIniPortNext ){                         pIniPortNext = pIniPort−&gt;pNext;           FreeSplMem( pIniPort );                         }           FreeSplMem( pIniLocalMon );                 }       BOOL       LcmxXcvOpenPort(                         LPCWSTR pszObject,           ACCESS_MASK GrantedAccess,           PHANDLE phXcv           )                 {                         OutputDebugString(_T(“LcmxXcvOpenPort\n”));                         return LcmXcvOpenPort(LcmhMonitor, pszObject, GrantedAccess, phXcv);                 }       MONITOR2 Monitor2 = (                         sizeof(MONITOR2),           LcmEnumPorts,           LcmOpenPort,                             NULL,   // OpenPortEx is not supported                         LcmStartDocPort,           LcmWritePort,           LcmReadPort,           LcmEndDocPort,           LcmClosePort,                             NULL,   // AddPort is not supported                         LcmAddPortEx,                             NULL,   // ConfigurePort is not supported           NULL,   // DeletePort is not supported                         LcmGetPrinterDataFromPort,           LcmSetPortTimeOuts,           LcmXcvOpenPort,           LcmXcvDataPort,           LcmXcvClosePort,           LcmShutdown                 };       LPMONITOR2       LocalMonInitializePrintMonitor2(                         PMONITORINIT pMonitorInit,           PHANDLE phMonitor           )                 {                         LPWSTR pPortTmp;           DWORD dwCharCount=1024, rc, i, j;           PINILOCALMON pIniLocalMon = NULL;           LPWSTR pPorts = NULL;                         OutputDebugString(_T(“LocalMonInitializePrintMonitor2\n”));                         if( !pMonitorInit−&gt;bLocal ) {                         return NULL;                         {                     /*   do {                         FreeSplMem((LPVOID)pPorts);           dwCharCount *= 2;           pPorts = (LPWSTR) AllocSplMem(dwCharCount*sizeof(WCHAR));           if ( !pPorts ) {                         DBGMSG(DBG_ERROR,                         (“Failed to alloc %d characters for ports\n”, dwCharCount));                         goto Fail;                         }           rc = GetProfileString(szPorts, NULL, szNULL, pPorts, dwCharCount);           if ( !rc∥dwCharCount &gt;= 1024*1024) { //Work around in GetProfileString bug                         DBGMSG(DBG_ERROR,                         (“GetProfilesString failed with %d\n”, GetLastError()));                         goto Fail;                         }                         } while (rc &gt;= dwCharCount − 2);                 */                         pIniLocalMon = (PINILOCALMON)AllocSplMem( sizeof( INILOCALMON ));           if( !pIniLocalMon ){                         goto Fail;                         }           pIniLocalMon−&gt;signature = ILM_SIGNATURE;           pIniLocalMon−&gt;pMonitorInit = pMonitorInit;           //           // dwCharCount is now the count of return buffer, not including           // the NULL terminator. When we are past pPorts[rc], then           // we have parsed the entire string.           //                     //   dwCharCount = rc;                         LcmEnterSplSem();           // We now have all the ports                 /* for(j = 0; j &lt;= dwCharCount; j += rc + 1){                         pPortTmp = pPorts + j;           rc = wcslen(pPortTmp);           if( !rc ){                         continue;                         }           if (!_wcsnicmp(pPortTmp, L“Ne”, 2)) {                         i = 2;                 // For Ne-ports                         //           if ( rc &gt; 2 &amp;&amp; pPortTmp[2] == L‘-’)                         ++i;                         for ( ;i &lt; rc − 1 &amp;&amp; iswdigit(pPortTmp[i]); ++i)           ;           if( i == rc − 1 &amp;&amp; pPortTmp[rc−1] == L‘:’) {                         continue;                         }                         }                         else if (!_wcsnicmp(pPortTmp,L“COM”,3)) {                         continue;                         }           else if (!_wcsnicmp(pPortTmp,L“LPT”,3)) {                         continue;                         }           else if(!_wcsnicmp(pPortTmp,L“FILE”,3)) {                         continue;                         }                 */                         LcmCreatePortEntry(pIniLocalMon, L“MPD Port”);                     //   }                         //FreeSplMem(pPorts);           LcmLeaveSplSem();           //CheckAndAddIrdaPort(pIniLocalMon);           *phMonitor = (HANDLE)pIniLocalMon;           return &amp;Monitor2;                 Fail:                         //FreeSplMem( pPorts );           FreeSplMem( pIniLocalMon );           return NULL;                 }       /*        *        */       BOOL WINAPI       DllMain(                         HANDLE hModule,           DWORD dwReason,           LPVOID lpRes)                 {                         switch (dwReason)           {           case DLL_PROCESS_ATTACH:                         DBGMSG(DBG_TRACE,(“IN LIKE FLYNN!\n”));           OutputDebugString(_T(“DLLEntryPoint: Attach\n”));                         LocalMonInit(hModule);           DisableThreadLibraryCalls(hModule);           return TRUE;                         case DLL_PROCESS_DETACH:                         return TRUE;                         }           UNREFERENCED_PARAMETER(lpRes);           return TRUE;                 }                  
 
     [0023] A system for mobile USB printing, includes a USB printer driver installed on a USB enabled host device, for automatically detecting and printing to a USB enabled printer, wherein the USB printer driver includes a generic driver for generating a print job in a page description language for each of a plurality of different printer types; a detector for detecting any USB enabled printers connected to a USB port on a host device; means, responsive to detection of a USB enabled printer connected to the USB port on the host device, for retrieving the detected USB enabled printer&#39;s device name; means, responsive to the printer&#39;s device name, for generating a print path between the host device and the detected USB enabled printer; and a spooler for sending the print job to the detected USB enabled printer using the print path; and a USB enabled printer connected to the host device via a USB connection.  
     [0024] The USB printer driver includes a generic print driver which is capable of creating or generating a print job in an appropriate PDL (such as printer-independent PostScript) for any supported printer. Any USB enabled printer may be used in the system, for example, USB Plug&#39;n Play capable printer. The generic driver may be in the form of a print driver, or a print driver plug-in for use in an application having a print functionality. The list of supported printers is not necessarily small, and could encompass a large variety of devices. The generic driver could support any number of printer features and page sizes, depending on the implementation and marketing goals of the end product. The USB printer driver may also include a port monitor or a language monitor for automatically detecting a USB connected printer and diverting the print job to that printer.  
     [0025] One embodiment of the automatic USB detection uses the Plug &#39;n Play technology inherent to Windows 2000/XP operating systems (as well as other Microsoft operating systems). Once invoked, the USB printer driver will scan the USB devices for a connected printer, and capture the USB device&#39;s product name. If the product name is in the supported list of printers, the print job is spooled through a port monitor to disk, and then sent to the printer directly from the spooler.  
     [0026] The USB printer driver may be implemented for many different operating systems. The following example is based on an implementation for a Windows operating system using Windows calls. The USB printer driver scans or detects for currently connected USB devices. For each found USB device, the USB printer driver creates a fully qualified device path (e.g., one which is suitable for a CreateFile call in Windows). The general approach is as follows:  
     [0027] Obtain a list of all present USB printer devices using SetupDiGetClassDevs( ) API Call.  
     [0028] Enumerate through all the devices in the list using SetupDiEnumDeviceInfo( ).  
     [0029] Try to open the device&#39;s registry key using SetupDiOpenDevRegKey( ).  
     [0030] If that succeeds, try to open the device&#39;s symbolic name using RegQueryValueEx( ).  
     [0031] If that succeeds, return the symbolic name as the fully qualified path for use with CreateFile( ).  
     [0032] The USB printer driver opens the Windows registry key for all the detected USB enabled printers. The USB printer&#39;s symbolic name is retried and then formatted so that it may be used in a CreateFile call. The formatted symbolic name is used as a parameter in the CreateFile call. Then the USB printer driver opens a file handle to the printer and the print job is sent.  
     [0033] This bypasses the standard usbmon.dll port monitor provided by Windows. Instead, the print job is re-routed through the USB printer driver&#39;s spooler, which opens the USB port, and spools the document directly to the discovered printer. This technique is further described in the following exemplary code of a portion of a mobile USB printer driver according to one embodiment of the invention which determines whether or not a USB printer is connected and if so, obtains the printers fully qualified path and user-friendly name.  
                                  /************************************************************************                     **   IsUSBPrinterConnected                         **   Purpose:   To provide ability to determine if a printer is connected USB                     **   currently. Returns the name of the USB printer and the fully       **   qualified path to print to. sizePrinterName and sizePath must be       **   set to the acutal size in bytes of the buffers being passed       **   (not the string length in TCHARs).                 ************************************************************************/       bool IsUSBPrinterConnected (TCHAR *szPrinterName, DWORD sizePrinterName,       TCHAR *szPath, DWORD sizePath)       {                         HIDEVINFO hDevInfo;           SP_DEVINFO_DATA DeviceInfoData;           DWORD i;                         bool bRetVal = false;                         // Create a HDEVINFO with all present devices.           hDevInfo = SetupDiGetClassDevs(NULL,                         _T(“USB”), // Enumerator           0,           DIGCF_PRESENT|DIGCF_ALLCLASSES);                         if (hDevInfo == INVALID_HANDLE_VALUE)           {                         // Insert error handling here.           return false;                         }           // Enumerate through all devices in Set.           DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);           for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,                         &amp;DeviceInfoData);i++)                         {                         DWORD DataT;                     //   LPSTR buffer = NULL;                         DWORD buffersize = 0;           //           // Call function with null to begin with, then use the returned buffer size to Alloc           // the buffer. Keep calling until success or an unknown failure.           //           if (SetupDiGetDeviceRegistryProperty(                         hDevInfo,           &amp;DeviceInfoData,           SPDRP_LOCATION_INFORMATION  ,                     //   SPDRP_DEVICEDESC,                         &amp;DataT,           (PBYTE)szPrinterName,           sizePrinterName,           NULL)) {                 //this means that it&#39;s hooked up.       //MessageBox(NULL, buffer,_T(“Found USB Printer”), MD_OK);       //ERROR CHECK HERE!!!!! MAKE SURE IT&#39;S THE RIGHT PRINTER                         HKEY hKey =           SetupDiOpenDevRegKey(hDevInfo,&amp;DeviceInfoData,DICS_FLAG_GLOBAL,                 O,DIREG_DEV, KEY_READ);                         DWORD oops = GetLastError();           if (hKey != INVALID_HANDLE_VALUE) {                     //   TCHAR junk[500];                         DWORD size = sizePath;           oops = RegQueryValueEx(hKey,_T(“SymbolicName”), NULL, NULL,                 (PBYTE)szPath, &amp;sizePath);                         if (oops == ERROR_SUCCESS) {                 //now, format the information       //what used to look like “/??/USB...” needs to look like “//./USB....”                                 szPath[0]= 92;   //   ‘/’           szPath[1]= 92;   //   ‘/’           szPath[2]= ‘.’;   //   ‘.’           szPath[3]= 92;   //   ‘/’           bRetVal = true;                         }                         }                         }                 }                         // Cleanup           SetupDiDestroyDeviceInfoList(hDevInfo);                         return bRetVal;                 }                  
 
     [0034] The following exemplary code of a portion of a mobile USB printer driver according to one embodiment of the invention describes the functionality that allows the driver to automatically direct the job to the appropriate USB printer using a spooler.  
                                  ***********************************************************************       ** LcmStartDocPort       **                     ** Purpose:   This is where much of the interesting things take place. We need to       **   determine if we are the walk-up printer driver, and if so, launch the       **   applicable dialog. Also, we need to create the appropriate spool file       **   and job ticket.                 ***********************************************************************/       BOOL       LcmStartDocPort(                         HANDLE hPort,           LPWSTR pPrinterName,           DWORD JobId,           DWORD Level,           LPBYTE pDocInfo)                 {                             PINIPORT   pIniPort = (PINIPORT)hPort;           HANDLE   hToken;                         PDOC_INFO_1 pDocInfo1 = (PDOC_INFO_1)pDocInfo;           DWORD Error = 0;                         OutputDebugString(_T(“LcmStartDocPort\n”));                         DBGMSG(DBG_TRACE, (“StartDocPort(%08x, %ws, %d, %d, %08x)\n”,                         hPort, pPrinterName, JobId, Level, pDocInfo));                         if (pIniPort−&gt;Status &amp; PP_STARTDOC) {                         return TRUE;                         }           LcmEnterSplSem();           pIniPort−&gt;Status |= PP_STARTDOC;           LcmLeaveSplSem();           pIniPort−&gt;hPrinter = NULL;           pIniPort−&gt;pPrinterName = AllocSplStr(pPrinterName);           if (pIniPort−&gt;pPrinterName) {                         if (OpenPrinter(pPrinterName, &amp;pIniPort−&gt;hPrinter, NULL)) {                         //try to get the job name (cast it to a doc_info_1                 structure, even though it might be doc_info_2                         _tcscpy(szJobDispName, ((DOC_INFO_1                 *)pDocInfo)−&gt;pDocName);                         pIniPort−&gt;JobId = JobId;                         //create some file paths           //get the base path           TCHAR szBasePath[64];           TCHAR szJobPath[MAX_PATH];           //Get the Base path from the registry           if(!_SpoolerJobTicketClass::GetSpoolPath(szBasePath,                 MAX_PATH))                         _tcscpy(szBasePath,_T(“c:\\temp\\”));                         //the job will be called                 &lt;base_path&gt;\opb_spool&lt;job_id&gt;.prn                         _stprintf(szJobPath,                 _T(“%sopb_spool_%d.prn”),szBasePath,JobId);                         //right here, we need to know if we are the Walk-up                 Printer Driver. Only this driver                         //will prompt the user at print time for an IP                 address.                         if (IsWalkupPrinter(pIniPort−&gt;hPrinter)) {                         //we know we need to launch our dialog           TCHAR szIPAddress[MAX_PATH];           TCHAR szPrinterModel[MAX_PATH];           _SpoolerJobTicketClass::PORT_TYPE PortType =                 _SpoolerJobTicketClass::_IP;                         //memset (szIPAddress, 0,                 sizeof(szIPAddress));                         HWND hParent = GetForeground Window();           //see if they have printed recently           int rc = IDNO; //this is a way to always                 drop into the next line if we don&#39;t call this                         //update our timer value based on user&#39;s                 settings                         global_timer_value =                 GetCountdownTimerValue();                         //check for USB           if (IsUSBPrinterConnected (szPrinterModel,                 MAX_PATH, szIPAddress MAX_PATH)) {                         rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE(DLG_NOTIFICATION), hParent,                         (int                 (_stdcall *)(void))&amp;PrintNoticeProc, (LPARAM)szPrinterModel );                         if (rc == IDOK)                         PortType =                 _SpoolerJobTicketClass::_USB;                         }           else if (PrintToLast(pIniPort−&gt;hPrinter)) {                         GetLastCachedIP(pIniPort−&gt;hPrinter,                 szIPAddress);                         rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE(DLG_NOTIFICATION), hParent, (int (_stdcall       *)(void))&amp;PrintNoticeProc, (LPARAM)szIPAddress );                         }           //this will be called either if PrintToLast                 is false, or if the user wishes to choose another printer                         if (rc == IDNO) {                         //we let them choose           //be kind of tricky, and pass the                 Printer name through this IP address variable                         _tcscpy(szIPAddress,pPrinterName);           rc = DialogBoxParam( LcmhInst,                 MAKEINTRESOURCE( DLG_WALKUP ), hParent, (int (_stdcall       *)(void))&amp;WalkupProc, (LPARAM)sIPAddress );                         }           //this will be the value if the user either                 lets the print to last dialog run it&#39;s course, or chooses a specific printer       themselves                         if (rc == IDOK) {                         //we go ahead and print...           //the user has successfully selected                 something                         //MessageBox(hParent, szIPAddress,                 NULL, MB_OK);                         TCHAR szJobTicketPath[MAX_PATH];           //the ticket will be called                 &lt;base_path&gt;\opb_spool_&lt;job_id&gt;.tkt                         _stprintf(szJobTicketPath,                 _T(“%sopb_spool_%d.tkt”),szBasePath,JobId);                         _SpoolerJobTicketClass NewJobTicket;           //copy the job name                 _tcscpy(NewJobTicket.pJobTicket−&gt;szJobName, ((DOC_INFO_1       *)pDocInfo)−&gt;pDocName);                         //we only support 1 printer for                 walk-up       NewJobTicket.pJobTicket−&gt;dwTotalAvailablePrinters = 1;                         //initialize the printer (only one                 of them for walk-up)       NewJobTicket.pJobTicket−&gt;printerInfo[0].enumPortType=PortType;       _tcscpy(NewJobTicket.pJobTicket−&gt;printerInfo[0].szPortVal, szIPAddress);                         //now, write it out           LaunchOPBSpooler();                 NewJobTicket.WriteJobTicketToDisk(szJobTicketPath);                         _SpoolerJobTicketClass test;                 test.ReadJobTicketFromDisk(szJobTicketPath);                         }           //this will be the case if the user cancels                 either the print to last dialog, or the choose printer dialog                         else if (rc == IDCANCEL) {                         //we cancel the job           DWORD retVal =                 SetJob(pIniPort−&gt;hPrinter, JobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);                         retVal = SetJob(pIniPort−&gt;hPrinter,                 JobId, 0, NULL, JOB_CONTROL_DELETE);                         }                         }                         //           // For non dosdevices CreateFile on the name of the port           //                         pIniPort−&gt;hFile = CreateFile(szJobPath,//pIniPort−&gt;PName,                         GENERIC_WRITE,           FILE_SHARE_READ,           NULL,           OPEN_ALWAYS,           FILE_ATTRIBUTE_NORMAL |           FILE_FLAG_SEQUENTIAL_SCAN,           NULL);                         if ( pIniPort−&gt;hFile != INVALID_HANDLE_VALUE)                         SetEndOfFile(pIniPort−&gt;hFile);                         }                         } // end of if (pIniPort−&gt;pPrinterName)           if (pIniPort−&gt;hFile == INVALID_HANDLE_VALUE)                         goto Fail;                         bNewStart = TRUE;                         return TRUE;                 Fail:                         SPLASSERT(pIniPort−&gt;hFile == INVALID_HANDLE_VALUE);           LcmEnterSplSem();           pIniPort−&gt;Status &amp;= ˜PP_STARTDOC;           LcmLeaveSplSem();           if (pIniPort−&gt;hPrinter) {                         ClosePrinter(pIniPort−&gt;hPrinter);                         }           if (pIniPort−&gt;pPrinterName) {                         FreeSplStr(pIniPort−&gt;pPrinterName);                         }           if (Error)                         SetLastError(Error);                         return FALSE;                 }                  
 
     [0035] The USB printer driver automatically detects a USB enabled printer connected to the user&#39;s PC. Referring to FIG. 2, the user selects print (step  101 ) from an application running on the user&#39;s PC. If the USB printer is selected as the default printer (or manually selected by the user) (step  102 ), the USB printer driver checks to see if a USB printer is connected to the USB port (step  104 ). If yes, the USB printer driver sends the job to the USB printer (step  106 ). If there is no USB printer connected, or if the USB printer connected is not supported by the USB printer driver, the user is prompted to select another printer (step  108 ). A dialog box displaying a list of available printers (step  110 ) such as those on FIG. 3 is displayed to the user. Alternatively, the dialog box may display a list of recently used printers. The dialog box may display the list of available printers by IP address, by DNS name, by UNC path. Suppose the user selects the beacon printer (step  112 ). The beacon printer driver then prompts the user to walk to the desired printer (step  114 ). At the desired printer, the user activates the beacon (step  116 ). The beacon driver then sends the print job to that printer.  
     [0036] The beacon printer driver and the USB printer driver can be installed on the same PC. Referring to FIG. 3, in the printer dialog box  200 , a user may select from any of printer # 1 , printer # 1 , printer # 3 , beacon printer and USB printer. If the user selects the beacon printer for printing, then the user will be prompted to physically activate the beacon at the desired printer. If the user is travelling, the user may select the USB printer and connect to a USB enabled printer.  
     [0037] The invention has been described with reference to particular embodiments for convenience only. Modifications and alterations will occur to others upon reading and understanding this specification taken together with the drawings. The embodiments are but examples, and various alternatives, modifications, variations or improvements may be made by those skilled in the art from this teaching which are intended to be encompassed by the following claims.