diff --git a/Recipe b/Recipe index b13b12f4..81472f37 100644 --- a/Recipe +++ b/Recipe @@ -217,7 +217,8 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint # Same thing on Unix. UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs -GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym x11misc +GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym + + x11misc gtkcomm OSXTERM = UXTERM osxwin osxdlg osxctrls # Non-SSH back ends (putty, puttytel, plink). @@ -294,13 +295,13 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss + + nogss gtkmain putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg + + xpmpucfg gtkmain puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg - + nogss + + nogss gtkmain plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal + ux_x11 noterm diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c new file mode 100644 index 00000000..28884654 --- /dev/null +++ b/unix/gtkcomm.c @@ -0,0 +1,272 @@ +/* + * gtkcomm.c: machinery in the GTK front end which is common to all + * programs that run a session in a terminal window, and also common + * across all _sessions_ rather than specific to one session. (Timers, + * uxsel etc.) + */ + +#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 +#endif + +#define CAT2(x,y) x ## y +#define CAT(x,y) CAT2(x,y) +#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)} + +#if GTK_CHECK_VERSION(2,0,0) +ASSERT(sizeof(long) <= sizeof(gsize)); +#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l) +#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p) +#else /* Gtk 1.2 */ +ASSERT(sizeof(long) <= sizeof(gpointer)); +#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) +#define GPOINTER_TO_LONG(p) ((long)(p)) +#endif + +/* ---------------------------------------------------------------------- + * File descriptors and uxsel. + */ + +struct uxsel_id { +#if GTK_CHECK_VERSION(2,0,0) + GIOChannel *chan; + guint watch_id; +#else + int id; +#endif +}; + +#if GTK_CHECK_VERSION(2,0,0) +gboolean fd_input_func(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + int sourcefd = g_io_channel_unix_get_fd(source); + /* + * We must process exceptional notifications before ordinary + * readability ones, or we may go straight past the urgent + * marker. + */ + if (condition & G_IO_PRI) + select_result(sourcefd, 4); + if (condition & G_IO_IN) + select_result(sourcefd, 1); + if (condition & G_IO_OUT) + select_result(sourcefd, 2); + + return TRUE; +} +#else +void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) +{ + if (condition & GDK_INPUT_EXCEPTION) + select_result(sourcefd, 4); + if (condition & GDK_INPUT_READ) + select_result(sourcefd, 1); + if (condition & GDK_INPUT_WRITE) + select_result(sourcefd, 2); +} +#endif + +uxsel_id *uxsel_input_add(int fd, int rwx) { + uxsel_id *id = snew(uxsel_id); + +#if GTK_CHECK_VERSION(2,0,0) + int flags = 0; + if (rwx & 1) flags |= G_IO_IN; + if (rwx & 2) flags |= G_IO_OUT; + if (rwx & 4) flags |= G_IO_PRI; + id->chan = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(id->chan, NULL, NULL); + id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags, + fd_input_func, NULL, NULL); +#else + int flags = 0; + if (rwx & 1) flags |= GDK_INPUT_READ; + if (rwx & 2) flags |= GDK_INPUT_WRITE; + if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; + assert(flags); + id->id = gdk_input_add(fd, flags, fd_input_func, NULL); +#endif + + return id; +} + +void uxsel_input_remove(uxsel_id *id) { +#if GTK_CHECK_VERSION(2,0,0) + g_source_remove(id->watch_id); + g_io_channel_unref(id->chan); +#else + gdk_input_remove(id->id); +#endif + sfree(id); +} + +/* ---------------------------------------------------------------------- + * Timers. + */ + +static guint timer_id = 0; + +static gint timer_trigger(gpointer data) +{ + unsigned long now = GPOINTER_TO_LONG(data); + unsigned long next, then; + long ticks; + + /* + * Destroy the timer we got here on. + */ + if (timer_id) { + g_source_remove(timer_id); + timer_id = 0; + } + + /* + * run_timers() may cause a call to timer_change_notify, in which + * case a new timer will already have been set up and left in + * timer_id. If it hasn't, and run_timers reports that some timing + * still needs to be done, we do it ourselves. + */ + if (run_timers(now, &next) && !timer_id) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); + } + + /* + * Returning FALSE means 'don't call this timer again', which + * _should_ be redundant given that we removed it above, but just + * in case, return FALSE anyway. + */ + return FALSE; +} + +void timer_change_notify(unsigned long next) +{ + long ticks; + + if (timer_id) + g_source_remove(timer_id); + + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + + timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); +} + +/* ---------------------------------------------------------------------- + * Toplevel callbacks. + */ + +static guint toplevel_callback_idle_id; +static int idle_fn_scheduled; + +static void notify_toplevel_callback(void *); + +/* + * Replacement code for the gtk_quit_add() function, which GTK2 - in + * their unbounded wisdom - deprecated without providing any usable + * replacement, and which we were using to ensure that our idle + * function for toplevel callbacks was only run from the outermost + * gtk_main(). + * + * We must make sure that all our subsidiary calls to gtk_main() are + * followed by a call to post_main(), so that the idle function can be + * re-established when we end up back at the top level. + */ +void post_main(void) +{ + if (gtk_main_level() == 1) + notify_toplevel_callback(NULL); +} + +static gint idle_toplevel_callback_func(gpointer data) +{ + if (gtk_main_level() > 1) { + /* + * We don't run callbacks if we're in the middle of a + * subsidiary gtk_main. So unschedule this idle function; it + * will be rescheduled by post_main() when we come back up a + * level, which is the earliest we might actually do + * something. + */ + if (idle_fn_scheduled) { /* double-check, just in case */ + g_source_remove(toplevel_callback_idle_id); + idle_fn_scheduled = FALSE; + } + } else { + run_toplevel_callbacks(); + } + + /* + * If we've emptied our toplevel callback queue, unschedule + * ourself. Otherwise, leave ourselves pending so we'll be called + * again to deal with more callbacks after another round of the + * event loop. + */ + if (!toplevel_callback_pending() && idle_fn_scheduled) { + g_source_remove(toplevel_callback_idle_id); + idle_fn_scheduled = FALSE; + } + + return TRUE; +} + +static void notify_toplevel_callback(void *frontend) +{ + if (!idle_fn_scheduled) { + toplevel_callback_idle_id = + g_idle_add(idle_toplevel_callback_func, NULL); + idle_fn_scheduled = TRUE; + } +} + +/* ---------------------------------------------------------------------- + * Setup function. The real main program must call this. + */ + +void gtkcomm_setup(void) +{ + uxsel_init(); + request_callback_notifications(notify_toplevel_callback, NULL); +} diff --git a/unix/gtkmain.c b/unix/gtkmain.c new file mode 100644 index 00000000..5e8b9fe2 --- /dev/null +++ b/unix/gtkmain.c @@ -0,0 +1,617 @@ +/* + * 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 +#endif + +static char *progname, **gtkargvstart; +static int ngtkargs; + +extern char **pty_argv; /* declared in pty.c */ +extern int use_pty_argv; + +static const char *app_name = "pterm"; + +char *x_get_default(const char *key) +{ +#ifndef NOT_X_WINDOWS + return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + 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, sersize, size; + char *data; + char option[80]; + int pipefd[2]; + + if (pipe(pipefd) < 0) { + perror("pipe"); + return; + } + + size = sersize = conf_serialised_size(conf); + if (use_pty_argv && pty_argv) { + for (i = 0; pty_argv[i]; i++) + size += strlen(pty_argv[i]) + 1; + } + + data = snewn(size, char); + conf_serialise(conf, data); + if (use_pty_argv && pty_argv) { + int p = sersize; + for (i = 0; pty_argv[i]; i++) { + strcpy(data + p, pty_argv[i]); + p += strlen(pty_argv[i]) + 1; + } + assert(p == size); + } + + sprintf(option, "---[%d,%d]", pipefd[0], size); + noncloexec(pipefd[0]); + fork_and_exec_self(pipefd[1], option, NULL); + close(pipefd[0]); + + i = ret = 0; + while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0) + i += ret; + if (ret < 0) + perror("write to pipe"); + close(pipefd[1]); + sfree(data); +} + +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, size_used; + char *data; + + 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); + } + + size_used = conf_deserialise(conf, data, size); + if (use_pty_argv && size > size_used) { + int n = 0; + i = size_used; + while (i < size) { + while (i < size && data[i]) i++; + if (i >= size) { + fprintf(stderr, "%s: malformed Duplicate Session data\n", + appname); + exit(1); + } + i++; + n++; + } + pty_argv = snewn(n+1, char *); + pty_argv[n] = NULL; + n = 0; + i = size_used; + while (i < size) { + char *p = data + i; + while (i < size && data[i]) i++; + assert(i < size); + i++; + pty_argv[n++] = dupstr(p); + } + } + + 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) { + if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) { + perror("output error"); + exit(1); + } +} + +static struct gui_data *the_inst; + +static const char *geometry_string; + +int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, + Conf *conf) +{ + int err = 0; + 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; hence it is not legal to + * use one of these macros as an unbraced statement between + * `if' and `else'. + */ +#define EXPECTS_ARG { \ + if (--argc <= 0) { \ + err = 1; \ + fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ + continue; \ + } else \ + val = *++argv; \ +} +#define SECOND_PASS_ONLY { if (!do_everything) continue; } + + 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; + int success = gdk_rgba_parse(&rgba, val); +#else + GdkColor col; + int success = gdk_color_parse(val, &col); +#endif + + if (!success) { + err = 1; + 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 = 1, 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_int(conf, CONF_stamp_utmp, 0); + + } else if (!strcmp(p, "-ut")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_stamp_utmp, 1); + + } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_login_shell, 0); + + } else if (!strcmp(p, "-ls")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_login_shell, 1); + + } else if (!strcmp(p, "-nethack")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_nethack_keypad, 1); + + } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_scrollbar, 0); + + } else if (!strcmp(p, "-sb")) { + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_scrollbar, 1); + + } else if (!strcmp(p, "-name")) { + EXPECTS_ARG; + app_name = val; + + } else if (!strcmp(p, "-xrm")) { + EXPECTS_ARG; + provide_xrm_string(val); + + } 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] != '-' && (!do_everything || + process_nonoption_arg(p, conf, + allow_launch))) { + /* do nothing */ + + } else { + err = 1; + fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); + } + } + + return err; +} + +extern int cfgbox(Conf *conf); + +int pt_main(int argc, char **argv) +{ + Conf *conf; + int need_config_box; + + setlocale(LC_CTYPE, ""); + + 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, 1); + + 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(conf_launchable(conf)); + need_config_box = FALSE; + } else { + /* By default, we bring up the config dialog, rather than launching + * a session. This gets set to TRUE if something happens to change + * that (e.g., a hostname is specified on the command-line). */ + int allow_launch = FALSE; + if (do_cmdline(argc, argv, 0, &allow_launch, conf)) + exit(1); /* pre-defaults pass to get -class */ + do_defaults(NULL, conf); + if (do_cmdline(argc, argv, 1, &allow_launch, conf)) + exit(1); /* post-defaults, do everything */ + + cmdline_run_saved(conf); + + if (loaded_session) + allow_launch = TRUE; + + need_config_box = (!allow_launch || !conf_launchable(conf)); + } + + /* + * Put up the config box. + */ + if (need_config_box && !cfgbox(conf)) + exit(0); /* config box hit Cancel */ + + /* + * Create the main session window. We don't really need to keep + * the return value - the fact that it'll be linked from a zillion + * GTK and glib bits and bobs known to the main loop will be + * sufficient to make everything actually happen - but we stash it + * in a global variable anyway, so that it'll be easy to find in a + * debugger. + */ + the_inst = new_session_window(conf, geometry_string); + + gtk_main(); + + return 0; +} diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 2879b57e..3ed58b08 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -46,36 +46,12 @@ #include "x11misc.h" -#define CAT2(x,y) x ## y -#define CAT(x,y) CAT2(x,y) -#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)} - -#if GTK_CHECK_VERSION(2,0,0) -ASSERT(sizeof(long) <= sizeof(gsize)); -#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l) -#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p) -#else /* Gtk 1.2 */ -ASSERT(sizeof(long) <= sizeof(gpointer)); -#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) -#define GPOINTER_TO_LONG(p) ((long)(p)) -#endif - /* Colours come in two flavours: configurable, and xterm-extended. */ #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) GdkAtom compound_text_atom, utf8_string_atom; -extern char **pty_argv; /* declared in pty.c */ -extern int use_pty_argv; - -/* - * Timers are global across all sessions (even if we were handling - * multiple sessions, which we aren't), so the current timer ID is - * a global variable. - */ -static guint timer_id = 0; - struct clipboard_data_instance; struct gui_data { @@ -142,8 +118,6 @@ struct gui_data { int ignore_sbar; int mouseptr_visible; int busy_status; - guint toplevel_callback_idle_id; - int idle_fn_scheduled, quit_fn_scheduled; int alt_keycode; int alt_digits; char *wintitle; @@ -158,8 +132,6 @@ struct gui_data { struct unicode_data ucsdata; Conf *conf; void *eventlogstuff; - char *progname, **gtkargvstart; - int ngtkargs; guint32 input_event_time; /* Timestamp of the most recent input event. */ int reconfiguring; #if GTK_CHECK_VERSION(3,4,0) @@ -196,21 +168,9 @@ struct draw_ctx { static int send_raw_mouse; -static const char *app_name = "pterm"; - static void start_backend(struct gui_data *inst); static void exit_callback(void *vinst); -char *x_get_default(const char *key) -{ -#ifndef NOT_X_WINDOWS - return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), - app_name, key); -#else - return NULL; -#endif -} - void connection_fatal(void *frontend, const char *p, ...) { struct gui_data *inst = (struct gui_data *)frontend; @@ -2014,48 +1974,6 @@ static void exit_callback(void *vinst) } } -/* - * Replacement code for the gtk_quit_add() function, which GTK2 - in - * their unbounded wisdom - deprecated without providing any usable - * replacement, and which we were using to ensure that our idle - * function for toplevel callbacks was only run from the outermost - * gtk_main(). - * - * We maintain a global variable with a list of 'struct gui_data' - * instances on which we should call inst_post_main() when a - * subsidiary gtk_main() terminates; then we must make sure that all - * our subsidiary calls to gtk_main() are followed by a call to - * post_main(). - * - * This is kind of overkill in the sense that at the time of writing - * we don't actually ever run more than one 'struct gui_data' instance - * in the same process, but we're _so nearly_ prepared to do that that - * I want to remain futureproof against the possibility of doing so in - * future. - */ -struct post_main_context { - struct post_main_context *next; - struct gui_data *inst; -}; -struct post_main_context *post_main_list_head = NULL; -static void request_post_main(struct gui_data *inst) -{ - struct post_main_context *node = snew(struct post_main_context); - node->next = post_main_list_head; - node->inst = inst; - post_main_list_head = node; -} -static void inst_post_main(struct gui_data *inst); -void post_main(void) -{ - while (post_main_list_head) { - struct post_main_context *node = post_main_list_head; - post_main_list_head = node->next; - inst_post_main(node->inst); - sfree(node); - } -} - void notify_remote_exit(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; @@ -2063,157 +1981,6 @@ void notify_remote_exit(void *frontend) queue_toplevel_callback(exit_callback, inst); } -static void notify_toplevel_callback(void *frontend); - -static void inst_post_main(struct gui_data *inst) -{ - if (gtk_main_level() == 1) { - notify_toplevel_callback(inst); - inst->quit_fn_scheduled = FALSE; - } else { - /* Apparently we're _still_ more than one level deep in - * gtk_main() instances, so we'll need another callback for - * when we get out of the next one. */ - request_post_main(inst); - } -} - -static gint idle_toplevel_callback_func(gpointer data) -{ - struct gui_data *inst = (struct gui_data *)data; - - if (gtk_main_level() > 1) { - /* - * We don't run the callbacks if we're in the middle of a - * subsidiary gtk_main. Instead, ask for a callback when we - * get back out of the subsidiary main loop (if we haven't - * already arranged one), so we can reschedule ourself then. - */ - if (!inst->quit_fn_scheduled) { - request_post_main(inst); - inst->quit_fn_scheduled = TRUE; - } - /* - * And unschedule this idle function, since we've now done - * everything we can until the innermost gtk_main has quit and - * can reschedule us with a chance of actually taking action. - */ - if (inst->idle_fn_scheduled) { /* double-check, just in case */ - g_source_remove(inst->toplevel_callback_idle_id); - inst->idle_fn_scheduled = FALSE; - } - } else { - run_toplevel_callbacks(); - } - - /* - * If we've emptied our toplevel callback queue, unschedule - * ourself. Otherwise, leave ourselves pending so we'll be called - * again to deal with more callbacks after another round of the - * event loop. - */ - if (!toplevel_callback_pending() && inst->idle_fn_scheduled) { - g_source_remove(inst->toplevel_callback_idle_id); - inst->idle_fn_scheduled = FALSE; - } - - return TRUE; -} - -static void notify_toplevel_callback(void *frontend) -{ - struct gui_data *inst = (struct gui_data *)frontend; - - if (!inst->idle_fn_scheduled) { - inst->toplevel_callback_idle_id = - g_idle_add(idle_toplevel_callback_func, inst); - inst->idle_fn_scheduled = TRUE; - } -} - -static gint timer_trigger(gpointer data) -{ - unsigned long now = GPOINTER_TO_LONG(data); - unsigned long next, then; - long ticks; - - /* - * Destroy the timer we got here on. - */ - if (timer_id) { - g_source_remove(timer_id); - timer_id = 0; - } - - /* - * run_timers() may cause a call to timer_change_notify, in which - * case a new timer will already have been set up and left in - * timer_id. If it hasn't, and run_timers reports that some timing - * still needs to be done, we do it ourselves. - */ - if (run_timers(now, &next) && !timer_id) { - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); - } - - /* - * Returning FALSE means 'don't call this timer again', which - * _should_ be redundant given that we removed it above, but just - * in case, return FALSE anyway. - */ - return FALSE; -} - -void timer_change_notify(unsigned long next) -{ - long ticks; - - if (timer_id) - g_source_remove(timer_id); - - ticks = next - GETTICKCOUNT(); - if (ticks <= 0) - ticks = 1; /* just in case */ - - timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); -} - -#if GTK_CHECK_VERSION(2,0,0) -gboolean fd_input_func(GIOChannel *source, GIOCondition condition, - gpointer data) -{ - int sourcefd = g_io_channel_unix_get_fd(source); - /* - * We must process exceptional notifications before ordinary - * readability ones, or we may go straight past the urgent - * marker. - */ - if (condition & G_IO_PRI) - select_result(sourcefd, 4); - if (condition & G_IO_IN) - select_result(sourcefd, 1); - if (condition & G_IO_OUT) - select_result(sourcefd, 2); - - return TRUE; -} -#else -void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) -{ - if (condition & GDK_INPUT_EXCEPTION) - select_result(sourcefd, 4); - if (condition & GDK_INPUT_READ) - select_result(sourcefd, 1); - if (condition & GDK_INPUT_WRITE) - select_result(sourcefd, 2); -} -#endif - void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); @@ -3742,341 +3509,6 @@ long get_windowid(void *frontend) } #endif -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) { - if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) { - perror("output error"); - exit(1); - } -} - -int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, - struct gui_data *inst, Conf *conf) -{ - int err = 0; - 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; hence it is not legal to - * use one of these macros as an unbraced statement between - * `if' and `else'. - */ -#define EXPECTS_ARG { \ - if (--argc <= 0) { \ - err = 1; \ - fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ - continue; \ - } else \ - val = *++argv; \ -} -#define SECOND_PASS_ONLY { if (!do_everything) continue; } - - 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; - -#if GTK_CHECK_VERSION(2,0,0) - inst->geometry = val; -#else - /* On GTK 1, we have to do this using raw Xlib */ - { - int flags, x, y; - unsigned int w, h; - flags = XParseGeometry(val, &x, &y, &w, &h); - if (flags & WidthValue) - conf_set_int(conf, CONF_width, w); - if (flags & HeightValue) - conf_set_int(conf, CONF_height, h); - - if (flags & (XValue | YValue)) { - inst->xpos = x; - inst->ypos = y; - inst->gotpos = TRUE; - inst->gravity = ((flags & XNegative ? 1 : 0) | - (flags & YNegative ? 2 : 0)); - } - } -#endif - - } 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; - int success = gdk_rgba_parse(&rgba, val); -#else - GdkColor col; - int success = gdk_color_parse(val, &col); -#endif - - if (!success) { - err = 1; - 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 = 1, 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_int(conf, CONF_stamp_utmp, 0); - - } else if (!strcmp(p, "-ut")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_stamp_utmp, 1); - - } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_login_shell, 0); - - } else if (!strcmp(p, "-ls")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_login_shell, 1); - - } else if (!strcmp(p, "-nethack")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_nethack_keypad, 1); - - } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_scrollbar, 0); - - } else if (!strcmp(p, "-sb")) { - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_scrollbar, 1); - - } else if (!strcmp(p, "-name")) { - EXPECTS_ARG; - app_name = val; - - } else if (!strcmp(p, "-xrm")) { - EXPECTS_ARG; - provide_xrm_string(val); - - } 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] != '-' && (!do_everything || - process_nonoption_arg(p, conf, - allow_launch))) { - /* do nothing */ - - } else { - err = 1; - fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); - } - } - - return err; -} - -struct uxsel_id { -#if GTK_CHECK_VERSION(2,0,0) - GIOChannel *chan; - guint watch_id; -#else - int id; -#endif -}; - -uxsel_id *uxsel_input_add(int fd, int rwx) { - uxsel_id *id = snew(uxsel_id); - -#if GTK_CHECK_VERSION(2,0,0) - int flags = 0; - if (rwx & 1) flags |= G_IO_IN; - if (rwx & 2) flags |= G_IO_OUT; - if (rwx & 4) flags |= G_IO_PRI; - id->chan = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(id->chan, NULL, NULL); - id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags, - fd_input_func, NULL, NULL); -#else - int flags = 0; - if (rwx & 1) flags |= GDK_INPUT_READ; - if (rwx & 2) flags |= GDK_INPUT_WRITE; - if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; - assert(flags); - id->id = gdk_input_add(fd, flags, fd_input_func, NULL); -#endif - - return id; -} - -void uxsel_input_remove(uxsel_id *id) { -#if GTK_CHECK_VERSION(2,0,0) - g_source_remove(id->watch_id); - g_io_channel_unref(id->chan); -#else - gdk_input_remove(id->id); -#endif - sfree(id); -} - int frontend_is_utf8(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; @@ -4408,200 +3840,16 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) inst->reconfiguring = FALSE; } -void fork_and_exec_self(struct gui_data *inst, 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 += inst->ngtkargs; - while (va_arg(ap, char *) != NULL) - n++; - va_end(ap); - - args = snewn(n, char *); - args[0] = inst->progname; - args[n-1] = NULL; - for (i = 0; i < inst->ngtkargs; i++) - args[i+1] = inst->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(inst->progname, args); - perror("exec"); - _exit(127); - - } else { - int status; - sfree(args); - waitpid(pid, &status, 0); - } - -} - void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) { struct gui_data *inst = (struct gui_data *)gdata; - /* - * 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, sersize, size; - char *data; - char option[80]; - int pipefd[2]; - if (pipe(pipefd) < 0) { - perror("pipe"); - return; - } - - size = sersize = conf_serialised_size(inst->conf); - if (use_pty_argv && pty_argv) { - for (i = 0; pty_argv[i]; i++) - size += strlen(pty_argv[i]) + 1; - } - - data = snewn(size, char); - conf_serialise(inst->conf, data); - if (use_pty_argv && pty_argv) { - int p = sersize; - for (i = 0; pty_argv[i]; i++) { - strcpy(data + p, pty_argv[i]); - p += strlen(pty_argv[i]) + 1; - } - assert(p == size); - } - - sprintf(option, "---[%d,%d]", pipefd[0], size); - noncloexec(pipefd[0]); - fork_and_exec_self(inst, pipefd[1], option, NULL); - close(pipefd[0]); - - i = ret = 0; - while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0) - i += ret; - if (ret < 0) - perror("write to pipe"); - close(pipefd[1]); - sfree(data); -} - -int read_dupsession_data(struct gui_data *inst, Conf *conf, char *arg) -{ - int fd, i, ret, size, size_used; - char *data; - - 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); - } - - size_used = conf_deserialise(conf, data, size); - if (use_pty_argv && size > size_used) { - int n = 0; - i = size_used; - while (i < size) { - while (i < size && data[i]) i++; - if (i >= size) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", - appname); - exit(1); - } - i++; - n++; - } - pty_argv = snewn(n+1, char *); - pty_argv[n] = NULL; - n = 0; - i = size_used; - while (i < size) { - char *p = data + i; - while (i < size && data[i]) i++; - assert(i < size); - i++; - pty_argv[n++] = dupstr(p); - } - } - - sfree(data); - - return 0; + launch_duplicate_session(inst->conf); } void new_session_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; - - fork_and_exec_self(inst, -1, NULL); + launch_new_session(); } void restart_session_menuitem(GtkMenuItem *item, gpointer data) @@ -4618,10 +3866,9 @@ void restart_session_menuitem(GtkMenuItem *item, gpointer data) void saved_session_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - fork_and_exec_self(inst, -1, "-load", str, NULL); + launch_saved_session(str); } void saved_session_freedata(GtkMenuItem *item, gpointer data) @@ -4814,13 +4061,10 @@ static void start_backend(struct gui_data *inst) gtk_widget_set_sensitive(inst->restartitem, FALSE); } -int pt_main(int argc, char **argv) +struct gui_data *new_session_window(Conf *conf, const char *geometry_string) { - extern int cfgbox(Conf *conf); struct gui_data *inst; - setlocale(LC_CTYPE, ""); - /* * Create an instance structure and initialise to zeroes */ @@ -4828,57 +4072,34 @@ int pt_main(int argc, char **argv) memset(inst, 0, sizeof(*inst)); inst->alt_keycode = -1; /* this one needs _not_ to be zero */ inst->busy_status = BUSY_NOT; - inst->conf = conf_new(); + inst->conf = conf; inst->wintitle = inst->icontitle = NULL; - inst->quit_fn_scheduled = FALSE; - inst->idle_fn_scheduled = FALSE; inst->drawtype = DRAWTYPE_DEFAULT; #if GTK_CHECK_VERSION(3,4,0) inst->cumulative_scroll = 0.0; #endif - /* defer any child exit handling until we're ready to deal with - * it */ - block_signal(SIGCHLD, 1); + if (geometry_string) { +#if GTK_CHECK_VERSION(2,0,0) + inst->geometry = geometry_string; +#else + /* On GTK 1, we have to do this using raw Xlib */ + int flags, x, y; + unsigned int w, h; + flags = XParseGeometry(geometry_string, &x, &y, &w, &h); + if (flags & WidthValue) + conf_set_int(conf, CONF_width, w); + if (flags & HeightValue) + conf_set_int(conf, CONF_height, h); - inst->progname = argv[0]; - /* - * Copy the original argv before letting gtk_init fiddle with - * it. It will be required later. - */ - { - int i, oldargc; - inst->gtkargvstart = snewn(argc-1, char *); - for (i = 1; i < argc; i++) - inst->gtkargvstart[i-1] = dupstr(argv[i]); - oldargc = argc; - gtk_init(&argc, &argv); - inst->ngtkargs = oldargc - argc; - } - - if (argc > 1 && !strncmp(argv[1], "---", 3)) { - read_dupsession_data(inst, inst->conf, argv[1]); - /* Splatter this argument so it doesn't clutter a ps listing */ - smemclr(argv[1], strlen(argv[1])); - } else { - /* By default, we bring up the config dialog, rather than launching - * a session. This gets set to TRUE if something happens to change - * that (e.g., a hostname is specified on the command-line). */ - int allow_launch = FALSE; - if (do_cmdline(argc, argv, 0, &allow_launch, inst, inst->conf)) - exit(1); /* pre-defaults pass to get -class */ - do_defaults(NULL, inst->conf); - if (do_cmdline(argc, argv, 1, &allow_launch, inst, inst->conf)) - exit(1); /* post-defaults, do everything */ - - cmdline_run_saved(inst->conf); - - if (loaded_session) - allow_launch = TRUE; - - if ((!allow_launch || !conf_launchable(inst->conf)) && - !cfgbox(inst->conf)) - exit(0); /* config box hit Cancel */ + if (flags & (XValue | YValue)) { + inst->xpos = x; + inst->ypos = y; + inst->gotpos = TRUE; + inst->gravity = ((flags & XNegative ? 1 : 0) | + (flags & YNegative ? 2 : 0)); + } +#endif } if (!compound_text_atom) @@ -5112,14 +4333,10 @@ int pt_main(int argc, char **argv) inst->eventlogstuff = eventlogstuff_new(); - request_callback_notifications(notify_toplevel_callback, inst); - inst->term = term_init(inst->conf, &inst->ucsdata, inst); inst->logctx = log_init(inst, inst->conf); term_provide_logctx(inst->term, inst->logctx); - uxsel_init(); - term_size(inst->term, inst->height, inst->width, conf_get_int(inst->conf, CONF_savelines)); @@ -5127,22 +4344,7 @@ int pt_main(int argc, char **argv) ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ - /* now we're reday to deal with the child exit handler being - * called */ - block_signal(SIGCHLD, 0); - - /* - * 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. Note that we do - * this _after_ (at least pterm) forks off its child process, - * since the child wants SIGPIPE handled in the usual way. - */ - block_signal(SIGPIPE, 1); - inst->exited = FALSE; - gtk_main(); - - return 0; + return inst; } diff --git a/unix/unix.h b/unix/unix.h index 62e20112..3b8982c2 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -94,6 +94,18 @@ unsigned long getticks(void); /* based on gettimeofday(2) */ */ #define FLAG_STDERR_TTY 0x1000 +/* The per-session frontend structure managed by gtkwin.c */ +struct gui_data; +struct gui_data *new_session_window(Conf *conf, const char *geometry_string); + +/* Defined in gtkmain.c */ +void launch_duplicate_session(Conf *conf); +void launch_new_session(void); +void launch_saved_session(const char *str); + +/* Defined in gtkcomm.c */ +void gtkcomm_setup(void); + /* Things pty.c needs from pterm.c */ const char *get_x_display(void *frontend); int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */ diff --git a/unix/uxpty.c b/unix/uxpty.c index 79a60f3c..e96ff356 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -912,14 +912,14 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, /* * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by * our parent, particularly by things like sh -c 'pterm &' and - * some window or session managers. SIGCHLD, meanwhile, was - * blocked during pt_main() startup. Reverse all this for our - * child process. + * some window or session managers. SIGPIPE was also + * (potentially) blocked by us during startup. Reverse all + * this for our child process. */ putty_signal(SIGINT, SIG_DFL); putty_signal(SIGQUIT, SIG_DFL); putty_signal(SIGPIPE, SIG_DFL); - block_signal(SIGCHLD, 0); + block_signal(SIGPIPE, 0); if (pty_argv) { /* * Exec the exact argument list we were given.