/* * PROJECT: ReactOS System Control Panel Applet * LICENSE: GPL - See COPYING in the top level directory * FILE: dll/cpl/sysdm/general.c * PURPOSE: General System Information * COPYRIGHT: Copyright Thomas Weidenmueller * Copyright 2006 Ged Murphy * Copyright 2006-2007 Colin Finck * */ #include "precomp.h" #include #include #include #define ANIM_STEP 2 #define ANIM_TIME 50 #define ID_SYSUPTIME_UPDATE_TIMER 1 typedef struct _IMGINFO { HBITMAP hBitmap; INT cxSource; INT cySource; INT iPlanes; INT iBits; } IMGINFO, *PIMGINFO; typedef ULONGLONG (WINAPI *PFGETTICKCOUNT64)(VOID); static PIMGINFO pImgInfo; static const BLENDFUNCTION BlendFunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; static HMODULE hKernel32Vista = NULL; static PFGETTICKCOUNT64 pGetTickCount64 = NULL; static WCHAR szUptimeFormat[64]; VOID ShowLastWin32Error(HWND hWndOwner) { LPTSTR lpMsg; DWORD LastError; LastError = GetLastError(); if (LastError == ERROR_SUCCESS) return; if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, LastError, LANG_USER_DEFAULT, (LPTSTR)&lpMsg, 0, NULL)) { return; } MessageBox(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR); LocalFree(lpMsg); } static VOID InitLogo(HWND hwndDlg) { BITMAP logoBitmap; BITMAP maskBitmap; BITMAPINFO bmpi; HDC hDC, hDCLogo, hDCMask; HBITMAP hMask = NULL, hLogo = NULL; HBITMAP hAlphaLogo = NULL; COLORREF *pBits; INT line, column; hDC = GetDC(hwndDlg); hDCLogo = CreateCompatibleDC(NULL); hDCMask = CreateCompatibleDC(NULL); if (hDC == NULL || hDCLogo == NULL || hDCMask == NULL) goto Cleanup; ZeroMemory(pImgInfo, sizeof(*pImgInfo)); ZeroMemory(&bmpi, sizeof(bmpi)); hLogo = (HBITMAP)LoadImageW(hApplet, MAKEINTRESOURCEW(IDB_ROSBMP), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); hMask = (HBITMAP)LoadImageW(hApplet, MAKEINTRESOURCEW(IDB_ROSMASK), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); if (hLogo == NULL || hMask == NULL) goto Cleanup; GetObject(hLogo, sizeof(logoBitmap), &logoBitmap); GetObject(hMask, sizeof(maskBitmap), &maskBitmap); if (logoBitmap.bmHeight != maskBitmap.bmHeight || logoBitmap.bmWidth != maskBitmap.bmWidth) goto Cleanup; bmpi.bmiHeader.biSize = sizeof(BITMAPINFO); bmpi.bmiHeader.biWidth = logoBitmap.bmWidth; bmpi.bmiHeader.biHeight = logoBitmap.bmHeight; bmpi.bmiHeader.biPlanes = 1; bmpi.bmiHeader.biBitCount = 32; bmpi.bmiHeader.biCompression = BI_RGB; bmpi.bmiHeader.biSizeImage = 4 * logoBitmap.bmWidth * logoBitmap.bmHeight; /* Create a premultiplied bitmap */ hAlphaLogo = CreateDIBSection(hDC, &bmpi, DIB_RGB_COLORS, (PVOID*)&pBits, 0, 0); if (!hAlphaLogo) goto Cleanup; SelectObject(hDCLogo, hLogo); SelectObject(hDCMask, hMask); for (line = logoBitmap.bmHeight - 1; line >= 0; line--) { for (column = 0; column < logoBitmap.bmWidth; column++) { COLORREF alpha = GetPixel(hDCMask, column, line) & 0xFF; COLORREF Color = GetPixel(hDCLogo, column, line); DWORD r, g, b; r = GetRValue(Color) * alpha / 255; g = GetGValue(Color) * alpha / 255; b = GetBValue(Color) * alpha / 255; *pBits++ = b | (g << 8) | (r << 16) | (alpha << 24); } } pImgInfo->hBitmap = hAlphaLogo; pImgInfo->cxSource = logoBitmap.bmWidth; pImgInfo->cySource = logoBitmap.bmHeight; pImgInfo->iBits = logoBitmap.bmBitsPixel; pImgInfo->iPlanes = logoBitmap.bmPlanes; Cleanup: if (hMask != NULL) DeleteObject(hMask); if (hLogo != NULL) DeleteObject(hLogo); if (hDCMask != NULL) DeleteDC(hDCMask); if (hDCLogo != NULL) DeleteDC(hDCLogo); if (hDC != NULL) ReleaseDC(hwndDlg, hDC); } LRESULT CALLBACK RosImageProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static UINT timerid = 0, top = 0, offset; static HBITMAP hCreditsBitmap; switch (uMsg) { case WM_LBUTTONDBLCLK: if (wParam & (MK_CONTROL | MK_SHIFT)) { if (timerid == 0) { HDC hDC; HDC hCreditsDC = NULL, hLogoDC = NULL; HFONT hFont = NULL; NONCLIENTMETRICS ncm; RECT rcCredits; TCHAR szCredits[2048]; INT iDevsHeight; hDC = GetDC(NULL); if (hDC == NULL) goto Cleanup; top = 0; offset = 0; hCreditsDC = CreateCompatibleDC(hDC); hLogoDC = CreateCompatibleDC(hCreditsDC); if (hCreditsDC == NULL || hLogoDC == NULL) goto Cleanup; SetRect(&rcCredits, 0, 0, 0, 0); ncm.cbSize = sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0); hFont = CreateFontIndirect(&ncm.lfMessageFont); if (!hFont) goto Cleanup; SelectObject(hCreditsDC, hFont); LoadString(hApplet, IDS_DEVS, szCredits, sizeof(szCredits) / sizeof(TCHAR)); DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CALCRECT); iDevsHeight = rcCredits.bottom - rcCredits.top; hCreditsBitmap = CreateBitmap(pImgInfo->cxSource, (2 * pImgInfo->cySource) + iDevsHeight + 1, pImgInfo->iPlanes, pImgInfo->iBits, NULL); if (!hCreditsBitmap) goto Cleanup; SelectObject(hLogoDC, pImgInfo->hBitmap); SelectObject(hCreditsDC, hCreditsBitmap); offset += pImgInfo->cySource; SetRect(&rcCredits, 0, 0, pImgInfo->cxSource, (2 * pImgInfo->cySource) + iDevsHeight + 1); FillRect(hCreditsDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE)); SetRect(&rcCredits, 0, offset, pImgInfo->cxSource, offset + iDevsHeight + 1); SetBkMode(hCreditsDC, TRANSPARENT); OffsetRect(&rcCredits, 1, 1); SetTextColor(hCreditsDC, GetSysColor(COLOR_BTNSHADOW)); DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CENTER); OffsetRect(&rcCredits, -1, -1); SetTextColor(hCreditsDC, GetSysColor(COLOR_WINDOWTEXT)); DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CENTER); offset += iDevsHeight; AlphaBlend(hCreditsDC, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, hLogoDC, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc); AlphaBlend(hCreditsDC, 0, offset, pImgInfo->cxSource, pImgInfo->cySource, hLogoDC, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc); timerid = SetTimer(hwnd, 1, ANIM_TIME, NULL); Cleanup: if (hFont != NULL) DeleteObject(hFont); if (hLogoDC != NULL) DeleteDC(hLogoDC); if (hCreditsDC != NULL) DeleteDC(hCreditsDC); if (hDC != NULL) ReleaseDC(NULL, hDC); } } break; case WM_LBUTTONDOWN: if (timerid) { RECT rcCredits; HDC hDC = GetDC(hwnd); if (hDC != NULL) { GetClientRect(hwnd, &rcCredits); SetRect(&rcCredits, 0, 0, rcCredits.right, pImgInfo->cySource); FillRect(hDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE)); ReleaseDC(hwnd, hDC); } KillTimer(hwnd, timerid); if (hCreditsBitmap != NULL) DeleteObject(hCreditsBitmap); InvalidateRect(hwnd, NULL, FALSE); top = 0; timerid = 0; } break; case WM_TIMER: top += ANIM_STEP; if (top > offset) { RECT rcCredits; HDC hDC = GetDC(hwnd); if (hDC != NULL) { GetClientRect(hwnd, &rcCredits); SetRect(&rcCredits, 0, 0, rcCredits.right, pImgInfo->cySource); FillRect(hDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE)); ReleaseDC(hwnd, hDC); } KillTimer(hwnd, timerid); if (hCreditsBitmap != NULL) DeleteObject(hCreditsBitmap); top = 0; timerid = 0; } InvalidateRect(hwnd, NULL, FALSE); break; case WM_PAINT: { PAINTSTRUCT PS; HDC hdcMem, hdc; LONG left; hdc = wParam != 0 ? (HDC)wParam : BeginPaint(hwnd, &PS); GetClientRect(hwnd, &PS.rcPaint); /* Position image in center of dialog */ left = (PS.rcPaint.right - pImgInfo->cxSource) / 2; hdcMem = CreateCompatibleDC(hdc); if (hdcMem != NULL) { if(timerid != 0) { SelectObject(hdcMem, hCreditsBitmap); BitBlt(hdc, left, PS.rcPaint.top, PS.rcPaint.right - PS.rcPaint.left, PS.rcPaint.top + pImgInfo->cySource, hdcMem, 0, top, SRCCOPY); } else { SelectObject(hdcMem, pImgInfo->hBitmap); AlphaBlend(hdc, left, PS.rcPaint.top, pImgInfo->cxSource, pImgInfo->cySource, hdcMem, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc); } DeleteDC(hdcMem); } if (wParam == 0) EndPaint(hwnd,&PS); break; } } return TRUE; } static VOID SetRegTextData(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID) { LPTSTR lpBuf = NULL; DWORD BufSize = 0; DWORD Type; if (RegQueryValueEx(hKey, Value, NULL, &Type, NULL, &BufSize) == ERROR_SUCCESS) { lpBuf = HeapAlloc(GetProcessHeap(), 0, BufSize); if (!lpBuf) return; if (RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)lpBuf, &BufSize) == ERROR_SUCCESS) SetDlgItemText(hwnd, uID, lpBuf); HeapFree(GetProcessHeap(), 0, lpBuf); } } static INT SetProcNameString(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID1, UINT uID2) { LPTSTR lpBuf = NULL; DWORD BufSize = 0; DWORD Type; INT Ret = 0; TCHAR szBuf[31]; TCHAR* szLastSpace; INT LastSpace = 0; if (RegQueryValueEx(hKey, Value, NULL, &Type, NULL, &BufSize) == ERROR_SUCCESS) { lpBuf = HeapAlloc(GetProcessHeap(), 0, BufSize); if (!lpBuf) return 0; if (RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)lpBuf, &BufSize) == ERROR_SUCCESS) { if (BufSize > ((30 + 1) * sizeof(TCHAR))) { /* Wrap the Processor Name String like XP does: * * - Take the first 30 characters and look for the last space. * * Then wrap the string after this space. * * - If no space is found, wrap the string after character 30. * * * * For example the Processor Name String of a Pentium 4 is right-aligned. * * With this wrapping the first line looks centered. */ _tcsncpy(szBuf, lpBuf, 30); szBuf[30] = 0; szLastSpace = _tcsrchr(szBuf, ' '); if (szLastSpace == 0) { LastSpace = 30; } else { LastSpace = (szLastSpace - szBuf); szBuf[LastSpace] = 0; } _tcsncpy(szBuf, lpBuf, LastSpace); SetDlgItemText(hwnd, uID1, szBuf); SetDlgItemText(hwnd, uID2, lpBuf+LastSpace+1); /* Return the number of used lines */ Ret = 2; } else { SetDlgItemText(hwnd, uID1, lpBuf); Ret = 1; } } HeapFree(GetProcessHeap(), 0, lpBuf); } return Ret; } static VOID MakeFloatValueString(DOUBLE* dFloatValue, LPTSTR szOutput, LPTSTR szAppend) { TCHAR szDecimalSeparator[4]; /* Get the decimal separator for the current locale */ if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, szDecimalSeparator, sizeof(szDecimalSeparator) / sizeof(TCHAR)) > 0) { UCHAR uDecimals; UINT uIntegral; /* Show the value with two decimals */ uIntegral = (UINT)*dFloatValue; uDecimals = (UCHAR)((UINT)(*dFloatValue * 100) - uIntegral * 100); wsprintf(szOutput, _T("%u%s%02u %s"), uIntegral, szDecimalSeparator, uDecimals, szAppend); } } static VOID SetProcSpeed(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID) { TCHAR szBuf[64], szHz[16]; DWORD BufSize = sizeof(DWORD); DWORD Type = REG_SZ; PROCESSOR_POWER_INFORMATION ppi; ZeroMemory(&ppi, sizeof(ppi)); if ((CallNtPowerInformation(ProcessorInformation, NULL, 0, (PVOID)&ppi, sizeof(ppi)) == STATUS_SUCCESS && ppi.CurrentMhz != 0) || RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)&ppi.CurrentMhz, &BufSize) == ERROR_SUCCESS) { if (ppi.CurrentMhz < 1000) { if (!LoadString(hApplet, IDS_MEGAHERTZ, szHz, _countof(szHz))) { return; } StringCchPrintf(szBuf, _countof(szBuf), _T("%lu %s"), ppi.CurrentMhz, szHz); } else { double flt = ppi.CurrentMhz / 1000.0; if (!LoadString(hApplet, IDS_GIGAHERTZ, szHz, _countof(szHz))) { return; } MakeFloatValueString(&flt, szBuf, szHz); } SetDlgItemText(hwnd, uID, szBuf); } } static VOID GetSystemInformation(HWND hwnd) { HKEY hKey; TCHAR SysKey[] = _T("HARDWARE\\DESCRIPTION\\System"); TCHAR ProcKey[] = _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"); MEMORYSTATUSEX MemStat; TCHAR Buf[32]; WCHAR SMBiosName[96]; INT CurMachineLine = IDC_MACHINELINE1; /* * Get hardware device name or motherboard name * using information from raw SMBIOS data */ if (GetSystemName(SMBiosName, _countof(SMBiosName))) { SetDlgItemText(hwnd, CurMachineLine, SMBiosName); CurMachineLine++; } else { /* If SMBIOS is not available, use System Identifier */ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, SysKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { SetRegTextData(hwnd, hKey, _T("Identifier"), CurMachineLine); CurMachineLine++; RegCloseKey(hKey); } } /* * Get Processor information * although undocumented, this information is being pulled * directly out of the registry instead of via setupapi as it * contains all the info we need, and should remain static */ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, ProcKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { INT PrevMachineLine; SetRegTextData(hwnd, hKey, _T("VendorIdentifier"), CurMachineLine); CurMachineLine++; PrevMachineLine = CurMachineLine; CurMachineLine += SetProcNameString(hwnd, hKey, _T("ProcessorNameString"), CurMachineLine, CurMachineLine + 1); if (CurMachineLine == PrevMachineLine) { /* TODO: Try obtaining CPU name from WMI (i.e. CIM_Processor) */ /* Brand String is not available, use Identifier instead */ CurMachineLine += SetProcNameString(hwnd, hKey, _T("Identifier"), CurMachineLine, CurMachineLine + 1); } SetProcSpeed(hwnd, hKey, _T("~MHz"), CurMachineLine); CurMachineLine++; RegCloseKey(hKey); } /* Get total physical RAM */ MemStat.dwLength = sizeof(MemStat); if (GlobalMemoryStatusEx(&MemStat)) { TCHAR szStr[32]; double dTotalPhys; if (MemStat.ullTotalPhys > 1024 * 1024 * 1024) { UINT i = 0; static const UINT uStrId[] = { IDS_GIGABYTE, IDS_TERABYTE, IDS_PETABYTE}; // We're dealing with GBs or more MemStat.ullTotalPhys /= 1024 * 1024; if (MemStat.ullTotalPhys > 1024 * 1024) { // We're dealing with TBs or more MemStat.ullTotalPhys /= 1024; i++; if (MemStat.ullTotalPhys > 1024 * 1024) { // We're dealing with PBs or more MemStat.ullTotalPhys /= 1024; i++; dTotalPhys = (double)MemStat.ullTotalPhys / 1024; } else { dTotalPhys = (double)MemStat.ullTotalPhys / 1024; } } else { dTotalPhys = (double)MemStat.ullTotalPhys / 1024; } LoadString(hApplet, uStrId[i], szStr, sizeof(szStr) / sizeof(TCHAR)); MakeFloatValueString(&dTotalPhys, Buf, szStr); } else { // We're dealing with MBs, don't show any decimals LoadString(hApplet, IDS_MEGABYTE, szStr, sizeof(szStr) / sizeof(TCHAR)); wsprintf(Buf, _T("%u %s"), (UINT)MemStat.ullTotalPhys / 1024 / 1024, szStr); } SetDlgItemText(hwnd, CurMachineLine, Buf); } } static VOID GetSystemVersion(HWND hwnd) { HWND hRosVersion; SIZE_T lenStr, lenVersion; PCWSTR pwszVersion = L" " TEXT(KERNEL_VERSION_RC); PWSTR pwszStr; lenVersion = wcslen(pwszVersion); if (lenVersion == 0) { return; } hRosVersion = GetDlgItem(hwnd, IDC_ROSVERSION); if (!hRosVersion) { return; } lenStr = GetWindowTextLengthW(hRosVersion); lenStr += lenVersion + 1; pwszStr = HeapAlloc(GetProcessHeap(), 0, lenStr * sizeof(WCHAR)); if (!pwszStr) { return; } GetWindowText(hRosVersion, pwszStr, lenStr); StringCchCatW(pwszStr, lenStr, pwszVersion); SetWindowText(hRosVersion, pwszStr); HeapFree(GetProcessHeap(), 0, pwszStr); } /** * @brief * An equivalent of GetTickCount64, implemented using QueryPerformanceCounter. * * @return * The number of milliseconds that have elapsed since the system was started. */ static ULONGLONG GetTickCountQPC(VOID) { LARGE_INTEGER Counter, Frequency; QueryPerformanceCounter(&Counter); QueryPerformanceFrequency(&Frequency); return (Counter.QuadPart * 1000) / Frequency.QuadPart; } static VOID GetSystemUptime(HWND hwndDlg) { HWND hUptimeLabel; ULONGLONG cMilliseconds; ULONG cSeconds; WCHAR szBuf[64]; hUptimeLabel = GetDlgItem(hwndDlg, IDC_UPTIME); if (!hUptimeLabel) { return; } if (pGetTickCount64) { cMilliseconds = pGetTickCount64(); } else { cMilliseconds = GetTickCountQPC(); } cSeconds = cMilliseconds / 1000; StringCchPrintfW(szBuf, _countof(szBuf), szUptimeFormat, cSeconds / (60*60*24), // Days (cSeconds / (60*60)) % 24, // Hours (cSeconds / 60) % 60, // Minutes cSeconds % 60); // Seconds SetWindowTextW(hUptimeLabel, szBuf); /* Set update timer (reset timeout if the timer exists) */ SetTimer(hwndDlg, ID_SYSUPTIME_UPDATE_TIMER, 1000 - (cMilliseconds % 1000), NULL); } static VOID InitSystemUptime(HWND hwndDlg) { HMODULE hKernel32; /* Load time format string */ if (LoadStringW(hApplet, IDS_UPTIME_FORMAT, szUptimeFormat, _countof(szUptimeFormat)) == 0) { return; } /* Load required DLLs */ hKernel32 = GetModuleHandleW(L"kernel32.dll"); if (hKernel32) { pGetTickCount64 = (PFGETTICKCOUNT64)GetProcAddress(hKernel32, "GetTickCount64"); if (!pGetTickCount64) { hKernel32Vista = LoadLibraryW(L"kernel32_vista.dll"); if (hKernel32Vista) { pGetTickCount64 = (PFGETTICKCOUNT64)GetProcAddress(hKernel32Vista, "GetTickCount64"); } } } /* Show system uptime and set update timer */ GetSystemUptime(hwndDlg); } /* Property page dialog callback */ INT_PTR CALLBACK GeneralPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); UNREFERENCED_PARAMETER(wParam); switch (uMsg) { case WM_INITDIALOG: { pImgInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IMGINFO)); if (pImgInfo == NULL) { EndDialog(hwndDlg, 0); return FALSE; } InitLogo(hwndDlg); SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_ROSIMG), GWLP_WNDPROC, (LONG_PTR)RosImageProc); GetSystemInformation(hwndDlg); GetSystemVersion(hwndDlg); InitSystemUptime(hwndDlg); break; } case WM_DESTROY: { KillTimer(hwndDlg, ID_SYSUPTIME_UPDATE_TIMER); if (hKernel32Vista) { FreeLibrary(hKernel32Vista); } HeapFree(GetProcessHeap(), 0, pImgInfo); break; } case WM_TIMER: { if (wParam == ID_SYSUPTIME_UPDATE_TIMER) { /* Update system uptime */ GetSystemUptime(hwndDlg); } break; } case WM_COMMAND: { if (LOWORD(wParam) == IDC_LICENCE) { DialogBox(hApplet, MAKEINTRESOURCE(IDD_LICENCE), hwndDlg, LicenceDlgProc); return TRUE; } break; } case WM_DRAWITEM: { LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT)lParam; if (lpDrawItem->CtlID == IDC_ROSIMG) { HDC hdcMem; LONG left; /* Position image in centre of dialog */ left = (lpDrawItem->rcItem.right - pImgInfo->cxSource) / 2; hdcMem = CreateCompatibleDC(lpDrawItem->hDC); if (hdcMem != NULL) { SelectObject(hdcMem, pImgInfo->hBitmap); BitBlt(lpDrawItem->hDC, left, lpDrawItem->rcItem.top, lpDrawItem->rcItem.right - lpDrawItem->rcItem.left, lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top, hdcMem, 0, 0, SRCCOPY); DeleteDC(hdcMem); } } return TRUE; } case WM_NOTIFY: { NMHDR *nmhdr = (NMHDR *)lParam; if (nmhdr->idFrom == IDC_ROSHOMEPAGE_LINK && nmhdr->code == NM_CLICK) { PNMLINK nml = (PNMLINK)nmhdr; ShellExecuteW(hwndDlg, L"open", nml->item.szUrl, NULL, NULL, SW_SHOWNORMAL); } break; } } return FALSE; }