main-gtk-simple.c 19 KB

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