diff --git a/Buildscr b/Buildscr index 83083f96..4c4868d4 100644 --- a/Buildscr +++ b/Buildscr @@ -168,34 +168,31 @@ in putty/icons do make -j$(nproc) in putty do convert -size 164x312 'gradient:blue-white' -distort SRT -90 -swirl 180 \( -size 329x312 canvas:white \) +append \( icons/putty-48.png -geometry +28+24 \) -composite \( icons/pscp-48.png -geometry +88+96 \) -composite \( icons/puttygen-48.png -geometry +28+168 \) -composite \( icons/pageant-48.png -geometry +88+240 \) -composite windows/msidialog.bmp in putty do convert -size 493x58 canvas:white \( icons/putty-48.png -geometry +440+5 \) -composite windows/msibanner.bmp -# Build the standard binaries, in both 32- and 64-bit flavours. +mkdir putty/windows/build32 +mkdir putty/windows/build64 +mkdir putty/windows/buildold +mkdir putty/windows/abuild32 +mkdir putty/windows/abuild64 + +# Build the binaries to go in the installer, in both 32- and 64-bit +# flavours. # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows with clangcl32 do mkdir build32 && Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl64 do mkdir build64 && Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc) +in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc) # Build experimental Arm Windows binaries. -in putty/windows with clangcl_a32 do mkdir abuild32 && Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl_a64 do mkdir abuild64 && Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc) - -# Build the 'old' binaries, which should still run on all 32-bit -# versions of Windows back to Win95 (but not Win32s). These link -# against Visual Studio 2003 libraries (the more modern versions -# assume excessively modern Win32 API calls to be available), specify -# a subsystem version of 4.0, and compile with /arch:IA32 to prevent -# the use of modern CPU features like MMX which older machines also -# might not have. -in putty/windows with clangcl32_2003 do mkdir buildold && Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj EXTRA_libs=libcpmt.lib XFLAGS=/arch:IA32 all -j$(nproc) +in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc) # Remove Windows binaries for the test programs we don't want to ship, -# like testbn.exe. (But we still _built_ them, to ensure the build +# like testcrypt.exe. (But we still _built_ them, to ensure the build # worked.) in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs # Code-sign the Windows binaries, if the local bob config provides a # script to do so in a cross-compiling way. We assume here that the @@ -220,6 +217,42 @@ in putty/windows do ./msiplatform.py installera64.msi Arm64 # Sign the Windows installers. ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi installera32.msi installera64.msi +# Delete the binaries and resource files from the build directories, +# so that we can rebuild them differently. +in putty/windows/build32 do rm -f *.exe *.res *.rcpp +in putty/windows/build64 do rm -f *.exe *.res *.rcpp +in putty/windows/abuild32 do rm -f *.exe *.res *.rcpp +in putty/windows/abuild64 do rm -f *.exe *.res *.rcpp + +# Build the standalone binaries, in both 32- and 64-bit flavours. +# These differ from the previous set in that they embed the help file. +in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc) +in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc) + +# Build the 'old' binaries, which should still run on all 32-bit +# versions of Windows back to Win95 (but not Win32s). These link +# against Visual Studio 2003 libraries (the more modern versions +# assume excessively modern Win32 API calls to be available), specify +# a subsystem version of 4.0, and compile with /arch:IA32 to prevent +# the use of modern CPU features like MMX which older machines also +# might not have. +# +# There's no installer to go with these, so they must also embed the +# help file. +in putty/windows with clangcl32_2003 do Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ RCFL=-DEMBED_CHM $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj EXTRA_libs=libcpmt.lib XFLAGS=/arch:IA32 all -j$(nproc) + +# Remove test programs again. +in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs +in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs +in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs +in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs +in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs + +# Code-sign the standalone versions of the binaries. +ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe + in putty/doc do make mostlyclean in putty/doc do make $(Docmakever) -j$(nproc) in putty/windows/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt diff --git a/Recipe b/Recipe index 12c7acd3..b62a2db1 100644 --- a/Recipe +++ b/Recipe @@ -64,9 +64,7 @@ # # - COMPAT=/DNO_HTMLHELP (Windows only) # Disables PuTTY's use of , which is not available -# with some development environments. The resulting binary -# will only look for an old-style WinHelp file (.HLP/.CNT), and -# will ignore any .CHM file. +# with some development environments. # # If you don't have this header, you may be able to use the copy # supplied with HTML Help Workshop. @@ -333,11 +331,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 winnojmp sessprep noterm LIBS + + winx11 plink.res winnojmp sessprep noterm winnohlp LIBS pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC - + pscp.res winnojmp LIBS + + pscp.res winnojmp winnohlp LIBS psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC - + psftp.res winnojmp LIBS + + psftp.res winnojmp winnohlp LIBS pageant : [G] winpgnt pageant sshrsa sshpubk sshdes ARITH sshmd5 version + tree234 MISC sshaes sshsha winsecur winpgntc aqsync sshdss sshsh256 diff --git a/misc.c b/misc.c index 236938af..0a86fa61 100644 --- a/misc.c +++ b/misc.c @@ -272,6 +272,14 @@ char *buildinfo(const char *newline) } } #endif +#if defined _WINDOWS + { + int echm = has_embedded_chm(); + if (echm >= 0) + strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); + } +#endif #if defined _WINDOWS && defined MINEFIELD strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); diff --git a/windows/installer.wxs b/windows/installer.wxs index 005d7e03..914d78a2 100644 --- a/windows/installer.wxs +++ b/windows/installer.wxs @@ -243,7 +243,11 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx Name="PuTTY Manual" Advertise="no" /> + + #include "putty.h" +#include "win_res.h" + +#ifdef NO_HTMLHELP + +/* If htmlhelp.h is not available, we can't do any of this at all */ +bool has_help(void) { return false; } +void init_help(void) { } +void shutdown_help(void) { } +void launch_help(HWND hwnd, const char *topic) { } +void quit_help(HWND hwnd) { } + +#else -#ifndef NO_HTMLHELP #include -#endif /* NO_HTMLHELP */ + +static char *chm_path = NULL; +static bool chm_created_by_us = false; static bool requested_help; -static char *help_path; -static bool help_has_contents; -#ifndef NO_HTMLHELP DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR)); -static char *chm_path; -#endif /* NO_HTMLHELP */ + +static HRSRC chm_hrsrc; +static DWORD chm_resource_size = 0; +static const void *chm_resource = NULL; + +int has_embedded_chm(void) +{ + static bool checked = false; + if (!checked) { + checked = true; + + chm_hrsrc = FindResource( + NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE), + MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE)); + } + return chm_hrsrc != NULL ? 1 : 0; +} + +static bool find_chm_resource(void) +{ + static bool checked = false; + if (checked) /* we've been here already */ + goto out; + checked = true; + + /* + * Look for a CHM file embedded in this executable as a custom + * resource. + */ + if (!has_embedded_chm()) /* set up chm_hrsrc and check if it's NULL */ + goto out; + + chm_resource_size = SizeofResource(NULL, chm_hrsrc); + if (chm_resource_size == 0) + goto out; + + HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc); + if (chm_hglobal == NULL) + goto out; + + chm_resource = (const uint8_t *)LockResource(chm_hglobal); + + out: + return chm_resource != NULL; +} + +static bool load_chm_resource(void) +{ + bool toret = false; + char *filename = NULL; + HANDLE filehandle = INVALID_HANDLE_VALUE; + bool created = false; + + static bool tried_to_load = false; + if (tried_to_load) + goto out; + tried_to_load = true; + + /* + * We've found it! Now write it out into a separate file, so that + * htmlhelp.exe can handle it. + */ + + /* GetTempPath is documented as returning a size of up to + * MAX_PATH+1 which does not count the NUL */ + char tempdir[MAX_PATH + 2]; + if (GetTempPath(sizeof(tempdir), tempdir) == 0) + goto out; + + unsigned long pid = GetCurrentProcessId(); + + for (uint64_t counter = 0;; counter++) { + filename = dupprintf( + "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter); + filehandle = CreateFile( + filename, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + + if (filehandle != INVALID_HANDLE_VALUE) + break; /* success! */ + + if (GetLastError() != ERROR_FILE_EXISTS) + goto out; /* failed for some other reason! */ + + sfree(filename); + filename = NULL; + } + created = true; + + const uint8_t *p = (const uint8_t *)chm_resource; + for (DWORD pos = 0; pos < chm_resource_size; pos++) { + DWORD to_write = chm_resource_size - pos; + DWORD written = 0; + + if (!WriteFile(filehandle, p + pos, to_write, &written, NULL)) + goto out; + pos += written; + } + + chm_path = filename; + filename = NULL; + chm_created_by_us = true; + toret = true; + + out: + if (created && !toret) + DeleteFile(filename); + sfree(filename); + if (filehandle != INVALID_HANDLE_VALUE) + CloseHandle(filehandle); + return toret; +} + +static bool find_chm_from_installation(void) +{ + static const char *const reg_paths[] = { + "Software\\SimonTatham\\PuTTY64\\CHMPath", + "Software\\SimonTatham\\PuTTY\\CHMPath", + }; + + for (size_t i = 0; i < lenof(reg_paths); i++) { + char *filename = registry_get_string( + HKEY_LOCAL_MACHINE, reg_paths[i], NULL); + + if (filename) { + chm_path = filename; + chm_created_by_us = false; + return true; + } + } + + return false; +} void init_help(void) { - char b[2048], *p, *q, *r; - FILE *fp; + /* Just in case of multiple calls */ + static bool already_called = false; + if (already_called) + return; + already_called = true; - GetModuleFileName(NULL, b, sizeof(b) - 1); - r = b; - p = strrchr(b, '\\'); - if (p && p >= r) r = p+1; - q = strrchr(b, ':'); - if (q && q >= r) r = q+1; - strcpy(r, PUTTY_HELP_FILE); - if ( (fp = fopen(b, "r")) != NULL) { - help_path = dupstr(b); - fclose(fp); - } else - help_path = NULL; - strcpy(r, PUTTY_HELP_CONTENTS); - if ( (fp = fopen(b, "r")) != NULL) { - help_has_contents = true; - fclose(fp); - } else - help_has_contents = false; - -#ifndef NO_HTMLHELP - strcpy(r, PUTTY_CHM_FILE); - if ( (fp = fopen(b, "r")) != NULL) { - chm_path = dupstr(b); - fclose(fp); - } else - chm_path = NULL; - if (chm_path) { - HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); - GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); - if (!p_HtmlHelpA) { - sfree(chm_path); - chm_path = NULL; - if (dllHH) - FreeLibrary(dllHH); - } + /* + * Don't even try looking for the CHM file if we can't even find + * the HtmlHelp() API function. + */ + HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); + GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); + if (!p_HtmlHelpA) { + FreeLibrary(dllHH); + return; } -#endif /* NO_HTMLHELP */ + + /* + * If there's a CHM file embedded in this executable, we should + * use that as the first choice. + */ + if (find_chm_resource()) + return; + + /* + * Otherwise, try looking for the CHM in the location that the + * installer marked in the registry. + */ + if (find_chm_from_installation()) + return; } void shutdown_help(void) { - /* Nothing to do currently. - * (If we were running HTML Help single-threaded, this is where we'd - * call HH_UNINITIALIZE.) */ + if (chm_path && chm_created_by_us) { + p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); + DeleteFile(chm_path); + } + sfree(chm_path); + chm_path = NULL; + chm_created_by_us = false; } bool has_help(void) { - /* - * FIXME: it would be nice here to disregard help_path on - * platforms that didn't have WINHLP32. But that's probably - * unrealistic, since even Vista will have it if the user - * specifically downloads it. - */ - return (help_path != NULL -#ifndef NO_HTMLHELP - || chm_path -#endif /* NO_HTMLHELP */ - ); + return chm_path != NULL || chm_resource != NULL; } void launch_help(HWND hwnd, const char *topic) { + if (!chm_path && chm_resource) { + /* + * If we've been called without already having a file name for + * the CHM file, that might be because we've located it in our + * resource section but not written it to a temp file yet. Do + * so now, on first use. + */ + load_chm_resource(); + } + + /* If we _still_ don't have a CHM pathname, we just can't display help. */ + if (!chm_path) + return; + if (topic) { int colonpos = strcspn(topic, ":"); + assert(topic[colonpos] != '\0'); -#ifndef NO_HTMLHELP - if (chm_path) { - char *fname; - assert(topic[colonpos] != '\0'); - fname = dupprintf("%s::/%s.html>main", chm_path, - topic + colonpos + 1); - p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); - sfree(fname); - } else -#endif /* NO_HTMLHELP */ - if (help_path) { - char *cmd = dupprintf("JI(`',`%.*s')", colonpos, topic); - WinHelp(hwnd, help_path, HELP_COMMAND, (ULONG_PTR)cmd); - sfree(cmd); - } + char *fname = dupprintf( + "%s::/%s.html>main", chm_path, topic + colonpos + 1); + p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); + sfree(fname); } else { -#ifndef NO_HTMLHELP - if (chm_path) { - p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); - } else -#endif /* NO_HTMLHELP */ - if (help_path) { - WinHelp(hwnd, help_path, - help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0); - } + p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); } requested_help = true; } void quit_help(HWND hwnd) { - if (requested_help) { -#ifndef NO_HTMLHELP - if (chm_path) { - p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); - } else -#endif /* NO_HTMLHELP */ - if (help_path) { - WinHelp(hwnd, help_path, HELP_QUIT, 0); - } - requested_help = false; - } + if (requested_help) + p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); + if (chm_path && chm_created_by_us) + DeleteFile(chm_path); } + +#endif /* NO_HTMLHELP */ diff --git a/windows/winhelp.rc2 b/windows/winhelp.rc2 new file mode 100644 index 00000000..f1e26e73 --- /dev/null +++ b/windows/winhelp.rc2 @@ -0,0 +1,5 @@ +#include "win_res.h" + +#ifdef EMBED_CHM +ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE "../doc/putty.chm" +#endif diff --git a/windows/winmisc.c b/windows/winmisc.c index 2df3a6b0..00b2fcd2 100644 --- a/windows/winmisc.c +++ b/windows/winmisc.c @@ -430,3 +430,40 @@ void dputs(const char *buf) fflush(debug_fp); } #endif + +char *registry_get_string(HKEY root, const char *path, const char *leaf) +{ + HKEY key = root; + bool need_close_key = false; + char *toret = NULL, *str = NULL; + + if (path) { + if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) + goto out; + need_close_key = true; + } + + DWORD type, size; + if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ) + goto out; + + str = snewn(size + 1, char); + DWORD size_got = size; + if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, + &size_got) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ || size_got > size) + goto out; + str[size_got] = '\0'; + + toret = str; + str = NULL; + + out: + if (need_close_key) + RegCloseKey(key); + sfree(str); + return toret; +} diff --git a/windows/winnohlp.c b/windows/winnohlp.c new file mode 100644 index 00000000..62ddc65c --- /dev/null +++ b/windows/winnohlp.c @@ -0,0 +1,15 @@ +/* + * nohelp.c: implement the has_embedded_chm() function for + * applications that have no help file at all, so that misc.c's + * buildinfo string knows not to talk meaninglessly about whether the + * nonexistent help file is present. + */ + +#include +#include +#include +#include + +#include "putty.h" + +int has_embedded_chm(void) { return -1; } diff --git a/windows/winstuff.h b/windows/winstuff.h index 2fb2acf6..08d434c2 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -228,6 +228,7 @@ void shutdown_help(void); bool has_help(void); void launch_help(HWND hwnd, const char *topic); void quit_help(HWND hwnd); +int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ /* * The terminal and logging context are notionally local to the @@ -700,4 +701,7 @@ char *get_jumplist_registry_entries(void); #define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT +/* In winmisc.c */ +char *registry_get_string(HKEY root, const char *path, const char *leaf); + #endif