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