--- /dev/null
+/*
+ * winjump.c: support for Windows 7 jump lists.
+ *
+ * The Windows 7 jumplist is a customizable list defined by the
+ * application. It is persistent across application restarts: the OS
+ * maintains the list when the app is not running. The list is shown
+ * when the user right-clicks on the taskbar button of a running app
+ * or a pinned non-running application. We use the jumplist to
+ * maintain a list of recently started saved sessions, started either
+ * by doubleclicking on a saved session, or with the command line
+ * "-load" parameter.
+ *
+ * Since the jumplist is write-only: it can only be replaced and the
+ * current list cannot be read, we must maintain the contents of the
+ * list persistantly in the registry. The file winstore.h contains
+ * functions to directly manipulate these registry entries. This file
+ * contains higher level functions to manipulate the jumplist.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "storage.h"
+
+#define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in
+ * the jumplist than this, regardless of
+ * user preferences. */
+
+/*
+ * COM structures and functions.
+ */
+#ifndef PROPERTYKEY_DEFINED
+#define PROPERTYKEY_DEFINED
+typedef struct _tagpropertykey {
+ GUID fmtid;
+ DWORD pid;
+} PROPERTYKEY;
+#endif
+#ifndef _REFPROPVARIANT_DEFINED
+#define _REFPROPVARIANT_DEFINED
+typedef PROPVARIANT *REFPROPVARIANT;
+#endif
+
+#define IID_IShellLink IID_IShellLinkA
+
+typedef struct ICustomDestinationListVtbl {
+ HRESULT ( __stdcall *QueryInterface ) (
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *SetAppID )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *BeginList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [out] */ UINT *pcMinSlots,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *AppendCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszCategory,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *AppendKnownCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] KNOWNDESTCATEGORY*/ int category);
+
+ HRESULT ( __stdcall *AddUserTasks )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *CommitList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *GetRemovedDestinations )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const IID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *DeleteList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][unique][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *AbortList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+} ICustomDestinationListVtbl;
+
+typedef struct ICustomDestinationList
+{
+ ICustomDestinationListVtbl *lpVtbl;
+} ICustomDestinationList;
+
+typedef struct IObjectArrayVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IObjectArray*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IObjectArray*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IObjectArray*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+} IObjectArrayVtbl;
+
+typedef struct IObjectArray
+{
+ IObjectArrayVtbl *lpVtbl;
+} IObjectArray;
+
+typedef struct IShellLinkVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszFile,
+ /* [in] */ int cch,
+ /* [unique][out][in] */ WIN32_FIND_DATAA *pfd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *GetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] LPITEMIDLIST*/ void **ppidl);
+
+ HRESULT ( __stdcall *SetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] LPITEMIDLIST*/ void *pidl);
+
+ HRESULT ( __stdcall *GetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszName,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszName);
+
+ HRESULT ( __stdcall *GetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszDir,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszDir);
+
+ HRESULT ( __stdcall *GetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszArgs,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszArgs);
+
+ HRESULT ( __stdcall *GetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ WORD *pwHotkey);
+
+ HRESULT ( __stdcall *SetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ WORD wHotkey);
+
+ HRESULT ( __stdcall *GetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ int *piShowCmd);
+
+ HRESULT ( __stdcall *SetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ int iShowCmd);
+
+ HRESULT ( __stdcall *GetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszIconPath,
+ /* [in] */ int cch,
+ /* [out] */ int *piIcon);
+
+ HRESULT ( __stdcall *SetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszIconPath,
+ /* [in] */ int iIcon);
+
+ HRESULT ( __stdcall *SetRelativePath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszPathRel,
+ /* [in] */ DWORD dwReserved);
+
+ HRESULT ( __stdcall *Resolve )(
+ /* [in] IShellLink*/ void *This,
+ /* [unique][in] */ HWND hwnd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *SetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszFile);
+
+} IShellLinkVtbl;
+
+typedef struct IShellLink
+{
+ IShellLinkVtbl *lpVtbl;
+} IShellLink;
+
+typedef struct IObjectCollectionVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppv);
+
+ HRESULT ( __stdcall *AddObject )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ void *punk);
+
+ HRESULT ( __stdcall *AddFromArray )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ IObjectArray *poaSource);
+
+ HRESULT ( __stdcall *RemoveObjectAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex);
+
+ HRESULT ( __stdcall *Clear )(
+ /* [in] IShellLink*/ void *This);
+
+} IObjectCollectionVtbl;
+
+typedef struct IObjectCollection
+{
+ IObjectCollectionVtbl *lpVtbl;
+} IObjectCollection;
+
+typedef struct IPropertyStoreVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IPropertyStore*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IPropertyStore*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [out] */ DWORD *cProps);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ DWORD iProp,
+ /* [out] */ PROPERTYKEY *pkey);
+
+ HRESULT ( __stdcall *GetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [out] */ PROPVARIANT *pv);
+
+ HRESULT ( __stdcall *SetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [in] */ REFPROPVARIANT propvar);
+
+ HRESULT ( __stdcall *Commit )(
+ /* [in] IPropertyStore*/ void *This);
+} IPropertyStoreVtbl;
+
+typedef struct IPropertyStore
+{
+ IPropertyStoreVtbl *lpVtbl;
+} IPropertyStore;
+
+static const CLSID CLSID_DestinationList = {
+ 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6}
+};
+static const CLSID CLSID_ShellLink = {
+ 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const CLSID CLSID_EnumerableObjectCollection = {
+ 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a}
+};
+static const IID IID_IObjectCollection = {
+ 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95}
+};
+static const IID IID_IShellLink = {
+ 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const IID IID_ICustomDestinationList = {
+ 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e}
+};
+static const IID IID_IObjectArray = {
+ 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9}
+};
+static const IID IID_IPropertyStore = {
+ 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99}
+};
+static const PROPERTYKEY PKEY_Title = {
+ {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}},
+ 0x00000002
+};
+
+#define COMPTR(type, obj) &IID_##type, ((sizeof((obj)-(type **)(obj))), (obj))
+
+static char putty_path[2048];
+
+/*
+ * Function to make an IShellLink describing a particular PuTTY
+ * command. If 'appname' is null, the command run will be the one
+ * returned by GetModuleFileName, i.e. our own executable; if it's
+ * non-null then it will be assumed to be a filename in the same
+ * directory as our own executable, and the return value will be NULL
+ * if that file doesn't exist.
+ *
+ * If 'sessionname' is null then no command line will be passed to the
+ * program. If it's non-null, the command line will be that text
+ * prefixed with an @ (to load a PuTTY saved session).
+ *
+ * Hence, you can launch a saved session using make_shell_link(NULL,
+ * sessionname), and launch another app using e.g.
+ * make_shell_link("puttygen.exe", NULL).
+ */
+static IShellLink *make_shell_link(const char *appname,
+ const char *sessionname)
+{
+ IShellLink *ret;
+ char *app_path, *param_string, *desc_string;
+ void *psettings_tmp;
+ IPropertyStore *pPS;
+ PROPVARIANT pv;
+
+ /* Retrieve path to executable. */
+ if (!putty_path[0])
+ GetModuleFileName(NULL, putty_path, sizeof(putty_path) - 1);
+ if (appname) {
+ char *p, *q = putty_path;
+ FILE *fp;
+
+ if ((p = strrchr(q, '\\')) != NULL) q = p+1;
+ if ((p = strrchr(q, ':')) != NULL) q = p+1;
+ app_path = dupprintf("%.*s%s", (int)(q - putty_path), putty_path,
+ appname);
+ if ((fp = fopen(app_path, "r")) == NULL) {
+ sfree(app_path);
+ return NULL;
+ }
+ fclose(fp);
+ } else {
+ app_path = dupstr(putty_path);
+ }
+
+ /* Check if this is a valid session, otherwise don't add. */
+ if (sessionname) {
+ psettings_tmp = open_settings_r(sessionname);
+ if (!psettings_tmp)
+ return NULL;
+ close_settings_r(psettings_tmp);
+ }
+
+ /* Create the new item. */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(IShellLink, &ret))))
+ return NULL;
+
+ /* Set path, parameters, icon and description. */
+ ret->lpVtbl->SetPath(ret, app_path);
+
+ if (sessionname) {
+ param_string = dupcat("@", sessionname, NULL);
+ } else {
+ param_string = dupstr("");
+ }
+ ret->lpVtbl->SetArguments(ret, param_string);
+ sfree(param_string);
+
+ if (sessionname) {
+ desc_string = dupcat("Connect to PuTTY session '",
+ sessionname, "'", NULL);
+ } else {
+ assert(appname);
+ desc_string = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+ }
+ ret->lpVtbl->SetDescription(ret, desc_string);
+ sfree(desc_string);
+
+ ret->lpVtbl->SetIconLocation(ret, app_path, 0);
+
+ /* To set the link title, we require the property store of the link. */
+ if (SUCCEEDED(ret->lpVtbl->QueryInterface(ret,
+ COMPTR(IPropertyStore, &pPS)))) {
+ PropVariantInit(&pv);
+ pv.vt = VT_LPSTR;
+ if (sessionname) {
+ pv.pszVal = dupstr(sessionname);
+ } else {
+ assert(appname);
+ pv.pszVal = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+ }
+ pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv);
+ sfree(pv.pszVal);
+ pPS->lpVtbl->Commit(pPS);
+ pPS->lpVtbl->Release(pPS);
+ }
+
+ sfree(app_path);
+
+ return ret;
+}
+
+/* Updates jumplist from registry. */
+static void update_jumplist_from_registry(void)
+{
+ const char *piterator;
+ UINT num_items;
+ int jumplist_counter;
+ UINT nremoved;
+
+ /* Variables used by the cleanup code must be initialised to NULL,
+ * so that we don't try to free or release them if they were never
+ * set up. */
+ ICustomDestinationList *pCDL = NULL;
+ char *pjumplist_reg_entries = NULL;
+ IObjectCollection *collection = NULL;
+ IObjectArray *array = NULL;
+ IShellLink *link = NULL;
+ IObjectArray *pRemoved = NULL;
+ int need_abort = FALSE;
+
+ /*
+ * Create an ICustomDestinationList: the top-level object which
+ * deals with jump list management.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL))))
+ goto cleanup;
+
+ /*
+ * Call its BeginList method to start compiling a list. This gives
+ * us back 'num_items' (a hint derived from systemwide
+ * configuration about how many things to put on the list) and
+ * 'pRemoved' (user configuration about things to leave off the
+ * list).
+ */
+ if (!SUCCEEDED(pCDL->lpVtbl->BeginList(pCDL, &num_items,
+ COMPTR(IObjectArray, &pRemoved))))
+ goto cleanup;
+ need_abort = TRUE;
+ if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved)))
+ nremoved = 0;
+
+ /*
+ * Create an object collection to form the 'Recent Sessions'
+ * category on the jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Go through the jump list entries from the registry and add each
+ * one to the collection.
+ */
+ pjumplist_reg_entries = get_jumplist_registry_entries();
+ piterator = pjumplist_reg_entries;
+ jumplist_counter = 0;
+ while (*piterator != '\0' &&
+ (jumplist_counter < min(MAX_JUMPLIST_ITEMS, (int) num_items))) {
+ link = make_shell_link(NULL, piterator);
+ if (link) {
+ UINT i;
+ int found;
+
+ /*
+ * Check that the link isn't in the user-removed list.
+ */
+ for (i = 0, found = FALSE; i < nremoved && !found; i++) {
+ IShellLink *rlink;
+ if (SUCCEEDED(pRemoved->lpVtbl->GetAt
+ (pRemoved, i, COMPTR(IShellLink, &rlink)))) {
+ char desc1[2048], desc2[2048];
+ if (SUCCEEDED(link->lpVtbl->GetDescription
+ (link, desc1, sizeof(desc1)-1)) &&
+ SUCCEEDED(rlink->lpVtbl->GetDescription
+ (rlink, desc2, sizeof(desc2)-1)) &&
+ !strcmp(desc1, desc2)) {
+ found = TRUE;
+ }
+ rlink->lpVtbl->Release(rlink);
+ }
+ }
+
+ if (!found) {
+ collection->lpVtbl->AddObject(collection, link);
+ jumplist_counter++;
+ }
+
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+ sfree(pjumplist_reg_entries);
+ pjumplist_reg_entries = NULL;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array);
+
+ /*
+ * Create an object collection to form the 'Tasks' category on the
+ * jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Add task entries for PuTTYgen and Pageant.
+ */
+ piterator = "Pageant.exe\0PuTTYgen.exe\0\0";
+ while (*piterator != '\0') {
+ link = make_shell_link(piterator, NULL);
+ if (link) {
+ collection->lpVtbl->AddObject(collection, link);
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Create another object collection to form the user tasks
+ * category.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface
+ (collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Commit the jump list.
+ */
+ pCDL->lpVtbl->CommitList(pCDL);
+ need_abort = FALSE;
+
+ /*
+ * Clean up.
+ */
+ cleanup:
+ if (pRemoved) pRemoved->lpVtbl->Release(pRemoved);
+ if (pCDL && need_abort) pCDL->lpVtbl->AbortList(pCDL);
+ if (pCDL) pCDL->lpVtbl->Release(pCDL);
+ if (collection) collection->lpVtbl->Release(collection);
+ if (array) array->lpVtbl->Release(array);
+ if (link) link->lpVtbl->Release(link);
+ sfree(pjumplist_reg_entries);
+}
+
+/* Clears the entire jumplist. */
+static void clear_jumplist(void)
+{
+ ICustomDestinationList *pCDL;
+ UINT num_items;
+ IObjectArray *pRemoved;
+
+ if (CoCreateInstance(&CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL)) == S_OK) {
+ pCDL->lpVtbl->DeleteList(pCDL, NULL);
+ pCDL->lpVtbl->Release(pCDL);
+ }
+
+}
+
+/* Adds a saved session to the Windows 7 jumplist. */
+void add_session_to_jumplist(const char * const sessionname)
+{
+ if ((osVersion.dwMajorVersion < 6) ||
+ (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
+
+/* Removes a saved session from the Windows jumplist. */
+void remove_session_from_jumplist(const char * const sessionname)
+{
+ if ((osVersion.dwMajorVersion < 6) ||
+ (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
+static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
+static const char *const reg_jumplist_value = "Recent sessions";
static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
static const char hex[16] = "0123456789ABCDEF";
sfree(p);
RegCloseKey(subkey1);
+
+ remove_session_from_jumplist(sessionname);
}
struct enumsettings {
}
}
+/*
+ * Internal function supporting the jump list registry code. All the
+ * functions to add, remove and read the list have substantially
+ * similar content, so this is a generalisation of all of them which
+ * transforms the list in the registry by prepending 'add' (if
+ * non-null), removing 'rem' from what's left (if non-null), and
+ * returning the resulting concatenated list of strings in 'out' (if
+ * non-null).
+ */
+static int transform_jumplist_registry
+ (const char *add, const char *rem, char **out)
+{
+ int ret;
+ HKEY pjumplist_key, psettings_tmp;
+ DWORD type;
+ int value_length;
+ char *old_value, *new_value;
+ char *piterator_old, *piterator_new, *piterator_tmp;
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
+ &pjumplist_key, NULL);
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
+ }
+
+ /* Get current list of saved sessions in the registry. */
+ value_length = 200;
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ /* When the passed buffer is too small, ERROR_MORE_DATA is
+ * returned and the required size is returned in the length
+ * argument. */
+ if (ret == ERROR_MORE_DATA) {
+ sfree(old_value);
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ }
+
+ if (ret == ERROR_FILE_NOT_FOUND) {
+ /* Value doesn't exist yet. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ } else if (ret != ERROR_SUCCESS) {
+ /* Some non-recoverable error occurred. */
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ } else if (type != REG_MULTI_SZ) {
+ /* The value present in the registry has the wrong type: we
+ * try to delete it and start from an empty value. */
+ ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
+ if (ret != ERROR_SUCCESS) {
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ }
+
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /* Check validity of registry data: REG_MULTI_SZ value must end
+ * with \0\0. */
+ piterator_tmp = old_value;
+ while (((piterator_tmp - old_value) < (value_length - 1)) &&
+ !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
+ ++piterator_tmp;
+ }
+
+ if ((piterator_tmp - old_value) >= (value_length-1)) {
+ /* Invalid value. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /*
+ * Modify the list, if we're modifying.
+ */
+ if (add || rem) {
+ /* Walk through the existing list and construct the new list of
+ * saved sessions. */
+ new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
+ piterator_new = new_value;
+ piterator_old = old_value;
+
+ /* First add the new item to the beginning of the list. */
+ if (add) {
+ strcpy(piterator_new, add);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ /* Now add the existing list, taking care to leave out the removed
+ * item, if it was already in the existing list. */
+ while (*piterator_old != '\0') {
+ if (!rem || strcmp(piterator_old, rem) != 0) {
+ /* Check if this is a valid session, otherwise don't add. */
+ psettings_tmp = open_settings_r(piterator_old);
+ if (psettings_tmp != NULL) {
+ close_settings_r(psettings_tmp);
+ strcpy(piterator_new, piterator_old);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ }
+ piterator_old += strlen(piterator_old) + 1;
+ }
+ *piterator_new = '\0';
+ ++piterator_new;
+
+ /* Save the new list to the registry. */
+ ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
+ new_value, piterator_new - new_value);
+
+ old_value = new_value;
+ sfree(new_value);
+ } else
+ ret = ERROR_SUCCESS;
+
+ /*
+ * Either return or free the result.
+ */
+ if (out)
+ *out = old_value;
+ else
+ sfree(old_value);
+
+ /* Clean up and return. */
+ RegCloseKey(pjumplist_key);
+
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
+ } else {
+ return JUMPLISTREG_OK;
+ }
+}
+
+/* Adds a new entry to the jumplist entries in the registry. */
+int add_to_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(item, item, NULL);
+}
+
+/* Removes an item from the jumplist entries in the registry. */
+int remove_from_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(NULL, item, NULL);
+}
+
+/* Returns the jumplist entries from the registry. Caller must free
+ * the returned pointer. */
+char *get_jumplist_registry_entries (void)
+{
+ char *list_value;
+
+ if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) {
+ list_value = snewn(2, char);
+ *list_value = '\0';
+ *(list_value + 1) = '\0';
+ }
+ return list_value;
+}
+
/*
* Recursively delete a registry key and everything under it.
*/