2 * winjump.c: support for Windows 7 jump lists.
4 * The Windows 7 jumplist is a customizable list defined by the
5 * application. It is persistent across application restarts: the OS
6 * maintains the list when the app is not running. The list is shown
7 * when the user right-clicks on the taskbar button of a running app
8 * or a pinned non-running application. We use the jumplist to
9 * maintain a list of recently started saved sessions, started either
10 * by doubleclicking on a saved session, or with the command line
13 * Since the jumplist is write-only: it can only be replaced and the
14 * current list cannot be read, we must maintain the contents of the
15 * list persistantly in the registry. The file winstore.h contains
16 * functions to directly manipulate these registry entries. This file
17 * contains higher level functions to manipulate the jumplist.
25 #define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in
26 * the jumplist than this, regardless of
27 * user preferences. */
30 * COM structures and functions.
32 #ifndef PROPERTYKEY_DEFINED
33 #define PROPERTYKEY_DEFINED
34 typedef struct _tagpropertykey
{
39 #ifndef _REFPROPVARIANT_DEFINED
40 #define _REFPROPVARIANT_DEFINED
41 typedef PROPVARIANT
*REFPROPVARIANT
;
44 #define IID_IShellLink IID_IShellLinkA
46 typedef struct ICustomDestinationListVtbl
{
47 HRESULT ( __stdcall
*QueryInterface
) (
48 /* [in] ICustomDestinationList*/ void *This
,
49 /* [in] */ const GUID
* const riid
,
50 /* [out] */ void **ppvObject
);
52 ULONG ( __stdcall
*AddRef
)(
53 /* [in] ICustomDestinationList*/ void *This
);
55 ULONG ( __stdcall
*Release
)(
56 /* [in] ICustomDestinationList*/ void *This
);
58 HRESULT ( __stdcall
*SetAppID
)(
59 /* [in] ICustomDestinationList*/ void *This
,
60 /* [string][in] */ LPCWSTR pszAppID
);
62 HRESULT ( __stdcall
*BeginList
)(
63 /* [in] ICustomDestinationList*/ void *This
,
64 /* [out] */ UINT
*pcMinSlots
,
65 /* [in] */ const GUID
* const riid
,
66 /* [out] */ void **ppv
);
68 HRESULT ( __stdcall
*AppendCategory
)(
69 /* [in] ICustomDestinationList*/ void *This
,
70 /* [string][in] */ LPCWSTR pszCategory
,
71 /* [in] IObjectArray*/ void *poa
);
73 HRESULT ( __stdcall
*AppendKnownCategory
)(
74 /* [in] ICustomDestinationList*/ void *This
,
75 /* [in] KNOWNDESTCATEGORY*/ int category
);
77 HRESULT ( __stdcall
*AddUserTasks
)(
78 /* [in] ICustomDestinationList*/ void *This
,
79 /* [in] IObjectArray*/ void *poa
);
81 HRESULT ( __stdcall
*CommitList
)(
82 /* [in] ICustomDestinationList*/ void *This
);
84 HRESULT ( __stdcall
*GetRemovedDestinations
)(
85 /* [in] ICustomDestinationList*/ void *This
,
86 /* [in] */ const IID
* const riid
,
87 /* [out] */ void **ppv
);
89 HRESULT ( __stdcall
*DeleteList
)(
90 /* [in] ICustomDestinationList*/ void *This
,
91 /* [string][unique][in] */ LPCWSTR pszAppID
);
93 HRESULT ( __stdcall
*AbortList
)(
94 /* [in] ICustomDestinationList*/ void *This
);
96 } ICustomDestinationListVtbl
;
98 typedef struct ICustomDestinationList
100 ICustomDestinationListVtbl
*lpVtbl
;
101 } ICustomDestinationList
;
103 typedef struct IObjectArrayVtbl
105 HRESULT ( __stdcall
*QueryInterface
)(
106 /* [in] IObjectArray*/ void *This
,
107 /* [in] */ const GUID
* const riid
,
108 /* [out] */ void **ppvObject
);
110 ULONG ( __stdcall
*AddRef
)(
111 /* [in] IObjectArray*/ void *This
);
113 ULONG ( __stdcall
*Release
)(
114 /* [in] IObjectArray*/ void *This
);
116 HRESULT ( __stdcall
*GetCount
)(
117 /* [in] IObjectArray*/ void *This
,
118 /* [out] */ UINT
*pcObjects
);
120 HRESULT ( __stdcall
*GetAt
)(
121 /* [in] IObjectArray*/ void *This
,
122 /* [in] */ UINT uiIndex
,
123 /* [in] */ const GUID
* const riid
,
124 /* [out] */ void **ppv
);
128 typedef struct IObjectArray
130 IObjectArrayVtbl
*lpVtbl
;
133 typedef struct IShellLinkVtbl
135 HRESULT ( __stdcall
*QueryInterface
)(
136 /* [in] IShellLink*/ void *This
,
137 /* [in] */ const GUID
* const riid
,
138 /* [out] */ void **ppvObject
);
140 ULONG ( __stdcall
*AddRef
)(
141 /* [in] IShellLink*/ void *This
);
143 ULONG ( __stdcall
*Release
)(
144 /* [in] IShellLink*/ void *This
);
146 HRESULT ( __stdcall
*GetPath
)(
147 /* [in] IShellLink*/ void *This
,
148 /* [string][out] */ LPSTR pszFile
,
150 /* [unique][out][in] */ WIN32_FIND_DATAA
*pfd
,
151 /* [in] */ DWORD fFlags
);
153 HRESULT ( __stdcall
*GetIDList
)(
154 /* [in] IShellLink*/ void *This
,
155 /* [out] LPITEMIDLIST*/ void **ppidl
);
157 HRESULT ( __stdcall
*SetIDList
)(
158 /* [in] IShellLink*/ void *This
,
159 /* [in] LPITEMIDLIST*/ void *pidl
);
161 HRESULT ( __stdcall
*GetDescription
)(
162 /* [in] IShellLink*/ void *This
,
163 /* [string][out] */ LPSTR pszName
,
166 HRESULT ( __stdcall
*SetDescription
)(
167 /* [in] IShellLink*/ void *This
,
168 /* [string][in] */ LPCSTR pszName
);
170 HRESULT ( __stdcall
*GetWorkingDirectory
)(
171 /* [in] IShellLink*/ void *This
,
172 /* [string][out] */ LPSTR pszDir
,
175 HRESULT ( __stdcall
*SetWorkingDirectory
)(
176 /* [in] IShellLink*/ void *This
,
177 /* [string][in] */ LPCSTR pszDir
);
179 HRESULT ( __stdcall
*GetArguments
)(
180 /* [in] IShellLink*/ void *This
,
181 /* [string][out] */ LPSTR pszArgs
,
184 HRESULT ( __stdcall
*SetArguments
)(
185 /* [in] IShellLink*/ void *This
,
186 /* [string][in] */ LPCSTR pszArgs
);
188 HRESULT ( __stdcall
*GetHotkey
)(
189 /* [in] IShellLink*/ void *This
,
190 /* [out] */ WORD
*pwHotkey
);
192 HRESULT ( __stdcall
*SetHotkey
)(
193 /* [in] IShellLink*/ void *This
,
194 /* [in] */ WORD wHotkey
);
196 HRESULT ( __stdcall
*GetShowCmd
)(
197 /* [in] IShellLink*/ void *This
,
198 /* [out] */ int *piShowCmd
);
200 HRESULT ( __stdcall
*SetShowCmd
)(
201 /* [in] IShellLink*/ void *This
,
202 /* [in] */ int iShowCmd
);
204 HRESULT ( __stdcall
*GetIconLocation
)(
205 /* [in] IShellLink*/ void *This
,
206 /* [string][out] */ LPSTR pszIconPath
,
208 /* [out] */ int *piIcon
);
210 HRESULT ( __stdcall
*SetIconLocation
)(
211 /* [in] IShellLink*/ void *This
,
212 /* [string][in] */ LPCSTR pszIconPath
,
213 /* [in] */ int iIcon
);
215 HRESULT ( __stdcall
*SetRelativePath
)(
216 /* [in] IShellLink*/ void *This
,
217 /* [string][in] */ LPCSTR pszPathRel
,
218 /* [in] */ DWORD dwReserved
);
220 HRESULT ( __stdcall
*Resolve
)(
221 /* [in] IShellLink*/ void *This
,
222 /* [unique][in] */ HWND hwnd
,
223 /* [in] */ DWORD fFlags
);
225 HRESULT ( __stdcall
*SetPath
)(
226 /* [in] IShellLink*/ void *This
,
227 /* [string][in] */ LPCSTR pszFile
);
231 typedef struct IShellLink
233 IShellLinkVtbl
*lpVtbl
;
236 typedef struct IObjectCollectionVtbl
238 HRESULT ( __stdcall
*QueryInterface
)(
239 /* [in] IShellLink*/ void *This
,
240 /* [in] */ const GUID
* const riid
,
241 /* [out] */ void **ppvObject
);
243 ULONG ( __stdcall
*AddRef
)(
244 /* [in] IShellLink*/ void *This
);
246 ULONG ( __stdcall
*Release
)(
247 /* [in] IShellLink*/ void *This
);
249 HRESULT ( __stdcall
*GetCount
)(
250 /* [in] IShellLink*/ void *This
,
251 /* [out] */ UINT
*pcObjects
);
253 HRESULT ( __stdcall
*GetAt
)(
254 /* [in] IShellLink*/ void *This
,
255 /* [in] */ UINT uiIndex
,
256 /* [in] */ const GUID
* const riid
,
257 /* [iid_is][out] */ void **ppv
);
259 HRESULT ( __stdcall
*AddObject
)(
260 /* [in] IShellLink*/ void *This
,
261 /* [in] */ void *punk
);
263 HRESULT ( __stdcall
*AddFromArray
)(
264 /* [in] IShellLink*/ void *This
,
265 /* [in] */ IObjectArray
*poaSource
);
267 HRESULT ( __stdcall
*RemoveObjectAt
)(
268 /* [in] IShellLink*/ void *This
,
269 /* [in] */ UINT uiIndex
);
271 HRESULT ( __stdcall
*Clear
)(
272 /* [in] IShellLink*/ void *This
);
274 } IObjectCollectionVtbl
;
276 typedef struct IObjectCollection
278 IObjectCollectionVtbl
*lpVtbl
;
281 typedef struct IPropertyStoreVtbl
283 HRESULT ( __stdcall
*QueryInterface
)(
284 /* [in] IPropertyStore*/ void *This
,
285 /* [in] */ const GUID
* const riid
,
286 /* [iid_is][out] */ void **ppvObject
);
288 ULONG ( __stdcall
*AddRef
)(
289 /* [in] IPropertyStore*/ void *This
);
291 ULONG ( __stdcall
*Release
)(
292 /* [in] IPropertyStore*/ void *This
);
294 HRESULT ( __stdcall
*GetCount
)(
295 /* [in] IPropertyStore*/ void *This
,
296 /* [out] */ DWORD
*cProps
);
298 HRESULT ( __stdcall
*GetAt
)(
299 /* [in] IPropertyStore*/ void *This
,
300 /* [in] */ DWORD iProp
,
301 /* [out] */ PROPERTYKEY
*pkey
);
303 HRESULT ( __stdcall
*GetValue
)(
304 /* [in] IPropertyStore*/ void *This
,
305 /* [in] */ const PROPERTYKEY
* const key
,
306 /* [out] */ PROPVARIANT
*pv
);
308 HRESULT ( __stdcall
*SetValue
)(
309 /* [in] IPropertyStore*/ void *This
,
310 /* [in] */ const PROPERTYKEY
* const key
,
311 /* [in] */ REFPROPVARIANT propvar
);
313 HRESULT ( __stdcall
*Commit
)(
314 /* [in] IPropertyStore*/ void *This
);
315 } IPropertyStoreVtbl
;
317 typedef struct IPropertyStore
319 IPropertyStoreVtbl
*lpVtbl
;
322 static const CLSID CLSID_DestinationList
= {
323 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6}
325 static const CLSID CLSID_ShellLink
= {
326 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
328 static const CLSID CLSID_EnumerableObjectCollection
= {
329 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a}
331 static const IID IID_IObjectCollection
= {
332 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95}
334 static const IID IID_IShellLink
= {
335 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
337 static const IID IID_ICustomDestinationList
= {
338 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e}
340 static const IID IID_IObjectArray
= {
341 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9}
343 static const IID IID_IPropertyStore
= {
344 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99}
346 static const PROPERTYKEY PKEY_Title
= {
347 {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}},
351 #define COMPTR(type, obj) &IID_##type, ((sizeof((obj)-(type **)(obj))), (obj))
353 static char putty_path
[2048];
356 * Function to make an IShellLink describing a particular PuTTY
357 * command. If 'appname' is null, the command run will be the one
358 * returned by GetModuleFileName, i.e. our own executable; if it's
359 * non-null then it will be assumed to be a filename in the same
360 * directory as our own executable, and the return value will be NULL
361 * if that file doesn't exist.
363 * If 'sessionname' is null then no command line will be passed to the
364 * program. If it's non-null, the command line will be that text
365 * prefixed with an @ (to load a PuTTY saved session).
367 * Hence, you can launch a saved session using make_shell_link(NULL,
368 * sessionname), and launch another app using e.g.
369 * make_shell_link("puttygen.exe", NULL).
371 static IShellLink
*make_shell_link(const char *appname
,
372 const char *sessionname
)
375 char *app_path
, *param_string
, *desc_string
;
380 /* Retrieve path to executable. */
382 GetModuleFileName(NULL
, putty_path
, sizeof(putty_path
) - 1);
384 char *p
, *q
= putty_path
;
387 if ((p
= strrchr(q
, '\\')) != NULL
) q
= p
+1;
388 if ((p
= strrchr(q
, ':')) != NULL
) q
= p
+1;
389 app_path
= dupprintf("%.*s%s", (int)(q
- putty_path
), putty_path
,
391 if ((fp
= fopen(app_path
, "r")) == NULL
) {
397 app_path
= dupstr(putty_path
);
400 /* Check if this is a valid session, otherwise don't add. */
402 psettings_tmp
= open_settings_r(sessionname
);
405 close_settings_r(psettings_tmp
);
408 /* Create the new item. */
409 if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink
, NULL
,
410 CLSCTX_INPROC_SERVER
,
411 COMPTR(IShellLink
, &ret
))))
414 /* Set path, parameters, icon and description. */
415 ret
->lpVtbl
->SetPath(ret
, app_path
);
418 param_string
= dupcat("@", sessionname
, NULL
);
420 param_string
= dupstr("");
422 ret
->lpVtbl
->SetArguments(ret
, param_string
);
426 desc_string
= dupcat("Connect to PuTTY session '",
427 sessionname
, "'", NULL
);
430 desc_string
= dupprintf("Run %.*s", strcspn(appname
, "."), appname
);
432 ret
->lpVtbl
->SetDescription(ret
, desc_string
);
435 ret
->lpVtbl
->SetIconLocation(ret
, app_path
, 0);
437 /* To set the link title, we require the property store of the link. */
438 if (SUCCEEDED(ret
->lpVtbl
->QueryInterface(ret
,
439 COMPTR(IPropertyStore
, &pPS
)))) {
440 PropVariantInit(&pv
);
443 pv
.pszVal
= dupstr(sessionname
);
446 pv
.pszVal
= dupprintf("Run %.*s", strcspn(appname
, "."), appname
);
448 pPS
->lpVtbl
->SetValue(pPS
, &PKEY_Title
, &pv
);
450 pPS
->lpVtbl
->Commit(pPS
);
451 pPS
->lpVtbl
->Release(pPS
);
459 /* Updates jumplist from registry. */
460 static void update_jumplist_from_registry(void)
462 const char *piterator
;
464 int jumplist_counter
;
467 /* Variables used by the cleanup code must be initialised to NULL,
468 * so that we don't try to free or release them if they were never
470 ICustomDestinationList
*pCDL
= NULL
;
471 char *pjumplist_reg_entries
= NULL
;
472 IObjectCollection
*collection
= NULL
;
473 IObjectArray
*array
= NULL
;
474 IShellLink
*link
= NULL
;
475 IObjectArray
*pRemoved
= NULL
;
476 int need_abort
= FALSE
;
479 * Create an ICustomDestinationList: the top-level object which
480 * deals with jump list management.
482 if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList
, NULL
,
483 CLSCTX_INPROC_SERVER
,
484 COMPTR(ICustomDestinationList
, &pCDL
))))
488 * Call its BeginList method to start compiling a list. This gives
489 * us back 'num_items' (a hint derived from systemwide
490 * configuration about how many things to put on the list) and
491 * 'pRemoved' (user configuration about things to leave off the
494 if (!SUCCEEDED(pCDL
->lpVtbl
->BeginList(pCDL
, &num_items
,
495 COMPTR(IObjectArray
, &pRemoved
))))
498 if (!SUCCEEDED(pRemoved
->lpVtbl
->GetCount(pRemoved
, &nremoved
)))
502 * Create an object collection to form the 'Recent Sessions'
503 * category on the jump list.
505 if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection
,
506 NULL
, CLSCTX_INPROC_SERVER
,
507 COMPTR(IObjectCollection
, &collection
))))
511 * Go through the jump list entries from the registry and add each
512 * one to the collection.
514 pjumplist_reg_entries
= get_jumplist_registry_entries();
515 piterator
= pjumplist_reg_entries
;
516 jumplist_counter
= 0;
517 while (*piterator
!= '\0' &&
518 (jumplist_counter
< min(MAX_JUMPLIST_ITEMS
, (int) num_items
))) {
519 link
= make_shell_link(NULL
, piterator
);
525 * Check that the link isn't in the user-removed list.
527 for (i
= 0, found
= FALSE
; i
< nremoved
&& !found
; i
++) {
529 if (SUCCEEDED(pRemoved
->lpVtbl
->GetAt
530 (pRemoved
, i
, COMPTR(IShellLink
, &rlink
)))) {
531 char desc1
[2048], desc2
[2048];
532 if (SUCCEEDED(link
->lpVtbl
->GetDescription
533 (link
, desc1
, sizeof(desc1
)-1)) &&
534 SUCCEEDED(rlink
->lpVtbl
->GetDescription
535 (rlink
, desc2
, sizeof(desc2
)-1)) &&
536 !strcmp(desc1
, desc2
)) {
539 rlink
->lpVtbl
->Release(rlink
);
544 collection
->lpVtbl
->AddObject(collection
, link
);
548 link
->lpVtbl
->Release(link
);
551 piterator
+= strlen(piterator
) + 1;
553 sfree(pjumplist_reg_entries
);
554 pjumplist_reg_entries
= NULL
;
557 * Get the array form of the collection we've just constructed,
558 * and put it in the jump list.
560 if (!SUCCEEDED(collection
->lpVtbl
->QueryInterface
561 (collection
, COMPTR(IObjectArray
, &array
))))
564 pCDL
->lpVtbl
->AppendCategory(pCDL
, L
"Recent Sessions", array
);
567 * Create an object collection to form the 'Tasks' category on the
570 if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection
,
571 NULL
, CLSCTX_INPROC_SERVER
,
572 COMPTR(IObjectCollection
, &collection
))))
576 * Add task entries for PuTTYgen and Pageant.
578 piterator
= "Pageant.exe\0PuTTYgen.exe\0\0";
579 while (*piterator
!= '\0') {
580 link
= make_shell_link(piterator
, NULL
);
582 collection
->lpVtbl
->AddObject(collection
, link
);
583 link
->lpVtbl
->Release(link
);
586 piterator
+= strlen(piterator
) + 1;
590 * Get the array form of the collection we've just constructed,
591 * and put it in the jump list.
593 if (!SUCCEEDED(collection
->lpVtbl
->QueryInterface
594 (collection
, COMPTR(IObjectArray
, &array
))))
597 pCDL
->lpVtbl
->AddUserTasks(pCDL
, array
);
600 * Now we can clean up the array and collection variables, so as
601 * to be able to reuse them.
603 array
->lpVtbl
->Release(array
);
605 collection
->lpVtbl
->Release(collection
);
609 * Create another object collection to form the user tasks
612 if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection
,
613 NULL
, CLSCTX_INPROC_SERVER
,
614 COMPTR(IObjectCollection
, &collection
))))
618 * Get the array form of the collection we've just constructed,
619 * and put it in the jump list.
621 if (!SUCCEEDED(collection
->lpVtbl
->QueryInterface
622 (collection
, COMPTR(IObjectArray
, &array
))))
625 pCDL
->lpVtbl
->AddUserTasks(pCDL
, array
);
628 * Now we can clean up the array and collection variables, so as
629 * to be able to reuse them.
631 array
->lpVtbl
->Release(array
);
633 collection
->lpVtbl
->Release(collection
);
637 * Commit the jump list.
639 pCDL
->lpVtbl
->CommitList(pCDL
);
646 if (pRemoved
) pRemoved
->lpVtbl
->Release(pRemoved
);
647 if (pCDL
&& need_abort
) pCDL
->lpVtbl
->AbortList(pCDL
);
648 if (pCDL
) pCDL
->lpVtbl
->Release(pCDL
);
649 if (collection
) collection
->lpVtbl
->Release(collection
);
650 if (array
) array
->lpVtbl
->Release(array
);
651 if (link
) link
->lpVtbl
->Release(link
);
652 sfree(pjumplist_reg_entries
);
655 /* Clears the entire jumplist. */
656 static void clear_jumplist(void)
658 ICustomDestinationList
*pCDL
;
660 IObjectArray
*pRemoved
;
662 if (CoCreateInstance(&CLSID_DestinationList
, NULL
, CLSCTX_INPROC_SERVER
,
663 COMPTR(ICustomDestinationList
, &pCDL
)) == S_OK
) {
664 pCDL
->lpVtbl
->DeleteList(pCDL
, NULL
);
665 pCDL
->lpVtbl
->Release(pCDL
);
670 /* Adds a saved session to the Windows 7 jumplist. */
671 void add_session_to_jumplist(const char * const sessionname
)
673 if ((osVersion
.dwMajorVersion
< 6) ||
674 (osVersion
.dwMajorVersion
== 6 && osVersion
.dwMinorVersion
< 1))
675 return; /* do nothing on pre-Win7 systems */
677 if (add_to_jumplist_registry(sessionname
) == JUMPLISTREG_OK
) {
678 update_jumplist_from_registry();
680 /* Make sure we don't leave the jumplist dangling. */
685 /* Removes a saved session from the Windows jumplist. */
686 void remove_session_from_jumplist(const char * const sessionname
)
688 if ((osVersion
.dwMajorVersion
< 6) ||
689 (osVersion
.dwMajorVersion
== 6 && osVersion
.dwMinorVersion
< 1))
690 return; /* do nothing on pre-Win7 systems */
692 if (remove_from_jumplist_registry(sessionname
) == JUMPLISTREG_OK
) {
693 update_jumplist_from_registry();
695 /* Make sure we don't leave the jumplist dangling. */