Make IPv6 support for Unix work, and make it a lot simpler in the process.
[u/mdw/putty] / unix / pterm.c
index 9d92bc7..00c33ef 100644 (file)
 
 #define NCOLOURS (lenof(((Config *)0)->colours))
 
+GdkAtom compound_text_atom, utf8_string_atom;
+
+extern char **pty_argv;               /* declared in pty.c */
+extern int use_pty_argv;
+
 struct gui_data {
     GtkWidget *window, *area, *sbar;
     GtkBox *hbox;
     GtkAdjustment *sbar_adjust;
     GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2;
+    GtkWidget *sessionsmenu;
     GdkPixmap *pixmap;
     GdkFont *fonts[4];                 /* normal, bold, wide, widebold */
     struct {
@@ -55,10 +61,10 @@ struct gui_data {
     char *pasteout_data, *pasteout_data_utf8;
     int pasteout_data_len, pasteout_data_utf8_len;
     int font_width, font_height;
+    int width, height;
     int ignore_sbar;
     int mouseptr_visible;
     guint term_paste_idle_id;
-    GdkAtom compound_text_atom, utf8_string_atom;
     int alt_keycode;
     int alt_digits;
     char wintitle[sizeof(((Config *)0)->wintitle)];
@@ -72,6 +78,9 @@ struct gui_data {
     int exited;
     struct unicode_data ucsdata;
     Config cfg;
+    void *eventlogstuff;
+    char *progname, **gtkargvstart;
+    int ngtkargs;
 };
 
 struct draw_ctx {
@@ -90,8 +99,7 @@ char *x_get_default(const char *key)
 
 void connection_fatal(void *frontend, char *p, ...)
 {
-    Terminal *term = (Terminal *)frontend;
-    struct gui_data *inst = (struct gui_data *)term->frontend;
+    struct gui_data *inst = (struct gui_data *)frontend;
 
     va_list ap;
     char *msg;
@@ -150,24 +158,19 @@ void ldisc_update(void *frontend, int echo, int edit)
      */
 }
 
-int askappend(void *frontend, Filename filename)
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
 {
-    /*
-     * Logging in an xterm-alike is liable to be something you only
-     * do at serious diagnostic need. Hence, I'm going to take the
-     * easy option for now and assume we always want to overwrite
-     * log files. I can always make it properly configurable later.
-     */
-    return 2;
+    struct gui_data *inst = (struct gui_data *)frontend;
+    return term_data(inst->term, is_stderr, data, len);
 }
 
 void logevent(void *frontend, char *string)
 {
-    /*
-     * This is not a very helpful function: events are logged
-     * pretty much exclusively by the back end, and our pty back
-     * end is self-contained. So we need do nothing.
-     */
+    struct gui_data *inst = (struct gui_data *)frontend;
+
+    log_eventlog(inst->logctx, string);
+
+    logevent_dlg(inst->eventlogstuff, string);
 }
 
 int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
@@ -208,8 +211,7 @@ static Mouse_Button translate_button(Mouse_Button button)
  */
 void *get_window(void *frontend)
 {
-    Terminal *term = (Terminal *)frontend;
-    struct gui_data *inst = (struct gui_data *)term->frontend;
+    struct gui_data *inst = (struct gui_data *)frontend;
     return inst->window;
 }
 
@@ -345,10 +347,11 @@ char *get_window_title(void *frontend, int icon)
 
 gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data)
 {
-    /*
-     * We could implement warn-on-close here if we really wanted
-     * to.
-     */
+    struct gui_data *inst = (struct gui_data *)data;
+    if (!inst->exited && inst->cfg.warn_on_close) {
+       if (!reallyclose(inst))
+           return TRUE;
+    }
     return FALSE;
 }
 
@@ -367,40 +370,45 @@ gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
     int w, h, need_size = 0;
+    GdkGC *gc;
 
+    /*
+     * See if the terminal size has changed, in which case we must
+     * let the terminal know.
+     */
     w = (event->width - 2*inst->cfg.window_border) / inst->font_width;
     h = (event->height - 2*inst->cfg.window_border) / inst->font_height;
-
-    if (w != inst->cfg.width || h != inst->cfg.height) {
-       if (inst->pixmap) {
-           gdk_pixmap_unref(inst->pixmap);
-           inst->pixmap = NULL;
-       }
-       inst->cfg.width = w;
-       inst->cfg.height = h;
+    if (w != inst->width || h != inst->height) {
+       inst->cfg.width = inst->width = w;
+       inst->cfg.height = inst->height = h;
        need_size = 1;
     }
-    if (!inst->pixmap) {
-       GdkGC *gc;
-
-       inst->pixmap = gdk_pixmap_new(widget->window,
-                                     (inst->cfg.width * inst->font_width +
-                                      2*inst->cfg.window_border),
-                                     (inst->cfg.height * inst->font_height +
-                                      2*inst->cfg.window_border), -1);
-
-       gc = gdk_gc_new(inst->area->window);
-       gdk_gc_set_foreground(gc, &inst->cols[18]);   /* default background */
-       gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
-                          inst->cfg.width * inst->font_width + 2*inst->cfg.window_border,
-                          inst->cfg.height * inst->font_height + 2*inst->cfg.window_border);
-       gdk_gc_unref(gc);
+
+    if (inst->pixmap) {
+       gdk_pixmap_unref(inst->pixmap);
+       inst->pixmap = NULL;
     }
 
-    if (need_size) {
+    inst->pixmap = gdk_pixmap_new(widget->window,
+                                 (inst->cfg.width * inst->font_width +
+                                  2*inst->cfg.window_border),
+                                 (inst->cfg.height * inst->font_height +
+                                  2*inst->cfg.window_border), -1);
+
+    gc = gdk_gc_new(inst->area->window);
+    gdk_gc_set_foreground(gc, &inst->cols[18]);   /* default background */
+    gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
+                      inst->cfg.width * inst->font_width + 2*inst->cfg.window_border,
+                      inst->cfg.height * inst->font_height + 2*inst->cfg.window_border);
+    gdk_gc_unref(gc);
+
+    if (need_size && inst->term) {
        term_size(inst->term, h, w, inst->cfg.savelines);
     }
 
+    if (inst->term)
+       term_invalidate(inst->term);
+
     return TRUE;
 }
 
@@ -430,7 +438,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
     char output[32];
-    int start, end;
+    int start, end, special;
 
     /* By default, nothing is generated. */
     end = start = 0;
@@ -554,6 +562,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
            return TRUE;
        }
 
+       special = FALSE;
+
        /* ALT+things gives leading Escape. */
        output[0] = '\033';
        strncpy(output+1, event->string, 31);
@@ -577,6 +587,15 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
            (event->state & GDK_CONTROL_MASK)) {
            output[1] = '\003';
            end = 2;
+           special = TRUE;
+       }
+
+       /* We handle Return ourselves, because it needs to be flagged as
+        * special to ldisc. */
+       if (event->keyval == GDK_Return) {
+           output[1] = '\015';
+           end = 2;
+           special = TRUE;
        }
 
        /* Control-2, Control-Space and Control-@ are NUL */
@@ -602,12 +621,14 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
            !(event->state & GDK_SHIFT_MASK)) {
            output[1] = inst->cfg.bksp_is_delete ? '\x7F' : '\x08';
            end = 2;
+           special = TRUE;
        }
        /* For Shift Backspace, do opposite of what is configured. */
        if (event->keyval == GDK_BackSpace &&
            (event->state & GDK_SHIFT_MASK)) {
            output[1] = inst->cfg.bksp_is_delete ? '\x08' : '\x7F';
            end = 2;
+           special = TRUE;
        }
 
        /* Shift-Tab is ESC [ Z */
@@ -913,7 +934,14 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        printf("\n");
 #endif
 
-       if (!inst->direct_to_font) {
+       if (special) {
+           /*
+            * For special control characters, the character set
+            * should never matter.
+            */
+           output[end] = '\0';        /* NUL-terminate */
+           ldisc_send(inst->ldisc, output+start, -2, 1);
+       } else if (!inst->direct_to_font) {
            /*
             * The stuff we've just generated is assumed to be
             * ISO-8859-1! This sounds insane, but `man
@@ -1158,8 +1186,11 @@ void request_resize(void *frontend, int w, int h)
     gtk_widget_set_size_request(inst->area, area_x, area_y);
 #else
     gtk_widget_set_usize(inst->area, area_x, area_y);
+    gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
 #endif
 
+    gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
+
 #if GTK_CHECK_VERSION(2,0,0)
     gtk_window_resize(GTK_WINDOW(inst->window),
                      area_x + offset_x, area_y + offset_y);
@@ -1177,10 +1208,11 @@ static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b)
     inst->cols[n].green = g * 0x0101;
     inst->cols[n].blue = b * 0x0101;
 
+    gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1);
     gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,
                              FALSE, FALSE, success);
     if (!success[0])
-       g_error("pterm: couldn't allocate colour %d (#%02x%02x%02x)\n",
+       g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname,
                n, r, g, b);
 }
 
@@ -1237,8 +1269,9 @@ void palette_reset(void *frontend)
                              FALSE, FALSE, success);
     for (i = 0; i < NCOLOURS; i++) {
        if (!success[i])
-           g_error("pterm: couldn't allocate colour %d (#%02x%02x%02x)\n",
-                   i, inst->cfg.colours[i][0], inst->cfg.colours[i][1], inst->cfg.colours[i][2]);
+           g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
+                    appname, i, inst->cfg.colours[i][0],
+                    inst->cfg.colours[i][1], inst->cfg.colours[i][2]);
     }
 
     set_window_background(inst);
@@ -1298,18 +1331,21 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
        gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
                                 GDK_SELECTION_TYPE_STRING, 1);
        gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
-                                inst->compound_text_atom, 1);
+                                compound_text_atom, 1);
        if (inst->pasteout_data_utf8)
            gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
-                                    inst->utf8_string_atom, 1);
+                                    utf8_string_atom, 1);
     }
+
+    if (must_deselect)
+       term_deselect(inst->term);
 }
 
 void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
                   guint info, guint time_stamp, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    if (seldata->target == inst->utf8_string_atom)
+    if (seldata->target == utf8_string_atom)
        gtk_selection_data_set(seldata, seldata->target, 8,
                               inst->pasteout_data_utf8,
                               inst->pasteout_data_utf8_len);
@@ -1352,7 +1388,7 @@ void request_paste(void *frontend)
         * fall back to an ordinary string.
         */
        gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
-                             inst->utf8_string_atom, GDK_CURRENT_TIME);
+                             utf8_string_atom, GDK_CURRENT_TIME);
     } else {
        /*
         * If we're in direct-to-font mode, we disable UTF-8
@@ -1370,7 +1406,7 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
 {
     struct gui_data *inst = (struct gui_data *)data;
 
-    if (seldata->target == inst->utf8_string_atom && seldata->length <= 0) {
+    if (seldata->target == utf8_string_atom && seldata->length <= 0) {
        /*
         * Failed to get a UTF-8 selection string. Try an ordinary
         * string.
@@ -1385,7 +1421,7 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
      */
     if (seldata->length <= 0 ||
        (seldata->type != GDK_SELECTION_TYPE_STRING &&
-        seldata->type != inst->utf8_string_atom))
+        seldata->type != utf8_string_atom))
        return;                        /* Nothing happens. */
 
     if (inst->pastein_data)
@@ -1394,7 +1430,7 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
     inst->pastein_data = snewn(seldata->length, wchar_t);
     inst->pastein_data_len = seldata->length;
     inst->pastein_data_len =
-       mb_to_wc((seldata->type == inst->utf8_string_atom ?
+       mb_to_wc((seldata->type == utf8_string_atom ?
                  CS_UTF8 : inst->ucsdata.line_codepage),
                 0, seldata->data, seldata->length,
                 inst->pastein_data, inst->pastein_data_len);
@@ -1953,7 +1989,7 @@ void modalfatalbox(char *p, ...)
 void cmdline_error(char *p, ...)
 {
     va_list ap;
-    fprintf(stderr, "plink: ");
+    fprintf(stderr, "%s: ", appname);
     va_start(ap, p);
     vfprintf(stderr, p, ap);
     va_end(ap);
@@ -1968,8 +2004,7 @@ char *get_x_display(void *frontend)
 
 long get_windowid(void *frontend)
 {
-    Terminal *term = (Terminal *)frontend;
-    struct gui_data *inst = (struct gui_data *)(term->frontend);
+    struct gui_data *inst = (struct gui_data *)frontend;
     return (long)GDK_WINDOW_XWINDOW(inst->area->window);
 }
 
@@ -2004,8 +2039,7 @@ int do_cmdline(int argc, char **argv, int do_everything,
                struct gui_data *inst, Config *cfg)
 {
     int err = 0;
-    extern char **pty_argv;           /* declared in pty.c */
-    extern int use_pty_argv;
+    char *val;
 
     /*
      * Macros to make argument handling easier. Note that because
@@ -2018,14 +2052,13 @@ int do_cmdline(int argc, char **argv, int do_everything,
 #define EXPECTS_ARG { \
     if (--argc <= 0) { \
        err = 1; \
-       fprintf(stderr, "pterm: %s expects an argument\n", p); \
+       fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
         continue; \
     } else \
        val = *++argv; \
 }
 #define SECOND_PASS_ONLY { if (!do_everything) continue; }
 
-    char *val;
     while (--argc > 0) {
        char *p = *++argv;
         int ret;
@@ -2105,7 +2138,8 @@ int do_cmdline(int argc, char **argv, int do_everything,
            SECOND_PASS_ONLY;
            if (!gdk_color_parse(val, &col)) {
                err = 1;
-               fprintf(stderr, "pterm: unable to parse colour \"%s\"\n", val);
+               fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
+                        appname, val);
            } else {
                int index;
                index = (!strcmp(p, "-fg") ? 0 :
@@ -2134,9 +2168,10 @@ int do_cmdline(int argc, char **argv, int do_everything,
                pty_argv[argc] = NULL;
                break;                 /* finished command-line processing */
            } else
-               err = 1, fprintf(stderr, "pterm: -e expects an argument\n");
+               err = 1, fprintf(stderr, "%s: -e expects an argument\n",
+                                 appname);
 
-       } else if (!strcmp(p, "-T")) {
+       } else if (!strcmp(p, "-title")) {
            EXPECTS_ARG;
            SECOND_PASS_ONLY;
            strncpy(cfg->wintitle, val, sizeof(cfg->wintitle));
@@ -2195,7 +2230,7 @@ int do_cmdline(int argc, char **argv, int do_everything,
 
        } else {
            err = 1;
-           fprintf(stderr, "pterm: unrecognized option '%s'\n", p);
+           fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
        }
     }
 
@@ -2289,6 +2324,80 @@ void uxsel_input_remove(int id) {
     gdk_input_remove(id);
 }
 
+void setup_fonts_ucs(struct gui_data *inst)
+{
+    int font_charset;
+
+    if (inst->fonts[0])
+        gdk_font_unref(inst->fonts[0]);
+    if (inst->fonts[1])
+        gdk_font_unref(inst->fonts[1]);
+    if (inst->fonts[2])
+        gdk_font_unref(inst->fonts[2]);
+    if (inst->fonts[3])
+        gdk_font_unref(inst->fonts[3]);
+
+    inst->fonts[0] = gdk_font_load(inst->cfg.font.name);
+    if (!inst->fonts[0]) {
+       fprintf(stderr, "%s: unable to load font \"%s\"\n", appname,
+               inst->cfg.font.name);
+       exit(1);
+    }
+    font_charset = set_font_info(inst, 0);
+    if (inst->cfg.boldfont.name[0]) {
+       inst->fonts[1] = gdk_font_load(inst->cfg.boldfont.name);
+       if (!inst->fonts[1]) {
+           fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname,
+                   inst->cfg.boldfont.name);
+           exit(1);
+       }
+       set_font_info(inst, 1);
+    } else
+       inst->fonts[1] = NULL;
+    if (inst->cfg.widefont.name[0]) {
+       inst->fonts[2] = gdk_font_load(inst->cfg.widefont.name);
+       if (!inst->fonts[2]) {
+           fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname,
+                   inst->cfg.widefont.name);
+           exit(1);
+       }
+       set_font_info(inst, 2);
+    } else
+       inst->fonts[2] = NULL;
+    if (inst->cfg.wideboldfont.name[0]) {
+       inst->fonts[3] = gdk_font_load(inst->cfg.wideboldfont.name);
+       if (!inst->fonts[3]) {
+           fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n",
+                    appname, inst->cfg.wideboldfont.name);
+           exit(1);
+       }
+       set_font_info(inst, 3);
+    } else
+       inst->fonts[3] = NULL;
+
+    inst->font_width = gdk_char_width(inst->fonts[0], ' ');
+    inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent;
+
+    inst->direct_to_font = init_ucs(&inst->ucsdata,
+                                   inst->cfg.line_codepage, font_charset);
+}
+
+void set_geom_hints(struct gui_data *inst)
+{
+    GdkGeometry geom;
+    geom.min_width = inst->font_width + 2*inst->cfg.window_border;
+    geom.min_height = inst->font_height + 2*inst->cfg.window_border;
+    geom.max_width = geom.max_height = -1;
+    geom.base_width = 2*inst->cfg.window_border;
+    geom.base_height = 2*inst->cfg.window_border;
+    geom.width_inc = inst->font_width;
+    geom.height_inc = inst->font_height;
+    geom.min_aspect = geom.max_aspect = 0;
+    gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom,
+                                  GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
+                                  GDK_HINT_RESIZE_INC);
+}
+
 void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
@@ -2302,6 +2411,12 @@ void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
     ldisc_send(inst->ldisc, NULL, 0, 0);
 }
 
+void copy_all_menuitem(GtkMenuItem *item, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+    term_copyall(inst->term);
+}
+
 void special_menuitem(GtkMenuItem *item, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
@@ -2312,14 +2427,335 @@ void special_menuitem(GtkMenuItem *item, gpointer data)
 
 void about_menuitem(GtkMenuItem *item, gpointer data)
 {
-    /* struct gui_data *inst = (struct gui_data *)data; */
-    about_box();
+    struct gui_data *inst = (struct gui_data *)data;
+    about_box(inst->window);
+}
+
+void event_log_menuitem(GtkMenuItem *item, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+    showeventlog(inst->eventlogstuff, inst->window);
+}
+
+void change_settings_menuitem(GtkMenuItem *item, gpointer data)
+{
+    /* This maps colour indices in inst->cfg to those used in inst->cols. */
+    static const int ww[] = {
+        6, 7, 8, 9, 10, 11, 12, 13,
+        14, 15, 16, 17, 18, 19, 20, 21,
+        0, 1, 2, 3, 4, 5
+    };
+    struct gui_data *inst = (struct gui_data *)data;
+    char *title = dupcat(appname, " Reconfiguration", NULL);
+    Config cfg2, oldcfg;
+    int i, need_size;
+
+    cfg2 = inst->cfg;                  /* structure copy */
+
+    if (do_config_box(title, &cfg2, 1)) {
+
+        oldcfg = inst->cfg;            /* structure copy */
+        inst->cfg = cfg2;              /* structure copy */
+
+        /* Pass new config data to the logging module */
+        log_reconfig(inst->logctx, &cfg2);
+        /*
+         * Flush the line discipline's edit buffer in the case
+         * where local editing has just been disabled.
+         */
+        ldisc_send(inst->ldisc, NULL, 0, 0);
+        /* Pass new config data to the terminal */
+        term_reconfig(inst->term, &cfg2);
+        /* Pass new config data to the back end */
+        inst->back->reconfig(inst->backhandle, &cfg2);
+
+        /*
+         * Just setting inst->cfg is sufficient to cause colour
+         * setting changes to appear on the next ESC]R palette
+         * reset. But we should also check whether any colour
+         * settings have been changed, and revert the ones that
+         * have to the new default, on the assumption that the user
+         * is most likely to want an immediate update.
+         */
+        for (i = 0; i < NCOLOURS; i++) {
+            if (oldcfg.colours[ww[i]][0] != cfg2.colours[ww[i]][0] ||
+                oldcfg.colours[ww[i]][1] != cfg2.colours[ww[i]][1] ||
+                oldcfg.colours[ww[i]][2] != cfg2.colours[ww[i]][2])
+                real_palette_set(inst, i, cfg2.colours[ww[i]][0],
+                                 cfg2.colours[ww[i]][1],
+                                 cfg2.colours[ww[i]][2]);
+        }
+
+        /*
+         * If the scrollbar needs to be shown, hidden, or moved
+         * from one end to the other of the window, do so now.
+         */
+        if (oldcfg.scrollbar != cfg2.scrollbar) {
+            if (cfg2.scrollbar)
+                gtk_widget_show(inst->sbar);
+            else
+                gtk_widget_hide(inst->sbar);
+        }
+        if (oldcfg.scrollbar_on_left != cfg2.scrollbar_on_left) {
+            gtk_box_reorder_child(inst->hbox, inst->sbar,
+                                  cfg2.scrollbar_on_left ? 0 : 1);
+        }
+
+        /*
+         * Change the window title, if required.
+         */
+        if (strcmp(oldcfg.wintitle, cfg2.wintitle))
+            set_title(inst, cfg2.wintitle);
+
+        /*
+         * Redo the whole tangled fonts and Unicode mess if
+         * necessary.
+         */
+        if (strcmp(oldcfg.font.name, cfg2.font.name) ||
+            strcmp(oldcfg.boldfont.name, cfg2.boldfont.name) ||
+            strcmp(oldcfg.widefont.name, cfg2.widefont.name) ||
+            strcmp(oldcfg.wideboldfont.name, cfg2.wideboldfont.name) ||
+            strcmp(oldcfg.line_codepage, cfg2.line_codepage)) {
+            setup_fonts_ucs(inst);
+            need_size = 1;
+        } else
+            need_size = 0;
+
+        /*
+         * Resize the window.
+         */
+        if (oldcfg.width != cfg2.width || oldcfg.height != cfg2.height ||
+            oldcfg.window_border != cfg2.window_border || need_size) {
+            set_geom_hints(inst);
+            request_resize(inst, cfg2.width, cfg2.height);
+        } else {
+           /*
+            * The above will have caused a call to term_size() for
+            * us if it happened. If the user has fiddled with only
+            * the scrollback size, the above will not have
+            * happened and we will need an explicit term_size()
+            * here.
+            */
+           if (oldcfg.savelines != cfg2.savelines)
+               term_size(inst->term, inst->term->rows, inst->term->cols,
+                         cfg2.savelines);
+       }
+
+        term_invalidate(inst->term);
+    }
+    sfree(title);
+}
+
+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");
+       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;
+       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 cfg 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, size;
+    char *data;
+    char option[80];
+    int pipefd[2];
+
+    if (pipe(pipefd) < 0) {
+       perror("pipe");
+       return;
+    }
+
+    size = sizeof(inst->cfg);
+    if (use_pty_argv && pty_argv) {
+       for (i = 0; pty_argv[i]; i++)
+           size += strlen(pty_argv[i]) + 1;
+    }
+
+    data = snewn(size, char);
+    memcpy(data, &inst->cfg, sizeof(inst->cfg));
+    if (use_pty_argv && pty_argv) {
+       int p = sizeof(inst->cfg);
+       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);
+    fcntl(pipefd[0], F_SETFD, 0);
+    fork_and_exec_self(inst, pipefd[1], option, NULL);
+    close(pipefd[0]);
+
+    i = 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, Config *cfg, char *arg)
+{
+    int fd, i, ret, size;
+    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 = 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);
+    }
+
+    memcpy(cfg, data, sizeof(Config));
+    if (use_pty_argv && size > sizeof(Config)) {
+       int n = 0;
+       i = sizeof(Config);
+       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 = sizeof(Config);
+       while (i < size) {
+           char *p = data + i;
+           while (i < size && data[i]) i++;
+           assert(i < size);
+           i++;
+           pty_argv[n++] = dupstr(p);
+       }
+    }
+
+    return 0;
+}
+
+void new_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+
+    fork_and_exec_self(inst, -1, NULL);
+}
+
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+    char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+    fork_and_exec_self(inst, -1, "-load", str, NULL);
+}
+
+void saved_session_freedata(GtkMenuItem *item, gpointer data)
+{
+    char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+    sfree(str);
 }
 
 void update_specials_menu(void *frontend)
 {
-    Terminal *term = (Terminal *)frontend;
-    struct gui_data *inst = (struct gui_data *)term->frontend;
+    struct gui_data *inst = (struct gui_data *)frontend;
 
     const struct telnet_special *specials;
 
@@ -2354,13 +2790,6 @@ int pt_main(int argc, char **argv)
     extern Backend *select_backend(Config *cfg);
     extern int cfgbox(Config *cfg);
     struct gui_data *inst;
-    int font_charset;
-
-    /* defer any child exit handling until we're ready to deal with
-     * it */
-    block_signal(SIGCHLD, 1);
-
-    gtk_init(&argc, &argv);
 
     /*
      * Create an instance structure and initialise to zeroes
@@ -2369,63 +2798,54 @@ int pt_main(int argc, char **argv)
     memset(inst, 0, sizeof(*inst));
     inst->alt_keycode = -1;            /* this one needs _not_ to be zero */
 
-    if (do_cmdline(argc, argv, 0, inst, &inst->cfg))
-       exit(1);                       /* pre-defaults pass to get -class */
-    do_defaults(NULL, &inst->cfg);
-    if (do_cmdline(argc, argv, 1, inst, &inst->cfg))
-       exit(1);                       /* post-defaults, do everything */
-
-    cmdline_run_saved(&inst->cfg);
+    /* defer any child exit handling until we're ready to deal with
+     * it */
+    block_signal(SIGCHLD, 1);
 
-    if (!*inst->cfg.host && !cfgbox(&inst->cfg))
-       exit(0);                       /* config box hit Cancel */
+    /*
+     * SIGPIPE is not something we want to see terminating the
+     * process.
+     */
+    block_signal(SIGPIPE, 1);
 
-    inst->fonts[0] = gdk_font_load(inst->cfg.font.name);
-    if (!inst->fonts[0]) {
-       fprintf(stderr, "pterm: unable to load font \"%s\"\n",
-               inst->cfg.font.name);
-       exit(1);
+    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;
     }
-    font_charset = set_font_info(inst, 0);
-    if (inst->cfg.boldfont.name[0]) {
-       inst->fonts[1] = gdk_font_load(inst->cfg.boldfont.name);
-       if (!inst->fonts[1]) {
-           fprintf(stderr, "pterm: unable to load bold font \"%s\"\n",
-                   inst->cfg.boldfont.name);
-           exit(1);
-       }
-       set_font_info(inst, 1);
-    } else
-       inst->fonts[1] = NULL;
-    if (inst->cfg.widefont.name[0]) {
-       inst->fonts[2] = gdk_font_load(inst->cfg.widefont.name);
-       if (!inst->fonts[2]) {
-           fprintf(stderr, "pterm: unable to load wide font \"%s\"\n",
-                   inst->cfg.widefont.name);
-           exit(1);
-       }
-       set_font_info(inst, 2);
-    } else
-       inst->fonts[2] = NULL;
-    if (inst->cfg.wideboldfont.name[0]) {
-       inst->fonts[3] = gdk_font_load(inst->cfg.wideboldfont.name);
-       if (!inst->fonts[3]) {
-           fprintf(stderr, "pterm: unable to load wide/bold font \"%s\"\n",
-                   inst->cfg.wideboldfont.name);
-           exit(1);
-       }
-       set_font_info(inst, 3);
-    } else
-       inst->fonts[3] = NULL;
 
-    inst->font_width = gdk_char_width(inst->fonts[0], ' ');
-    inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent;
+    if (argc > 1 && !strncmp(argv[1], "---", 3)) {
+       read_dupsession_data(inst, &inst->cfg, argv[1]);
+       /* Splatter this argument so it doesn't clutter a ps listing */
+       memset(argv[1], 0, strlen(argv[1]));
+    } else {
+       if (do_cmdline(argc, argv, 0, inst, &inst->cfg))
+           exit(1);                   /* pre-defaults pass to get -class */
+       do_defaults(NULL, &inst->cfg);
+       if (do_cmdline(argc, argv, 1, inst, &inst->cfg))
+           exit(1);                   /* post-defaults, do everything */
 
-    inst->compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
-    inst->utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+       cmdline_run_saved(&inst->cfg);
 
-    inst->direct_to_font = init_ucs(&inst->ucsdata,
-                                   inst->cfg.line_codepage, font_charset);
+       if (!*inst->cfg.host && !cfgbox(&inst->cfg))
+           exit(0);                   /* config box hit Cancel */
+    }
+
+    if (!compound_text_atom)
+        compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+    if (!utf8_string_atom)
+        utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+
+    setup_fonts_ucs(inst);
 
     inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 
@@ -2434,43 +2854,36 @@ int pt_main(int argc, char **argv)
      */
     palette_reset(inst);
 
+    inst->width = inst->cfg.width;
+    inst->height = inst->cfg.height;
+
     inst->area = gtk_drawing_area_new();
     gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),
                          inst->font_width * inst->cfg.width + 2*inst->cfg.window_border,
                          inst->font_height * inst->cfg.height + 2*inst->cfg.window_border);
-    if (inst->cfg.scrollbar) {
-       inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
-       inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
-    }
+    inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
+    inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
     inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
-    if (inst->cfg.scrollbar) {
-       if (inst->cfg.scrollbar_on_left)
-           gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);
-       else
-           gtk_box_pack_end(inst->hbox, inst->sbar, FALSE, FALSE, 0);
-    }
+    /*
+     * We always create the scrollbar; it remains invisible if
+     * unwanted, so we can pop it up quickly if it suddenly becomes
+     * desirable.
+     */
+    if (inst->cfg.scrollbar_on_left)
+        gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);
     gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0);
+    if (!inst->cfg.scrollbar_on_left)
+        gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);
 
     gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
 
-    {
-       GdkGeometry geom;
-       geom.min_width = inst->font_width + 2*inst->cfg.window_border;
-       geom.min_height = inst->font_height + 2*inst->cfg.window_border;
-       geom.max_width = geom.max_height = -1;
-       geom.base_width = 2*inst->cfg.window_border;
-       geom.base_height = 2*inst->cfg.window_border;
-       geom.width_inc = inst->font_width;
-       geom.height_inc = inst->font_height;
-       geom.min_aspect = geom.max_aspect = 0;
-       gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom,
-                                     GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
-                                     GDK_HINT_RESIZE_INC);
-    }
+    set_geom_hints(inst);
 
     gtk_widget_show(inst->area);
     if (inst->cfg.scrollbar)
        gtk_widget_show(inst->sbar);
+    else
+       gtk_widget_hide(inst->sbar);
     gtk_widget_show(GTK_WIDGET(inst->hbox));
 
     if (inst->gotpos) {
@@ -2530,6 +2943,7 @@ int pt_main(int argc, char **argv)
     {
        GtkWidget *menuitem;
        char *s;
+       extern const int use_event_log, new_session, saved_sessions;
 
        inst->menu = gtk_menu_new();
 
@@ -2542,6 +2956,40 @@ int pt_main(int argc, char **argv)
        gtk_signal_connect(GTK_OBJECT(menuitem), "activate", \
                               GTK_SIGNAL_FUNC(func), inst); \
 } while (0)
+       if (new_session)
+           MKMENUITEM("New Session", new_session_menuitem);
+        MKMENUITEM("Duplicate Session", dup_session_menuitem);
+       if (saved_sessions) {
+           struct sesslist sesslist;
+           int i;
+
+           inst->sessionsmenu = gtk_menu_new();
+
+           get_sesslist(&sesslist, TRUE);
+           for (i = 1; i < sesslist.nsessions; i++) {
+               menuitem = gtk_menu_item_new_with_label(sesslist.sessions[i]);
+               gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+               gtk_widget_show(menuitem);
+               gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+                                   dupstr(sesslist.sessions[i]));
+               gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                                  GTK_SIGNAL_FUNC(saved_session_menuitem),
+                                  inst);
+               gtk_signal_connect(GTK_OBJECT(menuitem), "destroy",
+                                  GTK_SIGNAL_FUNC(saved_session_freedata),
+                                  inst);
+           }
+           get_sesslist(&sesslist, FALSE);
+
+           MKMENUITEM("Saved Sessions", NULL);
+           gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
+                                     inst->sessionsmenu);
+       }
+       MKMENUITEM(NULL, NULL);
+        MKMENUITEM("Change Settings", change_settings_menuitem);
+       MKMENUITEM(NULL, NULL);
+       if (use_event_log)
+           MKMENUITEM("Event Log", event_log_menuitem);
        MKMENUITEM("Special Commands", NULL);
        inst->specialsmenu = gtk_menu_new();
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);
@@ -2550,6 +2998,7 @@ int pt_main(int argc, char **argv)
        inst->specialsitem2 = menuitem;
        MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
        MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
+       MKMENUITEM("Copy All", copy_all_menuitem);
        MKMENUITEM(NULL, NULL);
        s = dupcat("About ", appname, NULL);
        MKMENUITEM(s, about_menuitem);
@@ -2564,6 +3013,8 @@ int pt_main(int argc, char **argv)
     inst->currcursor = inst->textcursor;
     show_mouseptr(inst, 1);
 
+    inst->eventlogstuff = eventlogstuff_new();
+
     inst->term = term_init(&inst->cfg, &inst->ucsdata, inst);
     inst->logctx = log_init(inst, &inst->cfg);
     term_provide_logctx(inst->term, inst->logctx);
@@ -2576,7 +3027,7 @@ int pt_main(int argc, char **argv)
     {
        char *realhost, *error;
 
-       error = inst->back->init((void *)inst->term, &inst->backhandle,
+       error = inst->back->init((void *)inst, &inst->backhandle,
                                  &inst->cfg, inst->cfg.host, inst->cfg.port,
                                  &realhost, inst->cfg.tcp_nodelay);
 
@@ -2598,7 +3049,7 @@ int pt_main(int argc, char **argv)
         }
     }
     inst->back->provide_logctx(inst->backhandle, inst->logctx);
-    update_specials_menu(inst->term);
+    update_specials_menu(inst);
 
     term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle);