Source file: /~heha/mb-iwp/Antriebe/Schrittmotorsteuerung/smc.zip/smc1.cpp

#include "smc1.h"
#include "wheelcap.h"
#include <stdlib.h>
#include <tchar.h>

HINSTANCE ghInstance;	// "zeigt" zur Nur-Ressource-DLL oder enthält 0x400000
NOTIFYICONDATA nid;
char sDecimal[2];
#define WM_TrayNotify (WM_USER+200)
#define WM_ShowPropWnd (WM_USER+202)

CONFIG Config;		// Persistente Daten
GDI Gdi;		// GDI-Objekte
//HWND PropWnd;		// Eigenschaftsfenster (<>0 wenn vorhanden)

/************************************************
 * Das Hauptfenster ist eine Sprechblase	*
 ************************************************/

static RECT TextRect, IlluRect;
static HICON TitleIcon, KnobIcon;

#define ROUNDNESS 16	// Abrundung
#define INITIALW 200	// Breite in Pixel
#define INITIALH 50	// Höhe in Pixel


// Erstellt alle global verwendeten GDI-Objekte,
// ggf. abhängig von Systemfarben!
void GDI::Create() {
 TitleIcon=LoadIcon(ghInstance,MAKEINTRESOURCE(1));
 KnobIcon=(HICON)LoadImage(ghInstance,MAKEINTRESOURCE(2),IMAGE_ICON,16,16,LR_SHARED);
 SmallFont=CreateFont(-12,0,0,0,FW_NORMAL,0,0,0,
   DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,
   T("Arial Narrow"));
 NormalFont=CreateFont(-12,0,0,0,FW_NORMAL,0,0,0,
   DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,
   T("Arial"));
 BoldFont=CreateFont(-12,0,0,0,FW_BOLD,0,0,0,
   DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,
   T("Arial"));
 FramePen=CreatePen(PS_INSIDEFRAME,1,GetSysColor(COLOR_INFOTEXT));
/*
 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);
 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
void GDI::Delete() {
 int i;
 for (i=0; i<sizeof(*this)/sizeof(HGDIOBJ); i++) {
  DeleteObject(((HGDIOBJ*)this)[i]);
 }
}

// Lädt Konfigurationsdaten aus Registrierung
bool CONFIG::Load() {
 HKEY key;
 bool ret=false;
 if (!RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\SMC"),0,KEY_QUERY_VALUE,&key)) {
  DWORD size=sizeof(*this);
  if (!RegQueryValueEx(key,T("Config"),NULL,NULL,(LPBYTE)this,&size)
    && size==sizeof(*this)) ret=true;
  RegCloseKey(key);
 }
 return ret;
}

// Speichert Konfigurationsdaten in Registrierung
void CONFIG::Save() const {
 HKEY key;
 if (RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\SMC"),
   0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) return;
 RegSetValueEx(key,T("Config"),0,REG_BINARY,(LPBYTE)this,sizeof(*this));
 RegCloseKey(key);
}

static void HideTrayIcon() {
 Shell_NotifyIcon(NIM_DELETE,&nid);
}

static void ShowTrayIcon() {
 nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;
 nid.uVersion=NOTIFYICON_VERSION;	// Features (W2K, WXP) aktivieren
 Shell_NotifyIcon(NIM_ADD,&nid);
}

static void UpdateTrayIcon() {
// if (nid.uFlags&NIF_INFO) Sprechblase();
 if (Shell_NotifyIcon(NIM_MODIFY,&nid)) return;
 ShowTrayIcon();
}

void SetTrayTip() {
 LoadString(ghInstance,1,nid.szTip,elemof(nid.szTip));
 UpdateTrayIcon();
}

#define ACHSN 23	// Anzahl Achsen
#define GREIFN 20	// Anzahl Greifer

static const LPTSTR sAchse[ACHSN]={
 T("OWIS-Lineartisch: Greiferverschiebung X"),
 T("OWIS-Drehtisch: Revolver C"),
 T("Eigenbau-Lineartisch (unbenutzt)"),
 T("Greiferplattform Z-Achse (Hubbewegung)"),
 T("Greiferplattform links/rechts neigen B"),
 T("Greiferplattform vorn/hinten nicken A"),
 T("Greiferplattform Einzelmotor vorn links"),
 T("Greiferplattform Einzelmotor vorn rechts"),
 T("Greiferplattform Einzelmotor hinten"),
 T("STANDA-Schiebetisch 045507: Greiferverschiebung Y"),
 T("STANDA-Drehtisch: Greiferdrehung"),
 T("STANDA-Schiebetisch 048635: Greiferhöhe Z"),
 T("frei"),
 T("frei"),
 T("frei"),
 T("Fokusmotor 0"),
 T("Fokusmotor 1"),
 T("SMARACT-Kinematik links/rechts X"),
 T("SMARACT-Kinematik vorn/hinten Y"),
 T("SMARACT-Kinematik Drehung C"),
 T("SMARACT-Einzelmotor oben"),
 T("SMARACT-Einzelmotor Mitte"),
 T("SMARACT-Einzelmotor unten")};

// Achsspezifische konstante Daten, die der Firmware nicht bekannt sind
struct ACHSINFO{
 float ups;	// Einheiten pro Ganzschritt, 0 beim Fokusmotor (weil unbekannt)
 float len;	// Gesamtstellweg in Einheiten (umlaufend rotatorisch: 360)
 BYTE nk;	// Nachkommastellen (bei ganzen mm)
 BYTE nSteuer;	// Steuerungs-Nummer
 BYTE nAchse;	// Achs-Nummer der Steuerung
 BYTE flags;	// Besonderheiten:
		//  Bit 0 = umlaufend
		//  Bit 1 = Einheit °, sonst mm
		//  Bit 2 = überspringen bei Durchwahl
 DWORD depend;	// Lineare Abhängigkeiten (bei virtueller Achse), triple-weise (6 Tripel)
		//  Bit 2:0 = vzb. Anteil Motor 0
		//  Bit 5:3 = vzb. Anteil Motor 1
		//  Bit 8:6 = vzb. Anteil Motor 2
		//  Bit 11:9 = vzb. Anteil Motor 3
		//  Bit 14:12 = vzb. Anteil Motor 4
		//  Bit 17:15 = vzb. Anteil Motor 5
		//  Bit 18 = Steuerung ohne echte Mikroschritte
 float GetPos(BYTE f) const;
 int GetPosStr(char*,int,BYTE f) const;	// liefert Position (f=4) oder Geschwindigkeit (f=2) oder Info-Bits
};

// noch unimplementierte Koordinatentrafo für Parallelkinematik
static float GetPPosX(const ACHSINFO&pai, BYTE f) {
 return 0;
}

static float GetPPosY(const ACHSINFO&pai, BYTE f) {
 return 0;
}

static float GetPPosC(const ACHSINFO&pai, BYTE f) {
 return 0;
}

static const ACHSINFO ai[ACHSN]={
 {1.0F/200, 70.0F,4,0,0,0,01001000},
 {1.0F/100,360.0F,3,0,1,3,01010000},
 {1.0F/672, 24.0F,4,0,2,4,01100000},
 {0.5F/24/3,15.0F,3,0,3,4,01000111},	// 3 Achsen addiert
 {0.5F/24,  10.0F,3,0,4,6,01000071},	// Achse[1] subtrahiert, Achse[0] addiert
 {0.5F/24/2,10.0F,3,0,5,6,01000611},	// Achse[2] 2x subtrahiert von Achse[1] + Achse[0]
 {0.5F/24,  15.0F,3,0,6,4,01000001},
 {0.5F/24,  15.0F,3,0,7,4,01000010},
 {0.5F/24,  15.0F,3,0,8,4,01000100},
 {1.0F/600, 25.0F,4,1,0,0,01001000},
 {1.0F/100,360.0F,3,1,1,3,01010000},
 {1.0F/600, 25.0F,4,1,2,0,01100000},
 {0,        25,   4,1,3,4,01000001},
 {0,        25,   4,1,4,4,01000010},
 {0,        25,   4,1,5,4,01000100},
 {0,        25,   0,1,6,0,0},
 {0,        25,   0,1,7,4,0},
 {0.5F/1000,10.0F,3,2,3,0,(DWORD)GetPPosX},
 {0.5F/1000,10.0F,3,2,4,0,(DWORD)GetPPosY},
 {0.5F/1000,10.0F,3,2,5,2,(DWORD)GetPPosC},
 {0.5F/1000,15.0F,3,2,0,0,01000001},
 {0.5F/1000,15.0F,3,2,1,0,01000010},
 {0.5F/1000,15.0F,3,2,2,0,01000100}};

float ACHSINFO::GetPos(BYTE f) const{
 union{
  int i;				// Integer-Zwischengröße
  BYTE b[4];				// Returnwert für f=0
  float f;				// Returnwert für f=1,2,4
 }r={0};
 if (depend) {
  if (depend<0x400000) {			// reguläre Anhängigkeits-Info
   r.i=0;
   if (!f) r.b[0]--;
   DWORD dep=depend&0777777;		// 6 motorweise Abhängigkeiten
   MOTOR *pmd=md+6*nSteuer;		// Zeiger auf Motordaten
   for (; dep; dep>>=3,pmd++) {
    char fak=(char)((char)dep<<5)>>5;	// jeweiligen Faktor mit Vorzeichen erweitern
    if (fak) switch (f) {
     case 0:
      r.b[0]&=pmd->phase;		// Bits UND-verknüpfen
      r.b[1]|=pmd->phase;		// ODER-verknüpfen
      r.b[2]^=pmd->phase;		// XOR-verknüpfen (wozu?)
      r.b[3]++; break;			// Anzahl der Verknüpfungen (um Unsinn aufzudecken)
     case 1: r.i+=pmd->accel*fak; break;// sonst mit Faktor addieren
     case 2: r.i+=pmd->speed*fak; break;
     case 4: r.i+=pmd->ist*fak; break;
    }
   }
   if (f) {
    if (f==4 && depend&1<<18) r.i&=~255;// Bei Positionsangaben Mikroschritte entfernen
    r.f=ups*r.i/512;			// Physikalische Größe
    switch (f) {
     case 1: r.f/=1E-3F; nobreak;	// Beschleunigung, Einheit pro s²
     case 2: r.f/=256E-6F; break;	// Geschwindigkeit, Einheit pro Sekunde
    }
   }
  }else r.f=((float(*)(const ACHSINFO&,BYTE))depend)(*this,f);	// Funktionszeiger aufrufen
 }
 return r.f;
}

int ACHSINFO::GetPosStr(char*s, int l, BYTE f) const{
 int ret=0;
 if (f) {
  const char *suffix[]={NULL,"/s²","/s",NULL,""};
  ret=_snprintf(s,l,"% .*f %s%s",nk,GetPos(f&7),flags&2?"°":"mm",suffix[f]);
  char*p=StrChrA(s,'.');
  if (p) *p=*sDecimal;
 }else if (l>6) {
  BYTE b[4];
  *(float*)&b=GetPos(0);
  static const char flagchar[]="brlhf";	// Zeichen für BRAKE, REF, LEFT, HOLD, FULL (Bits in Phase)
  const char *fl=flagchar;
  for (BYTE m=1; m<32; m<<=1, fl++) {
   if (b[1]&m) *s++ = b[3]>1 && b[0]&m ? _toupper(*fl) : *fl, ret++;
  }	// Großbuchstaben für kombinierte Achsen, wenn Flag auf ALLE Achsen zutrifft
  b[1]>>=5; b[0]>>=5;			// Phase (3 Bits)
  *s++ = (b[1]==b[0]) ? b[0]+'0' : '?', ret++;
  *s=0;	// terminieren (nur zum Debuggen nötig)
 }
 return ret;
}

static const LPTSTR sSteuer[]={
 T("SM1 @ COM%d"),
 T("SM2 @ COM%d"),
 T("HCU-3D @ USB"),
 NULL};

int Achse;
int Greifer;
BYTE GreiferState;
BYTE KeyState;	// zum Hervorheben von Textteilen

static int GetGreifStr(char*s, int l, BYTE f=0) {
 return _snprintf(s,l,"%s",GreiferState?"EIN":"AUS");
}

// Einen symbolisierten Drehknopf ausgeben
static void OutKnob(HDC dc, bool yes) {
 if (yes) {
  POINT p;
  GetCurrentPositionEx(dc,&p);
  DrawIconEx(dc,p.x,p.y,KnobIcon,0,0,0,0,DI_NORMAL);
  MoveToEx(dc,p.x+20,p.y,NULL);
 }
}

// Text ausgeben
static void OutText(HDC dc) {
 SetTextAlign(dc,TA_UPDATECP);
 TCHAR buf[128];
 const ACHSINFO&rai=ai[Achse];
// 1. Zeile: welche Achse
 bool focus=(KeyState&0x0E)==8;		// Fokus für Drehknopf und Mausrad: linke (blaue) Taste gedrückt
 int l=_sntprintf(buf,elemof(buf),T("Achse %d: %s"),Achse,sAchse[Achse]);
 MoveToEx(dc,TextRect.left,TextRect.top,NULL);
 SelectFont(dc,focus?Gdi.BoldFont:Gdi.NormalFont);
 OutKnob(dc,focus);
 TextOut(dc,0,0,buf,l);
// 2. Zeile: Achsposition
 SelectFont(dc,Gdi.NormalFont);
 MoveToEx(dc,TextRect.left,(TextRect.top*2+TextRect.bottom)/3,NULL);
 focus=(KeyState&0x0E)==0;		// Fokus wenn keine Taste gedrückt
 OutKnob(dc,focus);
 TextOut(dc,0,0,T("Position: "),10);
 l=rai.GetPosStr(buf,elemof(buf),4);
 SelectFont(dc,focus?Gdi.BoldFont:Gdi.NormalFont);
 TextOut(dc,0,0,buf,l);
// Achsgeschwindigkeit
 TextOut(dc,0,0,T("   "),3);
 focus=(KeyState&0x0E)==2;		// Fokus wenn Shift-Taste (Drehknopf) gedrückt
 OutKnob(dc,focus);
 SelectFont(dc,Gdi.NormalFont);
 TextOut(dc,0,0,T("Geschw.: "),9);
 l=rai.GetPosStr(buf,elemof(buf),2);
 SelectFont(dc,focus?Gdi.BoldFont:Gdi.NormalFont);
 TextOut(dc,0,0,buf,l);
// 3. Zeile: welcher Greifer und Greiferzustand
 focus=(KeyState&0x0E)==4;		// Fokus wenn zweite (grüne) Taste gedrückt
 MoveToEx(dc,TextRect.left,(TextRect.top+TextRect.bottom*2)/3,NULL);
 l=_sntprintf(buf,elemof(buf),T("Greifer %d: "),Greifer);
 SelectFont(dc,focus?Gdi.BoldFont:Gdi.NormalFont);
 OutKnob(dc,focus);
 TextOut(dc,0,0,buf,l);
 l=GetGreifStr(buf,elemof(buf));
 SelectFont(dc,KeyState&1?Gdi.BoldFont:Gdi.NormalFont);
 TextOut(dc,0,0,buf,l);
// Info
 MoveToEx(dc,(TextRect.left+TextRect.right)/2,(TextRect.top+TextRect.bottom*2)/3,NULL);
 SelectFont(dc,Gdi.SmallFont);
 TextOut(dc,0,0,T("Info: "),6);
 l=rai.GetPosStr(buf,elemof(buf),0);
 TextOut(dc,0,0,buf,l);
// 2. Zeile rechts: Steuereinheit für aktuelle Achse
 SelectFont(dc,Gdi.NormalFont);
 SetTextAlign(dc,TA_RIGHT);
 SetTextColor(dc,0xC0C0C0);
 l=_sntprintf(buf,elemof(buf),sSteuer[rai.nSteuer],Config.SerialNo[rai.nSteuer]+1);
 TextOut(dc,TextRect.right,(TextRect.top*2+TextRect.bottom)/3,buf,l);
// 3. Zeile rechts: Steuereinheit für aktuellen Greifer (zurzeit immer SM1)
 l=_sntprintf(buf,elemof(buf),sSteuer[0],Config.SerialNo[0]+1);
 TextOut(dc,TextRect.right,(TextRect.top+TextRect.bottom*2)/3,buf,l);
}

// Status aus Motorsteuerung lesen
static void ReadCurState() {
 BYTE s[1], r[4];
 s[0]='@';
 if (w[0].SendRecv(s,sizeof(s),r,sizeof(r))==sizeof(r)) {
  Achse=r[0];
  Greifer=r[1];
  GreiferState=r[2];
 }
}

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"));
   Gdi.Create();
   InitStruct(&nid,sizeof(nid));
   nid.hWnd=Wnd;
   nid.uID=1;
   nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;
   nid.uCallbackMessage=WM_TrayNotify;
   nid.hIcon=(HICON)LoadImage(ghInstance,MAKEINTRESOURCE(1),IMAGE_ICON,16,16,LR_SHARED);
//   SetStrings();
   SetTrayTip();
   InfoWindow(Wnd);
   StartWorker();
   SendMessage(Wnd,WM_WININICHANGE,0,0);
   ReadCurState();
  }break;

  case WM_WININICHANGE: {
   GetProfileStringA("intl","sDecimal",".",sDecimal,elemof(sDecimal));
   InvalidateRect(Wnd,NULL,TRUE);
  }break;

  case WM_NCHITTEST: {		// Maus über Fenster
   SetTimer(Wnd,1,nid.uTimeout*1000,NULL);			// Timer neu starten
   POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
   RECT r;
   GetWindowRect(Wnd,&r);
   BYTE hitcode=0;
   if (r.left<=pt.x && pt.x<r.left+ROUNDNESS/2) hitcode|=1;	// linker Rand
   if (r.right-ROUNDNESS/2<=pt.x && pt.x<r.right) hitcode|=2;	// rechter Rand
   if (r.top<=pt.y && pt.y<r.top+ROUNDNESS/2) hitcode|=4;		// oberer Rand
   if (r.bottom-ROUNDNESS/2<=pt.y && pt.y<r.bottom) hitcode|=8;	// unterer Rand
   static const BYTE htcode[16]={
	HTCAPTION,	HTLEFT,		HTRIGHT,	HTRIGHT,
	HTTOP,		HTTOPLEFT,	HTTOPRIGHT,	HTTOP,
	HTBOTTOM,	HTBOTTOMLEFT,	HTBOTTOMRIGHT,	HTBOTTOM,
	HTBOTTOM,	HTBOTTOMLEFT,	HTBOTTOMRIGHT,	HTBOTTOM};
   return htcode[hitcode];
  }

  case WM_SIZE: {
   int w=GET_X_LPARAM(lParam);
   int h=GET_Y_LPARAM(lParam);
   SetRect(&TextRect,6+32+6,6,w-6,6+3*16);	// rechts neben dem Icon
   SetRect(&IlluRect,6,6+3*16+6,w-6,h-6-6-3*16-6);
   if (Gdi.rgn) DeleteRgn(Gdi.rgn);
   Gdi.rgn=CreateRoundRectRgn(0,0,w,h,ROUNDNESS,ROUNDNESS);
   SetWindowRgn(Wnd,Gdi.rgn,FALSE);
  }nobreak;

  case WM_MOVE: {
   RECT r;
   GetWindowRect(Wnd,&r);
   Config.WinPos.set(r);	// Fensterkoordinaten nachführen (zum späteren Speichern)
  }break;

  case WM_PAINT: {
   PAINTSTRUCT ps;
//   HFONT of;
   BeginPaint(Wnd,&ps);
   RECT r;
   GetClientRect(Wnd,&r);
	// Blase mit Rahmen zeichnen (ohne "Mundstück")
   SelectBrush(ps.hdc,GetSysColorBrush(COLOR_INFOBK));
   SelectPen(ps.hdc,Gdi.FramePen);
   RoundRect(ps.hdc,0,0,r.right-1,r.bottom-1,ROUNDNESS,ROUNDNESS);
	// Icon und Überschrift ausgeben
   DrawIcon(ps.hdc,8,8,TitleIcon);
   SetBkMode(ps.hdc,TRANSPARENT);
   SetTextColor(ps.hdc,GetSysColor(COLOR_INFOTEXT));
//   of=SelectFont(ps.hdc,Gdi.TitleFont);
//   DrawText(ps.hdc,nid.szInfoTitle,-1,&TitleRect,DT_NOPREFIX|DT_SINGLELINE);
	// Textkörper ausgeben
//   SelectFont(ps.hdc,GetStockFont(DEFAULT_GUI_FONT));
//   DrawText(ps.hdc,nid.szInfo,-1,&TextRect,DT_NOPREFIX|DT_WORDBREAK);
   OutText(ps.hdc);
   EndPaint(Wnd,&ps);
  }return 0;

  case WM_SHOWWINDOW: if (wParam) {
   SetTimer(Wnd,1,nid.uTimeout*1000,NULL);	// Fenster verschwindet automatisch
  }break;

  case WM_TIMER: switch (wParam) {
   case 1: {
    KillTimer(Wnd,wParam);
    ShowWindow(Wnd,SW_HIDE);
   }break;
  }break;

  case WM_ShowPropWnd: {	// Zweiter Programmstart
//   ShowProperties();	// PropSheet öffnen - und Tray-Icon zeigen
  }break;

  case WM_LokalInput: {
   LOKALINPUT k;
   k.lp=lParam;
   KeyState=k.ks;
   const ACHSINFO*pai=ai+Achse;
   if (k.kp&1) {	// Greifer schalten
    GreiferState^=true;
    w[0].Send(GreiferState?'g':'h');
   }
   if (k.kr&2) {	// Motor stoppen
    w[pai->nSteuer].Send('c');
   }
   if (k.delta) switch (k.ks&0x0F) {
    case 0:
    case 1: {	// Schritte machen
     if (k.delta<0) for (;k.delta; k.delta++) w[pai->nSteuer].Send('-');
     else	    for (;k.delta; k.delta--) w[pai->nSteuer].Send('+');
    }break;

    case 2:
    case 3: {
//     debug(("Delta=%d, Steuerung=%d",k.delta,pai->nSteuer));
     if (k.delta<0) for (;k.delta; k.delta++) w[pai->nSteuer].Send('b');
     else	    for (;k.delta; k.delta--) w[pai->nSteuer].Send('a');
    }break;	// fahren
    
    case 4: {		// Greifer weiterschalten
     Greifer+=k.delta;
     if (Greifer<0) Greifer+=GREIFN;
     if (Greifer>=GREIFN) Greifer-=GREIFN;
     BYTE s[2],r[4];
     s[0]='G'+Greifer;
     s[1]='@';
     if (w[0].SendRecv(s,sizeof(s),r,sizeof(r))==sizeof(r)) GreiferState=r[2];
     else MessageBeep(MB_ICONEXCLAMATION);
    }break;

    case 8: {		// Achse weiterschalten
     Achse+=k.delta;
     if (Achse<0) Achse+=ACHSN;
     if (Achse>=ACHSN) Achse-=ACHSN;
     pai=ai+Achse;
     switch (pai->nSteuer) {
      case 0:
      case 1: {
       w[pai->nSteuer].Send('0'+pai->nAchse);
      }break;
     }
    }break;

    default: Beep(500,20);	// ungültiges KeyState
   }
   InvalidateRect(Wnd,NULL,TRUE);
   ShowWindow(Wnd,SW_SHOWNA);
  }break;

  case WM_MotorChange: {	// wenn sich etwas tut ...
   div_t d=div(wParam,6);	// d.quot=Steuer-Nummer, d.rem=Motor-Nummer (nicht Achs-Nummer!)
   const ACHSINFO*pai=ai+Achse;
   if (d.quot==pai->nSteuer && ai[Achse].depend&(7<<(d.rem*3))) {
    InvalidateRect(Wnd,NULL,TRUE);
    ShowWindow(Wnd,SW_SHOWNA);
   }
  }break;

  case WM_WHEELCAP: {
   ShowWindow(MainWnd,SW_SHOWNA);
   Beep(1000+(short)HIWORD(lParam),30);
  }break;

  case WM_LBUTTONDOWN:
  case WM_RBUTTONDOWN:
   ShowWindow(Wnd,SW_HIDE);
  break;

  case WM_TrayNotify: switch (lParam) {
   case WM_LBUTTONDOWN: {
    ShowWindow(Wnd,SW_SHOWNA);
   }break;
   case NIN_SELECT:
   case NIN_KEYSELECT: {
//    ShowProperties();
   }break;
   case WM_RBUTTONDOWN:
   case WM_CONTEXTMENU: {
    HMENU m=LoadMenu(ghInstance,MAKEINTRESOURCE(100));
    GetLastError();

    MENUITEMINFO mii;
    InitStruct(&mii,sizeof(mii));
    mii.fMask=MIIM_STATE;
    mii.fState=MFS_DEFAULT;
    if (IsWindowVisible(MainWnd)) mii.fState|=MFS_CHECKED;
    SetMenuItemInfo(m,101,FALSE,&mii);

    POINT p;
    GetCursorPos(&p);
    SetForegroundWindow(Wnd);			// soll so sein
    TrackPopupMenu(GetSubMenu(m,0),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;
   }break;
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 101: {
    ShowWindow(Wnd,IsWindowVisible(Wnd)?SW_HIDE:SW_SHOWNA);
   }break;
   case 102: {	// Eigenschaften....
//    ShowProperties();
   }break;
   case 2: {	// Beenden
    SendMessage(Wnd,WM_CLOSE,0,0);
   }break;
  }break;

  case WM_ENDSESSION: if (wParam) {
   Config.Save();
  }break;

  case WM_DESTROY: {
   Config.Save();
//   nid.uFlags&=~NIF_INFO; Sprechblase();// Sprechblase entfernen
   StopWorker();
   InfoWindow(0);
   HideTrayIcon();
   Gdi.Delete();
   PostQuitMessage(0);
  }break;

  default:
  if (Msg==RegMsg_TaskbarCreated) ShowTrayIcon();	// Explorer-Absturz-Behandlung

 }
 return DefWindowProc(Wnd,Msg,wParam,lParam);
}

void CALLBACK WinMainCRTStartup() {
 HWND Wnd=FindWindow(T("SMC"),NULL);
 if (Wnd) {	// Nachricht hinschicken
  PostMessage(Wnd,WM_ShowPropWnd,0,0);
 }else{
  ghInstance=GetModuleHandle(NULL);
  WNDCLASSEX wc;
  InitStruct(&wc,sizeof(wc));
  wc.style=CS_BYTEALIGNWINDOW|CS_SAVEBITS|CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc=MainWndProc;
  wc.hInstance=(HINSTANCE)0x400000;
  wc.hCursor=LoadCursor(0,IDC_ARROW);
  wc.lpszClassName=T("SMC");
  RegisterClassEx(&wc);

  InitCommonControls();
  LoadString(ghInstance,1,MBoxTitle,elemof(MBoxTitle));

  if (!Config.Load()) {
   Config.WinPos.left=(Config.WinPos.right=(short)GetSystemMetrics(SM_CXFULLSCREEN)-16)-INITIALW;
   Config.WinPos.top=(Config.WinPos.bottom=(short)GetSystemMetrics(SM_CYFULLSCREEN)-16)-INITIALH;
   Config.SerialNo[0]=3;
   Config.SerialNo[1]=4;
  }

  CreateWindowEx(WS_EX_TOPMOST|WS_EX_TOOLWINDOW|WS_EX_NOPARENTNOTIFY,
    T("SMC"),NULL,WS_POPUP,
    Config.WinPos.left,Config.WinPos.top,Config.WinPos.width(),Config.WinPos.height(),0,0,(HINSTANCE)0x400000,NULL);

  MSG Msg;
  while (GetMessage(&Msg,0,0,0)) {
//   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;
//     Config.Save();
//    }
//   }
   TranslateMessage(&Msg);
   DispatchMessage(&Msg);
  }
  ExitProcess(Msg.wParam);
 }
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded