/****************************************************************
* DCF77-Funkuhr-Empfänger über serielles oder paralleles Port *
* haftmann#software, Juni 2007 *
****************************************************************/
/* Änderungen: -=Bugfix, +=neues Feature, *=Änderung
-070703 DefConfig-Laden (war alles Null), ZuLang begrenzt auf 200
*070703 ShellVersion für echte Sprechblase unter Me
+080801 Serielle Eingabedaten an RxD eingebaut (Polarität hierbei fest:
Trägerabsenkung = positive Spannung, voller Träger = negative Spannung)
*080817 Keine initialisierten RW-Daten (/GF, leider ohne Änderung der EXE-Größe)
*200902 syntaktische Feinheit: rndint durch lrint und rndint64 durch llrint ersetzt
*/
#include "Funkuhr.h"
#define FARBE_KURZ RGB(128,128,0) // kurze Pulse: dunkelgelb
#define FARBE_LANG RGB(255,255,0) // lange Pulse: hellgelb
//#define FARBE_KEIN RGB(128,128,128)
#define FARBE_FEHL RGB(192,0,0) // fehlerhafte Pulse: rot
#define FARBE_DIVI RGB(0,0,255) // Diskriminator-Linien: blau gepunktet
#define FARBE_ZGR RGB(0,0,255) // Stunden- und Minutenzeiger (3 Pixel breit)
#define FARBE_ZGR2 RGB(64,255,255)
HINSTANCE ghInstance; // "zeigt" zur Nur-Ressource-DLL oder enthält 0x400000
HINSTANCE ghThisInst; // enthält 0x400000, bei Win64 'was anderes
NOTIFYICONDATA nid;
#define WM_TrayNotify (WM_USER+200)
#define WM_ShowPropWnd (WM_USER+202)
TConfig Config; // Persistente Daten
TGdiObj GdiObj; // GDI-Objekte
TEmpfang Empfang; // Empfangsdaten
HWND PropWnd; // Eigenschaftsfenster (!0 wenn vorhanden)
long (WINAPI*Decrypt)(BYTE*); // Funktionszeiger
DWORD Cache24[480]; // 2 sinnvolle kByte sollten in der Registry auch noch Platz haben
TCHAR sDecimal[2]; // Dezimaltrennzeichen (Punkt oder Komma) für Koordinatenangaben
TCHAR sTime[2]; // Uhrzeit-Trennzeichen (typ. Doppelpunkt) für Sonnen/Mond-Auf/Untergangszeiten
TCHAR sGrad[4]; // Grad-Zeichen (oder "grd")
static HICON ghIcons[2];
static WORD ShellVersion; // zur Zuschaltung von Sprechblasen
BYTE CmdLineOpt; // Bit 0 = startet mit Einstell-Dialog
// Bit 1 = startet mit Karte
// Bit 2 = schaltet Detektion von laufender Instanz ab
// Bit 3 = Speicherort HKLM statt HKCU
// Bit 4 = Als Dienst (versuchen) - unter W2K dauert StartServiceCtrlDispatcher() zu lange!
bool iMeasure; // 0 = metrisch, 1 = zöllig
bool ChmHelp; // CHM(HTML)- statt HLP(RTF)-Hilfe
char TryReOpen; // Wiederholversuche beim Öffnen des Gerätehandles
DWORD WinVer; // LOBYTE: 4=Win98,Me; 5=2k,XP; 6=Vista,7,8,10
TCHAR HelpName[MAX_PATH];
TCHAR CacheName[MAX_PATH];
// Size = Breite, Höhe der Bitmap (16, 32 oder 48)
// Besser wäre das Arbeiten mit einer Alphakanal-Bitmap (geht das??),
// statt mit einer "krummen" festen Transparentfarbe
#define TRANSCOLOR RGB(190,190,190)
static HDC BeginPaintIcon(int Size) {
HDC dc1,dc; // Bildschirm-DC, Zeichen-DC
HBRUSH br;
dc1=GetDC(0);
dc=CreateCompatibleDC(dc1);
SelectBitmap(dc,CreateCompatibleBitmap(dc1,Size,Size));
br=SelectBrush(dc,CreateSolidBrush(GetNearestColor(dc1,TRANSCOLOR)));
ReleaseDC(0,dc1);
PatBlt(dc,0,0,Size,Size,PATCOPY);
DeleteBrush(SelectBrush(dc,br)); // weißen Pinsel zurück
return dc;
}
// dc = Ergebnis von BeginPaintIcon
static HICON EndPaintIcon(HDC dc) {
HICON ret;
ICONINFO ii;
BITMAP bm; // Höhe/Breite der übergebenen Bitmap (32x32?)
HDC dcMono;
dcMono=CreateCompatibleDC(dc);
ii.fIcon=TRUE;
ii.hbmColor=GetCurrentObject(dc,OBJ_BITMAP);
GetObject(ii.hbmColor,sizeof(bm),&bm);
ii.hbmMask=CreateCompatibleBitmap(dcMono,bm.bmWidth,bm.bmHeight);
SelectBitmap(dcMono,ii.hbmMask);
SetBkColor(dc,TRANSCOLOR);
// erzeugt 1-Bits bei transparenten Pixeln (Hintergrund), sonst 0-Bits
BitBlt(dcMono,0,0,bm.bmWidth,bm.bmHeight,dc,0,0,SRCCOPY);
// löscht transparente Pixel zu 0 (schwarz); keine XOR-Wirkung
BitBlt(dc,0,0,bm.bmWidth,bm.bmHeight,dcMono,0,0,0x00220326/*DSna*/);
DeleteDC(dcMono);
DeleteDC(dc);
ret=CreateIconIndirect(&ii);
DeleteBitmap(ii.hbmMask);
DeleteBitmap(ii.hbmColor);
return ret;
}
static void DrawIconUhr(HDC dc,bool hell) {
int i;
SelectBrush(dc,(&GdiObj.brKurz)[hell]);
SelectPen(dc,GetStockPen(NULL_PEN));
Ellipse(dc,0,0,17,17);
SetViewportOrgEx(dc,8,8,NULL);
if (Empfang.LastOK) {
SelectPen(dc,GdiObj.peZgr[hell]);
i=(Empfang.Dek.bHour*60+Empfang.Dek.bMinute)/12;
Line(dc,0,0,sinus(4,i),sinus(4,i+45));
i=Empfang.Dek.bMinute;
Line(dc,0,0,sinus(7,i),sinus(7,i+45));
}else{
static const POINT PolyPt[]={
{-5,5},{-3,0},{-1,5}};
SelectBrush(dc,GdiObj.brBlau[hell]);
SelectPen(dc,GdiObj.peBlau[hell]);
if (Empfang.Luecke) {
i=Empfang.Sek;
OffsetViewportOrgEx(dc,sinus(5,i+37)+3,
sinus(5,i+37+45)-4,NULL);
Polygon(dc,PolyPt,elemof(PolyPt));
SetViewportOrgEx(dc,8,8,NULL);
}else{
Polygon(dc,PolyPt,elemof(PolyPt));
switch (Empfang.Sek&3) {
case 3: Arc(dc,-37,-11,7,14, 7,3,4,-10); nobreak;
case 2: Arc(dc,-34, -8,4,11, 4,2,4, -9); nobreak;
case 1: Arc(dc,-31, -5,1, 8, 4,1,4, -6);
}
}
}
if (Empfang.Luecke) {
SelectPen(dc,GetStockPen(hell?BLACK_PEN:WHITE_PEN));
i=Empfang.Sek;
Line(dc,0,0,sinus(15,i),sinus(15,i+45));
}
SetViewportOrgEx(dc,0,0,NULL);
}
void MakeIcons(void) {
HDC dc;
if (ghIcons[0] && DestroyIcon(ghIcons[0])) ghIcons[0]=0;
if (ghIcons[1] && DestroyIcon(ghIcons[1])) ghIcons[1]=0;
if (AnimYes()) {
if (Config.TrayIconBlink) {
dc=BeginPaintIcon(16);
DrawIconUhr(dc,false);
ghIcons[0]=EndPaintIcon(dc);
}
dc=BeginPaintIcon(16);
DrawIconUhr(dc,true);
ghIcons[1]=EndPaintIcon(dc);
}else{
ghIcons[1]=LoadImage(ghInstance,MAKEINTRESOURCE(100),IMAGE_ICON,16,16,0);
}
}
// Erstellt alle global verwendeten GDI-Objekte,
// ggf. abhängig von Systemfarben!
static void CreateGdiObj(void) {
LOGFONT lf;
GdiObj.brKurz=CreateSolidBrush(FARBE_KURZ);
GdiObj.brLang=CreateSolidBrush(FARBE_LANG);
GdiObj.brFehl=CreateSolidBrush(FARBE_FEHL);
GdiObj.peDivi=CreatePen(PS_DOT,0,FARBE_DIVI);
GdiObj.peXor =CreatePen(PS_SOLID,0,
GetSysColor(COLOR_3DFACE)^GetSysColor(COLOR_WINDOWTEXT));
GdiObj.fnQuer=CreateFont(20,0,0,0,FW_BOLD,0,0,0,0,0,0,0,0,T("Arial"));
GetObject(GetStockFont(DEFAULT_GUI_FONT),sizeof(lf),&lf);
lf.lfItalic=TRUE;
GdiObj.fnKursiv=CreateFontIndirect(&lf);
lf.lfItalic=FALSE;
lf.lfWeight=FW_BOLD;
GdiObj.fnFett=CreateFontIndirect(&lf);
GdiObj.peZgr[0]=CreatePen(PS_SOLID,2,FARBE_ZGR2); // dicker Stift (Zeiger)
GdiObj.peZgr[1]=CreatePen(PS_SOLID,2,FARBE_ZGR);
GdiObj.peBlau[0]=CreatePen(PS_SOLID,0,FARBE_ZGR2); // dünner Stift (Bögen)
GdiObj.peBlau[1]=CreatePen(PS_SOLID,0,FARBE_ZGR);
GdiObj.brBlau[0]=CreateSolidBrush(FARBE_ZGR2); // Pinsel (Dreieck)
GdiObj.brBlau[1]=CreateSolidBrush(FARBE_ZGR);
}
// Löscht alle global verwendeten GDI-Objekte
static void DeleteGdiObj(void) {
int i;
for (i=0; i<sizeof(GdiObj)/sizeof(HGDIOBJ); i++) {
DeleteObject(((HGDIOBJ*)&GdiObj)[i]);
}
}
static bool alternative_loaded;
// Standardmäßig wird nur von/nach HKCU (HKEY_CURRENT_USER) geladen/gespeichert.
// Nach HKLM (HKEY_LOCAL_MACHINE) wird nur gespeichert, wenn von da aus auch geladen wurde.
// Umkehren lässt sich HKCU und HKLM mit Bit 3 des Kommandozeilenschalters.
static bool LoadConf(HKEY root) {
HKEY key;
DWORD size=sizeof(Config);
if (!RegOpenKeyEx(root,T("Software\\h#s\\Funkuhr"),
0,KEY_QUERY_VALUE,&key)) {
RegQueryValueEx(key,T("Config"),NULL,NULL,(LPBYTE)&Config,&size);
if (Decrypt) {
DWORD buf[elemof(Cache24)];
size=sizeof(Cache24);
RegQueryValueEx(key,T("Cache24"),NULL,NULL,(LPBYTE)&buf,&size);
if (size==sizeof(Cache24)) Cache24Merge(buf);
}
RegCloseKey(key);
return true;
}
return false;
}
// Lädt Konfigurationsdaten aus Registrierung
static void LoadConfig(void) {
static const TConfig DefConfig={
{16,16}, // WinPos
0, // Where: Seriell
0, // SerialNo: COM1
1, // SerialLineIn: DSR
1, // SerialLineOut: RTS
0x378, // ParallelAddr: LPT1
014, // ParallelLineIn: ONL
023, // ParallelLineOut: SEL
8, // Piep: ziemlich laut
MINU, // Minuten: alle
50, // ZuKurz: 50 ms
160, // Trenn: 160 ms
270/2, // ZuLang: 270 ms
100, // MaxJitter: 100 ms
0, // MinuteZurueck: aktuelle Minute
29, // Region: Sachsen
0x5F, // Checkmarks: alles außer SYSTEMTIME
8, // AfterHour: alle 8 Stunden
0, // MsgAtDiff: immer
0, // LastActionCode: keine
0, // LastActionTime: keine
0, // LastSet: nicht
0, // TrayIconVis: immer sichtbar
1, // TrayIconBlink: ja
1, // TrayIconAus: aus wenn OK
0, // ActivePropSheet: Hardware
5, // ToNoSignal: 5 s
120, // ToRepNoSig: 120 s
10, // ToBalloon: 10 s
15, // ToSilence: 15 min
0, // cbSort
0x51, // kShow
0, // kMini
0xBF, // kVis
{0,0,0,0}, // kPos
{0,0}, // kMitte
0, // kScale
0x50, // kAutoScale: kleine (nicht winzige) Symbol- und Schriftgröße
1, // kWetter: heute Tag
-1, // uRegion: keine (uPos deaktiviert)
{307,756}, // uPos: Chemnitz
0, // iSoundCard
200, // iSampleRate: 200 kSa/s
77500/2, // iFiltFreq: Geradeausempfänger (erfordert deaktivierten Tiefpass)
0, // iAFC
0x11, // iDemodAGC: AFC eingeschaltet, mittlerer Amplitudenwert
0, // iJoystick
0, // iJoyButton
0, // iUsbPrn
0x0D, // iUsbInput: PaperEnd(12)
0, // iUsbHid
0x82, // pedantic: Fehlerkorrektur erlaubt, prüfe Empfang.Okay[minute+1]
{-32768,0}, // kMiniPos
};
#ifdef _M_AMD64
__movsd((DWORD*)&Config,(DWORD*)&DefConfig,sizeof(Config)/4);
#else
Config=DefConfig;
#endif
if (Decrypt) Config.TrayIconAus=2; // Vorgabe: immer empfangen wenn HKW581-Chip vorhanden
if (CacheName[0]) {
HANDLE h=CreateFile(CacheName,GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,0);
if (h!=INVALID_HANDLE_VALUE) {
DWORD dw;
ReadFile(h,Cache24,sizeof(Cache24),&dw,NULL);
CloseHandle(h);
}
}
if (CmdLineOpt&8) {
alternative_loaded=LoadConf(HKEY_CURRENT_USER);
LoadConf(HKEY_LOCAL_MACHINE); // HKLM ist bestimmend
}else{
if (IsUserAdmin()) alternative_loaded=LoadConf(HKEY_LOCAL_MACHINE);
LoadConf(HKEY_CURRENT_USER); // HKCU ist bestimmend
}
}
void SaveConf(HKEY root, WHATSAVE what) {
HKEY key;
RegSetValue(root,T("Software\\h#s"),REG_SZ,
T("haftmann#software"),17*sizeof(TCHAR));
if (!RegCreateKeyEx(root,T("Software\\h#s\\Funkuhr"),
0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) {
if (what&CONFIG) {
TCHAR s[64];
int l=LoadString(ghInstance,1,s,elemof(s))+1;
RegSetValueEx(key,NULL,0,REG_SZ,(BYTE*)s,l*sizeof(TCHAR));
RegSetValueEx(key,T("Config"),0,REG_BINARY,(LPBYTE)&Config,sizeof(Config));
}
if (what&CACHE24 && Decrypt) {
RegSetValueEx(key,T("Cache24"),0,REG_BINARY,(LPBYTE)&Cache24,sizeof(Cache24));
}
RegCloseKey(key);
}
}
// Speichert Konfigurationsdaten in Registrierung
// Bei <alles> == false wird nur "Cache24" aktualisiert
void SaveConfig(WHATSAVE what) {
if (CmdLineOpt&8) {
SaveConf(HKEY_LOCAL_MACHINE,what);
if (alternative_loaded) SaveConf(HKEY_CURRENT_USER,what);
}else{
SaveConf(HKEY_CURRENT_USER,what);
if (alternative_loaded) SaveConf(HKEY_LOCAL_MACHINE,what);
}
if (what&CACHE24 && CacheName[0]) {
HANDLE h=CreateFile(CacheName,GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,TRUNCATE_EXISTING,0,0);
if (h!=INVALID_HANDLE_VALUE) {
DWORD dw;
WriteFile(h,Cache24,sizeof(Cache24),&dw,NULL);
CloseHandle(h);
}
}
}
// Liefert true wenn Icon/Sound animiert werden sollte
bool AnimYes(void) {
return Config.TrayIconAus==3 || Empfang.Ein==3;
}
// liefert true wenn Icon gezeigt werden sollte
static bool GetTrayIconVis(void) {
return PropWnd
|| AnimYes() && Config.TrayIconVis<2
|| !Config.TrayIconVis;
}
static void HideTrayIconIf(void) {
if (nid.uFlags&NIF_INFO) return; // warten bis Sprechblase verschwindet!
Shell_NotifyIcon(NIM_DELETE,&nid);
}
static void ShowTrayIcon(void) {
nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;
nid.uVersion=NOTIFYICON_VERSION; // Features (W2K, WXP) aktivieren
Shell_NotifyIcon(NIM_ADD,&nid);
}
static void UpdateTrayIcon(void) {
if (nid.uFlags&NIF_INFO && ShellVersion<0x0500) Sprechblase();
if (Shell_NotifyIcon(NIM_MODIFY,&nid)) return;
ShowTrayIcon();
}
static void UpdateTrayIconIf(void) {
if (GetTrayIconVis()) UpdateTrayIcon();
}
void AnimateTrayIcon() {
nid.hIcon=ghIcons[AnimYes()?Empfang.Signal:1];
if (!nid.hIcon) return; // keine dunkle Version: nichts tun!
nid.uFlags=NIF_ICON;
UpdateTrayIconIf();
if (PropWnd) {
SendMessage(PropWnd,WM_SETICON,ICON_SMALL,(LPARAM)nid.hIcon);
}
}
static void RetrievePropWndPos(void) {
RECT r;
if (!PropWnd) return;
GetWindowRect(PropWnd,&r);
Config.WinPos.x=(short)r.left;
Config.WinPos.y=(short)r.top;
}
static void FillPsp(PROPSHEETPAGE *psp, int i) {
static const DLGPROC PropDlgProcs[9]={
HardwareDlgProc,
StellenDlgProc,
TrayIconDlgProc,
UeberDlgProc,
EmpfangDlgProc,
DiagnoseDlgProc, // Titel = "Bit-Anzeige"
HistogrammDlgProc,
WetterDlgProc,
DemodulatorDlgProc};
InitStruct(psp,sizeof(*psp));
psp->dwFlags=HelpName[0]?PSP_HASHELP|PSP_USEICONID:PSP_USEICONID;
psp->hInstance=ghInstance;
psp->pszTemplate=MAKEINTRESOURCE(101+i);
psp->pszIcon=MAKEINTRESOURCE(101+i);
psp->pfnDlgProc=PropDlgProcs[i];
}
/**************************************************
* Fummeleien mit dem Owner der Eigenschaftsseite *
**************************************************/
// Der Besitzer lässt sich nur beim Erzeugen des Fensters festlegen.
// Wenn dieser verschwindet, muss das Fenster neu erzeugt werden
void PropRespawn() {
if (PropWnd) {
RetrievePropWndPos();
SendMessage(PropWnd,PSM_PRESSBUTTON,PSBTN_CANCEL,0);
DestroyWindow(PropWnd);
PropWnd=0;
ShowProperties();
}
}
// Eigenschaftsseite Nr. iTemplate an Stelle iPos einfügen, oder iPos löschen.
// Die Fenstergröße wird angepasst.
void PropInsDelSheet(int iTemplate, int iPos) {
if (PropWnd) {
if (iTemplate<0) {
PropSheet_RemovePage(PropWnd,iPos,0); // offenbar wird der Speicher freigegeben
}else{
PROPSHEETPAGE psp;
FillPsp(&psp,iTemplate);
PropSheet_InsertPage(PropWnd,iPos,CreatePropertySheetPage(&psp));
}
PropSheet_RecalcPageSizes(PropWnd);
}
}
// Am besten ist diese Einstellung im Systemmenü des Eigenschafts-Dialogs
// aufgehoben. Hier wird das Menü verlängert.
// Die umgekehrte Prozedur ist nur erforderlich, wenn das Kartenfenster verschwindet.
void PropAppendSysMenu() {
TCHAR s[32];
HMENU m=GetSystemMenu(PropWnd,FALSE);
LoadString(ghInstance,47,s,elemof(s));
AppendMenu(m,Config.Checkmarks&0x80?MF_CHECKED:MF_UNCHECKED,0x10,s);
}
// Die Nachrichten des Eigenschaftsfensters lassen sich offenbar
// nur mit einer Unterklassenprozedur abfangen.
static WNDPROC DefPropWndProc;
static LRESULT CALLBACK PropWndProc(HWND Wnd, int msg, WPARAM wParam, LPARAM lParam) {
if (Wnd==PropWnd && msg==WM_SYSCOMMAND && (wParam&0xFFF0)==0x10) {
Config.Checkmarks^=0x80;
if (hFirst) PropRespawn();
}
return CallWindowProc(DefPropWndProc,Wnd,msg,wParam,lParam);
}
// sicherheitshalber mehrfach aufrufen
static void myInitCommonControls(void) {
static const INITCOMMONCONTROLSEX icc={sizeof(icc),
ICC_USEREX_CLASSES|ICC_WIN95_CLASSES};
assert(InitCommonControlsEx((INITCOMMONCONTROLSEX*)&icc));
}
// HTML-Hilfe:
// * Im Dienst-Modus aufrufbar machen
// * Mit CSS3-Features (runde Ecken) ausstatten
static void PrepareChmHelp(DWORD hieve) {
HKEY msk;
if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,
T("SOFTWARE\\Microsoft"),0,KEY_CREATE_SUB_KEY|KEY_SET_VALUE|hieve,&msk)) {
HKEY key;
if (CmdLineOpt&0x10 && !RegCreateKeyEx(msk, // als Dienst gestartet?
T("HTMLHelp\\1.x\\HHRestrictions"),
0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) {
DWORD val=1;
RegSetValueEx(key,T("EnableNonInteractiveUser"),0,REG_DWORD,(LPBYTE)&val,sizeof val);
RegCloseKey(key);
}
if (!RegCreateKeyEx(msk,
T("Internet Explorer\\MAIN\\FeatureControl\\FEATURE_BROWSER_EMULATION"),
0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) {
TCHAR s[MAX_PATH];
DWORD val=9000; // Versionsnummer
GetModuleFileName(0,s,elemof(s));
RegSetValueEx(key,PathFindFileName(s),0,REG_DWORD,(LPBYTE)&val,sizeof val);
RegCloseKey(key);
}
RegCloseKey(msk);
}
}
// Aufruf des Eigenschaften-Dialogs (Menüpunkt oder linker Mausklick)
void ShowProperties() {
if (PropWnd) {
if (hFirst && Config.Checkmarks&0x80 && IsIconic(hFirst)) ShowWindow(hFirst,SW_RESTORE);
SetActiveWindow(PropWnd);
BringWindowToTop(PropWnd);
}else{
int i,j;
PROPSHEETHEADER psh;
PROPSHEETPAGE psp[9];
myInitCommonControls(); // Versuch: Noch einmal wegen Fehlstart beim Dienst-Start (W2k)
InitStruct(&psh,sizeof(psh));
psh.dwFlags=PSH_NOAPPLYNOW|PSH_PROPSHEETPAGE|PSH_PROPTITLE|
PSH_USEICONID|PSH_MODELESS|(HelpName[0]?PSH_HASHELP:PSH_NOCONTEXTHELP);
if (Config.Checkmarks&0x80) psh.hwndParent=hFirst;
psh.hInstance=ghInstance;
psh.pszIcon=MAKEINTRESOURCE(100);
psh.pszCaption=MAKEINTRESOURCE(1);
psh.nStartPage=Config.ActivePropSheet;
psh.ppsp=psp;
for (i=j=0; i<elemof(psp); i++) {
if (i==7 && !Decrypt) continue;
if (i==8 && Config.Where!=2) continue;
FillPsp(psp+j,i);
j++;
}
psh.nPages=j;
PropWnd=(HWND)PropertySheet(&psh);
if (hFirst) PropAppendSysMenu();
DefPropWndProc=SubclassWindow(PropWnd,PropWndProc);
// Es erscheint NICHT möglich, die Position eher festzulegen!
SetWindowPos(PropWnd,0,Config.WinPos.x,Config.WinPos.y,0,0,SWP_NOSIZE|SWP_NOZORDER);
SendMessage(PropWnd,DM_REPOSITION,0,0);
PropSheet_CancelToClose(PropWnd);
EnableDlgItem(PropWnd,2,TRUE);
// MoveWindow(GetDlgItem(PropWnd,2),0,0,0,0,TRUE);
ShowWindow(GetDlgItem(PropWnd,2),SW_HIDE);
// SetDlgItemText(PropWnd,1,T("Schließen"));
SendMessage(PropWnd,WM_SETICON,ICON_BIG,(LPARAM)LoadIcon(ghThisInst,MAKEINTRESOURCE(100)));
MakeIcons();
AnimateTrayIcon();
}
}
// Langweilige Auswertung der Hilfeanforderung zusammengefasst:
void DefHelpProc(HWND Wnd, UINT Msg, LPARAM lParam, UINT id) {
switch (Msg) {
case WM_NOTIFY: {
LPPSHNOTIFY psn=(LPPSHNOTIFY)lParam;
switch (psn->hdr.code) {
case PSN_HELP: {
if (ChmHelp) assert(HtmlHelp(Wnd,HelpName,HH_HELP_CONTEXT,MAKELONG(0,id)));
else assert(WinHelp(Wnd,HelpName,HELP_CONTEXT,MAKELONG(0,id)));
}break;
}
}break;
case WM_HELP: {
HELPINFO*hi=(HELPINFO*)lParam;
if (ChmHelp) {
PTSTR p=HelpName+lstrlen(HelpName);
lstrcpyn(p,T(">popup"),(int)(HelpName+elemof(HelpName)-p));
assert(HtmlHelp(Wnd,HelpName,HH_HELP_CONTEXT,MAKELONG(hi->iCtrlId,id)));
*p=0;
}else assert(WinHelp(Wnd,HelpName,HELP_CONTEXTPOPUP,MAKELONG(hi->iCtrlId,id)));
}break;
}
}
U64 GetSystemFileTime(bool bLocal) {
U64 ft,lft;
GetSystemTimeAsFileTime(&ft.ft); // performanter als GetSystemTime() und SystemTimeToFileTime()
if (!bLocal) return ft;
FileTimeToLocalFileTime(&ft.ft,&lft.ft);
return lft;
}
static DWORD GetSystemDosTime() {
union{
WORD w[2];
DWORD dw;
}ret;
U64 ft=GetSystemFileTime(false);
FileTimeToDosDateTime(&ft.ft,ret.w+1,ret.w);
return ret.dw;
}
// Wecker stellen - fürs nächste Mal Uhr stellen
void StartWecker(void) {
U64 ft1,ft2;
if (!Config.LastSet) goto Sofortstart;
ft2=GetSystemFileTime(false);
DosDateTimeToFileTime(HIWORD(Config.LastSet),LOWORD(Config.LastSet),&ft1.ft);
ft2.ull-=ft1.ull; // jetzt Differenz (in 100-ns-Schritten)
// Eine riesige Zahl (eigentlich negativ) ergibt sich, wenn LastSet
// in der Zukunft liegt; dann sowieso sofort starten mit dem Stellen
ft1.ull=HOUR2FILETIME(Config.AfterHour);
if (ft2.ull>=ft1.ull) {
Sofortstart:
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,3));
PostMessage(MainWnd,WM_SetActivity,3,0);
}else{
ft1.ull-=ft2.ull; // Zeitlicher Abstand bis zum gewünschten Stellen
ft1.dw[0]=(DWORD)(ft1.ull/10000); // Millisekunden; 255 Stunden passen in's DWORD
SetTimer(MainWnd,100,ft1.dw[0],NULL);
}
}
static void _cdecl Blase(UINT idIcon, UINT Timeout, UINT idTitle, UINT idText, ...) {
va_list va;
TCHAR buf[256];
va_start(va,idText);
nid.uFlags=NIF_INFO;
nid.uTimeout=Timeout*Config.ToBalloon;
nid.dwInfoFlags=idIcon;
LoadString(ghInstance,idTitle,nid.szInfoTitle,elemof(nid.szInfoTitle));
LoadString(ghInstance,idText,buf,elemof(buf));
wvnsprintf(nid.szInfo,elemof(nid.szInfo),buf,va);
UpdateTrayIcon(); // immer zeigen!
// Unter Win2k fehlendes NIN_BALLOONTIMEOUT nachliefern
if (ShellVersion==0x0500) SetTimer(MainWnd,103,Timeout*1000,NULL);
}
static void SetLastActionCode(BYTE Code) {
Config.LastActionCode=Code;
Config.LastActionTime=GetSystemDosTime();
InfoPropSheet(13,0);
}
// Berechnet vzl. Zeitdifferenz aus <Empfang.ZeitNeu> und <Empfang.ZeitAlt>
// und liefert das Vorzeichen
static bool CalcTimeDiff(U64 *diff){
diff->ll=Empfang.ZeitNeu.ll-Empfang.ZeitAlt.ll;
if ((LONG)diff->dw[1]<0) {diff->ll=-diff->ll; return true;}
return false;
}
// Vorzeichen (normalerweise aus Systemsteuerung!) liefern
// BUG: Problematisch ist '±' bspw. in Griechenland oder Weißrussland (bei ANSI-Kompilat)
static TCHAR Vorzeichen(bool sign, DWORD zahl) {
return zahl?sign?T(''):T('+'):T('±');
}
// PE: Empfang.ZeitNeu enthält von Funkuhr bereitgestellte Zeit
void ComputerUhrStellen() {
SYSTEMTIME st;
Empfang.ZeitAlt=GetSystemFileTime(!(Config.Checkmarks&32)); // retten zum Zeitvergleich
FileTimeToSystemTime(&Empfang.ZeitNeu.ft,&st);
Empfang.SelfTimeChange=true;
if ((Config.Checkmarks&32?SetSystemTime:SetLocalTime)(&st)) {
if (LOBYTE(WinVer)<5) PostMessage(HWND_TOPMOST,WM_TIMECHANGE,0,0);
if (!(Config.Checkmarks&32)) geotz=(((int)Empfang.Data[Empfang.Index]>>17&1)+1)*-60;
}else{
TCHAR s_diff[64];
U64 diff;
bool sign=CalcTimeDiff(&diff);
diff.ull/=10000; // Millisekunden; Überlauf bei 49 Tagen
StrFromTimeInterval(s_diff,elemof(s_diff),diff.dw[0],6);
Blase(NIIF_ERROR,9,39,40,Vorzeichen(sign,diff.dw[0]),s_diff); /* fehlende Rechte monieren */
SetLastActionCode(39);
}
if (!Config.Checkmarks&2 || Config.AfterHour) {
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,Config.TrayIconAus>=2?2:0));
PostMessage(MainWnd,WM_SetActivity,Config.TrayIconAus>=2?2:0,0);
}
}
bool SystemTimeToString(const SYSTEMTIME*st,UINT DateFormat,UINT TimeFormat,LPTSTR p,int len) {
int l=GetDateFormat(LOCALE_USER_DEFAULT,DateFormat,st,NULL,p,len);
if (!l) return false;
p+=l; len-=l;
p[-1]=' ';
return GetTimeFormat(LOCALE_USER_DEFAULT,TimeFormat,st,NULL,p,len)!=0;
}
// FileTime in String (Datum + Uhrzeit) umwandeln (dazwischen 1 Leerzeichen)
bool FileTimeToString(const FILETIME*pft,LPTSTR p,int len,UINT DateFormat,UINT TimeFormat,bool ToLocalTime) {
SYSTEMTIME st;
if (ToLocalTime) {
FILETIME ft;
FileTimeToLocalFileTime(pft,&ft); // Zeiger dürfen nicht gleich sein lt. Doku
pft=&ft;
}
FileTimeToSystemTime(pft,&st);
return SystemTimeToString(&st,DateFormat,TimeFormat,p,len);
}
void SetTrayTip(void) {
LPTSTR p=nid.szTip;
LPTSTR e=nid.szTip+elemof(nid.szTip);
p+=LoadString(ghInstance,1,p,(int)(e-p)); // "Funkuhr" (Programmtitel)
// "(auf Empfang)" oder "(inaktiv)" oder so ähnlich
p+=LoadString(ghInstance,Empfang.Ein+28,p,(int)(e-p));
if (Empfang.LastOK) {
// Ab Windows 2000, XP: empfangene Zeit auf zweite Zeile, sonst auf nur einer Zeile möglich
// Bei einzeilig ist die Zeichenzahl auf 64 begrenzt, daher kurzes Datumsformat ausgeben
p+=LoadString(ghInstance,ShellVersion<0x0500?20:19,p,(int)(e-p)); // " - " / "\nEmpfangene Zeit:"
SystemTimeToString(&Empfang.Dek.st,ShellVersion<0x0500?DATE_SHORTDATE:DATE_LONGDATE,TIME_NOSECONDS,p,(int)(e-p));
}
nid.uFlags|=NIF_TIP;
UpdateTrayIconIf();
}
void TrayIconVisChanged(void) {
if (GetTrayIconVis()) UpdateTrayIcon();
else HideTrayIconIf();
}
static void ZeigeStellDifferenz(void) {
U64 diff;
bool sign=CalcTimeDiff(&diff); // Vorzeichen und Betrag
DbgPrintf(("%s(%d) : ZeigeStellDifferenz\n",__FILE__,__LINE__));
if (diff.ull>=24*(ULONGLONG)36000000000) { // mehr als ±24h: immer Warnung ausgeben
TCHAR s_alt[64],s_neu[64];
FileTimeToString(&Empfang.ZeitAlt.ft,s_alt,elemof(s_alt),DATE_LONGDATE,0,Config.Checkmarks&32);
FileTimeToString(&Empfang.ZeitNeu.ft,s_neu,elemof(s_neu),DATE_LONGDATE,0,Config.Checkmarks&32);
Blase(NIIF_WARNING,9,34,38,s_alt,s_neu);
SetLastActionCode(41);
}else if (Config.Checkmarks&16 // weniger als ±24h: auf Wunsch Info ausgeben
&& diff.dw[0]>=Config.MsgAtDiff*10000000UL) {
diff.dw[0]/=10000; // jetzt Millisekunden
if (diff.dw[0]<1000) { // nur Millisekunden (MsgAtDiff=0)
Blase(NIIF_INFO,1,34,35,Vorzeichen(sign,diff.dw[0]),diff.dw[0]);
}else{
TCHAR s_diff[64];
StrFromTimeInterval(s_diff,elemof(s_diff),diff.dw[0],6);
Blase(NIIF_INFO,3,34,36+sign,s_diff);
}
}
}
/******************************************************
* Eine DLL-Version lässt sich nur dynamisch erfragen *
******************************************************/
// ermittelt Versionsnummer einer DLL (High=Haupt, Low=Neben)
static WORD GetDllVersion(PCSTR LibName) {
WORD ret=0;
HINSTANCE hLib=LoadLibraryA(LibName);
if (hLib) {
DLLGETVERSIONPROC DllGetVersion=(DLLGETVERSIONPROC)GetProcAddress(hLib,"DllGetVersion");
if (DllGetVersion) {
DLLVERSIONINFO dvi;
dvi.cbSize=sizeof(dvi);
if (SUCCEEDED(DllGetVersion(&dvi))) ret=MAKEWORD(dvi.dwMinorVersion,dvi.dwMajorVersion);
}
FreeLibrary(hLib);
}
return ret;
}
static void SetMitternachtsTimer() {
U64 ft=GetSystemFileTime(true);
ft.ull/=10000; // Millisekunden
ft.ull%=24*60*60*1000; // Millisekunden des laufenden Tages
SetTimer(MainWnd,104,24*60*60*1000-ft.Lo,NULL);
}
// Suche vorhergehendes oder nächstes Thread-Fenster, für F6
// Mit dieser Struktur und einer verrückten Enum-Prozedur
// kann man nachher zirkulär durch alle Fenster schalten,
// ohne alle (in ein dynamisches Array) aufzeichnen zu müssen,
// oder 2x enumerieren zu müssen.
typedef struct{
HWND first,last,next,prev,anchor;
int n; // Anzahl aller Thread-Fenster
int match; // -1 = noch nicht gefunden, n = Fundstelle
}ETWP;
static BOOL CALLBACK EnumThreadWndProc(HWND Wnd, LPARAM lParam) {
TCHAR n[8];
GetClassName(Wnd,n,elemof(n));
if (!lstrcmpi(n,KARTECLASSNAME) && GetWindowStyle(Wnd)&WS_SYSMENU || Wnd==PropWnd) { // nur diese zählen
ETWP *etwp=(ETWP*)lParam;
if (!etwp->n) etwp->first=Wnd; // am Anfang mitmeißeln
if (Wnd==etwp->anchor) etwp->match=etwp->n;
if (etwp->match<0) etwp->prev=Wnd; // mitmeißeln bis gefunden
else if (etwp->match==etwp->n-1) etwp->next=Wnd; // 1 hinter Fundstelle mitmeißeln
etwp->last=Wnd; // immer mitmeißeln
etwp->n++;
}
return TRUE;
}
//Kartenfenster und PropertySheet durchzählen
static HWND GetThreadWindowSibling(HWND anchor, UINT cmd) {
ETWP etwp;
__stosd((DWORD*)&etwp,0,sizeof(etwp)/4);
etwp.anchor=anchor;
etwp.match--;
EnumThreadWindows(GetCurrentThreadId(),EnumThreadWndProc,(LPARAM)&etwp);
if (etwp.match<0) etwp.prev=0;
switch (cmd) {
case GW_HWNDFIRST:
case GW_HWNDLAST:
case GW_HWNDNEXT:
case GW_HWNDPREV: return &etwp.first[cmd]; // liegen bereits in der Struktur in der passenden Reihenfolge!
case 10: return etwp.next?etwp.next:etwp.first; // wie GW_HWNDNEXT, aber umlaufend
case 11: return etwp.prev?etwp.prev:etwp.last; // wie GW_HWNDPREV, aber umlaufend
}
return 0; // bei Fehler
}
static LRESULT CALLBACK MainWndProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
static UINT RegMsg_TaskbarCreated;
switch (Msg) {
case WM_CREATE: {
RegMsg_TaskbarCreated=RegisterWindowMessage(T("TaskbarCreated"));
TryReOpen=2; // mehrere Versuche (auf Arbeit geht manchmal das schnöde COM-Port nicht)
LoadConfig();
CreateGdiObj();
MakeIcons();
ShellVersion=GetDllVersion("shell32.dll");
InitStruct(&nid,sizeof(nid));
nid.hWnd=Wnd;
nid.uID=1;
nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;
nid.uCallbackMessage=WM_TrayNotify;
nid.hIcon=ghIcons[1];
SetTrayTip();
if (Config.Checkmarks&1) { // beim Programmstart
PostMessage(Wnd,WM_TIMER,100,0);
}else if (Config.Checkmarks&2) { // aller xx Stunden
StartWecker();
}
SendMessage(Wnd,WM_WININICHANGE,0,0);
LoadString(ghInstance,71,sGrad,elemof(sGrad));
InitTZ();
SetMitternachtsTimer();
if (CmdLineOpt&1) ShowProperties(); // (dient eher als Debughilfe)
if (CmdLineOpt&2) Karte();
}break;
case WM_WININICHANGE: { // s.a. GetNumberFormat()
GetProfileString(T("Intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
GetProfileString(T("Intl"),T("sTime"),T(":"),sTime,elemof(sTime));
iMeasure=(bool)GetProfileInt(T("Intl"),T("iMeasure"),0);
}break;
case WM_FUNKRECV: {
// DbgPrintf(("%s(%d) : Handle_WM_FUNKRECV(%d)\n",__FILE__,__LINE__,wParam));
if (Empfang.Ein==3) SetTimer(Wnd,101,5000,NULL);
// zum Signalausfall melden (5 Sekunden, nur in der »heißen Phase«)
OnSignalChange((bool)wParam,(DWORD)lParam);
}break;
case WM_TIMER: switch ((BYTE)wParam) {
case 1: OnTimerPulsAuswertung(); break; // 250 ms nach Impulsstart (Filter)
case 2: OnTimerSek59(); break; // 1 s nach Impulsstart (59. Sekunde)
case 100: { // Wecker abgelaufen
KillTimer(Wnd,wParam);
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,3));
PostMessage(Wnd,WM_SetActivity,3,0);
}break;
case 101: { // Kein Signal (»Empfang.Ein« muss hier ==3 sein)
SetTimer(Wnd,101,Config.ToRepNoSig*1000,NULL); // die nächste Blase in 2 Minuten
Blase(NIIF_WARNING,3,32,33); // egal was »Config.TrayIconVis« sagt!
Empfang.LastOK=0; // keine Zeiger!
Empfang.Signal=true; // für MakeIcons(): helles Icon erzwingen
MakeIcons();
AnimateTrayIcon();
SetLastActionCode(32); // erwähnenswert?
}break;
case 102: { // 15 Minuten Empfang sind um! (»Empfang.Ein« muss hier ==3 sein!)
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,Config.TrayIconAus?1:0));
PostMessage(Wnd,WM_SetActivity,Config.TrayIconAus?1:0,0);
}break;
case 103: { // Win2k fehlendes NIN_BALLOONTIMEOUT nachreichen
KillTimer(Wnd,wParam);
PostMessage(nid.hWnd,nid.uCallbackMessage,nid.uID,NIN_BALLOONTIMEOUT);
}break;
case 104: { // Mitternachtstimer
InfoPropSheet(17,0); // Tageswechsel: Wettersymbole „nachrücken“ lassen
SetMitternachtsTimer();
}break;
}break;
case WM_TIMECHANGE: if (Empfang.SelfTimeChange) {
Empfang.SelfTimeChange=false;
Config.LastSet=GetSystemDosTime();
ZeigeStellDifferenz();
InfoPropSheet(10,0);
}else{
DbgPrintf(("Systemzeit extern verstellt\n"));
if (Config.Checkmarks&4) {
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,3));
PostMessage(Wnd,WM_SetActivity,3,0);
}
SetMitternachtsTimer();
}break;
case WM_POWERBROADCAST:
case WM_POWER: {
DbgPrintf(("WM_POWER(BROADCAST) mit wParam=%d\n",wParam));
TryReOpen=2;
}break;
case WM_TryReOpen: { // Worker-Thread bittet um Terminierung und Neustart
DbgPrintf(("Worker-Tread: Neustart?, TryReOpen=%d\n",TryReOpen));
if (TryReOpen) { // Nur wenn WM_POWER oder Programmstart vorausging
EndeEmpfang(); // (unabhängig von Empfang.Ein)
Sleep(200); // blockiert GUI-Thread! Sollte hier nicht stören
StartEmpfang();
}
}break;
case WM_ShowPropWnd: { // Zweiter Programmstart
ShowProperties(); // PropSheet öffnen - und Tray-Icon zeigen
}break;
case WM_TrayNotify: switch (lParam) {
case WM_LBUTTONDOWN:
case NIN_SELECT:
case NIN_KEYSELECT: {
ShowProperties();
}break;
case WM_RBUTTONDOWN:
case WM_CONTEXTMENU: {
POINT p;
HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(100));
HMENU sm=GetSubMenu(m,0);
SetMenuDefaultItem(sm,102,0);
if (!Decrypt) DeleteMenu(sm,103,0);
else if (hFirst) CheckMenuItem(sm,103,MF_CHECKED);
if (PropWnd) CheckMenuItem(sm,102,MF_CHECKED);
if (Empfang.Ein==3) CheckMenuItem(sm,101,MF_CHECKED);
else if (Empfang.Ein) CheckMenuRadioItem(sm,101,101,101,0); // Stiller Empfang: mit Punkt
GetCursorPos(&p);
SetForegroundWindow(Wnd); // soll so sein
TrackPopupMenu(sm,TPM_RIGHTALIGN|TPM_RIGHTBUTTON,
p.x,p.y,0,Wnd,NULL);
PostMessage(Wnd,WM_NULL,0,0); // soll so sein
Shell_NotifyIcon(NIM_SETFOCUS,&nid); // soll so sein
DestroyMenu(m);
}break;
// Diese Messages scheinen nicht unter W2k aufzutauchen! Ersatz??
case NIN_BALLOONTIMEOUT: // Signalausfall-Meldung weg?
case NIN_BALLOONUSERCLICK: { // (oder "fehlende Rechte")
nid.uFlags&=~NIF_INFO;
if (!GetTrayIconVis()) HideTrayIconIf(); // Icon verschwinden lassen!
}break;
}break;
case WM_COMMAND: switch (LOBYTE(wParam)) {
case 10:
case 11: { // F6: Fenster durchschalten
// (den Fokus zum Tray-Icon schalten geht zwar auch, aber von da aus geht's mit F6 nicht wieder weg)
SetActiveWindow(GetThreadWindowSibling(GetActiveWindow(),LOBYTE(wParam)));
}break;
case 12: { // F7: Zum Tray-Icon gehen (Win2k und höher)
Shell_NotifyIcon(NIM_SETFOCUS,&nid);
}break;
case 13: { // Shift+F7: Kontextmenü bedienen (bspw. zum Beenden) für Win98 ohne Maus
SendMessage(Wnd,WM_TrayNotify,0,WM_CONTEXTMENU);
}break;
case 101: { // Empfang ein/aus (zu tun!)
DbgPrintf(("%s(%d) : WM_SetActivity(%d)\n",__FILE__,__LINE__,Empfang.Ein?0:3));
PostMessage(Wnd,WM_SetActivity,Empfang.Ein==3?0:3,0);
}break;
case 102: { // Eigenschaften....
ShowProperties();
}break;
case 103: Karte(); break; // Wetterkarte
case 2: { // Beenden
SendMessage(Wnd,WM_CLOSE,0,0);
}break;
}break;
case WM_SetActivity: if (wParam!=Empfang.Ein) {
BYTE vorher=Empfang.Ein; // alter Zustand
Empfang.Ein=(BYTE)wParam; // jetzt (synchron) setzen
DbgPrintf(("%s(%d) : Handle WM_SetActivity(%d->%d)\n",__FILE__,__LINE__,vorher,Empfang.Ein));
if (Config.TrayIconAus<3 && vorher==3 && Config.Where!=2 && Config.Piep
&& Empfang.Signal && !Empfang.DauerPiep) {
StopBeep(); // Schweigen!
if (Empfang.Ein!=3) DoneBeep();
}
KillTimer(Wnd,102); // kein 15-Minuten-Timer
KillTimer(Wnd,101); // kein Signalausfall-Timer
switch ((BYTE)wParam) {
case 3: { // „heißer“ Empfang
if (Config.TrayIconVis<2) SetTrayTip();
if (vorher!=3 && Config.Where!=2 && Config.Piep && !Empfang.DauerPiep)
InitBeep((BYTE)(10-Config.Piep));
if (!vorher && !StartEmpfang()) {
MBox(Wnd,Config.Where<2?42+Config.Where:44,MB_OK,Config.Where ? INPOUTDLL : (PTSTR)(Config.SerialNo+1));
Config.ActivePropSheet=0; // Hardware-Dialog zeigen
ShowProperties();
}
SetTimer(Wnd,101,Config.ToNoSignal*1000,NULL); // Für's „Kein Signal“ ausgeben
if (Config.TrayIconAus<2)
SetTimer(Wnd,102,Config.ToSilence*60000,NULL); // Für Empfangs-Abbruch
}break;
case 2: // "kalter" Empfang nach (erfolgreichem) Stellen der Computer-Uhr
case 1: { // "kalter" Empfang nach erfolglosem Empfang nach 15 Minuten
if (!vorher) StartEmpfang();
}goto RuheIcon;
case 0: { // kein Empfang, Empfänger WIRKLICH aus
Empfang.LastOK=Empfang.Luecke=0;
if (vorher) EndeEmpfang();
RuheIcon:
if (!GetTrayIconVis()) HideTrayIconIf();
else {
MakeIcons();
AnimateTrayIcon(); // setze Standard-Bild
SetTrayTip(); // setze „ausgeschaltet“
}
}break;
}
InfoPropSheet(12,vorher);
}break;
case WM_ENDSESSION: if (wParam) {
RetrievePropWndPos();
SaveConfig(CONFIG);
}break;
case WM_DESTROY: {
if (hFirst) DestroyWindow(hFirst); // Positionsdaten retten lassen
RetrievePropWndPos();
SaveConfig(CONFIG);
nid.uFlags&=~NIF_INFO; Sprechblase();// Sprechblase entfernen
HideTrayIconIf();
EndeEmpfang();
DeleteGdiObj();
PostQuitMessage(0);
}break;
default:
if (Msg==RegMsg_TaskbarCreated) { // Explorer-Absturz-Behandlung
if (GetTrayIconVis()) ShowTrayIcon();
}
}
return DefWindowProc(Wnd,Msg,wParam,lParam);
}
// Hilfsfunktion, liefert NULL(!) wenn Datei vorhanden
static PTSTR LocateFileHelper(PTSTR buf, PCTSTR name) {
PTSTR p=PathFindFileName(buf); // Zeiger auf Dateiname
lstrcpyn(p,name,(int)(buf+MAX_PATH-p));// Dateiname ersetzen
if (PathFileExists(buf)) return NULL;
return p; // im „Fehlerfall“ Zeiger auf <name> in <buf>
}
// Sucht eine Datei erst im aktuellen, dann im EXE-Verzeichnis, dann eins darüber, und setzt <buf> entsprechend
// <buf> wird stillschweigend mit MAX_PATH Zeichen vorausgesetzt
void LocateFile(PTSTR buf, PCTSTR name) {
if (PathFileExists(name)) {
int l=GetCurrentDirectory(MAX_PATH-1,buf);
if (buf[l-1]!='\\') buf[l++]='\\';
lstrcpyn(buf+l,name,MAX_PATH-l);
}else{
PTSTR p;
// Das Verzeichnis der EXE-Datei prüfen
GetModuleFileName(0,buf,MAX_PATH);
p=LocateFileHelper(buf,name);
if (!p) return; // gefunden
*--p=0; // am Backslash abhacken
p=LocateFileHelper(buf,name);
if (!p) return;
buf[0]=0; // Kein Dateiname
}
}
static void LocateFileNls(PTSTR buf, PCTSTR name) {
PCTSTR pDot=StrChr(name,'.');
TCHAR lang[10];
TCHAR nameNls[32];
if (pDot) {
GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SISO639LANGNAME,lang,elemof(lang)); // liefert "de"
lstrcpyn(nameNls,name,min((int)(pDot-name+2),elemof(nameNls)));
StrCatBuff(nameNls,lang,elemof(nameNls));
StrCatBuff(nameNls,pDot,elemof(nameNls));
LocateFile(buf,nameNls);
if (*buf) return;
}
LocateFile(buf,name);
}
// LoadLibrary mit erweiterter Suche: Ein Verzeichnis über dem EXE-Verzeichnis
HINSTANCE MyLoadLibrary(PCTSTR name) {
HINSTANCE ret=LoadLibrary(name);
if (!ret) {
TCHAR buf[MAX_PATH];
LocateFile(buf,name);
if (buf[0]) ret=LoadLibrary(buf);
}
return ret;
}
static int mymain() {
MSG Msg;
// absichern, dass nur eine Instanz läuft
// (ginge auch über "shared"-Variablen)
HWND Wnd=FindWindow(T("DCF77"),NULL);
if (Wnd && !(CmdLineOpt&4)) { // Nachricht hinschicken
Msg.wParam=PostMessage(Wnd,WM_ShowPropWnd,0,0);
}else{
HACCEL hAccel;
WNDCLASSEX wc;
WinVer=GetVersion();
ghThisInst=GetModuleHandle(NULL);
// Kritischen Abschnitt in wutils.c initialisieren (130510)
InitCritSec();
// Sprachspezifische Ressourcen-DLL laden
ghInstance=MyLoadLibrary(T("Funkuhr.lng")); // Nur-Ressource-DLL (optional)
if (!ghInstance) ghInstance=ghThisInst;
// Hauptfensterklasse registrieren
InitStruct(&wc,sizeof(wc));
wc.lpfnWndProc=MainWndProc;
wc.hInstance=ghThisInst;
wc.lpszClassName=T("DCF77");
RegisterClassEx(&wc);
// Common Controls, ...ex-Version erforderlich unter Win2k!
myInitCommonControls();
if (LOBYTE(WinVer)<5) RegisterSprechblase();
LoadString(ghInstance,1,MBoxTitle,elemof(MBoxTitle));
// Feststellen, wo die Hilfedatei ist
LocateFileNls(HelpName,T("Funkuhr.chm")); // Hilfedatei (optional)
if (HelpName[0]) {
ChmHelp=true;
PrepareChmHelp(0);
if (MAKEWORD(HIBYTE(WinVer),LOBYTE(WinVer))>=0x50A) PrepareChmHelp(
#ifdef _M_IX86
KEY_WOW64_64KEY
#else
KEY_WOW64_32KEY
#endif
);
}else LocateFileNls(HelpName,T("Funkuhr.hlp"));
DbgPrintf(("HelpName=<%s>\n",HelpName));
// Lokalisieren einer Cache24-Datei
LocateFile(CacheName,T("Cache24")); // Cachedatei (optional)
DbgPrintf(("CacheName=<%s>\n",CacheName));
// Laden der PIC-Anbindung oder -Nachbildung
(FARPROC)Decrypt=GetProcAddress(MyLoadLibrary(T("hkw581.dll")),(PSTR)1);
RegisterKarte();
CreateWindowEx(0,T("DCF77"),NULL,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,0,0,ghThisInst,NULL);
// HWND_MESSAGE als hWndParent: Klingt zwar sinnvoll, aber da kommt kein
// WM_TIMECHANGE an, nicht mal unter Windows 98
hAccel=LoadAccelerators(ghThisInst,MAKEINTRESOURCE(1));
while (GetMessage(&Msg,0,0,0)) {
TranslateAccelerator(MainWnd,hAccel,&Msg);
if (PropWnd) { // notwendig für nicht-modale Eigenschaftsfenster
HWND PageWnd=PropSheet_GetCurrentPageHwnd(PropWnd);
if (PageWnd) {
Config.ActivePropSheet=PropSheet_HwndToIndex(PropWnd,PageWnd);
if (PropSheet_IsDialogMessage(PropWnd,&Msg)) continue;
}else{
RetrievePropWndPos();
DestroyWindow(PropWnd);
PropWnd=0;
SaveConfig(CONFIG);
if (!GetTrayIconVis()) HideTrayIconIf();
}
}
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
return (int)Msg.wParam;
}
static SERVICE_STATUS_HANDLE ssh;
static SERVICE_STATUS ss;
static void WINAPI ServiceCtrlHandler(DWORD Msg) {
switch (Msg) {
case SERVICE_CONTROL_PAUSE: {
PostMessage(MainWnd,WM_SetActivity,0,0);
ss.dwCurrentState=SERVICE_PAUSED;
SetServiceStatus(ssh,&ss);
}break;
case SERVICE_CONTROL_CONTINUE: {
PostMessage(MainWnd,WM_SetActivity,3,0);
ss.dwCurrentState=SERVICE_RUNNING;
SetServiceStatus(ssh,&ss);
}break;
case SERVICE_CONTROL_STOP: {
PostMessage(MainWnd,WM_CLOSE,0,0);
}break;
}
}
static void WINAPI ServiceMain(DWORD argc, PTSTR *argv) {
ssh=RegisterServiceCtrlHandler(T("DCF77"),ServiceCtrlHandler);
if (!ssh) return;
ss.dwServiceType=SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS; // = 0x110
ss.dwCurrentState=SERVICE_RUNNING; // = 4
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_PAUSE_CONTINUE;
SetServiceStatus(ssh,&ss);
// blockiert bis zum manuellen Beenden oder beim Empfang von SERVICE_CONTROL_STOP:
ss.dwWin32ExitCode=mymain();
ss.dwControlsAccepted=0;
ss.dwCurrentState=SERVICE_STOPPED;
ss.dwCheckPoint=3;
SetServiceStatus(ssh,&ss); // kein CloseServiceHandle!
}
void _stdcall WinMainCRTStartup(void) {
static const SERVICE_TABLE_ENTRY svctbl[]={{T("DCF77"),ServiceMain},{NULL,NULL}};
CmdLineOpt=(BYTE)StrToInt(PathGetArgs(GetCommandLine()));
// als Dienst gestartet „hängt“ die Funktion StartServiceCtrlDispatcher()
// bis ServiceMain() beendet ist
ExitProcess(CmdLineOpt&0x10&&StartServiceCtrlDispatcher(svctbl)?0:mymain());
}
Detected encoding: ANSI (CP1252) | 4
|
|