Source file: /~heha/hsn/esptool.zip/comsel.cpp

#include "esptool.h"
#include <windowsx.h>
#include <shlwapi.h>
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h>	//CM_Get_Parent

/*
 * Usage: For querying a COM port, call ComSel::dialog(hwndParent).
 * It requires a dialog resource with a ComboboxEx for the COM port
 * and a Combobox for the baud rate.
 * The ComSel object (i.e. an instance of it) represents the user selection.
 * The setting is persistent, i.e. the constructor loads last state from registry.
 */

/**********************
 * Configurable items *
 **********************/

#define DEF_BAUDRATES 38400,74880,115200,230400,460800,576000,921600,1000000,1152000,1500000,2000000,4000000

#ifndef elemof
# define elemof(x) (sizeof(x)/sizeof(*(x)))
#endif

// registry load/save section name to configure: take file name
// Generating another key by renaming EXE file is the intent.
// Let caller reserve space for the returned string pointer.
static TCHAR*getExeName(TCHAR exepath[MAX_PATH]) {
 GetModuleFileName(0,exepath,MAX_PATH);
 TCHAR*n=PathFindFileName(exepath),	// name only, no path
      *e=PathFindExtension(n);		// onto last '.' or terminating '\0'
 if (e!=n) *e=0;			// kill extension but avoid misbehaviour on ".htaccess"-like name
 return n;
}

ComSel::ComSel(BYTE p, DWORD b):showcmd(0),port(p),baud(b) {load();}

bool ComSel::load() {
 HKEY k1,k2;
 DWORD size=sizeof*this;
 if (RegOpenKey(HKEY_CURRENT_USER,TEXT("Software\\h#s"),&k1)) return false;
 TCHAR exepath[MAX_PATH];
 if (!RegOpenKey(k1,getExeName(exepath),&k2)) {
  if (RegQueryValueEx(k2,TEXT("ComSel"),0,0,(LPBYTE)this,&size)) size=0;
  RegCloseKey(k2);
 }
 RegCloseKey(k1);
 return !!size;
}

static void GetStringFileInfo(const TCHAR*subkey,TCHAR*s,UINT slen) {
 HGLOBAL r=LoadResource(0,FindResource(0,MAKEINTRESOURCE(1),RT_VERSION));
 void*p=LockResource(r);
 DWORD lang;	// = GetUserDefaultUILanguage() geht nicht unter Windows 98
 GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IDEFAULTLANGUAGE|LOCALE_RETURN_NUMBER,(TCHAR*)&lang,4);
 for(;;) {
  wnsprintf(s,slen,TEXT("\\StringFileInfo\\%08x\\%s"),MAKELONG(1200,lang),subkey);
  void*res;
  UINT rlen=slen;
  if (VerQueryValue(p,s,&res,&rlen)) {
   lstrcpyn(s,(TCHAR*)res,slen);
   wnsprintf(s,slen,TEXT("%s"),res);
   break;
  }
  *s=0;
  if (lang==0x0409) break;
  lang=0x0409;	// englisch-USA
 }
 UnlockResource(r);
 FreeResource(r);
}

bool ComSel::save() {
 HKEY k1,k2;
// Schlüssel-Zweig öffnen
 if (RegCreateKey(HKEY_CURRENT_USER,TEXT("Software\\h#s"),&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));
  RegSetValueEx(k2,TEXT("ComSel"),0,REG_BINARY,(PBYTE)this,sizeof*this);
  RegCloseKey(k2);
 }
 RegCloseKey(k1);
 return true;
}

static void MoveWnd(HWND hWnd,const POINTS&pt) {
 WINDOWPLACEMENT wp;
 wp.length=sizeof wp;
 GetWindowPlacement(hWnd,&wp);
 OffsetRect(&wp.rcNormalPosition,
	pt.x-wp.rcNormalPosition.left,
	pt.y-wp.rcNormalPosition.top);
 SetWindowPlacement(hWnd,&wp);
// Im Gegensatz zu MoveWindow sollte SetWindowPlacement dafür sorgen,
// dass das Fenster nicht außerhalb des Desktops platziert wird.
}

static char SaveWndPos(HWND hWnd,POINTS&pt) {
 WINDOWPLACEMENT wp;
 wp.length=sizeof wp;
 GetWindowPlacement(hWnd,&wp);
 pt.x=(short)wp.rcNormalPosition.left;
 pt.y=(short)wp.rcNormalPosition.top;
 return wp.showCmd;
}

// Sucht Index in nach ItemData sortierter Liste,
// dessen ItemData geradeso größer t ist
static int FindIndex(HWND hCombo, LPARAM t) {
 int l=0, r=ComboBox_GetCount(hCombo);
 while(l!=r) {
  int m=(l+r)>>1;
  LPARAM itemdata=ComboBox_GetItemData(hCombo,m);
  if (itemdata==t) return m;	// bei Treffer sofort raus (= chaotisches Einsortieren gleicher Werte)
  if (itemdata>t) r=m; else l=m+1;	// Sonst weiter teilen
 }
 return l;
}

static SP_CLASSIMAGELIST_DATA ild;

HANDLE ComSel::Open(UINT ComNr,DWORD dwFlags) {
 TCHAR s[12];
 wnsprintf(s,elemof(s),TEXT("\\\\.\\COM%u"),ComNr+1);
 HANDLE h=CreateFile(s,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,dwFlags,0);
 if (h==INVALID_HANDLE_VALUE) h=0;
 return h;
}

static HANDLE mutex;

class Tryopen{
 HWND hCombo;
 DWORD ComNr;
 static DWORD WINAPI ThreadProc(void*p) {return reinterpret_cast<Tryopen*>(p)->Threadproc();}
 DWORD Threadproc() {
  HANDLE h=ComSel::Open(ComNr);
  if (h) CloseHandle(h);
  else{
   WaitForSingleObject(mutex,INFINITE);
   COMBOBOXEXITEM cbei;
   cbei.mask=CBEIF_OVERLAY;
   cbei.iItem=FindIndex(hCombo,ComNr);
   cbei.iOverlay=2;
   SendMessage(hCombo,CBEM_SETITEM,0,(LPARAM)&cbei);
//   SendMessage(hCombo,CBEM_SETEXTENDEDSTYLE,0,0);	// Neuzeichnen auslösen? Nee!
   COMBOBOXINFO cbi;
   cbi.cbSize=sizeof cbi;
   if (GetComboBoxInfo(hCombo,&cbi)) {		// geht auch nicht!
    InvalidateRect(cbi.hwndList,0,TRUE);
   }else
    GetLastError();
   ReleaseMutex(mutex);
  }
  delete this;
  return DWORD(h);	// zur Kontrolle im MSVC
 }
public:
 Tryopen(HWND w,UINT n):hCombo(w),ComNr(n) {
  DWORD id;
  HANDLE thread=CreateThread(0,0,ThreadProc,this,0,&id);
  if (thread) CloseHandle(thread);	// Nicht am Thread-Eegebnis interessiert
 }
};

static void FillComboComPorts(HWND hCombo,UINT nr) {
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
 ComboBox_ResetContent(hCombo);
 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];
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_IMAGE|CBEIF_LPARAM|CBEIF_SELECTEDIMAGE|CBEIF_TEXT/*|CBEIF_IOVERLAY*/;
 cbei.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
  cbei.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.
  new Tryopen(hCombo,UINT(cbei.lParam)); // start-and-forget-thread (self-cleaning)
  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,&cbei.iImage);
  cbei.iSelectedImage=cbei.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);
  }
  WaitForSingleObject(mutex,INFINITE);	// for thread-safe hCombo access later
  cbei.iItem=FindIndex(hCombo,cbei.lParam);
  SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
  if (UINT(cbei.lParam)==nr) ComboBox_SetCurSel(hCombo,cbei.iItem);
  ReleaseMutex(mutex);
 }
 SetupDiDestroyDeviceInfoList(devs);
}

static void FillComboBaudRates(HWND hCombo,DWORD br) {
 ComboBox_ResetContent(hCombo);
 static const DWORD defs[]={DEF_BAUDRATES};
 bool found=false;
 TCHAR s[16];
 for (int i=0; i<elemof(defs); i++) {
  DWORD cur=defs[i];
  if (cur%1000000) wnsprintf(s,elemof(s),TEXT("%u"),cur);
  else wnsprintf(s,elemof(s),TEXT("%u M"),cur/1000000);
  int idx=ComboBox_AddString(hCombo,s);
  ComboBox_SetItemData(hCombo,idx,cur);
  if (cur==br) {ComboBox_SetCurSel(hCombo,idx); found=true;}
 }
 if (!found) {
  wnsprintf(s,elemof(s),TEXT("%u"),br);
  SetWindowText(hCombo,s);	// set editable value
 }
}

static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
 ComSel*o=reinterpret_cast<ComSel*>(GetWindowLongPtr(hDlg,DWLP_USER));
 switch (msg) {
  case WM_INITDIALOG: {
   ild.cbSize=sizeof ild;
   SetupDiGetClassImageList(&ild);
   mutex=CreateMutex(0,0,0);
   HICON icon=LoadIcon(GetModuleHandle(0),MAKEINTRESOURCE(100));
   SetClassLongPtr(hDlg,GCLP_HICON,(LONG_PTR)icon);
   SetWindowLongPtr(hDlg,DWLP_USER,lParam);
   o=reinterpret_cast<ComSel*>(lParam);
   if (o->showcmd) MoveWnd(hDlg,o->pos);
   HWND hCombo=GetDlgItem(hDlg,101);
   SendMessage(hCombo,CBEM_SETIMAGELIST,0,(LPARAM)ild.ImageList);
   FillComboComPorts(hCombo,o->port);
// Wenn nur 1 COM-Port da ist, und nicht selektiert, wird dieses sogleich selektiert
   if (ComboBox_GetCount(hCombo)==1 && ComboBox_GetCurSel(hCombo)) {
    ComboBox_SetCurSel(hCombo,0);
    SendMessage(hDlg,WM_COMMAND,MAKELONG(101,CBN_SELCHANGE),(LPARAM)hCombo);
   }
   if (ComboBox_GetCurSel(hCombo)==CB_ERR) EnableWindow(GetDlgItem(hDlg,IDOK),FALSE);
   FillComboBaudRates(GetDlgItem(hDlg,102),o->baud);
  }return TRUE;

  case WM_DEVICECHANGE: SetTimer(hDlg,1,1500,0); break;

  case WM_TIMER: switch (wParam) {
   case 1: {
    KillTimer(hDlg,(UINT)wParam);
    FillComboComPorts(GetDlgItem(hDlg,101),o->port);
   }break;
  }break;

  case WM_COMMAND: switch (wParam) {
   case MAKELONG(101,CBN_SELCHANGE): {
    o->port=(BYTE)ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam));
    EnableWindow(GetDlgItem(hDlg,IDOK),true);
   }break;
   case MAKELONG(102,CBN_EDITCHANGE): {
    DWORD b=GetDlgItemInt(hDlg,102,0,FALSE);
    if (b) o->baud=b;
    EnableWindow(GetDlgItem(hDlg,IDOK),!!b);
   }break;
   case MAKELONG(102,CBN_SELCHANGE): {
    o->baud=(DWORD)ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam));
   }break;
   case IDOK:
   case IDCANCEL: {
    o->showcmd=SaveWndPos(hDlg,o->pos);
    o->save();
    EndDialog(hDlg,int(wParam));
   }break;
  }break;
  case WM_DESTROY: {
   CloseHandle(mutex);
   SetupDiDestroyClassImageList(&ild);
  }break;
 }
 return FALSE;
}

bool ComSel::dialog(HWND hParent) {
 INITCOMMONCONTROLSEX icc={sizeof icc,ICC_USEREX_CLASSES};
 InitCommonControlsEx(&icc);
 return DialogBoxParam(0,MAKEINTRESOURCE(101),hParent,DlgProc,(LPARAM)this)==IDOK;
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded