Quelltext /~heha/-geheim/multiser.zip/comsel.cpp

#include "comsel.h"
#include <windowsx.h>
#include <shlwapi.h>	//wnsprintf
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h>	//CM_Get_Parent
#include <stdio.h>	//printf
#include <tchar.h>	//_tcstoul
#pragma intrinsic(memcpy,memset)

template<class T>T limit(T v, T lo, T hi) {return v<lo?lo:v>hi?hi:v;}

// Grenzt "p" in Intervall [l,r] ein und reduziert den Betrag von "d"
// um den Anteil des eingegrenzten "p" im Intervall [l,r]
// Für Fenster mit EINEM größenveränderlichen Element.
// Problem: Kann zu Verformungen durch Rundungseffekte führen.
static int propo(int d, int p, int l, int r) {return MulDiv(d,limit(p,l,r)-l,r-l);}

struct ComSel{
 SMALL_RECT winpos;
 Portlist&pl;
 SP_CLASSIMAGELIST_DATA ild;
 POINT szMinWindow;
 SIZE szClient;
 bool load();
 bool save();
 void FillListComPorts(HWND hList);
 ComSel(Portlist&p):pl(p),pResource(0) {
  ild.cbSize=sizeof ild;
  SetupDiGetClassImageList(&ild);
 }
 ~ComSel() {
  SetupDiDestroyClassImageList(&ild);
  if (pResource) delete[] pResource;
 }
private:
 void*pResource;
 void GetStringFileInfo(const TCHAR*subkey,TCHAR*s,UINT slen);
};

static TCHAR*getExeName(TCHAR s[MAX_PATH]) {
 GetModuleFileName(0,s,MAX_PATH);
 TCHAR*n=PathFindFileName(s);
 TCHAR*e=PathFindExtension(n); if (e!=n) *e=0;
 return n;
}

bool ComSel::load() {
 bool ret=false;
 HKEY k1,k2;
 if (RegOpenKey(HKEY_CURRENT_USER,TEXT("Software\\mb"),&k1)) return ret;
 TCHAR exepath[MAX_PATH];
 if (!RegOpenKey(k1,getExeName(exepath),&k2)) {
  BYTE dat[sizeof winpos+10];
  DWORD size=sizeof dat;
  if (!RegQueryValueEx(k2,TEXT("ComSel"),NULL,NULL,dat,&size)
  && size>=sizeof winpos) {
   ret=true;
   memcpy(&winpos,dat,sizeof winpos);
   size-=sizeof winpos;
   pl.commit(BYTE(size),false);
   memcpy(pl.data(),dat+sizeof winpos,size);
  }
  RegCloseKey(k2);
 }
 RegCloseKey(k1);
 return ret;
}

void ComSel::GetStringFileInfo(const TCHAR*subkey,TCHAR*s,UINT slen) {
 if (!pResource) {
  TCHAR exepath[MAX_PATH];
  GetModuleFileName(0,exepath,elemof(exepath));
  DWORD len=GetFileVersionInfoSize(exepath,&len);
  pResource=new BYTE[len];
  GetFileVersionInfo(exepath,0,len,pResource);
 }
// pResource=LockResource(LoadResource(0,FindResource(0,MAKEINTRESOURCE(1),RT_VERSION)));
 DWORD lang;	// = GetUserDefaultUILanguage() geht nicht unter Windows 98
 GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IDEFAULTLANGUAGE|LOCALE_RETURN_NUMBER,(TCHAR*)&lang,4);
 for(;;lang=0x0409) {	// second round with english-USA
  wnsprintf(s,slen,TEXT("\\StringFileInfo\\%08x\\%s"),MAKELONG(1200,lang),subkey);
  void*res;
  UINT rlen=slen;
  if (VerQueryValue(pResource,s,&res,&rlen)) {
   lstrcpyn(s,(TCHAR*)res,slen);
   wnsprintf(s,slen,TEXT("%s"),res);
   break;
  }
  *s=0;
  if (lang==0x0409) break;
 }
}

bool ComSel::save() {
 HKEY k1,k2;
// Schlüssel-Zweig öffnen
 if (RegCreateKey(HKEY_CURRENT_USER,TEXT("Software\\mb"),&k1)) return false;
 TCHAR s[64];
// eine Beschreibung schreiben (damit der Anwender weiß, worum es geht)
 GetStringFileInfo(TEXT("CompanyName"),s,elemof(s));
 if (*s) RegSetValue(k1,0,REG_SZ,s,(lstrlen(s)+1)*sizeof(TCHAR));
 TCHAR exepath[MAX_PATH];
 if (!RegCreateKey(k1,getExeName(exepath),&k2)) {
  GetStringFileInfo(TEXT("FileDescription"),s,elemof(s));
  if (*s) RegSetValue(k2,0,REG_SZ,s,(lstrlen(s)+1)*sizeof(TCHAR));
  BYTE dat[sizeof winpos+10];
  memcpy(dat,&winpos,sizeof winpos);
  memcpy(dat+sizeof winpos,pl.data(),pl.size());
  RegSetValueEx(k2,TEXT("ComSel"),0,REG_BINARY,dat,sizeof winpos+pl.size());
  RegCloseKey(k2);
 }
 RegCloseKey(k1);
 return true;
}

static void setWndPlace(HWND hWnd,const SMALL_RECT&r) {
 WINDOWPLACEMENT wp;
 wp.length=sizeof wp;
 GetWindowPlacement(hWnd,&wp);
 wp.rcNormalPosition.left  =r.Left;
 wp.rcNormalPosition.top   =r.Top;
 wp.rcNormalPosition.right =r.Right;
 wp.rcNormalPosition.bottom=r.Bottom;
 SetWindowPlacement(hWnd,&wp);
// Im Gegensatz zu MoveWindow sollte SetWindowPlacement dafür sorgen,
// dass das Fenster nicht außerhalb des Desktops platziert wird.
}

static void getWndPlace(HWND hWnd,SMALL_RECT&r) {
 WINDOWPLACEMENT wp;
 wp.length=sizeof wp;
 GetWindowPlacement(hWnd,&wp);
 r.Left  =short(wp.rcNormalPosition.left);
 r.Top   =short(wp.rcNormalPosition.top);
 r.Right =short(wp.rcNormalPosition.right);
 r.Bottom=short(wp.rcNormalPosition.bottom);
}
#if 0
// Sucht Index in nach ItemData sortierter Liste,
// dessen ItemData geradeso größer t ist
// Wird nicht gebraucht dank LVM_SORTITEMS!
static int FindIndex(HWND hList, LPARAM t) {
 int l=0, r=ListView_GetItemCount(hList);
 while(l!=r) {
  LVITEM lvi;
  lvi.mask=LVIF_PARAM;
  lvi.iItem=(l+r)>>1;
  lvi.iSubItem=0;
  ListView_GetItem(hList,&lvi);
  if (lvi.lParam==t) return lvi.iItem;	// bei Treffer sofort raus (= chaotisches Einsortieren gleicher Werte)
  if (lvi.lParam>t) r=lvi.iItem; else l=lvi.iItem+1;	// Sonst weiter teilen
 }
 return l;
}
#endif

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM) {
 return (int)lParam2-(int)lParam1;
}

void ComSel::FillListComPorts(HWND hList) {
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
 SendMessage(hList,LVM_DELETEALLITEMS,0,0);
 HANDLE devs=SetupDiGetClassDevs((LPGUID)&GUID_DEVCLASS_PORTS,0,0,DIGCF_PRESENT);
 if (devs==INVALID_HANDLE_VALUE) return;
 SP_DEVINFO_DATA devInfo;
 devInfo.cbSize=sizeof devInfo;
 TCHAR s[80];
 LVITEM lvi;
 memset(&lvi,0,sizeof lvi);
 lvi.mask=LVIF_IMAGE|LVIF_PARAM|LVIF_STATE|LVIF_TEXT;
 lvi.stateMask=LVIS_SELECTED;
 lvi.pszText=s;
 for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
  HKEY hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
  if (hKey==INVALID_HANDLE_VALUE) continue;
  TCHAR t[16];	// noch ein Unicode-String, wird später noch mal gebraucht
  *t=0;
  DWORD len=sizeof(t);
  RegQueryValueEx(hKey,TEXT("PortName"),0,0,(LPBYTE)t,&len);
  RegCloseKey(hKey);
  if (*t!='C') continue;	// Fehlschläge und LPTx ausfiltern
  lvi.lParam=StrToInt(t+3)-1;	// nullbasierte Nummer
// Bereits geöffnete COM-Ports mit einem Kreuzchen garnieren (unter Windows 10 komisch aussehend)
// Bei Bluetooth dauert das Öffnen zu lange! Daher Auslagerung in Threads.
// Unsauber aber hinnehmbar: Wird der Dialog vor dem Beenden aller Threads geschlossen,
// stoßen die Threadprozeduren auf ungültige <hCombo>-Fenster.
  DEVINST parent;
  const GUID*pguid=&GUID_DEVCLASS_PORTS;
// Neu: Klassen-Icon der übergeordneten Geräteklasse anzeigen (USB oder Steckkarte; bei com0com "Anschlüsse")
  if (!CM_Get_Parent(&parent,devInfo.DevInst,0)) {
   ULONG l=sizeof s;
   if (!CM_Get_DevNode_Registry_Property(parent,CM_DRP_CLASSGUID,0,s,&l,0)) {
    GUID guid;
#ifdef UNICODE
    if (!CLSIDFromString(s,&guid)) pguid=&guid;	// Zeiger ändern bei Erfolg
#else
    WCHAR u[40];
    MultiByteToWideChar(CP_UTF8,0,s,-1,u,elemof(u));
    if (!CLSIDFromString(u,&guid)) pguid=&guid;
#endif
   }
  }
  SetupDiGetClassImageIndex(&ild,pguid,&lvi.iImage);
//Das was im Gerätemanager zu sehen ist: Lang aber hilfreicher als nur COMx
  SetupDiGetDeviceRegistryProperty(devs,&devInfo,SPDRP_FRIENDLYNAME,0,(PBYTE)s,sizeof s,0);	// ab Windows 2k?
//Die COM-Portnummer sollte da enthalten sein. Falls nicht wird sie angehangen
  if (!StrStr(s,t)) {	// Achtung! StrStr(Heuhaufen,Nadel) vs. strstr(needle,haystack)
   int l=lstrlen(s);
   wnsprintf(s+l,elemof(s)-l,TEXT(" (%s)"),t);
  }
  //lvi.iItem=FindIndex(hList,lvi.lParam);
  lvi.state=memchr(pl.data(),BYTE(lvi.lParam),pl.size())?LVIS_SELECTED:0;
  ListView_InsertItem(hList,&lvi);
 }
 SetupDiDestroyDeviceInfoList(devs);
}

static INT_PTR CALLBACK DialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
 ComSel*o=reinterpret_cast<ComSel*>(GetWindowLongPtr(hDlg,DWLP_USER));
 switch (msg) {
  case WM_INITDIALOG: {
   o=reinterpret_cast<ComSel*>(lParam);
   SetWindowLongPtr(hDlg,DWLP_USER,(LPARAM)o);
   RECT r;
   GetWindowRect(hDlg,&r);
   o->szMinWindow.x=r.right-r.left;
   o->szMinWindow.y=r.bottom-r.top;
   GetClientRect(hDlg,&r);
   o->szClient.cx=r.right-r.left;
   o->szClient.cy=r.bottom-r.top;
   if (o->load()) setWndPlace(hDlg,o->winpos);
   HWND hList=GetDlgItem(hDlg,101);
   ListView_SetExtendedListViewStyle(hList,LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_INFOTIP);
   ListView_SetImageList(hList,o->ild.ImageList,LVSIL_SMALL);
   LVCOLUMN col;
   col.mask=0;
   ListView_InsertColumn(hList,0,&col);
   ListView_SortItems(hList,CompareFunc,(LPARAM)o);
   PostMessage(hDlg,WM_TIMER,1,0);
  }return TRUE;
  case WM_DEVICECHANGE: SetTimer(hDlg,1,1500,0); break;
  case WM_TIMER: switch (wParam) {
   case 1: {
    KillTimer(hDlg,(UINT)wParam);
    HWND hList=GetDlgItem(hDlg,101);
    o->FillListComPorts(hList);
    ListView_SetColumnWidth(hList,0,LVSCW_AUTOSIZE_USEHEADER);
   }break;
  }break;
  case WM_GETMINMAXINFO: {
   MINMAXINFO*mmi=reinterpret_cast<MINMAXINFO*>(lParam);
   mmi->ptMinTrackSize=o->szMinWindow;
  }break;
  case WM_SIZE: if (wParam!=SIZE_MINIMIZED) {
   SIZE dz={GET_X_LPARAM(lParam)-o->szClient.cx,
	    GET_Y_LPARAM(lParam)-o->szClient.cy};// get size change
   if (!dz.cx&&!dz.cy) break;	// do nothing when client size doesn't change
   o->szClient.cx+=dz.cx;	// remember new size for next WM_SIZE
   o->szClient.cy+=dz.cy;
   HWND hList=GetDlgItem(hDlg,101);
   RECT ref;			// reference rectangle
   GetWindowRect(hList,&ref);
   HDWP hdwp=BeginDeferWindowPos(4);
   for(HWND w=GetFirstChild(hDlg);w;w=GetNextSibling(w)) {
    RECT r;
    GetWindowRect(w,&r);
    r.left  +=propo(dz.cx,r.left,  ref.left,ref.right);
    r.top   +=propo(dz.cy,r.top,   ref.top,ref.bottom);
    r.right +=propo(dz.cx,r.right, ref.left,ref.right);
    r.bottom+=propo(dz.cy,r.bottom,ref.top,ref.bottom);	// close-up comboboxes need extra size processing!
    r.right-=r.left;
    r.bottom-=r.top;
    ScreenToClient(hDlg,(POINT*)&r);
    hdwp=DeferWindowPos(hdwp,w,0,r.left,r.top,r.right,r.bottom,SWP_NOZORDER);
   }
   EndDeferWindowPos(hdwp);
   ListView_SetColumnWidth(hList,0,LVSCW_AUTOSIZE_USEHEADER);	// must be invoked again on size change
  }break;
  case WM_COMMAND: switch (wParam) {
   case MAKELONG(IDOK,BN_CLICKED): {
    HWND hList=GetDlgItem(hDlg,101);
    o->pl.clear();
    LVITEM lvi;
    lvi.mask=LVIF_STATE|LVIF_PARAM;
    lvi.iSubItem=0;
    lvi.stateMask=LVIS_SELECTED;
    for (lvi.iItem=0;;lvi.iItem++) {
     if (!ListView_GetItem(hList,&lvi)) break;
     if (lvi.state && !o->pl.push_back(BYTE(lvi.lParam))) {
      MessageBox(hDlg,TEXT("Too many selected items!"),0,0);
      return FALSE;
     }
    }
    getWndPlace(hDlg,o->winpos);
    o->save();
   }nobreak;
   case MAKELONG(IDCANCEL,BN_CLICKED): EndDialog(hDlg,(int)wParam); break;
  }break;
 }
 return FALSE;
}

INT_PTR Portlist::dialog(HWND hwndParent) {
 InitCommonControls();
 ComSel cs(*this);
 return DialogBoxParam(0,MAKEINTRESOURCE(101),hwndParent,DialogProc,(LPARAM)&cs);
}

bool Portlist::fromCommandLine(const TCHAR*ptr) {
 TCHAR*endptr;
 for(;;) {
  unsigned u=_tcstoul(ptr,&endptr,10);
  if (endptr==ptr) {
   printf("Malformed number, no digit!");
   return false;
  }
  if (!u || u>256) {
   printf("Illegal number, zero or too large!");
   return false;
  }
  if (!push_back(BYTE(u-1))) {
   printf("Too many numbers given!");
   return false;
  }
  if (!*endptr) return true;
  ptr=endptr+1;	// next possible number
 }
}
Vorgefundene Kodierung: ANSI (CP1252)4
Umlaute falsch? - Datei sei ANSI-kodiert (CP1252)