]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkmain.c
Unix buildinfo: stop saying 'GTK' in pure CLI utilities.
[PuTTY.git] / unix / gtkmain.c
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 #endif
45
46 static char *progname, **gtkargvstart;
47 static int ngtkargs;
48
49 extern char **pty_argv;        /* declared in pty.c */
50 extern int use_pty_argv;
51
52 static const char *app_name = "pterm";
53
54 char *x_get_default(const char *key)
55 {
56 #ifndef NOT_X_WINDOWS
57     return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
58                        app_name, key);
59 #else
60     return NULL;
61 #endif
62 }
63
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
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, sersize, size;
157     char *data;
158     char option[80];
159     int pipefd[2];
160
161     if (pipe(pipefd) < 0) {
162         perror("pipe");
163         return;
164     }
165
166     size = sersize = conf_serialised_size(conf);
167     if (use_pty_argv && pty_argv) {
168         for (i = 0; pty_argv[i]; i++)
169             size += strlen(pty_argv[i]) + 1;
170     }
171
172     data = snewn(size, char);
173     conf_serialise(conf, data);
174     if (use_pty_argv && pty_argv) {
175         int p = sersize;
176         for (i = 0; pty_argv[i]; i++) {
177             strcpy(data + p, pty_argv[i]);
178             p += strlen(pty_argv[i]) + 1;
179         }
180         assert(p == size);
181     }
182
183     sprintf(option, "---[%d,%d]", pipefd[0], size);
184     noncloexec(pipefd[0]);
185     fork_and_exec_self(pipefd[1], option, NULL);
186     close(pipefd[0]);
187
188     i = ret = 0;
189     while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
190         i += ret;
191     if (ret < 0)
192         perror("write to pipe");
193     close(pipefd[1]);
194     sfree(data);
195 }
196
197 void launch_new_session(void)
198 {
199     fork_and_exec_self(-1, NULL);
200 }
201
202 void launch_saved_session(const char *str)
203 {
204     fork_and_exec_self(-1, "-load", str, NULL);
205 }
206
207 int read_dupsession_data(Conf *conf, char *arg)
208 {
209     int fd, i, ret, size, size_used;
210     char *data;
211
212     if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
213         fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
214         exit(1);
215     }
216
217     data = snewn(size, char);
218     i = ret = 0;
219     while (i < size && (ret = read(fd, data + i, size - i)) > 0)
220         i += ret;
221     if (ret < 0) {
222         perror("read from pipe");
223         exit(1);
224     } else if (i < size) {
225         fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
226                 appname);
227         exit(1);
228     }
229
230     size_used = conf_deserialise(conf, data, size);
231     if (use_pty_argv && size > size_used) {
232         int n = 0;
233         i = size_used;
234         while (i < size) {
235             while (i < size && data[i]) i++;
236             if (i >= size) {
237                 fprintf(stderr, "%s: malformed Duplicate Session data\n",
238                         appname);
239                 exit(1);
240             }
241             i++;
242             n++;
243         }
244         pty_argv = snewn(n+1, char *);
245         pty_argv[n] = NULL;
246         n = 0;
247         i = size_used;
248         while (i < size) {
249             char *p = data + i;
250             while (i < size && data[i]) i++;
251             assert(i < size);
252             i++;
253             pty_argv[n++] = dupstr(p);
254         }
255     }
256
257     sfree(data);
258
259     return 0;
260 }
261
262 static void help(FILE *fp) {
263     if(fprintf(fp,
264 "pterm option summary:\n"
265 "\n"
266 "  --display DISPLAY         Specify X display to use (note '--')\n"
267 "  -name PREFIX              Prefix when looking up resources (default: pterm)\n"
268 "  -fn FONT                  Normal text font\n"
269 "  -fb FONT                  Bold text font\n"
270 "  -geometry GEOMETRY        Position and size of window (size in characters)\n"
271 "  -sl LINES                 Number of lines of scrollback\n"
272 "  -fg COLOUR, -bg COLOUR    Foreground/background colour\n"
273 "  -bfg COLOUR, -bbg COLOUR  Foreground/background bold colour\n"
274 "  -cfg COLOUR, -bfg COLOUR  Foreground/background cursor colour\n"
275 "  -T TITLE                  Window title\n"
276 "  -ut, +ut                  Do(default) or do not update utmp\n"
277 "  -ls, +ls                  Do(default) or do not make shell a login shell\n"
278 "  -sb, +sb                  Do(default) or do not display a scrollbar\n"
279 "  -log PATH, -sessionlog PATH  Log all output to a file\n"
280 "  -nethack                  Map numeric keypad to hjklyubn direction keys\n"
281 "  -xrm RESOURCE-STRING      Set an X resource\n"
282 "  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"
283          ) < 0 || fflush(fp) < 0) {
284         perror("output error");
285         exit(1);
286     }
287 }
288
289 static void version(FILE *fp) {
290     char *buildinfo_text = buildinfo("\n");
291     if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
292        fflush(fp) < 0) {
293         perror("output error");
294         exit(1);
295     }
296     sfree(buildinfo_text);
297 }
298
299 static struct gui_data *the_inst;
300
301 static const char *geometry_string;
302
303 int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
304                Conf *conf)
305 {
306     int err = 0;
307     char *val;
308
309     /*
310      * Macros to make argument handling easier. Note that because
311      * they need to call `continue', they cannot be contained in
312      * the usual do {...} while (0) wrapper to make them
313      * syntactically single statements; hence it is not legal to
314      * use one of these macros as an unbraced statement between
315      * `if' and `else'.
316      */
317 #define EXPECTS_ARG { \
318     if (--argc <= 0) { \
319         err = 1; \
320         fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
321         continue; \
322     } else \
323         val = *++argv; \
324 }
325 #define SECOND_PASS_ONLY { if (!do_everything) continue; }
326
327     while (--argc > 0) {
328         const char *p = *++argv;
329         int ret;
330
331         /*
332          * Shameless cheating. Debian requires all X terminal
333          * emulators to support `-T title'; but
334          * cmdline_process_param will eat -T (it means no-pty) and
335          * complain that pterm doesn't support it. So, in pterm
336          * only, we convert -T into -title.
337          */
338         if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
339             !strcmp(p, "-T"))
340             p = "-title";
341
342         ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
343                                     do_everything ? 1 : -1, conf);
344
345         if (ret == -2) {
346             cmdline_error("option \"%s\" requires an argument", p);
347         } else if (ret == 2) {
348             --argc, ++argv;            /* skip next argument */
349             continue;
350         } else if (ret == 1) {
351             continue;
352         }
353
354         if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
355             FontSpec *fs;
356             EXPECTS_ARG;
357             SECOND_PASS_ONLY;
358             fs = fontspec_new(val);
359             conf_set_fontspec(conf, CONF_font, fs);
360             fontspec_free(fs);
361
362         } else if (!strcmp(p, "-fb")) {
363             FontSpec *fs;
364             EXPECTS_ARG;
365             SECOND_PASS_ONLY;
366             fs = fontspec_new(val);
367             conf_set_fontspec(conf, CONF_boldfont, fs);
368             fontspec_free(fs);
369
370         } else if (!strcmp(p, "-fw")) {
371             FontSpec *fs;
372             EXPECTS_ARG;
373             SECOND_PASS_ONLY;
374             fs = fontspec_new(val);
375             conf_set_fontspec(conf, CONF_widefont, fs);
376             fontspec_free(fs);
377
378         } else if (!strcmp(p, "-fwb")) {
379             FontSpec *fs;
380             EXPECTS_ARG;
381             SECOND_PASS_ONLY;
382             fs = fontspec_new(val);
383             conf_set_fontspec(conf, CONF_wideboldfont, fs);
384             fontspec_free(fs);
385
386         } else if (!strcmp(p, "-cs")) {
387             EXPECTS_ARG;
388             SECOND_PASS_ONLY;
389             conf_set_str(conf, CONF_line_codepage, val);
390
391         } else if (!strcmp(p, "-geometry")) {
392             EXPECTS_ARG;
393             SECOND_PASS_ONLY;
394             geometry_string = val;
395         } else if (!strcmp(p, "-sl")) {
396             EXPECTS_ARG;
397             SECOND_PASS_ONLY;
398             conf_set_int(conf, CONF_savelines, atoi(val));
399
400         } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
401                    !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
402                    !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
403             EXPECTS_ARG;
404             SECOND_PASS_ONLY;
405
406             {
407 #if GTK_CHECK_VERSION(3,0,0)
408                 GdkRGBA rgba;
409                 int success = gdk_rgba_parse(&rgba, val);
410 #else
411                 GdkColor col;
412                 int success = gdk_color_parse(val, &col);
413 #endif
414
415                 if (!success) {
416                     err = 1;
417                     fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
418                             appname, val);
419                 } else {
420 #if GTK_CHECK_VERSION(3,0,0)
421                     int r = rgba.red * 255;
422                     int g = rgba.green * 255;
423                     int b = rgba.blue * 255;
424 #else
425                     int r = col.red / 256;
426                     int g = col.green / 256;
427                     int b = col.blue / 256;
428 #endif
429
430                     int index;
431                     index = (!strcmp(p, "-fg") ? 0 :
432                              !strcmp(p, "-bg") ? 2 :
433                              !strcmp(p, "-bfg") ? 1 :
434                              !strcmp(p, "-bbg") ? 3 :
435                              !strcmp(p, "-cfg") ? 4 :
436                              !strcmp(p, "-cbg") ? 5 : -1);
437                     assert(index != -1);
438
439                     conf_set_int_int(conf, CONF_colours, index*3+0, r);
440                     conf_set_int_int(conf, CONF_colours, index*3+1, g);
441                     conf_set_int_int(conf, CONF_colours, index*3+2, b);
442                 }
443             }
444
445         } else if (use_pty_argv && !strcmp(p, "-e")) {
446             /* This option swallows all further arguments. */
447             if (!do_everything)
448                 break;
449
450             if (--argc > 0) {
451                 int i;
452                 pty_argv = snewn(argc+1, char *);
453                 ++argv;
454                 for (i = 0; i < argc; i++)
455                     pty_argv[i] = argv[i];
456                 pty_argv[argc] = NULL;
457                 break;                 /* finished command-line processing */
458             } else
459                 err = 1, fprintf(stderr, "%s: -e expects an argument\n",
460                                  appname);
461
462         } else if (!strcmp(p, "-title")) {
463             EXPECTS_ARG;
464             SECOND_PASS_ONLY;
465             conf_set_str(conf, CONF_wintitle, val);
466
467         } else if (!strcmp(p, "-log")) {
468             Filename *fn;
469             EXPECTS_ARG;
470             SECOND_PASS_ONLY;
471             fn = filename_from_str(val);
472             conf_set_filename(conf, CONF_logfilename, fn);
473             conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
474             filename_free(fn);
475
476         } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
477             SECOND_PASS_ONLY;
478             conf_set_int(conf, CONF_stamp_utmp, 0);
479
480         } else if (!strcmp(p, "-ut")) {
481             SECOND_PASS_ONLY;
482             conf_set_int(conf, CONF_stamp_utmp, 1);
483
484         } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
485             SECOND_PASS_ONLY;
486             conf_set_int(conf, CONF_login_shell, 0);
487
488         } else if (!strcmp(p, "-ls")) {
489             SECOND_PASS_ONLY;
490             conf_set_int(conf, CONF_login_shell, 1);
491
492         } else if (!strcmp(p, "-nethack")) {
493             SECOND_PASS_ONLY;
494             conf_set_int(conf, CONF_nethack_keypad, 1);
495
496         } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
497             SECOND_PASS_ONLY;
498             conf_set_int(conf, CONF_scrollbar, 0);
499
500         } else if (!strcmp(p, "-sb")) {
501             SECOND_PASS_ONLY;
502             conf_set_int(conf, CONF_scrollbar, 1);
503
504         } else if (!strcmp(p, "-name")) {
505             EXPECTS_ARG;
506             app_name = val;
507
508         } else if (!strcmp(p, "-xrm")) {
509             EXPECTS_ARG;
510             provide_xrm_string(val);
511
512         } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
513             help(stdout);
514             exit(0);
515
516         } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
517             version(stdout);
518             exit(0);
519
520         } else if (!strcmp(p, "-pgpfp")) {
521             pgp_fingerprints();
522             exit(1);
523
524         } else if(p[0] != '-' && (!do_everything ||
525                                   process_nonoption_arg(p, conf,
526                                                         allow_launch))) {
527             /* do nothing */
528
529         } else {
530             err = 1;
531             fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
532         }
533     }
534
535     return err;
536 }
537
538 GtkWidget *make_gtk_toplevel_window(void *frontend)
539 {
540     return gtk_window_new(GTK_WINDOW_TOPLEVEL);
541 }
542
543 extern int cfgbox(Conf *conf);
544
545 const int buildinfo_gtk_relevant = TRUE;
546
547 int main(int argc, char **argv)
548 {
549     Conf *conf;
550     int need_config_box;
551
552     setlocale(LC_CTYPE, "");
553
554     {
555         /* Call the function in ux{putty,pterm}.c to do app-type
556          * specific setup */
557         extern void setup(int);
558         setup(TRUE);     /* TRUE means we are a one-session process */
559     }
560
561     progname = argv[0];
562
563     /*
564      * Copy the original argv before letting gtk_init fiddle with
565      * it. It will be required later.
566      */
567     {
568         int i, oldargc;
569         gtkargvstart = snewn(argc-1, char *);
570         for (i = 1; i < argc; i++)
571             gtkargvstart[i-1] = dupstr(argv[i]);
572         oldargc = argc;
573         gtk_init(&argc, &argv);
574         ngtkargs = oldargc - argc;
575     }
576
577     conf = conf_new();
578
579     gtkcomm_setup();
580
581     /*
582      * Block SIGPIPE: if we attempt Duplicate Session or similar and
583      * it falls over in some way, we certainly don't want SIGPIPE
584      * terminating the main pterm/PuTTY. However, we'll have to
585      * unblock it again when pterm forks.
586      */
587     block_signal(SIGPIPE, 1);
588
589     if (argc > 1 && !strncmp(argv[1], "---", 3)) {
590         extern const int dup_check_launchable;
591
592         read_dupsession_data(conf, argv[1]);
593         /* Splatter this argument so it doesn't clutter a ps listing */
594         smemclr(argv[1], strlen(argv[1]));
595
596         assert(!dup_check_launchable || conf_launchable(conf));
597         need_config_box = FALSE;
598     } else {
599         /* By default, we bring up the config dialog, rather than launching
600          * a session. This gets set to TRUE if something happens to change
601          * that (e.g., a hostname is specified on the command-line). */
602         int allow_launch = FALSE;
603         if (do_cmdline(argc, argv, 0, &allow_launch, conf))
604             exit(1);                   /* pre-defaults pass to get -class */
605         do_defaults(NULL, conf);
606         if (do_cmdline(argc, argv, 1, &allow_launch, conf))
607             exit(1);                   /* post-defaults, do everything */
608
609         cmdline_run_saved(conf);
610
611         if (loaded_session)
612             allow_launch = TRUE;
613
614         need_config_box = (!allow_launch || !conf_launchable(conf));
615     }
616
617     /*
618      * Put up the config box.
619      */
620     if (need_config_box && !cfgbox(conf))
621         exit(0);                       /* config box hit Cancel */
622
623     /*
624      * Create the main session window. We don't really need to keep
625      * the return value - the fact that it'll be linked from a zillion
626      * GTK and glib bits and bobs known to the main loop will be
627      * sufficient to make everything actually happen - but we stash it
628      * in a global variable anyway, so that it'll be easy to find in a
629      * debugger.
630      */
631     the_inst = new_session_window(conf, geometry_string);
632
633     gtk_main();
634
635     return 0;
636 }