Support for Windows 7 jump lists (right-click on a program's taskbar
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 23 Dec 2010 17:32:28 +0000 (17:32 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 23 Dec 2010 17:32:28 +0000 (17:32 +0000)
icon, even if the program isn't running at the time, to be presented
with an application-defined collection of helpful links). The current
jump list is updated every time a saved session is loaded, and shows
the last few launchable saved sessions (i.e. not those like Default
Settings) that were loaded. Also, if Pageant or PuTTYgen or both is in
the same directory as the PuTTY binary, the jump list will present
links to launch those too.

Based on a patch sent last year by Daniel B. Roy, though it's barely
recognisable any more...

git-svn-id: svn://svn.tartarus.org/sgt/putty@9046 cda61777-01e9-0310-a592-d414129be87e

Recipe
cmdline.c
putty.h
settings.c
windows/window.c
windows/winjump.c [new file with mode: 0644]
windows/winnojmp.c [new file with mode: 0644]
windows/winstore.c
windows/winstuff.h

diff --git a/Recipe b/Recipe
index 48e304c..1a34bb8 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -249,7 +249,7 @@ TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi
 
 # GUI front end and terminal emulator (putty, puttytel).
 GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
-         + winutils wincfg sercfg winhelp
+         + winutils wincfg sercfg winhelp winjump
 
 # Same thing on Unix.
 UXTERM   = TERMINAL uxcfg sercfg uxucs uxprint timing
@@ -283,7 +283,7 @@ CHARSET  = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
 
 # Standard libraries.
 LIBS     = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib
-         + shell32.lib winmm.lib imm32.lib winspool.lib
+         + shell32.lib winmm.lib imm32.lib winspool.lib ole32.lib
 
 # Network backend sets. This also brings in the relevant attachment
 # to proxy.c depending on whether we're crypto-avoidant or not.
@@ -307,11 +307,11 @@ U_BE_NOSSH = be_nos_s uxser nocproxy
 putty    : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS
 puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS
 plink    : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC
-         + winx11 plink.res LIBS
+         + winx11 plink.res winnojmp LIBS
 pscp     : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
-         + pscp.res LIBS
+         + pscp.res winnojmp LIBS
 psftp    : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
-         + psftp.res LIBS
+         + psftp.res winnojmp LIBS
 
 pageant  : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234
          + misc sshaes sshsha winpgntc sshdss sshsh256 sshsh512 winutils
@@ -320,7 +320,7 @@ pageant  : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234
 puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc
          + sshpubk sshaes sshsh256 sshsh512 import winutils puttygen.res
-        + tree234 notiming winhelp LIBS wintime
+        + tree234 notiming winhelp winnojmp LIBS wintime
 
 pterm    : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
          + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
index 73c6f73..bfb2b42 100644 (file)
--- a/cmdline.c
+++ b/cmdline.c
@@ -172,6 +172,7 @@ int cmdline_process_param(char *p, char *value, int need_save, Config *cfg)
         * saved. */
        do_defaults(value, cfg);
        loaded_session = TRUE;
+       cmdline_session_name = dupstr(value);
        return 2;
     }
     if (!strcmp(p, "-ssh")) {
diff --git a/putty.h b/putty.h
index 9a55757..e80afad 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -666,6 +666,10 @@ GLOBAL int default_port;
  * This is set TRUE by cmdline.c iff a session is loaded with "-load".
  */
 GLOBAL int loaded_session;
+/*
+ * This is set to the name of the loaded session.
+ */
+GLOBAL char *cmdline_session_name;
 
 struct RSAKey;                        /* be a little careful of scope */
 
@@ -1246,4 +1250,15 @@ void expire_timer_context(void *ctx);
 int run_timers(long now, long *next);
 void timer_change_notify(long next);
 
+/*
+ * Define no-op macros for the jump list functions, on platforms that
+ * don't support them. (This is a bit of a hack, and it'd be nicer to
+ * localise even the calls to those functions into the Windows front
+ * end, but it'll do for the moment.)
+ */
+#ifndef JUMPLIST_SUPPORTED
+#define add_session_to_jumplist(x) ((void)0)
+#define remove_session_from_jumplist(x) ((void)0)
+#endif
+
 #endif
index 372a954..2afb9f5 100644 (file)
@@ -507,6 +507,9 @@ void load_settings(char *section, Config * cfg)
     sesskey = open_settings_r(section);
     load_open_settings(sesskey, cfg);
     close_settings_r(sesskey);
+
+    if (cfg_launchable(cfg))
+        add_session_to_jumplist(section);
 }
 
 void load_open_settings(void *sesskey, Config *cfg)
index 12b6fe7..7cd306d 100644 (file)
@@ -317,6 +317,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
     WNDCLASS wndclass;
     MSG msg;
+    HRESULT hr;
     int guess_width, guess_height;
 
     hinst = inst;
@@ -355,6 +356,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     init_flashwindow();
 
     /*
+     * Initialize COM.
+     */
+    hr = CoInitialize(NULL);
+    if (hr != S_OK && hr != S_FALSE) {
+        char *str = dupprintf("%s Fatal Error", appname);
+       MessageBox(NULL, "Failed to initialize COM subsystem",
+                  str, MB_OK | MB_ICONEXCLAMATION);
+       sfree(str);
+       return 1;
+    }
+
+    /*
      * Process the command line.
      */
     {
@@ -381,14 +394,21 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
        /*
         * Process a couple of command-line options which are more
-        * easily dealt with before the line is broken up into
-        * words. These are the soon-to-be-defunct @sessionname and
-        * the internal-use-only &sharedmemoryhandle, neither of
-        * which are combined with anything else.
+        * easily dealt with before the line is broken up into words.
+        * These are the old-fashioned but convenient @sessionname and
+        * the internal-use-only &sharedmemoryhandle, neither of which
+        * are combined with anything else.
         */
        while (*p && isspace(*p))
            p++;
        if (*p == '@') {
+            /*
+             * An initial @ means that the whole of the rest of the
+             * command line should be treated as the name of a saved
+             * session, with _no quoting or escaping_. This makes it a
+             * very convenient means of automated saved-session
+             * launching, via IDM_SAVEDSESS or Windows 7 jump lists.
+             */
            int i = strlen(p);
            while (i > 1 && isspace(p[i - 1]))
                i--;
@@ -867,6 +887,9 @@ void cleanup_exit(int code)
     }
     shutdown_help();
 
+    /* Clean up COM. */
+    CoUninitialize();
+
     exit(code);
 }
 
@@ -2044,7 +2067,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                           / MENU_SAVED_STEP) + 1;
                    if (sessno < (unsigned)sesslist.nsessions) {
                        char *session = sesslist.sessions[sessno];
-                       /* XXX spaces? quotes? "-load"? */
                        cl = dupprintf("putty @%s", session);
                        inherit_handles = FALSE;
                        freecl = TRUE;
diff --git a/windows/winjump.c b/windows/winjump.c
new file mode 100644 (file)
index 0000000..6552e07
--- /dev/null
@@ -0,0 +1,698 @@
+/*
+ * 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();
+    }
+}
diff --git a/windows/winnojmp.c b/windows/winnojmp.c
new file mode 100644 (file)
index 0000000..5a3ae83
--- /dev/null
@@ -0,0 +1,7 @@
+/*\r
+ * winnojmp.c: stub jump list functions for Windows executables that\r
+ * don't update the jump list.\r
+ */\r
+\r
+void add_session_to_jumplist(const char * const sessionname) {}\r
+void remove_session_from_jumplist(const char * const sessionname) {}\r
index 6e80434..00049b7 100644 (file)
@@ -17,6 +17,8 @@
 #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";
@@ -243,6 +245,8 @@ void del_settings(const char *sessionname)
     sfree(p);
 
     RegCloseKey(subkey1);
+
+    remove_session_from_jumplist(sessionname);
 }
 
 struct enumsettings {
@@ -582,6 +586,169 @@ void write_random_seed(void *data, int len)
 }
 
 /*
+ * 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.
  */
 static void registry_recursive_remove(HKEY key)
index c0e06dc..28f6556 100644 (file)
@@ -126,6 +126,14 @@ typedef struct terminal_tag Terminal;
 #define PUTTY_REG_GPARENT "Software"
 #define PUTTY_REG_GPARENT_CHILD "SimonTatham"
 
+/* Result values for the jumplist registry functions. */
+#define JUMPLISTREG_OK 0
+#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
+#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
+#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
+#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
+#define JUMPLISTREG_ERROR_INVALID_VALUE 5
+
 #define PUTTY_HELP_FILE "putty.hlp"
 #define PUTTY_CHM_FILE "putty.chm"
 #define PUTTY_HELP_CONTENTS "putty.cnt"
@@ -500,4 +508,34 @@ void agent_schedule_callback(void (*callback)(void *, void *, int),
  */
 extern Backend serial_backend;
 
+/*
+ * Exports from winjump.c.
+ */
+#define JUMPLIST_SUPPORTED             /* suppress #defines in putty.h */
+void add_session_to_jumplist(const char * const sessionname);
+void remove_session_from_jumplist(const char * const sessionname);
+
+/*
+ * Extra functions in winstore.c over and above the interface in
+ * storage.h.
+ *
+ * These functions manipulate the Registry section which mirrors the
+ * current Windows 7 jump list. (Because the real jump list storage is
+ * write-only, we need to keep another copy of whatever we put in it,
+ * so that we can put in a slightly modified version the next time.)
+ */
+
+/* Adds a saved session to the registry jump list mirror. 'item' is a
+ * string naming a saved session. */
+int add_to_jumplist_registry(const char *item);
+
+/* Removes an item from the registry jump list mirror. */
+int remove_from_jumplist_registry(const char *item);
+
+/* Returns the current jump list entries from the registry. Caller
+ * must free the returned pointer, which points to a contiguous
+ * sequence of NUL-terminated strings in memory, terminated with an
+ * empty one. */
+char *get_jumplist_registry_entries(void);
+
 #endif