diff --git a/Recipe b/Recipe index 48e304c3..1a34bb86 100644 --- 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 diff --git a/cmdline.c b/cmdline.c index 73c6f734..bfb2b426 100644 --- 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 9a557570..e80afadd 100644 --- 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 diff --git a/settings.c b/settings.c index 372a954d..2afb9f57 100644 --- a/settings.c +++ b/settings.c @@ -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) diff --git a/windows/window.c b/windows/window.c index 12b6fe71..7cd306da 100644 --- a/windows/window.c +++ b/windows/window.c @@ -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; @@ -354,6 +355,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 index 00000000..6552e074 --- /dev/null +++ b/windows/winjump.c @@ -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 + +#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 index 00000000..5a3ae83e --- /dev/null +++ b/windows/winnojmp.c @@ -0,0 +1,7 @@ +/* + * winnojmp.c: stub jump list functions for Windows executables that + * don't update the jump list. + */ + +void add_session_to_jumplist(const char * const sessionname) {} +void remove_session_from_jumplist(const char * const sessionname) {} diff --git a/windows/winstore.c b/windows/winstore.c index 6e804346..00049b74 100644 --- a/windows/winstore.c +++ b/windows/winstore.c @@ -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 { @@ -581,6 +585,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. */ diff --git a/windows/winstuff.h b/windows/winstuff.h index c0e06dce..28f6556b 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -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