X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/f160b7b8908cac4a7ca37b49928b7855fe0a11fe..3e4a1fd769ceed639b6c27b7095e1172996adb86:/unix/gtkwin.c diff --git a/unix/gtkwin.c b/unix/gtkwin.c index dc8d6607..e4af6280 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -34,6 +34,16 @@ #define CAT(x,y) CAT2(x,y) #define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)} +#if GTK_CHECK_VERSION(2,0,0) +ASSERT(sizeof(long) <= sizeof(gsize)); +#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l) +#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p) +#else /* Gtk 1.2 */ +ASSERT(sizeof(long) <= sizeof(gpointer)); +#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l)) +#define GPOINTER_TO_LONG(p) ((long)(p)) +#endif + /* Colours come in two flavours: configurable, and xterm-extended. */ #define NCFGCOLOURS (lenof(((Config *)0)->colours)) #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ @@ -75,6 +85,7 @@ struct gui_data { int mouseptr_visible; int busy_status; guint term_paste_idle_id; + guint term_exit_idle_id; int alt_keycode; int alt_digits; char wintitle[sizeof(((Config *)0)->wintitle)]; @@ -166,14 +177,9 @@ int platform_default_i(const char *name, int def) return def; } +/* Dummy routine, only required in plink. */ void ldisc_update(void *frontend, int echo, int edit) { - /* - * This is a stub in pterm. If I ever produce a Unix - * command-line ssh/telnet/rlogin client (i.e. a port of plink) - * then it will require some termios manoeuvring analogous to - * that in the Windows plink.c, but here it's meaningless. - */ } char *get_ttymode(void *frontend, const char *mode) @@ -500,9 +506,9 @@ gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; - char output[32]; + char output[256]; wchar_t ucsoutput[2]; - int ucsval, start, end, special, use_ucsoutput; + int ucsval, start, end, special, output_charset, use_ucsoutput; /* Remember the timestamp. */ inst->input_event_time = event->time; @@ -510,6 +516,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) /* By default, nothing is generated. */ end = start = 0; special = use_ucsoutput = FALSE; + output_charset = CS_ISO8859_1; /* * If Alt is being released after typing an Alt+numberpad @@ -528,6 +535,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_DEBUGGING printf("Alt key up, keycode = %d\n", inst->alt_keycode); #endif + /* + * FIXME: we might usefully try to do something clever here + * about interpreting the generated key code in a way that's + * appropriate to the line code page. + */ output[0] = inst->alt_keycode; end = 1; goto done; @@ -635,15 +647,57 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) /* ALT+things gives leading Escape. */ output[0] = '\033'; - strncpy(output+1, event->string, 31); - if (!*event->string && +#if !GTK_CHECK_VERSION(2,0,0) + /* + * In vanilla X, and hence also GDK 1.2, the string received + * as part of a keyboard event is assumed to be in + * ISO-8859-1. (Seems woefully shortsighted in i18n terms, + * but it's true: see the man page for XLookupString(3) for + * confirmation.) + */ + output_charset = CS_ISO8859_1; + strncpy(output+1, event->string, lenof(output)-1); +#else + /* + * GDK 2.0 arranges to have done some translation for us: in + * GDK 2.0, event->string is encoded in the current locale. + * + * (However, it's also deprecated; we really ought to be + * using a GTKIMContext.) + * + * So we use the standard C library function mbstowcs() to + * convert from the current locale into Unicode; from there + * we can convert to whatever PuTTY is currently working in. + * (In fact I convert straight back to UTF-8 from + * wide-character Unicode, for the sake of simplicity: that + * way we can still use exactly the same code to manipulate + * the string, such as prefixing ESC.) + */ + output_charset = CS_UTF8; + { + wchar_t widedata[32], *wp; + int wlen; + int ulen; + + wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, + event->string, strlen(event->string), + widedata, lenof(widedata)-1); + + wp = widedata; + ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, + CS_UTF8, NULL, NULL, 0); + output[1+ulen] = '\0'; + } +#endif + + if (!output[1] && (ucsval = keysym_to_unicode(event->keyval)) >= 0) { ucsoutput[0] = '\033'; ucsoutput[1] = ucsval; use_ucsoutput = TRUE; end = 2; } else { - output[31] = '\0'; + output[lenof(output)-1] = '\0'; end = strlen(output); } if (event->state & GDK_MOD1_MASK) { @@ -653,7 +707,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) start = 1; /* Control-` is the same as Control-\ (unless gtk has a better idea) */ - if (!event->string[0] && event->keyval == '`' && + if (!output[1] && event->keyval == '`' && (event->state & GDK_CONTROL_MASK)) { output[1] = '\x1C'; use_ucsoutput = FALSE; @@ -678,7 +732,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) } /* Control-2, Control-Space and Control-@ are NUL */ - if (!event->string[0] && + if (!output[1] && (event->keyval == ' ' || event->keyval == '2' || event->keyval == '@') && (event->state & (GDK_SHIFT_MASK | @@ -689,10 +743,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) } /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ - if (!event->string[0] && event->keyval == ' ' && + if (!output[1] && event->keyval == ' ' && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) { output[1] = '\240'; + output_charset = CS_ISO8859_1; use_ucsoutput = FALSE; end = 2; } @@ -1007,19 +1062,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break; } if (xkey) { - /* - * The arrow keys normally do ESC [ A and so on. In - * app cursor keys mode they do ESC O A instead. - * Ctrl toggles the two modes. - */ - if (inst->term->vt52_mode) { - end = 1 + sprintf(output+1, "\033%c", xkey); - } else if (!inst->term->app_cursor_keys ^ - !(event->state & GDK_CONTROL_MASK)) { - end = 1 + sprintf(output+1, "\033O%c", xkey); - } else { - end = 1 + sprintf(output+1, "\033[%c", xkey); - } + end = 1 + format_arrow_key(output+1, inst->term, xkey, + event->state & GDK_CONTROL_MASK); use_ucsoutput = FALSE; goto done; } @@ -1048,20 +1092,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) ldisc_send(inst->ldisc, output+start, -2, 1); } else if (!inst->direct_to_font) { if (!use_ucsoutput) { - /* - * The stuff we've just generated is assumed to be - * ISO-8859-1! This sounds insane, but `man - * XLookupString' agrees: strings of this type - * returned from the X server are hardcoded to - * 8859-1. Strictly speaking we should be doing - * this using some sort of GtkIMContext, which (if - * we're lucky) would give us our data directly in - * Unicode; but that's not supported in GTK 1.2 as - * far as I can tell, and it's poorly documented - * even in 2.0, so it'll have to wait. - */ if (inst->ldisc) - lpage_send(inst->ldisc, CS_ISO8859_1, output+start, + lpage_send(inst->ldisc, output_charset, output+start, end-start, 1); } else { /* @@ -1087,45 +1119,46 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) return TRUE; } -gint button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) +gboolean button_internal(struct gui_data *inst, guint32 timestamp, + GdkEventType type, guint ebutton, guint state, + gdouble ex, gdouble ey) { - struct gui_data *inst = (struct gui_data *)data; int shift, ctrl, alt, x, y, button, act; /* Remember the timestamp. */ - inst->input_event_time = event->time; + inst->input_event_time = timestamp; show_mouseptr(inst, 1); - if (event->button == 4 && event->type == GDK_BUTTON_PRESS) { + if (ebutton == 4 && type == GDK_BUTTON_PRESS) { term_scroll(inst->term, 0, -5); return TRUE; } - if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { + if (ebutton == 5 && type == GDK_BUTTON_PRESS) { term_scroll(inst->term, 0, +5); return TRUE; } - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & GDK_MOD1_MASK; + shift = state & GDK_SHIFT_MASK; + ctrl = state & GDK_CONTROL_MASK; + alt = state & GDK_MOD1_MASK; - if (event->button == 3 && ctrl) { + if (ebutton == 3 && ctrl) { gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, - event->button, event->time); + ebutton, timestamp); return TRUE; } - if (event->button == 1) + if (ebutton == 1) button = MBT_LEFT; - else if (event->button == 2) + else if (ebutton == 2) button = MBT_MIDDLE; - else if (event->button == 3) + else if (ebutton == 3) button = MBT_RIGHT; else return FALSE; /* don't even know what button! */ - switch (event->type) { + switch (type) { case GDK_BUTTON_PRESS: act = MA_CLICK; break; case GDK_BUTTON_RELEASE: act = MA_RELEASE; break; case GDK_2BUTTON_PRESS: act = MA_2CLK; break; @@ -1137,8 +1170,8 @@ gint button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) act != MA_CLICK && act != MA_RELEASE) return TRUE; /* we ignore these in raw mouse mode */ - x = (event->x - inst->cfg.window_border) / inst->font_width; - y = (event->y - inst->cfg.window_border) / inst->font_height; + x = (ex - inst->cfg.window_border) / inst->font_width; + y = (ey - inst->cfg.window_border) / inst->font_height; term_mouse(inst->term, button, translate_button(button), act, x, y, shift, ctrl, alt); @@ -1146,6 +1179,36 @@ gint button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) return TRUE; } +gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + return button_internal(inst, event->time, event->type, event->button, + event->state, event->x, event->y); +} + +#if GTK_CHECK_VERSION(2,0,0) +/* + * In GTK 2, mouse wheel events have become a new type of event. + * This handler translates them back into button-4 and button-5 + * presses so that I don't have to change my old code too much :-) + */ +gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + guint button; + + if (event->direction == GDK_SCROLL_UP) + button = 4; + else if (event->direction == GDK_SCROLL_DOWN) + button = 5; + else + return FALSE; + + return button_internal(inst, event->time, GDK_BUTTON_PRESS, + button, event->state, event->x, event->y); +} +#endif + gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; @@ -1189,9 +1252,9 @@ void frontend_keypress(void *handle) exit(0); } -void notify_remote_exit(void *frontend) +static gint idle_exit_func(gpointer data) { - struct gui_data *inst = (struct gui_data *)frontend; + struct gui_data *inst = (struct gui_data *)data; int exitcode; if (!inst->exited && @@ -1211,20 +1274,30 @@ void notify_remote_exit(void *frontend) term_provide_resize_fn(inst->term, NULL, NULL); update_specials_menu(inst); } - gtk_widget_show(inst->restartitem); + gtk_widget_set_sensitive(inst->restartitem, TRUE); } + + gtk_idle_remove(inst->term_exit_idle_id); + return TRUE; +} + +void notify_remote_exit(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + + inst->term_exit_idle_id = gtk_idle_add(idle_exit_func, inst); } static gint timer_trigger(gpointer data) { - long now = GPOINTER_TO_INT(data); + long now = GPOINTER_TO_LONG(data); long next; long ticks; if (run_timers(now, &next)) { ticks = next - GETTICKCOUNT(); timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger, - GINT_TO_POINTER(next)); + LONG_TO_GPOINTER(next)); } /* @@ -1246,7 +1319,7 @@ void timer_change_notify(long next) ticks = 1; /* just in case */ timer_id = gtk_timeout_add(ticks, timer_trigger, - GINT_TO_POINTER(next)); + LONG_TO_GPOINTER(next)); } void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) @@ -1598,6 +1671,9 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, inst->input_event_time)) { +#if GTK_CHECK_VERSION(2,0,0) + gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY); +#endif gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 1); if (inst->pasteout_data_ctext) @@ -3122,6 +3198,7 @@ static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) (GtkCallback)gtk_widget_destroy, NULL); get_sesslist(&sesslist, TRUE); + /* skip sesslist.sessions[0] == Default Settings */ for (i = 1; i < sesslist.nsessions; i++) { GtkWidget *menuitem = gtk_menu_item_new_with_label(sesslist.sessions[i]); @@ -3136,6 +3213,13 @@ static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) GTK_SIGNAL_FUNC(saved_session_freedata), inst); } + if (sesslist.nsessions <= 1) { + GtkWidget *menuitem = + gtk_menu_item_new_with_label("(No sessions)"); + gtk_widget_set_sensitive(menuitem, FALSE); + gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); + gtk_widget_show(menuitem); + } get_sesslist(&sesslist, FALSE); /* free up */ } @@ -3275,7 +3359,7 @@ static void start_backend(struct gui_data *inst) ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle, inst); - gtk_widget_hide(inst->restartitem); + gtk_widget_set_sensitive(inst->restartitem, FALSE); } int pt_main(int argc, char **argv) @@ -3346,6 +3430,9 @@ int pt_main(int argc, char **argv) init_cutbuffers(); inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (inst->cfg.winclass[0]) + gtk_window_set_wmclass(GTK_WINDOW(inst->window), + inst->cfg.winclass, inst->cfg.winclass); /* * Set up the colour map. @@ -3413,6 +3500,10 @@ int pt_main(int argc, char **argv) GTK_SIGNAL_FUNC(button_event), inst); gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event", GTK_SIGNAL_FUNC(button_event), inst); +#if GTK_CHECK_VERSION(2,0,0) + gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event", + GTK_SIGNAL_FUNC(scroll_event), inst); +#endif gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event", GTK_SIGNAL_FUNC(motion_event), inst); gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received", @@ -3459,10 +3550,10 @@ int pt_main(int argc, char **argv) GTK_SIGNAL_FUNC(func), inst); \ } while (0) if (new_session) - MKMENUITEM("New Session", new_session_menuitem); + MKMENUITEM("New Session...", new_session_menuitem); MKMENUITEM("Restart Session", restart_session_menuitem); inst->restartitem = menuitem; - gtk_widget_hide(inst->restartitem); + gtk_widget_set_sensitive(inst->restartitem, FALSE); MKMENUITEM("Duplicate Session", dup_session_menuitem); if (saved_sessions) { inst->sessionsmenu = gtk_menu_new(); @@ -3473,7 +3564,7 @@ int pt_main(int argc, char **argv) inst->sessionsmenu); } MKMENUITEM(NULL, NULL); - MKMENUITEM("Change Settings", change_settings_menuitem); + MKMENUITEM("Change Settings...", change_settings_menuitem); MKMENUITEM(NULL, NULL); if (use_event_log) MKMENUITEM("Event Log", event_log_menuitem);