Support the SSH-2 mechanism for sending signals to a running session. Neither
[u/mdw/putty] / unix / pterm.c
index e3ea2f9..ca2dab8 100644 (file)
@@ -22,6 +22,7 @@
 #include <gdk/gdkx.h>
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include <X11/Xatom.h>
 
 #define PUTTY_DO_GLOBALS              /* actually _define_ globals */
 
@@ -43,7 +44,8 @@ struct gui_data {
     GtkWidget *window, *area, *sbar;
     GtkBox *hbox;
     GtkAdjustment *sbar_adjust;
-    GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2;
+    GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
+       *restartitem;
     GtkWidget *sessionsmenu;
     GdkPixmap *pixmap;
     GdkFont *fonts[4];                 /* normal, bold, wide, widebold */
@@ -93,6 +95,8 @@ static int send_raw_mouse;
 
 static char *app_name = "pterm";
 
+static void start_backend(struct gui_data *inst);
+
 char *x_get_default(const char *key)
 {
     return XGetDefault(GDK_DISPLAY(), app_name, key);
@@ -710,7 +714,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                 * in xterm function key mode we change which two...
                 */
              case GDK_KP_Add:
-               if (inst->cfg.funky_type == 2) {
+               if (inst->cfg.funky_type == FUNKY_XTERM) {
                    if (event->state & GDK_SHIFT_MASK)
                        xkey = 'l';
                    else
@@ -840,7 +844,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                break;
            }
            /* Reorder edit keys to physical order */
-           if (inst->cfg.funky_type == 3 && code <= 6)
+           if (inst->cfg.funky_type == FUNKY_VT400 && code <= 6)
                code = "\0\2\1\4\5\3\6"[code];
 
            if (inst->term->vt52_mode && code > 0 && code <= 6) {
@@ -849,7 +853,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                goto done;
            }
 
-           if (inst->cfg.funky_type == 5 &&     /* SCO function keys */
+           if (inst->cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
                code >= 11 && code <= 34) {
                char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
                int index = 0;
@@ -873,7 +877,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                use_ucsoutput = FALSE;
                goto done;
            }
-           if (inst->cfg.funky_type == 5 &&     /* SCO small keypad */
+           if (inst->cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
                code >= 1 && code <= 6) {
                char codes[] = "HL.FIG";
                if (code == 3) {
@@ -885,7 +889,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                use_ucsoutput = FALSE;
                goto done;
            }
-           if ((inst->term->vt52_mode || inst->cfg.funky_type == 4) &&
+           if ((inst->term->vt52_mode || inst->cfg.funky_type == FUNKY_VT100P) &&
                code >= 11 && code <= 24) {
                int offt = 0;
                if (code > 15)
@@ -901,12 +905,12 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                use_ucsoutput = FALSE;
                goto done;
            }
-           if (inst->cfg.funky_type == 1 && code >= 11 && code <= 15) {
+           if (inst->cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
                end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11);
                use_ucsoutput = FALSE;
                goto done;
            }
-           if (inst->cfg.funky_type == 2 && code >= 11 && code <= 14) {
+           if (inst->cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
                if (inst->term->vt52_mode)
                    end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11);
                else
@@ -980,7 +984,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
             * should never matter.
             */
            output[end] = '\0';        /* NUL-terminate */
-           ldisc_send(inst->ldisc, output+start, -2, 1);
+           if (inst->ldisc)
+               ldisc_send(inst->ldisc, output+start, -2, 1);
        } else if (!inst->direct_to_font) {
            if (!use_ucsoutput) {
                /*
@@ -995,21 +1000,24 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                 * far as I can tell, and it's poorly documented
                 * even in 2.0, so it'll have to wait.
                 */
-               lpage_send(inst->ldisc, CS_ISO8859_1, output+start,
-                          end-start, 1);
+               if (inst->ldisc)
+                   lpage_send(inst->ldisc, CS_ISO8859_1, output+start,
+                              end-start, 1);
            } else {
                /*
                 * We generated our own Unicode key data from the
                 * keysym, so use that instead.
                 */
-               luni_send(inst->ldisc, ucsoutput+start, end-start, 1);
+               if (inst->ldisc)
+                   luni_send(inst->ldisc, ucsoutput+start, end-start, 1);
            }
        } else {
            /*
             * In direct-to-font mode, we just send the string
             * exactly as we received it.
             */
-           ldisc_send(inst->ldisc, output+start, end-start, 1);
+           if (inst->ldisc)
+               ldisc_send(inst->ldisc, output+start, end-start, 1);
        }
 
        show_mouseptr(inst, 0);
@@ -1133,6 +1141,17 @@ gint timer_func(gpointer data)
        if (inst->cfg.close_on_exit == FORCE_ON ||
            (inst->cfg.close_on_exit == AUTO && exitcode == 0))
            exit(0);                   /* just go. */
+       if (inst->ldisc) {
+           ldisc_free(inst->ldisc);
+           inst->ldisc = NULL;
+       }
+       if (inst->back) {
+           inst->back->free(inst->backhandle);
+           inst->backhandle = NULL;
+           inst->back = NULL;
+           update_specials_menu(inst);
+       }
+       gtk_widget_show(inst->restartitem);
     }
 
     term_update(inst->term);
@@ -1332,6 +1351,51 @@ void palette_reset(void *frontend)
     set_window_background(inst);
 }
 
+/* Ensure that all the cut buffers exist - according to the ICCCM, we must
+ * do this before we start using cut buffers.
+ */
+void init_cutbuffers()
+{
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, "", 0);
+}
+
+/* Store the data in a cut-buffer. */
+void store_cutbuffer(char * ptr, int len)
+{
+    /* ICCCM says we must rotate the buffers before storing to buffer 0. */
+    XRotateBuffers(GDK_DISPLAY(), 1);
+    XStoreBytes(GDK_DISPLAY(), ptr, len);
+}
+
+/* Retrieve data from a cut-buffer.
+ * Returned data needs to be freed with XFree().
+ */
+char * retrieve_cutbuffer(int * nbytes)
+{
+    char * ptr;
+    ptr = XFetchBytes(GDK_DISPLAY(), nbytes);
+    if (*nbytes <= 0 && ptr != 0) {
+       XFree(ptr);
+       ptr = 0;
+    }
+    return ptr;
+}
+
 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
@@ -1378,7 +1442,10 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
            memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems);
            inst->pasteout_data_ctext_len = tp.nitems;
            XFree(tp.value);
-       }
+       } else {
+            inst->pasteout_data_ctext = NULL;
+            inst->pasteout_data_ctext_len = 0;
+        }
     } else {
        inst->pasteout_data_utf8 = NULL;
        inst->pasteout_data_utf8_len = 0;
@@ -1400,6 +1467,8 @@ void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
            sresize(inst->pasteout_data, inst->pasteout_data_len, char);
     }
 
+    store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len);
+
     if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY,
                                inst->input_event_time)) {
        gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
@@ -1496,6 +1565,7 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
     char *text;
     int length, count, ret;
     int free_list_required = 0;
+    int free_required = 0;
     int charset;
 
     if (seldata->target == utf8_string_atom && seldata->length <= 0) {
@@ -1521,42 +1591,56 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
     }
 
     /*
-     * Any other failure should just go foom.
+     * If we have data, but it's not of a type we can deal with,
+     * we have to ignore the data.
      */
-    if (seldata->length <= 0 ||
-       (seldata->type != GDK_SELECTION_TYPE_STRING &&
-        seldata->type != compound_text_atom &&
-        seldata->type != utf8_string_atom))
-       return;                        /* Nothing happens. */
-
+    if (seldata->length > 0 &&
+       seldata->type != GDK_SELECTION_TYPE_STRING &&
+       seldata->type != compound_text_atom &&
+       seldata->type != utf8_string_atom)
+       return;
 
     /*
-     * Convert COMPOUND_TEXT into UTF-8.
+     * If we have no data, try looking in a cut buffer.
      */
-    if (seldata->type == compound_text_atom) {
-       tp.value = seldata->data;
-       tp.encoding = (Atom) seldata->type;
-       tp.format = seldata->format;
-       tp.nitems = seldata->length;
-       ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp, &list, &count);
-       if (ret != 0 || count != 1) {
-           /*
-            * Compound text failed; fall back to STRING.
-            */
-           gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
-                                 GDK_SELECTION_TYPE_STRING,
-                                 inst->input_event_time);
+    if (seldata->length <= 0) {
+       text = retrieve_cutbuffer(&length);
+       if (length == 0)
            return;
-       }
-       text = list[0];
-       length = strlen(list[0]);
-       charset = CS_UTF8;
-       free_list_required = 1;
+       /* Xterm is rumoured to expect Latin-1, though I havn't checked the
+        * source, so use that as a de-facto standard. */
+       charset = CS_ISO8859_1;
+       free_required = 1;
     } else {
-       text = (char *)seldata->data;
-       length = seldata->length;
-       charset = (seldata->type == utf8_string_atom ?
-                  CS_UTF8 : inst->ucsdata.line_codepage);
+       /*
+        * Convert COMPOUND_TEXT into UTF-8.
+        */
+       if (seldata->type == compound_text_atom) {
+           tp.value = seldata->data;
+           tp.encoding = (Atom) seldata->type;
+           tp.format = seldata->format;
+           tp.nitems = seldata->length;
+           ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp,
+                                             &list, &count);
+           if (ret != 0 || count != 1) {
+               /*
+                * Compound text failed; fall back to STRING.
+                */
+               gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+                                     GDK_SELECTION_TYPE_STRING,
+                                     inst->input_event_time);
+               return;
+           }
+           text = list[0];
+           length = strlen(list[0]);
+           charset = CS_UTF8;
+           free_list_required = 1;
+       } else {
+           text = (char *)seldata->data;
+           length = seldata->length;
+           charset = (seldata->type == utf8_string_atom ?
+                      CS_UTF8 : inst->ucsdata.line_codepage);
+       }
     }
 
     if (inst->pastein_data)
@@ -1575,6 +1659,8 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
 
     if (free_list_required)
        XFreeStringList(list);
+    if (free_required)
+       XFree(text);
 }
 
 gint idle_paste_func(gpointer data)
@@ -1712,15 +1798,21 @@ void free_ctx(Context ctx)
  *
  * We are allowed to fiddle with the contents of `text'.
  */
-void do_text_internal(Context ctx, int x, int y, char *text, int len,
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
                      unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
     GdkGC *gc = dctx->gc;
-
+    int ncombining, combining;
     int nfg, nbg, t, fontid, shadow, rlen, widefactor;
 
+    if (attr & TATTR_COMBINING) {
+       ncombining = len;
+       len = 1;
+    } else
+       ncombining = 1;
+
     nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
     nfg = 2 * (nfg & 0xF) + (nfg & 0x10 ? 1 : 0);
     nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
@@ -1755,7 +1847,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
            shadow = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -1788,9 +1880,9 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
        wchar_t *wcs;
        int i;
 
-       wcs = snewn(len+1, wchar_t);
-       for (i = 0; i < len; i++) {
-           wcs[i] = (wchar_t) ((attr & CSET_MASK) + (text[i] & CHAR_MASK));
+       wcs = snewn(len*ncombining+1, wchar_t);
+       for (i = 0; i < len*ncombining; i++) {
+           wcs[i] = text[i];
        }
 
        if (inst->fonts[fontid] == NULL) {
@@ -1821,33 +1913,40 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
             * FIXME: when we have a wide-char equivalent of
             * from_unicode, use it instead of this.
             */
-           for (i = 0; i <= len; i++)
-               gwcs[i] = wcs[i];
-           gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
-                            x*inst->font_width+inst->cfg.window_border,
-                            y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                            gwcs, len*2);
+           for (combining = 0; combining < ncombining; combining++) {
+               for (i = 0; i <= len; i++)
+                   gwcs[i] = wcs[i + combining];
+               gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
+                                x*inst->font_width+inst->cfg.window_border,
+                                y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                                gwcs, len*2);
+               if (shadow)
+                   gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc,
+                                    x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
+                                    y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                                    gwcs, len*2);
+           }
            sfree(gwcs);
        } else {
            gcs = snewn(len+1, gchar);
-           wc_to_mb(inst->fontinfo[fontid].charset, 0,
-                    wcs, len, gcs, len, ".", NULL, NULL);
-           gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
-                         x*inst->font_width+inst->cfg.window_border,
-                         y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                         gcs, len);
+           for (combining = 0; combining < ncombining; combining++) {
+               wc_to_mb(inst->fontinfo[fontid].charset, 0,
+                        wcs + combining, len, gcs, len, ".", NULL, NULL);
+               gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
+                             x*inst->font_width+inst->cfg.window_border,
+                             y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                             gcs, len);
+               if (shadow)
+                   gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
+                                 x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset,
+                                 y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
+                                 gcs, len);
+           }
            sfree(gcs);
        }
        sfree(wcs);
     }
 
-    if (shadow) {
-       gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc,
-                     x*inst->font_width+inst->cfg.window_border + inst->cfg.shadowboldoffset,
-                     y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,
-                     text, len);
-    }
-
     if (attr & ATTR_UNDER) {
        int uheight = inst->fonts[0]->ascent + 1;
        if (uheight >= inst->font_height)
@@ -1858,7 +1957,7 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                      y*inst->font_height + uheight + inst->cfg.window_border);
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        /*
         * I can't find any plausible StretchBlt equivalent in the
         * X server, so I'm going to do this the slow and painful
@@ -1874,13 +1973,13 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                            y*inst->font_height+inst->cfg.window_border,
                            x*inst->font_width+inst->cfg.window_border + 2*i+1,
                            y*inst->font_height+inst->cfg.window_border,
-                           len * inst->font_width - i, inst->font_height);
+                           len * widefactor * inst->font_width - i, inst->font_height);
        }
        len *= 2;
-       if (lattr != LATTR_WIDE) {
+       if ((lattr & LATTR_MODE) != LATTR_WIDE) {
            int dt, db;
            /* Now stretch vertically, in the same way. */
-           if (lattr == LATTR_BOT)
+           if ((lattr & LATTR_MODE) == LATTR_BOT)
                dt = 0, db = 1;
            else
                dt = 1, db = 0;
@@ -1888,15 +1987,15 @@ void do_text_internal(Context ctx, int x, int y, char *text, int len,
                gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap,
                                x*inst->font_width+inst->cfg.window_border,
                                y*inst->font_height+inst->cfg.window_border+dt*i+db,
-                               x*widefactor*inst->font_width+inst->cfg.window_border,
+                               x*inst->font_width+inst->cfg.window_border,
                                y*inst->font_height+inst->cfg.window_border+dt*(i+1),
-                               len * inst->font_width, inst->font_height-i-1);
+                               len * widefactor * inst->font_width, inst->font_height-i-1);
            }
        }
     }
 }
 
-void do_text(Context ctx, int x, int y, char *text, int len,
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
             unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
@@ -1912,7 +2011,7 @@ void do_text(Context ctx, int x, int y, char *text, int len,
        widefactor = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -1929,14 +2028,14 @@ void do_text(Context ctx, int x, int y, char *text, int len,
                    len*widefactor*inst->font_width, inst->font_height);
 }
 
-void do_cursor(Context ctx, int x, int y, char *text, int len,
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
               unsigned long attr, int lattr)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
     GdkGC *gc = dctx->gc;
 
-    int passive, widefactor;
+    int active, passive, widefactor;
 
     if (attr & TATTR_PASCURS) {
        attr &= ~TATTR_PASCURS;
@@ -1945,16 +2044,21 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
        passive = 0;
     if ((attr & TATTR_ACTCURS) && inst->cfg.cursor_type != 0) {
        attr &= ~TATTR_ACTCURS;
-    }
+        active = 1;
+    } else
+        active = 0;
     do_text_internal(ctx, x, y, text, len, attr, lattr);
 
+    if (attr & TATTR_COMBINING)
+       len = 1;
+
     if (attr & ATTR_WIDE) {
        widefactor = 2;
     } else {
        widefactor = 1;
     }
 
-    if (lattr != LATTR_NORM) {
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
        x *= 2;
        if (x >= inst->term->cols)
            return;
@@ -1974,7 +2078,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
            gdk_draw_rectangle(inst->pixmap, gc, 0,
                               x*inst->font_width+inst->cfg.window_border,
                               y*inst->font_height+inst->cfg.window_border,
-                              len*inst->font_width-1, inst->font_height-1);
+                              len*widefactor*inst->font_width-1, inst->font_height-1);
        }
     } else {
        int uheight;
@@ -1982,7 +2086,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
 
        int char_width;
 
-       if ((attr & ATTR_WIDE) || lattr != LATTR_NORM)
+       if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)
            char_width = 2*inst->font_width;
        else
            char_width = inst->font_width;
@@ -1996,7 +2100,7 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
            starty = y * inst->font_height + inst->cfg.window_border + uheight;
            dx = 1;
            dy = 0;
-           length = len * char_width;
+           length = len * widefactor * char_width;
        } else {
            int xadjust = 0;
            if (attr & TATTR_RIGHTCURS)
@@ -2017,10 +2121,10 @@ void do_cursor(Context ctx, int x, int y, char *text, int len,
                startx += dx;
                starty += dy;
            }
-       } else {
+       } else if (active) {
            gdk_draw_line(inst->pixmap, gc, startx, starty,
                          startx + (length-1) * dx, starty + (length-1) * dy);
-       }
+       } /* else no cursor (e.g., blinked off) */
     }
 
     gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
@@ -2558,23 +2662,27 @@ void setup_fonts_ucs(struct gui_data *inst)
     }
     font_charset = set_font_info(inst, 0);
 
-    if (inst->cfg.boldfont.name[0]) {
-       name = inst->cfg.boldfont.name;
-       guessed = FALSE;
+    if (inst->cfg.shadowbold) {
+       inst->fonts[1] = NULL;
     } else {
-       name = guess_derived_font_name(inst->fonts[0], TRUE, FALSE);
-       guessed = TRUE;
-    }
-    inst->fonts[1] = name ? gdk_font_load(name) : NULL;
-    if (inst->fonts[1]) {
-       set_font_info(inst, 1);
-    } else if (!guessed) {
-       fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname,
-               inst->cfg.boldfont.name);
-       exit(1);
+       if (inst->cfg.boldfont.name[0]) {
+           name = inst->cfg.boldfont.name;
+           guessed = FALSE;
+       } else {
+           name = guess_derived_font_name(inst->fonts[0], TRUE, FALSE);
+           guessed = TRUE;
+       }
+       inst->fonts[1] = name ? gdk_font_load(name) : NULL;
+       if (inst->fonts[1]) {
+           set_font_info(inst, 1);
+       } else if (!guessed) {
+           fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname,
+                   inst->cfg.boldfont.name);
+           exit(1);
+       }
+       if (guessed)
+           sfree(name);
     }
-    if (guessed)
-       sfree(name);
 
     if (inst->cfg.widefont.name[0]) {
        name = inst->cfg.widefont.name;
@@ -2594,39 +2702,43 @@ void setup_fonts_ucs(struct gui_data *inst)
     if (guessed)
        sfree(name);
 
-    if (inst->cfg.wideboldfont.name[0]) {
-       name = inst->cfg.wideboldfont.name;
-       guessed = FALSE;
+    if (inst->cfg.shadowbold) {
+       inst->fonts[3] = NULL;
     } else {
-       /*
-        * Here we have some choices. We can widen the bold font,
-        * bolden the wide font, or widen and bolden the standard
-        * font. Try them all, in that order!
-        */
-       if (inst->cfg.widefont.name[0])
-           name = guess_derived_font_name(inst->fonts[2], TRUE, FALSE);
-       else if (inst->cfg.boldfont.name[0])
-           name = guess_derived_font_name(inst->fonts[1], FALSE, TRUE);
-       else
-           name = guess_derived_font_name(inst->fonts[0], TRUE, TRUE);
-       guessed = TRUE;
-    }
-    inst->fonts[3] = name ? gdk_font_load(name) : NULL;
-    if (inst->fonts[3]) {
-       set_font_info(inst, 3);
-    } else if (!guessed) {
-       fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n", appname,
-               inst->cfg.wideboldfont.name);
-       exit(1);
+       if (inst->cfg.wideboldfont.name[0]) {
+           name = inst->cfg.wideboldfont.name;
+           guessed = FALSE;
+       } else {
+           /*
+            * Here we have some choices. We can widen the bold font,
+            * bolden the wide font, or widen and bolden the standard
+            * font. Try them all, in that order!
+            */
+           if (inst->cfg.widefont.name[0])
+               name = guess_derived_font_name(inst->fonts[2], TRUE, FALSE);
+           else if (inst->cfg.boldfont.name[0])
+               name = guess_derived_font_name(inst->fonts[1], FALSE, TRUE);
+           else
+               name = guess_derived_font_name(inst->fonts[0], TRUE, TRUE);
+           guessed = TRUE;
+       }
+       inst->fonts[3] = name ? gdk_font_load(name) : NULL;
+       if (inst->fonts[3]) {
+           set_font_info(inst, 3);
+       } else if (!guessed) {
+           fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n", appname,
+                   inst->cfg.wideboldfont.name);
+           exit(1);
+       }
+       if (guessed)
+           sfree(name);
     }
-    if (guessed)
-       sfree(name);
 
     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,
+    inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage,
+                                   inst->cfg.utf8_override, font_charset,
                                    inst->cfg.vtmode);
 }
 
@@ -2656,7 +2768,8 @@ void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
     term_pwron(inst->term);
-    ldisc_send(inst->ldisc, NULL, 0, 0);
+    if (inst->ldisc)
+       ldisc_send(inst->ldisc, NULL, 0, 0);
 }
 
 void copy_all_menuitem(GtkMenuItem *item, gpointer data)
@@ -2671,7 +2784,8 @@ void special_menuitem(GtkMenuItem *item, gpointer data)
     int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
                                                   "user-data"));
 
-    inst->back->special(inst->backhandle, code);
+    if (inst->back)
+       inst->back->special(inst->backhandle, code);
 }
 
 void about_menuitem(GtkMenuItem *item, gpointer data)
@@ -2712,11 +2826,13 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
          * Flush the line discipline's edit buffer in the case
          * where local editing has just been disabled.
          */
-        ldisc_send(inst->ldisc, NULL, 0, 0);
+        if (inst->ldisc)
+           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);
+        if (inst->back)
+           inst->back->reconfig(inst->backhandle, &cfg2);
 
         /*
          * Just setting inst->cfg is sufficient to cause colour
@@ -2777,7 +2893,8 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
             strcmp(oldcfg.widefont.name, cfg2.widefont.name) ||
             strcmp(oldcfg.wideboldfont.name, cfg2.wideboldfont.name) ||
             strcmp(oldcfg.line_codepage, cfg2.line_codepage) ||
-           oldcfg.vtmode != cfg2.vtmode) {
+           oldcfg.vtmode != cfg2.vtmode ||
+           oldcfg.shadowbold != cfg2.shadowbold) {
             setup_fonts_ucs(inst);
             need_size = 1;
         } else
@@ -3006,6 +3123,17 @@ void new_session_menuitem(GtkMenuItem *item, gpointer data)
     fork_and_exec_self(inst, -1, NULL);
 }
 
+void restart_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+
+    if (!inst->back) {
+       logevent(inst, "----- Session restarted -----");
+       start_backend(inst);
+       inst->exited = FALSE;
+    }
+}
+
 void saved_session_menuitem(GtkMenuItem *item, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
@@ -3027,23 +3155,56 @@ void update_specials_menu(void *frontend)
 
     const struct telnet_special *specials;
 
-    specials = inst->back->get_specials(inst->backhandle);
+    if (inst->back)
+       specials = inst->back->get_specials(inst->backhandle);
+    else
+       specials = NULL;
+
+    /* I believe this disposes of submenus too. */
     gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
                          (GtkCallback)gtk_widget_destroy, NULL);
     if (specials) {
        int i;
-       GtkWidget *menuitem;
-       for (i = 0; specials[i].name; i++) {
-           if (*specials[i].name) {
+       GtkWidget *menu = inst->specialsmenu;
+       /* A lame "stack" for submenus that will do for now. */
+       GtkWidget *saved_menu = NULL;
+       int nesting = 1;
+       for (i = 0; nesting > 0; i++) {
+           GtkWidget *menuitem = NULL;
+           switch (specials[i].code) {
+             case TS_SUBMENU:
+               assert (nesting < 2);
+               saved_menu = menu; /* XXX lame stacking */
+               menu = gtk_menu_new();
+               menuitem = gtk_menu_item_new_with_label(specials[i].name);
+               gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+               gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
+               gtk_widget_show(menuitem);
+               menuitem = NULL;
+               nesting++;
+               break;
+             case TS_EXITMENU:
+               nesting--;
+               if (nesting) {
+                   menu = saved_menu; /* XXX lame stacking */
+                   saved_menu = NULL;
+               }
+               break;
+             case TS_SEP:
+               menuitem = gtk_menu_item_new();
+               break;
+             default:
                menuitem = gtk_menu_item_new_with_label(specials[i].name);
                gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
                                    GINT_TO_POINTER(specials[i].code));
                gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                                   GTK_SIGNAL_FUNC(special_menuitem), inst);
-           } else
-               menuitem = gtk_menu_item_new();
-           gtk_container_add(GTK_CONTAINER(inst->specialsmenu), menuitem);
-           gtk_widget_show(menuitem);
+               break;
+           }
+           if (menuitem) {
+               gtk_container_add(GTK_CONTAINER(menu), menuitem);
+               gtk_widget_show(menuitem);
+           }
        }
        gtk_widget_show(inst->specialsitem1);
        gtk_widget_show(inst->specialsitem2);
@@ -3053,9 +3214,50 @@ void update_specials_menu(void *frontend)
     }
 }
 
-int pt_main(int argc, char **argv)
+static void start_backend(struct gui_data *inst)
 {
     extern Backend *select_backend(Config *cfg);
+    char *realhost;
+    const char *error;
+
+    inst->back = select_backend(&inst->cfg);
+
+    error = inst->back->init((void *)inst, &inst->backhandle,
+                            &inst->cfg, inst->cfg.host, inst->cfg.port,
+                            &realhost, inst->cfg.tcp_nodelay,
+                            inst->cfg.tcp_keepalives);
+
+    if (error) {
+       char *msg = dupprintf("Unable to open connection to %s:\n%s",
+                             inst->cfg.host, error);
+       inst->exited = TRUE;
+       fatal_message_box(inst->window, msg);
+       sfree(msg);
+       exit(0);
+    }
+
+    if (inst->cfg.wintitle[0]) {
+       set_title(inst, inst->cfg.wintitle);
+       set_icon(inst, inst->cfg.wintitle);
+    } else {
+       char *title = make_default_wintitle(realhost);
+       set_title(inst, title);
+       set_icon(inst, title);
+       sfree(title);
+    }
+    inst->back->provide_logctx(inst->backhandle, inst->logctx);
+
+    term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle);
+
+    inst->ldisc =
+       ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle,
+                    inst);
+
+    gtk_widget_hide(inst->restartitem);
+}
+
+int pt_main(int argc, char **argv)
+{
     extern int cfgbox(Config *cfg);
     struct gui_data *inst;
 
@@ -3108,6 +3310,7 @@ int pt_main(int argc, char **argv)
         utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
 
     setup_fonts_ucs(inst);
+    init_cutbuffers();
 
     inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 
@@ -3220,6 +3423,9 @@ int pt_main(int argc, char **argv)
 } while (0)
        if (new_session)
            MKMENUITEM("New Session", new_session_menuitem);
+        MKMENUITEM("Restart Session", restart_session_menuitem);
+       inst->restartitem = menuitem;
+       gtk_widget_hide(inst->restartitem);
         MKMENUITEM("Duplicate Session", dup_session_menuitem);
        if (saved_sessions) {
            struct sesslist sesslist;
@@ -3258,6 +3464,8 @@ int pt_main(int argc, char **argv)
        inst->specialsitem1 = menuitem;
        MKMENUITEM(NULL, NULL);
        inst->specialsitem2 = menuitem;
+       gtk_widget_hide(inst->specialsitem1);
+       gtk_widget_hide(inst->specialsitem2);
        MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
        MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
        MKMENUITEM("Copy All", copy_all_menuitem);
@@ -3285,41 +3493,8 @@ int pt_main(int argc, char **argv)
 
     term_size(inst->term, inst->cfg.height, inst->cfg.width, inst->cfg.savelines);
 
-    inst->back = select_backend(&inst->cfg);
-    {
-       char *realhost;
-       const char *error;
-
-       error = inst->back->init((void *)inst, &inst->backhandle,
-                                 &inst->cfg, inst->cfg.host, inst->cfg.port,
-                                 &realhost, inst->cfg.tcp_nodelay);
-
-       if (error) {
-           char *msg = dupprintf("Unable to open connection to %s:\n%s",
-                                  inst->cfg.host, error);
-            inst->exited = TRUE;
-           fatal_message_box(inst->window, msg);
-            sfree(msg);
-           return 0;
-       }
+    start_backend(inst);
 
-        if (inst->cfg.wintitle[0]) {
-            set_title(inst, inst->cfg.wintitle);
-            set_icon(inst, inst->cfg.wintitle);
-       } else {
-            char *title = make_default_wintitle(realhost);
-            set_title(inst, title);
-            set_icon(inst, title);
-            sfree(title);
-        }
-    }
-    inst->back->provide_logctx(inst->backhandle, inst->logctx);
-    update_specials_menu(inst);
-
-    term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle);
-
-    inst->ldisc =
-       ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle, inst);
     ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
 
     /* now we're reday to deal with the child exit handler being