Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Printer Monitor polling and event driven

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Printer Monitor polling and event driven

    The code below is a port from a ms sample application
    You can download the original source from http://support.microsoft.com/support.../Q196/8/05.asp

    With the gratefull help of Tom Hanlin this app works now.

    1. Select a Printer for polling (Menu: Printer/Select)
    2. on NT 3.51 or above you may select os event driven mode (Menu:Options/Use ...(Check that menuitem)

    Code:
    #Dim All
    #Register None
    #Compile Exe "PrintMon.Exe"
    #Include "COMMCTRL.INC"
    #Resource "printjob.pbr"  '--->search for printjob.pbr.uue near the end of this message
    
    'REDEFINED API DECLARARTIONS FROM WIN32API.INC
    'USE THIS VERSIONS OR MAKE OTHER DECLARES THAT WORKS
    Declare Function FindFirstPrinterChangeNotification Lib "WINSPOOL.DRV" Alias "FindFirstPrinterChangeNotification" ( _
        ByVal hPrinter As Dword, _
        ByVal fdwFlags As Dword, _
        ByVal fdwOptions As Dword, _
        pPrinterNotifyOptions As Dword) As Long
    
    Declare Function FindNextPrinterChangeNotification Lib "WINSPOOL.DRV" Alias "FindNextPrinterChangeNotification" ( _
        ByVal hChange As Dword, _
        pdwChange As Dword, _
        pPrinterNotifyOptions As Dword, _
        ppPrinterNotifyInfo As Dword) As Long
    
    
    'MISSING API CONSTANTS
    %JOB_STATUS_DELETED             = &H00000100
    %JOB_STATUS_BLOCKED_DEVQ        = &H00000200
    %JOB_STATUS_USR_INTERVENTION    = &H00000400      'WRONG API DECLARATION ->%JOB_STATUS_USER_INTERVENTION   = &H10000
    %JOB_STATUS_RESTART             = &H00000800
    
    %PRINTER_STATUS_SERVER_UNKNOWN  = &H00800000
    %PRINTER_STATUS_POWER_SAVE      = &H01000000
    
    
    %PRINTER_NOTIFY_TYPE                        = &H00??
    %JOB_NOTIFY_TYPE                            = &H01??
    
    %PRINTER_NOTIFY_FIELD_SERVER_NAME           = &H00??
    %PRINTER_NOTIFY_FIELD_PRINTER_NAME          = &H01??
    %PRINTER_NOTIFY_FIELD_SHARE_NAME            = &H02??
    %PRINTER_NOTIFY_FIELD_PORT_NAME             = &H03??
    %PRINTER_NOTIFY_FIELD_DRIVER_NAME           = &H04??
    %PRINTER_NOTIFY_FIELD_COMMENT               = &H05??
    %PRINTER_NOTIFY_FIELD_LOCATION              = &H06??
    %PRINTER_NOTIFY_FIELD_DEVMODE               = &H07??
    %PRINTER_NOTIFY_FIELD_SEPFILE               = &H08??
    %PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR       = &H09??
    %PRINTER_NOTIFY_FIELD_PARAMETERS            = &H0A??
    %PRINTER_NOTIFY_FIELD_DATATYPE              = &H0B??
    %PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR   = &H0C??
    %PRINTER_NOTIFY_FIELD_ATTRIBUTES            = &H0D??
    %PRINTER_NOTIFY_FIELD_PRIORITY              = &H0E??
    %PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY      = &H0F??
    %PRINTER_NOTIFY_FIELD_START_TIME            = &H10??
    %PRINTER_NOTIFY_FIELD_UNTIL_TIME            = &H11??
    %PRINTER_NOTIFY_FIELD_STATUS                = &H12??
    %PRINTER_NOTIFY_FIELD_STATUS_STRING         = &H13??
    %PRINTER_NOTIFY_FIELD_CJOBS                 = &H14??
    %PRINTER_NOTIFY_FIELD_AVERAGE_PPM           = &H15??
    %PRINTER_NOTIFY_FIELD_TOTAL_PAGES           = &H16??
    %PRINTER_NOTIFY_FIELD_PAGES_PRINTED         = &H17??
    %PRINTER_NOTIFY_FIELD_TOTAL_BYTES           = &H18??
    %PRINTER_NOTIFY_FIELD_BYTES_PRINTED         = &H19??
    %PRINTER_NOTIFY_FIELD_OBJECT_GUID           = &H1A??
    
    %JOB_NOTIFY_FIELD_PRINTER_NAME              = &H00??
    %JOB_NOTIFY_FIELD_MACHINE_NAME              = &H01??
    %JOB_NOTIFY_FIELD_PORT_NAME                 = &H02??
    %JOB_NOTIFY_FIELD_USER_NAME                 = &H03??
    %JOB_NOTIFY_FIELD_NOTIFY_NAME               = &H04??
    %JOB_NOTIFY_FIELD_DATATYPE                  = &H05??
    %JOB_NOTIFY_FIELD_PRINT_PROCESSOR           = &H06??
    %JOB_NOTIFY_FIELD_PARAMETERS                = &H07??
    %JOB_NOTIFY_FIELD_DRIVER_NAME               = &H08??
    %JOB_NOTIFY_FIELD_DEVMODE                   = &H09??
    %JOB_NOTIFY_FIELD_STATUS                    = &H0A??
    %JOB_NOTIFY_FIELD_STATUS_STRING             = &H0B??
    %JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR       = &H0C??
    %JOB_NOTIFY_FIELD_DOCUMENT                  = &H0D??
    %JOB_NOTIFY_FIELD_PRIORITY                  = &H0E??
    %JOB_NOTIFY_FIELD_POSITION                  = &H0F??
    %JOB_NOTIFY_FIELD_SUBMITTED                 = &H10??
    %JOB_NOTIFY_FIELD_START_TIME                = &H11??
    %JOB_NOTIFY_FIELD_UNTIL_TIME                = &H12??
    %JOB_NOTIFY_FIELD_TIME                      = &H13??
    %JOB_NOTIFY_FIELD_TOTAL_PAGES               = &H14??
    %JOB_NOTIFY_FIELD_PAGES_PRINTED             = &H15??
    %JOB_NOTIFY_FIELD_TOTAL_BYTES               = &H16??
    %JOB_NOTIFY_FIELD_BYTES_PRINTED             = &H17??
    
    %PRINTER_NOTIFY_OPTIONS_REFRESH             = &H01
    %PRINTER_NOTIFY_INFO_DISCARDED              = &H01
    
    'MISSING TYPE DECLARATIONS
    Type PRINTER_NOTIFY_OPTIONS_TYPE
        wType           As Word
        Reserved0       As Word
        Reserved1       As Dword
        Reserved2       As Dword
        Count           As Dword
        pFields         As Word Ptr
    End Type
    
    Type PRINTER_NOTIFY_OPTIONS
        Version         As Dword
        Flags           As Dword
        Count           As Dword
        pTypes          As PRINTER_NOTIFY_OPTIONS_TYPE Ptr
    End Type
    
    Type NotifyDataUnionType
        cbBuf   As Dword
        pBuf    As Dword
    End Type
    
    Union NotifyDataUnion
        adwData(1)      As Dword
        pniUnionData    As NotifyDataUnionType
    End Union
    
    
    Type PRINTER_NOTIFY_INFO_DATA
        wType           As Word
        Field           As Word
        Reserved        As Dword
        Id              As Dword
        NotifyData      As NotifyDataUnion
    End Type
    
    Type PRINTER_NOTIFY_INFO
        Version         As Dword
        Flags           As Dword
        Count           As Dword
        aData(0)        As PRINTER_NOTIFY_INFO_DATA
    End Type
    
    'MISSING API FUNCTION ECLARATIONS
    Declare Function FreePrinterNotifyInfo Lib "winspool.drv" Alias "FreePrinterNotifyInfo"  (ByVal pPrinterNotifyInfo As PRINTER_NOTIFY_INFO Ptr) As Long
    
    
    'APPLICATION CONSTANTS & TYPE DECLARATIONS
    Type STRSIZE
        pszStr          As Asciiz Ptr
        cSize           As Integer
    End Type
    
    Type THREADPARAM
        hWnd            As Dword            '/* the main aplication window */
        hPrinter        As Dword            '/* the printer to watch */
        hPrevThread     As Dword            '/* The previous thread to be closed */
    End Type
    
    Global              g_hInst As Dword                '/* current application instance */
    Global              g_hWndJobList   As Dword        '/* Window handle of list view control */
    Global              g_bUsePrinterChanges As Dword   '/* Toggle for NT feature, polling by default */
    
    '/* Thread synchronization globals */
    Global              g_nPollIntervalms As Dword      '/* milliseconds polling resolution */
    Global              g_hTerminateEvent As Dword      '/* Signalling event for thread to terminate. */
    Global              g_hForceRefreshEvent As Dword   '/* Signal for thread to wakeup and refresh */
    Global              bCloseApp As Dword              '/* If true, the thread should post a WM_CLOSE */
    Global              pnColumns As Dword Ptr
    
    Global              szAppName As Asciiz * 255
    Global              szTitle As Asciiz * 255
    
    %PrinterNameSize            = 200
    %ColumnHeadingSize          = 20
    
    %DOCUMENTNAME               = 0
    %OWNERNAME                  = 1
    %STATUS                     = 2
    %SUBMITTED                  = 3
    %PROGRESS                   = 4
    %NFIELDS                    = 5
    
    '/*
    ' * Current state of the Printer Queue we are watching
    ' */
    Type PRINTERDATA
        pszPrinterName  As Asciiz Ptr   '// The shell "friendly" name
        pszServerName   As Asciiz Ptr   '// The server of the printer
        pszShareName    As Asciiz Ptr   '// The share for the printer
        dwStatus        As Dword        '// Current status of the printer
        cJobs           As Dword        '// # of jobs currently in the printer
        hPrinter        As Dword        '// An open handle to the printer
    End Type
    
    '/*
    ' * Current state of a particular job in the queue.
    ' */
    Type JOBDATA
        '// members of the JOB_INFO_* structure we are interested in.
        '// Copy them here for safekeeping between updates
        JobId           As Dword
        pszDocument     As Asciiz Ptr       '//DOCUMENTNAME
        pszOwner        As Asciiz Ptr       '//OWNERNAME
    
        pszStatus       As Asciiz Ptr       '//STATUS
        dwStatus        As Dword
    
        PagesPrinted    As Dword            '//PROGRESS
        TotalPages      As Dword
        nSize           As Dword
        BytesPrinted    As Dword
    
        Submitted       As SYSTEMTIME       '//SUBMITTED
    
        '// Keep track of what parts change on each update.
        '// Usefull for altering only that piece of data
        '// in the ListView control
        Changed(%NFIELDS - 1)    As Byte
    End Type
    
    '/*
    ' * Top of the data structure
    ' */
    Type QUEUEDATA
        Printer         As PRINTERDATA
        nJobs           As Long
        nAllocated      As Long
        pJobs           As JOBDATA Ptr
    End Type
    
    'Dialogs Identifiers
    %IDS_COLUMN1                = 1
    %IDS_COLUMN2                = 2
    %IDS_COLUMN3                = 3
    %IDS_COLUMN4                = 4
    %IDS_COLUMN5                = 5
    %IDS_COLUMN6                = 6
    %IDR_PRINTJOB               = 101
    %IDI_APP                    = 102
    %IDD_ABOUTBOX               = 103
    %IDD_SELECTPRINTER          = 106
    %IDR_COLUMNWIDTHS           = 109
    %IDR_COLUMNCOUNT            = 111
    %IDC_FILEDESCRIPTION        = 1000
    %IDC_PRODUCTVERSION         = 1001
    %IDC_LEGALCOPYRIGHT         = 1002
    %IDC_COMPANYNAME            = 1003
    %IDC_LEGALTRADEMARKS        = 1004
    %IDC_EDITSELECTPRINTER      = 1005
    %IDC_LISTVIEW               = 1006
    %IDM_NEW                    = 40001
    %IDM_OPEN                   = 40002
    %IDM_SAVE                   = 40003
    %IDM_SAVEAS                 = 40004
    %IDM_PRINT                  = 40005
    %IDM_PRINTSETUP             = 40006
    %IDM_EXIT                   = 40007
    %IDM_UNDO                   = 40008
    %IDM_CUT                    = 40009
    %IDM_COPY                   = 40010
    %IDM_PASTE                  = 40011
    %IDM_LINK                   = 40012
    %IDM_LINKS                  = 40013
    %IDM_HELPCONTENTS           = 40014
    %IDM_HELPSEARCH             = 40015
    %IDM_HELPHELP               = 40016
    %IDM_ABOUT                  = 40017
    %ID_FILE_REFRESH            = 40018
    %IDM_SELECTPRINTER          = 40019
    %IDM_PRINTERNOTIFICATION    = 40020
    %IDC_STATIC                 = -1
    
    
    Function IS_NT As Long
        Function = (GetVersion() < &H80000000???)
    End Function
    
    Function MakeIntResource (ByVal iRcID As Long) As Dword
      Function = MakDwd(iRcID, 0)
    End Function
    
    Sub ResizeQueList(ByVal hwndListView As Dword, ByVal hwndParent As Dword)
    
        Dim rc As RECT
    
        GetClientRect hwndParent, rc
    
        MoveWindow hwndListView, rc.nleft, rc.ntop, rc.nright - rc.nleft, rc.nbottom - rc.ntop, %TRUE
    
    End Sub
    
    Sub ResizeWindowToControlWidth(ByVal hWnd As Dword, ByVal hWndControl As Dword)
    
        Dim rcWindow As RECT
        Dim rcClient As RECT
    
        '/* Set required client area to accomodate our columns */
        GetClientRect hWnd, rcClient
        GetWindowRect hWndControl, rcWindow
        rcClient.nright = rcClient.nleft + (rcWindow.nright - rcWindow.nleft)
    
        '/* Calculate the window size to accomodate the Client size */
        AdjustWindowRect rcClient, %WS_OVERLAPPEDWINDOW, %TRUE
        GetWindowRect hWnd, rcWindow
    
        '/* New Width and old Height */
        rcWindow.nbottom = rcClient.nbottom-rcClient.ntop
        rcWindow.nright = rcClient.nright-rcClient.nleft
    
        '/* Adjust the window size */
        MoveWindow hWnd, rcWindow.nleft, rcWindow.ntop, rcWindow.nright, rcWindow.nbottom, %FALSE
    
    End Sub
    
    Function InitQueList(ByVal hwndListView As Dword) As Dword
    
        Dim lvCol As LV_COLUMN
        Dim i   As Long
        Dim nColumns As Long
        Dim nListWidth As Long
        Dim ColumnWidths As Word Ptr
        Dim hColumnWidthsRsrc As Dword
        Dim hColumnWidths As Dword
        Dim hColumnCountRsrc As Dword
        Dim hColumnCount As Dword
        Dim heading As Asciiz * 21'(ColumnHeadingSize + 1)
        Dim rcWindow As RECT
    
        '// empty the list just to make sure.
        ListView_DeleteAllItems hwndListView
        While ListView_DeleteColumn(hwndListView, 0)  <> 0
        Wend
    
        '/* pull out the # of columns from the resource */
        hColumnCountRsrc = FindResource(g_hInst, "#" & Format$(%IDR_COLUMNCOUNT), ByVal %RT_RCDATA)
        hColumnCount = LoadResource(g_hInst, hColumnCountRsrc)
        pnColumns = LockResource(hColumnCount)
        nColumns = @pnColumns      '// promote to an int
    
        '/* grab a pointer to an array of widths */
        hColumnWidthsRsrc = FindResource(g_hInst, "#" & Format$(%IDR_COLUMNWIDTHS), ByVal %RT_RCDATA)
        hColumnWidths = LoadResource(g_hInst, hColumnWidthsRsrc)
        ColumnWidths = LockResource(hColumnWidths)
    
        '//initialize the columns
        nListWidth = 0
        lvCol.mask = %LVCF_FMT Or %LVCF_WIDTH Or %LVCF_TEXT Or %LVCF_SUBITEM
        lvCol.fmt = %LVCFMT_LEFT
        For i = 0 To nColumns - 1
            '/* adjust the size of each column */
            lvCol.cx = @ColumnWidths[i]
    
            '/* accumulate width of columns and their seperations */
            nListWidth = nListWidth + @ColumnWidths[i] + 1
    
            LoadString g_hInst, %IDS_COLUMN1 + i, heading, %ColumnHeadingSize + 1
            lvCol.pszText = VarPtr(heading)
            ListView_InsertColumn hwndListView, i, lvCol
        Next
    
        '/* Resize the window */
        GetWindowRect hwndListView, rcWindow
        MoveWindow hwndListView, rcWindow.nleft, rcWindow.ntop, nListWidth, rcWindow.nbottom-rcWindow.ntop, 0
    
        Function = 1
    
    End Function
    
    Function CreateQueList(hwndParent As Dword) As Dword
    
        Dim dwStyle As Dword
        Dim hwndListView As Dword
        Dim bSuccess As Dword
        bSuccess = 1
    
        '/*
        ' * List View Styles for this control
        ' */
        dwStyle = %WS_TABSTOP Or %WS_CHILD Or %WS_BORDER Or %LVS_REPORT Or %WS_VISIBLE
    
        hwndListView = CreateWindowEx(   %WS_EX_CLIENTEDGE, _       '// ex style
                                         $WC_LISTVIEW, _            '// class name - defined in commctrl.h
                                         "", _                      '// dummy text
                                         dwStyle, _                 '// style
                                         0, _                       '// x position
                                         0, _                       '// y position
                                         0, _                       '// width
                                         0, _                       '// height
                                         hwndParent, _              '// parent
                                         %IDC_LISTVIEW, _           '// ID
                                         g_hInst, _                 '// instance
                                         ByVal 0)                   '// no extra data
    
        '/* Very bad, Bail */
        If IsFalse hwndListView Then
            Function = 0
            Exit Function
        End If
    
        '/* Setup the control via resources */
        InitQueList hwndListView
    
        '/* make the parent wide enough for the child window */
        ResizeWindowToControlWidth hwndParent, hwndListView
    
        Function = hwndListView
    
    End Function
    
    Function CenterWindow (ByVal hwndChild As Dword, ByVal hwndParent As Dword) As Long
    
        Dim rChild As RECT
        Dim rParent As RECT
        Dim wChild As Long
        Dim hChild As Long
        Dim wParent As Long
        Dim hParent As Long
        Dim wScreen As Long
        Dim hScreen As Long
        Dim xNew As Long
        Dim yNew As Long
        Dim hdc As Dword
    
        '// Get the Height and Width of the child window
        GetWindowRect hwndChild, rChild
        wChild = rChild.nright - rChild.nleft
        hChild = rChild.nbottom - rChild.ntop
    
        '// Get the Height and Width of the parent window
        GetWindowRect hwndParent, rParent
        wParent = rParent.nright - rParent.nleft
        hParent = rParent.nbottom - rParent.ntop
    
        '// Get the display limits
        hDC = GetDC(hwndChild)
        wScreen = GetDeviceCaps (hdc, %HORZRES)
        hScreen = GetDeviceCaps (hdc, %VERTRES)
        ReleaseDC hwndChild, hDC
    
        '// Calculate new X position, then adjust for screen
        xNew = rParent.nleft + ((wParent - wChild) / 2)
        If xNew < 0 Then
            xNew = 0
        ElseIf (xNew + wChild) > wScreen Then
            xNew = wScreen - wChild
        End If
    
    
        '// Calculate new Y position, then adjust for screen
        yNew = rParent.ntop + ((hParent - hChild) / 2)
        If (yNew < 0) Then
            yNew = 0
        ElseIf (yNew + hChild) > hScreen Then
            yNew = hScreen - hChild
        End If
    
        '// Set it, and return
        Function = SetWindowPos (hwndChild, ByVal 0, xNew, yNew, 0, 0, %SWP_NOSIZE Or %SWP_NOZORDER)
    
    End Function
    
    Function About(ByVal hDlg As Dword, ByVal message As Dword, ByVal uParam As Dword, ByVal lParam As Long) As Long
    
        Static hfontDlg As Dword
        Dim lpVersion As Asciiz Ptr
        Dim dwVerInfoSize As Dword
        Dim dwVerHnd As Dword
        Dim uVersionLen As Dword
        Dim wRootLen  As Word
        Dim bRetCode As Long
        Dim i As Long
        Dim szFullPath As Asciiz * 256
        Dim szResult As Asciiz * 256
        Dim szGetName As Asciiz * 256
    
        Select Case message
            Case %WM_INITDIALOG  '// message: initialize dialog box
                '// Center the dialog over the application window
                CenterWindow hDlg, GetWindow (hDlg, %GW_OWNER)
    
                '// Get version information from the application
                GetModuleFileName g_hInst, szFullPath, SizeOf(szFullPath)
                dwVerInfoSize = GetFileVersionInfoSize(szFullPath, dwVerHnd)
    
                If dwVerInfoSize > 0 Then
                    '// If we were able to get the information, process it:
                    Dim lpstrVffInfo As Asciiz Ptr
                    Dim hMem As Dword
                    hMem = GlobalAlloc(%GMEM_MOVEABLE, dwVerInfoSize)
                    lpstrVffInfo = GlobalLock(hMem)
                    GetFileVersionInfo szFullPath, dwVerHnd, dwVerInfoSize, ByVal lpstrVffInfo
                    lstrcpy szGetName, "\\StringFileInfo\\040904e4\\"
                    wRootLen = lstrlen(szGetName)
    
                    '// Walk through the dialog items that we want to replace:
                    For i = %IDC_FILEDESCRIPTION To %IDC_LEGALTRADEMARKS
                        GetDlgItemText hDlg, i, szResult, SizeOf(szResult)
                        Mid$(szGetName, wRootLen, 1) = Chr$(0)
                        lstrcat szGetName, szResult
                        uVersionLen = 0
                        lpVersion = 0
                        bRetCode = VerQueryValue(lpstrVffInfo, szGetName, lpVersion, uVersionLen)       '// For MIPS strictness
                        If bRetCode And uVersionLen And lpVersion Then
                           '// Replace dialog item text with version info
                                lstrcpy szResult, ByVal lpVersion
                                SetDlgItemText hDlg, i, szResult
                        End If
                    Next
                    GlobalUnlock hMem
                    GlobalFree hMem
                End If
                '// Create a font to use
                hfontDlg = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %VARIABLE_PITCH Or %FF_SWISS, "")
    
                '// Walk through the dialog items and change font
                For i = %IDC_FILEDESCRIPTION To %IDC_LEGALTRADEMARKS
                    SendMessage GetDlgItem(hDlg, i), %WM_SETFONT, hfontDlg, %TRUE
                Next
                Function = 1
                Exit Function
    
            Case %WM_COMMAND                        '// message: received a command
                If LoWrd(uParam) = %IDOK _          '// "OK" box selected?
                Or LoWrd(uParam) = %IDCANCEL Then   '// System menu close command?
                    EndDialog hDlg, %TRUE           '// Exit the dialog
                    DeleteObject hfontDlg
                    Function = 1
                    Exit Function
                End If
        End Select
        Function = 0
    
    End Function
    
    Function GetPrinterDlgProc(ByVal hDlg As Dword, ByVal message As Dword, ByVal uParam As Dword, ByVal lParam As Long) As Long
    
        Static pDlgParam As STRSIZE Ptr  '/* keep the dialog's parameters around */
    
        Select Case message
            Case %WM_INITDIALOG     '/* message: initialize dialog box*/
                CenterWindow hDlg, GetWindow (hDlg, %GW_OWNER)
    
                pDlgParam = lParam
                SetDlgItemText hDlg, %IDC_EDITSELECTPRINTER, ByVal @pDlgParam.pszStr
                Function = 1
                Exit Function
    
            Case %WM_COMMAND
                Select Case LoWrd(uParam)
                    Case %IDOK      '/* "OK" button selected? */
                        GetDlgItemText hDlg, %IDC_EDITSELECTPRINTER, ByVal @pDlgParam.pszStr, @pDlgParam.cSize
                        EndDialog hDlg, %TRUE
                        Function = 1
                        Exit Function
    
                    Case %IDCANCEL  '/* Exit the dialog */
                        EndDialog hDlg, %FALSE
                        Function = 1
                        Exit Function
                End Select
        End Select
    
        Function = 0
    
    End Function
    
    Function GetPrinterPath( _
                    ByVal hWnd As Dword, _              '/* window handle to main window */
                    ByRef pszPrinter As Asciiz, _   '/* string in which to store user's printer */
                    ByVal cStrSize As Dword) As Long    '/* size of string */
    
        Dim dlgParameters As STRSIZE
    
        dlgParameters.pszStr = VarPtr(pszPrinter)
        dlgParameters.cSize = cStrSize
    
        Function = DialogBoxParam( _
                g_hInst, _                              '/* current instance of app */
                "#" & Format$(%IDD_SELECTPRINTER), _    '/* dialog template */
                hWnd, _                                 '/* parent of the dialog */
                CodePtr(GetPrinterDlgProc), _           '/* dialog procedure */
                VarPtr(dlgParameters))                  '/* the string for the printer's name */
    
    End Function
    
    Function IsPrinterHandle(ByVal hPrinter As Dword ) As Long
    
        Dim cbNeeded    As Dword
        Dim Error_      As Dword
        Dim bRet        As Long
        bRet = %TRUE
    
        If IsFalse GetPrinter(hPrinter, 2, ByVal 0, 0, cbNeeded) Then
            Error_ = GetLastError()
            bRet = %FALSE
            If( Error_ = %ERROR_INSUFFICIENT_BUFFER) Then
                bRet = %TRUE
            End If
        End If
    
        Function = bRet
    
    End Function
    
    Function StopUpdateThread(ByVal hThread As Dword, ByVal bCloseAppNow As Long) As Long
        '/* No thread running? */
        If IsFalse hThread Then
            Function = 0
            Exit Function
        End If
    
        '/* Should we close the application too? */
        bCloseApp = bCloseAppNow
    
        '/* tell thread to quit */
        SetEvent g_hTerminateEvent
    
        Function = 1
    
    End Function
    
    Sub FreeJob(ByVal pJob As JOBDATA Ptr)
        HeapFree GetProcessHeap(), ByVal 0, @pJob.pszDocument
        HeapFree GetProcessHeap(), ByVal 0, @pJob.pszOwner
        HeapFree GetProcessHeap(), ByVal 0, @pJob.pszStatus
    '    ZeroMemory pJob, SizeOf(JOBDATA)
    End Sub
    
    Sub FreeJobs(ByVal pQueueData As QUEUEDATA Ptr)
        Dim i   As Long
        For i=0 To @pQueueData.nJobs - 1
            FreeJob VarPtr(@[email protected][i])
        Next
        HeapFree GetProcessHeap(), ByVal 0, @pQueueData.pJobs
        @pQueueData.pJobs = 0
        @pQueueData.nAllocated = 0
        @pQueueData.nJobs = 0
    End Sub
    
    Sub FreeQueueData(ByVal pQueueData As QUEUEDATA Ptr)
        FreeJobs pQueueData
        HeapFree GetProcessHeap(), ByVal 0, @pQueueData.Printer.pszPrinterName
        HeapFree GetProcessHeap(), ByVal 0, @pQueueData.Printer.pszServerName
        HeapFree GetProcessHeap(), ByVal 0, @pQueueData.Printer.pszShareName
        HeapFree GetProcessHeap(), ByVal 0, @pQueueData.pJobs
    '    ZeroMemory pQueueData, SizeOf(QUEUEDATA)
    End Sub
    
    Function ReplaceString(ByRef ppStr As Dword, pSrcStr As Asciiz Ptr) As Long
    
        Dim pNewStr As Asciiz Ptr
    
        '/*
        ' * OK, why not, propagate the NULL value.
        ' */
        If pSrcStr = 0 Then
            HeapFree GetProcessHeap(), 0 , ppStr
            ppStr = 0
            Function = %TRUE
            Exit Function
        End If
    
        '/* Make a copy of the source */
        pNewStr = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, lstrlen(@pSrcStr) + 1)
        '/* bad thing happened, bail */
        If pNewStr = 0 Then
            Function = %FALSE
            Exit Function
        End If
    
        lstrcpy ByVal pNewStr, ByVal pSrcStr
        '/* cleanup previous String */
        HeapFree GetProcessHeap(), 0, ppStr
    
        '/* send it back to caller */
        ppStr = pNewStr
        Function = %TRUE
    
    End Function
    
    Function SetPrinterQueueData(ByVal pQueueData As QUEUEDATA Ptr, ByVal pPI As PRINTER_INFO_2 Ptr) As Long
    
        Dim pd  As PRINTERDATA
    
        If pPI = 0 Then
            Function = 0
            Exit Function
        End If
    
        '/* Initialize and copy pertinent data */
        'ZeroMemory pd, sizeof(pd)
    
        ReplaceString pd.pszPrinterName, ByVal @pPI.pPrinterName
        ReplaceString pd.pszServerName, ByVal @pPI.pServerName
        ReplaceString pd.pszShareName, ByVal @pPI.pShareName
    
        pd.dwStatus = @pPI.Status
        pd.cJobs = @pPI.cJobs
        pd.hPrinter = @pQueueData.Printer.hPrinter
    
        '/* Cleanup previous printer data */
        HeapFree GetProcessHeap(), 0, @pQueueData.Printer.pszPrinterName
        HeapFree GetProcessHeap(), 0, @pQueueData.Printer.pszServerName
        HeapFree GetProcessHeap(), 0, @pQueueData.Printer.pszShareName
    
        '/* copy it */
        LSet @pQueueData.Printer = pd
        Function = %TRUE
    
    End Function
    
    Function AllocQueueData(ByVal pQueueData As QUEUEDATA Ptr, ByVal nJobs As Dword) As Long
    
        Dim NewData As QUEUEDATA
    
        '/* if we are to realloc, it should be expansion */
        If nJobs < @pQueueData.nAllocated Then
            Function = %FALSE
            Exit Function
        End If
    
        '/* short cut the trivial */
        If nJobs =  @pQueueData.nAllocated Then
            Function = %TRUE
            Exit Function
        End If
    
        '/* Allocate a new Queue */
        'ZeroMemory(&NewData, sizeof(NewData)
    
        If @pQueueData.pJobs = 0 Then
            NewData.pJobs = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, SizeOf(JOBDATA) * nJobs)
        Else
            NewData.pJobs = HeapReAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, @pQueueData.pJobs, SizeOf(JOBDATA) * nJobs)
        End If
        If NewData.pJobs = 0 And nJobs Then
            Function = %FALSE
            Exit Function
        End If
    
        NewData.nJobs = @pQueueData.nJobs
        NewData.nAllocated = nJobs
        NewData.Printer = @pQueueData.Printer
    
        '/* Initialize the new allocated data space */
        'ZeroMemory(&NewData.pJobs[NewData.nJobs], sizeof(JOBDATA)*(NewData.nAllocated-NewData.nJobs)
    
        Poke$ pQueueData, Peek$(VarPtr(NewData), SizeOf(QUEUEDATA))
    
        Function = %TRUE
    
    End Function
    
    Function NewJob(ByVal pJob As JOBDATA Ptr, ByVal pJI As JOB_INFO_2 Ptr) As Long
        '/* pJob assumed uninitilazed */
        'ZeroMemory pJob, SizeOf(JOBDATA)
    
        '/* Copy the data we want */
        @pJob.JobId = @pJI.JobId
    
        If IsFalse ReplaceString(@pJob.pszDocument, @pJI.pDocument) Then
            GoTo Fail
        End If
        If IsFalse ReplaceString(@pJob.pszOwner, @pJI.pUserName) Then
            GoTo Fail
        End If
        If IsFalse ReplaceString(@pJob.pszStatus, @pJI.pStatus) Then
            GoTo Fail
        End If
    
        @pJob.dwStatus = @pJI.Status
        @pJob.PagesPrinted = @pJI.PagesPrinted
        @pJob.TotalPages = @pJI.TotalPages
        @pJob.nSize = @pJI.nSize
        @pJob.Submitted = @pJI.Submitted
        @pJob.BytesPrinted = 0
    
        '/* Initialize the changed data to now chagnes */
        'memset(&pJob->Changed, (char)TRUE, sizeof(pJob->Changed)
        Dim i As Long
        For i = 0 To %NFIELDS - 1
            @pJob.Changed(i) = %TRUE
        Next
        Function = %TRUE
        Exit Function
    
    '/* Bail when failure occurs */
    Fail:
        FreeJob pJob
    
        Function = %FALSE
    
    End Function
    
    Function AddJobData(ByVal pQueueData As QUEUEDATA Ptr, ByVal pJI As JOB_INFO_2 Ptr) As Long
        '/* allocated more space if necessary */
        If @pQueueData.nJobs = @pQueueData.nAllocated Then
            If IsFalse AllocQueueData(pQueueData, @pQueueData.nAllocated + 1) Then
                Function = %FALSE
                Exit Function
            End If
        End If
        '/* Add the Job at the end */
        If IsFalse NewJob(VarPtr(@[email protected][@pQueueData.nJobs]), pJI) Then
            Function = %FALSE
            Exit Function
        End If
    
        Incr @pQueueData.nJobs
    
        Function = %TRUE
    
    End Function
    
    Sub MakePrinterStatusStr(pBuffer As Asciiz Ptr, ByVal nChars As Integer, ByVal pStatus As Dword)
    
        Dim pszStr As String
    
        '/* Pick which string to use */
        Select Case pStatus
            Case %PRINTER_STATUS_PAUSED
                pszStr = "Paused"
            Case %PRINTER_STATUS_OFFLINE
                pszStr = "Offline"
            Case %PRINTER_STATUS_PAPER_OUT
                pszStr = "Paper Out"
            Case %PRINTER_STATUS_PAPER_JAM
                pszStr = "Paper Jam"
            Case %PRINTER_STATUS_PAPER_PROBLEM
                pszStr = "Paper Problem"
            Case %PRINTER_STATUS_ERROR
                pszStr = "Error"
            Case %PRINTER_STATUS_PRINTING
                pszStr = "Printing"
            Case %PRINTER_STATUS_OUTPUT_BIN_FULL
                pszStr = "Output Bin Full"
            Case %PRINTER_STATUS_USER_INTERVENTION
                pszStr = "User Intervention Required"
            Case %PRINTER_STATUS_MANUAL_FEED
                pszStr = "Manual Feed"
            Case %PRINTER_STATUS_IO_ACTIVE
                pszStr = "IO Active"
            Case %PRINTER_STATUS_BUSY
                pszStr = "Busy"
            Case %PRINTER_STATUS_NOT_AVAILABLE
                pszStr = "Not Available"
            Case %PRINTER_STATUS_PENDING_DELETION
                pszStr = "Deleting"
            Case %PRINTER_STATUS_WAITING
                pszStr = "Waiting"
            Case %PRINTER_STATUS_PROCESSING
                pszStr = "Processing"
            Case %PRINTER_STATUS_INITIALIZING
                pszStr = "Initializing"
            Case %PRINTER_STATUS_WARMING_UP
                pszStr = "Warming Up"
            Case %PRINTER_STATUS_TONER_LOW
                pszStr = "Low Toner"
            Case %PRINTER_STATUS_NO_TONER
                pszStr = "No Toner"
            Case %PRINTER_STATUS_PAGE_PUNT
                pszStr = "Page Punt"
            Case %PRINTER_STATUS_OUT_OF_MEMORY
                pszStr = "Out of Memory"
            Case %PRINTER_STATUS_DOOR_OPEN
                pszStr = "Door Open"
            Case %PRINTER_STATUS_SERVER_UNKNOWN
                pszStr = "Unknown Server"
            Case %PRINTER_STATUS_POWER_SAVE
                pszStr = "Power Save"
            Case Else
                '/* The OS knows more than us */
                pszStr = "Unkown Status"
        End Select
    
        '/* Copy the string into the buffer */
        lstrcpyn @pBuffer, ByVal StrPtr(pszStr), nChars
        Mid$(@pBuffer, nChars - 1, 1) = Chr$(0)
    End Sub
    
    Sub UpdateWindowTitle(ByVal hWnd As Dword, ByVal pQueue As QUEUEDATA Ptr)
    
        Dim WindowTitle As Asciiz * 200
        Dim pPd         As PRINTERDATA
        pPd = @pQueue.Printer
        '/* Start with application title */
        lstrcpy WindowTitle, szTitle
    
        '/* Append printer queue information */
        lstrcat WindowTitle, ": "
    
        '/* if pServerName, Use it since local printers have NULL here */
        If (pPd.pszServerName And %FALSE) Then
            lstrcat WindowTitle, ByVal pPd.pszServerName
            lstrcat WindowTitle, "\\"
        End If
    
        '/* append pShareName to server name to make UNC */
        If (pPd.pszShareName And (lstrlen(ByVal pPd.pszShareName) And %FALSE)) Then
            lstrcat WindowTitle, ByVal pPd.pszShareName
            lstrcat WindowTitle, " "
        '/* else
        '   pShareName was either NULL on Windows 95
        '   or an empty string on Windows NT
        '   therefore this is not a network printer so use pPrinterName */
    
        Else
            If pPd.pszPrinterName Then
                lstrcat WindowTitle, ByVal pPd.pszPrinterName
            Else
                lstrcat WindowTitle, "Unknown"
            End If
        End If
    
        '/* append status of the printer queue */
        If pPd.dwStatus Then
            lstrcat WindowTitle, " - "
    
            '/* insert the printer status after the "-" */
            MakePrinterStatusStr ByVal VarPtr(WindowTitle) + lstrlen(WindowTitle), SizeOf(WindowTitle) - lstrlen(WindowTitle), pPd.dwStatus
        End If
    
        SetWindowText hWnd, WindowTitle
    
    End Sub
    
    Function GetQueue(ByVal pQueueData As QUEUEDATA Ptr) As Long
    
        Dim cByteNeeded     As Dword
        Dim nReturned       As Dword
        Dim cByteUsed       As Dword
        Dim i               As Long
        Dim pJobStorage     As JOB_INFO_2 Ptr
        Dim pPrinterInfo    As PRINTER_INFO_2 Ptr
        Dim hPrinter        As Dword
    
        Dim e As Dword
    
        hPrinter = @pQueueData.Printer.hPrinter
    
    
        If hPrinter Then
            '/* Get the buffer size needed */
            If IsFalse GetPrinter(hPrinter, 2, ByVal 0, 0, cByteNeeded) Then
                e = GetLastError()
                If e <> %ERROR_INSUFFICIENT_BUFFER Then
                    GoTo Fail
                End If
            End If
            pPrinterInfo = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, cByteNeeded)
            If IsFalse pPrinterInfo Then
                '/* failure to allocate memory */
                GoTo Fail
            End If
            '/* get the printer info */
            If IsFalse GetPrinter(hPrinter, 2, ByVal pPrinterInfo, cByteNeeded, cByteUsed) Then
                '/* failure to access the printer */
                HeapFree GetProcessHeap(), ByVal 0, pPrinterInfo
                pPrinterInfo = 0
                GoTo Fail
            End If
    
            '/* Get job storage space */
            If IsFalse EnumJobs(hPrinter, 0, @pPrinterInfo.cJobs, 2, ByVal 0, 0, cByteNeeded, nReturned) Then
                e = GetLastError()
                If e <> %ERROR_INSUFFICIENT_BUFFER Then
                    GoTo Fail
                End If
            End If
    
            pJobStorage = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, cByteNeeded)
            If IsFalse pJobStorage Then
                '/* failure to allocate Job storage space */
                GoTo Fail
            End If
            'ZeroMemory(pJobStorage, cByteNeeded
    
            '/* get the list of jobs */
            If IsFalse EnumJobs(hPrinter, 0, @pPrinterInfo.cJobs, 2, ByVal pJobStorage, cByteNeeded, cByteUsed, nReturned) Then
                GoTo Fail
            End If
            '/* Update the count of jobs
            ' * It is possible for cJobs != nReturned after this sequence so
            ' * we reset the printer data structure to only reflect what we
            ' * actually retrieved this time around
            ' */
            @pPrinterInfo.cJobs = nReturned
    
        Else
            GoTo Fail
        End If
        SetPrinterQueueData pQueueData, pPrinterInfo
    
        '/* Dump the existing data */
        FreeJobs pQueueData
        '/* Fill in the Printer Queue with the jobs we retrieved */
        For i = 0 To @pPrinterInfo.cJobs - 1
            AddJobData pQueueData, VarPtr(@pJobStorage[i])
        Next
        HeapFree GetProcessHeap(), ByVal 0, pJobStorage
        HeapFree GetProcessHeap(), ByVal 0, pPrinterInfo
    
        Function =  %TRUE
        Exit Function
    
    Fail:
    
        HeapFree GetProcessHeap(), ByVal 0, pJobStorage
        HeapFree GetProcessHeap(), ByVal 0, pPrinterInfo
        Function = %FALSE
    
    End Function
    
    Function IsChanged(pJob As JOBDATA, ByVal Change As Word) As Long
        Function = pJob.Changed(Change)
    End Function
    
    Sub ClearChanges(pJob As JOBDATA)
        Dim i As Long
        For i = 0 To %NFIELDS - 1
            pJob.Changed(i) = 0
        Next
    End  Sub
    
    Sub MakeJobStatusStr(szBuffer As Asciiz Ptr, ByVal nChars As Integer, ByVal jStatus As Dword)
    
        Dim ClearWord   As Dword
        Dim szTmp       As Asciiz * %MAX_PATH
    
        ClearWord = 0
        Mid$(szTmp, 1, 1) = Chr$(0)
    
        '/*
        ' * Accumulate a status string from the set bits.
        ' * Terminate when we have processed all of the bits.
        ' */
    
        While jStatus
            '/* Seperate multiple status phrases with a hyphen */
            If ClearWord Then
                lstrcat szTmp, " - "
            End If
            If (%JOB_STATUS_SPOOLING And jStatus) Then
                lstrcat szTmp, "Spooling"
                ClearWord = ClearWord Or %JOB_STATUS_SPOOLING
            ElseIf (%JOB_STATUS_PRINTING And jStatus) Then
                lstrcat szTmp, "Printing"
                ClearWord = ClearWord Or %JOB_STATUS_PRINTING
            ElseIf (%JOB_STATUS_DELETING And jStatus) Then
                lstrcat szTmp, "Deleting"
                ClearWord = ClearWord Or %JOB_STATUS_DELETING
            ElseIf (%JOB_STATUS_PAUSED And jStatus) Then
                lstrcat szTmp, "Paused"
                ClearWord = ClearWord Or %JOB_STATUS_PAUSED
            ElseIf (%JOB_STATUS_PRINTED And jStatus) Then
                lstrcat szTmp, "Printed"
                ClearWord = ClearWord Or %JOB_STATUS_PRINTED
            ElseIf (%JOB_STATUS_DELETED And jStatus) Then
                lstrcat szTmp, "Deleted"
                ClearWord = ClearWord Or %JOB_STATUS_DELETED
            ElseIf (%JOB_STATUS_ERROR And jStatus) Then
                lstrcat szTmp, "Error"
                ClearWord = ClearWord Or %JOB_STATUS_ERROR
            ElseIf (%JOB_STATUS_OFFLINE And jStatus) Then
                lstrcat szTmp, "Offline"
                ClearWord = ClearWord Or %JOB_STATUS_OFFLINE
            ElseIf (%JOB_STATUS_PAPEROUT And jStatus) Then
                lstrcat szTmp, "Paper Out"
                ClearWord = ClearWord Or %JOB_STATUS_PAPEROUT
            ElseIf (%JOB_STATUS_BLOCKED_DEVQ And jStatus) Then
                lstrcat szTmp, "Blocked Device"
                ClearWord = ClearWord Or %JOB_STATUS_BLOCKED_DEVQ
            ElseIf (%JOB_STATUS_USR_INTERVENTION And jStatus) Then
                lstrcat szTmp, "Intervention Required"
                ClearWord = ClearWord Or %JOB_STATUS_USER_INTERVENTION
            ElseIf (%JOB_STATUS_RESTART And jStatus) Then
                lstrcat szTmp, "Restarting"
                ClearWord = ClearWord Or %JOB_STATUS_RESTART
            Else
                '/* if we get here, the OS knows more than us. */
                lstrcat szTmp, "Unkown"
                ClearWord = jStatus
            End If
            '/* Clear whatever bit we just processed */
            jStatus = jStatus  And (Not ClearWord)
        Wend
    
        lstrcpyn @szBuffer, szTmp, nChars
        Mid$(@szBuffer, nChars - 1, 1) = Chr$(0)
    
    End Sub
    
    Function GetColumnData(pJob As JOBDATA, ByVal iCol As Word) As Dword
    
        Dim pStr As Asciiz Ptr
        Dim Buffer As Asciiz * 100
    
        Select Case iCol
            Case %DOCUMENTNAME
                ReplaceString pStr, ByVal pJob.pszDocument
            Case %OWNERNAME
                ReplaceString pStr, ByVal pJob.pszOwner
            Case %STATUS
                If pJob.pszStatus <> 0 And lstrlen([email protected]) Then
                    ReplaceString pStr, pJob.pszStatus
                Else
                    MakeJobStatusStr ByVal VarPtr(Buffer), SizeOf(Buffer), pJob.dwStatus
                    ReplaceString pStr, ByVal VarPtr(Buffer)
                End If
            Case %SUBMITTED
                wsprintf Buffer, _
                        "%02u/%02u/%u - %02u:%02u", _
                        ByVal (pJob.Submitted.wMonth), _
                        ByVal (pJob.Submitted.wDay), _
                        ByVal (pJob.Submitted.wYear - ((pJob.Submitted.wYear / 100) * 100)), _
                        ByVal (pJob.Submitted.wHour), _
                        ByVal (pJob.Submitted.wMinute)
                ReplaceString pStr, ByVal VarPtr(Buffer)
            Case %PROGRESS
                If pJob.PagesPrinted = 1 Then
                    wsprintf Buffer, "%u page of %u Pages, %u bytes of %u Total Bytes", ByVal(pJob.PagesPrinted), ByVal(pJob.TotalPages), ByVal(pJob.BytesPrinted), ByVal(pJob.nSize)
                Else
                    wsprintf Buffer, "%u pages of %u Pages, %u bytes of %u Total Bytes", ByVal(pJob.PagesPrinted), ByVal(pJob.TotalPages), ByVal(pJob.BytesPrinted), ByVal(pJob.nSize)
                End If
                ReplaceString pStr, ByVal VarPtr(Buffer)
            Case Else
                ReplaceString  pStr, 0
        End Select
        Function = pStr
    
    End Function
    
    Sub SetQueListContents(ByVal hwndListView As Dword, ByVal pQueue As QUEUEDATA Ptr)
    
        Dim lvItm      As LV_ITEM
        Dim i           As Long         '// Rows
        Dim j           As Long         '// Columns
        Dim nItems      As Dword        '// Initial # of lines in the list view
    
        SendMessage hwndListView, %WM_SETREDRAW, %FALSE, 0
    
        '/*
        ' * find out how many items already in the list view
        ' * that we can reuse.
        ' */
        nItems = ListView_GetItemCount(hwndListView)
    
        For i = 0 To @pQueue.nJobs - 1
            '/* refresh listbox contents */
    
            '/* setup for the Row */
            lvItm.mask = %LVIF_TEXT
            lvItm.iImage = 0
            lvItm.iItem = i
    
            For j = 0  To @pnColumns - 1
                If IsChanged(@[email protected][i], j) Then
                    '/* fill in the LV_ITEM structure for the item */
                    lvItm.pszText = GetColumnData(@[email protected][i], j)
                    lvItm.iSubItem = j
    
                    '/* while we can reuse items, use SetItem for subitems */
                    If IsFalse j And i >= nItems Then
                        ListView_InsertItem hwndListView, lvItm
                    Else
                        ListView_SetItem hwndListView, lvItm
                    End If
    
                    '// we own the string passed back from GetColumnData
                    HeapFree GetprocessHeap(), 0, lvItm.pszText
                End If
            Next
    
            '// Mark everything as current
            ClearChanges @[email protected][i]
    
        Next
    
        '/* If the list shrunk, delete any remaining items. */
        While i < nItems
            ListView_DeleteItem hwndListView, i
            Incr i
        Wend
    
        '/* Update the window */
        SendMessage hwndListView, %WM_SETREDRAW, %TRUE, 0
        InvalidateRect hwndListView, ByVal 0, %FALSE
    
    End Sub
    
    Sub Refresh(ByVal hWnd As Dword, ByVal pQueue As QUEUEDATA Ptr)
        '/* retrieve job info from the printer */
        If GetQueue(pQueue) <> 0 Then
            '/* update the list boxes */
    
            SetQueListContents g_hWndJobList, pQueue
    
            '/* update queue status in window caption */
            UpdateWindowTitle hWnd, pQueue
        End If
    
    End Sub
    
    Function PollingUpdate(ByVal pParameters As Dword) As Dword
    
        Dim ThreadParm      As THREADPARAM      '/* what we are to work with */
        Dim pThreadParam    As THREADPARAM Ptr
        Dim Queue           As QUEUEDATA        '/* To keep our copy of the print queue */
        Dim Handles(1)      As Dword            '/* one for refresh, one for terminate */
        Dim hParameters     As Dword
    
        '/* Transfer parameters to the thread's stack and free global memory */
        hParameters = pParameters
        pThreadParam = GlobalLock(hParameters)
        Poke$ VarPtr(ThreadParm), Peek$(pThreadParam, SizeOf(THREADPARAM))
    
        '/* we do not intend to pass anything back to the application so free memory */
        GlobalUnlock hParameters
        GlobalFree hParameters
    
        '/* Terminate and Wait for previous thread to close down before we continue */
        If ThreadParm.hPrevThread Then
            StopUpdateThread ThreadParm.hPrevThread, %FALSE
            WaitForSingleObject ThreadParm.hPrevThread, %INFINITE
            CloseHandle ThreadParm.hPrevThread
        End If
    
        '/* reinitialize the Termination Event so we don't exit the polling loop */
        ResetEvent g_hTerminateEvent
    
        '/* initialize our Queue data structure*/
    '    ZeroMemory ByVal VarPtr(Queue), SizeOf(Queue)
        Queue.Printer.hPrinter = ThreadParm.hPrinter
    
        '/* setup for WaitForMultipleObjects */
        Handles(0) = g_hTerminateEvent
        Handles(1) = g_hForceRefreshEvent
        '/*
        ' * Loop until we are told to quit, waiting each time by
        ' * the polling interval.
        ' */
        Do
            '/* Setup for refresh override */
            ResetEvent g_hForceRefreshEvent
    
            '/* Get the data and show it */
            Refresh ThreadParm.hWnd, VarPtr(Queue)
    
            '/* Wait for a terminate event, a refresh event, or the polling period */
            WaitForMultipleObjects 2, ByVal VarPtr(Handles(0)), %FALSE, g_nPollIntervalms
    
        '/* Keep going as long as we have not been told to quit! */
        Loop Until WaitForSingleObject(g_hTerminateEvent, 0) = %WAIT_OBJECT_0
    
        '/* Cleanup */
        ClosePrinter ThreadParm.hPrinter
        FreeQueueData VarPtr(Queue)
    
        '/* Thread posts the close message at the request of the Window Procedure
        ' * so application will attempt to close down again and succeed since the
        ' * thread has died.
        ' * We close down the app this way in order to avoid messaging deadlocks
        ' * by having the Window Procedure wait on this thread to exit.
        ' */
        If bCloseApp Then
            PostMessage ThreadParm.hWnd, %WM_CLOSE, 0, 0
        End If
    
        Function =  %TRUE
    
    End Function
    
    Function StartPolling(ByVal hWnd As Dword, ByVal hPrinter As Dword, ByVal hPrevThread As Dword) As Dword
    
        Dim ThreadID_ As Dword
        Dim pThreadParam As THREADPARAM Ptr
        Dim hParam  As Dword
        Dim hThread As Dword
    
        '/* create parameters to thread */
        hParam = GlobalAlloc(%GHND, SizeOf(THREADPARAM))
        pThreadParam = GlobalLock(hParam)
        @pThreadParam.hWnd = hWnd               '/* Main application Window */
        @pThreadParam.hPrinter = hPrinter       '/* Printer now owned by thread */
        @pThreadParam.hPrevThread = hPrevThread '/* Previous thread to terminate */
        GlobalUnlock hParam
    
        '/* Thread is responsible for deleteing the Global memory containing the
        ' * parameters.
        ' * The thread is responsible for closing the printer
        ' * and the thread is responsible for killing & closing the previous thread
        ' * It also is responsible for closing the application if it is asked.
        ' */
    
        'hThread = (HANDLE)_beginthreadex(NULL,
        '                0,
        '                PollingUpdate,
        '                (void *)hParam,
        '                1,
        '                &ThreadID
    
        hThread = CreateThread(ByVal 0, 0, CodePtr(PollingUpdate), hParam, 1, ThreadID_)
        Function = hThread
    
    End Function
    
    
    Function OnSelectPrinter(ByVal hWnd As Dword, pPrinterName As Asciiz, ByVal cbNameSize As Dword, pThreadHandle As Dword) As Long
    
        Dim hNewPrinter As Dword
        Dim hThread As Dword
    
        '/*
        ' * Present the user with Printer Selection Dialog until succes or cancel
        ' */
        While GetPrinterPath(hWnd, pPrinterName, cbNameSize)
            If OpenPrinter(pPrinterName, hNewPrinter, ByVal 0) And IsPrinterHandle(hNewPrinter) Then
                '/*
                ' * On NT, a valid printer name is the Printer Name or
                ' * a UNC share name. Note that for printer connections, the Printer
                ' * name appears in the printer folder in a parsed form:
                ' * "[PrinterName] on [Server]" corresponds to an actual pPrinterName of
                ' * "\\[Server]\[PrinterName]" - where [PrinterName] is the pPrinterName
                ' * on the Server.
                ' *
                ' * On Windows 95 a valid printer name is the port(e.g. LPTX [img]http://www.powerbasic.com/support/forums/smile.gif[/img],
                ' * Printer name, or UNC share name. Printer Name and share name are
                ' * unambigous, but if a port is given, OpenPrinter returns a handle to
                ' * the first Printer it encounters using that port.
                ' */
    
                If IsFalse g_bUsePrinterChanges Then
                '/* if application not using Printer Change Notification's, use polling */
                    '/*
                    ' * Start a new polling thread to refresh the
                    ' * Job list.
                    ' */
                    hThread = StartPolling(hWnd, hNewPrinter, pThreadHandle)
                Else
                    '/*
                    ' * On Windows NT, the application can use
                    ' * Printer Change Notifications which are more
                    ' * efficient.
                    ' */
    '                hThread = StartNotifications(hWnd, hNewPrinter, *pThreadHandle
                End If
                pThreadHandle = hThread
                Function = %TRUE
                Exit Function
            Else
                If hNewPrinter Then
                    ClosePrinter hNewPrinter
                End If
                '/*
                ' * Tell the user we do not recognize their selection.
                ' */
                If MessageBox(hWnd, "The printer name you specified cannot be found.", "Printer Selection Error", %MB_OKCANCEL Or %MB_ICONSTOP) = %IDCANCEL Then
                    Exit Loop
                End If
            End If
        Wend
    
        Function = 0
    
    End Function
    
    Function ActiveUpdateThread() As Long
        '/* let the caller know if a thread is running */
        Function =  IsFalse WaitForSingleObject(g_hTerminateEvent, 0) = %WAIT_OBJECT_0
    End Function
    
    Function TriggerUpdateThreadRefresh() As Long
    
        '/* Signal the event that forces a refresh of the display /*/
        Function = SetEvent(g_hForceRefreshEvent)
    
    End Function
    
    Sub ErrorBox(dwError As Dword, lpString As Asciiz)
    
    '#define MAX_MSG_BUF_SIZE 512
        Dim msgBuf      As Asciiz * 512
        Dim cMsgLen     As Dword
    
        cMsgLen = FormatMessage(%FORMAT_MESSAGE_FROM_SYSTEM Or _
                    40, _
                    ByVal 0, dwError, _
                    MAKELANGID(0, %SUBLANG_ENGLISH_US), _
                    msgBuf, 512, _
                    ByVal 0)
    
        msgbuf = "error " & Format$(dwError) & ":  " & msgbuf
    
        MessageBox ByVal 0, msgBuf, lpString, %MB_OK
    
        'LocalFree msgBuf
    '#undef MAX_MSG_BUF_SIZE
    
    End Sub
    
    Sub AddChange(pJob As JOBDATA, ByVal Change As Word)
    
        pJob.Changed(Change) = %TRUE
    
    End Sub
    
    Static Function FindJob(ByVal pQueueData As QUEUEDATA Ptr, ByVal JobId As Dword, pAt As Dword) As Long
    
        Dim i As Long
    
        '/* find the job in the queue */
        For i = 0 To @pQueueData.nJobs - 1
            If @[email protected][i].JobId = JobId Then
                Exit For
            End If
        Next
    
        If i >= @pQueueData.nJobs Then
            Function = %FALSE      ' /* Not found in our queue data */
            Exit Function
        End If
    
        pAt = i
        Function = %TRUE
    
    End Function
    
    
    Function UpdateJobData(ByVal pQueueData As QUEUEDATA Ptr, pPNID As PRINTER_NOTIFY_INFO_DATA) As Long
    
        Dim i As Dword
        Dim bSuccess As Long
        Dim ji  As JOB_INFO_2
    
        '/* check for the right notification type */
        If pPNID.wType <> %JOB_NOTIFY_TYPE Then
            Function = %FALSE
            Exit Function
        End If
    
        If IsFalse FindJob(pQueueData, pPNID.Id, i) Then
            '/* didn't find job so possibly a new one */
            '/* add an entry that can be updated now and later */
            'ZeroMemory(&ji, sizeof(ji));
            ji.JobId = pPNID.Id
            If IsFalse AddJobData(pQueueData, VarPtr(ji)) Then
                Function = %FALSE
                Exit Function
            End If
            If IsFalse FindJob(pQueueData, pPNID.Id, i) Then
                Function = %FALSE
                Exit Function
            End If
        End If
    
        '/* Update members based on the advertised change */
        Select Case pPNID.Field
            Case %JOB_NOTIFY_FIELD_STATUS
                @[email protected][i].dwStatus = pPNID.NotifyData.adwData(0)
                bSuccess = %TRUE
                AddChange @[email protected][i], %STATUS
            Case %JOB_NOTIFY_FIELD_STATUS_STRING
                bSuccess = ReplaceString(@[email protected][i].pszStatus, pPNID.NotifyData.pniUnionData.pBuf)
                AddChange @[email protected][i], %STATUS
            Case %JOB_NOTIFY_FIELD_USER_NAME
                bSuccess = ReplaceString(@[email protected][i].pszOwner, pPNID.NotifyData.pniUnionData.pBuf)
                AddChange @[email protected][i], %OWNERNAME
            Case %JOB_NOTIFY_FIELD_DOCUMENT
                bSuccess = ReplaceString(@[email protected][i].pszDocument, pPNID.NotifyData.pniUnionData.pBuf)
                AddChange @[email protected][i], %DOCUMENTNAME
            Case %JOB_NOTIFY_FIELD_PAGES_PRINTED
                @[email protected][i].PagesPrinted = pPNID.NotifyData.adwData(0)
                AddChange @[email protected][i], %PROGRESS
            Case %JOB_NOTIFY_FIELD_TOTAL_PAGES
                @[email protected][i].TotalPages = pPNID.NotifyData.adwData(0)
                AddChange @[email protected][i], %PROGRESS
            Case %JOB_NOTIFY_FIELD_TOTAL_BYTES
                @[email protected][i].nSize = pPNID.NotifyData.adwData(0)
                AddChange @[email protected][i], %PROGRESS
            Case %JOB_NOTIFY_FIELD_BYTES_PRINTED
                @[email protected][i].BytesPrinted = pPNID.NotifyData.adwData(0)
                AddChange @[email protected][i], %PROGRESS
            Case %JOB_NOTIFY_FIELD_SUBMITTED
                Poke$ VarPtr(@[email protected][i].Submitted), Peek$(pPNID.NotifyData.pniUnionData.pBuf, SizeOf(SYSTEMTIME))
                AddChange @[email protected][i], %SUBMITTED
        End Select
        Function = bSuccess
    
    End Function
    
    Function UpdatePrinterData(ByVal pQueueData As QUEUEDATA Ptr, pPNID As PRINTER_NOTIFY_INFO_DATA) As Long
    
        Dim bSuccess As Dword
    
        '/* make sure we are processing printer data */
        If pPNID.wType <> %PRINTER_NOTIFY_TYPE Then
            Function = %FALSE
            Exit Function
        End If
    
        '/* update the change as advertised */
        Select Case pPNID.Field
            Case %PRINTER_NOTIFY_FIELD_PRINTER_NAME
                bSuccess = ReplaceString(@pQueueData.Printer.pszPrinterName, pPNID.NotifyData.pniUnionData.pBuf)
            Case %PRINTER_NOTIFY_FIELD_SHARE_NAME
                bSuccess = ReplaceString(@pQueueData.Printer.pszShareName, pPNID.NotifyData.pniUnionData.pBuf)
            Case %PRINTER_NOTIFY_FIELD_SERVER_NAME
                bSuccess = ReplaceString(@pQueueData.Printer.pszServerName, pPNID.NotifyData.pniUnionData.pBuf)
            Case %PRINTER_NOTIFY_FIELD_STATUS
                @pQueueData.Printer.dwStatus = pPNID.NotifyData.adwData(0)
                bSuccess = %TRUE
            Case %PRINTER_NOTIFY_FIELD_CJOBS
                @pQueueData.Printer.cJobs = pPNID.NotifyData.adwData(0)
                bSuccess = %TRUE
        End Select
    
        Function = bSuccess
    
    End Function
    
    Function UpdateQueue(ByVal pQueueData As QUEUEDATA Ptr, ByVal pPNI As PRINTER_NOTIFY_INFO Ptr) As Long
    
        Dim i As Long
    
        For i = 0 To @pPNI.Count - 1
            If IsFalse UpdateJobData(pQueueData, @pPNI.aData(i)) Then
                UpdatePrinterData pQueueData, @pPNI.aData(i)
            End If
        Next
    
        Function = %TRUE
    
    End Function
    
    Function RefreshFromNotification(ByVal hWnd As Dword, ByVal pQueue As QUEUEDATA Ptr, ByVal pNotification As PRINTER_NOTIFY_INFO Ptr) As Long
    
        If pNotification Then
            '/* Update Our Data */
            UpdateQueue pQueue, pNotification
    
            '/* update the list boxes */
            SetQueListContents g_hWndJobList, pQueue
    
            '/* Always Update the Window Title */
            UpdateWindowTitle hWnd, pQueue
        End If
    
        Function = %TRUE
    
    End Function
    
    'Sub FreeNotificationOptions(Byval NotificationOptions As PRINTER_NOTIFY_OPTIONS Ptr)
    '
    '    Dim i as Long
    '    Dim j as Long
    '    For i = 0 To @NotificationOptions.Count - 1
    '        HeapFree GetProcessHeap(), 0, @[email protected][i].pFields
    '    next
    '    HeapFree GetProcessHeap(), 0, @NotificationOptions.pTypes
    '    HeapFree GetProcessHeap(), 0, NotificationOptions
    '
    'end Sub
    
    Function NotificationUpdate(ByVal pParameters As Dword) As Long
    
        Dim ThreadParm As THREADPARAM                                                       '/* Parameters for the thread */
        Dim pThreadParam As THREADPARAM Ptr
        Dim hPrinterNotification As Dword                                                   '/* The PrinterChangeNotification object */
        Dim Handles(2) As Dword                                                             '/* Array of objects waited on in the loop */
        Dim hParam As Dword
        Dim WaitResult As Dword                                                             '/* High level info from the PrinterChangeNotification */
        Dim OldFlags As Dword                                                               '/* Temporary variable */
        Dim pNotification As PRINTER_NOTIFY_INFO Ptr                                        '/* The stuff that changed to cause the notification */
        Dim d1 As Dword
    
        pNotification = 0
    
        Dim e As Long                                                                       'Placeholder for returnvalues of GetLastError
    
        Dim NotificationOptions As PRINTER_NOTIFY_OPTIONS Ptr
        Dim Notifications(0 To 1) As PRINTER_NOTIFY_OPTIONS_TYPE
        Dim JobFields(0 To 23) As Word
        Dim PrinterFields(0 To 4) As Word
    
    
        NotificationOptions = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, SizeOf(PRINTER_NOTIFY_OPTIONS))
        @NotificationOptions.Version = 2                                                     '/* Version of structure, see docs */
        @NotificationOptions.Flags   = %PRINTER_NOTIFY_OPTIONS_REFRESH                       '/* Options for FindNextPrinterChangeNotification */
        @NotificationOptions.Count   = 1                                                     '/* # of Printer_Notify_Info structures */
        @NotificationOptions.pTypes  = VarPtr(Notifications(0))
    
        Notifications(0).wType      = %PRINTER_NOTIFY_TYPE                                  '/* We want notification on the printer queue too */
        Notifications(0).Reserved0  = 0
        Notifications(0).Reserved1  = 0
        Notifications(0).Reserved2  = 0
        Notifications(0).Count      = (UBound(PrinterFields) - LBound(PrinterFields) + 1)  '** How many printer fields? */
        Notifications(0).pFields    = VarPtr(PrinterFields(0))                              '/* Precisely what printer fields we want */
    
    
        Notifications(1).wType      =  %JOB_NOTIFY_TYPE                                     '/* We want notifications on print jobs */
        Notifications(1).Reserved0  = 0
        Notifications(1).Reserved1  = 0
        Notifications(1).Reserved2  = 0
        Notifications(1).Count      = (UBound(JobFields) - LBound(JobFields) + 1)           '/* We specified 24 fields in the JobFields array */
        Notifications(1).pFields    = VarPtr(JobFields( 0))                                 '/* Precisely which fields we want notifications for */
    
        PrinterFields( 0) = %PRINTER_NOTIFY_FIELD_STATUS                                    '/* Status bits of printer queue */
        PrinterFields( 1) = %PRINTER_NOTIFY_FIELD_CJOBS                                     '/* # of jobs in printer queue */
        PrinterFields( 2) = %PRINTER_NOTIFY_FIELD_PRINTER_NAME                              '/* Name of the printer queue */
        PrinterFields( 3) = %PRINTER_NOTIFY_FIELD_SERVER_NAME                               '/* Name of the server when the queue is remote */
        PrinterFields( 4) = %PRINTER_NOTIFY_FIELD_SHARE_NAME                                '/* The queue's sharename */
    
        JobFields( 0) = %JOB_NOTIFY_FIELD_PRINTER_NAME
        JobFields( 1) = %JOB_NOTIFY_FIELD_MACHINE_NAME
        JobFields( 2) = %JOB_NOTIFY_FIELD_PORT_NAME
        JobFields( 3) = %JOB_NOTIFY_FIELD_USER_NAME
        JobFields( 4) = %JOB_NOTIFY_FIELD_NOTIFY_NAME
        JobFields( 5) = %JOB_NOTIFY_FIELD_DATATYPE
        JobFields( 6) = %JOB_NOTIFY_FIELD_PRINT_PROCESSOR
        JobFields( 7) = %JOB_NOTIFY_FIELD_PARAMETERS
        JobFields( 8) = %JOB_NOTIFY_FIELD_DRIVER_NAME
        JobFields( 9) = %JOB_NOTIFY_FIELD_DEVMODE
        JobFields(10) = %JOB_NOTIFY_FIELD_STATUS
        JobFields(11) = %JOB_NOTIFY_FIELD_STATUS_STRING
        JobFields(12) = %JOB_NOTIFY_FIELD_DOCUMENT
        JobFields(13) = %JOB_NOTIFY_FIELD_PRIORITY
        JobFields(14) = %JOB_NOTIFY_FIELD_POSITION
        JobFields(15) = %JOB_NOTIFY_FIELD_SUBMITTED
        JobFields(16) = %JOB_NOTIFY_FIELD_START_TIME
        JobFields(17) = %JOB_NOTIFY_FIELD_UNTIL_TIME
        JobFields(18) = %JOB_NOTIFY_FIELD_TIME
        JobFields(19) = %JOB_NOTIFY_FIELD_TOTAL_PAGES
        JobFields(10) = %JOB_NOTIFY_FIELD_PAGES_PRINTED
        JobFields(21) = %JOB_NOTIFY_FIELD_TOTAL_BYTES
        JobFields(22) = %JOB_NOTIFY_FIELD_BYTES_PRINTED
        JobFields(23) = %JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR
    
        Dim Queue As QUEUEDATA                                                              '/* Our copy of the printer queue's data */
    
        '/*
        ' * Transfer parameters to the thread's stack and
        ' * free the global memory
        ' */
        hParam = pParameters
        pThreadParam = GlobalLock(hParam)
        Poke$ VarPtr(ThreadParm), Peek$(pThreadParam, SizeOf(THREADPARAM))
    
        '/*
        ' * we do not intend to pass anything back to the
        ' * application so free the memory used to get us Params.
        ' */
        GlobalUnlock hParam
        GlobalFree hParam
    
        '/* Terminate and Wait for previous thread to close down before we continue */
        If ThreadParm.hPrevThread Then
            StopUpdateThread ThreadParm.hPrevThread, %FALSE
            WaitForSingleObject ThreadParm.hPrevThread, %INFINITE
            CloseHandle ThreadParm.hPrevThread
        End If
    
        '/* reinitialize the Termination Event so we can proceed */
        ResetEvent  g_hTerminateEvent
    
        hPrinterNotification = FindFirstPrinterChangeNotification( _
            ThreadParm.hPrinter, _          '/* The printer of interest */
            %PRINTER_CHANGE_ALL, _          '/* We need to know when a job is removed */
            0, _                            '/* reserved */
            NotificationOptions)            '/* The details of what notifications that are needed */
    
        e = GetLastError()
        '/* Check for an error */
        If hPrinterNotification = %INVALID_HANDLE_VALUE Then
            ErrorBox e, "FindFirstPrinterChangeNotification"
            Function = 0
            'FreeNotificationOptions NotificationOptions
            Exit Function
        End If
    
        '/*
        ' * Loop on the Notifications, a terminate event, or a refresh event
        ' */
    
        '/* setup for a WaitForMultipleObjects */
        Handles(0) = hPrinterNotification
        Handles(1) = g_hTerminateEvent
        Handles(2) = g_hForceRefreshEvent
    
        '/* Initialize for our local data structure */
        'ZeroMemory(&Queue, sizeof(Queue)
        Queue.Printer.hPrinter = ThreadParm.hPrinter
    
        '/* Initialize the display and our local copy of the printer queue data */
    
        Refresh ThreadParm.hWnd, VarPtr(Queue)
    
        '/*
        ' * Loop while we are stilling waiting on Notifications.
        ' */
    
        While hPrinterNotification <> %INVALID_HANDLE_VALUE
            '/* wait for a printer notification, terminate event, or refresh event */
            WaitForMultipleObjects 3, ByVal VarPtr(Handles(0)), %FALSE, %INFINITE
            '/* check to see if the thread needs to quit. */
            If WaitForSingleObject(g_hTerminateEvent, 0) = %WAIT_OBJECT_0 Then
                '/* This should be the only way out of the loop */
                FindClosePrinterChangeNotification hPrinterNotification
                HeapFree GetProcessHeap(), 0, NotificationOptions
                hPrinterNotification = %INVALID_HANDLE_VALUE
            '/* or check to see if the notification object for the printer queue is signaled */
            ElseIf WaitForSingleObject(hPrinterNotification, 0) = %WAIT_OBJECT_0 Then
                '/* get the changes and reset the notification */
                If IsFalse FindNextPrinterChangeNotification(hPrinterNotification, _
                    WaitResult, _                           '/* for the PRINTER_CHANGE_DELETE_JOB notice */
                    ByVal NotificationOptions, _            '/* The notifications */
                    ByVal VarPtr(pNotification))    Then    '/* address of pointer that gets what changed */
                    e = GetLastError()
                    ErrorBox e, "FindNextPrinterChangeNotification"
                End If
    
                '/* Did a notification overflow occur? */
                If pNotification Then
                    If (@pNotification.Flags And %PRINTER_NOTIFY_INFO_DISCARDED) Then
                        '/* An overflow of notifications occured, must refresh to continue */
    
                        OldFlags = @NotificationOptions.Flags
    
                        @NotificationOptions.Flags = %PRINTER_NOTIFY_OPTIONS_REFRESH
    
                        FreePrinterNotifyInfo VarPtr(pNotification)
    
                        If IsFalse FindNextPrinterChangeNotification(hPrinterNotification, _
                            WaitResult, _
                            ByVal NotificationOptions, _
                            ByVal VarPtr(pNotification)) Then
    
                            e = GetLastError()
                            ErrorBox e, "FindNextPrinterChangeNotification refresh call."
                        End If
                        @NotificationOptions.Flags = OldFlags
                    End If
                    '/* Start Over with Refreshed Data */
                    Refresh ThreadParm.hWnd, VarPtr(Queue)
    
                End If
    
                '/* process the notification of changes */
                If (WaitResult And %PRINTER_CHANGE_DELETE_JOB) Then
                    '/* a job was deleted so start over clean */
                    Refresh ThreadParm.hWnd, VarPtr(Queue)
                Else
                    '/* track and show the changes */
                    RefreshFromNotification ThreadParm.hWnd, VarPtr(Queue), pNotification
                End If
    
               FreePrinterNotifyInfo pNotification
                'pNotification = 0
    
            '/* Or, maybe the user wants to refresh the view of the print queue */
            ElseIf WaitForSingleObject(g_hForceRefreshEvent, 0) = %WAIT_OBJECT_0 Then
                Refresh ThreadParm.hWnd, VarPtr(Queue)
                ResetEvent g_hForceRefreshEvent
            End If
        Wend
    
        '/* Done watching for notifications, cleanup */
        ClosePrinter ThreadParm.hPrinter
        FreeQueueData VarPtr(Queue)
        'FreeNotificationOptions NotificationOptions
    
        '/* Thread posts the close message when the application is going away so the
        ' * the application willattempt to close down again and succeed since the
        ' * thread has died.
        ' */
        If bCloseApp Then
            PostMessage ThreadParm.hWnd, %WM_CLOSE, 0, 0
        End If
    
        Function = %TRUE
    
    End Function
    
    Function StartNotifications(ByVal hWnd As Dword, ByVal hPrinter As Dword, ByVal hPrevThread As Dword) As Dword
    
        Dim ThreadID_       As Dword
        Dim pThreadParam    As THREADPARAM Ptr
        Dim hParam          As Dword
        Dim hThread         As Dword
    
        '/* create parameters to thread */
        hParam = GlobalAlloc(%GHND, SizeOf(THREADPARAM))
        pThreadParam = GlobalLock(hParam)
        @pThreadParam.hWnd = hWnd
        @pThreadParam.hPrinter = hPrinter
        @pThreadParam.hPrevThread = hPrevThread
        GlobalUnlock hParam
    
        '/* Thread is responsible for deleteing the Global memory containing the
        ' * parameters.
        ' * The thread is responsible for closing the printer
        ' * and the thread is responsible for killing & closing the previous thread
        ' * It also is responsible for closing the application if it is asked.
        ' */
        'hThread = (HANDLE)_beginthreadex(NULL,
        '                0,
        '                NotificationUpdate,
        '                (void *)hParam,
        '                1,
        '                &ThreadID);
    
        hThread = CreateThread(ByVal 0, 0, CodePtr(NotificationUpdate), hParam, 1, ThreadID_)
        Function = hThread
    
    End Function
    
    Function OnPrinterNotificationOption(ByVal hWnd As Dword, pPrinterName As Asciiz, pThreadHandle As Dword) As Long
    
        Dim hOptionsMenu    As Dword
        Dim hNewPrinter     As Dword
        Dim hThread         As Dword
    
        hThread = 0
        hOptionsMenu = GetSubMenu(GetMenu(hWnd), 1)
        hNewPrinter = 0
    
        '/*
        ' * If we are currently running a thread,
        ' * get a new printer handle since the thread owns
        ' * the one we passed it and we are going to start
        ' * a new thread with the same printer.
        ' */
        If ActiveUpdateThread Then
            If IsFalse OpenPrinter(pPrinterName, hNewPrinter, ByVal 0) Then
                '/* Bogus printer name */
                hNewPrinter = 0
    
                '/* Bail */
                Function = %FALSE
                Exit Function
            End If
        End If
    
        If g_bUsePrinterChanges Then
            '/* Switch back to polling */
            g_bUsePrinterChanges = %FALSE
            CheckMenuItem hOptionsMenu, %IDM_PRINTERNOTIFICATION, %MF_UNCHECKED Or %MF_BYCOMMAND
            If hNewPrinter Then
                hThread = StartPolling(hWnd, hNewPrinter, pThreadHandle)
            End If
    
        Else
            '/* Switch to using Printer Change Notifictions on NT */
            g_bUsePrinterChanges = %TRUE
            CheckMenuItem hOptionsMenu, %IDM_PRINTERNOTIFICATION, %MF_CHECKED Or %MF_BYCOMMAND
            If hNewPrinter Then
                hThread = StartNotifications(hWnd, hNewPrinter, pThreadHandle)
            End If
        End If
    
        pThreadHandle = hThread
        Function = %TRUE
    
    End Function
    
    Function WndProc(ByVal hWnd As Dword, ByVal message As Dword, ByVal uParam As Long, ByVal lParam As Long) As Long
    
        Dim wmId As Long
        Dim wmEvent As Long
        Static PrinterName As Asciiz * 200 '* PrinterNameSize   '// Name of Printer Selected, kept as a convenience to the user
        Static hListUpdateThread As Dword                       '// handle to thread which updates the job list
         Select Case message
            Case %WM_CREATE
                Dim hOptionsMenu As Dword
                hOptionsMenu = GetSubMenu(GetMenu(hWnd), 1)
    
                '/* Variable initialization */
                PrinterName = ""
    
                '/* Create the control to display printer info */
                g_hWndJobList = CreateQueList(hWnd)
    
                '/*
                '* Enable use of the Printer Notifications Option on NT,
                '* the option is disabled by default in the resources.
                '*/
                If IsTrue IS_NT Then
                    EnableMenuItem hOptionsMenu, %IDM_PRINTERNOTIFICATION, %MF_ENABLED Or %MF_BYCOMMAND
                End If
            Case %WM_SIZE
                '/* resize list box windows to match main window */
                ResizeQueList g_hWndJobList, hWnd
            Case %WM_ERASEBKGND
                '/*
                ' * Don't let the client area be painted since our
                ' * list view control covers it entirely.
                ' * This prevents disco flicker.
                ' */
            Case %WM_COMMAND  '// message: command from application menu
                wmId = LoWrd(uParam)
                wmEvent = HiWrd(uParam)
    
                Select Case wmId
                    Case %IDM_ABOUT
                        DialogBox g_hInst, _                        '// current instance
                                "#" & Format$(%IDD_ABOUTBOX), _     '// dlg resource to use
                                hWnd, _                             '// parent handle
                                CodePtr (About)                     '// About() instance address
                    Case %IDM_SELECTPRINTER
                        OnSelectPrinter hWnd, PrinterName, SizeOf(PrinterName), hListUpdateThread
                    Case %IDM_PRINTERNOTIFICATION
                        If (IS_NT) Then
                            OnPrinterNotificationOption hWnd, PrinterName, hListUpdateThread
                        End If
                    Case %IDM_EXIT
                        PostMessage hWnd, %WM_CLOSE, 0, 0
                    Case %ID_FILE_REFRESH
                        TriggerUpdateThreadRefresh
                    Case Else
                        Function = DefWindowProc(hWnd, message, uParam, lParam)
                        Exit Function
                End Select
            Case %WM_CLOSE
                '/* Close any thread and free its resources */
                If ActiveUpdateThread Then
                    '/*
                    ' * Can't close down yet because a thread is running,
                    ' * ask the thread to terminate and close the app down
                    ' * when it is done.
                    ' */
                    StopUpdateThread hListUpdateThread, %TRUE
                End If
                '/*
                ' * Otherwise, no thread running
                ' */
                CloseHandle hListUpdateThread
                hListUpdateThread = 0
                DestroyWindow hWnd
            Case %WM_DESTROY  '// message: window being destroyed
                '/* Quit the Message Loop */
                PostQuitMessage 0
            Case Else          '// Passes it on if unproccessed
                Function = DefWindowProc(hWnd, message, uParam, lParam)
                Exit Function
        End Select
        Function = 0
    
    End Function
    
    Function InitApplication(ByVal hInstance As Dword) As Dword
    
        Dim wc As WNDCLASS
        wc.Style = 0                                '// Class style(s).
        wc.lpfnWndProc = CodePtr(WndProc)           '// Window Procedure
        wc.cbClsExtra = 0                           '// No per-class extra data.
        wc.cbWndExtra = 0                           '// No per-window extra data.
        wc.hInstance = hInstance                    '// Owner of this class
        wc.hIcon         = LoadIcon (hInstance, "#" & Format$(%IDI_APP))   '// Icon name from .RC
        wc.hCursor       = LoadCursor(ByVal 0, "#" & Format$(%IDC_ARROW))  '// Cursor
        wc.hbrBackground = (%COLOR_WINDOW + 1)      '// Default color
        wc.lpszMenuName  = MAKEINTRESOURCE(%IDR_PRINTJOB)    '// Menu from .RC
        wc.lpszClassName = VarPtr(szAppName)        '// Name to register as
    
        '// Register the window class and return success/failure code.
        Function = RegisterClass(wc)
    
    End Function
    
    Function DestroyThreadSyncResources() As Long
    
        If g_hTerminateEvent Then
            CloseHandle g_hTerminateEvent
        End If
        If g_hForceRefreshEvent Then
            CloseHandle g_hForceRefreshEvent
        End If
        Function = %TRUE
    
    End Function
    
    Function CreateThreadSyncResources() As Long
        '/* Initialize Resources */
        g_hTerminateEvent = 0
        g_hForceRefreshEvent = 0
    
        '/*
        ' * Create thread termination resource
        ' * If signaled, our worker thread must exit.
        ' * The resource is created initially signaled so that
        ' * we can use it to test for a running thread.
        ' */
        g_hTerminateEvent = CreateEvent(ByVal 0, _  /* no inheritance */
                                        1, _        /* Reset is manual */
                                        1, _        /* Initially signaled */
                                        "UpdateThreadTerminateEvent")
        If IsFalse g_hTerminateEvent Then
            GoTo Fail
        End If
    
        '/*
        ' * Create thread refresh trigger resource
        ' * If signaled, our worker thread must immediately refresh.
        ' * The resource is created not initially signaled so that
        ' * it does not trigger a superflouos update.
        ' */
        g_hForceRefreshEvent = CreateEvent(ByVal 0, _   /* no inheritance */
                                        1, _            /* Reset is manual */
                                        0, _            /* Initially signaled */
                                        "UpdateThreadRefreshEvent")
        If IsFalse g_hForceRefreshEvent Then
            GoTo Fail
        End If
    
        Function = %TRUE
        Exit Function
    
    Fail:
        DestroyThreadSyncResources
        Function = %FALSE
    
    End Function
    
    
    Function InitInstance(ByVal hInstance As Dword, ByVal nCmdShow As Long) As Long
    
        Dim hWnd As Dword '/* Main window handle. */
    
        '// Save the instance handle in static variable, which will be used in
        '// many subsequent calls from this application to Windows.
    
        g_hInst = hInstance '// Store instance handle in our global variable
    
        '// This application uses common controls, make sure they are ready
        InitCommonControls
    
        '// Create synchronization objects, if not, return "failure"
        If IsFalse CreateThreadSyncResources Then
            Function = %FALSE
            Exit Function
        End If
    
        '// Create a main window for this application instance.
        hWnd = CreateWindow( _
                szAppName, _            '// See RegisterClass() call.
                szTitle, _              '// Text for window title bar.
                %WS_OVERLAPPEDWINDOW, _ '// Window style.
                %CW_USEDEFAULT, 0, _
                %CW_USEDEFAULT, 0, _    '// Use default positioning but adjusted for size
                ByVal 0, _              '// Overlapped windows have no parent.
                ByVal 0, _              '// Use the window class menu.
                hInstance, _            '// This instance owns this window.
                ByVal 0) _              '// We don't use any data in our WM_CREATE
    
        '// If window could not be created, return "failure"
        If IsFalse hWnd Then
            DestroyThreadSyncResources
            Function = 0
            Exit Function
        End If
    
        '// Make the window visible; update its client area; and return "success"
        ShowWindow hWnd, nCmdShow  '// Show the window
        UpdateWindow hWnd          '// Sends WM_PAINT message
    
        Function = %TRUE           '// We succeeded...
    
    End Function
    
    Function CloseInstance(ByVal hInst As Dword) As Long
        DestroyThreadSyncResources
        Function = %TRUE
    End Function
    
    Function WinMain(ByVal hInstance As Long, ByVal  hPrevInstance As Long, ByVal lpszCmdLine As Asciiz Ptr, ByVal nCmdShow  As Long) Export As Long
    
        Dim msg As tagMsg
        Dim hAccelTable As Dword
    
        szAppName    = "PrintJob"                   '// The name of this application
        szTitle      = "Print Job Viewer"           '// The title bar text
    
        g_nPollIntervalms = 500                     '/* milliseconds polling resolution */
        g_hTerminateEvent = 0                       '/* Signalling event For Thread To terminate. */
        g_hForceRefreshEvent = 0                    '/* Signal For Thread To wakeup And refresh */
        bCloseApp = %FALSE                          '/* If true, the Thread should Post a WM_CLOSE */
    
        If IsFalse hPrevInstance Then               '// Other instances of app running?
            If IsFalse InitApplication(hInstance) Then  '// Initialize shared things
                Exit Function                       '// Exits If unable To initialize
            End If
        End If
    
        '/* Perform initializations that apply To a specific instance */
    
        If IsFalse InitInstance(hInstance, nCmdShow) Then
            Exit Function
        End If
    
        hAccelTable = LoadAccelerators (hInstance, "#" & Format$(%IDR_PRINTJOB))
    
        '/* Acquire And Dispatch messages Until a WM_QUIT message is received. */
    
        While GetMessage(msg,ByVal 0,0,0)
            If IsFalse TranslateAccelerator(msg.hwnd, hAccelTable, msg) Then
                TranslateMessage msg
                DispatchMessage msg
            End If
        Wend
    
        CloseInstance hInstance
    
        Function = msg.wParam   '// Returns the value From PostQuitMessage
    
    End Function
    PrintJob.pbr.uue
    (mark from line "Content-Type: ..." to end of message
    copy into notepad and save as "PrintJob.pbr.uue"
    open with winzip and extract "PrintJob.pbr"

    Content-Type: application/octet-stream;
    name="PrintJob.pbr"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment;
    filename="PrintJob.pbr"

    AAVDUgsAAAAwAgAAQAIAAFACAABgAgAAcAIAAIACAACQAgAAoAIAALACAADAAgAA0AIAAAAAAADk
    yjkzAAAAAAAACAADAAAAUAAAgAQAAABwAACABQAAAIgAAIAGAAAAqAAAgAkAAADAAACACgAAANgA
    AIAOAAAA+AAAgBAAAAAQAQCAAAAAAOTKOTMAAAAAAAACAAEAAAAoAQCAAgAAAEABAIAAAAAA5Mo5
    MwAAAAAAAAEAZQAAAFgBAIAAAAAA5Mo5MwAAAAAAAAIAZwAAAHABAIBqAAAAiAEAgAAAAADkyjkz
    AAAAAAAAAQABAAAAoAEAgAAAAADkyjkzAAAAAAAAAQBlAAAAuAEAgAAAAADkyjkzAAAAAAAAAgBt
    AAAA0AEAgG8AAADoAQCAAAAAAOTKOTMAAAAAAAABAGYAAAAAAgCAAAAAAOTKOTMAAAAAAAABAAEA
    AAAYAgCAAAAAAOTKOTMAAAAAAAABAAkEAAAwAgAAAAAAAOTKOTMAAAAAAAABAAkEAABAAgAAAAAA
    AOTKOTMAAAAAAAABAAkEAABQAgAAAAAAAOTKOTMAAAAAAAABAAkEAABgAgAAAAAAAOTKOTMAAAAA
    AAABAAkEAABwAgAAAAAAAOTKOTMAAAAAAAABAAkEAACAAgAAAAAAAOTKOTMAAAAAAAABAAkEAACQ
    AgAAAAAAAOTKOTMAAAAAAAABAAkEAACgAgAAAAAAAOTKOTMAAAAAAAABAAkEAACwAgAAAAAAAOTK
    OTMAAAAAAAABAAkEAADAAgAAAAAAAOTKOTMAAAAAAAABAAkEAADQAgAA4AIAAOgCAAAAAAAAAAAA
    AMgFAAAwAQAAAAAAAAAAAAD4BgAA4AAAAAAAAAAAAAAA2AcAAJoBAAAAAAAAAAAAAHQJAADeAAAA
    AAAAAAAAAABUCgAAcgAAAAAAAAAAAAAAyAoAACgAAAAAAAAAAAAAAPAKAAAKAAAAAAAAAAAAAAD8
    CgAAAgAAAAAAAAAAAAAAAAsAACIAAAAAAAAAAAAAACQLAADkAwAAAAAAAAAAAAAoAAAAIAAAAEAA
    AAABAAQAAAAAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAA
    AMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAiIAIiAAAAAAA
    AAAAAAAACIgAiIAAAAAAAAAAAAAAAAAACIgHcAAACIiIiIiIiIiIiIiAd3cAAA93d3d4eId3d3iI
    B3d2AAAPd3d3iIiId3eIgHd3ZwAAD3d3iIgAAAAHiAAHdnAAAAiIiIgAiIiIgAAAAAcAAAAAAIiA
    d3d3d3cAAAgAAAAIiIiIB3d3d3d3dwCAAAAAh3d4gHd3d3d3d3dwAAAAAPd3eAd3d3d3d3d3dwCA
    AAD3d3gHd3d3d3d3d3dwiAAA93dwd3d3d3d3d3d3cIgAAPd3cHd3d3d3d3mZmZCIAAD3d3B3d3d3
    d3d5mZmQiAAA93dwd3d3d3d3d3d3cIgAAP//8P////////////CIAAAHd3D/////////+P/wiAAA
    AHd3B3d3d3d3d3iHB/gAAAAAAAd3d3d3d3eHdwAAAAAHAABwd3d3d3iId3BwAAAAAHAABwAAAAAA
    AAAHAAAAAAAHAIhw//////jwcAAAAAAAAHAA9wDMzMzABwAAAAAAAAAAAA/8AAAAB3/wAAAAAAAA
    AAAA//93f////wAAAAAAAAAACI/MzMzMzP+IAAAAAAAAAAAP////////8AAAAAAAAAAAD///////
    //AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AP//+ADwAAAA8AAAAPAAAADw
    AAAA8AAAAPAAAAHgAAADwAAAB4AAAAeAAAADgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABwAAAAeAA
    AAHgAAAB5wAAH/MAAD/5AAB//AAAf/+AAD//4AAf/+AAD//wAA//8AAP//AAD/////8oAAAAIAAA
    AEAAAAABAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAcAAAAOAAAAHYH///v
    B///3gf//70H/AcaB/P4BADv/BAf3/8gP7//gD9//8g/f//sPv//7D7/+Aw+//gMPv//7D7//+we
    ///sD3//3AB//8AAv/+gAEAAQADv/oAAMAEAABgHgAAP/8AAHADwAAf/4AAH/+AAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAABAAJgBQAHIAaQBuAHQAZQByAAAAAABSnCYAUgBlAGYAcgBlAHMAaAAA
    AAAAU5xTACYAZQBsAGUAYwB0AAAAAAAAAAAAgABHnEUAJgB4AGkAdAAAABAAJgBPAHAAdABpAG8A
    bgBzAAAAgQBUnCYAVQBzAGUAIABQAHIAaQBuAHQAZQByACAATgBvAHQAaQBmAGkAYwBhAHQAaQBv
    AG4AcwAAAJAAJgBIAGUAbABwAAAAgABRnCYAQQBiAG8AdQB0ACAAUAByAGkAbgB0ACAASgBvAGIA
    LgAuAC4AAACAAMgAAAAAAAgAFgARAKcAQAAAAAAAQQBiAG8AdQB0ACAAUAByAGkAbgB0AEoAbwBi
    AAAAAQADUAAAAACEAAIAIAAOAAEA//+AAE8ASwAAAAAAAAADAABQAAAAAAMAAgASABQA/////4IA
    //9mAAAAAAACUAAAAAAeAAIAZAAIAOsD//+CAEMAbwBtAHAAYQBuAHkATgBhAG0AZQAAAAAAAAAC
    UAAAAAAeAAoAUgAIAOgD//+CAEYAaQBsAGUARABlAHMAYwByAGkAcAB0AGkAbwBuAAAAAAAAAAJQ
    AAAAAHIACgAQAAgA6QP//4IAUAByAG8AZAB1AGMAdABWAGUAcgBzAGkAbwBuAAAAAAAAAAAAAlAA
    AAAAHgASAIkACADqA///ggBMAGUAZwBhAGwAQwBvAHAAeQByAGkAZwBoAHQAAAAAAAAAAAACUAAA
    AAAeACIAiAAbAOwD//+CAEwAZQBnAGEAbABUAHIAYQBkAGUAbQBhAHIAawBzAAAAAAAEAABQAAAA
    ABwAHwCKAAEA9QH//4IAAAAAAAAAwADIkAAAAAAEAAAAAADTAC0AAAAAAFYAaQBlAHcAIABQAHIA
    aQBuAHQAZQByAAAACABNAFMAIABTAGEAbgBzACAAUwBlAHIAaQBmAAAAAAAAAAJQAAAAAAYAEQAY
    AAgA/////4IAJgBQAHIAaQBuAHQAZQByADoAAAAAAIAAgVAAAAAAIAAPAHUADQDtA///gQAAAAAA
    AAABAAFQAAAAAJsABgAyAA4AAQD//4AATwBLAAAAAAAAAAAAAVAAAAAAmwAXADIADgACAP//gABD
    AGEAbgBjAGUAbAAAAAAAAAAAAA0ARABvAGMAdQBtAGUAbgB0ACAATgBhAG0AZQAFAE8AdwBuAGUA
    cgAGAFMAdABhAHQAdQBzAAkAUwB1AGIAbQBpAHQAdABlAGQACABQAHIAbwBnAHIAZQBzAHMAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAABIALwBRnAAAEgA/AFGcAAASAGUAU5wAABIAbgBUnAAAkgByAFKc
    AAAQAWQAbgB4AIgAAAAFAAAAAAABAAIAICAQAAEABADoAgAAAQAgIAIAAQABADABAAACAAAA5AM0
    AAAAVgBTAF8AVgBFAFIAUwBJAE8ATgBfAEkATgBGAE8AAAAAAL0E7/4AAAEAAwADAAAAAAADAAMA
    AAAAAD8AAAAKAAAABAAAAAEAAAAAAAAAAAAAAAAAAABEAwAAAQBTAHQAcgBpAG4AZwBGAGkAbABl
    AEkAbgBmAG8AAAAgAwAAAQAwADQAMAA5ADAANABlADQAAABMABYAAQBDAG8AbQBwAGEAbgB5AE4A
    YQBtAGUAAAAAAE0AaQBjAHIAbwBzAG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAAAAVAAW
    AAEARgBpAGwAZQBEAGUAcwBjAHIAaQBwAHQAaQBvAG4AAAAAAFAAcgBpAG4AdAAgAEoAbwBiACAA
    QQBwAHAAbABpAGMAYQB0AGkAbwBuAAAAKAAEAAEARgBpAGwAZQBWAGUAcgBzAGkAbwBuAAAAAAAx
    AC4AMAAAADIACQABAEkAbgB0AGUAcgBuAGEAbABOAGEAbQBlAAAAUAByAGkAbgB0AEoAbwBiAAAA
    AAB0ACgAAQBMAGUAZwBhAGwAQwBvAHAAeQByAGkAZwBoAHQAAABDAG8AcAB5AHIAaQBnAGgAdAAg
    AKkAIABNAGkAYwByAG8AcwBvAGYAdAAgAEMAbwByAHAALgAgADEAOQA5ADAAIAAtACAAMQA5ADkA
    OAAAAA4BcwABAEwAZQBnAGEAbABUAHIAYQBkAGUAbQBhAHIAawBzAAAAAABNAGkAYwByAG8AcwBv
    AGYAdACuACAAaQBzACAAYQAgAHIAZQBnAGkAcwB0AGUAcgBlAGQAIAB0AHIAYQBkAGUAbQBhAHIA
    awAgAG8AZgAgAE0AaQBjAHIAbwBzAG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAC4AIABX
    AGkAbgBkAG8AdwBzACgAVABNACkAIABpAHMAIABhACAAdAByAGEAZABlAG0AYQByAGsAIABvAGYA
    IABNAGkAYwByAG8AcwBvAGYAdAAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAAAAAAKAAAAAEATwBy
    AGkAZwBpAG4AYQBsAEYAaQBsAGUAbgBhAG0AZQAAADIACQABAFAAcgBvAGQAdQBjAHQATgBhAG0A
    ZQAAAAAAUAByAGkAbgB0AEoAbwBiAAAAAAAsAAQAAQBQAHIAbwBkAHUAYwB0AFYAZQByAHMAaQBv
    AG4AAAAxAC4AMAAAAEQAAAABAFYAYQByAEYAaQBsAGUASQBuAGYAbwAAAAAAJAAEAAAAVAByAGEA
    bgBzAGwAYQB0AGkAbwBuAAAAAAAJBOQE



    ------------------
Working...
X