/* Serial Printer Emulator:
* emulates a serial printer on a selectable COM port
* (port settings are adjustable too),
* and redirects it to a Windows printer (even selectable)
*
* Emulation eines seriellen Druckers:
* emuliert einen Drucker an einem auswählbaren COM-Port
* (Anschlusseinstellungen sind ebenfalls auswählbar),
* und leitet den Datenstrom zu einem (auch auswählbaren) Windows-Drucker
*
* Mit einem (käuflichen) Parallel-zu-Seriell-Umsetzer kann man so auch
* Druckausgaben von Parallelports auffangen.
* Für COM-Port-lose Rechner funktionieren auch USB-Seriell-Konverter.
*
* Siehe http://www.tu-chemnitz.de/~heha/hs/mein_msvc.htm
+140430 Voreinstellbare DIP-Schalter für ESC/P, noch ohne Dialog
ESC "R"-Voreinstellung je nach LocaleInfo (einige europäische Länder)
SetupDi-basierte COM-Port-Auflistung (>COM32)
+150818 Dialog mit UpDownControl; Sound-Test beim Rechtsklick auf "..."-Knopf
-150818 Dialog mit Ausgabedatei- oder Ausgabeprozess-Auswahl
*150818 Konfigurations-Speicherort HKCU statt HKLM; x64-Echse; Signierung
*150819 setjmp+longjmp statt Catch+Throw; SEH geht auch aber frisst mehr Platz.
C++-Exceptions gehen nicht (mit msvcrt-light.lib) zu übersetzen.
-221008 Restrukturierung DCB laden und speichern
(fehlendes GetCommDCB() kann zu Fehlern führen)
Keine Codesignatur mehr da eigenes Zertifikat abgelaufen
ShellExecute statt sndPlaySound wenn Endung != WAV
TODO:
Voreinstellungs-Dialog (crlf-Handling, Seitengröße usw.) für ESC/P
Grafikausgabe (sofern Bedarf besteht und Testdatei vorliegt)
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <shlwapi.h>
#include <mmsystem.h>
#include <setupapi.h>
#include <devguid.h>
#include <winspool.h>
#include <shellapi.h>
#include "escp.h"
#define T(x) TEXT(x) // Schreibfaulheit...
#define elemof(x) (sizeof(x)/sizeof(*(x))) // Elemente eines Arrays
#define nobreak // expliziter Durchlauf bei switch/case
#if (_MSC_VER <= 1200) // missing common control definitions (for split button)
struct NMBCDROPDOWN{
NMHDR hdr;
RECT rcButton;
};
#define BCN_DROPDOWN 0xFFFFFB20
#define BS_SPLITBUTTON 0x0000000C
#endif
void _fastcall InitStruct(LPVOID p, UINT len) {
LPUINT pp=(LPUINT)p;
*pp=len; len/=sizeof(UINT); len--;
if (len) do *++pp=0; while (--len);
}
TCHAR MBoxTitle[64];
int vMBox(HWND Wnd, UINT id, UINT style, va_list arglist) {
TCHAR buf1[256],buf2[1024];
LoadString(0,id,buf1,elemof(buf1));
wvnsprintf(buf2,elemof(buf2),buf1,arglist);
return MessageBox(Wnd,buf2,MBoxTitle,style);
}
int _cdecl MBox(HWND Wnd, UINT id, UINT style,...) {
return vMBox(Wnd,id,style,(va_list)(&style+1));
}
static TCHAR ModeString[64]=T("COM1 9600,n,8,1"); // idiotischerweise kommt Parität vor Datenbits
/* Wirkung der Suffixe ",p" (Hardware-Protokoll), ",x" (XON/XOFF-Protokoll)
und keiner (ohne Protokoll) bei BuildCommDCB(ModeString,&DCB):
Suffix ",p" ",x" ohne
fOutxCtsFlow 1 0 0
fOutxDsrFlow 1 0 0
fDtrControl 2 1 1
fRtsControl 2 1 1
fOutX 0 1 0
fInX 0 1 0
*/
struct{
WORD TimeOutMs; // TimeOut zur Druckjob-Beendigung in ms
BYTE PrintFilter; // 0==RAW, 1==ESC/P, 2==ESC/P2, 3==Dateiausgabe, 4==Standardeingabe für Prozess
BYTE keepdef; // Veränderungen durch Druckersteuerkommandos persistent halten
// 1 = Zwischen Einzelaufträgen
// 2 = Zusätzlich zwischen Neustarts von SPE.exe (also Reboots)
POINTS PageOffset; // Verschiebung der Druckdaten auf dem Papier in mm
DEFAULTS def; // DIP-Schalter (Mäuseklavier), siehe <escp.h>
}Config;
COMMTIMEOUTS to;
static TCHAR StartSound[MAX_PATH],EndSound[MAX_PATH];
static TCHAR PrinterName[MAX_PATH];
//Hilfsfunktion
static LONG RegQueryValueExL(HKEY hKey,LPCTSTR Tag,PVOID pValue,DWORD Len) {
return RegQueryValueEx(hKey,Tag,NULL,NULL,(LPBYTE)pValue,&Len);
}
static TCHAR Key0[]=T("Software\\h#s\\SPE");
static TCHAR Key1[]=T("Port");
static TCHAR Key2[]=T("PrinterName");
static TCHAR Key3[]=T("StartSound");
static TCHAR Key4[]=T("EndSound");
static TCHAR Key5[]=T("Config");
static void LoadSettings() {
HKEY hKey;
// Wenns mit Laden schief geht, wenigstens sinnvoll initialisieren
Config.TimeOutMs=5000;
// Schlüssel-Zweig öffnen
if (RegOpenKeyEx(HKEY_CURRENT_USER,Key0,0,KEY_QUERY_VALUE,&hKey)) return;
RegQueryValueExL(hKey,Key1,ModeString,sizeof ModeString);
RegQueryValueExL(hKey,Key2,PrinterName,sizeof PrinterName);
RegQueryValueExL(hKey,Key3,StartSound,sizeof StartSound);
RegQueryValueExL(hKey,Key4,EndSound,sizeof EndSound);
RegQueryValueExL(hKey,Key5,&Config,sizeof(Config));
RegCloseKey(hKey);
}
//Hilfsfunktion
static LONG RegSetValueExSZ(HKEY hKey,LPCTSTR Tag,LPCTSTR Value) {
if (!Value || !*Value) return RegDeleteValue(hKey,Tag);
else return RegSetValueEx(hKey,Tag,0,REG_SZ,
(PBYTE)Value,(lstrlen(Value)+1)*sizeof(TCHAR));
}
static void SaveSettings() {
HKEY hKey;
DWORD dispo;
// Schlüssel-Zweig öffnen
if (RegCreateKeyEx(HKEY_CURRENT_USER,Key0,0,
NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&hKey,&dispo)) return;
// eine Beschreibung schreiben (damit der Anwender weiß, worum es geht)
RegSetValueExSZ(hKey,0,MBoxTitle);
RegSetValueExSZ(hKey,Key1,ModeString);
RegSetValueExSZ(hKey,Key2,PrinterName);
RegSetValueExSZ(hKey,Key3,StartSound);
RegSetValueExSZ(hKey,Key4,EndSound);
RegSetValueEx(hKey,Key5,0,REG_BINARY,(PBYTE)&Config,sizeof(Config));
RegCloseKey(hKey);
}
static void EnabOffsets(HWND Wnd) {
BOOL ena=(unsigned)(Config.PrintFilter-1)<2;
EnableWindow(GetDlgItem(Wnd,22),ena);
EnableWindow(GetDlgItem(Wnd,122),ena);
EnableWindow(GetDlgItem(Wnd,23),ena);
EnableWindow(GetDlgItem(Wnd,123),ena);
int show=Config.PrintFilter<3?SW_SHOW:SW_HIDE;
ShowWindow(GetDlgItem(Wnd,13),show); // Combobox
show^=SW_SHOW^SW_HIDE; // umkehren
ShowWindow(GetDlgItem(Wnd,24),show); // Edit
ShowWindow(GetDlgItem(Wnd,25),show); // Button
TCHAR s[64],*sp=s;
s[LoadString(0,6,s,elemof(s)-1)+1]=0;
for (show=2;show<Config.PrintFilter;sp+=lstrlen(sp)+1) show++;
SetDlgItemText(Wnd,21,sp);
}
static WNDPROC DefButtonProc;
static void DoStartSound();
static void DoEndSound();
// Ungefähr das Verhalten eines SplitButtons nachmachen, für Windows XP.
// Optisch wird nichts verändert.
static LONG_PTR CALLBACK NotifyProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_GETDLGCODE: return CallWindowProc(DefButtonProc,Wnd,msg,wParam,lParam)|DLGC_WANTARROWS;
case WM_KEYDOWN: if (wParam!=VK_DOWN) break; else nobreak; // seltsamerweise geht auch Cursor Hoch!
case WM_CONTEXTMENU: {
NMBCDROPDOWN h;
h.hdr.hwndFrom=Wnd;
h.hdr.idFrom=GetDlgCtrlID(Wnd);
h.hdr.code=BCN_DROPDOWN;
GetWindowRect(Wnd,&h.rcButton);
MapWindowPoints(0,Wnd,(POINT*)&h.rcButton,2);
SendMessage(GetParent(Wnd),WM_NOTIFY,h.hdr.idFrom,(LPARAM)&h);
}return 0;
}
return CallWindowProc(DefButtonProc,Wnd,msg,wParam,lParam);
}
static void MkSplitButton(HWND Wnd) {
if (LOBYTE(GetVersion())>=6) {
DWORD style=GetWindowLong(Wnd,GWL_STYLE);
style|=BS_SPLITBUTTON;
SetWindowLong(Wnd,GWL_STYLE,style);
}else{
DefButtonProc=SubclassWindow(Wnd,NotifyProc);
}
}
static TCHAR stPortName[]=T("PortName");
static INT_PTR CALLBACK SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
TCHAR s[256],*sp;
HWND w;
CreateUpDownControl(WS_VISIBLE|WS_CHILD|WS_BORDER|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_HOTTRACK|UDS_NOTHOUSANDS|UDS_SETBUDDYINT,
0,0,0,0,Wnd,112,0,GetDlgItem(Wnd,12),1000,0,Config.TimeOutMs/1000);
SetDlgItemText(Wnd,16,StartSound);
SetDlgItemText(Wnd,18,EndSound);
s[LoadString(0,5,s,elemof(s)-1)+1]=0;
w=GetDlgItem(Wnd,20); // Combobox
for(sp=s;*sp;sp+=lstrlen(sp)+1) {
ComboBox_AddString(w,sp); // Combobox füllen
}
ComboBox_SetCurSel(w,Config.PrintFilter);
CreateUpDownControl(WS_VISIBLE|WS_CHILD|WS_BORDER|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_HOTTRACK|UDS_NOTHOUSANDS|UDS_SETBUDDYINT,
0,0,0,0,Wnd,122,0,GetDlgItem(Wnd,22),500,-500,Config.PageOffset.x);
CreateUpDownControl(WS_VISIBLE|WS_CHILD|WS_BORDER|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_HOTTRACK|UDS_NOTHOUSANDS|UDS_SETBUDDYINT,
0,0,0,0,Wnd,123,0,GetDlgItem(Wnd,23),500,-500,Config.PageOffset.y);
SetDlgItemText(Wnd,24,PrinterName);
MkSplitButton(GetDlgItem(Wnd,17));
MkSplitButton(GetDlgItem(Wnd,19));
EnabOffsets(Wnd);
SendMessage(Wnd,WM_TIMER,1,0);
}return TRUE;
case WM_DEVICECHANGE: SetTimer(Wnd,1,2500,NULL); break;
case WM_TIMER: if (wParam==1) {
KillTimer(Wnd,wParam);
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
HWND hCombo=GetDlgItem(Wnd,10);
ComboBox_ResetContent(hCombo);
HANDLE devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
if (devs!=INVALID_HANDLE_VALUE) {
DWORD ComNr=StrToInt(ModeString+3); // Einsbasierte Ist-Portnummer
SP_DEVINFO_DATA devInfo;
devInfo.cbSize=sizeof devInfo;
for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
HKEY hKey;
TCHAR s[16];
*s=0;
if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
==INVALID_HANDLE_VALUE) continue;
RegQueryValueExL(hKey,stPortName,s,sizeof s);
RegCloseKey(hKey);
if (*s=='C') { // Fehlschläge und LPTx ausfiltern
int idx=ComboBox_AddString(hCombo,PTSTR(s));
DWORD num=StrToInt(s+3); // auch hier: einsbasiert
ComboBox_SetItemData(hCombo,idx,num);
if (num==ComNr) ComboBox_SetCurSel(hCombo,idx);
}
}
SetupDiDestroyDeviceInfoList(devs);
}
// Drucker (neu) listen (richtig bei WM_DEVICECHANGE?)
DWORD NeedBytes=0,ArrLen,LastError;
PRINTER_INFO_5 *pPI=NULL;
hCombo=GetDlgItem(Wnd,13);
ComboBox_ResetContent(hCombo);
do{ // Endlosschleife, um dem Multitasksystem wechselnde Drucker zu erlauben
LastError=0;
if (EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS,
NULL,5,(LPBYTE)pPI,NeedBytes,&NeedBytes,&ArrLen)) break;
LastError=GetLastError();
if (pPI) LocalFree(pPI);
pPI=(PRINTER_INFO_5*)LocalAlloc(LPTR,NeedBytes);
}while(pPI && LastError==ERROR_INSUFFICIENT_BUFFER);
if (!pPI || LastError) break;
for(UINT i=0; i<ArrLen; i++) {
int idx=ComboBox_AddString(hCombo,pPI[i].pPrinterName);
if (!lstrcmpi(pPI[i].pPrinterName,PrinterName))
ComboBox_SetCurSel(hCombo,idx);
}
LocalFree(pPI);
}break;
case WM_NOTIFY: {
NMHDR*h=(NMHDR*)lParam;
if (h->code==BCN_DROPDOWN) {
NMBCDROPDOWN*h=(NMBCDROPDOWN*)lParam;
HMENU m=CreatePopupMenu();
TCHAR s[32];
LoadString(0,9,s,elemof(s));
AppendMenu(m,MF_STRING,2,s);
MapWindowPoints(h->hdr.hwndFrom,0,(POINT*)&h->rcButton,2);
if (TrackPopupMenu(m,TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RETURNCMD|TPM_RIGHTBUTTON,
h->rcButton.left,h->rcButton.bottom,0,Wnd,NULL)==2) switch (h->hdr.idFrom) {
case 17: {
GetDlgItemText(Wnd,16,StartSound,elemof(StartSound));
DoStartSound();
}break;
case 19: {
GetDlgItemText(Wnd,18,EndSound,elemof(EndSound));
DoEndSound();
}break;
}
DestroyMenu(m);
}
}break;
case WM_COMMAND: switch (LOBYTE(wParam)) {
case 10: switch (GET_WM_COMMAND_CMD(wParam,lParam)) {
case CBN_SELCHANGE: {
TCHAR buf[elemof(ModeString)], *rest=StrChr(ModeString,' ');
if (rest) ++rest;
wnsprintf(buf,elemof(buf),T("COM%u %s"),
ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam)),
rest);
StrCpyN(ModeString,buf,elemof(ModeString));
}break;
}break;
case 11: {
TCHAR ComName[8];
wnsprintf(ComName,elemof(ComName),T("COM%u"),StrToInt(ModeString+3));
COMMCONFIG cc;
InitStruct(&cc,sizeof cc);
cc.dcb.DCBlength=sizeof cc.dcb;
BuildCommDCB(ModeString,&cc.dcb);
if (CommConfigDialog(ComName,Wnd,&cc)) {
static const TCHAR*stopbits[]={T("1"),T("1.5"),T("2")};
const TCHAR*proto=T("");
if (cc.dcb.fInX) proto=T(",x");
else if (cc.dcb.fOutxDsrFlow) proto=T(",p");
wnsprintf(ModeString,elemof(ModeString),
T("%s %u,%c,%u,%s%s"),
ComName,
cc.dcb.BaudRate,
"noems"[cc.dcb.Parity], // no/odd/even/mark/space
cc.dcb.ByteSize,
stopbits[cc.dcb.StopBits], // 1.5 gibt's praktisch nie
proto
);
}
}break;
case 13: switch (GET_WM_COMMAND_CMD(wParam,lParam)) {
case CBN_SELCHANGE: {
ComboBox_GetLBText((HWND)lParam,ComboBox_GetCurSel((HWND)lParam),PrinterName);
}break;
}break;
case 17:
case 19: // Dateiauswahl für WAV-Dateien
case 25: { // Dateiauswahl für PRN-Datei oder ausführbare Datei
int strid=3;
OPENFILENAME ofn;
TCHAR FileName[MAX_PATH];
TCHAR Filter[256];
wParam--;
InitStruct(&ofn,sizeof(ofn));
ofn.lpstrFile=FileName;
ofn.nMaxFile=elemof(FileName);
ofn.lpstrFilter=Filter;
ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST;
if (wParam==24) {
strid=Config.PrintFilter-3+7;
if (Config.PrintFilter==3) ofn.Flags=OFN_HIDEREADONLY|OFN_PATHMUSTEXIST;
}
Filter[LoadString(0,strid,Filter,elemof(Filter)-1)+1]=0;
GetDlgItemText(Wnd,(int)wParam,FileName,elemof(FileName));
if ((ofn.Flags&OFN_FILEMUSTEXIST?GetOpenFileName:GetSaveFileName)(&ofn)) {
SetDlgItemText(Wnd,(int)wParam,FileName);
}
}break;
case 20: switch (GET_WM_COMMAND_CMD(wParam,lParam)) { // Druckerfilter
case CBN_SELCHANGE: {
Config.PrintFilter=ComboBox_GetCurSel((HWND)lParam);
EnabOffsets(Wnd); // Dialog verändern
}break;
}break;
case IDOK: {
BOOL ok;
UINT id;
HWND hWndItem=GetDlgItem(Wnd,10);
if (ComboBox_GetCurSel(hWndItem)<0) {
SetFocus(hWndItem);
break; // nicht OK!
}
if ((int)Config.PrintFilter<0) {
SetFocus(GetDlgItem(Wnd,20));
break; // nicht OK!
}
if (Config.PrintFilter<3) {
hWndItem=GetDlgItem(Wnd,13);
if (ComboBox_GetCurSel(hWndItem)<0) {
SetFocus(hWndItem);
break; // nicht OK!
}
}else GetDlgItemText(Wnd,24,PrinterName,elemof(PrinterName));
id=22;
Config.PageOffset.x=GetDlgItemInt(Wnd,id,&ok,TRUE);
if (!ok) goto editfail; // nicht OK!
id=23;
Config.PageOffset.y=GetDlgItemInt(Wnd,id,&ok,TRUE);
if (!ok) goto editfail; // nicht OK!
id=12;
Config.TimeOutMs=GetDlgItemInt(Wnd,id,&ok,FALSE)*1000;
if (!Config.TimeOutMs) { // Fehler, muss >0 sein
editfail:
hWndItem=GetDlgItem(Wnd,id);
Edit_SetSel(hWndItem,0,-1);
SetFocus(hWndItem);
break; // kein EndDialog()
}
GetDlgItemText(Wnd,16,StartSound,elemof(StartSound));
GetDlgItemText(Wnd,18,EndSound,elemof(EndSound));
}nobreak;
case IDCANCEL: {
EndDialog(Wnd,LOWORD(wParam));
}break;
}break;
}
return FALSE;
}
// Wie Windows NT auf allen Windows-Versionen (da fehlt ein shlwapi.Beep?)
#ifdef _M_IX86
static BOOL MyBeep(DWORD dwFreq, DWORD dwDuration) {
if ((long)GetVersion()>=0) return Beep(dwFreq,dwDuration);
else{ // Win32s, Win9x/Me
if (dwFreq<1843200L/0x10000/2) return FALSE;
dwFreq=1843200L/2/dwFreq; // Teilerwert
_asm{
cli
mov al,0xB6 // Timer2: Rechteckgenerator, 16bit
out 0x43,al
mov eax,dwFreq
out 0x42,al // Timer2: Endwert Low-Teil setzen
xchg ah,al
out 0x42,al // Timer2: Endwert High-Teil setzen
in al,0x61
or al,0x03
out 0x61,al // Ein- und Ausgang des Timer2 freischalten
sti
}
Sleep(dwDuration);
_asm{
cli
in al,0x61
and al,~0x03
out 0x61,al // Ein- und Ausgang des Timer2 sperren
sti
}
return TRUE;
}
}
#else
#define MyBeep Beep
#endif
static DWORD WINAPI TwoBeep(void*p) {
return MyBeep(LOWORD(p),200) && MyBeep(HIWORD(p),200);
}
static DWORD WINAPI Exec(void*p) {
const TCHAR*filename=(const TCHAR*)p;
return DWORD(ShellExecute(0,0,filename,0,0,SW_SHOWDEFAULT));
}
static bool DoSound(const TCHAR*filename) {
if (!filename) return false;
if (!*filename) return false;
// Der Returnwert von sndPlaySound ist immer true auch wenn's keine Audiodatei ist!
// Daher Endung angucken
if (!StrCmpI(PathFindExtension(filename),T(".wav"))
&& sndPlaySound(filename,SND_ASYNC)) return true;
// ShellExecute kann muddeln. Daher über gesonderten Thread starten
CreateThread(0,0,Exec,(void*)filename,0,0);
return true;
}
static void DoStartSound() {
if (!DoSound(StartSound)) CreateThread(0,0,TwoBeep,(void*)MAKELONG(300,400),0,0);
// Nicht beim Piepsen blockieren, sofort (parallel) Drucker öffnen
}
static void DoEndSound() {
if (!DoSound(EndSound)) CreateThread(0,0,TwoBeep,(void*)MAKELONG(400,300),0,0);
}
// Enthält der Dateiname einen Stern,
// wird dieser durch eine Dezimalzahl ersetzt und eine neue Datei erzeugt.
// Wenn nicht, wird die Datei zum Anhängen geöffnet.
static HANDLE CreateUniqueOrAppend(PTSTR filename) {
//_tcsftime()...
HANDLE ret=0;
PTSTR p=StrChr(filename,'*');
if (p) {
*p=0; // String dort zerhacken
for (DWORD i=1;;i++) {
TCHAR fname[MAX_PATH];
wnsprintf(fname,elemof(fname),T("%s%u%s"),filename,i,p+1);
ret=CreateFile(fname,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,0,0);
if (ret!=INVALID_HANDLE_VALUE) break; // Neue Datei erfolgreich erzeugt
ret=0;
if (GetLastError()!=ERROR_ALREADY_EXISTS) break; // sonstige Fehler brechen ab
}
*p='*'; // zurück-ersetzen
}else{
ret=CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,0,0);
if (ret==INVALID_HANDLE_VALUE) ret=0;
else if (GetLastError()==ERROR_ALREADY_EXISTS) SetFilePointer(ret,0,NULL,FILE_END);
}
return ret;
}
int TransferJob(HANDLE hCom) {
#ifndef _DEBUG
to.ReadTotalTimeoutConstant=0;
SetCommTimeouts(hCom,&to);
// zunächst unendlich blockieren, bis das erste Zeichen kommt
DWORD ev;
ClearCommError(hCom,&ev,NULL);
SetCommMask(hCom,EV_RXCHAR);
if (!WaitCommEvent(hCom,&ev,NULL)) return -1;
SetCommMask(hCom,0);
#endif
DoStartSound();
#ifndef _DEBUG
// fortan per TimeOut blockieren, um das letzte Zeichen des Druckjobs abzuschätzen
to.ReadTotalTimeoutConstant=Config.TimeOutMs;
SetCommTimeouts(hCom,&to);
#endif
switch (Config.PrintFilter) {
case 4: {
STARTUPINFO si;
PROCESS_INFORMATION pi;
InitStruct(&si,sizeof(si));
si.dwFlags=STARTF_USESTDHANDLES;
si.hStdInput=hCom;
CreateProcess(NULL,PrinterName,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi);
WaitForSingleObject(pi.hProcess,INFINITE);
DoEndSound();
}break;
case 3: { // Dateiausgabe
HANDLE hFile=CreateUniqueOrAppend(PrinterName);
if (!hFile) return -2;
BYTE buffer[1024];
DWORD br,bw;
while (ReadFile(hCom,buffer,sizeof(buffer),&br,NULL) && br) {
if (!WriteFile(hFile,buffer,br,&bw,NULL) || br!=bw) {
CloseHandle(hFile);
return -2;
}
}
DoEndSound();
CloseHandle(hFile);
}break;
case 1:
case 2: { // Ausgabe mit ESC/P-Interpretation an (GDI-)Drucker
HDC dc=CreateDC((int)GetVersion()>=0?T("WINSPOOL"):NULL,PrinterName,NULL,NULL);
if (!dc) goto Ende0;
DOCINFO di;
InitStruct(&di,sizeof(di));
di.lpszDocName=MBoxTitle;
StartDoc(dc,&di);
if (!Config.def.ly) ESCP::Default(&Config.def); // Zeilenabstand Null nicht zulassen!
ESCP escp;
escp.Init(&Config.def); // Vorgaben einsetzen
escp.Offset.x=MulDiv(Config.PageOffset.x,21600,254);
escp.Offset.y=MulDiv(Config.PageOffset.y,21600,254);
escp.mode=Config.PrintFilter;
escp.f=hCom;
escp.dc=dc;
escp.Filter();
if (Config.keepdef) {
escp.Save(&Config.def); // Vorgaben retten
if (Config.keepdef&2) SaveSettings(); // persistent sogar über Reboots
}
EndDoc(dc);
DeleteDC(dc);
DoEndSound();
}break;
case 0: {
HANDLE hPrinter;
if (!OpenPrinter(PrinterName,&hPrinter,NULL)) goto Ende0;
DOC_INFO_1 di;
InitStruct(&di,sizeof(di));
di.pDocName=MBoxTitle;
// di.pOutputFile=T("d:\\ttt.prn"); // Test!!
di.pDatatype=T("RAW");
if (!StartDocPrinter(hPrinter,1,(LPBYTE)&di)) goto Ende1;
if (!StartPagePrinter(hPrinter)) goto Ende2;
BYTE buffer[1024];
DWORD br,bw;
while (ReadFile(hCom,buffer,sizeof(buffer),&br,NULL) && br) {
if (!WritePrinter(hPrinter,buffer,br,&bw) || br!=bw) goto Ende3;
}
DoEndSound();
if (!EndPagePrinter(hPrinter)) goto Ende2;
if (!EndDocPrinter(hPrinter)) goto Ende1;
if (!ClosePrinter(hPrinter)) goto Ende0;
return 0;
Ende3: EndPagePrinter(hPrinter);
Ende2: EndDocPrinter(hPrinter);
Ende1: ClosePrinter(hPrinter);
}
Ende0: return -2;
}
return 0;
}
int WinMainCRTStartup() {
HANDLE hCom;
LPTSTR Args;
Args=PathGetArgs(GetCommandLine());
LoadString(0,0,MBoxTitle,elemof(MBoxTitle));
// Vorgaben laden
LoadSettings();
// Benutzerabfrage (bei Notwendigkeit oder Kommandozeilenschalter)
if (!StrToInt(ModeString+3) || !PrinterName[0] || *Args) {
rep1:
if (DialogBox(0,MAKEINTRESOURCE(1),0,SetupDlgProc)!=IDOK) ExitProcess(0);
SaveSettings();
}
#ifdef _DEBUG
static TCHAR ComName[]=T("Draft.prn");
#else
TCHAR ComName[12];
// serielle Schnittstelle öffnen
wnsprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),StrToInt(ModeString+3));
#endif
hCom=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,0);
if (hCom==INVALID_HANDLE_VALUE) switch (MBox(0,4,MB_RETRYCANCEL,StrToInt(ModeString+3))) {
case IDRETRY: goto rep1;
default: ExitProcess(3);
}
#ifndef _DEBUG
DCB dcb;
dcb.DCBlength=sizeof dcb;
GetCommState(hCom,&dcb);
BuildCommDCB(ModeString,&dcb);
// COMx [baud=b] [parity=p] [data=d] [stop=s] [to={on|off}]
// [xon={on|off}] [odsr={on|off}] [octs={on|off}] [dtr={on|off|hs}]
// [rts={on|off|hs|tg}] [idsr={on|off}]
// oder
// COMx baud,parity,data,stop [,[xp]]
SetCommState(hCom,&dcb);
#endif
for (;;) switch (TransferJob(hCom)) {
#ifdef _DEBUG
case 0: ExitProcess(0); // Debug: Keine Wiederholungen
#endif
case -1: switch (MBox(0,1,MB_RETRYCANCEL,StrToInt(ModeString+3))) {
case IDRETRY: CloseHandle(hCom); goto rep1;
}ExitProcess(1);
case -2: switch (MBox(0,2,MB_RETRYCANCEL,PrinterName)) {
case IDRETRY: CloseHandle(hCom); goto rep1;
}ExitProcess(2);
}
// Dieses Programm kann nur gekillt werden.
return 0;
}
/*******************
* Hilfsfunktionen *
*******************/
#ifdef _M_IX86
#if (_MSC_VER>=1400)
#define _alloca_probe _alloca_probe_16
#endif
extern "C" void _declspec(naked) _cdecl _alloca_probe() {
_asm{ dec eax
and al,0xFC
sub esp,eax // make room
jmp dword ptr[esp+eax] // fetch return address and jump
}
}
#elif defined(_M_AMD64)
extern "C" void __chkstk() {} // As all allocations are << 4KByte, stack checking is never needed.
#endif
Vorgefundene Kodierung: ANSI (CP1252) | 4
|
|