+/*\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