BSOne.SFC/Tocsg.Lib/VCL/Tocsg.Printer.pas

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.