main-gtk-simple.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. /*
  2. * main-gtk-simple.c: the common main-program code between the
  3. * straight-up Unix PuTTY and pterm, which they do not share with the
  4. * multi-session main-gtk-application.c.
  5. */
  6. #define _GNU_SOURCE
  7. #include <string.h>
  8. #include <assert.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <signal.h>
  12. #include <stdio.h>
  13. #include <time.h>
  14. #include <errno.h>
  15. #include <locale.h>
  16. #include <fcntl.h>
  17. #include <unistd.h>
  18. #include <sys/types.h>
  19. #include <sys/wait.h>
  20. #include <gtk/gtk.h>
  21. #if !GTK_CHECK_VERSION(3,0,0)
  22. #include <gdk/gdkkeysyms.h>
  23. #endif
  24. #if GTK_CHECK_VERSION(2,0,0)
  25. #include <gtk/gtkimmodule.h>
  26. #endif
  27. #define MAY_REFER_TO_GTK_IN_HEADERS
  28. #include "putty.h"
  29. #include "ssh.h"
  30. #include "terminal.h"
  31. #include "gtkcompat.h"
  32. #include "unifont.h"
  33. #include "gtkmisc.h"
  34. #ifndef NOT_X_WINDOWS
  35. #include <gdk/gdkx.h>
  36. #include <X11/Xlib.h>
  37. #include <X11/Xutil.h>
  38. #include <X11/Xatom.h>
  39. #include "x11misc.h"
  40. #endif
  41. static char *progname, **gtkargvstart;
  42. static int ngtkargs;
  43. static const char *app_name = "pterm";
  44. char *x_get_default(const char *key)
  45. {
  46. #ifndef NOT_X_WINDOWS
  47. Display *disp;
  48. if ((disp = get_x11_display()) == NULL)
  49. return NULL;
  50. return XGetDefault(disp, app_name, key);
  51. #else
  52. return NULL;
  53. #endif
  54. }
  55. void fork_and_exec_self(int fd_to_close, ...)
  56. {
  57. /*
  58. * Re-execing ourself is not an exact science under Unix. I do
  59. * the best I can by using /proc/self/exe if available and by
  60. * assuming argv[0] can be found on $PATH if not.
  61. *
  62. * Note that we also have to reconstruct the elements of the
  63. * original argv which gtk swallowed, since the user wants the
  64. * new session to appear on the same X display as the old one.
  65. */
  66. char **args;
  67. va_list ap;
  68. int i, n;
  69. int pid;
  70. /*
  71. * Collect the arguments with which to re-exec ourself.
  72. */
  73. va_start(ap, fd_to_close);
  74. n = 2; /* progname and terminating NULL */
  75. n += ngtkargs;
  76. while (va_arg(ap, char *) != NULL)
  77. n++;
  78. va_end(ap);
  79. args = snewn(n, char *);
  80. args[0] = progname;
  81. args[n-1] = NULL;
  82. for (i = 0; i < ngtkargs; i++)
  83. args[i+1] = gtkargvstart[i];
  84. i++;
  85. va_start(ap, fd_to_close);
  86. while ((args[i++] = va_arg(ap, char *)) != NULL);
  87. va_end(ap);
  88. assert(i == n);
  89. /*
  90. * Do the double fork.
  91. */
  92. pid = fork();
  93. if (pid < 0) {
  94. perror("fork");
  95. sfree(args);
  96. return;
  97. }
  98. if (pid == 0) {
  99. int pid2 = fork();
  100. if (pid2 < 0) {
  101. perror("fork");
  102. _exit(1);
  103. } else if (pid2 > 0) {
  104. /*
  105. * First child has successfully forked second child. My
  106. * Work Here Is Done. Note the use of _exit rather than
  107. * exit: the latter appears to cause destroy messages
  108. * to be sent to the X server. I suspect gtk uses
  109. * atexit.
  110. */
  111. _exit(0);
  112. }
  113. /*
  114. * If we reach here, we are the second child, so we now
  115. * actually perform the exec.
  116. */
  117. if (fd_to_close >= 0)
  118. close(fd_to_close);
  119. execv("/proc/self/exe", args);
  120. execvp(progname, args);
  121. perror("exec");
  122. _exit(127);
  123. } else {
  124. int status;
  125. sfree(args);
  126. waitpid(pid, &status, 0);
  127. }
  128. }
  129. void launch_duplicate_session(Conf *conf)
  130. {
  131. /*
  132. * For this feature we must marshal conf and (possibly) pty_argv
  133. * into a byte stream, create a pipe, and send this byte stream
  134. * to the child through the pipe.
  135. */
  136. int i, ret;
  137. strbuf *serialised;
  138. char option[80];
  139. int pipefd[2];
  140. if (pipe(pipefd) < 0) {
  141. perror("pipe");
  142. return;
  143. }
  144. serialised = strbuf_new();
  145. conf_serialise(BinarySink_UPCAST(serialised), conf);
  146. if (use_pty_argv && pty_argv)
  147. for (i = 0; pty_argv[i]; i++)
  148. put_asciz(serialised, pty_argv[i]);
  149. sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len);
  150. noncloexec(pipefd[0]);
  151. fork_and_exec_self(pipefd[1], option, NULL);
  152. close(pipefd[0]);
  153. i = ret = 0;
  154. while (i < serialised->len &&
  155. (ret = write(pipefd[1], serialised->s + i,
  156. serialised->len - i)) > 0)
  157. i += ret;
  158. if (ret < 0)
  159. perror("write to pipe");
  160. close(pipefd[1]);
  161. strbuf_free(serialised);
  162. }
  163. void launch_new_session(void)
  164. {
  165. fork_and_exec_self(-1, NULL);
  166. }
  167. void launch_saved_session(const char *str)
  168. {
  169. fork_and_exec_self(-1, "-load", str, NULL);
  170. }
  171. int read_dupsession_data(Conf *conf, char *arg)
  172. {
  173. int fd, i, ret, size;
  174. char *data;
  175. BinarySource src[1];
  176. if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
  177. fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
  178. exit(1);
  179. }
  180. data = snewn(size, char);
  181. i = ret = 0;
  182. while (i < size && (ret = read(fd, data + i, size - i)) > 0)
  183. i += ret;
  184. if (ret < 0) {
  185. perror("read from pipe");
  186. exit(1);
  187. } else if (i < size) {
  188. fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
  189. appname);
  190. exit(1);
  191. }
  192. BinarySource_BARE_INIT(src, data, size);
  193. if (!conf_deserialise(conf, src)) {
  194. fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
  195. exit(1);
  196. }
  197. if (use_pty_argv) {
  198. int pty_argc = 0;
  199. size_t argv_startpos = src->pos;
  200. while (get_asciz(src), !get_err(src))
  201. pty_argc++;
  202. src->err = BSE_NO_ERROR;
  203. if (pty_argc > 0) {
  204. src->pos = argv_startpos;
  205. pty_argv = snewn(pty_argc + 1, char *);
  206. pty_argv[pty_argc] = NULL;
  207. for (i = 0; i < pty_argc; i++)
  208. pty_argv[i] = dupstr(get_asciz(src));
  209. }
  210. }
  211. if (get_err(src) || get_avail(src) > 0) {
  212. fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
  213. exit(1);
  214. }
  215. sfree(data);
  216. return 0;
  217. }
  218. static void help(FILE *fp) {
  219. if (fprintf(fp,
  220. "pterm option summary:\n"
  221. "\n"
  222. " --display DISPLAY Specify X display to use (note '--')\n"
  223. " -name PREFIX Prefix when looking up resources (default: pterm)\n"
  224. " -fn FONT Normal text font\n"
  225. " -fb FONT Bold text font\n"
  226. " -geometry GEOMETRY Position and size of window (size in characters)\n"
  227. " -sl LINES Number of lines of scrollback\n"
  228. " -fg COLOUR, -bg COLOUR Foreground/background colour\n"
  229. " -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
  230. " -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
  231. " -T TITLE Window title\n"
  232. " -ut, +ut Do(default) or do not update utmp\n"
  233. " -ls, +ls Do(default) or do not make shell a login shell\n"
  234. " -sb, +sb Do(default) or do not display a scrollbar\n"
  235. " -log PATH, -sessionlog PATH Log all output to a file\n"
  236. " -nethack Map numeric keypad to hjklyubn direction keys\n"
  237. " -xrm RESOURCE-STRING Set an X resource\n"
  238. " -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
  239. ) < 0 || fflush(fp) < 0) {
  240. perror("output error");
  241. exit(1);
  242. }
  243. }
  244. static void version(FILE *fp) {
  245. char *buildinfo_text = buildinfo("\n");
  246. if (fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
  247. fflush(fp) < 0) {
  248. perror("output error");
  249. exit(1);
  250. }
  251. sfree(buildinfo_text);
  252. }
  253. static const char *geometry_string;
  254. void cmdline_error(const char *p, ...)
  255. {
  256. va_list ap;
  257. fprintf(stderr, "%s: ", appname);
  258. va_start(ap, p);
  259. vfprintf(stderr, p, ap);
  260. va_end(ap);
  261. fputc('\n', stderr);
  262. exit(1);
  263. }
  264. void window_setup_error(const char *errmsg)
  265. {
  266. fprintf(stderr, "%s: %s\n", appname, errmsg);
  267. exit(1);
  268. }
  269. bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
  270. {
  271. bool err = false;
  272. /*
  273. * Macros to make argument handling easier.
  274. *
  275. * Note that because they need to call `continue', they cannot be
  276. * contained in the usual do {...} while (0) wrapper to make them
  277. * syntactically single statements. I use the alternative if (1)
  278. * {...} else ((void)0).
  279. */
  280. #define EXPECTS_ARG if (1) { \
  281. if (!nextarg) { \
  282. err = true; \
  283. fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
  284. continue; \
  285. } else { \
  286. arglistpos++; \
  287. } \
  288. } else ((void)0)
  289. #define SECOND_PASS_ONLY if (1) { \
  290. if (!do_everything) \
  291. continue; \
  292. } else ((void)0)
  293. CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv);
  294. size_t arglistpos = 0;
  295. while (arglist->args[arglistpos]) {
  296. CmdlineArg *arg = arglist->args[arglistpos++];
  297. CmdlineArg *nextarg = arglist->args[arglistpos];
  298. const char *p = cmdline_arg_to_str(arg);
  299. const char *val = cmdline_arg_to_str(nextarg);
  300. int ret;
  301. /*
  302. * Shameless cheating. Debian requires all X terminal
  303. * emulators to support `-T title'; but
  304. * cmdline_process_param will eat -T (it means no-pty) and
  305. * complain that pterm doesn't support it. So, in pterm
  306. * only, we convert -T into -title.
  307. */
  308. if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
  309. !strcmp(p, "-T"))
  310. p = "-title";
  311. ret = cmdline_process_param(
  312. arg, nextarg, do_everything ? 1 : -1, conf);
  313. if (ret == -2) {
  314. cmdline_error("option \"%s\" requires an argument", p);
  315. } else if (ret == 2) {
  316. arglistpos++;
  317. continue;
  318. } else if (ret == 1) {
  319. continue;
  320. }
  321. if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
  322. FontSpec *fs;
  323. EXPECTS_ARG;
  324. SECOND_PASS_ONLY;
  325. fs = fontspec_new(val);
  326. conf_set_fontspec(conf, CONF_font, fs);
  327. fontspec_free(fs);
  328. } else if (!strcmp(p, "-fb")) {
  329. FontSpec *fs;
  330. EXPECTS_ARG;
  331. SECOND_PASS_ONLY;
  332. fs = fontspec_new(val);
  333. conf_set_fontspec(conf, CONF_boldfont, fs);
  334. fontspec_free(fs);
  335. } else if (!strcmp(p, "-fw")) {
  336. FontSpec *fs;
  337. EXPECTS_ARG;
  338. SECOND_PASS_ONLY;
  339. fs = fontspec_new(val);
  340. conf_set_fontspec(conf, CONF_widefont, fs);
  341. fontspec_free(fs);
  342. } else if (!strcmp(p, "-fwb")) {
  343. FontSpec *fs;
  344. EXPECTS_ARG;
  345. SECOND_PASS_ONLY;
  346. fs = fontspec_new(val);
  347. conf_set_fontspec(conf, CONF_wideboldfont, fs);
  348. fontspec_free(fs);
  349. } else if (!strcmp(p, "-cs")) {
  350. EXPECTS_ARG;
  351. SECOND_PASS_ONLY;
  352. conf_set_str(conf, CONF_line_codepage, val);
  353. } else if (!strcmp(p, "-geometry")) {
  354. EXPECTS_ARG;
  355. SECOND_PASS_ONLY;
  356. geometry_string = val;
  357. } else if (!strcmp(p, "-sl")) {
  358. EXPECTS_ARG;
  359. SECOND_PASS_ONLY;
  360. conf_set_int(conf, CONF_savelines, atoi(val));
  361. } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
  362. !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
  363. !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
  364. EXPECTS_ARG;
  365. SECOND_PASS_ONLY;
  366. {
  367. #if GTK_CHECK_VERSION(3,0,0)
  368. GdkRGBA rgba;
  369. bool success = gdk_rgba_parse(&rgba, val);
  370. #else
  371. GdkColor col;
  372. bool success = gdk_color_parse(val, &col);
  373. #endif
  374. if (!success) {
  375. err = true;
  376. fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
  377. appname, val);
  378. } else {
  379. #if GTK_CHECK_VERSION(3,0,0)
  380. int r = rgba.red * 255;
  381. int g = rgba.green * 255;
  382. int b = rgba.blue * 255;
  383. #else
  384. int r = col.red / 256;
  385. int g = col.green / 256;
  386. int b = col.blue / 256;
  387. #endif
  388. int index;
  389. index = (!strcmp(p, "-fg") ? 0 :
  390. !strcmp(p, "-bg") ? 2 :
  391. !strcmp(p, "-bfg") ? 1 :
  392. !strcmp(p, "-bbg") ? 3 :
  393. !strcmp(p, "-cfg") ? 4 :
  394. !strcmp(p, "-cbg") ? 5 : -1);
  395. assert(index != -1);
  396. conf_set_int_int(conf, CONF_colours, index*3+0, r);
  397. conf_set_int_int(conf, CONF_colours, index*3+1, g);
  398. conf_set_int_int(conf, CONF_colours, index*3+2, b);
  399. }
  400. }
  401. } else if (use_pty_argv && !strcmp(p, "-e")) {
  402. /* This option swallows all further arguments. */
  403. if (!do_everything)
  404. break;
  405. if (nextarg) {
  406. pty_argv = cmdline_arg_remainder(nextarg);
  407. break; /* finished command-line processing */
  408. } else
  409. err = true, fprintf(stderr, "%s: -e expects an argument\n",
  410. appname);
  411. } else if (!strcmp(p, "-title")) {
  412. EXPECTS_ARG;
  413. SECOND_PASS_ONLY;
  414. conf_set_str(conf, CONF_wintitle, val);
  415. } else if (!strcmp(p, "-log")) {
  416. EXPECTS_ARG;
  417. SECOND_PASS_ONLY;
  418. Filename *fn = cmdline_arg_to_filename(nextarg);
  419. conf_set_filename(conf, CONF_logfilename, fn);
  420. conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
  421. filename_free(fn);
  422. } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
  423. SECOND_PASS_ONLY;
  424. conf_set_bool(conf, CONF_stamp_utmp, false);
  425. } else if (!strcmp(p, "-ut")) {
  426. SECOND_PASS_ONLY;
  427. conf_set_bool(conf, CONF_stamp_utmp, true);
  428. } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
  429. SECOND_PASS_ONLY;
  430. conf_set_bool(conf, CONF_login_shell, false);
  431. } else if (!strcmp(p, "-ls")) {
  432. SECOND_PASS_ONLY;
  433. conf_set_bool(conf, CONF_login_shell, true);
  434. } else if (!strcmp(p, "-nethack")) {
  435. SECOND_PASS_ONLY;
  436. conf_set_bool(conf, CONF_nethack_keypad, true);
  437. } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
  438. SECOND_PASS_ONLY;
  439. conf_set_bool(conf, CONF_scrollbar, false);
  440. } else if (!strcmp(p, "-sb")) {
  441. SECOND_PASS_ONLY;
  442. conf_set_bool(conf, CONF_scrollbar, true);
  443. } else if (!strcmp(p, "-name")) {
  444. EXPECTS_ARG;
  445. app_name = val;
  446. } else if (!strcmp(p, "-xrm")) {
  447. EXPECTS_ARG;
  448. provide_xrm_string(val, appname);
  449. } else if (!strcmp(p, "-help") || !strcmp(p, "--help")) {
  450. help(stdout);
  451. exit(0);
  452. } else if (!strcmp(p, "-version") || !strcmp(p, "--version")) {
  453. version(stdout);
  454. exit(0);
  455. } else if (!strcmp(p, "-pgpfp")) {
  456. pgp_fingerprints();
  457. exit(0);
  458. } else if (has_ca_config_box &&
  459. (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") ||
  460. !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) {
  461. show_ca_config_box_synchronously();
  462. exit(0);
  463. } else if (p[0] != '-') {
  464. /* Non-option arguments not handled by cmdline.c are errors. */
  465. if (do_everything) {
  466. err = true;
  467. fprintf(stderr, "%s: unexpected non-option argument '%s'\n",
  468. appname, p);
  469. }
  470. } else {
  471. err = true;
  472. fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
  473. }
  474. }
  475. return err;
  476. }
  477. GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
  478. {
  479. return gtk_window_new(GTK_WINDOW_TOPLEVEL);
  480. }
  481. const bool buildinfo_gtk_relevant = true;
  482. struct post_initial_config_box_ctx {
  483. Conf *conf;
  484. const char *geometry_string;
  485. };
  486. static void post_initial_config_box(void *vctx, int result)
  487. {
  488. struct post_initial_config_box_ctx ctx =
  489. *(struct post_initial_config_box_ctx *)vctx;
  490. sfree(vctx);
  491. if (result > 0) {
  492. new_session_window(ctx.conf, ctx.geometry_string);
  493. } else {
  494. /* In this main(), which only runs one session in total, a
  495. * negative result from the initial config box means we simply
  496. * terminate. */
  497. conf_free(ctx.conf);
  498. gtk_main_quit();
  499. }
  500. }
  501. void session_window_closed(void)
  502. {
  503. gtk_main_quit();
  504. }
  505. int main(int argc, char **argv)
  506. {
  507. Conf *conf;
  508. bool need_config_box;
  509. setlocale(LC_CTYPE, "");
  510. enable_dit();
  511. /* Call the function in ux{putty,pterm}.c to do app-type
  512. * specific setup */
  513. setup(true); /* true means we are a one-session process */
  514. progname = argv[0];
  515. /*
  516. * Copy the original argv before letting gtk_init fiddle with
  517. * it. It will be required later.
  518. */
  519. {
  520. int i, oldargc;
  521. gtkargvstart = snewn(argc-1, char *);
  522. for (i = 1; i < argc; i++)
  523. gtkargvstart[i-1] = dupstr(argv[i]);
  524. oldargc = argc;
  525. gtk_init(&argc, &argv);
  526. ngtkargs = oldargc - argc;
  527. }
  528. conf = conf_new();
  529. gtkcomm_setup();
  530. /*
  531. * Block SIGPIPE: if we attempt Duplicate Session or similar and
  532. * it falls over in some way, we certainly don't want SIGPIPE
  533. * terminating the main pterm/PuTTY. However, we'll have to
  534. * unblock it again when pterm forks.
  535. */
  536. block_signal(SIGPIPE, true);
  537. if (argc > 1 && !strncmp(argv[1], "---", 3)) {
  538. read_dupsession_data(conf, argv[1]);
  539. /* Splatter this argument so it doesn't clutter a ps listing */
  540. smemclr(argv[1], strlen(argv[1]));
  541. assert(!dup_check_launchable || conf_launchable(conf));
  542. need_config_box = false;
  543. } else {
  544. if (do_cmdline(argc, argv, false, conf))
  545. exit(1); /* pre-defaults pass to get -class */
  546. do_defaults(NULL, conf);
  547. if (do_cmdline(argc, argv, true, conf))
  548. exit(1); /* post-defaults, do everything */
  549. cmdline_run_saved(conf);
  550. if (cmdline_tooltype & TOOLTYPE_HOST_ARG)
  551. need_config_box = !cmdline_host_ok(conf);
  552. else
  553. need_config_box = false;
  554. }
  555. if (need_config_box) {
  556. /*
  557. * Put up the initial config box, which will pass the provided
  558. * parameters (with conf updated) to new_session_window() when
  559. * (if) the user selects Open. Or it might close without
  560. * creating a session window, if the user selects Cancel. Or
  561. * it might just create the session window immediately if this
  562. * is a pterm-style app which doesn't have an initial config
  563. * box at all.
  564. */
  565. struct post_initial_config_box_ctx *ctx =
  566. snew(struct post_initial_config_box_ctx);
  567. ctx->conf = conf;
  568. ctx->geometry_string = geometry_string;
  569. initial_config_box(conf, post_initial_config_box, ctx);
  570. } else {
  571. /*
  572. * No initial config needed; just create the session window
  573. * now.
  574. */
  575. new_session_window(conf, geometry_string);
  576. }
  577. gtk_main();
  578. return 0;
  579. }