123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- /*
- * main-gtk-simple.c: the common main-program code between the
- * straight-up Unix PuTTY and pterm, which they do not share with the
- * multi-session main-gtk-application.c.
- */
- #define _GNU_SOURCE
- #include <string.h>
- #include <assert.h>
- #include <stdlib.h>
- #include <string.h>
- #include <signal.h>
- #include <stdio.h>
- #include <time.h>
- #include <errno.h>
- #include <locale.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <gtk/gtk.h>
- #if !GTK_CHECK_VERSION(3,0,0)
- #include <gdk/gdkkeysyms.h>
- #endif
- #if GTK_CHECK_VERSION(2,0,0)
- #include <gtk/gtkimmodule.h>
- #endif
- #define MAY_REFER_TO_GTK_IN_HEADERS
- #include "putty.h"
- #include "terminal.h"
- #include "gtkcompat.h"
- #include "unifont.h"
- #include "gtkmisc.h"
- #ifndef NOT_X_WINDOWS
- #include <gdk/gdkx.h>
- #include <X11/Xlib.h>
- #include <X11/Xutil.h>
- #include <X11/Xatom.h>
- #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 (has_ca_config_box &&
- (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") ||
- !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) {
- show_ca_config_box_synchronously();
- exit(0);
- } 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;
- }
|