#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: UTF-8 | 0
|