/* * gtkmain.c: the common main-program code between the straight-up * Unix PuTTY and pterm, which they do not share with the * multi-session gtkapp.c. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !GTK_CHECK_VERSION(3,0,0) #include #endif #if GTK_CHECK_VERSION(2,0,0) #include #endif #define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" #include "terminal.h" #include "gtkcompat.h" #include "gtkfont.h" #include "gtkmisc.h" #ifndef NOT_X_WINDOWS #include #include #include #include #include "x11misc.h" #endif static char *progname, **gtkargvstart; static int ngtkargs; static const char *app_name = "pterm"; char *x_get_default(const char *key) { #ifndef NOT_X_WINDOWS Display *disp; if ((disp = get_x11_display()) == NULL) return NULL; return XGetDefault(disp, app_name, key); #else return NULL; #endif } void fork_and_exec_self(int fd_to_close, ...) { /* * Re-execing ourself is not an exact science under Unix. I do * the best I can by using /proc/self/exe if available and by * assuming argv[0] can be found on $PATH if not. * * Note that we also have to reconstruct the elements of the * original argv which gtk swallowed, since the user wants the * new session to appear on the same X display as the old one. */ char **args; va_list ap; int i, n; int pid; /* * Collect the arguments with which to re-exec ourself. */ va_start(ap, fd_to_close); n = 2; /* progname and terminating NULL */ n += ngtkargs; while (va_arg(ap, char *) != NULL) n++; va_end(ap); args = snewn(n, char *); args[0] = progname; args[n-1] = NULL; for (i = 0; i < ngtkargs; i++) args[i+1] = gtkargvstart[i]; i++; va_start(ap, fd_to_close); while ((args[i++] = va_arg(ap, char *)) != NULL); va_end(ap); assert(i == n); /* * Do the double fork. */ pid = fork(); if (pid < 0) { perror("fork"); sfree(args); return; } if (pid == 0) { int pid2 = fork(); if (pid2 < 0) { perror("fork"); _exit(1); } else if (pid2 > 0) { /* * First child has successfully forked second child. My * Work Here Is Done. Note the use of _exit rather than * exit: the latter appears to cause destroy messages * to be sent to the X server. I suspect gtk uses * atexit. */ _exit(0); } /* * If we reach here, we are the second child, so we now * actually perform the exec. */ if (fd_to_close >= 0) close(fd_to_close); execv("/proc/self/exe", args); execvp(progname, args); perror("exec"); _exit(127); } else { int status; sfree(args); waitpid(pid, &status, 0); } } void launch_duplicate_session(Conf *conf) { /* * For this feature we must marshal conf and (possibly) pty_argv * into a byte stream, create a pipe, and send this byte stream * to the child through the pipe. */ int i, ret; strbuf *serialised; char option[80]; int pipefd[2]; if (pipe(pipefd) < 0) { perror("pipe"); return; } serialised = strbuf_new(); conf_serialise(BinarySink_UPCAST(serialised), conf); if (use_pty_argv && pty_argv) for (i = 0; pty_argv[i]; i++) put_asciz(serialised, pty_argv[i]); sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len); noncloexec(pipefd[0]); fork_and_exec_self(pipefd[1], option, NULL); close(pipefd[0]); i = ret = 0; while (i < serialised->len && (ret = write(pipefd[1], serialised->s + i, serialised->len - i)) > 0) i += ret; if (ret < 0) perror("write to pipe"); close(pipefd[1]); strbuf_free(serialised); } void launch_new_session(void) { fork_and_exec_self(-1, NULL); } void launch_saved_session(const char *str) { fork_and_exec_self(-1, "-load", str, NULL); } int read_dupsession_data(Conf *conf, char *arg) { int fd, i, ret, size; char *data; BinarySource src[1]; if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) { fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg); exit(1); } data = snewn(size, char); i = ret = 0; while (i < size && (ret = read(fd, data + i, size - i)) > 0) i += ret; if (ret < 0) { perror("read from pipe"); exit(1); } else if (i < size) { fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n", appname); exit(1); } BinarySource_BARE_INIT(src, data, size); if (!conf_deserialise(conf, src)) { fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); exit(1); } if (use_pty_argv) { int pty_argc = 0; size_t argv_startpos = src->pos; while (get_asciz(src), !get_err(src)) pty_argc++; src->err = BSE_NO_ERROR; if (pty_argc > 0) { src->pos = argv_startpos; pty_argv = snewn(pty_argc + 1, char *); pty_argv[pty_argc] = NULL; for (i = 0; i < pty_argc; i++) pty_argv[i] = dupstr(get_asciz(src)); } } if (get_err(src) || get_avail(src) > 0) { fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); exit(1); } sfree(data); return 0; } static void help(FILE *fp) { if(fprintf(fp, "pterm option summary:\n" "\n" " --display DISPLAY Specify X display to use (note '--')\n" " -name PREFIX Prefix when looking up resources (default: pterm)\n" " -fn FONT Normal text font\n" " -fb FONT Bold text font\n" " -geometry GEOMETRY Position and size of window (size in characters)\n" " -sl LINES Number of lines of scrollback\n" " -fg COLOUR, -bg COLOUR Foreground/background colour\n" " -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n" " -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n" " -T TITLE Window title\n" " -ut, +ut Do(default) or do not update utmp\n" " -ls, +ls Do(default) or do not make shell a login shell\n" " -sb, +sb Do(default) or do not display a scrollbar\n" " -log PATH, -sessionlog PATH Log all output to a file\n" " -nethack Map numeric keypad to hjklyubn direction keys\n" " -xrm RESOURCE-STRING Set an X resource\n" " -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n" ) < 0 || fflush(fp) < 0) { perror("output error"); exit(1); } } static void version(FILE *fp) { char *buildinfo_text = buildinfo("\n"); if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || fflush(fp) < 0) { perror("output error"); exit(1); } sfree(buildinfo_text); } static const char *geometry_string; void cmdline_error(const char *p, ...) { va_list ap; fprintf(stderr, "%s: ", appname); va_start(ap, p); vfprintf(stderr, p, ap); va_end(ap); fputc('\n', stderr); exit(1); } void window_setup_error(const char *errmsg) { fprintf(stderr, "%s: %s\n", appname, errmsg); exit(1); } bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) { bool err = false; char *val; /* * Macros to make argument handling easier. * * Note that because they need to call `continue', they cannot be * contained in the usual do {...} while (0) wrapper to make them * syntactically single statements. I use the alternative if (1) * {...} else ((void)0). */ #define EXPECTS_ARG if (1) { \ if (--argc <= 0) { \ err = true; \ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ continue; \ } else \ val = *++argv; \ } else ((void)0) #define SECOND_PASS_ONLY if (1) { \ if (!do_everything) \ continue; \ } else ((void)0) while (--argc > 0) { const char *p = *++argv; int ret; /* * Shameless cheating. Debian requires all X terminal * emulators to support `-T title'; but * cmdline_process_param will eat -T (it means no-pty) and * complain that pterm doesn't support it. So, in pterm * only, we convert -T into -title. */ if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) && !strcmp(p, "-T")) p = "-title"; ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), do_everything ? 1 : -1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { --argc, ++argv; /* skip next argument */ continue; } else if (ret == 1) { continue; } if (!strcmp(p, "-fn") || !strcmp(p, "-font")) { FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; fs = fontspec_new(val); conf_set_fontspec(conf, CONF_font, fs); fontspec_free(fs); } else if (!strcmp(p, "-fb")) { FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; fs = fontspec_new(val); conf_set_fontspec(conf, CONF_boldfont, fs); fontspec_free(fs); } else if (!strcmp(p, "-fw")) { FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; fs = fontspec_new(val); conf_set_fontspec(conf, CONF_widefont, fs); fontspec_free(fs); } else if (!strcmp(p, "-fwb")) { FontSpec *fs; EXPECTS_ARG; SECOND_PASS_ONLY; fs = fontspec_new(val); conf_set_fontspec(conf, CONF_wideboldfont, fs); fontspec_free(fs); } else if (!strcmp(p, "-cs")) { EXPECTS_ARG; SECOND_PASS_ONLY; conf_set_str(conf, CONF_line_codepage, val); } else if (!strcmp(p, "-geometry")) { EXPECTS_ARG; SECOND_PASS_ONLY; geometry_string = val; } else if (!strcmp(p, "-sl")) { EXPECTS_ARG; SECOND_PASS_ONLY; conf_set_int(conf, CONF_savelines, atoi(val)); } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { EXPECTS_ARG; SECOND_PASS_ONLY; { #if GTK_CHECK_VERSION(3,0,0) GdkRGBA rgba; bool success = gdk_rgba_parse(&rgba, val); #else GdkColor col; bool success = gdk_color_parse(val, &col); #endif if (!success) { err = true; fprintf(stderr, "%s: unable to parse colour \"%s\"\n", appname, val); } else { #if GTK_CHECK_VERSION(3,0,0) int r = rgba.red * 255; int g = rgba.green * 255; int b = rgba.blue * 255; #else int r = col.red / 256; int g = col.green / 256; int b = col.blue / 256; #endif int index; index = (!strcmp(p, "-fg") ? 0 : !strcmp(p, "-bg") ? 2 : !strcmp(p, "-bfg") ? 1 : !strcmp(p, "-bbg") ? 3 : !strcmp(p, "-cfg") ? 4 : !strcmp(p, "-cbg") ? 5 : -1); assert(index != -1); conf_set_int_int(conf, CONF_colours, index*3+0, r); conf_set_int_int(conf, CONF_colours, index*3+1, g); conf_set_int_int(conf, CONF_colours, index*3+2, b); } } } else if (use_pty_argv && !strcmp(p, "-e")) { /* This option swallows all further arguments. */ if (!do_everything) break; if (--argc > 0) { int i; pty_argv = snewn(argc+1, char *); ++argv; for (i = 0; i < argc; i++) pty_argv[i] = argv[i]; pty_argv[argc] = NULL; break; /* finished command-line processing */ } else err = true, fprintf(stderr, "%s: -e expects an argument\n", appname); } else if (!strcmp(p, "-title")) { EXPECTS_ARG; SECOND_PASS_ONLY; conf_set_str(conf, CONF_wintitle, val); } else if (!strcmp(p, "-log")) { Filename *fn; EXPECTS_ARG; SECOND_PASS_ONLY; fn = filename_from_str(val); conf_set_filename(conf, CONF_logfilename, fn); conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); filename_free(fn); } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_stamp_utmp, false); } else if (!strcmp(p, "-ut")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_stamp_utmp, true); } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_login_shell, false); } else if (!strcmp(p, "-ls")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_login_shell, true); } else if (!strcmp(p, "-nethack")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_nethack_keypad, true); } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_scrollbar, false); } else if (!strcmp(p, "-sb")) { SECOND_PASS_ONLY; conf_set_bool(conf, CONF_scrollbar, true); } else if (!strcmp(p, "-name")) { EXPECTS_ARG; app_name = val; } else if (!strcmp(p, "-xrm")) { EXPECTS_ARG; provide_xrm_string(val, appname); } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { help(stdout); exit(0); } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { version(stdout); exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); exit(1); } else if (p[0] != '-') { /* Non-option arguments not handled by cmdline.c are errors. */ if (do_everything) { err = true; fprintf(stderr, "%s: unexpected non-option argument '%s'\n", appname, p); } } else { err = true; fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); } } return err; } GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return gtk_window_new(GTK_WINDOW_TOPLEVEL); } const bool buildinfo_gtk_relevant = true; struct post_initial_config_box_ctx { Conf *conf; const char *geometry_string; }; static void post_initial_config_box(void *vctx, int result) { struct post_initial_config_box_ctx ctx = *(struct post_initial_config_box_ctx *)vctx; sfree(vctx); if (result > 0) { new_session_window(ctx.conf, ctx.geometry_string); } else { /* In this main(), which only runs one session in total, a * negative result from the initial config box means we simply * terminate. */ conf_free(ctx.conf); gtk_main_quit(); } } void session_window_closed(void) { gtk_main_quit(); } int main(int argc, char **argv) { Conf *conf; bool need_config_box; setlocale(LC_CTYPE, ""); /* Call the function in ux{putty,pterm}.c to do app-type * specific setup */ setup(true); /* true means we are a one-session process */ progname = argv[0]; /* * Copy the original argv before letting gtk_init fiddle with * it. It will be required later. */ { int i, oldargc; gtkargvstart = snewn(argc-1, char *); for (i = 1; i < argc; i++) gtkargvstart[i-1] = dupstr(argv[i]); oldargc = argc; gtk_init(&argc, &argv); ngtkargs = oldargc - argc; } conf = conf_new(); gtkcomm_setup(); /* * Block SIGPIPE: if we attempt Duplicate Session or similar and * it falls over in some way, we certainly don't want SIGPIPE * terminating the main pterm/PuTTY. However, we'll have to * unblock it again when pterm forks. */ block_signal(SIGPIPE, true); if (argc > 1 && !strncmp(argv[1], "---", 3)) { read_dupsession_data(conf, argv[1]); /* Splatter this argument so it doesn't clutter a ps listing */ smemclr(argv[1], strlen(argv[1])); assert(!dup_check_launchable || conf_launchable(conf)); need_config_box = false; } else { if (do_cmdline(argc, argv, false, conf)) exit(1); /* pre-defaults pass to get -class */ do_defaults(NULL, conf); if (do_cmdline(argc, argv, true, conf)) exit(1); /* post-defaults, do everything */ cmdline_run_saved(conf); if (cmdline_tooltype & TOOLTYPE_HOST_ARG) need_config_box = !cmdline_host_ok(conf); else need_config_box = false; } if (need_config_box) { /* * Put up the initial config box, which will pass the provided * parameters (with conf updated) to new_session_window() when * (if) the user selects Open. Or it might close without * creating a session window, if the user selects Cancel. Or * it might just create the session window immediately if this * is a pterm-style app which doesn't have an initial config * box at all. */ struct post_initial_config_box_ctx *ctx = snew(struct post_initial_config_box_ctx); ctx->conf = conf; ctx->geometry_string = geometry_string; initial_config_box(conf, post_initial_config_box, ctx); } else { /* * No initial config needed; just create the session window * now. */ new_session_window(conf, geometry_string); } gtk_main(); return 0; }