help.c 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. * help.c: centralised functions to launch Windows HTML Help files.
  3. */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <assert.h>
  8. #include "putty.h"
  9. #include "putty-rc.h"
  10. #ifdef NO_HTMLHELP
  11. /* If htmlhelp.h is not available, we can't do any of this at all */
  12. bool has_help(void) { return false; }
  13. void init_help(void) { }
  14. void shutdown_help(void) { }
  15. void launch_help(HWND hwnd, const char *topic) { }
  16. void quit_help(HWND hwnd) { }
  17. #else
  18. #include <htmlhelp.h>
  19. static char *chm_path = NULL;
  20. static bool chm_created_by_us = false;
  21. static bool requested_help;
  22. DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR));
  23. static HRSRC chm_hrsrc;
  24. static DWORD chm_resource_size = 0;
  25. static const void *chm_resource = NULL;
  26. int has_embedded_chm(void)
  27. {
  28. static bool checked = false;
  29. if (!checked) {
  30. checked = true;
  31. chm_hrsrc = FindResource(
  32. NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE),
  33. MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE));
  34. }
  35. return chm_hrsrc != NULL ? 1 : 0;
  36. }
  37. static bool find_chm_resource(void)
  38. {
  39. static bool checked = false;
  40. if (checked) /* we've been here already */
  41. goto out;
  42. checked = true;
  43. /*
  44. * Look for a CHM file embedded in this executable as a custom
  45. * resource.
  46. */
  47. if (!has_embedded_chm()) /* set up chm_hrsrc and check if it's NULL */
  48. goto out;
  49. chm_resource_size = SizeofResource(NULL, chm_hrsrc);
  50. if (chm_resource_size == 0)
  51. goto out;
  52. HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc);
  53. if (chm_hglobal == NULL)
  54. goto out;
  55. chm_resource = (const uint8_t *)LockResource(chm_hglobal);
  56. out:
  57. return chm_resource != NULL;
  58. }
  59. static bool load_chm_resource(void)
  60. {
  61. bool toret = false;
  62. char *filename = NULL;
  63. HANDLE filehandle = INVALID_HANDLE_VALUE;
  64. bool created = false;
  65. static bool tried_to_load = false;
  66. if (tried_to_load)
  67. goto out;
  68. tried_to_load = true;
  69. /*
  70. * We've found it! Now write it out into a separate file, so that
  71. * htmlhelp.exe can handle it.
  72. */
  73. /* GetTempPath is documented as returning a size of up to
  74. * MAX_PATH+1 which does not count the NUL */
  75. char tempdir[MAX_PATH + 2];
  76. if (GetTempPath(sizeof(tempdir), tempdir) == 0)
  77. goto out;
  78. unsigned long pid = GetCurrentProcessId();
  79. for (uint64_t counter = 0;; counter++) {
  80. filename = dupprintf(
  81. "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter);
  82. filehandle = CreateFile(
  83. filename, GENERIC_WRITE, FILE_SHARE_READ,
  84. NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
  85. if (filehandle != INVALID_HANDLE_VALUE)
  86. break; /* success! */
  87. if (GetLastError() != ERROR_FILE_EXISTS)
  88. goto out; /* failed for some other reason! */
  89. sfree(filename);
  90. filename = NULL;
  91. }
  92. created = true;
  93. const uint8_t *p = (const uint8_t *)chm_resource;
  94. for (DWORD pos = 0; pos < chm_resource_size ;) {
  95. DWORD to_write = chm_resource_size - pos;
  96. DWORD written = 0;
  97. if (!WriteFile(filehandle, p + pos, to_write, &written, NULL))
  98. goto out;
  99. pos += written;
  100. }
  101. chm_path = filename;
  102. filename = NULL;
  103. chm_created_by_us = true;
  104. toret = true;
  105. out:
  106. if (created && !toret)
  107. DeleteFile(filename);
  108. sfree(filename);
  109. if (filehandle != INVALID_HANDLE_VALUE)
  110. CloseHandle(filehandle);
  111. return toret;
  112. }
  113. static bool find_chm_from_installation(void)
  114. {
  115. static const char *const reg_paths[] = {
  116. "Software\\SimonTatham\\PuTTY64\\CHMPath",
  117. "Software\\SimonTatham\\PuTTY\\CHMPath",
  118. };
  119. for (size_t i = 0; i < lenof(reg_paths); i++) {
  120. char *filename = get_reg_sz_simple(
  121. HKEY_LOCAL_MACHINE, reg_paths[i], NULL);
  122. if (filename) {
  123. chm_path = filename;
  124. chm_created_by_us = false;
  125. return true;
  126. }
  127. }
  128. return false;
  129. }
  130. void init_help(void)
  131. {
  132. /* Just in case of multiple calls */
  133. static bool already_called = false;
  134. if (already_called)
  135. return;
  136. already_called = true;
  137. /*
  138. * Don't even try looking for the CHM file if we can't even find
  139. * the HtmlHelp() API function.
  140. */
  141. HINSTANCE dllHH = load_system32_dll("hhctrl.ocx");
  142. GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA);
  143. if (!p_HtmlHelpA) {
  144. FreeLibrary(dllHH);
  145. return;
  146. }
  147. /*
  148. * If there's a CHM file embedded in this executable, we should
  149. * use that as the first choice.
  150. */
  151. if (find_chm_resource())
  152. return;
  153. /*
  154. * Otherwise, try looking for the CHM in the location that the
  155. * installer marked in the registry.
  156. */
  157. if (find_chm_from_installation())
  158. return;
  159. }
  160. void shutdown_help(void)
  161. {
  162. if (chm_path && chm_created_by_us) {
  163. p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
  164. DeleteFile(chm_path);
  165. }
  166. sfree(chm_path);
  167. chm_path = NULL;
  168. chm_created_by_us = false;
  169. }
  170. bool has_help(void)
  171. {
  172. return chm_path != NULL || chm_resource != NULL;
  173. }
  174. void launch_help(HWND hwnd, const char *topic)
  175. {
  176. if (!chm_path && chm_resource) {
  177. /*
  178. * If we've been called without already having a file name for
  179. * the CHM file, that might be because we've located it in our
  180. * resource section but not written it to a temp file yet. Do
  181. * so now, on first use.
  182. */
  183. load_chm_resource();
  184. }
  185. /* If we _still_ don't have a CHM pathname, we just can't display help. */
  186. if (!chm_path)
  187. return;
  188. if (topic) {
  189. char *fname = dupprintf(
  190. "%s::/%s.html>main", chm_path, topic);
  191. p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0);
  192. sfree(fname);
  193. } else {
  194. p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0);
  195. }
  196. requested_help = true;
  197. }
  198. void quit_help(HWND hwnd)
  199. {
  200. if (requested_help)
  201. p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
  202. if (chm_path && chm_created_by_us)
  203. DeleteFile(chm_path);
  204. }
  205. #endif /* NO_HTMLHELP */