gtkmain.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. /*
  2. * gtkmain.c: the common main-program code between the straight-up
  3. * Unix PuTTY and pterm, which they do not share with the
  4. * multi-session gtkapp.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 "gtkfont.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. #endif
  39. static char *progname, **gtkargvstart;
  40. static int ngtkargs;
  41. extern char **pty_argv; /* declared in pty.c */
  42. extern int use_pty_argv;
  43. static const char *app_name = "pterm";
  44. char *x_get_default(const char *key)
  45. {
  46. #ifndef NOT_X_WINDOWS
  47. return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
  48. app_name, key);
  49. #else
  50. return NULL;
  51. #endif
  52. }
  53. void fork_and_exec_self(int fd_to_close, ...)
  54. {
  55. /*
  56. * Re-execing ourself is not an exact science under Unix. I do
  57. * the best I can by using /proc/self/exe if available and by
  58. * assuming argv[0] can be found on $PATH if not.
  59. *
  60. * Note that we also have to reconstruct the elements of the
  61. * original argv which gtk swallowed, since the user wants the
  62. * new session to appear on the same X display as the old one.
  63. */
  64. char **args;
  65. va_list ap;
  66. int i, n;
  67. int pid;
  68. /*
  69. * Collect the arguments with which to re-exec ourself.
  70. */
  71. va_start(ap, fd_to_close);
  72. n = 2; /* progname and terminating NULL */
  73. n += ngtkargs;
  74. while (va_arg(ap, char *) != NULL)
  75. n++;
  76. va_end(ap);
  77. args = snewn(n, char *);
  78. args[0] = progname;
  79. args[n-1] = NULL;
  80. for (i = 0; i < ngtkargs; i++)
  81. args[i+1] = gtkargvstart[i];
  82. i++;
  83. va_start(ap, fd_to_close);
  84. while ((args[i++] = va_arg(ap, char *)) != NULL);
  85. va_end(ap);
  86. assert(i == n);
  87. /*
  88. * Do the double fork.
  89. */
  90. pid = fork();
  91. if (pid < 0) {
  92. perror("fork");
  93. sfree(args);
  94. return;
  95. }
  96. if (pid == 0) {
  97. int pid2 = fork();
  98. if (pid2 < 0) {
  99. perror("fork");
  100. _exit(1);
  101. } else if (pid2 > 0) {
  102. /*
  103. * First child has successfully forked second child. My
  104. * Work Here Is Done. Note the use of _exit rather than
  105. * exit: the latter appears to cause destroy messages
  106. * to be sent to the X server. I suspect gtk uses
  107. * atexit.
  108. */
  109. _exit(0);
  110. }
  111. /*
  112. * If we reach here, we are the second child, so we now
  113. * actually perform the exec.
  114. */
  115. if (fd_to_close >= 0)
  116. close(fd_to_close);
  117. execv("/proc/self/exe", args);
  118. execvp(progname, args);
  119. perror("exec");
  120. _exit(127);
  121. } else {
  122. int status;
  123. sfree(args);
  124. waitpid(pid, &status, 0);
  125. }
  126. }
  127. void launch_duplicate_session(Conf *conf)
  128. {
  129. /*
  130. * For this feature we must marshal conf and (possibly) pty_argv
  131. * into a byte stream, create a pipe, and send this byte stream
  132. * to the child through the pipe.
  133. */
  134. int i, ret, sersize, size;
  135. char *data;
  136. char option[80];
  137. int pipefd[2];
  138. if (pipe(pipefd) < 0) {
  139. perror("pipe");
  140. return;
  141. }
  142. size = sersize = conf_serialised_size(conf);
  143. if (use_pty_argv && pty_argv) {
  144. for (i = 0; pty_argv[i]; i++)
  145. size += strlen(pty_argv[i]) + 1;
  146. }
  147. data = snewn(size, char);
  148. conf_serialise(conf, data);
  149. if (use_pty_argv && pty_argv) {
  150. int p = sersize;
  151. for (i = 0; pty_argv[i]; i++) {
  152. strcpy(data + p, pty_argv[i]);
  153. p += strlen(pty_argv[i]) + 1;
  154. }
  155. assert(p == size);
  156. }
  157. sprintf(option, "---[%d,%d]", pipefd[0], size);
  158. noncloexec(pipefd[0]);
  159. fork_and_exec_self(pipefd[1], option, NULL);
  160. close(pipefd[0]);
  161. i = ret = 0;
  162. while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
  163. i += ret;
  164. if (ret < 0)
  165. perror("write to pipe");
  166. close(pipefd[1]);
  167. sfree(data);
  168. }
  169. void launch_new_session(void)
  170. {
  171. fork_and_exec_self(-1, NULL);
  172. }
  173. void launch_saved_session(const char *str)
  174. {
  175. fork_and_exec_self(-1, "-load", str, NULL);
  176. }
  177. int read_dupsession_data(Conf *conf, char *arg)
  178. {
  179. int fd, i, ret, size, size_used;
  180. char *data;
  181. if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
  182. fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
  183. exit(1);
  184. }
  185. data = snewn(size, char);
  186. i = ret = 0;
  187. while (i < size && (ret = read(fd, data + i, size - i)) > 0)
  188. i += ret;
  189. if (ret < 0) {
  190. perror("read from pipe");
  191. exit(1);
  192. } else if (i < size) {
  193. fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
  194. appname);
  195. exit(1);
  196. }
  197. size_used = conf_deserialise(conf, data, size);
  198. if (use_pty_argv && size > size_used) {
  199. int n = 0;
  200. i = size_used;
  201. while (i < size) {
  202. while (i < size && data[i]) i++;
  203. if (i >= size) {
  204. fprintf(stderr, "%s: malformed Duplicate Session data\n",
  205. appname);
  206. exit(1);
  207. }
  208. i++;
  209. n++;
  210. }
  211. pty_argv = snewn(n+1, char *);
  212. pty_argv[n] = NULL;
  213. n = 0;
  214. i = size_used;
  215. while (i < size) {
  216. char *p = data + i;
  217. while (i < size && data[i]) i++;
  218. assert(i < size);
  219. i++;
  220. pty_argv[n++] = dupstr(p);
  221. }
  222. }
  223. sfree(data);
  224. return 0;
  225. }
  226. static void help(FILE *fp) {
  227. if(fprintf(fp,
  228. "pterm option summary:\n"
  229. "\n"
  230. " --display DISPLAY Specify X display to use (note '--')\n"
  231. " -name PREFIX Prefix when looking up resources (default: pterm)\n"
  232. " -fn FONT Normal text font\n"
  233. " -fb FONT Bold text font\n"
  234. " -geometry GEOMETRY Position and size of window (size in characters)\n"
  235. " -sl LINES Number of lines of scrollback\n"
  236. " -fg COLOUR, -bg COLOUR Foreground/background colour\n"
  237. " -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
  238. " -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
  239. " -T TITLE Window title\n"
  240. " -ut, +ut Do(default) or do not update utmp\n"
  241. " -ls, +ls Do(default) or do not make shell a login shell\n"
  242. " -sb, +sb Do(default) or do not display a scrollbar\n"
  243. " -log PATH, -sessionlog PATH Log all output to a file\n"
  244. " -nethack Map numeric keypad to hjklyubn direction keys\n"
  245. " -xrm RESOURCE-STRING Set an X resource\n"
  246. " -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
  247. ) < 0 || fflush(fp) < 0) {
  248. perror("output error");
  249. exit(1);
  250. }
  251. }
  252. static void version(FILE *fp) {
  253. if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) {
  254. perror("output error");
  255. exit(1);
  256. }
  257. }
  258. static struct gui_data *the_inst;
  259. static const char *geometry_string;
  260. int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
  261. Conf *conf)
  262. {
  263. int err = 0;
  264. char *val;
  265. /*
  266. * Macros to make argument handling easier. Note that because
  267. * they need to call `continue', they cannot be contained in
  268. * the usual do {...} while (0) wrapper to make them
  269. * syntactically single statements; hence it is not legal to
  270. * use one of these macros as an unbraced statement between
  271. * `if' and `else'.
  272. */
  273. #define EXPECTS_ARG { \
  274. if (--argc <= 0) { \
  275. err = 1; \
  276. fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
  277. continue; \
  278. } else \
  279. val = *++argv; \
  280. }
  281. #define SECOND_PASS_ONLY { if (!do_everything) continue; }
  282. while (--argc > 0) {
  283. const char *p = *++argv;
  284. int ret;
  285. /*
  286. * Shameless cheating. Debian requires all X terminal
  287. * emulators to support `-T title'; but
  288. * cmdline_process_param will eat -T (it means no-pty) and
  289. * complain that pterm doesn't support it. So, in pterm
  290. * only, we convert -T into -title.
  291. */
  292. if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
  293. !strcmp(p, "-T"))
  294. p = "-title";
  295. ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
  296. do_everything ? 1 : -1, conf);
  297. if (ret == -2) {
  298. cmdline_error("option \"%s\" requires an argument", p);
  299. } else if (ret == 2) {
  300. --argc, ++argv; /* skip next argument */
  301. continue;
  302. } else if (ret == 1) {
  303. continue;
  304. }
  305. if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
  306. FontSpec *fs;
  307. EXPECTS_ARG;
  308. SECOND_PASS_ONLY;
  309. fs = fontspec_new(val);
  310. conf_set_fontspec(conf, CONF_font, fs);
  311. fontspec_free(fs);
  312. } else if (!strcmp(p, "-fb")) {
  313. FontSpec *fs;
  314. EXPECTS_ARG;
  315. SECOND_PASS_ONLY;
  316. fs = fontspec_new(val);
  317. conf_set_fontspec(conf, CONF_boldfont, fs);
  318. fontspec_free(fs);
  319. } else if (!strcmp(p, "-fw")) {
  320. FontSpec *fs;
  321. EXPECTS_ARG;
  322. SECOND_PASS_ONLY;
  323. fs = fontspec_new(val);
  324. conf_set_fontspec(conf, CONF_widefont, fs);
  325. fontspec_free(fs);
  326. } else if (!strcmp(p, "-fwb")) {
  327. FontSpec *fs;
  328. EXPECTS_ARG;
  329. SECOND_PASS_ONLY;
  330. fs = fontspec_new(val);
  331. conf_set_fontspec(conf, CONF_wideboldfont, fs);
  332. fontspec_free(fs);
  333. } else if (!strcmp(p, "-cs")) {
  334. EXPECTS_ARG;
  335. SECOND_PASS_ONLY;
  336. conf_set_str(conf, CONF_line_codepage, val);
  337. } else if (!strcmp(p, "-geometry")) {
  338. EXPECTS_ARG;
  339. SECOND_PASS_ONLY;
  340. geometry_string = val;
  341. } else if (!strcmp(p, "-sl")) {
  342. EXPECTS_ARG;
  343. SECOND_PASS_ONLY;
  344. conf_set_int(conf, CONF_savelines, atoi(val));
  345. } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
  346. !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
  347. !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
  348. EXPECTS_ARG;
  349. SECOND_PASS_ONLY;
  350. {
  351. #if GTK_CHECK_VERSION(3,0,0)
  352. GdkRGBA rgba;
  353. int success = gdk_rgba_parse(&rgba, val);
  354. #else
  355. GdkColor col;
  356. int success = gdk_color_parse(val, &col);
  357. #endif
  358. if (!success) {
  359. err = 1;
  360. fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
  361. appname, val);
  362. } else {
  363. #if GTK_CHECK_VERSION(3,0,0)
  364. int r = rgba.red * 255;
  365. int g = rgba.green * 255;
  366. int b = rgba.blue * 255;
  367. #else
  368. int r = col.red / 256;
  369. int g = col.green / 256;
  370. int b = col.blue / 256;
  371. #endif
  372. int index;
  373. index = (!strcmp(p, "-fg") ? 0 :
  374. !strcmp(p, "-bg") ? 2 :
  375. !strcmp(p, "-bfg") ? 1 :
  376. !strcmp(p, "-bbg") ? 3 :
  377. !strcmp(p, "-cfg") ? 4 :
  378. !strcmp(p, "-cbg") ? 5 : -1);
  379. assert(index != -1);
  380. conf_set_int_int(conf, CONF_colours, index*3+0, r);
  381. conf_set_int_int(conf, CONF_colours, index*3+1, g);
  382. conf_set_int_int(conf, CONF_colours, index*3+2, b);
  383. }
  384. }
  385. } else if (use_pty_argv && !strcmp(p, "-e")) {
  386. /* This option swallows all further arguments. */
  387. if (!do_everything)
  388. break;
  389. if (--argc > 0) {
  390. int i;
  391. pty_argv = snewn(argc+1, char *);
  392. ++argv;
  393. for (i = 0; i < argc; i++)
  394. pty_argv[i] = argv[i];
  395. pty_argv[argc] = NULL;
  396. break; /* finished command-line processing */
  397. } else
  398. err = 1, fprintf(stderr, "%s: -e expects an argument\n",
  399. appname);
  400. } else if (!strcmp(p, "-title")) {
  401. EXPECTS_ARG;
  402. SECOND_PASS_ONLY;
  403. conf_set_str(conf, CONF_wintitle, val);
  404. } else if (!strcmp(p, "-log")) {
  405. Filename *fn;
  406. EXPECTS_ARG;
  407. SECOND_PASS_ONLY;
  408. fn = filename_from_str(val);
  409. conf_set_filename(conf, CONF_logfilename, fn);
  410. conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
  411. filename_free(fn);
  412. } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
  413. SECOND_PASS_ONLY;
  414. conf_set_int(conf, CONF_stamp_utmp, 0);
  415. } else if (!strcmp(p, "-ut")) {
  416. SECOND_PASS_ONLY;
  417. conf_set_int(conf, CONF_stamp_utmp, 1);
  418. } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
  419. SECOND_PASS_ONLY;
  420. conf_set_int(conf, CONF_login_shell, 0);
  421. } else if (!strcmp(p, "-ls")) {
  422. SECOND_PASS_ONLY;
  423. conf_set_int(conf, CONF_login_shell, 1);
  424. } else if (!strcmp(p, "-nethack")) {
  425. SECOND_PASS_ONLY;
  426. conf_set_int(conf, CONF_nethack_keypad, 1);
  427. } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
  428. SECOND_PASS_ONLY;
  429. conf_set_int(conf, CONF_scrollbar, 0);
  430. } else if (!strcmp(p, "-sb")) {
  431. SECOND_PASS_ONLY;
  432. conf_set_int(conf, CONF_scrollbar, 1);
  433. } else if (!strcmp(p, "-name")) {
  434. EXPECTS_ARG;
  435. app_name = val;
  436. } else if (!strcmp(p, "-xrm")) {
  437. EXPECTS_ARG;
  438. provide_xrm_string(val);
  439. } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
  440. help(stdout);
  441. exit(0);
  442. } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
  443. version(stdout);
  444. exit(0);
  445. } else if (!strcmp(p, "-pgpfp")) {
  446. pgp_fingerprints();
  447. exit(1);
  448. } else if(p[0] != '-' && (!do_everything ||
  449. process_nonoption_arg(p, conf,
  450. allow_launch))) {
  451. /* do nothing */
  452. } else {
  453. err = 1;
  454. fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
  455. }
  456. }
  457. return err;
  458. }
  459. GtkWidget *make_gtk_toplevel_window(void *frontend)
  460. {
  461. return gtk_window_new(GTK_WINDOW_TOPLEVEL);
  462. }
  463. extern int cfgbox(Conf *conf);
  464. int main(int argc, char **argv)
  465. {
  466. Conf *conf;
  467. int need_config_box;
  468. setlocale(LC_CTYPE, "");
  469. {
  470. /* Call the function in ux{putty,pterm}.c to do app-type
  471. * specific setup */
  472. extern void setup(int);
  473. setup(TRUE); /* TRUE means we are a one-session process */
  474. }
  475. progname = argv[0];
  476. /*
  477. * Copy the original argv before letting gtk_init fiddle with
  478. * it. It will be required later.
  479. */
  480. {
  481. int i, oldargc;
  482. gtkargvstart = snewn(argc-1, char *);
  483. for (i = 1; i < argc; i++)
  484. gtkargvstart[i-1] = dupstr(argv[i]);
  485. oldargc = argc;
  486. gtk_init(&argc, &argv);
  487. ngtkargs = oldargc - argc;
  488. }
  489. conf = conf_new();
  490. gtkcomm_setup();
  491. /*
  492. * Block SIGPIPE: if we attempt Duplicate Session or similar and
  493. * it falls over in some way, we certainly don't want SIGPIPE
  494. * terminating the main pterm/PuTTY. However, we'll have to
  495. * unblock it again when pterm forks.
  496. */
  497. block_signal(SIGPIPE, 1);
  498. if (argc > 1 && !strncmp(argv[1], "---", 3)) {
  499. extern const int dup_check_launchable;
  500. read_dupsession_data(conf, argv[1]);
  501. /* Splatter this argument so it doesn't clutter a ps listing */
  502. smemclr(argv[1], strlen(argv[1]));
  503. assert(!dup_check_launchable || conf_launchable(conf));
  504. need_config_box = FALSE;
  505. } else {
  506. /* By default, we bring up the config dialog, rather than launching
  507. * a session. This gets set to TRUE if something happens to change
  508. * that (e.g., a hostname is specified on the command-line). */
  509. int allow_launch = FALSE;
  510. if (do_cmdline(argc, argv, 0, &allow_launch, conf))
  511. exit(1); /* pre-defaults pass to get -class */
  512. do_defaults(NULL, conf);
  513. if (do_cmdline(argc, argv, 1, &allow_launch, conf))
  514. exit(1); /* post-defaults, do everything */
  515. cmdline_run_saved(conf);
  516. if (loaded_session)
  517. allow_launch = TRUE;
  518. need_config_box = (!allow_launch || !conf_launchable(conf));
  519. }
  520. /*
  521. * Put up the config box.
  522. */
  523. if (need_config_box && !cfgbox(conf))
  524. exit(0); /* config box hit Cancel */
  525. /*
  526. * Create the main session window. We don't really need to keep
  527. * the return value - the fact that it'll be linked from a zillion
  528. * GTK and glib bits and bobs known to the main loop will be
  529. * sufficient to make everything actually happen - but we stash it
  530. * in a global variable anyway, so that it'll be easy to find in a
  531. * debugger.
  532. */
  533. the_inst = new_session_window(conf, geometry_string);
  534. gtk_main();
  535. return 0;
  536. }