{*******************************************************} { } { Tocsg.Printer } { } { Copyright (C) 2019 kku } { } {*******************************************************} unit Tocsg.Printer; interface uses Tocsg.Obj, System.SysUtils, Winapi.Windows, Winapi.WinSpool, System.Classes, Tocsg.Thread, System.Generics.Collections; const MAX_JOB = 1024; SIG_PJL: array[0..8] of Byte = ($1B, $25, $2D, $31, $32, $33, $34, $35, $58); REG_KEY_PRINTERSX = 'SYSTEM\CurrentControlSet\Control\Print\Printers'; DMCOLOR_MONOCHROME = 1; {$EXTERNALSYM DMCOLOR_MONOCHROME} DMCOLOR_COLOR = 2; {$EXTERNALSYM DMCOLOR_COLOR} DMPAPER_LETTER = 1; // Letter 8 1/2 x 11 in DMPAPER_LETTERSMALL = 2; // Letter Small 8 1/2 x 11 in DMPAPER_TABLOID = 3; // Tabloid 11 x 17 in DMPAPER_LEDGER = 4; // Ledger 17 x 11 in DMPAPER_LEGAL = 5; // Legal 8 1/2 x 14 in DMPAPER_STATEMENT = 6; // Statement 5 1/2 x 8 1/2 in DMPAPER_EXECUTIVE = 7; // Executive 7 1/4 x 10 1/2 in DMPAPER_A3 = 8; // A3 297 x 420 mm DMPAPER_A4 = 9; // A4 210 x 297 mm DMPAPER_A4SMALL = 10; // A4 Small 210 x 297 mm DMPAPER_A5 = 11; // A5 148 x 210 mm DMPAPER_B4 = 12; // B4 (JIS) 250 x 354 DMPAPER_B5 = 13; // B5 (JIS) 182 x 257 mm DMPAPER_FOLIO = 14; // Folio 8 1/2 x 13 in DMPAPER_QUARTO = 15; // Quarto 215 x 275 mm DMPAPER_10X14 = 16; // 10x14 in DMPAPER_11X17 = 17; // 11x17 in DMPAPER_NOTE = 18; // Note 8 1/2 x 11 in DMPAPER_ENV_9 = 19; // Envelope #9 3 7/8 x 8 7/8 DMPAPER_ENV_10 = 20; // Envelope #10 4 1/8 x 9 1/2 DMPAPER_ENV_11 = 21; // Envelope #11 4 1/2 x 10 3/8 DMPAPER_ENV_12 = 22; // Envelope #12 4 \276 x 11 DMPAPER_ENV_14 = 23; // Envelope #14 5 x 11 1/2 DMPAPER_CSHEET = 24; // C size sheet DMPAPER_DSHEET = 25; // D size sheet DMPAPER_ESHEET = 26; // E size sheet DMPAPER_ENV_DL = 27; // Envelope DL 110 x 220mm DMPAPER_ENV_C5 = 28; // Envelope C5 162 x 229 mm DMPAPER_ENV_C3 = 29; // Envelope C3 324 x 458 mm DMPAPER_ENV_C4 = 30; // Envelope C4 229 x 324 mm DMPAPER_ENV_C6 = 31; // Envelope C6 114 x 162 mm DMPAPER_ENV_C65 = 32; // Envelope C65 114 x 229 mm DMPAPER_ENV_B4 = 33; // Envelope B4 250 x 353 mm DMPAPER_ENV_B5 = 34; // Envelope B5 176 x 250 mm DMPAPER_ENV_B6 = 35; // Envelope B6 176 x 125 mm DMPAPER_ENV_ITALY = 36; // Envelope 110 x 230 mm DMPAPER_ENV_MONARCH = 37; // Envelope Monarch 3.875 x 7.5 in DMPAPER_ENV_PERSONAL = 38; // 6 3/4 Envelope 3 5/8 x 6 1/2 in DMPAPER_FANFOLD_US = 39; // US Std Fanfold 14 7/8 x 11 in DMPAPER_FANFOLD_STD_GERMAN = 40; // German Std Fanfold 8 1/2 x 12 in DMPAPER_FANFOLD_LGL_GERMAN = 41; // German Legal Fanfold 8 1/2 x 13 in DMPAPER_ISO_B4 = 42; // B4 (ISO) 250 x 353 mm DMPAPER_JAPANESE_POSTCARD = 43; // Japanese Postcard 100 x 148 mm DMPAPER_9X11 = 44; // 9 x 11 in DMPAPER_10X11 = 45; // 10 x 11 in DMPAPER_15X11 = 46; // 15 x 11 in DMPAPER_ENV_INVITE = 47; // Envelope Invite 220 x 220 mm DMPAPER_RESERVED_48 = 48; // RESERVED--DO NOT USE DMPAPER_RESERVED_49 = 49; // RESERVED--DO NOT USE DMPAPER_LETTER_EXTRA = 50; // Letter Extra 9 \275 x 12 in DMPAPER_LEGAL_EXTRA = 51; // Legal Extra 9 \275 x 15 in DMPAPER_TABLOID_EXTRA = 52; // Tabloid Extra 11.69 x 18 in DMPAPER_A4_EXTRA = 53; // A4 Extra 9.27 x 12.69 in DMPAPER_LETTER_TRANSVERSE = 54; // Letter Transverse 8 \275 x 11 in DMPAPER_A4_TRANSVERSE = 55; // A4 Transverse 210 x 297 mm DMPAPER_LETTER_EXTRA_TRANSVERSE = 56; // Letter Extra Transverse 9\275 x 12 in DMPAPER_A_PLUS = 57; // SuperA/SuperA/A4 227 x 356 mm DMPAPER_B_PLUS = 58; // SuperB/SuperB/A3 305 x 487 mm DMPAPER_LETTER_PLUS = 59; // Letter Plus 8.5 x 12.69 in DMPAPER_A4_PLUS = 60; // A4 Plus 210 x 330 mm DMPAPER_A5_TRANSVERSE = 61; // A5 Transverse 148 x 210 mm DMPAPER_B5_TRANSVERSE = 62; // B5 (JIS) Transverse 182 x 257 mm DMPAPER_A3_EXTRA = 63; // A3 Extra 322 x 445 mm DMPAPER_A5_EXTRA = 64; // A5 Extra 174 x 235 mm DMPAPER_B5_EXTRA = 65; // B5 (ISO) Extra 201 x 276 mm DMPAPER_A2 = 66; // A2 420 x 594 mm DMPAPER_A3_TRANSVERSE = 67; // A3 Transverse 297 x 420 mm DMPAPER_A3_EXTRA_TRANSVERSE = 68; // A3 Extra Transverse 322 x 445 mm DMPAPER_DBL_JAPANESE_POSTCARD = 69; // Japanese Double Postcard 200 x 148 mm DMPAPER_A6 = 70; // A6 105 x 148 mm DMPAPER_JENV_KAKU2 = 71; // Japanese Envelope Kaku #2 DMPAPER_JENV_KAKU3 = 72; // Japanese Envelope Kaku #3 DMPAPER_JENV_CHOU3 = 73; // Japanese Envelope Chou #3 DMPAPER_JENV_CHOU4 = 74; // Japanese Envelope Chou #4 DMPAPER_LETTER_ROTATED = 75; // Letter Rotated 11 x 8 1/2 11 in DMPAPER_A3_ROTATED = 76; // A3 Rotated 420 x 297 mm DMPAPER_A4_ROTATED = 77; // A4 Rotated 297 x 210 mm DMPAPER_A5_ROTATED = 78; // A5 Rotated 210 x 148 mm DMPAPER_B4_JIS_ROTATED = 79; // B4 (JIS) Rotated 364 x 257 mm DMPAPER_B5_JIS_ROTATED = 80; // B5 (JIS) Rotated 257 x 182 mm DMPAPER_JAPANESE_POSTCARD_ROTATED = 81; // Japanese Postcard Rotated 148 x 100 mm DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED = 82; // Double Japanese Postcard Rotated 148 x 200 mm DMPAPER_A6_ROTATED = 83; // A6 Rotated 148 x 105 mm DMPAPER_JENV_KAKU2_ROTATED = 84; // Japanese Envelope Kaku #2 Rotated DMPAPER_JENV_KAKU3_ROTATED = 85; // Japanese Envelope Kaku #3 Rotated DMPAPER_JENV_CHOU3_ROTATED = 86; // Japanese Envelope Chou #3 Rotated DMPAPER_JENV_CHOU4_ROTATED = 87; // Japanese Envelope Chou #4 Rotated DMPAPER_B6_JIS = 88; // B6 (JIS) 128 x 182 mm DMPAPER_B6_JIS_ROTATED = 89; // B6 (JIS) Rotated 182 x 128 mm DMPAPER_12X11 = 90; // 12 x 11 in DMPAPER_JENV_YOU4 = 91; // Japanese Envelope You #4 DMPAPER_JENV_YOU4_ROTATED = 92; // Japanese Envelope You #4 Rotated DMPAPER_P16K = 93; // PRC 16K 146 x 215 mm DMPAPER_P32K = 94; // PRC 32K 97 x 151 mm DMPAPER_P32KBIG = 95; // PRC 32K(Big) 97 x 151 mm DMPAPER_PENV_1 = 96; // PRC Envelope #1 102 x 165 mm DMPAPER_PENV_2 = 97; // PRC Envelope #2 102 x 176 mm DMPAPER_PENV_3 = 98; // PRC Envelope #3 125 x 176 mm DMPAPER_PENV_4 = 99; // PRC Envelope #4 110 x 208 mm DMPAPER_PENV_5 = 100; // PRC Envelope #5 110 x 220 mm DMPAPER_PENV_6 = 101; // PRC Envelope #6 120 x 230 mm DMPAPER_PENV_7 = 102; // PRC Envelope #7 160 x 230 mm DMPAPER_PENV_8 = 103; // PRC Envelope #8 120 x 309 mm DMPAPER_PENV_9 = 104; // PRC Envelope #9 229 x 324 mm DMPAPER_PENV_10 = 105; // PRC Envelope #10 324 x 458 mm DMPAPER_P16K_ROTATED = 106; // PRC 16K Rotated DMPAPER_P32K_ROTATED = 107; // PRC 32K Rotated DMPAPER_P32KBIG_ROTATED = 108; // PRC 32K(Big) Rotated DMPAPER_PENV_1_ROTATED = 109; // PRC Envelope #1 Rotated 165 x 102 mm DMPAPER_PENV_2_ROTATED = 110; // PRC Envelope #2 Rotated 176 x 102 mm DMPAPER_PENV_3_ROTATED = 111; // PRC Envelope #3 Rotated 176 x 125 mm DMPAPER_PENV_4_ROTATED = 112; // PRC Envelope #4 Rotated 208 x 110 mm DMPAPER_PENV_5_ROTATED = 113; // PRC Envelope #5 Rotated 220 x 110 mm DMPAPER_PENV_6_ROTATED = 114; // PRC Envelope #6 Rotated 230 x 120 mm DMPAPER_PENV_7_ROTATED = 115; // PRC Envelope #7 Rotated 230 x 160 mm DMPAPER_PENV_8_ROTATED = 116; // PRC Envelope #8 Rotated 309 x 120 mm DMPAPER_PENV_9_ROTATED = 117; // PRC Envelope #9 Rotated 324 x 229 mm DMPAPER_PENV_10_ROTATED = 118; // PRC Envelope #10 Rotated 458 x 324 mm { 2085 DEVMODEA = record 2086 dmDeviceName : array[0..(CCHDEVICENAME)-1] of AnsiChar; 2087 dmSpecVersion : WORD; 2088 dmDriverVersion : WORD; 2089 dmSize : WORD; 2090 dmDriverExtra : WORD; 2091 dmFields : DWORD; 2092 case byte of 2093 1: (dmOrientation : SmallInt; 2094 dmPaperSize : SmallInt; 2095 dmPaperLength : SmallInt; 2096 dmPaperWidth : SmallInt; 2097 dmScale : SmallInt; 2098 dmCopies : SmallInt; 2099 dmDefaultSource : SmallInt; 2100 dmPrintQuality : SmallInt; 2101 dmColor : SmallInt; 2102 dmDuplex : SmallInt; 2103 dmYResolution : SmallInt; 2104 dmTTOption : SmallInt; 2105 dmCollate : SmallInt; 2106 dmFormName : array[0..(CCHFORMNAME)-1] of AnsiCHAR; 2107 dmLogPixels : WORD; 2108 dmBitsPerPel : DWORD; 2109 dmPelsWidth : DWORD; 2110 dmPelsHeight : DWORD; 2111 dmDisplayFlags : DWORD; 2112 dmDisplayFrequency : DWORD; 2113 dmICMMethod : DWORD; 2114 dmICMIntent : DWORD; 2115 dmMediaType : DWORD; 2116 dmDitherType : DWORD; 2117 dmICCManufacturer : DWORD; 2118 dmICCModel : DWORD 2119 ); 2120 2: (dmPosition: POINTL; 2121 dmDisplayOrientation: DWORD; 2122 dmDisplayFixedOutput: DWORD; 2123 ); 2124 end; 2125 2126 LPDEVMODEA = ^DEVMODEA; 2127 _DEVMODEA = DEVMODEA; 2128 TDEVMODEA = DEVMODEA; 2129 PDEVMODEA = LPDEVMODEA; 2130 2131 _devicemodeA = DEVMODEA; 2132 devicemodeA = DEVMODEA; 2133 tdevicemodeA = DEVMODEA; 2134 PDeviceModeA = LPDEVMODEA; 2135 } type POINTL = packed record x : LONG; y : LONG; end; PDevModeW = ^TDevModeW; TDevModeW = packed record dmDeviceName : array[0.. CCHDEVICENAME-1] of WCHAR; dmSpecVersion : WORD; dmDriverVersion : WORD; dmSize : WORD; dmDriverExtra : WORD; dmFields : DWORD; case byte of 1: ( dmOrientation : short; dmPaperSize : short; dmPaperLength : short; dmPaperWidth : short; dmScale : short; dmCopies : short; dmDefaultSource: short; dmPrintQuality : short; dmColor : short; dmDuplex : short; dmYResolution : short; dmTTOption : short; dmCollate : short; dmFormName : array [0..CCHFORMNAME-1] of wchar; dmLogPixels : WORD; dmBitsPerPel : DWORD; dmPelsWidth : DWORD; dmPelsHeight : DWORD; dmDisplayFlags : DWORD; dmDisplayFrequency : DWORD; dmICMMethod : DWORD; dmICMIntent : DWORD; dmMediaType : DWORD; dmDitherType : DWORD; dmReserved1 : DWORD; dmReserved2 : DWORD; dmPanningWidth : DWORD; dmPanningHeight: DWORD; ); 2: ( dmPosition: POINTL; dmDisplayOrientation: DWORD; dmDisplayFixedOutput: DWORD; ); end; PJOB_INFO_1 = ^TJOB_INFO_1; TJOB_INFO_1 = packed record dwJobId : DWORD; sPrinterName : LPTSTR; sMachineName : LPTSTR; sUserName : LPTSTR; sDocument : LPTSTR; sDatatype : LPTSTR; sStatus : LPTSTR; dwStatus : DWORD; dwPriority : DWORD; dwPosition : DWORD; dwTotalPages : DWORD; dwPagesPrinted : DWORD; Submitted : SYSTEMTIME; end; PJOB_INFO_2 = ^TJOB_INFO_2; TJOB_INFO_2 = record dwJobId : DWORD; pPrinterName : LPTSTR; pMachineName : LPTSTR; pUserName : LPTSTR; pDocument : LPTSTR; pNotifyName : LPTSTR; pDatatype : LPTSTR; pPrintProcessor : LPTSTR; pParameters : LPTSTR; pDriverName : LPTSTR; pDevMode : PDevModeW; pStatus : LPTSTR; pSecurityDescriptor : PSECURITY_DESCRIPTOR; dwStatus : DWORD; dwPriority : DWORD; dwPosition : DWORD; dwStartTime : DWORD; dwUntilTime : DWORD; dwTotalPages : DWORD; dwSize : DWORD; Submitted : SYSTEMTIME; dwTime : DWORD; dwPagesPrinted : DWORD; end; PPrtJobDevInfo = ^TPrtJobDevInfo; TPrtJobDevInfo = record sDataType, sDocName, sPtrName, sDrvName, sPaperInfo, sPrintProcessor: String; dwScale, dwTotalPage, dwPaperSizeT, dwCopyCount: DWORD; bColor, bPaperV: Boolean; DevMode: TDeviceMode; end; TPrtJobState = (jsAdd, jsWork, jsDelete); TPrtJobInfo = class(TTgObject) private dwID_: DWORD; hPrtHandle_: THandle; bCustomPause_: Boolean; sUserName_, sMachine_, sPort_, sDocument_, sPrinterName_: String; dwPagesPrinted_, dwTotalPages_, dwBytesPrinted_, dwTotalBytes_, dwChange_, dwStatus_ : DWORD; PrtJobState_: TPrtJobState; dtSubmitted_: TDateTime; bWorkEnd_: Boolean; // 작업상태 체크용 22_0719 12:30:58 kku procedure SetJobState(aPrtJobState: TPrtJobState); public // 출력물 수행 프로세스 정보를 넣는데 사용 25_0605 15:17:45 kku Wnd: HWND; // PID: DWORD; // PName: String; Constructor Create(hPrtHandle: THandle; dwID: DWORD; bPause: Boolean = false); function SetPrtJob(dwControl: DWORD): Boolean; function PausePrtJob: Boolean; function ResumePrtJob(bForce: Boolean = false): Boolean; procedure UpdatePrtFieldInfo(Info: TPrinterNotifyInfoData); function IsSpooling: Boolean; function IsSpooling2: Boolean; function GetJobDevInfo(var aInfo: TPrtJobDevInfo): Boolean; property ID: DWORD read dwID_; property UserName: String read sUserName_; property PrinterName: String read sPrinterName_; property Machine: String read sMachine_; property Port: String read sPort_; property Document: String read sDocument_; property JobDateTime: TDateTime read dtSubmitted_; property PagesPrinted: DWORD read dwPagesPrinted_; property TotalPages: DWORD read dwTotalPages_; property BytesPrinted: DWORD read dwBytesPrinted_; property TotalBytes: DWORD read dwTotalBytes_; property Status: DWORD read dwStatus_; property IsCustomPause: Boolean read bCustomPause_ write bCustomPause_; property PtrJobState: TPrtJobState read PrtJobState_ write SetJobState; property WorkEnd: Boolean read bWorkEnd_ write bWorkEnd_; end; TThdPrtSpoolWatch = class; TPrtChangeNotifyEvent = procedure(Sender: TThdPrtSpoolWatch; dwChange: DWORD) of object; TPrtNotifyJobInfoEvent = procedure(Sender: TThdPrtSpoolWatch; Job: TPrtJobInfo) of object; PPrtJobs = ^TPrtJobs; TPrtJobs = array [0..MAX_JOB-1] of TJobInfo1; TThdPrtSpoolWatch = class(TTgThread) private PrtJobInfo_: TPrtJobInfo; sSpoolDir_, sDeviceName_: String; hPrtChangeEvent_, hPrinter_: THandle; arrJobFields_: array [0..22] of WORD; PNO_: TPrinterNotifyOptions; bSync_: Boolean; arrPNOT_: array [0..0] of TPrinterNotifyOptionsType; JobList_: TDictionary; procedure OnJobValue(Sender: TObject; const Item: TPrtJobInfo; Action: TCollectionNotification); protected dwChangeMod_: DWORD; evPrtChangeNotifyEvent_: TPrtChangeNotifyEvent; evPrtNotifyJobInfoEvent_: TPrtNotifyJobInfoEvent; procedure ProcessChangeEvent; procedure ProcessNotifyJobInfoEvent; procedure Execute; override; public Constructor Create(bSync: Boolean; sDeviceName: String; dwWatchMod: DWORD = PRINTER_CHANGE_ALL); Destructor Destroy; override; procedure StopThread; override; property DeviceName: String read sDeviceName_; property SpoolDir: String read sSpoolDir_; property OnPrtChangeNotifyEvent: TPrtChangeNotifyEvent write evPrtChangeNotifyEvent_; property OnPrtNotifyJobInfoEvent: TPrtNotifyJobInfoEvent write evPrtNotifyJobInfoEvent_; end; TTgPrtSpoolWatch = class(TTgThread) private bIsWatch_: Boolean; dwWatchMod_: DWORD; WatchThdList_: TList; bSync_: Boolean; PrtList_: TStringList; procedure OnPrtNotify(Sender: TObject; const Item: TThdPrtSpoolWatch; Action: TCollectionNotification); protected evPrtChangeNotifyEvent_: TPrtChangeNotifyEvent; evPtrNotifyInfoEvent_: TPrtNotifyJobInfoEvent; procedure StartPrtWatch; procedure StopPrtWatch; procedure Execute; override; public Constructor Create(bSync: Boolean; dwWatchMod: DWORD = PRINTER_CHANGE_ALL); Destructor Destroy; override; property IsWatch: Boolean read bIsWatch_; property OnPrtChangeNotificationEvent: TPrtChangeNotifyEvent write evPrtChangeNotifyEvent_; property OnPrtNotificationEvent: TPrtNotifyJobInfoEvent write evPtrNotifyInfoEvent_; end; TPrinterPortType = (PTUnknown, PTLocal, PTTcpIp, PTWsd, PTShared); PPrinterInfo = ^TPrinterInfo; TPrinterInfo = record sIp, sPrtName, sDrvName, sPortName: String; PortType: TPrinterPortType; bIsPowerSaveMode: Boolean; dwStatus: DWORD; end; TPrtInfoList = TList; TPrintersInfo = class(TTgObject) private PrtInfoList_: TPrtInfoList; procedure OnPrtInfoNotify(Sender: TObject; const Item: PPrinterInfo; Action: TCollectionNotification); public Constructor Create; Destructor Destroy; override; function GetPrtInfoByPrtName(const sPrtName: String): PPrinterInfo; procedure RefreshList; procedure SaveToFile(const sPath: String); procedure LoadFromFile(const sPath: String); property PrtInfoList: TPrtInfoList read PrtInfoList_; end; function GetPrinterSpoolDir(hPrinter: THandle = 0): String; function GetLastSpoolPath(sSpoolDir: String): String; function GetDefaultPrinterName: string; function GetProcessNameByPrtDocName(sDocName: String): String; function PrinterDriverToName(sDrvName: String): String; function PrinterDriverToIP(sDrvName: String): String; function IsPJL(const sPath: String): Boolean; function GetQtyFromPJL(const sPath: String; var bCollate: Boolean): Integer; function IsPJLAndLanguagePLW(const sPath: string): Boolean; implementation uses Tocsg.Registry, Tocsg.Path, Tocsg.WinInfo, Tocsg.DateTime, Tocsg.Safe, Vcl.Printers, Tocsg.Exception, Tocsg.Files, Tocsg.Trace, Tocsg.Strings, Winapi.WinSvc, Tocsg.Service, System.Win.Registry, superobject, Tocsg.Json; function GetPrinterSpoolDir(hPrinter: THandle = 0): String; var dwLen, dwType: DWORD; sDir: PWideChar; begin Result := ''; if hPrinter <> 0 then begin dwLen := 0; dwType := REG_SZ; GetPrinterData(hPrinter, SPLREG_DEFAULT_SPOOL_DIRECTORY, @dwType, nil, 0, dwLen); if dwLen > 0 then begin sDir := AllocMem(dwLen); try if GetPrinterData(hPrinter, SPLREG_DEFAULT_SPOOL_DIRECTORY, @dwType, sDir, dwLen, dwLen) = ERROR_SUCCESS then Result := sDir; finally FreeMem(sDir, dwLen); end; end; end; if Result = '' then Result := GetRegValueAsString(HKEY_LOCAL_MACHINE, REG_KEY_PRINTERSX, 'DefaultSpoolDirectory'); if Result = '' then begin Result := GetWindowsDir + 'System32\spool\PRINTERS\'; if not DirectoryExists(Result) then Result := ''; end; if Result <> '' then begin // os가 64bit 이고 실행되는 프로그램이 32bit 용이면 system 폴더를 아래처럼 바꿔준다. // 그래야 syswow64 여기로 인식안한다. 2012-06-20 kku if IsWow64 and (SizeOf(NativeInt) = 4) then Result := StringReplace(Result, 'system32', 'sysnative', [rfIgnoreCase]); Result := IncludeTrailingBackslash(Result); end; end; function GetLastSpoolPath(sSpoolDir: String): String; var wfd: TWin32FindData; hSc: THandle; sDir, sPath: String; SplList: TStringList; i: Integer; begin TTgTrace.T('GetLastSpoolPath() ..', 9); Result := ''; try sDir := ExtractFilePath(sSpoolDir); if not ForceDirectories(sDir) then exit; sPath := sDir + '*.*'; hSc := FindFirstFile(PChar(sPath), wfd); if hSc = INVALID_HANDLE_VALUE then exit; Guard(SplList, TStringList.Create); try Repeat if (String(wfd.cFileName) <> '.') and (String(wfd.cFileName) <> '..') then if ((wfd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0) then begin TTgTrace.T('GetLastSpoolPath() .. %s', [wfd.cFileName], 9); if (GetFileExt(wfd.cFileName).ToUpper = 'SPL') then // (GetFileSizeHiLow(wfd.nFileSizeHigh, wfd.nFileSizeLow) > 0) then SplList.Add(sDir + wfd.cFileName); end; Until not FindNextFile(hSc, wfd); finally FindClose(hSc); end; TTgTrace.T('GetLastSpoolPath() .. Cnt=%d', [SplList.Count], 9); if SplList.Count > 0 then begin SplList.CustomSort(StringListCompareFileModifyDate); Result := SplList[SplList.Count - 1]; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetLastSpoolPath()'); end; end; { TPrtJobInfo } Constructor TPrtJobInfo.Create(hPrtHandle: THandle; dwID: DWORD; bPause: Boolean = false); begin Inherited Create; sUserName_ := ''; sMachine_ := ''; sPort_ := ''; sDocument_ := ''; dwPagesPrinted_ := 0; dwTotalPages_ := 0; dwBytesPrinted_ := 0; dwTotalBytes_ := 0; dwChange_ := 0; dwStatus_ := 0; dtSubmitted_ := 0; bWorkEnd_ := false; bCustomPause_ := false; hPrtHandle_ := hPrtHandle; dwID_ := dwID; PrtJobState_ := jsAdd; if bPause then PausePrtJob; end; procedure TPrtJobInfo.SetJobState(aPrtJobState: TPrtJobState); begin if PrtJobState_ <> aPrtJobState then PrtJobState_ := aPrtJobState; end; function TPrtJobInfo.SetPrtJob(dwControl: DWORD): Boolean; begin try Result := SetJob(hPrtHandle_, dwID_, 0, nil, dwControl); if not Result then _Trace('Fail .. Code=%d', [dwControl], 1); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. SetPrtJob()'); end; end; function TPrtJobInfo.PausePrtJob: Boolean; begin if not bCustomPause_ then begin try // 원격 프린터의 경우.. SetJob 호출 시 ERROR_ACCESS_DENIED 날 수 있다. // 많은걸 테스트 해보지 않아서 원인은 파악안됨 2012-06-21 kku {bCustomPause_ := }Result := SetPrtJob(JOB_CONTROL_PAUSE); if not Result then begin _Trace('Fail .. JOB_CONTROL_PAUSE'); exit; end; bCustomPause_ := true; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. PausePrtJob()'); end; end; end; function TPrtJobInfo.ResumePrtJob(bForce: Boolean = false): Boolean; begin if bCustomPause_ or bForce then begin {bCustomPause_ := not }Result := SetPrtJob(JOB_CONTROL_RESUME); bCustomPause_ := false; end; end; procedure TPrtJobInfo.UpdatePrtFieldInfo(Info: TPrinterNotifyInfoData); begin case Info.Field of JOB_NOTIFY_FIELD_PRINTER_NAME : sPrinterName_ := PChar(Info.NotifyData.Data.pBuf); JOB_NOTIFY_FIELD_USER_NAME : sUserName_ := PChar(Info.NotifyData.Data.pBuf); JOB_NOTIFY_FIELD_MACHINE_NAME : sMachine_ := PChar(Info.NotifyData.Data.pBuf); JOB_NOTIFY_FIELD_PORT_NAME : sPort_ := PChar(Info.NotifyData.Data.pBuf); JOB_NOTIFY_FIELD_DOCUMENT : sDocument_ := PChar(Info.NotifyData.Data.pBuf); JOB_NOTIFY_FIELD_SUBMITTED : dtSubmitted_ := ConvSystemTimeToDateTime_Local(TSystemTime(Info.NotifyData.Data.pBuf^)); JOB_NOTIFY_FIELD_PAGES_PRINTED : dwPagesPrinted_ := Info.NotifyData.adwData[0]; JOB_NOTIFY_FIELD_TOTAL_PAGES : dwTotalPages_ := Info.NotifyData.adwData[0]; JOB_NOTIFY_FIELD_BYTES_PRINTED : dwBytesPrinted_ := Info.NotifyData.adwData[0]; JOB_NOTIFY_FIELD_TOTAL_BYTES : dwTotalBytes_ := Info.NotifyData.adwData[0]; JOB_NOTIFY_FIELD_STATUS : begin dwStatus_ := Info.NotifyData.adwData[0]; // if (dwStatus_ and JOB_STATUS_SPOOLING) <> 0 then // begin // SetPrtJob(JOB_CONTROL_PAUSE); // dwStatus_ := dwStatus_ + 0; // end; end; // else // begin // _Trace('%X = %s', [Info.Field, PChar(Info.NotifyData.Data.pBuf)]); // end; end; end; function TPrtJobInfo.IsSpooling: Boolean; begin Result := (dwStatus_ and JOB_STATUS_SPOOLING) <> 0; end; function TPrtJobInfo.IsSpooling2: Boolean; var dwNeed: DWORD; pBuf: TBytes; begin Result := false; try if hPrtHandle_ = 0 then exit; dwNeed := 0; GetJob(hPrtHandle_, dwID_, 2, nil, 0, @dwNeed); // if not GetJob(hPrtHandle_, dwID_, 1, pBuf, 0, @dwNeed) then // exit; if dwNeed = 0 then exit; SetLength(pBuf, dwNeed); if GetJob(hPrtHandle_, dwID_, 2, pBuf, dwNeed, @dwNeed) then begin Result := (PJOB_INFO_2(pBuf).dwStatus and JOB_STATUS_SPOOLING) <> 0; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. IsSpooling2()'); end; end; function TPrtJobInfo.GetJobDevInfo(var aInfo: TPrtJobDevInfo): Boolean; var dwNeed: DWORD; pBuf: TBytes; begin Result := false; Finalize(aInfo); ZeroMemory(@aInfo, SizeOf(aInfo)); try if hPrtHandle_ = 0 then exit; dwNeed := 0; GetJob(hPrtHandle_, dwID_, 2, nil, 0, @dwNeed); if dwNeed = 0 then exit; SetLength(pBuf, dwNeed); if GetJob(hPrtHandle_, dwID_, 2, pBuf, dwNeed, @dwNeed) then begin aInfo.sDocName := PJOB_INFO_2(pBuf).pDocument; aInfo.sPtrName := PJOB_INFO_2(pBuf).pPrinterName; aInfo.sDrvName := PJOB_INFO_2(pBuf).pDriverName; aInfo.bColor := PJOB_INFO_2(pBuf).pDevMode.dmColor = DMCOLOR_COLOR; aInfo.dwTotalPage := PJOB_INFO_2(pBuf).dwTotalPages; aInfo.dwCopyCount := PJOB_INFO_2(pBuf).pDevMode.dmCopies; aInfo.dwScale := PJOB_INFO_2(pBuf).pDevMode.dmScale; if aInfo.dwScale = 0 then aInfo.dwScale := 100; aInfo.bPaperV := PJOB_INFO_2(pBuf).pDevMode.dmOrientation = 1; aInfo.sPrintProcessor := PJOB_INFO_2(pBuf).pPrintProcessor; aInfo.sDataType := PJOB_INFO_2(pBuf).pDatatype; aInfo.dwPaperSizeT := PJOB_INFO_2(pBuf).pDevMode.dmPaperSize; case aInfo.dwPaperSizeT of DMPAPER_LETTER : aInfo.sPaperInfo := 'Letter 8 1/2 x 11 in'; DMPAPER_LETTERSMALL : aInfo.sPaperInfo := 'Letter Small 8 1/2 x 11 in'; DMPAPER_TABLOID : aInfo.sPaperInfo := 'Tabloid 11 x 17 in'; DMPAPER_LEDGER : aInfo.sPaperInfo := 'Ledger 17 x 11 in'; DMPAPER_LEGAL : aInfo.sPaperInfo := 'Legal 8 1/2 x 14 in'; DMPAPER_A3 : aInfo.sPaperInfo := 'A3 297 x 420 mm'; DMPAPER_A4 : aInfo.sPaperInfo := 'A4 210 x 297 mm'; DMPAPER_A4SMALL : aInfo.sPaperInfo := 'A4 Small 210 x 297 mm'; DMPAPER_A5 : aInfo.sPaperInfo := 'A5 148 x 210 mm'; DMPAPER_B4 : aInfo.sPaperInfo := 'B4 (JIS) 250 x 354'; DMPAPER_B5 : aInfo.sPaperInfo := 'B5 (JIS) 182 x 257 mm'; DMPAPER_FOLIO : aInfo.sPaperInfo := 'Folio 8 1/2 x 13 in'; else aInfo.sPaperInfo := Format('ETC (%d) %d x %d', [aInfo.dwPaperSizeT, PJOB_INFO_2(pBuf).pDevMode.dmPaperWidth, PJOB_INFO_2(pBuf).pDevMode.dmPaperLength]); end; // aInfo.sPaperInfo try CopyMemory(@aInfo.DevMode, PJOB_INFO_2(pBuf).pDevMode, SizeOf(aInfo.DevMode)); except // .. end; Result := true; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetJobDevInfo()'); end; end; { TThdPrtSpoolWatch } Constructor TThdPrtSpoolWatch.Create(bSync: Boolean; sDeviceName: String; dwWatchMod: DWORD = PRINTER_CHANGE_ALL); procedure InitJobFields; begin hPrinter_ := 0; nLastError_ := ERROR_SUCCESS; evPrtChangeNotifyEvent_ := nil; evPrtNotifyJobInfoEvent_ := nil; arrJobFields_[0] := JOB_NOTIFY_FIELD_PRINTER_NAME; arrJobFields_[1] := JOB_NOTIFY_FIELD_MACHINE_NAME; arrJobFields_[2] := JOB_NOTIFY_FIELD_PORT_NAME; arrJobFields_[3] := JOB_NOTIFY_FIELD_USER_NAME; arrJobFields_[4] := JOB_NOTIFY_FIELD_NOTIFY_NAME; arrJobFields_[5] := JOB_NOTIFY_FIELD_DATATYPE; arrJobFields_[6] := JOB_NOTIFY_FIELD_PRINT_PROCESSOR; arrJobFields_[7] := JOB_NOTIFY_FIELD_PARAMETERS; arrJobFields_[8] := JOB_NOTIFY_FIELD_DRIVER_NAME; arrJobFields_[9] := JOB_NOTIFY_FIELD_DEVMODE; arrJobFields_[10] := JOB_NOTIFY_FIELD_STATUS; arrJobFields_[11] := JOB_NOTIFY_FIELD_STATUS_STRING; arrJobFields_[12] := JOB_NOTIFY_FIELD_DOCUMENT; arrJobFields_[13] := JOB_NOTIFY_FIELD_PRIORITY; arrJobFields_[14] := JOB_NOTIFY_FIELD_POSITION; arrJobFields_[15] := JOB_NOTIFY_FIELD_SUBMITTED; arrJobFields_[16] := JOB_NOTIFY_FIELD_START_TIME; arrJobFields_[17] := JOB_NOTIFY_FIELD_UNTIL_TIME; arrJobFields_[18] := JOB_NOTIFY_FIELD_TIME; arrJobFields_[19] := JOB_NOTIFY_FIELD_TOTAL_PAGES; arrJobFields_[20] := JOB_NOTIFY_FIELD_PAGES_PRINTED; arrJobFields_[21] := JOB_NOTIFY_FIELD_TOTAL_BYTES; arrJobFields_[22] := JOB_NOTIFY_FIELD_BYTES_PRINTED; arrPNOT_[0].wType := JOB_NOTIFY_TYPE; arrPNOT_[0].Reserved0 := 0; arrPNOT_[0].Reserved1 := 0; arrPNOT_[0].Reserved2 := 0; arrPNOT_[0].Count := SizeOf(arrJobFields_) div SizeOf(arrJobFields_[0]); arrPNOT_[0].pFields := @arrJobFields_; ZeroMemory(@PNO_, SizeOf(PNO_)); PNO_.Version := 2; PNO_.Flags := PRINTER_NOTIFY_OPTIONS_REFRESH; PNO_.Count := SizeOf(arrPNOT_) div SizeOf(arrPNOT_[0]); PNO_.pTypes := @arrPNOT_; end; //var // PD: PRINTER_DEFAULTS; begin Inherited Create; bSync_ := bSync; FreeOnTerminate := true; JobList_ := TDictionary.Create; JobList_.OnValueNotify := OnJobValue; dwChangeMod_ := dwWatchMod; sDeviceName_ := sDeviceName; InitJobFields; hPrtChangeEvent_ := INVALID_HANDLE_VALUE; hPrinter_ := 0; // PD.pDatatype := nil; // PD.pDevMode := nil; // PD.DesiredAccess := PRINTER_ALL_ACCESS; if OpenPrinter(PChar(sDeviceName_), hPrinter_, nil{@PD}) then begin hPrtChangeEvent_ := FindFirstPrinterChangeNotification(hPrinter_, dwChangeMod_, 0, @PNO_); if hPrtChangeEvent_ = INVALID_HANDLE_VALUE then begin nLastError_ := 2; exit; end; sSpoolDir_ := GetPrinterSpoolDir(hPrinter_); if sSpoolDir_ = '' then nLastError_ := 3; end else nLastError_ := 1; end; Destructor TThdPrtSpoolWatch.Destroy; begin if hPrtChangeEvent_ <> INVALID_HANDLE_VALUE then FindClosePrinterChangeNotification(hPrtChangeEvent_); if hPrinter_ <> 0 then ClosePrinter(hPrinter_); FreeAndNil(JobList_); Inherited; end; procedure TThdPrtSpoolWatch.StopThread; begin Inherited; if hPrtChangeEvent_ <> 0 then SetEvent(hPrtChangeEvent_); end; procedure TThdPrtSpoolWatch.OnJobValue(Sender: TObject; const Item: TPrtJobInfo; Action: TCollectionNotification); begin case Action of cnAdded: ; cnRemoved: Item.Free; cnExtracted: ; end; end; procedure TThdPrtSpoolWatch.ProcessChangeEvent; begin if Assigned(evPrtChangeNotifyEvent_) then evPrtChangeNotifyEvent_(Self, dwChangeMod_); end; procedure TThdPrtSpoolWatch.ProcessNotifyJobInfoEvent; begin if Assigned(evPrtNotifyJobInfoEvent_) then evPrtNotifyJobInfoEvent_(Self, PrtJobInfo_); end; procedure TThdPrtSpoolWatch.Execute; procedure DeletePrtJob; var dwNumJobs, dwByteNeeded: DWORD; pJobs: PPrtJobs; pJob: TJobInfo1; p: TPrtJobInfo; n, c: Integer; bFind: Boolean; enum: TEnumerator; begin EnumJobs(hPrinter_, 0, MAX_JOB, 1, nil, 0, dwByteNeeded, dwNumJobs); pJobs := AllocMem(dwByteNeeded); try dwNumJobs := 0; if EnumJobs(hPrinter_, 0, MAX_JOB, 1, pJobs, 0, dwByteNeeded, dwNumJobs) then begin if dwNumJobs > 0 then begin bFind := false; Guard(enum, JobList_.Values.GetEnumerator); while enum.MoveNext do begin p := enum.Current; for c := 0 to dwNumJobs - 1 do begin pJob := pJobs[c]; if pJob.JobId = p.ID then begin bFind := true; break; end; end; if not bFind then begin JobList_.Remove(p.dwID_); exit; end; end; end else JobList_.Clear; end; finally FreeMem(pJobs, dwByteNeeded); end; end; var i: Integer; dwJobID, dwOldFlags: DWORD; PNI: PPrinterNotifyInfo; pData: PPrinterNotifyInfoData; begin while not Terminated and not bWorkStop_ and (hPrtChangeEvent_ <> INVALID_HANDLE_VALUE) do begin PNI := nil; dwChangeMod_ := 0; try case WaitForSingleObject(hPrtChangeEvent_, INFINITE) of WAIT_OBJECT_0 : begin if Terminated or bWorkStop_ then break; if not FindNextPrinterChangeNotification(hPrtChangeEvent_, dwChangeMod_, @PNO_, Pointer(PNI)) then begin _Trace('Fail .. FindNextPrinterChangeNotification(), PrtName=%s, Error=%d', [sDeviceName_, GetLastError]); continue; end; if Assigned(evPrtChangeNotifyEvent_) then begin if bSync_ then Synchronize(ProcessChangeEvent) else evPrtChangeNotifyEvent_(Self, dwChangeMod_); end; if PNI <> nil then try if (PNI.Flags and PRINTER_NOTIFY_INFO_DISCARDED) <> 0 then begin dwOldFlags := PNO_.Flags; PNO_.Flags := PRINTER_NOTIFY_OPTIONS_REFRESH; FreePrinterNotifyInfo(PNI); FindNextPrinterChangeNotification(hPrtChangeEvent_, dwChangeMod_, @PNO_, Pointer(PNI)); PNO_.Flags := dwOldFlags; end; PrtJobInfo_ := nil; for i := 0 to Integer(PNI.Count) - 1 do begin pData := PPrinterNotifyInfoData(ULONGLONG(@PNI.aData) + (SizeOf(TPrinterNotifyInfoData) * i)); dwJobID := pData.Id; {$IFDEF DEBUG} ASSERT(pData.wType = JOB_NOTIFY_TYPE); {$ENDIF} if JobList_.ContainsKey(dwJobID) then PrtJobInfo_ := JobList_[dwJobID] else PrtJobInfo_ := nil; if not Assigned(PrtJobInfo_) then begin {$IFDEF DEBUG} ASSERT(not (dwChangeMod_ and PRINTER_CHANGE_ADD_JOB) <> 0); {$ELSE} if not ((dwChangeMod_ and PRINTER_CHANGE_ADD_JOB) <> 0) then break; {$ENDIF} PrtJobInfo_ := TPrtJobInfo.Create(hPrinter_, dwJobID); JobList_.Add(dwJobID, PrtJobInfo_); end else begin PrtJobInfo_.PtrJobState := jsWork; // job이 해제되는걸 이런식으로 판단하게 한다.. // 안그러면 계속 쌓이는데 job이 종료되는 시점을 분간할 방법이 없네;; 2011-07-25 kku if (dwChangeMod_ and PRINTER_CHANGE_DELETE_JOB) <> 0 then begin PrtJobInfo_.PtrJobState := jsDelete; end; PrtJobInfo_.UpdatePrtFieldInfo(pData^); // JOB_NOTIFY_FIELD_SUBMITTED 이거 올때까지 값을 채워준다. 22_0719 13:18:58 kku if pData.Field <> JOB_NOTIFY_FIELD_SUBMITTED then continue; if Assigned(evPrtNotifyJobInfoEvent_) then begin if bSync_ then Synchronize(ProcessNotifyJobInfoEvent) else evPrtNotifyJobInfoEvent_(Self, PrtJobInfo_); end; end; end; if (dwChangeMod_ and PRINTER_CHANGE_DELETE_JOB) <> 0 then DeletePrtJob; finally FreePrinterNotifyInfo(PNI); PNI := nil; end; end else break; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Execute()'); end; Sleep(50); end; end; { TTgPrtSpoolWatch } Constructor TTgPrtSpoolWatch.Create(bSync: Boolean; dwWatchMod: DWORD = PRINTER_CHANGE_ALL); begin Inherited Create; bIsWatch_ := false; bSync_ := bSync; dwWatchMod_ := dwWatchMod; PrtList_ := TStringList.Create; PrtList_.CaseSensitive := false; WatchThdList_ := TList.Create; WatchThdList_.OnNotify := OnPrtNotify; end; Destructor TTgPrtSpoolWatch.Destroy; begin StopPrtWatch; Inherited; FreeAndNil(WatchThdList_); FreeAndNil(PrtList_); end; procedure TTgPrtSpoolWatch.OnPrtNotify(Sender: TObject; const Item: TThdPrtSpoolWatch; Action: TCollectionNotification); var nStep: Integer; begin nStep := 0; try case Action of cnAdded: ; cnRemoved: begin nStep := 1; Item.StopThread; nStep := 2; Item.Terminate; nStep := 3; // Item.Free; end; cnExtracted: ; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. OnPrtNotify() .. Step=%d', [nStep]); end; end; procedure TTgPrtSpoolWatch.StartPrtWatch; var i: Integer; thd: TThdPrtSpoolWatch; begin try if Terminated or GetWorkStop then exit; if not bIsWatch_ then begin _Trace('StartPrtWatch() ..', 5); PrtList_.Clear; PrtList_.AddStrings(Printer.Printers); for i := 0 to PrtList_.Count - 1 do begin thd := TThdPrtSpoolWatch.Create(bSync_, PrtList_[i], dwWatchMod_); if thd.LastError = ERROR_SUCCESS then begin WatchThdList_.Add(thd); thd.OnPrtChangeNotifyEvent := evPrtChangeNotifyEvent_; thd.OnPrtNotifyJobInfoEvent := evPtrNotifyInfoEvent_; thd.StartThread; end else FreeAndNil(thd); end; bIsWatch_ := true; _Trace('StartPrtWatch() .. OK', 5); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. StartWatch()'); end; end; procedure TTgPrtSpoolWatch.StopPrtWatch; begin try if Terminated or GetWorkStop then exit; if bIsWatch_ then begin _Trace('StopPrtWatch() ..', 5); bIsWatch_ := false; WatchThdList_.Clear; _Trace('StopPrtWatch() .. OK', 5); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. StopWatch()'); end; end; procedure TTgPrtSpoolWatch.Execute; var llTick, llChkTick: ULONGLONG; ChkPrtList: TStringList; i: Integer; begin llChkTick := GetTickCount64; Guard(ChkPrtList, TStringList.Create); ChkPrtList.CaseSensitive := false; while not Terminated and not GetWorkStop do begin try case GetServiceStatus('Spooler') of SERVICE_RUNNING : begin if bIsWatch_ then begin llTick := GetTickCount64; if (llTick - llChkTick) >= 3000 then // 3초에 한번 프린터 변동사항 체크 25_0827 10:31:17 kku begin llChkTick := llTick; var Prt: TPrinter; Guard(Prt, TPrinter.Create); ChkPrtList.Clear; ChkPrtList.AddStrings(Prt.Printers); if PrtList_.Count <> ChkPrtList.Count then begin // 추가된 프린터 체크 for i := ChkPrtList.Count - 1 downto 0 do begin if PrtList_.IndexOf(ChkPrtList[i]) = -1 then begin _Trace('Printer 추가됨 .. Name=%s', [ChkPrtList[i]], 1); var thd: TThdPrtSpoolWatch := TThdPrtSpoolWatch.Create(bSync_, ChkPrtList[i], dwWatchMod_); if thd.LastError = ERROR_SUCCESS then begin WatchThdList_.Add(thd); thd.OnPrtChangeNotifyEvent := evPrtChangeNotifyEvent_; thd.OnPrtNotifyJobInfoEvent := evPtrNotifyInfoEvent_; thd.StartThread; end else begin _Trace('Fail .. Printer 감시 활성화 실패 .. Name=%s', [ChkPrtList[i]], 1); ChkPrtList.Delete(i); FreeAndNil(thd); end; end; end; // 제거된 프린터 체크 for i := 0 to PrtList_.Count - 1 do begin if ChkPrtList.IndexOf(PrtList_[i]) = -1 then begin var c: Integer; for c := 0 to WatchThdList_.Count - 1 do begin if CompareText(WatchThdList_[c].sDeviceName_, PrtList_[i]) = 0 then begin _Trace('Printer 제거됨 .. Name=%s', [PrtList_[i]], 1); WatchThdList_.Delete(c); break; end; end; end; end; PrtList_.Clear; PrtList_.AddStrings(ChkPrtList); end; end; end else begin _Trace('Print Spooler 서비스 감지됨.', 1); StartPrtWatch; end; end; SERVICE_STOPPED : begin if bIsWatch_ then begin _Trace('Print Spooler 서비스 중지됨.', 1); StopPrtWatch; end; end; end; Sleep(1000); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. Execute()'); end; end; end; //------------------------------------------------------------------------------ { TPrintersInfo } Constructor TPrintersInfo.Create; begin Inherited Create; PrtInfoList_ := TPrtInfoList.Create; PrtInfoList_.OnNotify := OnPrtInfoNotify; end; Destructor TPrintersInfo.Destroy; begin FreeAndNil(PrtInfoList_); Inherited; end; procedure TPrintersInfo.OnPrtInfoNotify(Sender: TObject; const Item: PPrinterInfo; Action: TCollectionNotification); begin if Action = cnRemoved then Dispose(Item); end; function TPrintersInfo.GetPrtInfoByPrtName(const sPrtName: String): PPrinterInfo; var i: Integer; begin Result := nil; try for i := 0 to PrtInfoList_.Count - 1 do begin if CompareText(sPrtName, PrtInfoList_[i].sPrtName) = 0 then begin Result := PrtInfoList_[i]; exit; end; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. GetPrtInfoByPrtName()'); end; end; procedure TPrintersInfo.RefreshList; var pEnt: PPrinterInfo; Reg: TRegistry; procedure FillPrinterDetails; var sRegKey: string; printHandle: THandle; pPrinterInfo: PPrinterInfo2; bytesNeeded: DWORD; begin try Reg.CloseKey; sRegKey := 'SYSTEM\CurrentControlSet\Control\Print\Printers\' + pEnt.sPrtName; if Reg.OpenKeyReadOnly(sRegKey) then begin pEnt.sDrvName := Reg.ReadString('Printer Driver'); pEnt.sPortName := Reg.ReadString('Port'); end; // WSD ports if pEnt.sPortName <> '' then begin Reg.CloseKey; sRegKey := 'SYSTEM\CurrentControlSet\Control\Print\Monitors\WSD Port\Ports\' + pEnt.sPortName; if Reg.KeyExists(sRegKey) then begin pEnt.sIp := pEnt.sPortName; pEnt.PortType := PTWsd; end else begin Reg.CloseKey; sRegKey := 'SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\' + pEnt.sPortName; if Reg.OpenKeyReadOnly(sRegKey) then begin pEnt.PortType := PTTcpIp; end end; end; //절약 모드 및 공유 PC 여부 확인 pPrinterInfo:= nil; try if not OpenPrinter(PChar(pEnt.sPrtName), printHandle, nil) then Exit; GetPrinter(printHandle, 2, nil, 0, @bytesNeeded); if (GetLastError <> ERROR_INSUFFICIENT_BUFFER) or (bytesNeeded = 0) then Exit; GetMem(pPrinterInfo, bytesNeeded); if GetPrinter(printHandle, 2, pPrinterInfo, bytesNeeded, @bytesNeeded) then begin pEnt.dwStatus := pPrinterInfo.Status; if ((pPrinterInfo.Attributes and PRINTER_ATTRIBUTE_NETWORK) <> 0) and ((pPrinterInfo.Attributes and PRINTER_ATTRIBUTE_SHARED) <> 0) and (pPrinterInfo.pShareName <> nil) then begin pEnt.PortType := PTShared; pEnt.sIp:= pPrinterInfo.pShareName; end; if (pEnt.dwStatus and PRINTER_STATUS_POWER_SAVE) <> 0 then pEnt.bIsPowerSaveMode:= True; end; if pEnt.PortType = ptUnknown then begin pEnt.PortType := PTLocal; end; finally if pPrinterInfo <> nil then FreeMem(pPrinterInfo); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. RefreshList() .. FillPrinterDetails()'); end; end; var pPrinters, pCurrentPrinter: PPrinterInfo2; dwPrinters, dwReturned: DWORD; i: Integer; portTypeStr: string; debug: string; begin try PrtInfoList_.Clear; EnumPrinters(PRINTER_ENUM_LOCAL or PRINTER_ENUM_CONNECTIONS, nil, 2, nil, 0, dwPrinters, dwReturned); if dwPrinters = 0 then begin _Trace('Fail .. RefreshList() .. EnumPrinters()', 1); exit; end; Guard(Reg, TRegistry.Create); Reg.RootKey := HKEY_LOCAL_MACHINE; GetMem(pPrinters, dwPrinters); try if EnumPrinters(PRINTER_ENUM_LOCAL or PRINTER_ENUM_CONNECTIONS, nil, 2, pPrinters, dwPrinters, dwPrinters, dwReturned) then begin pCurrentPrinter := pPrinters; for i := 0 to dwReturned - 1 do begin New(pEnt); ZeroMemory(pEnt, SizeOf(TPrinterInfo)); pEnt.sPrtName := pCurrentPrinter.pPrinterName; pEnt.sDrvName := pCurrentPrinter.pDriverName; pEnt.dwStatus := pCurrentPrinter.Status; pEnt.sPortName := pCurrentPrinter.pPortName; FillPrinterDetails; PrtInfoList_.Add(pEnt); //디버그 로깅 // mmo1.Lines.Add(Format('--- Printer [%d]: %s ---', [i, SafePChar(pCurrentPrinter.pPrinterName)])); // mmo1.Lines.Add(Format(' pServerName: %s', [SafePChar(pCurrentPrinter.pServerName)])); // mmo1.Lines.Add(Format(' pShareName: %s', [SafePChar(pCurrentPrinter.pShareName)])); // mmo1.Lines.Add(Format(' pPortName: %s', [SafePChar(pCurrentPrinter.pPortName)])); // mmo1.Lines.Add(Format(' pDriverName: %s', [SafePChar(pCurrentPrinter.pDriverName)])); // mmo1.Lines.Add(Format(' pComment: %s', [SafePChar(pCurrentPrinter.pComment)])); // mmo1.Lines.Add(Format(' pLocation: %s', [SafePChar(pCurrentPrinter.pLocation)])); // mmo1.Lines.Add(Format(' Attributes: 0x%x', [pCurrentPrinter.Attributes])); // mmo1.Lines.Add(Format(' Status: 0x%x', [pCurrentPrinter.Status])); // mmo1.Lines.Add(Format(' cJobs: %d', [pCurrentPrinter.cJobs])); // mmo1.Lines.Add(Format('--------------------', [])); // case printerDetails.portType of // PTLocal: portTypeStr := '로컬'; // PTTcpIp: portTypeStr := 'TCP/IP'; // PTWsd: portTypeStr := 'WSD PORT'; // PTShared: portTypeStr := '공유PC'; // else // portTypeStr := '알 수 없음'; // end; // // listItem := lvPrinters.Items.Add; // listItem.Caption := printerDetails.deviceName; // listItem.SubItems.Add(portTypeStr); // listItem.SubItems.Add(printerDetails.ip); // if printerDetails.isPowerSaveMode then // listItem.SubItems.Add('예') // else // listItem.SubItems.Add('아니요'); // listItem.SubItems.Add(printerDetails.driverName); // listItem.SubItems.Add(printerDetails.portName); // listItem.SubItems.Add(Format('0x%x', [printerDetails.status])); Inc(pCurrentPrinter); end; end; finally FreeMem(pPrinters); end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. RefreshList()'); end; end; procedure TPrintersInfo.SaveToFile(const sPath: String); var O, OA: ISuperObject; i: Integer; begin try OA := TSuperObject.Create(stArray); for i := 0 to PrtInfoList_.Count - 1 do begin OA.AsArray.Add(TTgJson.ValueToJsonObject(PrtInfoList_[i]^)); end; O := SO; O.O['List'] := OA; SaveJsonObjToFile(O, sPath); except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. SaveToFile()'); end; end; procedure TPrintersInfo.LoadFromFile(const sPath: String); var O: ISuperObject; i: Integer; pEnt: PPrinterInfo; begin try PrtInfoList_.Clear; if LoadJsonObjFromFile(O, sPath) then begin if (O.O['List'] = nil) or (O.O['List'].DataType <> stArray) then exit; for i := 0 to O.A['List'].Length - 1 do begin New(pEnt); // ZeroMemory(pEnt, SizeOf(PPrinterInfo)); pEnt^ := TTgJson.GetDataAsType(O.A['List'].O[i]); PrtInfoList_.Add(pEnt); end; end; except on E: Exception do ETgException.TraceException(Self, E, 'Fail .. SaveToFile()'); end; end; //------------------------------------------------------------------------------ function GetDefaultPrinterName: string; var arrBuf: array[0..255] of Char; dwSize: DWORD; begin dwSize := SizeOf(arrBuf); if GetDefaultPrinter(@arrBuf, @dwSize) then Result := StrPas(arrBuf) else Result := 'Unknown'; end; function GetProcessNameByPrtDocName(sDocName: String): String; var sExt: String; begin Result := ''; try sDocName := LowerCase(sDocName); if sDocName.StartsWith('microsoft powerpoint -') then begin Result := 'POWERPNT.EXE'; exit; end else if sDocName.StartsWith('microsoft word -') then begin Result := 'WINWORD.EXE'; exit; end; sExt := GetFileExt(sDocName); if (sExt = 'xls') or (sExt = 'xlsx') then begin Result := 'EXCEL.EXE'; exit; end else if (sExt = 'hwp') or (sExt = 'hwpx') then begin Result := 'hwp.exe'; exit; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetProcessNameByPrtDocName()'); end; end; function PrinterDriverToName(sDrvName: String): String; var dwFlags: DWORD; pBuf: TBytes; dwNeed, dwPrtCnt: DWORD; pInfo: PPrinterInfo2; i: Integer; begin Result := 'Unknown'; try dwNeed := 0; dwPrtCnt := 0; dwFlags := PRINTER_ENUM_LOCAL or PRINTER_ENUM_CONNECTIONS; if not EnumPrinters(dwFlags, nil, 2, nil, 0, dwNeed, dwPrtCnt) then begin // LogToReg('PrinterDriverToName2222-01 .. Error', IntToStr(GetLastError)); // exit; end; if dwNeed > 0 then begin SetLength(pBuf, dwNeed); end else exit; if not EnumPrinters(dwFlags, nil, 2, @pBuf[0], dwNeed, dwNeed, dwPrtCnt) then begin exit; end; if dwPrtCnt > 0 then begin pInfo := PPrinterInfo2(@pBuf[0]); for i := 0 to dwPrtCnt - 1 do begin if CompareText(pInfo.pDriverName, sDrvName) = 0 then begin Result := StrPas(pInfo.pPrinterName); exit; end; Inc(pInfo); end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. PrinterDriverToName()'); end; end; function PrinterDriverToIP(sDrvName: String): String; var dwFlags: DWORD; pBuf: TBytes; dwNeed, dwPrtCnt: DWORD; pInfo: PPrinterInfo2; i: Integer; begin Result := 'Unknown'; try dwNeed := 0; dwPrtCnt := 0; dwFlags := PRINTER_ENUM_LOCAL or PRINTER_ENUM_CONNECTIONS; if not EnumPrinters(dwFlags, nil, 2, nil, 0, dwNeed, dwPrtCnt) then begin // LogToReg('PrinterDriverToName2222-01 .. Error', IntToStr(GetLastError)); // exit; end; if dwNeed > 0 then begin SetLength(pBuf, dwNeed); end else exit; if not EnumPrinters(dwFlags, nil, 2, @pBuf[0], dwNeed, dwNeed, dwPrtCnt) then begin exit; end; if dwPrtCnt > 0 then begin pInfo := PPrinterInfo2(@pBuf[0]); for i := 0 to dwPrtCnt - 1 do begin if CompareText(pInfo.pDriverName, sDrvName) = 0 then begin Result := StrPas(pInfo.pPortName); exit; end; Inc(pInfo); end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. PrinterDriverToName()'); end; end; function IsPJL(const sPath: String): Boolean; begin try if FileExists(sPath) then Result := CheckSign(sPath, @SIG_PJL, Length(SIG_PJL)) else Result := false; except on E: Exception do ETgException.TraceException(E, 'Fail .. IsPJL()'); end; end; function GetQtyFromPJL(const sPath: String; var bCollate: Boolean): Integer; var fs: TFileStream; pBuf: TBytes; sText: AnsiString; nRead, nCopy: Integer; begin bCollate := true; // 기본 "한부씩 인쇄 설정" Result := 0; try if not IsPJL(sPath) then exit; Guard(fs, TFileStream.Create(sPath, fmOpenRead or fmShareDenyNone)); // 앞부분 4KB만 읽기 SetLength(pBuf, 4096); nRead := fs.Read(pBuf[0], Length(pBuf)); if nRead = 0 then exit; // TBytes → AnsiString 변환 SetString(sText, PAnsiChar(@pBuf[0]), nRead); Result := StrToIntDef(GetCapsuleStr('@PJL SET QTY=', #10, UpperCase(sText)), 0); nCopy := StrToIntDef(GetCapsuleStr('@PJL SET COPIES=', #10, UpperCase(sText)), 0); if nCopy > Result then begin // 한부씩 인쇄 안함을 사용할 경우 COPIES 값에 부수 정보가 들어가는거 같다? 25_0904 16:51:06 kku Result := nCopy; bCollate := false; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetQtyFromPJL()'); end; end; function IsPJLAndLanguagePLW(const sPath: string): Boolean; var fs: TFileStream; pBuf: TBytes; sText: AnsiString; nRead: Integer; begin Result := false; try if not IsPJL(sPath) then exit; Guard(fs, TFileStream.Create(sPath, fmOpenRead or fmShareDenyNone)); // 앞부분 4KB만 읽기 SetLength(pBuf, 4096); nRead := fs.Read(pBuf[0], Length(pBuf)); if nRead = 0 then exit; // TBytes → AnsiString 변환 SetString(sText, PAnsiChar(@pBuf[0]), nRead); // LANGUAGE=PLW 존재 여부 확인 if Pos('@PJL ENTER LANGUAGE=PLW', UpperCase(string(sText))) > 0 then Result := true; except on E: Exception do ETgException.TraceException(E, 'Fail .. IsPJLAndLanguagePLW()'); end; end; end.