xref: /reactos/dll/cpl/sysdm/general.c (revision e5993f13)
1 /*
2  * PROJECT:     ReactOS System Control Panel Applet
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/cpl/sysdm/general.c
5  * PURPOSE:     General System Information
6  * COPYRIGHT:   Copyright Thomas Weidenmueller <w3seek@reactos.org>
7  *              Copyright 2006 Ged Murphy <gedmurphy@gmail.com>
8  *              Copyright 2006-2007 Colin Finck <mail@colinfinck.de>
9  *
10  */
11 
12 #include "precomp.h"
13 
14 #include <winnls.h>
15 #include <powrprof.h>
16 #include <buildno.h>
17 
18 #define ANIM_STEP 2
19 #define ANIM_TIME 50
20 #define ID_SYSUPTIME_UPDATE_TIMER 1
21 
22 typedef struct _IMGINFO
23 {
24     HBITMAP hBitmap;
25     INT cxSource;
26     INT cySource;
27     INT iPlanes;
28     INT iBits;
29 } IMGINFO, *PIMGINFO;
30 
31 typedef ULONGLONG (WINAPI *PFGETTICKCOUNT64)(VOID);
32 
33 static PIMGINFO pImgInfo;
34 static const BLENDFUNCTION BlendFunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
35 static HMODULE hKernel32Vista = NULL;
36 static PFGETTICKCOUNT64 pGetTickCount64 = NULL;
37 static WCHAR szUptimeFormat[64];
38 
39 VOID ShowLastWin32Error(HWND hWndOwner)
40 {
41     LPTSTR lpMsg;
42     DWORD LastError;
43 
44     LastError = GetLastError();
45     if (LastError == ERROR_SUCCESS)
46         return;
47 
48     if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
49                        FORMAT_MESSAGE_FROM_SYSTEM |
50                        FORMAT_MESSAGE_IGNORE_INSERTS,
51                        NULL,
52                        LastError,
53                        LANG_USER_DEFAULT,
54                        (LPTSTR)&lpMsg,
55                        0, NULL))
56     {
57         return;
58     }
59 
60     MessageBox(hWndOwner, lpMsg, NULL, MB_OK | MB_ICONERROR);
61     LocalFree(lpMsg);
62 }
63 
64 
65 static VOID InitLogo(HWND hwndDlg)
66 {
67     BITMAP logoBitmap;
68     BITMAP maskBitmap;
69     BITMAPINFO bmpi;
70     HDC hDC, hDCLogo, hDCMask;
71     HBITMAP hMask = NULL, hLogo = NULL;
72     HBITMAP hAlphaLogo = NULL;
73     COLORREF *pBits;
74     INT line, column;
75 
76     hDC = GetDC(hwndDlg);
77     hDCLogo = CreateCompatibleDC(NULL);
78     hDCMask = CreateCompatibleDC(NULL);
79 
80     if (hDC == NULL || hDCLogo == NULL || hDCMask == NULL)
81         goto Cleanup;
82 
83     ZeroMemory(pImgInfo, sizeof(*pImgInfo));
84     ZeroMemory(&bmpi, sizeof(bmpi));
85 
86     hLogo = (HBITMAP)LoadImageW(hApplet, MAKEINTRESOURCEW(IDB_ROSBMP), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
87     hMask = (HBITMAP)LoadImageW(hApplet, MAKEINTRESOURCEW(IDB_ROSMASK), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
88 
89     if (hLogo == NULL || hMask == NULL)
90         goto Cleanup;
91 
92     GetObject(hLogo, sizeof(logoBitmap), &logoBitmap);
93     GetObject(hMask, sizeof(maskBitmap), &maskBitmap);
94 
95     if (logoBitmap.bmHeight != maskBitmap.bmHeight || logoBitmap.bmWidth != maskBitmap.bmWidth)
96         goto Cleanup;
97 
98     bmpi.bmiHeader.biSize = sizeof(BITMAPINFO);
99     bmpi.bmiHeader.biWidth = logoBitmap.bmWidth;
100     bmpi.bmiHeader.biHeight = logoBitmap.bmHeight;
101     bmpi.bmiHeader.biPlanes = 1;
102     bmpi.bmiHeader.biBitCount = 32;
103     bmpi.bmiHeader.biCompression = BI_RGB;
104     bmpi.bmiHeader.biSizeImage = 4 * logoBitmap.bmWidth * logoBitmap.bmHeight;
105 
106     /* Create a premultiplied bitmap */
107     hAlphaLogo = CreateDIBSection(hDC, &bmpi, DIB_RGB_COLORS, (PVOID*)&pBits, 0, 0);
108     if (!hAlphaLogo)
109         goto Cleanup;
110 
111     SelectObject(hDCLogo, hLogo);
112     SelectObject(hDCMask, hMask);
113 
114     for (line = logoBitmap.bmHeight - 1; line >= 0; line--)
115     {
116         for (column = 0; column < logoBitmap.bmWidth; column++)
117         {
118             COLORREF alpha = GetPixel(hDCMask, column, line) & 0xFF;
119             COLORREF Color = GetPixel(hDCLogo, column, line);
120             DWORD r, g, b;
121 
122             r = GetRValue(Color) * alpha / 255;
123             g = GetGValue(Color) * alpha / 255;
124             b = GetBValue(Color) * alpha / 255;
125 
126             *pBits++ = b | (g << 8) | (r << 16) | (alpha << 24);
127         }
128     }
129 
130     pImgInfo->hBitmap = hAlphaLogo;
131     pImgInfo->cxSource = logoBitmap.bmWidth;
132     pImgInfo->cySource = logoBitmap.bmHeight;
133     pImgInfo->iBits = logoBitmap.bmBitsPixel;
134     pImgInfo->iPlanes = logoBitmap.bmPlanes;
135 
136 Cleanup:
137     if (hMask != NULL) DeleteObject(hMask);
138     if (hLogo != NULL) DeleteObject(hLogo);
139     if (hDCMask != NULL) DeleteDC(hDCMask);
140     if (hDCLogo != NULL) DeleteDC(hDCLogo);
141     if (hDC != NULL) ReleaseDC(hwndDlg, hDC);
142 }
143 
144 LRESULT CALLBACK RosImageProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
145 {
146     static UINT timerid = 0, top = 0, offset;
147     static HBITMAP hCreditsBitmap;
148 
149     switch (uMsg)
150     {
151         case WM_LBUTTONDBLCLK:
152             if (wParam & (MK_CONTROL | MK_SHIFT))
153             {
154                 if (timerid == 0)
155                 {
156                     HDC hDC;
157                     HDC hCreditsDC = NULL, hLogoDC = NULL;
158                     HFONT hFont = NULL;
159                     NONCLIENTMETRICS ncm;
160                     RECT rcCredits;
161                     TCHAR szCredits[2048];
162                     INT iDevsHeight;
163 
164                     hDC = GetDC(NULL);
165                     if (hDC == NULL)
166                         goto Cleanup;
167 
168                     top = 0;
169                     offset = 0;
170 
171                     hCreditsDC = CreateCompatibleDC(hDC);
172                     hLogoDC = CreateCompatibleDC(hCreditsDC);
173 
174                     if (hCreditsDC == NULL || hLogoDC == NULL)
175                         goto Cleanup;
176 
177                     SetRect(&rcCredits, 0, 0, 0, 0);
178 
179                     ncm.cbSize = sizeof(NONCLIENTMETRICS);
180                     SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
181 
182                     hFont = CreateFontIndirect(&ncm.lfMessageFont);
183                     if (!hFont)
184                         goto Cleanup;
185                     SelectObject(hCreditsDC, hFont);
186 
187                     LoadString(hApplet, IDS_DEVS, szCredits, sizeof(szCredits) / sizeof(TCHAR));
188                     DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CALCRECT);
189 
190                     iDevsHeight = rcCredits.bottom - rcCredits.top;
191 
192                     hCreditsBitmap = CreateBitmap(pImgInfo->cxSource, (2 * pImgInfo->cySource) + iDevsHeight + 1, pImgInfo->iPlanes, pImgInfo->iBits, NULL);
193                     if (!hCreditsBitmap)
194                         goto Cleanup;
195 
196                     SelectObject(hLogoDC, pImgInfo->hBitmap);
197                     SelectObject(hCreditsDC, hCreditsBitmap);
198 
199                     offset += pImgInfo->cySource;
200 
201                     SetRect(&rcCredits, 0, 0, pImgInfo->cxSource, (2 * pImgInfo->cySource) + iDevsHeight + 1);
202                     FillRect(hCreditsDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE));
203 
204                     SetRect(&rcCredits, 0, offset, pImgInfo->cxSource, offset + iDevsHeight + 1);
205                     SetBkMode(hCreditsDC, TRANSPARENT);
206 
207                     OffsetRect(&rcCredits, 1, 1);
208                     SetTextColor(hCreditsDC, GetSysColor(COLOR_BTNSHADOW));
209                     DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CENTER);
210 
211                     OffsetRect(&rcCredits, -1, -1);
212                     SetTextColor(hCreditsDC, GetSysColor(COLOR_WINDOWTEXT));
213                     DrawText(hCreditsDC, szCredits, -1, &rcCredits, DT_CENTER);
214 
215                     offset += iDevsHeight;
216 
217                     AlphaBlend(hCreditsDC, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, hLogoDC, 0,  0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc);
218                     AlphaBlend(hCreditsDC, 0, offset, pImgInfo->cxSource, pImgInfo->cySource, hLogoDC, 0,  0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc);
219 
220                     timerid = SetTimer(hwnd, 1, ANIM_TIME, NULL);
221 
222 Cleanup:
223                     if (hFont != NULL) DeleteObject(hFont);
224                     if (hLogoDC != NULL) DeleteDC(hLogoDC);
225                     if (hCreditsDC != NULL) DeleteDC(hCreditsDC);
226                     if (hDC != NULL) ReleaseDC(NULL, hDC);
227                 }
228             }
229             break;
230         case WM_LBUTTONDOWN:
231             if (timerid)
232             {
233                 RECT rcCredits;
234                 HDC hDC = GetDC(hwnd);
235                 if (hDC != NULL)
236                 {
237                     GetClientRect(hwnd, &rcCredits);
238                     SetRect(&rcCredits, 0, 0, rcCredits.right, pImgInfo->cySource);
239                     FillRect(hDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE));
240                     ReleaseDC(hwnd, hDC);
241                 }
242                 KillTimer(hwnd, timerid);
243                 if (hCreditsBitmap != NULL)
244                     DeleteObject(hCreditsBitmap);
245 
246                 InvalidateRect(hwnd, NULL, FALSE);
247                 top = 0;
248                 timerid = 0;
249             }
250             break;
251         case WM_TIMER:
252             top += ANIM_STEP;
253 
254             if (top > offset)
255             {
256                 RECT rcCredits;
257                 HDC hDC = GetDC(hwnd);
258                 if (hDC != NULL)
259                 {
260                     GetClientRect(hwnd, &rcCredits);
261                     SetRect(&rcCredits, 0, 0, rcCredits.right, pImgInfo->cySource);
262                     FillRect(hDC, &rcCredits, GetSysColorBrush(COLOR_3DFACE));
263                     ReleaseDC(hwnd, hDC);
264                 }
265                 KillTimer(hwnd, timerid);
266                 if (hCreditsBitmap != NULL)
267                     DeleteObject(hCreditsBitmap);
268 
269                 top = 0;
270                 timerid = 0;
271             }
272 
273             InvalidateRect(hwnd, NULL, FALSE);
274             break;
275         case WM_PAINT:
276         {
277             PAINTSTRUCT PS;
278             HDC hdcMem, hdc;
279             LONG left;
280 
281             hdc = wParam != 0 ? (HDC)wParam : BeginPaint(hwnd, &PS);
282 
283             GetClientRect(hwnd, &PS.rcPaint);
284 
285             /* Position image in center of dialog */
286             left = (PS.rcPaint.right - pImgInfo->cxSource) / 2;
287             hdcMem = CreateCompatibleDC(hdc);
288 
289             if (hdcMem != NULL)
290             {
291                 if(timerid != 0)
292                 {
293                     SelectObject(hdcMem, hCreditsBitmap);
294                     BitBlt(hdc, left, PS.rcPaint.top, PS.rcPaint.right - PS.rcPaint.left, PS.rcPaint.top + pImgInfo->cySource, hdcMem, 0, top, SRCCOPY);
295                 }
296                 else
297                 {
298                     SelectObject(hdcMem, pImgInfo->hBitmap);
299                     AlphaBlend(hdc, left, PS.rcPaint.top, pImgInfo->cxSource, pImgInfo->cySource, hdcMem, 0, 0, pImgInfo->cxSource, pImgInfo->cySource, BlendFunc);
300                 }
301 
302                 DeleteDC(hdcMem);
303             }
304 
305             if (wParam == 0)
306                 EndPaint(hwnd,&PS);
307             break;
308         }
309     }
310     return TRUE;
311 }
312 
313 static VOID SetRegTextData(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID)
314 {
315     LPTSTR lpBuf = NULL;
316     DWORD BufSize = 0;
317     DWORD Type;
318 
319     if (RegQueryValueEx(hKey, Value, NULL, &Type, NULL, &BufSize) == ERROR_SUCCESS)
320     {
321         lpBuf = HeapAlloc(GetProcessHeap(), 0, BufSize);
322 
323         if (!lpBuf)
324             return;
325 
326         if (RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)lpBuf, &BufSize) == ERROR_SUCCESS)
327             SetDlgItemText(hwnd, uID, lpBuf);
328 
329         HeapFree(GetProcessHeap(), 0, lpBuf);
330     }
331 }
332 
333 static INT SetProcNameString(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID1, UINT uID2)
334 {
335     LPTSTR lpBuf = NULL;
336     DWORD BufSize = 0;
337     DWORD Type;
338     INT Ret = 0;
339     TCHAR szBuf[31];
340     TCHAR* szLastSpace;
341     INT LastSpace = 0;
342 
343     if (RegQueryValueEx(hKey, Value, NULL, &Type, NULL, &BufSize) == ERROR_SUCCESS)
344     {
345         lpBuf = HeapAlloc(GetProcessHeap(), 0, BufSize);
346 
347         if (!lpBuf)
348             return 0;
349 
350         if (RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)lpBuf, &BufSize) == ERROR_SUCCESS)
351         {
352             if (BufSize > ((30 + 1) * sizeof(TCHAR)))
353             {
354                 /* Wrap the Processor Name String like XP does:                           *
355                 *   - Take the first 30 characters and look for the last space.          *
356                 *     Then wrap the string after this space.                             *
357                 *   - If no space is found, wrap the string after character 30.          *
358                 *                                                                        *
359                 * For example the Processor Name String of a Pentium 4 is right-aligned. *
360                 * With this wrapping the first line looks centered.                      */
361 
362                 _tcsncpy(szBuf, lpBuf, 30);
363                 szBuf[30] = 0;
364                 szLastSpace = _tcsrchr(szBuf, ' ');
365 
366                 if (szLastSpace == 0)
367                 {
368                     LastSpace = 30;
369                 }
370                 else
371                 {
372                     LastSpace = (szLastSpace - szBuf);
373                     szBuf[LastSpace] = 0;
374                 }
375 
376                 _tcsncpy(szBuf, lpBuf, LastSpace);
377 
378                 SetDlgItemText(hwnd, uID1, szBuf);
379                 SetDlgItemText(hwnd, uID2, lpBuf+LastSpace+1);
380 
381                 /* Return the number of used lines */
382                 Ret = 2;
383             }
384             else
385             {
386                 SetDlgItemText(hwnd, uID1, lpBuf);
387                 Ret = 1;
388             }
389         }
390 
391         HeapFree(GetProcessHeap(), 0, lpBuf);
392     }
393 
394     return Ret;
395 }
396 
397 static VOID MakeFloatValueString(DOUBLE* dFloatValue, LPTSTR szOutput, LPTSTR szAppend)
398 {
399     TCHAR szDecimalSeparator[4];
400 
401     /* Get the decimal separator for the current locale */
402     if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, szDecimalSeparator, sizeof(szDecimalSeparator) / sizeof(TCHAR)) > 0)
403     {
404         UCHAR uDecimals;
405         UINT uIntegral;
406 
407         /* Show the value with two decimals */
408         uIntegral = (UINT)*dFloatValue;
409         uDecimals = (UCHAR)((UINT)(*dFloatValue * 100) - uIntegral * 100);
410 
411         wsprintf(szOutput, _T("%u%s%02u %s"), uIntegral, szDecimalSeparator, uDecimals, szAppend);
412     }
413 }
414 
415 static BOOL SetProcSpeed(HWND hwnd, HKEY hKey, LPTSTR Value, UINT uID)
416 {
417     TCHAR szBuf[64], szHz[16];
418     DWORD BufSize = sizeof(DWORD);
419     DWORD Type = REG_SZ;
420     PROCESSOR_POWER_INFORMATION ppi;
421 
422     ZeroMemory(&ppi, sizeof(ppi));
423 
424     if ((CallNtPowerInformation(ProcessorInformation,
425                                 NULL,
426                                 0,
427                                 (PVOID)&ppi,
428                                 sizeof(ppi)) == STATUS_SUCCESS &&
429          ppi.CurrentMhz != 0) || RegQueryValueEx(hKey, Value, NULL, &Type, (PBYTE)&ppi.CurrentMhz, &BufSize) == ERROR_SUCCESS)
430     {
431         if (ppi.CurrentMhz < 1000)
432         {
433             if (!LoadString(hApplet, IDS_MEGAHERTZ, szHz, _countof(szHz)))
434             {
435                 return FALSE;
436             }
437             StringCchPrintf(szBuf, _countof(szBuf), _T("%lu %s"), ppi.CurrentMhz, szHz);
438         }
439         else
440         {
441             double flt = ppi.CurrentMhz / 1000.0;
442             if (!LoadString(hApplet, IDS_GIGAHERTZ, szHz, _countof(szHz)))
443             {
444                 return FALSE;
445             }
446             MakeFloatValueString(&flt, szBuf, szHz);
447         }
448 
449         SetDlgItemText(hwnd, uID, szBuf);
450         return TRUE;
451     }
452 
453     return FALSE;
454 }
455 
456 static VOID GetSystemInformation(HWND hwnd)
457 {
458     HKEY hKey;
459     TCHAR SysKey[] = _T("HARDWARE\\DESCRIPTION\\System");
460     TCHAR ProcKey[] = _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
461     MEMORYSTATUSEX MemStat;
462     TCHAR Buf[32];
463     WCHAR SMBiosName[96];
464     INT CurMachineLine = IDC_MACHINELINE1;
465 
466     /*
467      * Get hardware device name or motherboard name
468      * using information from raw SMBIOS data
469      */
470     if (GetSystemName(SMBiosName, _countof(SMBiosName)))
471     {
472         SetDlgItemText(hwnd, CurMachineLine, SMBiosName);
473         CurMachineLine++;
474     }
475     else
476     {
477         /* If SMBIOS is not available, use System Identifier */
478         if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, SysKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
479         {
480             SetRegTextData(hwnd, hKey, _T("Identifier"), CurMachineLine);
481             CurMachineLine++;
482             RegCloseKey(hKey);
483         }
484     }
485     /*
486      * Get Processor information
487      * although undocumented, this information is being pulled
488      * directly out of the registry instead of via setupapi as it
489      * contains all the info we need, and should remain static
490      */
491     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, ProcKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
492     {
493         INT PrevMachineLine;
494 
495         SetRegTextData(hwnd, hKey, _T("VendorIdentifier"), CurMachineLine);
496         CurMachineLine++;
497 
498         PrevMachineLine = CurMachineLine;
499         CurMachineLine += SetProcNameString(hwnd,
500                                             hKey,
501                                             _T("ProcessorNameString"),
502                                             CurMachineLine,
503                                             CurMachineLine + 1);
504 
505         if (CurMachineLine == PrevMachineLine)
506         {
507             /* TODO: Try obtaining CPU name from WMI (i.e. CIM_Processor) */
508 
509             /* Brand String is not available, use Identifier instead */
510             CurMachineLine += SetProcNameString(hwnd,
511                                                 hKey,
512                                                 _T("Identifier"),
513                                                 CurMachineLine,
514                                                 CurMachineLine + 1);
515         }
516 
517         if (SetProcSpeed(hwnd, hKey, _T("~MHz"), CurMachineLine))
518             CurMachineLine++;
519         RegCloseKey(hKey);
520     }
521 
522     /* Get total physical RAM */
523     MemStat.dwLength = sizeof(MemStat);
524 
525     if (GlobalMemoryStatusEx(&MemStat))
526     {
527         TCHAR szStr[32];
528         double dTotalPhys;
529 
530         if (MemStat.ullTotalPhys > 1024 * 1024 * 1024)
531         {
532             UINT i = 0;
533             static const UINT uStrId[] = { IDS_GIGABYTE, IDS_TERABYTE, IDS_PETABYTE};
534 
535             // We're dealing with GBs or more
536             MemStat.ullTotalPhys /= 1024 * 1024;
537 
538             if (MemStat.ullTotalPhys > 1024 * 1024)
539             {
540                 // We're dealing with TBs or more
541                 MemStat.ullTotalPhys /= 1024;
542                 i++;
543 
544                 if (MemStat.ullTotalPhys > 1024 * 1024)
545                 {
546                     // We're dealing with PBs or more
547                     MemStat.ullTotalPhys /= 1024;
548                     i++;
549 
550                     dTotalPhys = (double)MemStat.ullTotalPhys / 1024;
551                 }
552                 else
553                 {
554                     dTotalPhys = (double)MemStat.ullTotalPhys / 1024;
555                 }
556             }
557             else
558             {
559                 dTotalPhys = (double)MemStat.ullTotalPhys / 1024;
560             }
561 
562             LoadString(hApplet, uStrId[i], szStr, sizeof(szStr) / sizeof(TCHAR));
563             MakeFloatValueString(&dTotalPhys, Buf, szStr);
564         }
565         else
566         {
567             // We're dealing with MBs, don't show any decimals
568             LoadString(hApplet, IDS_MEGABYTE, szStr, sizeof(szStr) / sizeof(TCHAR));
569             wsprintf(Buf, _T("%u %s"), (UINT)MemStat.ullTotalPhys / 1024 / 1024, szStr);
570         }
571 
572         SetDlgItemText(hwnd, CurMachineLine, Buf);
573     }
574 }
575 
576 static VOID GetSystemVersion(HWND hwnd)
577 {
578     HWND hRosVersion;
579     SIZE_T lenStr, lenVersion;
580     PCWSTR pwszVersion = L" " TEXT(KERNEL_VERSION_RC);
581     PWSTR pwszStr;
582 
583     lenVersion = wcslen(pwszVersion);
584     if (lenVersion == 0)
585     {
586         return;
587     }
588 
589     hRosVersion = GetDlgItem(hwnd, IDC_ROSVERSION);
590     if (!hRosVersion)
591     {
592         return;
593     }
594     lenStr = GetWindowTextLengthW(hRosVersion);
595     lenStr += lenVersion + 1;
596     pwszStr = HeapAlloc(GetProcessHeap(), 0, lenStr * sizeof(WCHAR));
597     if (!pwszStr)
598     {
599         return;
600     }
601     GetWindowText(hRosVersion, pwszStr, lenStr);
602 
603     StringCchCatW(pwszStr, lenStr, pwszVersion);
604     SetWindowText(hRosVersion, pwszStr);
605 
606     HeapFree(GetProcessHeap(), 0, pwszStr);
607 }
608 
609 /**
610  * @brief
611  * An equivalent of GetTickCount64, implemented using QueryPerformanceCounter.
612  *
613  * @return
614  * The number of milliseconds that have elapsed since the system was started.
615  */
616 static ULONGLONG GetTickCountQPC(VOID)
617 {
618     LARGE_INTEGER Counter, Frequency;
619 
620     QueryPerformanceCounter(&Counter);
621     QueryPerformanceFrequency(&Frequency);
622 
623     return (Counter.QuadPart * 1000) / Frequency.QuadPart;
624 }
625 
626 static VOID GetSystemUptime(HWND hwndDlg)
627 {
628     HWND hUptimeLabel;
629     ULONGLONG cMilliseconds;
630     ULONG cSeconds;
631     WCHAR szBuf[64];
632 
633     hUptimeLabel = GetDlgItem(hwndDlg, IDC_UPTIME);
634     if (!hUptimeLabel)
635     {
636         return;
637     }
638 
639     if (pGetTickCount64)
640     {
641         cMilliseconds = pGetTickCount64();
642     }
643     else
644     {
645         cMilliseconds = GetTickCountQPC();
646     }
647 
648     cSeconds = cMilliseconds / 1000;
649     StringCchPrintfW(szBuf, _countof(szBuf), szUptimeFormat,
650                      cSeconds / (60*60*24),     // Days
651                      (cSeconds / (60*60)) % 24, // Hours
652                      (cSeconds / 60) % 60,      // Minutes
653                      cSeconds % 60);            // Seconds
654 
655     SetWindowTextW(hUptimeLabel, szBuf);
656 
657     /* Set update timer (reset timeout if the timer exists) */
658     SetTimer(hwndDlg, ID_SYSUPTIME_UPDATE_TIMER, 1000 - (cMilliseconds % 1000), NULL);
659 }
660 
661 static VOID InitSystemUptime(HWND hwndDlg)
662 {
663     HMODULE hKernel32;
664 
665     /* Load time format string */
666     if (LoadStringW(hApplet, IDS_UPTIME_FORMAT, szUptimeFormat, _countof(szUptimeFormat)) == 0)
667     {
668         return;
669     }
670 
671     /* Load required DLLs */
672     hKernel32 = GetModuleHandleW(L"kernel32.dll");
673     if (hKernel32)
674     {
675         pGetTickCount64 = (PFGETTICKCOUNT64)GetProcAddress(hKernel32, "GetTickCount64");
676         if (!pGetTickCount64)
677         {
678             hKernel32Vista = LoadLibraryW(L"kernel32_vista.dll");
679             if (hKernel32Vista)
680             {
681                 pGetTickCount64 = (PFGETTICKCOUNT64)GetProcAddress(hKernel32Vista, "GetTickCount64");
682             }
683         }
684     }
685 
686     /* Show system uptime and set update timer */
687     GetSystemUptime(hwndDlg);
688 }
689 
690 /* Property page dialog callback */
691 INT_PTR CALLBACK GeneralPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
692 {
693     UNREFERENCED_PARAMETER(lParam);
694     UNREFERENCED_PARAMETER(wParam);
695 
696     switch (uMsg)
697     {
698         case WM_INITDIALOG:
699         {
700             pImgInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IMGINFO));
701             if (pImgInfo == NULL)
702             {
703                 EndDialog(hwndDlg, 0);
704                 return FALSE;
705             }
706 
707             InitLogo(hwndDlg);
708             SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_ROSIMG), GWLP_WNDPROC, (LONG_PTR)RosImageProc);
709             GetSystemInformation(hwndDlg);
710             GetSystemVersion(hwndDlg);
711             InitSystemUptime(hwndDlg);
712             break;
713         }
714 
715         case WM_DESTROY:
716         {
717             KillTimer(hwndDlg, ID_SYSUPTIME_UPDATE_TIMER);
718 
719             if (hKernel32Vista)
720             {
721                 FreeLibrary(hKernel32Vista);
722             }
723 
724             HeapFree(GetProcessHeap(), 0, pImgInfo);
725             break;
726         }
727 
728         case WM_TIMER:
729         {
730             if (wParam == ID_SYSUPTIME_UPDATE_TIMER)
731             {
732                 /* Update system uptime */
733                 GetSystemUptime(hwndDlg);
734             }
735 
736             break;
737         }
738 
739         case WM_COMMAND:
740         {
741             if (LOWORD(wParam) == IDC_LICENCE)
742             {
743                 DialogBox(hApplet, MAKEINTRESOURCE(IDD_LICENCE), hwndDlg, LicenceDlgProc);
744 
745                 return TRUE;
746             }
747             break;
748         }
749 
750         case WM_DRAWITEM:
751         {
752             LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT)lParam;
753 
754             if (lpDrawItem->CtlID == IDC_ROSIMG)
755             {
756                 HDC hdcMem;
757                 LONG left;
758 
759                 /* Position image in centre of dialog */
760                 left = (lpDrawItem->rcItem.right - pImgInfo->cxSource) / 2;
761 
762                 hdcMem = CreateCompatibleDC(lpDrawItem->hDC);
763                 if (hdcMem != NULL)
764                 {
765                     SelectObject(hdcMem, pImgInfo->hBitmap);
766                     BitBlt(lpDrawItem->hDC,
767                            left,
768                            lpDrawItem->rcItem.top,
769                            lpDrawItem->rcItem.right - lpDrawItem->rcItem.left,
770                            lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top,
771                            hdcMem,
772                            0,
773                            0,
774                            SRCCOPY);
775                     DeleteDC(hdcMem);
776                 }
777             }
778             return TRUE;
779         }
780 
781         case WM_NOTIFY:
782         {
783             NMHDR *nmhdr = (NMHDR *)lParam;
784 
785             if (nmhdr->idFrom == IDC_ROSHOMEPAGE_LINK && nmhdr->code == NM_CLICK)
786             {
787                 PNMLINK nml = (PNMLINK)nmhdr;
788 
789                 ShellExecuteW(hwndDlg, L"open", nml->item.szUrl, NULL, NULL, SW_SHOWNORMAL);
790             }
791             break;
792         }
793 
794     }
795 
796     return FALSE;
797 }
798