Source file: /~heha/hs/Funkuhr.zip/src/Funkuhr.c

/****************************************************************
 * 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
Wrong umlauts? - Assume file is ANSI (CP1252) encoded