OSDN Git Service

Add VC++ Project files for PuTTY DLL without exported functions.
[ffftp/ffftp.git] / putty / UNIX / GTKWIN.C
diff --git a/putty/UNIX/GTKWIN.C b/putty/UNIX/GTKWIN.C
new file mode 100644 (file)
index 0000000..f2a13ae
--- /dev/null
@@ -0,0 +1,3645 @@
+/*\r
+ * gtkwin.c: the main code that runs a PuTTY terminal emulator and\r
+ * backend in a GTK window.\r
+ */\r
+\r
+#define _GNU_SOURCE\r
+\r
+#include <string.h>\r
+#include <assert.h>\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <signal.h>\r
+#include <stdio.h>\r
+#include <time.h>\r
+#include <errno.h>\r
+#include <fcntl.h>\r
+#include <unistd.h>\r
+#include <sys/types.h>\r
+#include <sys/wait.h>\r
+#include <gtk/gtk.h>\r
+#include <gdk/gdkkeysyms.h>\r
+#include <gdk/gdkx.h>\r
+#include <X11/Xlib.h>\r
+#include <X11/Xutil.h>\r
+#include <X11/Xatom.h>\r
+\r
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */\r
+\r
+#include "putty.h"\r
+#include "terminal.h"\r
+#include "gtkfont.h"\r
+\r
+#define CAT2(x,y) x ## y\r
+#define CAT(x,y) CAT2(x,y)\r
+#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}\r
+\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+ASSERT(sizeof(long) <= sizeof(gsize));\r
+#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)\r
+#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)\r
+#else /* Gtk 1.2 */\r
+ASSERT(sizeof(long) <= sizeof(gpointer));\r
+#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))\r
+#define GPOINTER_TO_LONG(p) ((long)(p))\r
+#endif\r
+\r
+/* Colours come in two flavours: configurable, and xterm-extended. */\r
+#define NCFGCOLOURS (lenof(((Config *)0)->colours))\r
+#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */\r
+#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)\r
+\r
+GdkAtom compound_text_atom, utf8_string_atom;\r
+\r
+extern char **pty_argv;               /* declared in pty.c */\r
+extern int use_pty_argv;\r
+\r
+/*\r
+ * Timers are global across all sessions (even if we were handling\r
+ * multiple sessions, which we aren't), so the current timer ID is\r
+ * a global variable.\r
+ */\r
+static guint timer_id = 0;\r
+\r
+struct gui_data {\r
+    GtkWidget *window, *area, *sbar;\r
+    GtkBox *hbox;\r
+    GtkAdjustment *sbar_adjust;\r
+    GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,\r
+       *restartitem;\r
+    GtkWidget *sessionsmenu;\r
+    GdkPixmap *pixmap;\r
+    unifont *fonts[4];                 /* normal, bold, wide, widebold */\r
+    int xpos, ypos, gotpos, gravity;\r
+    GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;\r
+    GdkColor cols[NALLCOLOURS];\r
+    GdkColormap *colmap;\r
+    wchar_t *pastein_data;\r
+    int direct_to_font;\r
+    int pastein_data_len;\r
+    char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;\r
+    int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;\r
+    int font_width, font_height;\r
+    int width, height;\r
+    int ignore_sbar;\r
+    int mouseptr_visible;\r
+    int busy_status;\r
+    guint term_paste_idle_id;\r
+    guint term_exit_idle_id;\r
+    int alt_keycode;\r
+    int alt_digits;\r
+    char wintitle[sizeof(((Config *)0)->wintitle)];\r
+    char icontitle[sizeof(((Config *)0)->wintitle)];\r
+    int master_fd, master_func_id;\r
+    void *ldisc;\r
+    Backend *back;\r
+    void *backhandle;\r
+    Terminal *term;\r
+    void *logctx;\r
+    int exited;\r
+    struct unicode_data ucsdata;\r
+    Config cfg;\r
+    void *eventlogstuff;\r
+    char *progname, **gtkargvstart;\r
+    int ngtkargs;\r
+    guint32 input_event_time; /* Timestamp of the most recent input event. */\r
+    int reconfiguring;\r
+};\r
+\r
+struct draw_ctx {\r
+    GdkGC *gc;\r
+    struct gui_data *inst;\r
+};\r
+\r
+static int send_raw_mouse;\r
+\r
+static char *app_name = "pterm";\r
+\r
+static void start_backend(struct gui_data *inst);\r
+\r
+char *x_get_default(const char *key)\r
+{\r
+    return XGetDefault(GDK_DISPLAY(), app_name, key);\r
+}\r
+\r
+void connection_fatal(void *frontend, char *p, ...)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    va_list ap;\r
+    char *msg;\r
+    va_start(ap, p);\r
+    msg = dupvprintf(p, ap);\r
+    va_end(ap);\r
+    inst->exited = TRUE;\r
+    fatal_message_box(inst->window, msg);\r
+    sfree(msg);\r
+    if (inst->cfg.close_on_exit == FORCE_ON)\r
+        cleanup_exit(1);\r
+}\r
+\r
+/*\r
+ * Default settings that are specific to pterm.\r
+ */\r
+FontSpec platform_default_fontspec(const char *name)\r
+{\r
+    FontSpec ret;\r
+    if (!strcmp(name, "Font"))\r
+       strcpy(ret.name, "server:fixed");\r
+    else\r
+       *ret.name = '\0';\r
+    return ret;\r
+}\r
+\r
+Filename platform_default_filename(const char *name)\r
+{\r
+    Filename ret;\r
+    if (!strcmp(name, "LogFileName"))\r
+       strcpy(ret.path, "putty.log");\r
+    else\r
+       *ret.path = '\0';\r
+    return ret;\r
+}\r
+\r
+char *platform_default_s(const char *name)\r
+{\r
+    if (!strcmp(name, "SerialLine"))\r
+       return dupstr("/dev/ttyS0");\r
+    return NULL;\r
+}\r
+\r
+int platform_default_i(const char *name, int def)\r
+{\r
+    if (!strcmp(name, "CloseOnExit"))\r
+       return 2;  /* maps to FORCE_ON after painful rearrangement :-( */\r
+    if (!strcmp(name, "WinNameAlways"))\r
+       return 0;  /* X natively supports icon titles, so use 'em by default */\r
+    return def;\r
+}\r
+\r
+/* Dummy routine, only required in plink. */\r
+void ldisc_update(void *frontend, int echo, int edit)\r
+{\r
+}\r
+\r
+char *get_ttymode(void *frontend, const char *mode)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return term_get_ttymode(inst->term, mode);\r
+}\r
+\r
+int from_backend(void *frontend, int is_stderr, const char *data, int len)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return term_data(inst->term, is_stderr, data, len);\r
+}\r
+\r
+int from_backend_untrusted(void *frontend, const char *data, int len)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return term_data_untrusted(inst->term, data, len);\r
+}\r
+\r
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)p->frontend;\r
+    int ret;\r
+    ret = cmdline_get_passwd_input(p, in, inlen);\r
+    if (ret == -1)\r
+       ret = term_get_userpass_input(inst->term, p, in, inlen);\r
+    return ret;\r
+}\r
+\r
+void logevent(void *frontend, const char *string)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    log_eventlog(inst->logctx, string);\r
+\r
+    logevent_dlg(inst->eventlogstuff, string);\r
+}\r
+\r
+int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    if (which)\r
+       return inst->font_height;\r
+    else\r
+       return inst->font_width;\r
+}\r
+\r
+/*\r
+ * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)\r
+ * into a cooked one (SELECT, EXTEND, PASTE).\r
+ * \r
+ * In Unix, this is not configurable; the X button arrangement is\r
+ * rock-solid across all applications, everyone has a three-button\r
+ * mouse or a means of faking it, and there is no need to switch\r
+ * buttons around at all.\r
+ */\r
+static Mouse_Button translate_button(Mouse_Button button)\r
+{\r
+    /* struct gui_data *inst = (struct gui_data *)frontend; */\r
+\r
+    if (button == MBT_LEFT)\r
+       return MBT_SELECT;\r
+    if (button == MBT_MIDDLE)\r
+       return MBT_PASTE;\r
+    if (button == MBT_RIGHT)\r
+       return MBT_EXTEND;\r
+    return 0;                         /* shouldn't happen */\r
+}\r
+\r
+/*\r
+ * Return the top-level GtkWindow associated with a particular\r
+ * front end instance.\r
+ */\r
+void *get_window(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return inst->window;\r
+}\r
+\r
+/*\r
+ * Minimise or restore the window in response to a server-side\r
+ * request.\r
+ */\r
+void set_iconic(void *frontend, int iconic)\r
+{\r
+    /*\r
+     * GTK 1.2 doesn't know how to do this.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (iconic)\r
+       gtk_window_iconify(GTK_WINDOW(inst->window));\r
+    else\r
+       gtk_window_deiconify(GTK_WINDOW(inst->window));\r
+#endif\r
+}\r
+\r
+/*\r
+ * Move the window in response to a server-side request.\r
+ */\r
+void move_window(void *frontend, int x, int y)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    /*\r
+     * I assume that when the GTK version of this call is available\r
+     * we should use it. Not sure how it differs from the GDK one,\r
+     * though.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_window_move(GTK_WINDOW(inst->window), x, y);\r
+#else\r
+    gdk_window_move(inst->window->window, x, y);\r
+#endif\r
+}\r
+\r
+/*\r
+ * Move the window to the top or bottom of the z-order in response\r
+ * to a server-side request.\r
+ */\r
+void set_zorder(void *frontend, int top)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (top)\r
+       gdk_window_raise(inst->window->window);\r
+    else\r
+       gdk_window_lower(inst->window->window);\r
+}\r
+\r
+/*\r
+ * Refresh the window in response to a server-side request.\r
+ */\r
+void refresh_window(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    term_invalidate(inst->term);\r
+}\r
+\r
+/*\r
+ * Maximise or restore the window in response to a server-side\r
+ * request.\r
+ */\r
+void set_zoomed(void *frontend, int zoomed)\r
+{\r
+    /*\r
+     * GTK 1.2 doesn't know how to do this.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (zoomed)\r
+       gtk_window_maximize(GTK_WINDOW(inst->window));\r
+    else\r
+       gtk_window_unmaximize(GTK_WINDOW(inst->window));\r
+#endif\r
+}\r
+\r
+/*\r
+ * Report whether the window is iconic, for terminal reports.\r
+ */\r
+int is_iconic(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return !gdk_window_is_viewable(inst->window->window);\r
+}\r
+\r
+/*\r
+ * Report the window's position, for terminal reports.\r
+ */\r
+void get_window_pos(void *frontend, int *x, int *y)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    /*\r
+     * I assume that when the GTK version of this call is available\r
+     * we should use it. Not sure how it differs from the GDK one,\r
+     * though.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_window_get_position(GTK_WINDOW(inst->window), x, y);\r
+#else\r
+    gdk_window_get_position(inst->window->window, x, y);\r
+#endif\r
+}\r
+\r
+/*\r
+ * Report the window's pixel size, for terminal reports.\r
+ */\r
+void get_window_pixels(void *frontend, int *x, int *y)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    /*\r
+     * I assume that when the GTK version of this call is available\r
+     * we should use it. Not sure how it differs from the GDK one,\r
+     * though.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_window_get_size(GTK_WINDOW(inst->window), x, y);\r
+#else\r
+    gdk_window_get_size(inst->window->window, x, y);\r
+#endif\r
+}\r
+\r
+/*\r
+ * Return the window or icon title.\r
+ */\r
+char *get_window_title(void *frontend, int icon)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return icon ? inst->icontitle : inst->wintitle;\r
+}\r
+\r
+gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    if (!inst->exited && inst->cfg.warn_on_close) {\r
+       if (!reallyclose(inst))\r
+           return TRUE;\r
+    }\r
+    return FALSE;\r
+}\r
+\r
+static void update_mouseptr(struct gui_data *inst)\r
+{\r
+    switch (inst->busy_status) {\r
+      case BUSY_NOT:\r
+       if (!inst->mouseptr_visible) {\r
+           gdk_window_set_cursor(inst->area->window, inst->blankcursor);\r
+       } else if (send_raw_mouse) {\r
+           gdk_window_set_cursor(inst->area->window, inst->rawcursor);\r
+       } else {\r
+           gdk_window_set_cursor(inst->area->window, inst->textcursor);\r
+       }\r
+       break;\r
+      case BUSY_WAITING:    /* XXX can we do better? */\r
+      case BUSY_CPU:\r
+       /* We always display these cursors. */\r
+       gdk_window_set_cursor(inst->area->window, inst->waitcursor);\r
+       break;\r
+      default:\r
+       assert(0);\r
+    }\r
+}\r
+\r
+static void show_mouseptr(struct gui_data *inst, int show)\r
+{\r
+    if (!inst->cfg.hide_mouseptr)\r
+       show = 1;\r
+    inst->mouseptr_visible = show;\r
+    update_mouseptr(inst);\r
+}\r
+\r
+void draw_backing_rect(struct gui_data *inst)\r
+{\r
+    GdkGC *gc = gdk_gc_new(inst->area->window);\r
+    gdk_gc_set_foreground(gc, &inst->cols[258]);    /* default background */\r
+    gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,\r
+                      inst->cfg.width * inst->font_width + 2*inst->cfg.window_border,\r
+                      inst->cfg.height * inst->font_height + 2*inst->cfg.window_border);\r
+    gdk_gc_unref(gc);\r
+}\r
+\r
+gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    int w, h, need_size = 0;\r
+\r
+    /*\r
+     * See if the terminal size has changed, in which case we must\r
+     * let the terminal know.\r
+     */\r
+    w = (event->width - 2*inst->cfg.window_border) / inst->font_width;\r
+    h = (event->height - 2*inst->cfg.window_border) / inst->font_height;\r
+    if (w != inst->width || h != inst->height) {\r
+       inst->cfg.width = inst->width = w;\r
+       inst->cfg.height = inst->height = h;\r
+       need_size = 1;\r
+    }\r
+\r
+    if (inst->pixmap) {\r
+       gdk_pixmap_unref(inst->pixmap);\r
+       inst->pixmap = NULL;\r
+    }\r
+\r
+    inst->pixmap = gdk_pixmap_new(widget->window,\r
+                                 (inst->cfg.width * inst->font_width +\r
+                                  2*inst->cfg.window_border),\r
+                                 (inst->cfg.height * inst->font_height +\r
+                                  2*inst->cfg.window_border), -1);\r
+\r
+    draw_backing_rect(inst);\r
+\r
+    if (need_size && inst->term) {\r
+       term_size(inst->term, h, w, inst->cfg.savelines);\r
+    }\r
+\r
+    if (inst->term)\r
+       term_invalidate(inst->term);\r
+\r
+    return TRUE;\r
+}\r
+\r
+gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    /*\r
+     * Pass the exposed rectangle to terminal.c, which will call us\r
+     * back to do the actual painting.\r
+     */\r
+    if (inst->pixmap) {\r
+       gdk_draw_pixmap(widget->window,\r
+                       widget->style->fg_gc[GTK_WIDGET_STATE(widget)],\r
+                       inst->pixmap,\r
+                       event->area.x, event->area.y,\r
+                       event->area.x, event->area.y,\r
+                       event->area.width, event->area.height);\r
+    }\r
+    return TRUE;\r
+}\r
+\r
+#define KEY_PRESSED(k) \\r
+    (inst->keystate[(k) / 32] & (1 << ((k) % 32)))\r
+\r
+gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    char output[256];\r
+    wchar_t ucsoutput[2];\r
+    int ucsval, start, end, special, output_charset, use_ucsoutput;\r
+\r
+    /* Remember the timestamp. */\r
+    inst->input_event_time = event->time;\r
+\r
+    /* By default, nothing is generated. */\r
+    end = start = 0;\r
+    special = use_ucsoutput = FALSE;\r
+    output_charset = CS_ISO8859_1;\r
+\r
+    /*\r
+     * If Alt is being released after typing an Alt+numberpad\r
+     * sequence, we should generate the code that was typed.\r
+     * \r
+     * Note that we only do this if more than one key was actually\r
+     * pressed - I don't think Alt+NumPad4 should be ^D or that\r
+     * Alt+NumPad3 should be ^C, for example. There's no serious\r
+     * inconvenience in having to type a zero before a single-digit\r
+     * character code.\r
+     */\r
+    if (event->type == GDK_KEY_RELEASE &&\r
+       (event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L ||\r
+        event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) &&\r
+       inst->alt_keycode >= 0 && inst->alt_digits > 1) {\r
+#ifdef KEY_DEBUGGING\r
+       printf("Alt key up, keycode = %d\n", inst->alt_keycode);\r
+#endif\r
+       /*\r
+        * FIXME: we might usefully try to do something clever here\r
+        * about interpreting the generated key code in a way that's\r
+        * appropriate to the line code page.\r
+        */\r
+       output[0] = inst->alt_keycode;\r
+       end = 1;\r
+       goto done;\r
+    }\r
+\r
+    if (event->type == GDK_KEY_PRESS) {\r
+#ifdef KEY_DEBUGGING\r
+       {\r
+           int i;\r
+           printf("keypress: keyval = %04x, state = %08x; string =",\r
+                  event->keyval, event->state);\r
+           for (i = 0; event->string[i]; i++)\r
+               printf(" %02x", (unsigned char) event->string[i]);\r
+           printf("\n");\r
+       }\r
+#endif\r
+\r
+       /*\r
+        * NYI: Compose key (!!! requires Unicode faff before even trying)\r
+        */\r
+\r
+       /*\r
+        * If Alt has just been pressed, we start potentially\r
+        * accumulating an Alt+numberpad code. We do this by\r
+        * setting alt_keycode to -1 (nothing yet but plausible).\r
+        */\r
+       if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L ||\r
+            event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R)) {\r
+           inst->alt_keycode = -1;\r
+            inst->alt_digits = 0;\r
+           goto done;                 /* this generates nothing else */\r
+       }\r
+\r
+       /*\r
+        * If we're seeing a numberpad key press with Mod1 down,\r
+        * consider adding it to alt_keycode if that's sensible.\r
+        * Anything _else_ with Mod1 down cancels any possibility\r
+        * of an ALT keycode: we set alt_keycode to -2.\r
+        */\r
+       if ((event->state & GDK_MOD1_MASK) && inst->alt_keycode != -2) {\r
+           int digit = -1;\r
+           switch (event->keyval) {\r
+             case GDK_KP_0: case GDK_KP_Insert: digit = 0; break;\r
+             case GDK_KP_1: case GDK_KP_End: digit = 1; break;\r
+             case GDK_KP_2: case GDK_KP_Down: digit = 2; break;\r
+             case GDK_KP_3: case GDK_KP_Page_Down: digit = 3; break;\r
+             case GDK_KP_4: case GDK_KP_Left: digit = 4; break;\r
+             case GDK_KP_5: case GDK_KP_Begin: digit = 5; break;\r
+             case GDK_KP_6: case GDK_KP_Right: digit = 6; break;\r
+             case GDK_KP_7: case GDK_KP_Home: digit = 7; break;\r
+             case GDK_KP_8: case GDK_KP_Up: digit = 8; break;\r
+             case GDK_KP_9: case GDK_KP_Page_Up: digit = 9; break;\r
+           }\r
+           if (digit < 0)\r
+               inst->alt_keycode = -2;   /* it's invalid */\r
+           else {\r
+#ifdef KEY_DEBUGGING\r
+               printf("Adding digit %d to keycode %d", digit,\r
+                      inst->alt_keycode);\r
+#endif\r
+               if (inst->alt_keycode == -1)\r
+                   inst->alt_keycode = digit;   /* one-digit code */\r
+               else\r
+                   inst->alt_keycode = inst->alt_keycode * 10 + digit;\r
+                inst->alt_digits++;\r
+#ifdef KEY_DEBUGGING\r
+               printf(" gives new code %d\n", inst->alt_keycode);\r
+#endif\r
+               /* Having used this digit, we now do nothing more with it. */\r
+               goto done;\r
+           }\r
+       }\r
+\r
+       /*\r
+        * Shift-PgUp and Shift-PgDn don't even generate keystrokes\r
+        * at all.\r
+        */\r
+       if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) {\r
+           term_scroll(inst->term, 0, -inst->cfg.height/2);\r
+           return TRUE;\r
+       }\r
+       if (event->keyval == GDK_Page_Up && (event->state & GDK_CONTROL_MASK)) {\r
+           term_scroll(inst->term, 0, -1);\r
+           return TRUE;\r
+       }\r
+       if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) {\r
+           term_scroll(inst->term, 0, +inst->cfg.height/2);\r
+           return TRUE;\r
+       }\r
+       if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) {\r
+           term_scroll(inst->term, 0, +1);\r
+           return TRUE;\r
+       }\r
+\r
+       /*\r
+        * Neither does Shift-Ins.\r
+        */\r
+       if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) {\r
+           request_paste(inst);\r
+           return TRUE;\r
+       }\r
+\r
+       special = FALSE;\r
+       use_ucsoutput = FALSE;\r
+\r
+       /* ALT+things gives leading Escape. */\r
+       output[0] = '\033';\r
+#if !GTK_CHECK_VERSION(2,0,0)\r
+       /*\r
+        * In vanilla X, and hence also GDK 1.2, the string received\r
+        * as part of a keyboard event is assumed to be in\r
+        * ISO-8859-1. (Seems woefully shortsighted in i18n terms,\r
+        * but it's true: see the man page for XLookupString(3) for\r
+        * confirmation.)\r
+        */\r
+       output_charset = CS_ISO8859_1;\r
+       strncpy(output+1, event->string, lenof(output)-1);\r
+#else\r
+       /*\r
+        * GDK 2.0 arranges to have done some translation for us: in\r
+        * GDK 2.0, event->string is encoded in the current locale.\r
+        *\r
+        * (However, it's also deprecated; we really ought to be\r
+        * using a GTKIMContext.)\r
+        *\r
+        * So we use the standard C library function mbstowcs() to\r
+        * convert from the current locale into Unicode; from there\r
+        * we can convert to whatever PuTTY is currently working in.\r
+        * (In fact I convert straight back to UTF-8 from\r
+        * wide-character Unicode, for the sake of simplicity: that\r
+        * way we can still use exactly the same code to manipulate\r
+        * the string, such as prefixing ESC.)\r
+        */\r
+       output_charset = CS_UTF8;\r
+       {\r
+           wchar_t widedata[32], *wp;\r
+           int wlen;\r
+           int ulen;\r
+\r
+           wlen = mb_to_wc(DEFAULT_CODEPAGE, 0,\r
+                           event->string, strlen(event->string),\r
+                           widedata, lenof(widedata)-1);\r
+\r
+           wp = widedata;\r
+           ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,\r
+                                       CS_UTF8, NULL, NULL, 0);\r
+           output[1+ulen] = '\0';\r
+       }\r
+#endif\r
+\r
+       if (!output[1] &&\r
+           (ucsval = keysym_to_unicode(event->keyval)) >= 0) {\r
+           ucsoutput[0] = '\033';\r
+           ucsoutput[1] = ucsval;\r
+           use_ucsoutput = TRUE;\r
+           end = 2;\r
+       } else {\r
+           output[lenof(output)-1] = '\0';\r
+           end = strlen(output);\r
+       }\r
+       if (event->state & GDK_MOD1_MASK) {\r
+           start = 0;\r
+           if (end == 1) end = 0;\r
+       } else\r
+           start = 1;\r
+\r
+       /* Control-` is the same as Control-\ (unless gtk has a better idea) */\r
+       if (!output[1] && event->keyval == '`' &&\r
+           (event->state & GDK_CONTROL_MASK)) {\r
+           output[1] = '\x1C';\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+       }\r
+\r
+       /* Control-Break sends a Break special to the backend */\r
+       if (event->keyval == GDK_Break &&\r
+           (event->state & GDK_CONTROL_MASK)) {\r
+           if (inst->back)\r
+               inst->back->special(inst->backhandle, TS_BRK);\r
+           return TRUE;\r
+       }\r
+\r
+       /* We handle Return ourselves, because it needs to be flagged as\r
+        * special to ldisc. */\r
+       if (event->keyval == GDK_Return) {\r
+           output[1] = '\015';\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+           special = TRUE;\r
+       }\r
+\r
+       /* Control-2, Control-Space and Control-@ are NUL */\r
+       if (!output[1] &&\r
+           (event->keyval == ' ' || event->keyval == '2' ||\r
+            event->keyval == '@') &&\r
+           (event->state & (GDK_SHIFT_MASK |\r
+                            GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {\r
+           output[1] = '\0';\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+       }\r
+\r
+       /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */\r
+       if (!output[1] && event->keyval == ' ' &&\r
+           (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==\r
+           (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {\r
+           output[1] = '\240';\r
+           output_charset = CS_ISO8859_1;\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+       }\r
+\r
+       /* We don't let GTK tell us what Backspace is! We know better. */\r
+       if (event->keyval == GDK_BackSpace &&\r
+           !(event->state & GDK_SHIFT_MASK)) {\r
+           output[1] = inst->cfg.bksp_is_delete ? '\x7F' : '\x08';\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+           special = TRUE;\r
+       }\r
+       /* For Shift Backspace, do opposite of what is configured. */\r
+       if (event->keyval == GDK_BackSpace &&\r
+           (event->state & GDK_SHIFT_MASK)) {\r
+           output[1] = inst->cfg.bksp_is_delete ? '\x08' : '\x7F';\r
+           use_ucsoutput = FALSE;\r
+           end = 2;\r
+           special = TRUE;\r
+       }\r
+\r
+       /* Shift-Tab is ESC [ Z */\r
+       if (event->keyval == GDK_ISO_Left_Tab ||\r
+           (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) {\r
+           end = 1 + sprintf(output+1, "\033[Z");\r
+           use_ucsoutput = FALSE;\r
+       }\r
+       /* And normal Tab is Tab, if the keymap hasn't already told us.\r
+        * (Curiously, at least one version of the MacOS 10.5 X server\r
+        * doesn't translate Tab for us. */\r
+       if (event->keyval == GDK_Tab && end <= 1) {\r
+           output[1] = '\t';\r
+           end = 2;\r
+       }\r
+\r
+       /*\r
+        * NetHack keypad mode.\r
+        */\r
+       if (inst->cfg.nethack_keypad) {\r
+           char *keys = NULL;\r
+           switch (event->keyval) {\r
+             case GDK_KP_1: case GDK_KP_End: keys = "bB\002"; break;\r
+             case GDK_KP_2: case GDK_KP_Down: keys = "jJ\012"; break;\r
+             case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN\016"; break;\r
+             case GDK_KP_4: case GDK_KP_Left: keys = "hH\010"; break;\r
+             case GDK_KP_5: case GDK_KP_Begin: keys = "..."; break;\r
+             case GDK_KP_6: case GDK_KP_Right: keys = "lL\014"; break;\r
+             case GDK_KP_7: case GDK_KP_Home: keys = "yY\031"; break;\r
+             case GDK_KP_8: case GDK_KP_Up: keys = "kK\013"; break;\r
+             case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU\025"; break;\r
+           }\r
+           if (keys) {\r
+               end = 2;\r
+               if (event->state & GDK_CONTROL_MASK)\r
+                   output[1] = keys[2];\r
+               else if (event->state & GDK_SHIFT_MASK)\r
+                   output[1] = keys[1];\r
+               else\r
+                   output[1] = keys[0];\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+       }\r
+\r
+       /*\r
+        * Application keypad mode.\r
+        */\r
+       if (inst->term->app_keypad_keys && !inst->cfg.no_applic_k) {\r
+           int xkey = 0;\r
+           switch (event->keyval) {\r
+             case GDK_Num_Lock: xkey = 'P'; break;\r
+             case GDK_KP_Divide: xkey = 'Q'; break;\r
+             case GDK_KP_Multiply: xkey = 'R'; break;\r
+             case GDK_KP_Subtract: xkey = 'S'; break;\r
+               /*\r
+                * Keypad + is tricky. It covers a space that would\r
+                * be taken up on the VT100 by _two_ keys; so we\r
+                * let Shift select between the two. Worse still,\r
+                * in xterm function key mode we change which two...\r
+                */\r
+             case GDK_KP_Add:\r
+               if (inst->cfg.funky_type == FUNKY_XTERM) {\r
+                   if (event->state & GDK_SHIFT_MASK)\r
+                       xkey = 'l';\r
+                   else\r
+                       xkey = 'k';\r
+               } else if (event->state & GDK_SHIFT_MASK)\r
+                       xkey = 'm';\r
+               else\r
+                   xkey = 'l';\r
+               break;\r
+             case GDK_KP_Enter: xkey = 'M'; break;\r
+             case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break;\r
+             case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break;\r
+             case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break;\r
+             case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break;\r
+             case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break;\r
+             case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break;\r
+             case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break;\r
+             case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break;\r
+             case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break;\r
+             case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break;\r
+             case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break;\r
+           }\r
+           if (xkey) {\r
+               if (inst->term->vt52_mode) {\r
+                   if (xkey >= 'P' && xkey <= 'S')\r
+                       end = 1 + sprintf(output+1, "\033%c", xkey);\r
+                   else\r
+                       end = 1 + sprintf(output+1, "\033?%c", xkey);\r
+               } else\r
+                   end = 1 + sprintf(output+1, "\033O%c", xkey);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+       }\r
+\r
+       /*\r
+        * Next, all the keys that do tilde codes. (ESC '[' nn '~',\r
+        * for integer decimal nn.)\r
+        *\r
+        * We also deal with the weird ones here. Linux VCs replace F1\r
+        * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but\r
+        * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w\r
+        * respectively.\r
+        */\r
+       {\r
+           int code = 0;\r
+           switch (event->keyval) {\r
+             case GDK_F1:\r
+               code = (event->state & GDK_SHIFT_MASK ? 23 : 11);\r
+               break;\r
+             case GDK_F2:\r
+               code = (event->state & GDK_SHIFT_MASK ? 24 : 12);\r
+               break;\r
+             case GDK_F3:\r
+               code = (event->state & GDK_SHIFT_MASK ? 25 : 13);\r
+               break;\r
+             case GDK_F4:\r
+               code = (event->state & GDK_SHIFT_MASK ? 26 : 14);\r
+               break;\r
+             case GDK_F5:\r
+               code = (event->state & GDK_SHIFT_MASK ? 28 : 15);\r
+               break;\r
+             case GDK_F6:\r
+               code = (event->state & GDK_SHIFT_MASK ? 29 : 17);\r
+               break;\r
+             case GDK_F7:\r
+               code = (event->state & GDK_SHIFT_MASK ? 31 : 18);\r
+               break;\r
+             case GDK_F8:\r
+               code = (event->state & GDK_SHIFT_MASK ? 32 : 19);\r
+               break;\r
+             case GDK_F9:\r
+               code = (event->state & GDK_SHIFT_MASK ? 33 : 20);\r
+               break;\r
+             case GDK_F10:\r
+               code = (event->state & GDK_SHIFT_MASK ? 34 : 21);\r
+               break;\r
+             case GDK_F11:\r
+               code = 23;\r
+               break;\r
+             case GDK_F12:\r
+               code = 24;\r
+               break;\r
+             case GDK_F13:\r
+               code = 25;\r
+               break;\r
+             case GDK_F14:\r
+               code = 26;\r
+               break;\r
+             case GDK_F15:\r
+               code = 28;\r
+               break;\r
+             case GDK_F16:\r
+               code = 29;\r
+               break;\r
+             case GDK_F17:\r
+               code = 31;\r
+               break;\r
+             case GDK_F18:\r
+               code = 32;\r
+               break;\r
+             case GDK_F19:\r
+               code = 33;\r
+               break;\r
+             case GDK_F20:\r
+               code = 34;\r
+               break;\r
+           }\r
+           if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) {\r
+             case GDK_Home: case GDK_KP_Home:\r
+               code = 1;\r
+               break;\r
+             case GDK_Insert: case GDK_KP_Insert:\r
+               code = 2;\r
+               break;\r
+             case GDK_Delete: case GDK_KP_Delete:\r
+               code = 3;\r
+               break;\r
+             case GDK_End: case GDK_KP_End:\r
+               code = 4;\r
+               break;\r
+             case GDK_Page_Up: case GDK_KP_Page_Up:\r
+               code = 5;\r
+               break;\r
+             case GDK_Page_Down: case GDK_KP_Page_Down:\r
+               code = 6;\r
+               break;\r
+           }\r
+           /* Reorder edit keys to physical order */\r
+           if (inst->cfg.funky_type == FUNKY_VT400 && code <= 6)\r
+               code = "\0\2\1\4\5\3\6"[code];\r
+\r
+           if (inst->term->vt52_mode && code > 0 && code <= 6) {\r
+               end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+\r
+           if (inst->cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */\r
+               code >= 11 && code <= 34) {\r
+               char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";\r
+               int index = 0;\r
+               switch (event->keyval) {\r
+                 case GDK_F1: index = 0; break;\r
+                 case GDK_F2: index = 1; break;\r
+                 case GDK_F3: index = 2; break;\r
+                 case GDK_F4: index = 3; break;\r
+                 case GDK_F5: index = 4; break;\r
+                 case GDK_F6: index = 5; break;\r
+                 case GDK_F7: index = 6; break;\r
+                 case GDK_F8: index = 7; break;\r
+                 case GDK_F9: index = 8; break;\r
+                 case GDK_F10: index = 9; break;\r
+                 case GDK_F11: index = 10; break;\r
+                 case GDK_F12: index = 11; break;\r
+               }\r
+               if (event->state & GDK_SHIFT_MASK) index += 12;\r
+               if (event->state & GDK_CONTROL_MASK) index += 24;\r
+               end = 1 + sprintf(output+1, "\x1B[%c", codes[index]);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if (inst->cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */\r
+               code >= 1 && code <= 6) {\r
+               char codes[] = "HL.FIG";\r
+               if (code == 3) {\r
+                   output[1] = '\x7F';\r
+                   end = 2;\r
+               } else {\r
+                   end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]);\r
+               }\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if ((inst->term->vt52_mode || inst->cfg.funky_type == FUNKY_VT100P) &&\r
+               code >= 11 && code <= 24) {\r
+               int offt = 0;\r
+               if (code > 15)\r
+                   offt++;\r
+               if (code > 21)\r
+                   offt++;\r
+               if (inst->term->vt52_mode)\r
+                   end = 1 + sprintf(output+1,\r
+                                     "\x1B%c", code + 'P' - 11 - offt);\r
+               else\r
+                   end = 1 + sprintf(output+1,\r
+                                     "\x1BO%c", code + 'P' - 11 - offt);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if (inst->cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {\r
+               end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if (inst->cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {\r
+               if (inst->term->vt52_mode)\r
+                   end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11);\r
+               else\r
+                   end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if (inst->cfg.rxvt_homeend && (code == 1 || code == 4)) {\r
+               end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw");\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+           if (code) {\r
+               end = 1 + sprintf(output+1, "\x1B[%d~", code);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+       }\r
+\r
+       /*\r
+        * Cursor keys. (This includes the numberpad cursor keys,\r
+        * if we haven't already done them due to app keypad mode.)\r
+        * \r
+        * Here we also process un-numlocked un-appkeypadded KP5,\r
+        * which sends ESC [ G.\r
+        */\r
+       {\r
+           int xkey = 0;\r
+           switch (event->keyval) {\r
+             case GDK_Up: case GDK_KP_Up: xkey = 'A'; break;\r
+             case GDK_Down: case GDK_KP_Down: xkey = 'B'; break;\r
+             case GDK_Right: case GDK_KP_Right: xkey = 'C'; break;\r
+             case GDK_Left: case GDK_KP_Left: xkey = 'D'; break;\r
+             case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break;\r
+           }\r
+           if (xkey) {\r
+               end = 1 + format_arrow_key(output+1, inst->term, xkey,\r
+                                          event->state & GDK_CONTROL_MASK);\r
+               use_ucsoutput = FALSE;\r
+               goto done;\r
+           }\r
+       }\r
+       goto done;\r
+    }\r
+\r
+    done:\r
+\r
+    if (end-start > 0) {\r
+#ifdef KEY_DEBUGGING\r
+       int i;\r
+       printf("generating sequence:");\r
+       for (i = start; i < end; i++)\r
+           printf(" %02x", (unsigned char) output[i]);\r
+       printf("\n");\r
+#endif\r
+\r
+       if (special) {\r
+           /*\r
+            * For special control characters, the character set\r
+            * should never matter.\r
+            */\r
+           output[end] = '\0';        /* NUL-terminate */\r
+           if (inst->ldisc)\r
+               ldisc_send(inst->ldisc, output+start, -2, 1);\r
+       } else if (!inst->direct_to_font) {\r
+           if (!use_ucsoutput) {\r
+               if (inst->ldisc)\r
+                   lpage_send(inst->ldisc, output_charset, output+start,\r
+                              end-start, 1);\r
+           } else {\r
+               /*\r
+                * We generated our own Unicode key data from the\r
+                * keysym, so use that instead.\r
+                */\r
+               if (inst->ldisc)\r
+                   luni_send(inst->ldisc, ucsoutput+start, end-start, 1);\r
+           }\r
+       } else {\r
+           /*\r
+            * In direct-to-font mode, we just send the string\r
+            * exactly as we received it.\r
+            */\r
+           if (inst->ldisc)\r
+               ldisc_send(inst->ldisc, output+start, end-start, 1);\r
+       }\r
+\r
+       show_mouseptr(inst, 0);\r
+       term_seen_key_event(inst->term);\r
+    }\r
+\r
+    return TRUE;\r
+}\r
+\r
+gboolean button_internal(struct gui_data *inst, guint32 timestamp,\r
+                        GdkEventType type, guint ebutton, guint state,\r
+                        gdouble ex, gdouble ey)\r
+{\r
+    int shift, ctrl, alt, x, y, button, act;\r
+\r
+    /* Remember the timestamp. */\r
+    inst->input_event_time = timestamp;\r
+\r
+    show_mouseptr(inst, 1);\r
+\r
+    if (ebutton == 4 && type == GDK_BUTTON_PRESS) {\r
+       term_scroll(inst->term, 0, -5);\r
+       return TRUE;\r
+    }\r
+    if (ebutton == 5 && type == GDK_BUTTON_PRESS) {\r
+       term_scroll(inst->term, 0, +5);\r
+       return TRUE;\r
+    }\r
+\r
+    shift = state & GDK_SHIFT_MASK;\r
+    ctrl = state & GDK_CONTROL_MASK;\r
+    alt = state & GDK_MOD1_MASK;\r
+\r
+    if (ebutton == 3 && ctrl) {\r
+       gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL,\r
+                      ebutton, timestamp);\r
+       return TRUE;\r
+    }\r
+\r
+    if (ebutton == 1)\r
+       button = MBT_LEFT;\r
+    else if (ebutton == 2)\r
+       button = MBT_MIDDLE;\r
+    else if (ebutton == 3)\r
+       button = MBT_RIGHT;\r
+    else\r
+       return FALSE;                  /* don't even know what button! */\r
+\r
+    switch (type) {\r
+      case GDK_BUTTON_PRESS: act = MA_CLICK; break;\r
+      case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;\r
+      case GDK_2BUTTON_PRESS: act = MA_2CLK; break;\r
+      case GDK_3BUTTON_PRESS: act = MA_3CLK; break;\r
+      default: return FALSE;          /* don't know this event type */\r
+    }\r
+\r
+    if (send_raw_mouse && !(inst->cfg.mouse_override && shift) &&\r
+       act != MA_CLICK && act != MA_RELEASE)\r
+       return TRUE;                   /* we ignore these in raw mouse mode */\r
+\r
+    x = (ex - inst->cfg.window_border) / inst->font_width;\r
+    y = (ey - inst->cfg.window_border) / inst->font_height;\r
+\r
+    term_mouse(inst->term, button, translate_button(button), act,\r
+              x, y, shift, ctrl, alt);\r
+\r
+    return TRUE;\r
+}\r
+\r
+gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    return button_internal(inst, event->time, event->type, event->button,\r
+                          event->state, event->x, event->y);\r
+}\r
+\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+/*\r
+ * In GTK 2, mouse wheel events have become a new type of event.\r
+ * This handler translates them back into button-4 and button-5\r
+ * presses so that I don't have to change my old code too much :-)\r
+ */\r
+gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    guint button;\r
+\r
+    if (event->direction == GDK_SCROLL_UP)\r
+       button = 4;\r
+    else if (event->direction == GDK_SCROLL_DOWN)\r
+       button = 5;\r
+    else\r
+       return FALSE;\r
+\r
+    return button_internal(inst, event->time, GDK_BUTTON_PRESS,\r
+                          button, event->state, event->x, event->y);\r
+}\r
+#endif\r
+\r
+gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    int shift, ctrl, alt, x, y, button;\r
+\r
+    /* Remember the timestamp. */\r
+    inst->input_event_time = event->time;\r
+\r
+    show_mouseptr(inst, 1);\r
+\r
+    shift = event->state & GDK_SHIFT_MASK;\r
+    ctrl = event->state & GDK_CONTROL_MASK;\r
+    alt = event->state & GDK_MOD1_MASK;\r
+    if (event->state & GDK_BUTTON1_MASK)\r
+       button = MBT_LEFT;\r
+    else if (event->state & GDK_BUTTON2_MASK)\r
+       button = MBT_MIDDLE;\r
+    else if (event->state & GDK_BUTTON3_MASK)\r
+       button = MBT_RIGHT;\r
+    else\r
+       return FALSE;                  /* don't even know what button! */\r
+\r
+    x = (event->x - inst->cfg.window_border) / inst->font_width;\r
+    y = (event->y - inst->cfg.window_border) / inst->font_height;\r
+\r
+    term_mouse(inst->term, button, translate_button(button), MA_DRAG,\r
+              x, y, shift, ctrl, alt);\r
+\r
+    return TRUE;\r
+}\r
+\r
+void frontend_keypress(void *handle)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)handle;\r
+\r
+    /*\r
+     * If our child process has exited but not closed, terminate on\r
+     * any keypress.\r
+     */\r
+    if (inst->exited)\r
+       exit(0);\r
+}\r
+\r
+static gint idle_exit_func(gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    int exitcode;\r
+\r
+    if (!inst->exited &&\r
+        (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) {\r
+       inst->exited = TRUE;\r
+       if (inst->cfg.close_on_exit == FORCE_ON ||\r
+           (inst->cfg.close_on_exit == AUTO && exitcode == 0))\r
+           gtk_main_quit();           /* just go */\r
+       if (inst->ldisc) {\r
+           ldisc_free(inst->ldisc);\r
+           inst->ldisc = NULL;\r
+       }\r
+       if (inst->back) {\r
+           inst->back->free(inst->backhandle);\r
+           inst->backhandle = NULL;\r
+           inst->back = NULL;\r
+            term_provide_resize_fn(inst->term, NULL, NULL);\r
+           update_specials_menu(inst);\r
+       }\r
+       gtk_widget_set_sensitive(inst->restartitem, TRUE);\r
+    }\r
+\r
+    gtk_idle_remove(inst->term_exit_idle_id);\r
+    return TRUE;\r
+}\r
+\r
+void notify_remote_exit(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    inst->term_exit_idle_id = gtk_idle_add(idle_exit_func, inst);\r
+}\r
+\r
+static gint timer_trigger(gpointer data)\r
+{\r
+    long now = GPOINTER_TO_LONG(data);\r
+    long next;\r
+    long ticks;\r
+\r
+    if (run_timers(now, &next)) {\r
+       ticks = next - GETTICKCOUNT();\r
+       timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger,\r
+                                  LONG_TO_GPOINTER(next));\r
+    }\r
+\r
+    /*\r
+     * Never let a timer resume. If we need another one, we've\r
+     * asked for it explicitly above.\r
+     */\r
+    return FALSE;\r
+}\r
+\r
+void timer_change_notify(long next)\r
+{\r
+    long ticks;\r
+\r
+    if (timer_id)\r
+       gtk_timeout_remove(timer_id);\r
+\r
+    ticks = next - GETTICKCOUNT();\r
+    if (ticks <= 0)\r
+       ticks = 1;                     /* just in case */\r
+\r
+    timer_id = gtk_timeout_add(ticks, timer_trigger,\r
+                              LONG_TO_GPOINTER(next));\r
+}\r
+\r
+void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)\r
+{\r
+    /*\r
+     * We must process exceptional notifications before ordinary\r
+     * readability ones, or we may go straight past the urgent\r
+     * marker.\r
+     */\r
+    if (condition & GDK_INPUT_EXCEPTION)\r
+        select_result(sourcefd, 4);\r
+    if (condition & GDK_INPUT_READ)\r
+        select_result(sourcefd, 1);\r
+    if (condition & GDK_INPUT_WRITE)\r
+        select_result(sourcefd, 2);\r
+}\r
+\r
+void destroy(GtkWidget *widget, gpointer data)\r
+{\r
+    gtk_main_quit();\r
+}\r
+\r
+gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    term_set_focus(inst->term, event->in);\r
+    term_update(inst->term);\r
+    show_mouseptr(inst, 1);\r
+    return FALSE;\r
+}\r
+\r
+void set_busy_status(void *frontend, int status)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    inst->busy_status = status;\r
+    update_mouseptr(inst);\r
+}\r
+\r
+/*\r
+ * set or clear the "raw mouse message" mode\r
+ */\r
+void set_raw_mouse_mode(void *frontend, int activate)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    activate = activate && !inst->cfg.no_mouse_rep;\r
+    send_raw_mouse = activate;\r
+    update_mouseptr(inst);\r
+}\r
+\r
+void request_resize(void *frontend, int w, int h)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    int large_x, large_y;\r
+    int offset_x, offset_y;\r
+    int area_x, area_y;\r
+    GtkRequisition inner, outer;\r
+\r
+    /*\r
+     * This is a heinous hack dreamed up by the gnome-terminal\r
+     * people to get around a limitation in gtk. The problem is\r
+     * that in order to set the size correctly we really need to be\r
+     * calling gtk_window_resize - but that needs to know the size\r
+     * of the _whole window_, not the drawing area. So what we do\r
+     * is to set an artificially huge size request on the drawing\r
+     * area, recompute the resulting size request on the window,\r
+     * and look at the difference between the two. That gives us\r
+     * the x and y offsets we need to translate drawing area size\r
+     * into window size for real, and then we call\r
+     * gtk_window_resize.\r
+     */\r
+\r
+    /*\r
+     * We start by retrieving the current size of the whole window.\r
+     * Adding a bit to _that_ will give us a value we can use as a\r
+     * bogus size request which guarantees to be bigger than the\r
+     * current size of the drawing area.\r
+     */\r
+    get_window_pixels(inst, &large_x, &large_y);\r
+    large_x += 32;\r
+    large_y += 32;\r
+\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_widget_set_size_request(inst->area, large_x, large_y);\r
+#else\r
+    gtk_widget_set_usize(inst->area, large_x, large_y);\r
+#endif\r
+    gtk_widget_size_request(inst->area, &inner);\r
+    gtk_widget_size_request(inst->window, &outer);\r
+\r
+    offset_x = outer.width - inner.width;\r
+    offset_y = outer.height - inner.height;\r
+\r
+    area_x = inst->font_width * w + 2*inst->cfg.window_border;\r
+    area_y = inst->font_height * h + 2*inst->cfg.window_border;\r
+\r
+    /*\r
+     * Now we must set the size request on the drawing area back to\r
+     * something sensible before we commit the real resize. Best\r
+     * way to do this, I think, is to set it to what the size is\r
+     * really going to end up being.\r
+     */\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_widget_set_size_request(inst->area, area_x, area_y);\r
+    gtk_window_resize(GTK_WINDOW(inst->window),\r
+                     area_x + offset_x, area_y + offset_y);\r
+#else\r
+    gtk_widget_set_usize(inst->area, area_x, area_y);\r
+    gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);\r
+    /*\r
+     * I can no longer remember what this call to\r
+     * gtk_container_dequeue_resize_handler is for. It was\r
+     * introduced in r3092 with no comment, and the commit log\r
+     * message was uninformative. I'm _guessing_ its purpose is to\r
+     * prevent gratuitous resize processing on the window given\r
+     * that we're about to resize it anyway, but I have no idea\r
+     * why that's so incredibly vital.\r
+     * \r
+     * I've tried removing the call, and nothing seems to go\r
+     * wrong. I've backtracked to r3092 and tried removing the\r
+     * call there, and still nothing goes wrong. So I'm going to\r
+     * adopt the working hypothesis that it's superfluous; I won't\r
+     * actually remove it from the GTK 1.2 code, but I won't\r
+     * attempt to replicate its functionality in the GTK 2 code\r
+     * above.\r
+     */\r
+    gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));\r
+    gdk_window_resize(inst->window->window,\r
+                     area_x + offset_x, area_y + offset_y);\r
+#endif\r
+}\r
+\r
+static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b)\r
+{\r
+    gboolean success[1];\r
+\r
+    inst->cols[n].red = r * 0x0101;\r
+    inst->cols[n].green = g * 0x0101;\r
+    inst->cols[n].blue = b * 0x0101;\r
+\r
+    gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1);\r
+    gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,\r
+                             FALSE, TRUE, success);\r
+    if (!success[0])\r
+       g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname,\r
+               n, r, g, b);\r
+}\r
+\r
+void set_window_background(struct gui_data *inst)\r
+{\r
+    if (inst->area && inst->area->window)\r
+       gdk_window_set_background(inst->area->window, &inst->cols[258]);\r
+    if (inst->window && inst->window->window)\r
+       gdk_window_set_background(inst->window->window, &inst->cols[258]);\r
+}\r
+\r
+void palette_set(void *frontend, int n, int r, int g, int b)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (n >= 16)\r
+       n += 256 - 16;\r
+    if (n > NALLCOLOURS)\r
+       return;\r
+    real_palette_set(inst, n, r, g, b);\r
+    if (n == 258) {\r
+       /* Default Background changed. Ensure space between text area and\r
+        * window border is redrawn */\r
+       set_window_background(inst);\r
+       draw_backing_rect(inst);\r
+       gtk_widget_queue_draw(inst->area);\r
+    }\r
+}\r
+\r
+void palette_reset(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    /* This maps colour indices in inst->cfg to those used in inst->cols. */\r
+    static const int ww[] = {\r
+       256, 257, 258, 259, 260, 261,\r
+       0, 8, 1, 9, 2, 10, 3, 11,\r
+       4, 12, 5, 13, 6, 14, 7, 15\r
+    };\r
+    gboolean success[NALLCOLOURS];\r
+    int i;\r
+\r
+    assert(lenof(ww) == NCFGCOLOURS);\r
+\r
+    if (!inst->colmap) {\r
+       inst->colmap = gdk_colormap_get_system();\r
+    } else {\r
+       gdk_colormap_free_colors(inst->colmap, inst->cols, NALLCOLOURS);\r
+    }\r
+\r
+    for (i = 0; i < NCFGCOLOURS; i++) {\r
+       inst->cols[ww[i]].red = inst->cfg.colours[i][0] * 0x0101;\r
+       inst->cols[ww[i]].green = inst->cfg.colours[i][1] * 0x0101;\r
+       inst->cols[ww[i]].blue = inst->cfg.colours[i][2] * 0x0101;\r
+    }\r
+\r
+    for (i = 0; i < NEXTCOLOURS; i++) {\r
+       if (i < 216) {\r
+           int r = i / 36, g = (i / 6) % 6, b = i % 6;\r
+           inst->cols[i+16].red = r ? r * 0x2828 + 0x3737 : 0;\r
+           inst->cols[i+16].green = g ? g * 0x2828 + 0x3737 : 0;\r
+           inst->cols[i+16].blue = b ? b * 0x2828 + 0x3737 : 0;\r
+       } else {\r
+           int shade = i - 216;\r
+           shade = shade * 0x0a0a + 0x0808;\r
+           inst->cols[i+16].red = inst->cols[i+16].green =\r
+               inst->cols[i+16].blue = shade;\r
+       }\r
+    }\r
+\r
+    gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS,\r
+                             FALSE, TRUE, success);\r
+    for (i = 0; i < NALLCOLOURS; i++) {\r
+       if (!success[i])\r
+           g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",\r
+                    appname, i, inst->cfg.colours[i][0],\r
+                    inst->cfg.colours[i][1], inst->cfg.colours[i][2]);\r
+    }\r
+\r
+    /* Since Default Background may have changed, ensure that space\r
+     * between text area and window border is refreshed. */\r
+    set_window_background(inst);\r
+    if (inst->area && inst->area->window) {\r
+       draw_backing_rect(inst);\r
+       gtk_widget_queue_draw(inst->area);\r
+    }\r
+}\r
+\r
+/* Ensure that all the cut buffers exist - according to the ICCCM, we must\r
+ * do this before we start using cut buffers.\r
+ */\r
+void init_cutbuffers()\r
+{\r
+    unsigned char empty[] = "";\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);\r
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),\r
+                   XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);\r
+}\r
+\r
+/* Store the data in a cut-buffer. */\r
+void store_cutbuffer(char * ptr, int len)\r
+{\r
+    /* ICCCM says we must rotate the buffers before storing to buffer 0. */\r
+    XRotateBuffers(GDK_DISPLAY(), 1);\r
+    XStoreBytes(GDK_DISPLAY(), ptr, len);\r
+}\r
+\r
+/* Retrieve data from a cut-buffer.\r
+ * Returned data needs to be freed with XFree().\r
+ */\r
+char * retrieve_cutbuffer(int * nbytes)\r
+{\r
+    char * ptr;\r
+    ptr = XFetchBytes(GDK_DISPLAY(), nbytes);\r
+    if (*nbytes <= 0 && ptr != 0) {\r
+       XFree(ptr);\r
+       ptr = 0;\r
+    }\r
+    return ptr;\r
+}\r
+\r
+void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (inst->pasteout_data)\r
+       sfree(inst->pasteout_data);\r
+    if (inst->pasteout_data_ctext)\r
+       sfree(inst->pasteout_data_ctext);\r
+    if (inst->pasteout_data_utf8)\r
+       sfree(inst->pasteout_data_utf8);\r
+\r
+    /*\r
+     * Set up UTF-8 and compound text paste data. This only happens\r
+     * if we aren't in direct-to-font mode using the D800 hack.\r
+     */\r
+    if (!inst->direct_to_font) {\r
+       wchar_t *tmp = data;\r
+       int tmplen = len;\r
+       XTextProperty tp;\r
+       char *list[1];\r
+\r
+       inst->pasteout_data_utf8 = snewn(len*6, char);\r
+       inst->pasteout_data_utf8_len = len*6;\r
+       inst->pasteout_data_utf8_len =\r
+           charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8,\r
+                                inst->pasteout_data_utf8_len,\r
+                                CS_UTF8, NULL, NULL, 0);\r
+       if (inst->pasteout_data_utf8_len == 0) {\r
+           sfree(inst->pasteout_data_utf8);\r
+           inst->pasteout_data_utf8 = NULL;\r
+       } else {\r
+           inst->pasteout_data_utf8 =\r
+               sresize(inst->pasteout_data_utf8,\r
+                       inst->pasteout_data_utf8_len + 1, char);\r
+           inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0';\r
+       }\r
+\r
+       /*\r
+        * Now let Xlib convert our UTF-8 data into compound text.\r
+        */\r
+       list[0] = inst->pasteout_data_utf8;\r
+       if (Xutf8TextListToTextProperty(GDK_DISPLAY(), list, 1,\r
+                                       XCompoundTextStyle, &tp) == 0) {\r
+           inst->pasteout_data_ctext = snewn(tp.nitems+1, char);\r
+           memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems);\r
+           inst->pasteout_data_ctext_len = tp.nitems;\r
+           XFree(tp.value);\r
+       } else {\r
+            inst->pasteout_data_ctext = NULL;\r
+            inst->pasteout_data_ctext_len = 0;\r
+        }\r
+    } else {\r
+       inst->pasteout_data_utf8 = NULL;\r
+       inst->pasteout_data_utf8_len = 0;\r
+       inst->pasteout_data_ctext = NULL;\r
+       inst->pasteout_data_ctext_len = 0;\r
+    }\r
+\r
+    inst->pasteout_data = snewn(len*6, char);\r
+    inst->pasteout_data_len = len*6;\r
+    inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0,\r
+                                      data, len, inst->pasteout_data,\r
+                                      inst->pasteout_data_len,\r
+                                      NULL, NULL, NULL);\r
+    if (inst->pasteout_data_len == 0) {\r
+       sfree(inst->pasteout_data);\r
+       inst->pasteout_data = NULL;\r
+    } else {\r
+       inst->pasteout_data =\r
+           sresize(inst->pasteout_data, inst->pasteout_data_len, char);\r
+    }\r
+\r
+    store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len);\r
+\r
+    if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY,\r
+                               inst->input_event_time)) {\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+       gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY);\r
+#endif\r
+       gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,\r
+                                GDK_SELECTION_TYPE_STRING, 1);\r
+       if (inst->pasteout_data_ctext)\r
+           gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,\r
+                                    compound_text_atom, 1);\r
+       if (inst->pasteout_data_utf8)\r
+           gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,\r
+                                    utf8_string_atom, 1);\r
+    }\r
+\r
+    if (must_deselect)\r
+       term_deselect(inst->term);\r
+}\r
+\r
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,\r
+                  guint info, guint time_stamp, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    if (seldata->target == utf8_string_atom)\r
+       gtk_selection_data_set(seldata, seldata->target, 8,\r
+                              (unsigned char *)inst->pasteout_data_utf8,\r
+                              inst->pasteout_data_utf8_len);\r
+    else if (seldata->target == compound_text_atom)\r
+       gtk_selection_data_set(seldata, seldata->target, 8,\r
+                              (unsigned char *)inst->pasteout_data_ctext,\r
+                              inst->pasteout_data_ctext_len);\r
+    else\r
+       gtk_selection_data_set(seldata, seldata->target, 8,\r
+                              (unsigned char *)inst->pasteout_data,\r
+                              inst->pasteout_data_len);\r
+}\r
+\r
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,\r
+                    gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    term_deselect(inst->term);\r
+    if (inst->pasteout_data)\r
+       sfree(inst->pasteout_data);\r
+    if (inst->pasteout_data_ctext)\r
+       sfree(inst->pasteout_data_ctext);\r
+    if (inst->pasteout_data_utf8)\r
+       sfree(inst->pasteout_data_utf8);\r
+    inst->pasteout_data = NULL;\r
+    inst->pasteout_data_len = 0;\r
+    inst->pasteout_data_ctext = NULL;\r
+    inst->pasteout_data_ctext_len = 0;\r
+    inst->pasteout_data_utf8 = NULL;\r
+    inst->pasteout_data_utf8_len = 0;\r
+    return TRUE;\r
+}\r
+\r
+void request_paste(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    /*\r
+     * In Unix, pasting is asynchronous: all we can do at the\r
+     * moment is to call gtk_selection_convert(), and when the data\r
+     * comes back _then_ we can call term_do_paste().\r
+     */\r
+\r
+    if (!inst->direct_to_font) {\r
+       /*\r
+        * First we attempt to retrieve the selection as a UTF-8\r
+        * string (which we will convert to the correct code page\r
+        * before sending to the session, of course). If that\r
+        * fails, selection_received() will be informed and will\r
+        * fall back to an ordinary string.\r
+        */\r
+       gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,\r
+                             utf8_string_atom,\r
+                             inst->input_event_time);\r
+    } else {\r
+       /*\r
+        * If we're in direct-to-font mode, we disable UTF-8\r
+        * pasting, and go straight to ordinary string data.\r
+        */\r
+       gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,\r
+                             GDK_SELECTION_TYPE_STRING,\r
+                             inst->input_event_time);\r
+    }\r
+}\r
+\r
+gint idle_paste_func(gpointer data);   /* forward ref */\r
+\r
+void selection_received(GtkWidget *widget, GtkSelectionData *seldata,\r
+                       guint time, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    XTextProperty tp;\r
+    char **list;\r
+    char *text;\r
+    int length, count, ret;\r
+    int free_list_required = 0;\r
+    int free_required = 0;\r
+    int charset;\r
+\r
+    if (seldata->target == utf8_string_atom && seldata->length <= 0) {\r
+       /*\r
+        * Failed to get a UTF-8 selection string. Try compound\r
+        * text next.\r
+        */\r
+       gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,\r
+                             compound_text_atom,\r
+                             inst->input_event_time);\r
+       return;\r
+    }\r
+\r
+    if (seldata->target == compound_text_atom && seldata->length <= 0) {\r
+       /*\r
+        * Failed to get UTF-8 or compound text. Try an ordinary\r
+        * string.\r
+        */\r
+       gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,\r
+                             GDK_SELECTION_TYPE_STRING,\r
+                             inst->input_event_time);\r
+       return;\r
+    }\r
+\r
+    /*\r
+     * If we have data, but it's not of a type we can deal with,\r
+     * we have to ignore the data.\r
+     */\r
+    if (seldata->length > 0 &&\r
+       seldata->type != GDK_SELECTION_TYPE_STRING &&\r
+       seldata->type != compound_text_atom &&\r
+       seldata->type != utf8_string_atom)\r
+       return;\r
+\r
+    /*\r
+     * If we have no data, try looking in a cut buffer.\r
+     */\r
+    if (seldata->length <= 0) {\r
+       text = retrieve_cutbuffer(&length);\r
+       if (length == 0)\r
+           return;\r
+       /* Xterm is rumoured to expect Latin-1, though I havn't checked the\r
+        * source, so use that as a de-facto standard. */\r
+       charset = CS_ISO8859_1;\r
+       free_required = 1;\r
+    } else {\r
+       /*\r
+        * Convert COMPOUND_TEXT into UTF-8.\r
+        */\r
+       if (seldata->type == compound_text_atom) {\r
+           tp.value = seldata->data;\r
+           tp.encoding = (Atom) seldata->type;\r
+           tp.format = seldata->format;\r
+           tp.nitems = seldata->length;\r
+           ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp,\r
+                                             &list, &count);\r
+           if (ret != 0 || count != 1) {\r
+               /*\r
+                * Compound text failed; fall back to STRING.\r
+                */\r
+               gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,\r
+                                     GDK_SELECTION_TYPE_STRING,\r
+                                     inst->input_event_time);\r
+               return;\r
+           }\r
+           text = list[0];\r
+           length = strlen(list[0]);\r
+           charset = CS_UTF8;\r
+           free_list_required = 1;\r
+       } else {\r
+           text = (char *)seldata->data;\r
+           length = seldata->length;\r
+           charset = (seldata->type == utf8_string_atom ?\r
+                      CS_UTF8 : inst->ucsdata.line_codepage);\r
+       }\r
+    }\r
+\r
+    if (inst->pastein_data)\r
+       sfree(inst->pastein_data);\r
+\r
+    inst->pastein_data = snewn(length, wchar_t);\r
+    inst->pastein_data_len = length;\r
+    inst->pastein_data_len =\r
+       mb_to_wc(charset, 0, text, length,\r
+                inst->pastein_data, inst->pastein_data_len);\r
+\r
+    term_do_paste(inst->term);\r
+\r
+    if (term_paste_pending(inst->term))\r
+       inst->term_paste_idle_id = gtk_idle_add(idle_paste_func, inst);\r
+\r
+    if (free_list_required)\r
+       XFreeStringList(list);\r
+    if (free_required)\r
+       XFree(text);\r
+}\r
+\r
+gint idle_paste_func(gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    if (term_paste_pending(inst->term))\r
+       term_paste(inst->term);\r
+    else\r
+       gtk_idle_remove(inst->term_paste_idle_id);\r
+\r
+    return TRUE;\r
+}\r
+\r
+\r
+void get_clip(void *frontend, wchar_t ** p, int *len)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    if (p) {\r
+       *p = inst->pastein_data;\r
+       *len = inst->pastein_data_len;\r
+    }\r
+}\r
+\r
+static void set_window_titles(struct gui_data *inst)\r
+{\r
+    /*\r
+     * We must always call set_icon_name after calling set_title,\r
+     * since set_title will write both names. Irritating, but such\r
+     * is life.\r
+     */\r
+    gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);\r
+    if (!inst->cfg.win_name_always)\r
+       gdk_window_set_icon_name(inst->window->window, inst->icontitle);\r
+}\r
+\r
+void set_title(void *frontend, char *title)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    strncpy(inst->wintitle, title, lenof(inst->wintitle));\r
+    inst->wintitle[lenof(inst->wintitle)-1] = '\0';\r
+    set_window_titles(inst);\r
+}\r
+\r
+void set_icon(void *frontend, char *title)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    strncpy(inst->icontitle, title, lenof(inst->icontitle));\r
+    inst->icontitle[lenof(inst->icontitle)-1] = '\0';\r
+    set_window_titles(inst);\r
+}\r
+\r
+void set_sbar(void *frontend, int total, int start, int page)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    if (!inst->cfg.scrollbar)\r
+       return;\r
+    inst->sbar_adjust->lower = 0;\r
+    inst->sbar_adjust->upper = total;\r
+    inst->sbar_adjust->value = start;\r
+    inst->sbar_adjust->page_size = page;\r
+    inst->sbar_adjust->step_increment = 1;\r
+    inst->sbar_adjust->page_increment = page/2;\r
+    inst->ignore_sbar = TRUE;\r
+    gtk_adjustment_changed(inst->sbar_adjust);\r
+    inst->ignore_sbar = FALSE;\r
+}\r
+\r
+void scrollbar_moved(GtkAdjustment *adj, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    if (!inst->cfg.scrollbar)\r
+       return;\r
+    if (!inst->ignore_sbar)\r
+       term_scroll(inst->term, 1, (int)adj->value);\r
+}\r
+\r
+void sys_cursor(void *frontend, int x, int y)\r
+{\r
+    /*\r
+     * This is meaningless under X.\r
+     */\r
+}\r
+\r
+/*\r
+ * This is still called when mode==BELL_VISUAL, even though the\r
+ * visual bell is handled entirely within terminal.c, because we\r
+ * may want to perform additional actions on any kind of bell (for\r
+ * example, taskbar flashing in Windows).\r
+ */\r
+void do_beep(void *frontend, int mode)\r
+{\r
+    if (mode == BELL_DEFAULT)\r
+       gdk_beep();\r
+}\r
+\r
+int char_width(Context ctx, int uc)\r
+{\r
+    /*\r
+     * Under X, any fixed-width font really _is_ fixed-width.\r
+     * Double-width characters will be dealt with using a separate\r
+     * font. For the moment we can simply return 1.\r
+     * \r
+     * FIXME: but is that also true of Pango?\r
+     */\r
+    return 1;\r
+}\r
+\r
+Context get_ctx(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    struct draw_ctx *dctx;\r
+\r
+    if (!inst->area->window)\r
+       return NULL;\r
+\r
+    dctx = snew(struct draw_ctx);\r
+    dctx->inst = inst;\r
+    dctx->gc = gdk_gc_new(inst->area->window);\r
+    return dctx;\r
+}\r
+\r
+void free_ctx(Context ctx)\r
+{\r
+    struct draw_ctx *dctx = (struct draw_ctx *)ctx;\r
+    /* struct gui_data *inst = dctx->inst; */\r
+    GdkGC *gc = dctx->gc;\r
+    gdk_gc_unref(gc);\r
+    sfree(dctx);\r
+}\r
+\r
+/*\r
+ * Draw a line of text in the window, at given character\r
+ * coordinates, in given attributes.\r
+ *\r
+ * We are allowed to fiddle with the contents of `text'.\r
+ */\r
+void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,\r
+                     unsigned long attr, int lattr)\r
+{\r
+    struct draw_ctx *dctx = (struct draw_ctx *)ctx;\r
+    struct gui_data *inst = dctx->inst;\r
+    GdkGC *gc = dctx->gc;\r
+    int ncombining, combining;\r
+    int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold;\r
+    int monochrome = gtk_widget_get_visual(inst->area)->depth == 1;\r
+\r
+    if (attr & TATTR_COMBINING) {\r
+       ncombining = len;\r
+       len = 1;\r
+    } else\r
+       ncombining = 1;\r
+\r
+    nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT);\r
+    nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT);\r
+    if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) {\r
+       t = nfg;\r
+       nfg = nbg;\r
+       nbg = t;\r
+    }\r
+    if (inst->cfg.bold_colour && (attr & ATTR_BOLD)) {\r
+       if (nfg < 16) nfg |= 8;\r
+       else if (nfg >= 256) nfg |= 1;\r
+    }\r
+    if (inst->cfg.bold_colour && (attr & ATTR_BLINK)) {\r
+       if (nbg < 16) nbg |= 8;\r
+       else if (nbg >= 256) nbg |= 1;\r
+    }\r
+    if ((attr & TATTR_ACTCURS) && !monochrome) {\r
+       nfg = 260;\r
+       nbg = 261;\r
+    }\r
+\r
+    fontid = shadow = 0;\r
+\r
+    if (attr & ATTR_WIDE) {\r
+       widefactor = 2;\r
+       fontid |= 2;\r
+    } else {\r
+       widefactor = 1;\r
+    }\r
+\r
+    if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) {\r
+       bold = 1;\r
+       fontid |= 1;\r
+    } else {\r
+       bold = 0;\r
+    }\r
+\r
+    if (!inst->fonts[fontid]) {\r
+       int i;\r
+       /*\r
+        * Fall back through font ids with subsets of this one's\r
+        * set bits, in order.\r
+        */\r
+       for (i = fontid; i-- > 0 ;) {\r
+           if (i & ~fontid)\r
+               continue;              /* some other bit is set */\r
+           if (inst->fonts[i]) {\r
+               fontid = i;\r
+               break;\r
+           }\r
+       }\r
+       assert(inst->fonts[fontid]);   /* we should at least have hit zero */\r
+    }\r
+\r
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {\r
+       x *= 2;\r
+       if (x >= inst->term->cols)\r
+           return;\r
+       if (x + len*2*widefactor > inst->term->cols)\r
+           len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */\r
+       rlen = len * 2;\r
+    } else\r
+       rlen = len;\r
+\r
+    {\r
+       GdkRectangle r;\r
+\r
+       r.x = x*inst->font_width+inst->cfg.window_border;\r
+       r.y = y*inst->font_height+inst->cfg.window_border;\r
+       r.width = rlen*widefactor*inst->font_width;\r
+       r.height = inst->font_height;\r
+       gdk_gc_set_clip_rectangle(gc, &r);\r
+    }\r
+\r
+    gdk_gc_set_foreground(gc, &inst->cols[nbg]);\r
+    gdk_draw_rectangle(inst->pixmap, gc, 1,\r
+                      x*inst->font_width+inst->cfg.window_border,\r
+                      y*inst->font_height+inst->cfg.window_border,\r
+                      rlen*widefactor*inst->font_width, inst->font_height);\r
+\r
+    gdk_gc_set_foreground(gc, &inst->cols[nfg]);\r
+    {\r
+       gchar *gcs;\r
+\r
+       /*\r
+        * FIXME: this length is hardwired on the assumption that\r
+        * conversions from wide to multibyte characters will\r
+        * never generate more than 10 bytes for a single wide\r
+        * character.\r
+        */\r
+       gcs = snewn(len*10+1, gchar);\r
+\r
+       for (combining = 0; combining < ncombining; combining++) {\r
+           int mblen = wc_to_mb(inst->fonts[fontid]->real_charset, 0,\r
+                                text + combining, len, gcs, len*10+1, ".",\r
+                                NULL, NULL);\r
+           unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid],\r
+                             x*inst->font_width+inst->cfg.window_border,\r
+                             y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent,\r
+                             gcs, mblen, widefactor > 1, bold, inst->font_width);\r
+       }\r
+\r
+       sfree(gcs);\r
+    }\r
+\r
+    if (attr & ATTR_UNDER) {\r
+       int uheight = inst->fonts[0]->ascent + 1;\r
+       if (uheight >= inst->font_height)\r
+           uheight = inst->font_height - 1;\r
+       gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->cfg.window_border,\r
+                     y*inst->font_height + uheight + inst->cfg.window_border,\r
+                     (x+len)*widefactor*inst->font_width-1+inst->cfg.window_border,\r
+                     y*inst->font_height + uheight + inst->cfg.window_border);\r
+    }\r
+\r
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {\r
+       /*\r
+        * I can't find any plausible StretchBlt equivalent in the\r
+        * X server, so I'm going to do this the slow and painful\r
+        * way. This will involve repeated calls to\r
+        * gdk_draw_pixmap() to stretch the text horizontally. It's\r
+        * O(N^2) in time and O(N) in network bandwidth, but you\r
+        * try thinking of a better way. :-(\r
+        */\r
+       int i;\r
+       for (i = 0; i < len * widefactor * inst->font_width; i++) {\r
+           gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap,\r
+                           x*inst->font_width+inst->cfg.window_border + 2*i,\r
+                           y*inst->font_height+inst->cfg.window_border,\r
+                           x*inst->font_width+inst->cfg.window_border + 2*i+1,\r
+                           y*inst->font_height+inst->cfg.window_border,\r
+                           len * widefactor * inst->font_width - i, inst->font_height);\r
+       }\r
+       len *= 2;\r
+       if ((lattr & LATTR_MODE) != LATTR_WIDE) {\r
+           int dt, db;\r
+           /* Now stretch vertically, in the same way. */\r
+           if ((lattr & LATTR_MODE) == LATTR_BOT)\r
+               dt = 0, db = 1;\r
+           else\r
+               dt = 1, db = 0;\r
+           for (i = 0; i < inst->font_height; i+=2) {\r
+               gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap,\r
+                               x*inst->font_width+inst->cfg.window_border,\r
+                               y*inst->font_height+inst->cfg.window_border+dt*i+db,\r
+                               x*inst->font_width+inst->cfg.window_border,\r
+                               y*inst->font_height+inst->cfg.window_border+dt*(i+1),\r
+                               len * widefactor * inst->font_width, inst->font_height-i-1);\r
+           }\r
+       }\r
+    }\r
+}\r
+\r
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,\r
+            unsigned long attr, int lattr)\r
+{\r
+    struct draw_ctx *dctx = (struct draw_ctx *)ctx;\r
+    struct gui_data *inst = dctx->inst;\r
+    GdkGC *gc = dctx->gc;\r
+    int widefactor;\r
+\r
+    do_text_internal(ctx, x, y, text, len, attr, lattr);\r
+\r
+    if (attr & ATTR_WIDE) {\r
+       widefactor = 2;\r
+    } else {\r
+       widefactor = 1;\r
+    }\r
+\r
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {\r
+       x *= 2;\r
+       if (x >= inst->term->cols)\r
+           return;\r
+       if (x + len*2*widefactor > inst->term->cols)\r
+           len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */\r
+       len *= 2;\r
+    }\r
+\r
+    gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,\r
+                   x*inst->font_width+inst->cfg.window_border,\r
+                   y*inst->font_height+inst->cfg.window_border,\r
+                   x*inst->font_width+inst->cfg.window_border,\r
+                   y*inst->font_height+inst->cfg.window_border,\r
+                   len*widefactor*inst->font_width, inst->font_height);\r
+}\r
+\r
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,\r
+              unsigned long attr, int lattr)\r
+{\r
+    struct draw_ctx *dctx = (struct draw_ctx *)ctx;\r
+    struct gui_data *inst = dctx->inst;\r
+    GdkGC *gc = dctx->gc;\r
+\r
+    int active, passive, widefactor;\r
+\r
+    if (attr & TATTR_PASCURS) {\r
+       attr &= ~TATTR_PASCURS;\r
+       passive = 1;\r
+    } else\r
+       passive = 0;\r
+    if ((attr & TATTR_ACTCURS) && inst->cfg.cursor_type != 0) {\r
+       attr &= ~TATTR_ACTCURS;\r
+        active = 1;\r
+    } else\r
+        active = 0;\r
+    do_text_internal(ctx, x, y, text, len, attr, lattr);\r
+\r
+    if (attr & TATTR_COMBINING)\r
+       len = 1;\r
+\r
+    if (attr & ATTR_WIDE) {\r
+       widefactor = 2;\r
+    } else {\r
+       widefactor = 1;\r
+    }\r
+\r
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {\r
+       x *= 2;\r
+       if (x >= inst->term->cols)\r
+           return;\r
+       if (x + len*2*widefactor > inst->term->cols)\r
+           len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */\r
+       len *= 2;\r
+    }\r
+\r
+    if (inst->cfg.cursor_type == 0) {\r
+       /*\r
+        * An active block cursor will already have been done by\r
+        * the above do_text call, so we only need to do anything\r
+        * if it's passive.\r
+        */\r
+       if (passive) {\r
+           gdk_gc_set_foreground(gc, &inst->cols[261]);\r
+           gdk_draw_rectangle(inst->pixmap, gc, 0,\r
+                              x*inst->font_width+inst->cfg.window_border,\r
+                              y*inst->font_height+inst->cfg.window_border,\r
+                              len*widefactor*inst->font_width-1, inst->font_height-1);\r
+       }\r
+    } else {\r
+       int uheight;\r
+       int startx, starty, dx, dy, length, i;\r
+\r
+       int char_width;\r
+\r
+       if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)\r
+           char_width = 2*inst->font_width;\r
+       else\r
+           char_width = inst->font_width;\r
+\r
+       if (inst->cfg.cursor_type == 1) {\r
+           uheight = inst->fonts[0]->ascent + 1;\r
+           if (uheight >= inst->font_height)\r
+               uheight = inst->font_height - 1;\r
+\r
+           startx = x * inst->font_width + inst->cfg.window_border;\r
+           starty = y * inst->font_height + inst->cfg.window_border + uheight;\r
+           dx = 1;\r
+           dy = 0;\r
+           length = len * widefactor * char_width;\r
+       } else {\r
+           int xadjust = 0;\r
+           if (attr & TATTR_RIGHTCURS)\r
+               xadjust = char_width - 1;\r
+           startx = x * inst->font_width + inst->cfg.window_border + xadjust;\r
+           starty = y * inst->font_height + inst->cfg.window_border;\r
+           dx = 0;\r
+           dy = 1;\r
+           length = inst->font_height;\r
+       }\r
+\r
+       gdk_gc_set_foreground(gc, &inst->cols[261]);\r
+       if (passive) {\r
+           for (i = 0; i < length; i++) {\r
+               if (i % 2 == 0) {\r
+                   gdk_draw_point(inst->pixmap, gc, startx, starty);\r
+               }\r
+               startx += dx;\r
+               starty += dy;\r
+           }\r
+       } else if (active) {\r
+           gdk_draw_line(inst->pixmap, gc, startx, starty,\r
+                         startx + (length-1) * dx, starty + (length-1) * dy);\r
+       } /* else no cursor (e.g., blinked off) */\r
+    }\r
+\r
+    gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,\r
+                   x*inst->font_width+inst->cfg.window_border,\r
+                   y*inst->font_height+inst->cfg.window_border,\r
+                   x*inst->font_width+inst->cfg.window_border,\r
+                   y*inst->font_height+inst->cfg.window_border,\r
+                   len*widefactor*inst->font_width, inst->font_height);\r
+}\r
+\r
+GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val)\r
+{\r
+    /*\r
+     * Truly hideous hack: GTK doesn't allow us to set the mouse\r
+     * cursor foreground and background colours unless we've _also_\r
+     * created our own cursor from bitmaps. Therefore, I need to\r
+     * load the `cursor' font and draw glyphs from it on to\r
+     * pixmaps, in order to construct my cursors with the fg and bg\r
+     * I want. This is a gross hack, but it's more self-contained\r
+     * than linking in Xlib to find the X window handle to\r
+     * inst->area and calling XRecolorCursor, and it's more\r
+     * futureproof than hard-coding the shapes as bitmap arrays.\r
+     */\r
+    static GdkFont *cursor_font = NULL;\r
+    GdkPixmap *source, *mask;\r
+    GdkGC *gc;\r
+    GdkColor cfg = { 0, 65535, 65535, 65535 };\r
+    GdkColor cbg = { 0, 0, 0, 0 };\r
+    GdkColor dfg = { 1, 65535, 65535, 65535 };\r
+    GdkColor dbg = { 0, 0, 0, 0 };\r
+    GdkCursor *ret;\r
+    gchar text[2];\r
+    gint lb, rb, wid, asc, desc, w, h, x, y;\r
+\r
+    if (cursor_val == -2) {\r
+       gdk_font_unref(cursor_font);\r
+       return NULL;\r
+    }\r
+\r
+    if (cursor_val >= 0 && !cursor_font) {\r
+       cursor_font = gdk_font_load("cursor");\r
+       if (cursor_font)\r
+           gdk_font_ref(cursor_font);\r
+    }\r
+\r
+    /*\r
+     * Get the text extent of the cursor in question. We use the\r
+     * mask character for this, because it's typically slightly\r
+     * bigger than the main character.\r
+     */\r
+    if (cursor_val >= 0) {\r
+       text[1] = '\0';\r
+       text[0] = (char)cursor_val + 1;\r
+       gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc);\r
+       w = rb-lb; h = asc+desc; x = -lb; y = asc;\r
+    } else {\r
+       w = h = 1;\r
+       x = y = 0;\r
+    }\r
+\r
+    source = gdk_pixmap_new(NULL, w, h, 1);\r
+    mask = gdk_pixmap_new(NULL, w, h, 1);\r
+\r
+    /*\r
+     * Draw the mask character on the mask pixmap.\r
+     */\r
+    gc = gdk_gc_new(mask);\r
+    gdk_gc_set_foreground(gc, &dbg);\r
+    gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h);\r
+    if (cursor_val >= 0) {\r
+       text[1] = '\0';\r
+       text[0] = (char)cursor_val + 1;\r
+       gdk_gc_set_foreground(gc, &dfg);\r
+       gdk_draw_text(mask, cursor_font, gc, x, y, text, 1);\r
+    }\r
+    gdk_gc_unref(gc);\r
+\r
+    /*\r
+     * Draw the main character on the source pixmap.\r
+     */\r
+    gc = gdk_gc_new(source);\r
+    gdk_gc_set_foreground(gc, &dbg);\r
+    gdk_draw_rectangle(source, gc, 1, 0, 0, w, h);\r
+    if (cursor_val >= 0) {\r
+       text[1] = '\0';\r
+       text[0] = (char)cursor_val;\r
+       gdk_gc_set_foreground(gc, &dfg);\r
+       gdk_draw_text(source, cursor_font, gc, x, y, text, 1);\r
+    }\r
+    gdk_gc_unref(gc);\r
+\r
+    /*\r
+     * Create the cursor.\r
+     */\r
+    ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y);\r
+\r
+    /*\r
+     * Clean up.\r
+     */\r
+    gdk_pixmap_unref(source);\r
+    gdk_pixmap_unref(mask);\r
+\r
+    return ret;\r
+}\r
+\r
+void modalfatalbox(char *p, ...)\r
+{\r
+    va_list ap;\r
+    fprintf(stderr, "FATAL ERROR: ");\r
+    va_start(ap, p);\r
+    vfprintf(stderr, p, ap);\r
+    va_end(ap);\r
+    fputc('\n', stderr);\r
+    exit(1);\r
+}\r
+\r
+void cmdline_error(char *p, ...)\r
+{\r
+    va_list ap;\r
+    fprintf(stderr, "%s: ", appname);\r
+    va_start(ap, p);\r
+    vfprintf(stderr, p, ap);\r
+    va_end(ap);\r
+    fputc('\n', stderr);\r
+    exit(1);\r
+}\r
+\r
+char *get_x_display(void *frontend)\r
+{\r
+    return gdk_get_display();\r
+}\r
+\r
+long get_windowid(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+    return (long)GDK_WINDOW_XWINDOW(inst->area->window);\r
+}\r
+\r
+static void help(FILE *fp) {\r
+    if(fprintf(fp,\r
+"pterm option summary:\n"\r
+"\n"\r
+"  --display DISPLAY         Specify X display to use (note '--')\n"\r
+"  -name PREFIX              Prefix when looking up resources (default: pterm)\n"\r
+"  -fn FONT                  Normal text font\n"\r
+"  -fb FONT                  Bold text font\n"\r
+"  -geometry GEOMETRY        Position and size of window (size in characters)\n"\r
+"  -sl LINES                 Number of lines of scrollback\n"\r
+"  -fg COLOUR, -bg COLOUR    Foreground/background colour\n"\r
+"  -bfg COLOUR, -bbg COLOUR  Foreground/background bold colour\n"\r
+"  -cfg COLOUR, -bfg COLOUR  Foreground/background cursor colour\n"\r
+"  -T TITLE                  Window title\n"\r
+"  -ut, +ut                  Do(default) or do not update utmp\n"\r
+"  -ls, +ls                  Do(default) or do not make shell a login shell\n"\r
+"  -sb, +sb                  Do(default) or do not display a scrollbar\n"\r
+"  -log PATH                 Log all output to a file\n"\r
+"  -nethack                  Map numeric keypad to hjklyubn direction keys\n"\r
+"  -xrm RESOURCE-STRING      Set an X resource\n"\r
+"  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"\r
+        ) < 0 || fflush(fp) < 0) {\r
+       perror("output error");\r
+       exit(1);\r
+    }\r
+}\r
+\r
+int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,\r
+               struct gui_data *inst, Config *cfg)\r
+{\r
+    int err = 0;\r
+    char *val;\r
+\r
+    /*\r
+     * Macros to make argument handling easier. Note that because\r
+     * they need to call `continue', they cannot be contained in\r
+     * the usual do {...} while (0) wrapper to make them\r
+     * syntactically single statements; hence it is not legal to\r
+     * use one of these macros as an unbraced statement between\r
+     * `if' and `else'.\r
+     */\r
+#define EXPECTS_ARG { \\r
+    if (--argc <= 0) { \\r
+       err = 1; \\r
+       fprintf(stderr, "%s: %s expects an argument\n", appname, p); \\r
+        continue; \\r
+    } else \\r
+       val = *++argv; \\r
+}\r
+#define SECOND_PASS_ONLY { if (!do_everything) continue; }\r
+\r
+    while (--argc > 0) {\r
+       char *p = *++argv;\r
+        int ret;\r
+\r
+       /*\r
+        * Shameless cheating. Debian requires all X terminal\r
+        * emulators to support `-T title'; but\r
+        * cmdline_process_param will eat -T (it means no-pty) and\r
+        * complain that pterm doesn't support it. So, in pterm\r
+        * only, we convert -T into -title.\r
+        */\r
+       if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&\r
+           !strcmp(p, "-T"))\r
+           p = "-title";\r
+\r
+        ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),\r
+                                    do_everything ? 1 : -1, cfg);\r
+\r
+       if (ret == -2) {\r
+           cmdline_error("option \"%s\" requires an argument", p);\r
+       } else if (ret == 2) {\r
+           --argc, ++argv;            /* skip next argument */\r
+            continue;\r
+       } else if (ret == 1) {\r
+            continue;\r
+        }\r
+\r
+       if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->font.name, val, sizeof(cfg->font.name));\r
+           cfg->font.name[sizeof(cfg->font.name)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-fb")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->boldfont.name, val, sizeof(cfg->boldfont.name));\r
+           cfg->boldfont.name[sizeof(cfg->boldfont.name)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-fw")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->widefont.name, val, sizeof(cfg->widefont.name));\r
+           cfg->widefont.name[sizeof(cfg->widefont.name)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-fwb")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->wideboldfont.name, val, sizeof(cfg->wideboldfont.name));\r
+           cfg->wideboldfont.name[sizeof(cfg->wideboldfont.name)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-cs")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->line_codepage, val, sizeof(cfg->line_codepage));\r
+           cfg->line_codepage[sizeof(cfg->line_codepage)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-geometry")) {\r
+           int flags, x, y;\r
+           unsigned int w, h;\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+\r
+           flags = XParseGeometry(val, &x, &y, &w, &h);\r
+           if (flags & WidthValue)\r
+               cfg->width = (int)w;\r
+           if (flags & HeightValue)\r
+               cfg->height = (int)h;\r
+\r
+            if (flags & (XValue | YValue)) {\r
+                inst->xpos = x;\r
+                inst->ypos = y;\r
+                inst->gotpos = TRUE;\r
+                inst->gravity = ((flags & XNegative ? 1 : 0) |\r
+                                 (flags & YNegative ? 2 : 0));\r
+            }\r
+\r
+       } else if (!strcmp(p, "-sl")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           cfg->savelines = atoi(val);\r
+\r
+       } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||\r
+                  !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||\r
+                  !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {\r
+           GdkColor col;\r
+\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           if (!gdk_color_parse(val, &col)) {\r
+               err = 1;\r
+               fprintf(stderr, "%s: unable to parse colour \"%s\"\n",\r
+                        appname, val);\r
+           } else {\r
+               int index;\r
+               index = (!strcmp(p, "-fg") ? 0 :\r
+                        !strcmp(p, "-bg") ? 2 :\r
+                        !strcmp(p, "-bfg") ? 1 :\r
+                        !strcmp(p, "-bbg") ? 3 :\r
+                        !strcmp(p, "-cfg") ? 4 :\r
+                        !strcmp(p, "-cbg") ? 5 : -1);\r
+               assert(index != -1);\r
+               cfg->colours[index][0] = col.red / 256;\r
+               cfg->colours[index][1] = col.green / 256;\r
+               cfg->colours[index][2] = col.blue / 256;\r
+           }\r
+\r
+       } else if (use_pty_argv && !strcmp(p, "-e")) {\r
+           /* This option swallows all further arguments. */\r
+           if (!do_everything)\r
+               break;\r
+\r
+           if (--argc > 0) {\r
+               int i;\r
+               pty_argv = snewn(argc+1, char *);\r
+               ++argv;\r
+               for (i = 0; i < argc; i++)\r
+                   pty_argv[i] = argv[i];\r
+               pty_argv[argc] = NULL;\r
+               break;                 /* finished command-line processing */\r
+           } else\r
+               err = 1, fprintf(stderr, "%s: -e expects an argument\n",\r
+                                 appname);\r
+\r
+       } else if (!strcmp(p, "-title")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->wintitle, val, sizeof(cfg->wintitle));\r
+           cfg->wintitle[sizeof(cfg->wintitle)-1] = '\0';\r
+\r
+       } else if (!strcmp(p, "-log")) {\r
+           EXPECTS_ARG;\r
+           SECOND_PASS_ONLY;\r
+           strncpy(cfg->logfilename.path, val, sizeof(cfg->logfilename.path));\r
+           cfg->logfilename.path[sizeof(cfg->logfilename.path)-1] = '\0';\r
+           cfg->logtype = LGTYP_DEBUG;\r
+\r
+       } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->stamp_utmp = 0;\r
+\r
+       } else if (!strcmp(p, "-ut")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->stamp_utmp = 1;\r
+\r
+       } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->login_shell = 0;\r
+\r
+       } else if (!strcmp(p, "-ls")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->login_shell = 1;\r
+\r
+       } else if (!strcmp(p, "-nethack")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->nethack_keypad = 1;\r
+\r
+       } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->scrollbar = 0;\r
+\r
+       } else if (!strcmp(p, "-sb")) {\r
+           SECOND_PASS_ONLY;\r
+           cfg->scrollbar = 0;\r
+\r
+       } else if (!strcmp(p, "-name")) {\r
+           EXPECTS_ARG;\r
+           app_name = val;\r
+\r
+       } else if (!strcmp(p, "-xrm")) {\r
+           EXPECTS_ARG;\r
+           provide_xrm_string(val);\r
+\r
+       } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {\r
+           help(stdout);\r
+           exit(0);\r
+\r
+        } else if (!strcmp(p, "-pgpfp")) {\r
+            pgp_fingerprints();\r
+            exit(1);\r
+\r
+       } else if(p[0] != '-' && (!do_everything ||\r
+                                  process_nonoption_arg(p, cfg,\r
+                                                       allow_launch))) {\r
+            /* do nothing */\r
+\r
+       } else {\r
+           err = 1;\r
+           fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);\r
+       }\r
+    }\r
+\r
+    return err;\r
+}\r
+\r
+int uxsel_input_add(int fd, int rwx) {\r
+    int flags = 0;\r
+    if (rwx & 1) flags |= GDK_INPUT_READ;\r
+    if (rwx & 2) flags |= GDK_INPUT_WRITE;\r
+    if (rwx & 4) flags |= GDK_INPUT_EXCEPTION;\r
+    assert(flags);\r
+    return gdk_input_add(fd, flags, fd_input_func, NULL);\r
+}\r
+\r
+void uxsel_input_remove(int id) {\r
+    gdk_input_remove(id);\r
+}\r
+\r
+void setup_fonts_ucs(struct gui_data *inst)\r
+{\r
+    if (inst->fonts[0])\r
+        unifont_destroy(inst->fonts[0]);\r
+    if (inst->fonts[1])\r
+        unifont_destroy(inst->fonts[1]);\r
+    if (inst->fonts[2])\r
+        unifont_destroy(inst->fonts[2]);\r
+    if (inst->fonts[3])\r
+        unifont_destroy(inst->fonts[3]);\r
+\r
+    inst->fonts[0] = unifont_create(inst->area, inst->cfg.font.name,\r
+                                   FALSE, FALSE,\r
+                                   inst->cfg.shadowboldoffset,\r
+                                   inst->cfg.shadowbold);\r
+    if (!inst->fonts[0]) {\r
+       fprintf(stderr, "%s: unable to load font \"%s\"\n", appname,\r
+               inst->cfg.font.name);\r
+       exit(1);\r
+    }\r
+\r
+    if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) {\r
+       inst->fonts[1] = NULL;\r
+    } else {\r
+       inst->fonts[1] = unifont_create(inst->area, inst->cfg.boldfont.name,\r
+                                       FALSE, TRUE,\r
+                                       inst->cfg.shadowboldoffset,\r
+                                       inst->cfg.shadowbold);\r
+       if (!inst->fonts[1]) {\r
+           fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname,\r
+                   inst->cfg.boldfont.name);\r
+           exit(1);\r
+       }\r
+    }\r
+\r
+    if (inst->cfg.widefont.name[0]) {\r
+       inst->fonts[2] = unifont_create(inst->area, inst->cfg.widefont.name,\r
+                                       TRUE, FALSE,\r
+                                       inst->cfg.shadowboldoffset,\r
+                                       inst->cfg.shadowbold);\r
+       if (!inst->fonts[2]) {\r
+           fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname,\r
+                   inst->cfg.widefont.name);\r
+           exit(1);\r
+       }\r
+    } else {\r
+       inst->fonts[2] = NULL;\r
+    }\r
+\r
+    if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) {\r
+       inst->fonts[3] = NULL;\r
+    } else {\r
+       inst->fonts[3] = unifont_create(inst->area,\r
+                                       inst->cfg.wideboldfont.name, TRUE,\r
+                                       TRUE, inst->cfg.shadowboldoffset,\r
+                                       inst->cfg.shadowbold);\r
+       if (!inst->fonts[3]) {\r
+           fprintf(stderr, "%s: unable to load wide bold font \"%s\"\n", appname,\r
+                   inst->cfg.boldfont.name);\r
+           exit(1);\r
+       }\r
+    }\r
+\r
+    inst->font_width = inst->fonts[0]->width;\r
+    inst->font_height = inst->fonts[0]->height;\r
+\r
+    inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage,\r
+                                   inst->cfg.utf8_override,\r
+                                   inst->fonts[0]->public_charset,\r
+                                   inst->cfg.vtmode);\r
+}\r
+\r
+void set_geom_hints(struct gui_data *inst)\r
+{\r
+    GdkGeometry geom;\r
+    geom.min_width = inst->font_width + 2*inst->cfg.window_border;\r
+    geom.min_height = inst->font_height + 2*inst->cfg.window_border;\r
+    geom.max_width = geom.max_height = -1;\r
+    geom.base_width = 2*inst->cfg.window_border;\r
+    geom.base_height = 2*inst->cfg.window_border;\r
+    geom.width_inc = inst->font_width;\r
+    geom.height_inc = inst->font_height;\r
+    geom.min_aspect = geom.max_aspect = 0;\r
+    gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom,\r
+                                  GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |\r
+                                  GDK_HINT_RESIZE_INC);\r
+}\r
+\r
+void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    term_clrsb(inst->term);\r
+}\r
+\r
+void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    term_pwron(inst->term, TRUE);\r
+    if (inst->ldisc)\r
+       ldisc_send(inst->ldisc, NULL, 0, 0);\r
+}\r
+\r
+void copy_all_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    term_copyall(inst->term);\r
+}\r
+\r
+void special_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),\r
+                                                  "user-data"));\r
+\r
+    if (inst->back)\r
+       inst->back->special(inst->backhandle, code);\r
+}\r
+\r
+void about_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    about_box(inst->window);\r
+}\r
+\r
+void event_log_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    showeventlog(inst->eventlogstuff, inst->window);\r
+}\r
+\r
+void change_settings_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    /* This maps colour indices in inst->cfg to those used in inst->cols. */\r
+    static const int ww[] = {\r
+       256, 257, 258, 259, 260, 261,\r
+       0, 8, 1, 9, 2, 10, 3, 11,\r
+       4, 12, 5, 13, 6, 14, 7, 15\r
+    };\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    char *title = dupcat(appname, " Reconfiguration", NULL);\r
+    Config cfg2, oldcfg;\r
+    int i, need_size;\r
+\r
+    assert(lenof(ww) == NCFGCOLOURS);\r
+\r
+    if (inst->reconfiguring)\r
+      return;\r
+    else\r
+      inst->reconfiguring = TRUE;\r
+\r
+    cfg2 = inst->cfg;                  /* structure copy */\r
+\r
+    if (do_config_box(title, &cfg2, 1,\r
+                     inst->back?inst->back->cfg_info(inst->backhandle):0)) {\r
+\r
+        oldcfg = inst->cfg;            /* structure copy */\r
+        inst->cfg = cfg2;              /* structure copy */\r
+\r
+        /* Pass new config data to the logging module */\r
+        log_reconfig(inst->logctx, &cfg2);\r
+        /*\r
+         * Flush the line discipline's edit buffer in the case\r
+         * where local editing has just been disabled.\r
+         */\r
+        if (inst->ldisc)\r
+           ldisc_send(inst->ldisc, NULL, 0, 0);\r
+        /* Pass new config data to the terminal */\r
+        term_reconfig(inst->term, &cfg2);\r
+        /* Pass new config data to the back end */\r
+        if (inst->back)\r
+           inst->back->reconfig(inst->backhandle, &cfg2);\r
+\r
+        /*\r
+         * Just setting inst->cfg is sufficient to cause colour\r
+         * setting changes to appear on the next ESC]R palette\r
+         * reset. But we should also check whether any colour\r
+         * settings have been changed, and revert the ones that\r
+         * have to the new default, on the assumption that the user\r
+         * is most likely to want an immediate update.\r
+         */\r
+        for (i = 0; i < NCFGCOLOURS; i++) {\r
+            if (oldcfg.colours[i][0] != cfg2.colours[i][0] ||\r
+                oldcfg.colours[i][1] != cfg2.colours[i][1] ||\r
+                oldcfg.colours[i][2] != cfg2.colours[i][2]) {\r
+                real_palette_set(inst, ww[i], cfg2.colours[i][0],\r
+                                 cfg2.colours[i][1],\r
+                                 cfg2.colours[i][2]);\r
+\r
+               /*\r
+                * If the default background has changed, we must\r
+                * repaint the space in between the window border\r
+                * and the text area.\r
+                */\r
+               if (i == 258) {\r
+                   set_window_background(inst);\r
+                   draw_backing_rect(inst);\r
+               }\r
+           }\r
+        }\r
+\r
+        /*\r
+         * If the scrollbar needs to be shown, hidden, or moved\r
+         * from one end to the other of the window, do so now.\r
+         */\r
+        if (oldcfg.scrollbar != cfg2.scrollbar) {\r
+            if (cfg2.scrollbar)\r
+                gtk_widget_show(inst->sbar);\r
+            else\r
+                gtk_widget_hide(inst->sbar);\r
+        }\r
+        if (oldcfg.scrollbar_on_left != cfg2.scrollbar_on_left) {\r
+            gtk_box_reorder_child(inst->hbox, inst->sbar,\r
+                                  cfg2.scrollbar_on_left ? 0 : 1);\r
+        }\r
+\r
+        /*\r
+         * Change the window title, if required.\r
+         */\r
+        if (strcmp(oldcfg.wintitle, cfg2.wintitle))\r
+            set_title(inst, cfg2.wintitle);\r
+       set_window_titles(inst);\r
+\r
+        /*\r
+         * Redo the whole tangled fonts and Unicode mess if\r
+         * necessary.\r
+         */\r
+        if (strcmp(oldcfg.font.name, cfg2.font.name) ||\r
+            strcmp(oldcfg.boldfont.name, cfg2.boldfont.name) ||\r
+            strcmp(oldcfg.widefont.name, cfg2.widefont.name) ||\r
+            strcmp(oldcfg.wideboldfont.name, cfg2.wideboldfont.name) ||\r
+            strcmp(oldcfg.line_codepage, cfg2.line_codepage) ||\r
+           oldcfg.vtmode != cfg2.vtmode ||\r
+           oldcfg.shadowbold != cfg2.shadowbold) {\r
+            setup_fonts_ucs(inst);\r
+            need_size = 1;\r
+        } else\r
+            need_size = 0;\r
+\r
+        /*\r
+         * Resize the window.\r
+         */\r
+        if (oldcfg.width != cfg2.width || oldcfg.height != cfg2.height ||\r
+            oldcfg.window_border != cfg2.window_border || need_size) {\r
+            set_geom_hints(inst);\r
+            request_resize(inst, cfg2.width, cfg2.height);\r
+        } else {\r
+           /*\r
+            * The above will have caused a call to term_size() for\r
+            * us if it happened. If the user has fiddled with only\r
+            * the scrollback size, the above will not have\r
+            * happened and we will need an explicit term_size()\r
+            * here.\r
+            */\r
+           if (oldcfg.savelines != cfg2.savelines)\r
+               term_size(inst->term, inst->term->rows, inst->term->cols,\r
+                         cfg2.savelines);\r
+       }\r
+\r
+        term_invalidate(inst->term);\r
+\r
+       /*\r
+        * We do an explicit full redraw here to ensure the window\r
+        * border has been redrawn as well as the text area.\r
+        */\r
+       gtk_widget_queue_draw(inst->area);\r
+    }\r
+    sfree(title);\r
+    inst->reconfiguring = FALSE;\r
+}\r
+\r
+void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...)\r
+{\r
+    /*\r
+     * Re-execing ourself is not an exact science under Unix. I do\r
+     * the best I can by using /proc/self/exe if available and by\r
+     * assuming argv[0] can be found on $PATH if not.\r
+     * \r
+     * Note that we also have to reconstruct the elements of the\r
+     * original argv which gtk swallowed, since the user wants the\r
+     * new session to appear on the same X display as the old one.\r
+     */\r
+    char **args;\r
+    va_list ap;\r
+    int i, n;\r
+    int pid;\r
+\r
+    /*\r
+     * Collect the arguments with which to re-exec ourself.\r
+     */\r
+    va_start(ap, fd_to_close);\r
+    n = 2;                            /* progname and terminating NULL */\r
+    n += inst->ngtkargs;\r
+    while (va_arg(ap, char *) != NULL)\r
+       n++;\r
+    va_end(ap);\r
+\r
+    args = snewn(n, char *);\r
+    args[0] = inst->progname;\r
+    args[n-1] = NULL;\r
+    for (i = 0; i < inst->ngtkargs; i++)\r
+       args[i+1] = inst->gtkargvstart[i];\r
+\r
+    i++;\r
+    va_start(ap, fd_to_close);\r
+    while ((args[i++] = va_arg(ap, char *)) != NULL);\r
+    va_end(ap);\r
+\r
+    assert(i == n);\r
+\r
+    /*\r
+     * Do the double fork.\r
+     */\r
+    pid = fork();\r
+    if (pid < 0) {\r
+       perror("fork");\r
+       return;\r
+    }\r
+\r
+    if (pid == 0) {\r
+       int pid2 = fork();\r
+       if (pid2 < 0) {\r
+           perror("fork");\r
+           _exit(1);\r
+       } else if (pid2 > 0) {\r
+           /*\r
+            * First child has successfully forked second child. My\r
+            * Work Here Is Done. Note the use of _exit rather than\r
+            * exit: the latter appears to cause destroy messages\r
+            * to be sent to the X server. I suspect gtk uses\r
+            * atexit.\r
+            */\r
+           _exit(0);\r
+       }\r
+\r
+       /*\r
+        * If we reach here, we are the second child, so we now\r
+        * actually perform the exec.\r
+        */\r
+       if (fd_to_close >= 0)\r
+           close(fd_to_close);\r
+\r
+       execv("/proc/self/exe", args);\r
+       execvp(inst->progname, args);\r
+       perror("exec");\r
+       _exit(127);\r
+\r
+    } else {\r
+       int status;\r
+       waitpid(pid, &status, 0);\r
+    }\r
+\r
+}\r
+\r
+void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)gdata;\r
+    /*\r
+     * For this feature we must marshal cfg and (possibly) pty_argv\r
+     * into a byte stream, create a pipe, and send this byte stream\r
+     * to the child through the pipe.\r
+     */\r
+    int i, ret, size;\r
+    char *data;\r
+    char option[80];\r
+    int pipefd[2];\r
+\r
+    if (pipe(pipefd) < 0) {\r
+       perror("pipe");\r
+       return;\r
+    }\r
+\r
+    size = sizeof(inst->cfg);\r
+    if (use_pty_argv && pty_argv) {\r
+       for (i = 0; pty_argv[i]; i++)\r
+           size += strlen(pty_argv[i]) + 1;\r
+    }\r
+\r
+    data = snewn(size, char);\r
+    memcpy(data, &inst->cfg, sizeof(inst->cfg));\r
+    if (use_pty_argv && pty_argv) {\r
+       int p = sizeof(inst->cfg);\r
+       for (i = 0; pty_argv[i]; i++) {\r
+           strcpy(data + p, pty_argv[i]);\r
+           p += strlen(pty_argv[i]) + 1;\r
+       }\r
+       assert(p == size);\r
+    }\r
+\r
+    sprintf(option, "---[%d,%d]", pipefd[0], size);\r
+    fcntl(pipefd[0], F_SETFD, 0);\r
+    fork_and_exec_self(inst, pipefd[1], option, NULL);\r
+    close(pipefd[0]);\r
+\r
+    i = ret = 0;\r
+    while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)\r
+       i += ret;\r
+    if (ret < 0)\r
+       perror("write to pipe");\r
+    close(pipefd[1]);\r
+    sfree(data);\r
+}\r
+\r
+int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg)\r
+{\r
+    int fd, i, ret, size;\r
+    char *data;\r
+\r
+    if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {\r
+       fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);\r
+       exit(1);\r
+    }\r
+\r
+    data = snewn(size, char);\r
+    i = ret = 0;\r
+    while (i < size && (ret = read(fd, data + i, size - i)) > 0)\r
+       i += ret;\r
+    if (ret < 0) {\r
+       perror("read from pipe");\r
+       exit(1);\r
+    } else if (i < size) {\r
+       fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",\r
+               appname);\r
+       exit(1);\r
+    }\r
+\r
+    memcpy(cfg, data, sizeof(Config));\r
+    if (use_pty_argv && size > sizeof(Config)) {\r
+       int n = 0;\r
+       i = sizeof(Config);\r
+       while (i < size) {\r
+           while (i < size && data[i]) i++;\r
+           if (i >= size) {\r
+               fprintf(stderr, "%s: malformed Duplicate Session data\n",\r
+                       appname);\r
+               exit(1);\r
+           }\r
+           i++;\r
+           n++;\r
+       }\r
+       pty_argv = snewn(n+1, char *);\r
+       pty_argv[n] = NULL;\r
+       n = 0;\r
+       i = sizeof(Config);\r
+       while (i < size) {\r
+           char *p = data + i;\r
+           while (i < size && data[i]) i++;\r
+           assert(i < size);\r
+           i++;\r
+           pty_argv[n++] = dupstr(p);\r
+       }\r
+    }\r
+\r
+    return 0;\r
+}\r
+\r
+void new_session_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    fork_and_exec_self(inst, -1, NULL);\r
+}\r
+\r
+void restart_session_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+\r
+    if (!inst->back) {\r
+       logevent(inst, "----- Session restarted -----");\r
+       term_pwron(inst->term, FALSE);\r
+       start_backend(inst);\r
+       inst->exited = FALSE;\r
+    }\r
+}\r
+\r
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");\r
+\r
+    fork_and_exec_self(inst, -1, "-load", str, NULL);\r
+}\r
+\r
+void saved_session_freedata(GtkMenuItem *item, gpointer data)\r
+{\r
+    char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");\r
+\r
+    sfree(str);\r
+}\r
+\r
+static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)data;\r
+    struct sesslist sesslist;\r
+    int i;\r
+\r
+    gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu),\r
+                         (GtkCallback)gtk_widget_destroy, NULL);\r
+\r
+    get_sesslist(&sesslist, TRUE);\r
+    /* skip sesslist.sessions[0] == Default Settings */\r
+    for (i = 1; i < sesslist.nsessions; i++) {\r
+       GtkWidget *menuitem =\r
+           gtk_menu_item_new_with_label(sesslist.sessions[i]);\r
+       gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);\r
+       gtk_widget_show(menuitem);\r
+       gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",\r
+                           dupstr(sesslist.sessions[i]));\r
+       gtk_signal_connect(GTK_OBJECT(menuitem), "activate",\r
+                          GTK_SIGNAL_FUNC(saved_session_menuitem),\r
+                          inst);\r
+       gtk_signal_connect(GTK_OBJECT(menuitem), "destroy",\r
+                          GTK_SIGNAL_FUNC(saved_session_freedata),\r
+                          inst);\r
+    }\r
+    if (sesslist.nsessions <= 1) {\r
+       GtkWidget *menuitem =\r
+           gtk_menu_item_new_with_label("(No sessions)");\r
+       gtk_widget_set_sensitive(menuitem, FALSE);\r
+       gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);\r
+       gtk_widget_show(menuitem);\r
+    }\r
+    get_sesslist(&sesslist, FALSE); /* free up */\r
+}\r
+\r
+void set_window_icon(GtkWidget *window, const char *const *const *icon,\r
+                    int n_icon)\r
+{\r
+    GdkPixmap *iconpm;\r
+    GdkBitmap *iconmask;\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    GList *iconlist;\r
+    int n;\r
+#endif\r
+\r
+    if (!n_icon)\r
+       return;\r
+\r
+    gtk_widget_realize(window);\r
+    iconpm = gdk_pixmap_create_from_xpm_d(window->window, &iconmask,\r
+                                         NULL, (gchar **)icon[0]);\r
+    gdk_window_set_icon(window->window, NULL, iconpm, iconmask);\r
+\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    iconlist = NULL;\r
+    for (n = 0; n < n_icon; n++) {\r
+       iconlist =\r
+           g_list_append(iconlist,\r
+                         gdk_pixbuf_new_from_xpm_data((const gchar **)\r
+                                                      icon[n]));\r
+    }\r
+    gdk_window_set_icon_list(window->window, iconlist);\r
+#endif\r
+}\r
+\r
+void update_specials_menu(void *frontend)\r
+{\r
+    struct gui_data *inst = (struct gui_data *)frontend;\r
+\r
+    const struct telnet_special *specials;\r
+\r
+    if (inst->back)\r
+       specials = inst->back->get_specials(inst->backhandle);\r
+    else\r
+       specials = NULL;\r
+\r
+    /* I believe this disposes of submenus too. */\r
+    gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),\r
+                         (GtkCallback)gtk_widget_destroy, NULL);\r
+    if (specials) {\r
+       int i;\r
+       GtkWidget *menu = inst->specialsmenu;\r
+       /* A lame "stack" for submenus that will do for now. */\r
+       GtkWidget *saved_menu = NULL;\r
+       int nesting = 1;\r
+       for (i = 0; nesting > 0; i++) {\r
+           GtkWidget *menuitem = NULL;\r
+           switch (specials[i].code) {\r
+             case TS_SUBMENU:\r
+               assert (nesting < 2);\r
+               saved_menu = menu; /* XXX lame stacking */\r
+               menu = gtk_menu_new();\r
+               menuitem = gtk_menu_item_new_with_label(specials[i].name);\r
+               gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);\r
+               gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);\r
+               gtk_widget_show(menuitem);\r
+               menuitem = NULL;\r
+               nesting++;\r
+               break;\r
+             case TS_EXITMENU:\r
+               nesting--;\r
+               if (nesting) {\r
+                   menu = saved_menu; /* XXX lame stacking */\r
+                   saved_menu = NULL;\r
+               }\r
+               break;\r
+             case TS_SEP:\r
+               menuitem = gtk_menu_item_new();\r
+               break;\r
+             default:\r
+               menuitem = gtk_menu_item_new_with_label(specials[i].name);\r
+               gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",\r
+                                   GINT_TO_POINTER(specials[i].code));\r
+               gtk_signal_connect(GTK_OBJECT(menuitem), "activate",\r
+                                  GTK_SIGNAL_FUNC(special_menuitem), inst);\r
+               break;\r
+           }\r
+           if (menuitem) {\r
+               gtk_container_add(GTK_CONTAINER(menu), menuitem);\r
+               gtk_widget_show(menuitem);\r
+           }\r
+       }\r
+       gtk_widget_show(inst->specialsitem1);\r
+       gtk_widget_show(inst->specialsitem2);\r
+    } else {\r
+       gtk_widget_hide(inst->specialsitem1);\r
+       gtk_widget_hide(inst->specialsitem2);\r
+    }\r
+}\r
+\r
+static void start_backend(struct gui_data *inst)\r
+{\r
+    extern Backend *select_backend(Config *cfg);\r
+    char *realhost;\r
+    const char *error;\r
+\r
+    inst->back = select_backend(&inst->cfg);\r
+\r
+    error = inst->back->init((void *)inst, &inst->backhandle,\r
+                            &inst->cfg, inst->cfg.host, inst->cfg.port,\r
+                            &realhost, inst->cfg.tcp_nodelay,\r
+                            inst->cfg.tcp_keepalives);\r
+\r
+    if (error) {\r
+       char *msg = dupprintf("Unable to open connection to %s:\n%s",\r
+                             inst->cfg.host, error);\r
+       inst->exited = TRUE;\r
+       fatal_message_box(inst->window, msg);\r
+       sfree(msg);\r
+       exit(0);\r
+    }\r
+\r
+    if (inst->cfg.wintitle[0]) {\r
+       set_title(inst, inst->cfg.wintitle);\r
+       set_icon(inst, inst->cfg.wintitle);\r
+    } else {\r
+       char *title = make_default_wintitle(realhost);\r
+       set_title(inst, title);\r
+       set_icon(inst, title);\r
+       sfree(title);\r
+    }\r
+    sfree(realhost);\r
+\r
+    inst->back->provide_logctx(inst->backhandle, inst->logctx);\r
+\r
+    term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle);\r
+\r
+    inst->ldisc =\r
+       ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle,\r
+                    inst);\r
+\r
+    gtk_widget_set_sensitive(inst->restartitem, FALSE);\r
+}\r
+\r
+int pt_main(int argc, char **argv)\r
+{\r
+    extern int cfgbox(Config *cfg);\r
+    struct gui_data *inst;\r
+\r
+    /*\r
+     * Create an instance structure and initialise to zeroes\r
+     */\r
+    inst = snew(struct gui_data);\r
+    memset(inst, 0, sizeof(*inst));\r
+    inst->alt_keycode = -1;            /* this one needs _not_ to be zero */\r
+    inst->busy_status = BUSY_NOT;\r
+\r
+    /* defer any child exit handling until we're ready to deal with\r
+     * it */\r
+    block_signal(SIGCHLD, 1);\r
+\r
+    inst->progname = argv[0];\r
+    /*\r
+     * Copy the original argv before letting gtk_init fiddle with\r
+     * it. It will be required later.\r
+     */\r
+    {\r
+       int i, oldargc;\r
+       inst->gtkargvstart = snewn(argc-1, char *);\r
+       for (i = 1; i < argc; i++)\r
+           inst->gtkargvstart[i-1] = dupstr(argv[i]);\r
+       oldargc = argc;\r
+       gtk_init(&argc, &argv);\r
+       inst->ngtkargs = oldargc - argc;\r
+    }\r
+\r
+    if (argc > 1 && !strncmp(argv[1], "---", 3)) {\r
+       read_dupsession_data(inst, &inst->cfg, argv[1]);\r
+       /* Splatter this argument so it doesn't clutter a ps listing */\r
+       memset(argv[1], 0, strlen(argv[1]));\r
+    } else {\r
+       /* By default, we bring up the config dialog, rather than launching\r
+        * a session. This gets set to TRUE if something happens to change\r
+        * that (e.g., a hostname is specified on the command-line). */\r
+       int allow_launch = FALSE;\r
+       if (do_cmdline(argc, argv, 0, &allow_launch, inst, &inst->cfg))\r
+           exit(1);                   /* pre-defaults pass to get -class */\r
+       do_defaults(NULL, &inst->cfg);\r
+       if (do_cmdline(argc, argv, 1, &allow_launch, inst, &inst->cfg))\r
+           exit(1);                   /* post-defaults, do everything */\r
+\r
+       cmdline_run_saved(&inst->cfg);\r
+\r
+       if (loaded_session)\r
+           allow_launch = TRUE;\r
+\r
+       if ((!allow_launch || !cfg_launchable(&inst->cfg)) &&\r
+           !cfgbox(&inst->cfg))\r
+           exit(0);                   /* config box hit Cancel */\r
+    }\r
+\r
+    if (!compound_text_atom)\r
+        compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);\r
+    if (!utf8_string_atom)\r
+        utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);\r
+\r
+    inst->area = gtk_drawing_area_new();\r
+\r
+    setup_fonts_ucs(inst);\r
+    init_cutbuffers();\r
+\r
+    inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);\r
+    if (inst->cfg.winclass[0])\r
+        gtk_window_set_wmclass(GTK_WINDOW(inst->window),\r
+                               inst->cfg.winclass, inst->cfg.winclass);\r
+\r
+    /*\r
+     * Set up the colour map.\r
+     */\r
+    palette_reset(inst);\r
+\r
+    inst->width = inst->cfg.width;\r
+    inst->height = inst->cfg.height;\r
+\r
+    gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),\r
+                         inst->font_width * inst->cfg.width + 2*inst->cfg.window_border,\r
+                         inst->font_height * inst->cfg.height + 2*inst->cfg.window_border);\r
+    inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));\r
+    inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);\r
+    inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));\r
+    /*\r
+     * We always create the scrollbar; it remains invisible if\r
+     * unwanted, so we can pop it up quickly if it suddenly becomes\r
+     * desirable.\r
+     */\r
+    if (inst->cfg.scrollbar_on_left)\r
+        gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);\r
+    gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0);\r
+    if (!inst->cfg.scrollbar_on_left)\r
+        gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);\r
+\r
+    gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));\r
+\r
+    set_geom_hints(inst);\r
+\r
+    gtk_widget_show(inst->area);\r
+    if (inst->cfg.scrollbar)\r
+       gtk_widget_show(inst->sbar);\r
+    else\r
+       gtk_widget_hide(inst->sbar);\r
+    gtk_widget_show(GTK_WIDGET(inst->hbox));\r
+\r
+    if (inst->gotpos) {\r
+        int x = inst->xpos, y = inst->ypos;\r
+        GtkRequisition req;\r
+        gtk_widget_size_request(GTK_WIDGET(inst->window), &req);\r
+        if (inst->gravity & 1) x += gdk_screen_width() - req.width;\r
+        if (inst->gravity & 2) y += gdk_screen_height() - req.height;\r
+       gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);\r
+       gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);\r
+    }\r
+\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "destroy",\r
+                      GTK_SIGNAL_FUNC(destroy), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event",\r
+                      GTK_SIGNAL_FUNC(delete_window), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event",\r
+                      GTK_SIGNAL_FUNC(key_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "key_release_event",\r
+                      GTK_SIGNAL_FUNC(key_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event",\r
+                      GTK_SIGNAL_FUNC(focus_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event",\r
+                      GTK_SIGNAL_FUNC(focus_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event",\r
+                      GTK_SIGNAL_FUNC(configure_area), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",\r
+                      GTK_SIGNAL_FUNC(expose_area), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event",\r
+                      GTK_SIGNAL_FUNC(button_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event",\r
+                      GTK_SIGNAL_FUNC(button_event), inst);\r
+#if GTK_CHECK_VERSION(2,0,0)\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event",\r
+                      GTK_SIGNAL_FUNC(scroll_event), inst);\r
+#endif\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event",\r
+                      GTK_SIGNAL_FUNC(motion_event), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received",\r
+                      GTK_SIGNAL_FUNC(selection_received), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get",\r
+                      GTK_SIGNAL_FUNC(selection_get), inst);\r
+    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event",\r
+                      GTK_SIGNAL_FUNC(selection_clear), inst);\r
+    if (inst->cfg.scrollbar)\r
+       gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed",\r
+                          GTK_SIGNAL_FUNC(scrollbar_moved), inst);\r
+    gtk_widget_add_events(GTK_WIDGET(inst->area),\r
+                         GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |\r
+                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |\r
+                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK);\r
+\r
+    {\r
+       extern const char *const *const main_icon[];\r
+       extern const int n_main_icon;\r
+       set_window_icon(inst->window, main_icon, n_main_icon);\r
+    }\r
+\r
+    gtk_widget_show(inst->window);\r
+\r
+    set_window_background(inst);\r
+\r
+    /*\r
+     * Set up the Ctrl+rightclick context menu.\r
+     */\r
+    {\r
+       GtkWidget *menuitem;\r
+       char *s;\r
+       extern const int use_event_log, new_session, saved_sessions;\r
+\r
+       inst->menu = gtk_menu_new();\r
+\r
+#define MKMENUITEM(title, func) do                                      \\r
+        {                                                               \\r
+            menuitem = gtk_menu_item_new_with_label(title);             \\r
+            gtk_container_add(GTK_CONTAINER(inst->menu), menuitem);     \\r
+            gtk_widget_show(menuitem);                                  \\r
+            gtk_signal_connect(GTK_OBJECT(menuitem), "activate",        \\r
+                               GTK_SIGNAL_FUNC(func), inst);            \\r
+        } while (0)\r
+\r
+#define MKSUBMENU(title) do                                             \\r
+        {                                                               \\r
+            menuitem = gtk_menu_item_new_with_label(title);             \\r
+            gtk_container_add(GTK_CONTAINER(inst->menu), menuitem);     \\r
+            gtk_widget_show(menuitem);                                  \\r
+        } while (0)\r
+\r
+#define MKSEP() do                                                      \\r
+        {                                                               \\r
+            menuitem = gtk_menu_item_new();                             \\r
+            gtk_container_add(GTK_CONTAINER(inst->menu), menuitem);     \\r
+            gtk_widget_show(menuitem);                                  \\r
+        } while (0)\r
+\r
+       if (new_session)\r
+           MKMENUITEM("New Session...", new_session_menuitem);\r
+        MKMENUITEM("Restart Session", restart_session_menuitem);\r
+       inst->restartitem = menuitem;\r
+       gtk_widget_set_sensitive(inst->restartitem, FALSE);\r
+        MKMENUITEM("Duplicate Session", dup_session_menuitem);\r
+       if (saved_sessions) {\r
+           inst->sessionsmenu = gtk_menu_new();\r
+           /* sessionsmenu will be updated when it's invoked */\r
+           /* XXX is this the right way to do dynamic menus in Gtk? */\r
+           MKMENUITEM("Saved Sessions", update_savedsess_menu);\r
+           gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),\r
+                                     inst->sessionsmenu);\r
+       }\r
+       MKSEP();\r
+        MKMENUITEM("Change Settings...", change_settings_menuitem);\r
+       MKSEP();\r
+       if (use_event_log)\r
+           MKMENUITEM("Event Log", event_log_menuitem);\r
+       MKSUBMENU("Special Commands");\r
+       inst->specialsmenu = gtk_menu_new();\r
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);\r
+       inst->specialsitem1 = menuitem;\r
+       MKSEP();\r
+       inst->specialsitem2 = menuitem;\r
+       gtk_widget_hide(inst->specialsitem1);\r
+       gtk_widget_hide(inst->specialsitem2);\r
+       MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);\r
+       MKMENUITEM("Reset Terminal", reset_terminal_menuitem);\r
+       MKMENUITEM("Copy All", copy_all_menuitem);\r
+       MKSEP();\r
+       s = dupcat("About ", appname, NULL);\r
+       MKMENUITEM(s, about_menuitem);\r
+       sfree(s);\r
+#undef MKMENUITEM\r
+#undef MKSUBMENU\r
+#undef MKSEP\r
+    }\r
+\r
+    inst->textcursor = make_mouse_ptr(inst, GDK_XTERM);\r
+    inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);\r
+    inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);\r
+    inst->blankcursor = make_mouse_ptr(inst, -1);\r
+    make_mouse_ptr(inst, -2);         /* clean up cursor font */\r
+    inst->currcursor = inst->textcursor;\r
+    show_mouseptr(inst, 1);\r
+\r
+    inst->eventlogstuff = eventlogstuff_new();\r
+\r
+    inst->term = term_init(&inst->cfg, &inst->ucsdata, inst);\r
+    inst->logctx = log_init(inst, &inst->cfg);\r
+    term_provide_logctx(inst->term, inst->logctx);\r
+\r
+    uxsel_init();\r
+\r
+    term_size(inst->term, inst->cfg.height, inst->cfg.width, inst->cfg.savelines);\r
+\r
+    start_backend(inst);\r
+\r
+    ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */\r
+\r
+    /* now we're reday to deal with the child exit handler being\r
+     * called */\r
+    block_signal(SIGCHLD, 0);\r
+\r
+    /*\r
+     * Block SIGPIPE: if we attempt Duplicate Session or similar\r
+     * and it falls over in some way, we certainly don't want\r
+     * SIGPIPE terminating the main pterm/PuTTY. Note that we do\r
+     * this _after_ (at least pterm) forks off its child process,\r
+     * since the child wants SIGPIPE handled in the usual way.\r
+     */\r
+    block_signal(SIGPIPE, 1);\r
+\r
+    inst->exited = FALSE;\r
+\r
+    gtk_main();\r
+\r
+    return 0;\r
+}\r