2 * Add/Remove Programs applet
3 * Partially based on Wine Uninstaller
5 * Copyright 2000 Andreas Mohr
6 * Copyright 2004 Hannu Valtonen
7 * Copyright 2005 Jonathan Ernst
8 * Copyright 2001-2002, 2008 Owen Rudge
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #define NONAMELESSUNION
29 #include "wine/port.h"
30 #include "wine/unicode.h"
31 #include "wine/debug.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(appwizcpl
);
50 /* define a maximum length for various buffers we use */
51 #define MAX_STRING_LEN 1024
53 typedef struct APPINFO
{
66 WCHAR regkey
[MAX_STRING_LEN
];
71 static struct APPINFO
*AppInfo
= NULL
;
72 static HINSTANCE hInst
;
74 /* names of registry keys */
75 static const WCHAR BackSlashW
[] = { '\\', 0 };
76 static const WCHAR DisplayNameW
[] = {'D','i','s','p','l','a','y','N','a','m','e',0};
77 static const WCHAR DisplayIconW
[] = {'D','i','s','p','l','a','y','I','c','o','n',0};
78 static const WCHAR DisplayVersionW
[] = {'D','i','s','p','l','a','y','V','e','r',
80 static const WCHAR PublisherW
[] = {'P','u','b','l','i','s','h','e','r',0};
81 static const WCHAR ContactW
[] = {'C','o','n','t','a','c','t',0};
82 static const WCHAR HelpLinkW
[] = {'H','e','l','p','L','i','n','k',0};
83 static const WCHAR HelpTelephoneW
[] = {'H','e','l','p','T','e','l','e','p','h',
85 static const WCHAR ReadmeW
[] = {'R','e','a','d','m','e',0};
86 static const WCHAR URLUpdateInfoW
[] = {'U','R','L','U','p','d','a','t','e','I',
88 static const WCHAR CommentsW
[] = {'C','o','m','m','e','n','t','s',0};
89 static const WCHAR UninstallCommandlineW
[] = {'U','n','i','n','s','t','a','l','l',
90 'S','t','r','i','n','g',0};
92 static const WCHAR PathUninstallW
[] = {
93 'S','o','f','t','w','a','r','e','\\',
94 'M','i','c','r','o','s','o','f','t','\\',
95 'W','i','n','d','o','w','s','\\',
96 'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
97 'U','n','i','n','s','t','a','l','l',0 };
99 /******************************************************************************
101 * Description: Entry point for DLL file
103 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
,
106 TRACE("(%p, %d, %p)\n", hinstDLL
, fdwReason
, lpvReserved
);
110 case DLL_PROCESS_ATTACH
:
117 /******************************************************************************
119 * Description: Frees memory used by an AppInfo structure, and any children.
121 static void FreeAppInfo(APPINFO
*info
)
125 APPINFO
*next_info
= info
->next
;
127 HeapFree(GetProcessHeap(), 0, info
->title
);
128 HeapFree(GetProcessHeap(), 0, info
->path
);
129 HeapFree(GetProcessHeap(), 0, info
->icon
);
130 HeapFree(GetProcessHeap(), 0, info
->publisher
);
131 HeapFree(GetProcessHeap(), 0, info
->version
);
132 HeapFree(GetProcessHeap(), 0, info
);
137 /******************************************************************************
138 * Name : ReadApplicationsFromRegistry
139 * Description: Creates a linked list of uninstallable applications from the
141 * Parameters : root - Which registry root to read from (HKCU/HKLM)
142 * Returns : TRUE if successful, FALSE otherwise
144 static BOOL
ReadApplicationsFromRegistry(HKEY root
)
146 HKEY hkeyUninst
, hkeyApp
;
148 DWORD sizeOfSubKeyName
, displen
, uninstlen
;
149 WCHAR subKeyName
[256];
150 WCHAR key_app
[MAX_STRING_LEN
];
152 APPINFO
*iter
= AppInfo
;
156 if (RegOpenKeyExW(root
, PathUninstallW
, 0, KEY_READ
, &hkeyUninst
) !=
160 lstrcpyW(key_app
, PathUninstallW
);
161 lstrcatW(key_app
, BackSlashW
);
162 p
= key_app
+lstrlenW(PathUninstallW
)+1;
164 sizeOfSubKeyName
= sizeof(subKeyName
) / sizeof(subKeyName
[0]);
168 /* find the end of the list */
169 for (iter
= AppInfo
; iter
->next
; iter
= iter
->next
);
172 for (i
= 0; RegEnumKeyExW(hkeyUninst
, i
, subKeyName
, &sizeOfSubKeyName
, NULL
,
173 NULL
, NULL
, NULL
) != ERROR_NO_MORE_ITEMS
; ++i
)
175 lstrcpyW(p
, subKeyName
);
176 RegOpenKeyExW(root
, key_app
, 0, KEY_READ
, &hkeyApp
);
181 if ((RegQueryValueExW(hkeyApp
, DisplayNameW
, 0, 0, NULL
, &displen
) ==
182 ERROR_SUCCESS
) && (RegQueryValueExW(hkeyApp
, UninstallCommandlineW
,
183 0, 0, NULL
, &uninstlen
) == ERROR_SUCCESS
))
185 /* if we already have iter, allocate the next entry */
188 iter
->next
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
,
189 sizeof(struct APPINFO
));
198 /* if not, start the list */
199 iter
= AppInfo
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
,
200 sizeof(struct APPINFO
));
206 iter
->title
= HeapAlloc(GetProcessHeap(), 0, displen
);
211 RegQueryValueExW(hkeyApp
, DisplayNameW
, 0, 0, (LPBYTE
)iter
->title
,
214 /* now get DisplayIcon */
216 RegQueryValueExW(hkeyApp
, DisplayIconW
, 0, 0, NULL
, &displen
);
222 iter
->icon
= HeapAlloc(GetProcessHeap(), 0, displen
);
227 RegQueryValueExW(hkeyApp
, DisplayIconW
, 0, 0, (LPBYTE
)iter
->icon
,
230 /* separate the index from the icon name, if supplied */
231 iconPtr
= strchrW(iter
->icon
, ',');
236 iter
->iconIdx
= atoiW(iconPtr
);
240 iter
->path
= HeapAlloc(GetProcessHeap(), 0, uninstlen
);
245 RegQueryValueExW(hkeyApp
, UninstallCommandlineW
, 0, 0,
246 (LPBYTE
)iter
->path
, &uninstlen
);
248 /* publisher, version */
249 if (RegQueryValueExW(hkeyApp
, PublisherW
, 0, 0, NULL
, &displen
) ==
252 iter
->publisher
= HeapAlloc(GetProcessHeap(), 0, displen
);
254 if (!iter
->publisher
)
257 RegQueryValueExW(hkeyApp
, PublisherW
, 0, 0, (LPBYTE
)iter
->publisher
,
261 if (RegQueryValueExW(hkeyApp
, DisplayVersionW
, 0, 0, NULL
, &displen
) ==
264 iter
->version
= HeapAlloc(GetProcessHeap(), 0, displen
);
269 RegQueryValueExW(hkeyApp
, DisplayVersionW
, 0, 0, (LPBYTE
)iter
->version
,
274 iter
->regroot
= root
;
275 lstrcpyW(iter
->regkey
, subKeyName
);
280 RegCloseKey(hkeyApp
);
281 sizeOfSubKeyName
= sizeof(subKeyName
) / sizeof(subKeyName
[0]);
288 RegCloseKey(hkeyApp
);
292 RegCloseKey(hkeyUninst
);
297 /******************************************************************************
298 * Name : AddApplicationsToList
299 * Description: Populates the list box with applications.
300 * Parameters : hWnd - Handle of the dialog box
302 static void AddApplicationsToList(HWND hWnd
, HIMAGELIST hList
)
304 APPINFO
*iter
= AppInfo
;
316 if (ExtractIconExW(iter
->icon
, iter
->iconIdx
, NULL
, &hIcon
, 1) == 1)
318 index
= ImageList_AddIcon(hList
, hIcon
);
323 lvItem
.mask
= LVIF_IMAGE
| LVIF_TEXT
| LVIF_PARAM
;
324 lvItem
.iItem
= iter
->id
;
326 lvItem
.pszText
= iter
->title
;
327 lvItem
.iImage
= index
;
328 lvItem
.lParam
= iter
->id
;
330 index
= ListView_InsertItemW(hWnd
, &lvItem
);
332 /* now add the subitems (columns) */
333 ListView_SetItemTextW(hWnd
, index
, 1, iter
->publisher
);
334 ListView_SetItemTextW(hWnd
, index
, 2, iter
->version
);
340 /******************************************************************************
341 * Name : RemoveItemsFromList
342 * Description: Clears the application list box.
343 * Parameters : hWnd - Handle of the dialog box
345 static void RemoveItemsFromList(HWND hWnd
)
347 SendDlgItemMessageW(hWnd
, IDL_PROGRAMS
, LVM_DELETEALLITEMS
, 0, 0);
350 /******************************************************************************
352 * Description: Frees memory used by the application linked list.
354 static inline void EmptyList(void)
356 FreeAppInfo(AppInfo
);
360 /******************************************************************************
361 * Name : UpdateButtons
362 * Description: Enables/disables the Add/Remove button depending on current
363 * selection in list box.
364 * Parameters : hWnd - Handle of the dialog box
366 static void UpdateButtons(HWND hWnd
)
368 BOOL sel
= ListView_GetSelectedCount(GetDlgItem(hWnd
, IDL_PROGRAMS
)) != 0;
370 EnableWindow(GetDlgItem(hWnd
, IDC_ADDREMOVE
), sel
);
371 EnableWindow(GetDlgItem(hWnd
, IDC_SUPPORT_INFO
), sel
);
374 /******************************************************************************
375 * Name : UninstallProgram
376 * Description: Executes the specified program's installer.
377 * Parameters : id - the internal ID of the installer to remove
379 static void UninstallProgram(int id
)
383 PROCESS_INFORMATION info
;
384 WCHAR errormsg
[MAX_STRING_LEN
];
385 WCHAR sUninstallFailed
[MAX_STRING_LEN
];
389 LoadStringW(hInst
, IDS_UNINSTALL_FAILED
, sUninstallFailed
,
390 sizeof(sUninstallFailed
) / sizeof(sUninstallFailed
[0]));
392 for (iter
= AppInfo
; iter
; iter
= iter
->next
)
396 TRACE("Uninstalling %s (%s)\n", wine_dbgstr_w(iter
->title
),
397 wine_dbgstr_w(iter
->path
));
399 memset(&si
, 0, sizeof(STARTUPINFOW
));
400 si
.cb
= sizeof(STARTUPINFOW
);
401 si
.wShowWindow
= SW_NORMAL
;
402 res
= CreateProcessW(NULL
, iter
->path
, NULL
, NULL
, FALSE
, 0, NULL
,
407 CloseHandle(info
.hThread
);
409 /* wait for the process to exit */
410 WaitForSingleObject(info
.hProcess
, INFINITE
);
411 CloseHandle(info
.hProcess
);
415 wsprintfW(errormsg
, sUninstallFailed
, iter
->path
);
417 if (MessageBoxW(0, errormsg
, iter
->title
, MB_YESNO
|
418 MB_ICONQUESTION
) == IDYES
)
420 /* delete the application's uninstall entry */
421 RegOpenKeyExW(iter
->regroot
, PathUninstallW
, 0, KEY_READ
, &hkey
);
422 RegDeleteKeyW(hkey
, iter
->regkey
);
432 /**********************************************************************************
433 * Name : SetInfoDialogText
434 * Description: Sets the text of a label in a window, based upon a registry entry
435 * or string passed to the function.
436 * Parameters : hKey - registry entry to read from, NULL if not reading
438 * lpKeyName - key to read from, or string to check if hKey is NULL
439 * lpAltMessage - alternative message if entry not found
440 * hWnd - handle of dialog box
441 * iDlgItem - ID of label in dialog box
443 static void SetInfoDialogText(HKEY hKey
, LPWSTR lpKeyName
, LPWSTR lpAltMessage
,
444 HWND hWnd
, int iDlgItem
)
446 WCHAR buf
[MAX_STRING_LEN
];
450 hWndDlgItem
= GetDlgItem(hWnd
, iDlgItem
);
452 /* if hKey is null, lpKeyName contains the string we want to check */
455 if ((lpKeyName
) && (lstrlenW(lpKeyName
) > 0))
456 SetWindowTextW(hWndDlgItem
, lpKeyName
);
458 SetWindowTextW(hWndDlgItem
, lpAltMessage
);
462 buflen
= MAX_STRING_LEN
;
464 if ((RegQueryValueExW(hKey
, lpKeyName
, 0, 0, (LPBYTE
) buf
, &buflen
) ==
465 ERROR_SUCCESS
) && (lstrlenW(buf
) > 0))
466 SetWindowTextW(hWndDlgItem
, buf
);
468 SetWindowTextW(hWndDlgItem
, lpAltMessage
);
472 /******************************************************************************
473 * Name : SupportInfoDlgProc
474 * Description: Callback procedure for support info dialog
475 * Parameters : hWnd - hWnd of the window
476 * msg - reason for calling function
477 * wParam - additional parameter
478 * lParam - additional parameter
479 * Returns : Dependant on message
481 static BOOL CALLBACK
SupportInfoDlgProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
485 WCHAR oldtitle
[MAX_STRING_LEN
];
486 WCHAR buf
[MAX_STRING_LEN
];
487 WCHAR key
[MAX_STRING_LEN
];
488 WCHAR notfound
[MAX_STRING_LEN
];
493 for (iter
= AppInfo
; iter
; iter
= iter
->next
)
495 if (iter
->id
== (int) lParam
)
497 lstrcpyW(key
, PathUninstallW
);
498 lstrcatW(key
, BackSlashW
);
499 lstrcatW(key
, iter
->regkey
);
501 /* check the application's registry entries */
502 RegOpenKeyExW(iter
->regroot
, key
, 0, KEY_READ
, &hkey
);
504 /* Load our "not specified" string */
505 LoadStringW(hInst
, IDS_NOT_SPECIFIED
, notfound
,
506 sizeof(notfound
) / sizeof(notfound
[0]));
508 /* Update the data for items already read into the structure */
509 SetInfoDialogText(NULL
, iter
->publisher
, notfound
, hWnd
,
511 SetInfoDialogText(NULL
, iter
->version
, notfound
, hWnd
,
514 /* And now update the data for those items in the registry */
515 SetInfoDialogText(hkey
, (LPWSTR
) ContactW
, notfound
, hWnd
,
517 SetInfoDialogText(hkey
, (LPWSTR
) HelpLinkW
, notfound
, hWnd
,
519 SetInfoDialogText(hkey
, (LPWSTR
) HelpTelephoneW
, notfound
, hWnd
,
521 SetInfoDialogText(hkey
, (LPWSTR
) ReadmeW
, notfound
, hWnd
,
523 SetInfoDialogText(hkey
, (LPWSTR
) URLUpdateInfoW
, notfound
, hWnd
,
525 SetInfoDialogText(hkey
, (LPWSTR
) CommentsW
, notfound
, hWnd
,
528 /* Update the main label with the app name */
529 if (GetWindowTextW(GetDlgItem(hWnd
, IDC_INFO_LABEL
), oldtitle
,
530 MAX_STRING_LEN
) != 0)
532 wsprintfW(buf
, oldtitle
, iter
->title
);
533 SetWindowTextW(GetDlgItem(hWnd
, IDC_INFO_LABEL
), buf
);
548 switch (LOWORD(wParam
))
551 EndDialog(hWnd
, TRUE
);
562 /******************************************************************************
564 * Description: Displays the Support Information dialog
565 * Parameters : hWnd - Handle of the main dialog
566 * id - ID of the application to display information for
568 static void SupportInfo(HWND hWnd
, int id
)
570 DialogBoxParamW(hInst
, MAKEINTRESOURCEW(IDD_INFO
), hWnd
, (DLGPROC
)
571 SupportInfoDlgProc
, (LPARAM
) id
);
574 /* Definition of column headers for AddListViewColumns function */
575 typedef struct AppWizColumn
{
581 AppWizColumn columns
[] = {
582 {200, LVCFMT_LEFT
, IDS_COLUMN_NAME
},
583 {150, LVCFMT_LEFT
, IDS_COLUMN_PUBLISHER
},
584 {100, LVCFMT_LEFT
, IDS_COLUMN_VERSION
},
587 /******************************************************************************
588 * Name : AddListViewColumns
589 * Description: Adds column headers to the list view control.
590 * Parameters : hWnd - Handle of the list view control.
591 * Returns : TRUE if completed successfully, FALSE otherwise.
593 static BOOL
AddListViewColumns(HWND hWnd
)
595 WCHAR buf
[MAX_STRING_LEN
];
599 lvc
.mask
= LVCF_FMT
| LVCF_TEXT
| LVCF_SUBITEM
| LVCF_WIDTH
;
601 /* Add the columns */
602 for (i
= 0; i
< sizeof(columns
) / sizeof(columns
[0]); i
++)
607 /* set width and format */
608 lvc
.cx
= columns
[i
].width
;
609 lvc
.fmt
= columns
[i
].fmt
;
611 LoadStringW(hInst
, columns
[i
].title
, buf
, sizeof(buf
) / sizeof(buf
[0]));
613 if (ListView_InsertColumnW(hWnd
, i
, &lvc
) == -1)
620 /******************************************************************************
621 * Name : AddListViewImageList
622 * Description: Creates an ImageList for the list view control.
623 * Parameters : hWnd - Handle of the list view control.
624 * Returns : Handle of the image list.
626 static HIMAGELIST
AddListViewImageList(HWND hWnd
)
631 hSmall
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
),
634 /* Add default icon to image list */
635 hDefaultIcon
= LoadIconW(hInst
, MAKEINTRESOURCEW(ICO_MAIN
));
636 ImageList_AddIcon(hSmall
, hDefaultIcon
);
637 DestroyIcon(hDefaultIcon
);
639 (void) ListView_SetImageList(hWnd
, hSmall
, LVSIL_SMALL
);
644 /******************************************************************************
645 * Name : ResetApplicationList
646 * Description: Empties the app list, if need be, and recreates it.
647 * Parameters : bFirstRun - TRUE if this is the first time this is run, FALSE otherwise
648 * hWnd - handle of the dialog box
649 * hImageList - handle of the image list
650 * Returns : New handle of the image list.
652 static HIMAGELIST
ResetApplicationList(BOOL bFirstRun
, HWND hWnd
, HIMAGELIST hImageList
)
656 hWndListView
= GetDlgItem(hWnd
, IDL_PROGRAMS
);
658 /* if first run, create the image list and add the listview columns */
661 if (!AddListViewColumns(hWndListView
))
664 else /* we need to remove the existing things first */
666 RemoveItemsFromList(hWnd
);
667 ImageList_Destroy(hImageList
);
669 /* reset the list, since it's probably changed if the uninstallation was
674 /* now create the image list and add the applications to the listview */
675 hImageList
= AddListViewImageList(hWndListView
);
677 ReadApplicationsFromRegistry(HKEY_LOCAL_MACHINE
);
678 ReadApplicationsFromRegistry(HKEY_CURRENT_USER
);
680 AddApplicationsToList(hWndListView
, hImageList
);
686 /******************************************************************************
688 * Description: Callback procedure for main tab
689 * Parameters : hWnd - hWnd of the window
690 * msg - reason for calling function
691 * wParam - additional parameter
692 * lParam - additional parameter
693 * Returns : Dependant on message
695 static BOOL CALLBACK
MainDlgProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
698 static HIMAGELIST hImageList
;
705 hImageList
= ResetApplicationList(TRUE
, hWnd
, hImageList
);
713 RemoveItemsFromList(hWnd
);
714 ImageList_Destroy(hImageList
);
721 nmh
= (LPNMHDR
) lParam
;
728 case LVN_ITEMCHANGED
:
738 switch (LOWORD(wParam
))
741 selitem
= SendDlgItemMessageW(hWnd
, IDL_PROGRAMS
,
742 LVM_GETNEXTITEM
, -1, LVNI_FOCUSED
|LVNI_SELECTED
);
746 lvItem
.iItem
= selitem
;
747 lvItem
.mask
= LVIF_PARAM
;
749 if (SendDlgItemMessageW(hWnd
, IDL_PROGRAMS
, LVM_GETITEMW
,
750 0, (LPARAM
) &lvItem
))
751 UninstallProgram(lvItem
.lParam
);
754 hImageList
= ResetApplicationList(FALSE
, hWnd
, hImageList
);
758 case IDC_SUPPORT_INFO
:
759 selitem
= SendDlgItemMessageW(hWnd
, IDL_PROGRAMS
,
760 LVM_GETNEXTITEM
, -1, LVNI_FOCUSED
| LVNI_SELECTED
);
764 lvItem
.iItem
= selitem
;
765 lvItem
.mask
= LVIF_PARAM
;
767 if (SendDlgItemMessageW(hWnd
, IDL_PROGRAMS
, LVM_GETITEMW
,
768 0, (LPARAM
) &lvItem
))
769 SupportInfo(hWnd
, lvItem
.lParam
);
781 /******************************************************************************
783 * Description: Main routine for applet
784 * Parameters : hWnd - hWnd of the Control Panel
786 static void StartApplet(HWND hWnd
)
789 PROPSHEETHEADERW psh
;
790 WCHAR tab_title
[MAX_STRING_LEN
], app_title
[MAX_STRING_LEN
];
792 /* Load the strings we will use */
793 LoadStringW(hInst
, IDS_TAB1_TITLE
, tab_title
, sizeof(tab_title
) / sizeof(tab_title
[0]));
794 LoadStringW(hInst
, IDS_CPL_TITLE
, app_title
, sizeof(app_title
) / sizeof(app_title
[0]));
796 /* Fill out the PROPSHEETPAGE */
797 psp
.dwSize
= sizeof (PROPSHEETPAGEW
);
798 psp
.dwFlags
= PSP_USETITLE
;
799 psp
.hInstance
= hInst
;
800 psp
.u
.pszTemplate
= MAKEINTRESOURCEW (IDD_MAIN
);
801 psp
.u2
.pszIcon
= NULL
;
802 psp
.pfnDlgProc
= (DLGPROC
) MainDlgProc
;
803 psp
.pszTitle
= tab_title
;
806 /* Fill out the PROPSHEETHEADER */
807 psh
.dwSize
= sizeof (PROPSHEETHEADERW
);
808 psh
.dwFlags
= PSH_PROPSHEETPAGE
| PSH_USEICONID
;
809 psh
.hwndParent
= hWnd
;
810 psh
.hInstance
= hInst
;
811 psh
.u
.pszIcon
= NULL
;
812 psh
.pszCaption
= app_title
;
815 psh
.pfnCallback
= NULL
;
816 psh
.u2
.nStartPage
= 0;
818 /* Display the property sheet */
819 PropertySheetW (&psh
);
822 /******************************************************************************
824 * Description: Entry point for Control Panel applets
825 * Parameters : hwndCPL - hWnd of the Control Panel
826 * message - reason for calling function
827 * lParam1 - additional parameter
828 * lParam2 - additional parameter
829 * Returns : Dependant on message
831 LONG CALLBACK
CPlApplet(HWND hwndCPL
, UINT message
, LPARAM lParam1
, LPARAM lParam2
)
833 INITCOMMONCONTROLSEX iccEx
;
838 iccEx
.dwSize
= sizeof(iccEx
);
839 iccEx
.dwICC
= ICC_LISTVIEW_CLASSES
| ICC_TAB_CLASSES
;
841 InitCommonControlsEx(&iccEx
);
850 CPLINFO
*appletInfo
= (CPLINFO
*) lParam2
;
852 appletInfo
->idIcon
= ICO_MAIN
;
853 appletInfo
->idName
= IDS_CPL_TITLE
;
854 appletInfo
->idInfo
= IDS_CPL_DESC
;
855 appletInfo
->lData
= 0;
861 StartApplet(hwndCPL
);