xref: /reactos/dll/cpl/hotplug/hotplug.c (revision 31876ba8)
1 /*
2 * PROJECT:     Safely Remove Hardware Applet
3 * LICENSE:     GPL - See COPYING in the top level directory
4 * FILE:        dll/cpl/hotplug/hotplug.c
5 * PURPOSE:     applet initialization
6 * PROGRAMMERS: Johannes Anderwald (johannes.anderwald@reactos.org)
7 */
8 
9 #include "hotplug.h"
10 
11 #include <initguid.h>
12 #include <devguid.h>
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 
18 typedef struct _HOTPLUG_DATA
19 {
20     HICON hIcon;
21     HICON hIconSm;
22     SP_CLASSIMAGELIST_DATA ImageListData;
23     HMENU hPopupMenu;
24     DWORD dwFlags;
25 } HOTPLUG_DATA, *PHOTPLUG_DATA;
26 
27 
28 // globals
29 HINSTANCE hApplet = 0;
30 
31 /* Applets */
32 APPLET Applets[NUM_APPLETS] =
33 {
34     {IDI_HOTPLUG, IDS_CPLNAME, IDS_CPLDESCRIPTION, InitApplet}
35 };
36 
37 static
38 DWORD
39 GetHotPlugFlags(VOID)
40 {
41     HKEY hKey = NULL;
42     DWORD dwFlags = 0;
43     DWORD dwSize, dwError;
44 
45     dwError = RegOpenKeyExW(HKEY_CURRENT_USER,
46                             REGSTR_PATH_SYSTRAY,
47                             0,
48                             KEY_READ,
49                             &hKey);
50     if (dwError != ERROR_SUCCESS)
51         goto done;
52 
53     dwSize = sizeof(dwFlags);
54     dwError = RegQueryValueExW(hKey,
55                                L"HotPlugFlags",
56                                NULL,
57                                NULL,
58                                (LPBYTE)&dwFlags,
59                                &dwSize);
60     if (dwError != ERROR_SUCCESS)
61         dwFlags = 0;
62 
63 done:
64     if (hKey != NULL)
65         RegCloseKey(hKey);
66 
67     return dwFlags;
68 }
69 
70 
71 static
72 DWORD
73 SetHotPlugFlags(
74     _In_ DWORD dwFlags)
75 {
76     HKEY hKey = NULL;
77     DWORD dwError;
78 
79     dwError = RegCreateKeyExW(HKEY_CURRENT_USER,
80                               REGSTR_PATH_SYSTRAY,
81                               0,
82                               NULL,
83                               REG_OPTION_NON_VOLATILE,
84                               KEY_WRITE,
85                               NULL,
86                               &hKey,
87                               NULL);
88     if (dwError != ERROR_SUCCESS)
89         goto done;
90 
91     dwError = RegSetValueExW(hKey,
92                              L"HotPlugFlags",
93                              0,
94                              REG_DWORD,
95                              (LPBYTE)&dwFlags,
96                              sizeof(dwFlags));
97 
98 done:
99     if (hKey != NULL)
100         RegCloseKey(hKey);
101 
102     return dwError;
103 }
104 
105 
106 static
107 HTREEITEM
108 InsertDeviceTreeItem(
109     _In_ HWND hwndDeviceTree,
110     _In_ HTREEITEM hParent,
111     _In_ DWORD DevInst,
112     _In_ PHOTPLUG_DATA pHotplugData)
113 {
114     WCHAR szDisplayName[40];
115     WCHAR szGuidString[MAX_GUID_STRING_LEN];
116     TVINSERTSTRUCTW tvItem;
117     GUID ClassGuid;
118     INT nClassImage;
119     DWORD dwSize;
120     CONFIGRET cr;
121 
122     /* Get the device description */
123     dwSize = sizeof(szDisplayName);
124     cr = CM_Get_DevNode_Registry_Property(DevInst,
125                                           CM_DRP_DEVICEDESC,
126                                           NULL,
127                                           szDisplayName,
128                                           &dwSize,
129                                           0);
130     if (cr != CR_SUCCESS)
131         wcscpy(szDisplayName, L"Unknown Device");
132 
133     /* Get the class GUID */
134     dwSize = sizeof(szGuidString);
135     cr = CM_Get_DevNode_Registry_Property(DevInst,
136                                           CM_DRP_CLASSGUID,
137                                           NULL,
138                                           szGuidString,
139                                           &dwSize,
140                                           0);
141     if (cr == CR_SUCCESS)
142     {
143         pSetupGuidFromString(szGuidString, &ClassGuid);
144     }
145     else
146     {
147         memcpy(&ClassGuid, &GUID_DEVCLASS_UNKNOWN, sizeof(GUID));
148     }
149 
150     /* Get the image for the class this device is in */
151     SetupDiGetClassImageIndex(&pHotplugData->ImageListData,
152                               &ClassGuid,
153                               &nClassImage);
154 
155     /* Add it to the device tree */
156     ZeroMemory(&tvItem, sizeof(tvItem));
157     tvItem.hParent = hParent;
158     tvItem.hInsertAfter = TVI_LAST;
159 
160     tvItem.item.mask = TVIF_STATE | TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
161     tvItem.item.state = TVIS_EXPANDED;
162     tvItem.item.stateMask = TVIS_EXPANDED;
163     tvItem.item.pszText = szDisplayName;
164     tvItem.item.iImage = nClassImage;
165     tvItem.item.iSelectedImage = nClassImage;
166     tvItem.item.lParam = (LPARAM)DevInst;
167 
168     return TreeView_InsertItem(hwndDeviceTree, &tvItem);
169 }
170 
171 
172 static
173 VOID
174 RecursiveInsertSubDevices(
175     _In_ HWND hwndDeviceTree,
176     _In_ HTREEITEM hParentItem,
177     _In_ DWORD ParentDevInst,
178     _In_ PHOTPLUG_DATA pHotplugData)
179 {
180     HTREEITEM hTreeItem;
181     DEVINST ChildDevInst;
182     CONFIGRET cr;
183 
184     DPRINT("RecursiveInsertSubDevices()\n");
185 
186     cr = CM_Get_Child(&ChildDevInst, ParentDevInst, 0);
187     if (cr != CR_SUCCESS)
188     {
189         DPRINT("No child! %lu\n", cr);
190         return;
191     }
192 
193     hTreeItem = InsertDeviceTreeItem(hwndDeviceTree,
194                                      hParentItem,
195                                      ChildDevInst,
196                                      pHotplugData);
197     if (hTreeItem != NULL)
198     {
199         RecursiveInsertSubDevices(hwndDeviceTree,
200                                   hTreeItem,
201                                   ChildDevInst,
202                                   pHotplugData);
203     }
204 
205     for (;;)
206     {
207         cr = CM_Get_Sibling(&ChildDevInst, ChildDevInst, 0);
208         if (cr != CR_SUCCESS)
209         {
210             DPRINT("No sibling! %lu\n", cr);
211             return;
212         }
213 
214         hTreeItem = InsertDeviceTreeItem(hwndDeviceTree,
215                                          hParentItem,
216                                          ChildDevInst,
217                                          pHotplugData);
218         if (hTreeItem != NULL)
219         {
220             RecursiveInsertSubDevices(hwndDeviceTree,
221                                       hTreeItem,
222                                       ChildDevInst,
223                                       pHotplugData);
224         }
225     }
226 }
227 
228 
229 static
230 VOID
231 EnumHotpluggedDevices(
232     HWND hwndDeviceTree,
233     PHOTPLUG_DATA pHotplugData)
234 {
235     SP_DEVINFO_DATA did = { 0 };
236     HDEVINFO hdev;
237     int idev;
238     DWORD dwCapabilities, dwSize;
239     ULONG ulStatus, ulProblem;
240     HTREEITEM hTreeItem;
241     CONFIGRET cr;
242 
243     DPRINT1("EnumHotpluggedDevices()\n");
244 
245     TreeView_DeleteAllItems(hwndDeviceTree);
246 
247     hdev = SetupDiGetClassDevs(NULL, NULL, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
248     if (hdev == INVALID_HANDLE_VALUE)
249         return;
250 
251     did.cbSize = sizeof(did);
252 
253     /* Enumerate all the attached devices */
254     for (idev = 0; SetupDiEnumDeviceInfo(hdev, idev, &did); idev++)
255     {
256         ulStatus = 0;
257         ulProblem = 0;
258 
259         cr = CM_Get_DevNode_Status(&ulStatus,
260                                    &ulProblem,
261                                    did.DevInst,
262                                    0);
263         if (cr != CR_SUCCESS)
264             continue;
265 
266         dwCapabilities = 0,
267         dwSize = sizeof(dwCapabilities);
268         cr = CM_Get_DevNode_Registry_Property(did.DevInst,
269                                               CM_DRP_CAPABILITIES,
270                                               NULL,
271                                               &dwCapabilities,
272                                               &dwSize,
273                                               0);
274         if (cr != CR_SUCCESS)
275             continue;
276 
277         /* Add devices that require safe removal to the device tree */
278         if ( (dwCapabilities & CM_DEVCAP_REMOVABLE) &&
279             !(dwCapabilities & CM_DEVCAP_DOCKDEVICE) &&
280             !(dwCapabilities & CM_DEVCAP_SURPRISEREMOVALOK) &&
281             ((dwCapabilities & CM_DEVCAP_EJECTSUPPORTED) || (ulStatus & DN_DISABLEABLE)) &&
282             ulProblem == 0)
283         {
284             hTreeItem = InsertDeviceTreeItem(hwndDeviceTree,
285                                              TVI_ROOT,
286                                              did.DevInst,
287                                              pHotplugData);
288 
289             if ((hTreeItem != NULL) && (pHotplugData->dwFlags & HOTPLUG_DISPLAY_DEVICE_COMPONENTS))
290             {
291                 RecursiveInsertSubDevices(hwndDeviceTree,
292                                           hTreeItem,
293                                           did.DevInst,
294                                           pHotplugData);
295             }
296         }
297     }
298 
299     SetupDiDestroyDeviceInfoList(hdev);
300 }
301 
302 
303 static
304 VOID
305 UpdateButtons(
306     HWND hwndDlg)
307 {
308     BOOL bEnabled;
309 
310     bEnabled = (TreeView_GetCount(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE)) != 0);
311 
312     EnableWindow(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_PROPERTIES), bEnabled);
313     EnableWindow(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_STOP), bEnabled);
314 }
315 
316 
317 static
318 VOID
319 ShowContextMenu(
320     HWND hwndDlg,
321     HWND hwndTreeView,
322     PHOTPLUG_DATA pHotplugData)
323 {
324     HTREEITEM hTreeItem;
325     RECT rc;
326     POINT pt;
327 
328     hTreeItem = TreeView_GetSelection(hwndTreeView);
329     if (hTreeItem == NULL)
330         return;
331 
332     TreeView_GetItemRect(hwndTreeView, hTreeItem, &rc, TRUE);
333 
334     pt.x = (rc.left + rc.right) / 2;
335     pt.y = (rc.top + rc.bottom) / 2;
336     ClientToScreen(hwndTreeView, &pt);
337     TrackPopupMenu(GetSubMenu(pHotplugData->hPopupMenu, 0),
338                    TPM_LEFTALIGN | TPM_TOPALIGN,
339                    pt.x,
340                    pt.y,
341                    0,
342                    hwndDlg,
343                    NULL);
344 }
345 
346 
347 static
348 DEVINST
349 GetSelectedDeviceInst(
350     _In_ HWND hwndDeviceTree)
351 {
352     HTREEITEM hTreeItem;
353     TVITEMW item;
354 
355     hTreeItem = TreeView_GetSelection(hwndDeviceTree);
356     if (hTreeItem == NULL)
357         return 0;
358 
359     ZeroMemory(&item, sizeof(item));
360     item.mask = TVIF_PARAM;
361     item.hItem = hTreeItem;
362 
363     TreeView_GetItem(hwndDeviceTree, &item);
364 
365     return item.lParam;
366 }
367 
368 static
369 VOID
370 ShowDeviceProperties(
371     _In_ HWND hwndParent,
372     _In_ DEVINST DevInst)
373 {
374     ULONG ulSize;
375     CONFIGRET cr;
376     LPWSTR pszDevId;
377 
378     cr = CM_Get_Device_ID_Size(&ulSize, DevInst, 0);
379     if (cr != CR_SUCCESS || ulSize == 0)
380         return;
381 
382     /* Take the terminating NULL into account */
383     ulSize++;
384 
385     pszDevId = HeapAlloc(GetProcessHeap(), 0, ulSize * sizeof(WCHAR));
386     if (pszDevId == NULL)
387         return;
388 
389     cr = CM_Get_Device_IDW(DevInst, pszDevId, ulSize, 0);
390     if (cr == CR_SUCCESS)
391     {
392         typedef int (WINAPI *PFDEVICEPROPERTIESW)(HWND, LPCWSTR, LPCWSTR, BOOL);
393         HMODULE hDevMgrDll;
394         PFDEVICEPROPERTIESW pDevicePropertiesW;
395 
396         hDevMgrDll = LoadLibraryW(L"devmgr.dll");
397         if (hDevMgrDll != NULL)
398         {
399             pDevicePropertiesW = (PFDEVICEPROPERTIESW)GetProcAddress(hDevMgrDll, "DevicePropertiesW");
400             if (pDevicePropertiesW != NULL)
401                 pDevicePropertiesW(hwndParent, NULL, pszDevId, FALSE);
402 
403             FreeLibrary(hDevMgrDll);
404         }
405     }
406 
407     HeapFree(GetProcessHeap(), 0, pszDevId);
408 }
409 
410 INT_PTR
411 CALLBACK
412 SafeRemovalDlgProc(
413     HWND hwndDlg,
414     UINT uMsg,
415     WPARAM wParam,
416     LPARAM lParam)
417 {
418     PHOTPLUG_DATA pHotplugData;
419 
420     pHotplugData = (PHOTPLUG_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER);
421 
422     switch (uMsg)
423     {
424         case WM_INITDIALOG:
425             pHotplugData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HOTPLUG_DATA));
426             if (pHotplugData != NULL)
427             {
428                 WCHAR szWindowTitle[MAX_PATH];
429 
430                 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pHotplugData);
431 
432                 if (LoadStringW(hApplet,
433                                 IDS_CPLNAME,
434                                 szWindowTitle,
435                                 ARRAYSIZE(szWindowTitle)))
436                 {
437                     SetWindowTextW(hwndDlg, szWindowTitle);
438                 }
439 
440                 pHotplugData->hIcon = (HICON)LoadImageW(hApplet,
441                                                         MAKEINTRESOURCEW(IDI_HOTPLUG),
442                                                         IMAGE_ICON,
443                                                         GetSystemMetrics(SM_CXICON),
444                                                         GetSystemMetrics(SM_CYICON),
445                                                         LR_DEFAULTCOLOR);
446                 pHotplugData->hIconSm = (HICON)LoadImageW(hApplet,
447                                                           MAKEINTRESOURCEW(IDI_HOTPLUG),
448                                                           IMAGE_ICON,
449                                                           GetSystemMetrics(SM_CXSMICON),
450                                                           GetSystemMetrics(SM_CYSMICON),
451                                                           LR_DEFAULTCOLOR);
452                 SendMessageW(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)pHotplugData->hIcon);
453                 SendMessageW(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)pHotplugData->hIconSm);
454 
455                 pHotplugData->ImageListData.cbSize = sizeof(pHotplugData->ImageListData);
456                 SetupDiGetClassImageList(&pHotplugData->ImageListData);
457 
458                 pHotplugData->hPopupMenu = LoadMenu(hApplet, MAKEINTRESOURCE(IDM_POPUP_DEVICE_TREE));
459 
460                 pHotplugData->dwFlags = GetHotPlugFlags();
461 
462                 if (pHotplugData->dwFlags & HOTPLUG_DISPLAY_DEVICE_COMPONENTS)
463                     Button_SetCheck(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DISPLAY_COMPONENTS), BST_CHECKED);
464 
465                 TreeView_SetImageList(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE),
466                                       pHotplugData->ImageListData.ImageList,
467                                       TVSIL_NORMAL);
468 
469                 EnumHotpluggedDevices(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE),
470                                       pHotplugData);
471                 UpdateButtons(hwndDlg);
472             }
473             return TRUE;
474 
475         case WM_COMMAND:
476             switch (LOWORD(wParam))
477             {
478                 case IDCLOSE:
479                     KillTimer(hwndDlg, 1);
480                     EndDialog(hwndDlg, TRUE);
481                     break;
482 
483                 case IDC_SAFE_REMOVE_DISPLAY_COMPONENTS:
484                     if (HIWORD(wParam) == BN_CLICKED)
485                     {
486                         if (pHotplugData != NULL)
487                         {
488                             if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DISPLAY_COMPONENTS)) == BST_CHECKED)
489                                 pHotplugData->dwFlags |= HOTPLUG_DISPLAY_DEVICE_COMPONENTS;
490                             else
491                                 pHotplugData->dwFlags &= ~HOTPLUG_DISPLAY_DEVICE_COMPONENTS;
492 
493                             SetHotPlugFlags(pHotplugData->dwFlags);
494 
495                             EnumHotpluggedDevices(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE),
496                                                   pHotplugData);
497                         }
498                     }
499                     break;
500 
501                 case IDC_SAFE_REMOVE_PROPERTIES:
502                 case IDM_PROPERTIES:
503                 {
504                     HWND hwndDevTree = GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE);
505                     ShowDeviceProperties(hwndDlg, GetSelectedDeviceInst(hwndDevTree));
506                     break;
507                 }
508             }
509             break;
510 
511         case WM_DEVICECHANGE:
512             switch (wParam)
513             {
514                 case DBT_DEVNODES_CHANGED:
515                     SetTimer(hwndDlg, 1, 500, NULL);
516                     break;
517             }
518             break;
519 
520         case WM_TIMER:
521             if (wParam == 1)
522             {
523                 KillTimer(hwndDlg, 1);
524 
525                 if (pHotplugData != NULL)
526                 {
527                     EnumHotpluggedDevices(GetDlgItem(hwndDlg, IDC_SAFE_REMOVE_DEVICE_TREE),
528                                           pHotplugData);
529                     UpdateButtons(hwndDlg);
530                 }
531             }
532             break;
533 
534         case WM_NOTIFY:
535             if (((LPNMHDR)lParam)->idFrom == IDC_SAFE_REMOVE_DEVICE_TREE)
536             {
537                 if (((LPNMHDR)lParam)->code == NM_RCLICK)
538                 {
539                     if (pHotplugData != NULL)
540                     {
541                         ShowContextMenu(hwndDlg,
542                                         ((LPNMHDR)lParam)->hwndFrom,
543                                         pHotplugData);
544                         return TRUE;
545                     }
546                 }
547             }
548             break;
549 
550         case WM_CLOSE:
551             KillTimer(hwndDlg, 1);
552             EndDialog(hwndDlg, TRUE);
553             break;
554 
555         case WM_DESTROY:
556             if (pHotplugData != NULL)
557             {
558                 if (pHotplugData->hPopupMenu != NULL)
559                     DestroyMenu(pHotplugData->hPopupMenu);
560 
561                 SetupDiDestroyClassImageList(&pHotplugData->ImageListData);
562 
563                 if (pHotplugData->hIconSm)
564                 {
565                     DestroyIcon(pHotplugData->hIconSm);
566                 }
567 
568                 if (pHotplugData->hIcon)
569                 {
570                     DestroyIcon(pHotplugData->hIcon);
571                 }
572 
573                 HeapFree(GetProcessHeap(), 0, pHotplugData);
574                 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)NULL);
575             }
576             break;
577     }
578 
579     return FALSE;
580 }
581 
582 
583 LONG
584 APIENTRY
585 InitApplet(
586     HWND hwnd,
587     UINT uMsg,
588     LPARAM wParam,
589     LPARAM lParam)
590 {
591     DPRINT("InitApplet()\n");
592 
593     DialogBox(hApplet,
594               MAKEINTRESOURCE(IDD_SAFE_REMOVE_HARDWARE_DIALOG),
595               hwnd,
596               SafeRemovalDlgProc);
597 
598     // TODO
599     return TRUE;
600 }
601 
602 
603 LONG
604 CALLBACK
605 CPlApplet(
606     HWND hwndCPl,
607     UINT uMsg,
608     LPARAM lParam1,
609     LPARAM lParam2)
610 {
611     UINT i = (UINT)lParam1;
612 
613     switch(uMsg)
614     {
615         case CPL_INIT:
616             return TRUE;
617 
618         case CPL_GETCOUNT:
619             return NUM_APPLETS;
620 
621         case CPL_INQUIRE:
622             if (i < NUM_APPLETS)
623             {
624                 CPLINFO *CPlInfo = (CPLINFO*)lParam2;
625                 CPlInfo->lData = 0;
626                 CPlInfo->idIcon = Applets[i].idIcon;
627                 CPlInfo->idName = Applets[i].idName;
628                 CPlInfo->idInfo = Applets[i].idDescription;
629             }
630             else
631             {
632                 return TRUE;
633             }
634             break;
635 
636         case CPL_DBLCLK:
637             if (i < NUM_APPLETS)
638                 Applets[i].AppletProc(hwndCPl, uMsg, lParam1, lParam2);
639             else
640                 return TRUE;
641             break;
642 
643         case CPL_STARTWPARMSW:
644             if (i < NUM_APPLETS)
645                 return Applets[i].AppletProc(hwndCPl, uMsg, lParam1, lParam2);
646             break;
647     }
648     return FALSE;
649 }
650 
651 
652 INT
653 WINAPI
654 DllMain(
655     HINSTANCE hinstDLL,
656     DWORD dwReason,
657     LPVOID lpvReserved)
658 {
659     UNREFERENCED_PARAMETER(lpvReserved);
660 
661     switch (dwReason)
662     {
663         case DLL_PROCESS_ATTACH:
664         case DLL_THREAD_ATTACH:
665             hApplet = hinstDLL;
666             break;
667     }
668     return TRUE;
669 }
670