1775 lines
55 KiB
Plaintext
1775 lines
55 KiB
Plaintext
{*******************************************************}
|
|
{ }
|
|
{ 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<DWORD,TPrtJobInfo>;
|
|
|
|
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<TThdPrtSpoolWatch>;
|
|
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<PPrinterInfo>;
|
|
|
|
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<DWORD,TPrtJobInfo>.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<TPrtJobInfo>;
|
|
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<TThdPrtSpoolWatch>.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<TPrinterInfo>(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<TPrinterInfo>(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.
|