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