#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
|
|