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.
20 #include <sys/types.h>
23 #if !GTK_CHECK_VERSION(3,0,0)
24 #include <gdk/gdkkeysyms.h>
27 #if GTK_CHECK_VERSION(2,0,0)
28 #include <gtk/gtkimmodule.h>
31 #define MAY_REFER_TO_GTK_IN_HEADERS
35 #include "gtkcompat.h"
42 #include <X11/Xutil.h>
43 #include <X11/Xatom.h>
46 static char *progname, **gtkargvstart;
49 extern char **pty_argv; /* declared in pty.c */
50 extern int use_pty_argv;
52 static const char *app_name = "pterm";
54 char *x_get_default(const char *key)
57 return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
64 void fork_and_exec_self(int fd_to_close, ...)
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.
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.
81 * Collect the arguments with which to re-exec ourself.
83 va_start(ap, fd_to_close);
84 n = 2; /* progname and terminating NULL */
86 while (va_arg(ap, char *) != NULL)
90 args = snewn(n, char *);
93 for (i = 0; i < ngtkargs; i++)
94 args[i+1] = gtkargvstart[i];
97 va_start(ap, fd_to_close);
98 while ((args[i++] = va_arg(ap, char *)) != NULL);
104 * Do the double fork.
118 } else if (pid2 > 0) {
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
130 * If we reach here, we are the second child, so we now
131 * actually perform the exec.
133 if (fd_to_close >= 0)
136 execv("/proc/self/exe", args);
137 execvp(progname, args);
144 waitpid(pid, &status, 0);
149 void launch_duplicate_session(Conf *conf)
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.
156 int i, ret, sersize, size;
161 if (pipe(pipefd) < 0) {
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;
172 data = snewn(size, char);
173 conf_serialise(conf, data);
174 if (use_pty_argv && pty_argv) {
176 for (i = 0; pty_argv[i]; i++) {
177 strcpy(data + p, pty_argv[i]);
178 p += strlen(pty_argv[i]) + 1;
183 sprintf(option, "---[%d,%d]", pipefd[0], size);
184 noncloexec(pipefd[0]);
185 fork_and_exec_self(pipefd[1], option, NULL);
189 while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
192 perror("write to pipe");
197 void launch_new_session(void)
199 fork_and_exec_self(-1, NULL);
202 void launch_saved_session(const char *str)
204 fork_and_exec_self(-1, "-load", str, NULL);
207 int read_dupsession_data(Conf *conf, char *arg)
209 int fd, i, ret, size, size_used;
212 if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
213 fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
217 data = snewn(size, char);
219 while (i < size && (ret = read(fd, data + i, size - i)) > 0)
222 perror("read from pipe");
224 } else if (i < size) {
225 fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
230 size_used = conf_deserialise(conf, data, size);
231 if (use_pty_argv && size > size_used) {
235 while (i < size && data[i]) i++;
237 fprintf(stderr, "%s: malformed Duplicate Session data\n",
244 pty_argv = snewn(n+1, char *);
250 while (i < size && data[i]) i++;
253 pty_argv[n++] = dupstr(p);
262 static void help(FILE *fp) {
264 "pterm option summary:\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");
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 ||
293 perror("output error");
296 sfree(buildinfo_text);
299 static struct gui_data *the_inst;
301 static const char *geometry_string;
303 int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
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
317 #define EXPECTS_ARG { \
320 fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
325 #define SECOND_PASS_ONLY { if (!do_everything) continue; }
328 const char *p = *++argv;
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.
338 if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
342 ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
343 do_everything ? 1 : -1, conf);
346 cmdline_error("option \"%s\" requires an argument", p);
347 } else if (ret == 2) {
348 --argc, ++argv; /* skip next argument */
350 } else if (ret == 1) {
354 if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
358 fs = fontspec_new(val);
359 conf_set_fontspec(conf, CONF_font, fs);
362 } else if (!strcmp(p, "-fb")) {
366 fs = fontspec_new(val);
367 conf_set_fontspec(conf, CONF_boldfont, fs);
370 } else if (!strcmp(p, "-fw")) {
374 fs = fontspec_new(val);
375 conf_set_fontspec(conf, CONF_widefont, fs);
378 } else if (!strcmp(p, "-fwb")) {
382 fs = fontspec_new(val);
383 conf_set_fontspec(conf, CONF_wideboldfont, fs);
386 } else if (!strcmp(p, "-cs")) {
389 conf_set_str(conf, CONF_line_codepage, val);
391 } else if (!strcmp(p, "-geometry")) {
394 geometry_string = val;
395 } else if (!strcmp(p, "-sl")) {
398 conf_set_int(conf, CONF_savelines, atoi(val));
400 } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
401 !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
402 !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
407 #if GTK_CHECK_VERSION(3,0,0)
409 int success = gdk_rgba_parse(&rgba, val);
412 int success = gdk_color_parse(val, &col);
417 fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
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;
425 int r = col.red / 256;
426 int g = col.green / 256;
427 int b = col.blue / 256;
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);
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);
445 } else if (use_pty_argv && !strcmp(p, "-e")) {
446 /* This option swallows all further arguments. */
452 pty_argv = snewn(argc+1, char *);
454 for (i = 0; i < argc; i++)
455 pty_argv[i] = argv[i];
456 pty_argv[argc] = NULL;
457 break; /* finished command-line processing */
459 err = 1, fprintf(stderr, "%s: -e expects an argument\n",
462 } else if (!strcmp(p, "-title")) {
465 conf_set_str(conf, CONF_wintitle, val);
467 } else if (!strcmp(p, "-log")) {
471 fn = filename_from_str(val);
472 conf_set_filename(conf, CONF_logfilename, fn);
473 conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
476 } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
478 conf_set_int(conf, CONF_stamp_utmp, 0);
480 } else if (!strcmp(p, "-ut")) {
482 conf_set_int(conf, CONF_stamp_utmp, 1);
484 } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
486 conf_set_int(conf, CONF_login_shell, 0);
488 } else if (!strcmp(p, "-ls")) {
490 conf_set_int(conf, CONF_login_shell, 1);
492 } else if (!strcmp(p, "-nethack")) {
494 conf_set_int(conf, CONF_nethack_keypad, 1);
496 } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
498 conf_set_int(conf, CONF_scrollbar, 0);
500 } else if (!strcmp(p, "-sb")) {
502 conf_set_int(conf, CONF_scrollbar, 1);
504 } else if (!strcmp(p, "-name")) {
508 } else if (!strcmp(p, "-xrm")) {
510 provide_xrm_string(val);
512 } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
516 } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
520 } else if (!strcmp(p, "-pgpfp")) {
524 } else if(p[0] != '-' && (!do_everything ||
525 process_nonoption_arg(p, conf,
531 fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
538 GtkWidget *make_gtk_toplevel_window(void *frontend)
540 return gtk_window_new(GTK_WINDOW_TOPLEVEL);
543 extern int cfgbox(Conf *conf);
545 int main(int argc, char **argv)
550 setlocale(LC_CTYPE, "");
553 /* Call the function in ux{putty,pterm}.c to do app-type
555 extern void setup(int);
556 setup(TRUE); /* TRUE means we are a one-session process */
562 * Copy the original argv before letting gtk_init fiddle with
563 * it. It will be required later.
567 gtkargvstart = snewn(argc-1, char *);
568 for (i = 1; i < argc; i++)
569 gtkargvstart[i-1] = dupstr(argv[i]);
571 gtk_init(&argc, &argv);
572 ngtkargs = oldargc - argc;
580 * Block SIGPIPE: if we attempt Duplicate Session or similar and
581 * it falls over in some way, we certainly don't want SIGPIPE
582 * terminating the main pterm/PuTTY. However, we'll have to
583 * unblock it again when pterm forks.
585 block_signal(SIGPIPE, 1);
587 if (argc > 1 && !strncmp(argv[1], "---", 3)) {
588 extern const int dup_check_launchable;
590 read_dupsession_data(conf, argv[1]);
591 /* Splatter this argument so it doesn't clutter a ps listing */
592 smemclr(argv[1], strlen(argv[1]));
594 assert(!dup_check_launchable || conf_launchable(conf));
595 need_config_box = FALSE;
597 /* By default, we bring up the config dialog, rather than launching
598 * a session. This gets set to TRUE if something happens to change
599 * that (e.g., a hostname is specified on the command-line). */
600 int allow_launch = FALSE;
601 if (do_cmdline(argc, argv, 0, &allow_launch, conf))
602 exit(1); /* pre-defaults pass to get -class */
603 do_defaults(NULL, conf);
604 if (do_cmdline(argc, argv, 1, &allow_launch, conf))
605 exit(1); /* post-defaults, do everything */
607 cmdline_run_saved(conf);
612 need_config_box = (!allow_launch || !conf_launchable(conf));
616 * Put up the config box.
618 if (need_config_box && !cfgbox(conf))
619 exit(0); /* config box hit Cancel */
622 * Create the main session window. We don't really need to keep
623 * the return value - the fact that it'll be linked from a zillion
624 * GTK and glib bits and bobs known to the main loop will be
625 * sufficient to make everything actually happen - but we stash it
626 * in a global variable anyway, so that it'll be easy to find in a
629 the_inst = new_session_window(conf, geometry_string);