Here's a patch to unix/gtkwin.c which adds input method support. Notes:
* I've made it all conditional on USE_XIM, so you can compile without it. If you don't care about being able to do that, some of the code at the end of the keypress handling function could perhaps be improved. * I chose to work around a bug in mb_to_wc() where it crashes if passed "\0"; you might prefer to fix it instead. * I've tested with Japanese input, in both a UTF-8 and a EUC-JP locale (you do get 'current locale' strings in key events if using the IM), and with the C locale (in which case the IM says "nothing doing"). * However, I haven't tested on a system with no IM set up at all :-) * For all this to work we have to run in the real locale, not the C locale [at least at the point when we open the IM, or it will refuse to work] I haven't audited the other putty code to see if this is safe! * In particular, the mb_to_wc() etc code in uxucs.c put us back in the C locale again... * We should probably really call gdk_set_locale() on startup whether compiled USE_XIM or not. * I notice some remarks in the xterm source to the effect that the font for the input method is supposed to be in the locale encoding, not UTF8. Don't know whether this affects pterm. In any case it might be nice to be able to specify the font for the IM separately from the main text font (then you can have it a bit bigger if you like) -- xterm allows this. -- PMM ===begin patch=== --- gtkwin.c.orig 2005-10-29 22:58:37.000000000 +0100 +++ gtkwin.c 2005-10-30 22:07:36.000000000 +0000 @@ -37,6 +37,12 @@ #define NCFGCOLOURS (lenof(((Config *)0)->colours)) #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */ #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) +/* Index of foreground/background colours in cols[] */ +#define FGCOLIDX 256 +#define BGCOLIDX 258 + +/* Support X Input Methods ? */ +#define USE_XIM GdkAtom compound_text_atom, utf8_string_atom; @@ -96,6 +102,13 @@ int ngtkargs; guint32 input_event_time; /* Timestamp of the most recent input event. */ int reconfiguring; +#ifdef USE_XIM + GdkICAttr *ic_attr; + GdkIC *ic; + GdkFont *ic_fontset; + int cursor_x; + int cursor_y; +#endif }; struct draw_ctx { @@ -407,13 +420,138 @@ void draw_backing_rect(struct gui_data *inst) { GdkGC *gc = gdk_gc_new(inst->area->window); - gdk_gc_set_foreground(gc, &inst->cols[258]); /* default background */ + gdk_gc_set_foreground(gc, &inst->cols[BGCOLIDX]); /* default background */ gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0, inst->cfg.width * inst->font_width + 2*inst->cfg.window_border, inst->cfg.height * inst->font_height + 2*inst->cfg.window_border); gdk_gc_unref(gc); } +#ifdef USE_XIM + +/* These macros should evaluate true if A and B differ */ +#define COLORCMP(A,B) ((A).pixel != (B).pixel) +#define FONTCMP(A,B) (!gdk_font_equal(A,B)) +#define SIMPLECMP(A,B) ((A) != (B)) + +#define IC_SET_IF_CHANGED(FIELD,VALUE,MASKBIT) IC_CMP_SET_IF_CHANGED(SIMPLECMP,FIELD,VALUE,MASKBIT) + +/* This macro assumes the following local variables: attr, inst, attrmask */ +#define FILL_IN_ATTRS() do { \ + if (attr->style & GDK_IM_PREEDIT_POSITION) { \ + int width = inst->cfg.width * inst->font_width; \ + int height = inst->cfg.height * inst->font_height; \ + int border = inst->cfg.window_border; \ + IC_SET_IF_CHANGED(spot_location.x, border + inst->cursor_x * inst->font_width, GDK_IC_SPOT_LOCATION); \ + IC_SET_IF_CHANGED(spot_location.y, border + (inst->cursor_y+1) * inst->font_height - 1, GDK_IC_SPOT_LOCATION); \ + IC_SET_IF_CHANGED(preedit_area.x, border, GDK_IC_PREEDIT_AREA); \ + IC_SET_IF_CHANGED(preedit_area.y, border, GDK_IC_PREEDIT_AREA); \ + IC_SET_IF_CHANGED(preedit_area.width, width, GDK_IC_PREEDIT_AREA); \ + IC_SET_IF_CHANGED(preedit_area.height, height, GDK_IC_PREEDIT_AREA); \ + IC_CMP_SET_IF_CHANGED(FONTCMP,preedit_fontset, inst->ic_fontset, GDK_IC_PREEDIT_FONTSET); \ + } \ + IC_SET_IF_CHANGED(preedit_colormap, inst->colmap, GDK_IC_PREEDIT_COLORMAP); \ + /* These fields are GtkColors and can't be compared with a simple == */ \ + IC_CMP_SET_IF_CHANGED(COLORCMP,preedit_foreground, inst->cols[FGCOLIDX], GDK_IC_PREEDIT_FOREGROUND); \ + IC_CMP_SET_IF_CHANGED(COLORCMP,preedit_background, inst->cols[BGCOLIDX], GDK_IC_PREEDIT_BACKGROUND); \ + } while (0) + +/* We define IC_CMP_SET_IF_CHANGED suitably to give two functions: + * set_ic_attrs() sets all the fields in the attr struct: this + * is used only when initially creating the IC + * update_ic_attrs() updates only the fields which have changed, + * and then calls gdk_ic_set_attr() to tell GDK about them. + */ + +static void update_ic_attrs(struct gui_data *inst) +{ + GdkICAttributesType attrmask = 0; + GdkICAttr *attr = inst->ic_attr; + + if (!inst->ic) { + return; + } + +#define IC_CMP_SET_IF_CHANGED(CMPMACRO,FIELD,VALUE,MASKBIT) do { \ + if (CMPMACRO(attr->FIELD, VALUE)) { attr->FIELD = (VALUE); attrmask |= MASKBIT; } \ + } while (0) + FILL_IN_ATTRS(); +#undef IC_CMP_SET_IF_CHANGED + + if (attrmask) + gdk_ic_set_attr(inst->ic, inst->ic_attr, attrmask); +} + +/* Fill in a GdkICAttr struct from the current width/height/font/border/colours. + * Essentially we set all attributes which aren't set-once. + * Returns a GdkICAttributesType mask with bits set where we've set + * fields in attr. We assume attr->style is valid. + * This is called only when first creating the IC. + */ +static GdkICAttributesType set_ic_attrs(struct gui_data *inst, GdkICAttr *attr) +{ + GdkICAttributesType attrmask = 0; + +#define IC_CMP_SET_IF_CHANGED(CMPMACRO,FIELD,VALUE,MASKBIT) do { \ + attr->FIELD = (VALUE); attrmask |= MASKBIT; \ + } while (0) + FILL_IN_ATTRS(); +#undef IC_CMP_SET_IF_CHANGED + + return attrmask; +} + + +void realize_area (GtkWidget *widget, gpointer data) +{ + /* When we've been realized we can set up the input method. + * In theory we should hook unrealize to destroy things, but + * nothing else here does that (since the widget lives for the + * whole lifetime of the pterm process). + */ + struct gui_data *inst = (struct gui_data *)data; + GdkEventMask mask; + GdkICAttr *attr; + GdkICAttributesType attrmask = GDK_IC_ALL_REQ; + /* we (the application) support this: roughly, over-the-spot only */ + GdkIMStyle style = GDK_IM_PREEDIT_NONE + | GDK_IM_PREEDIT_NOTHING + | GDK_IM_PREEDIT_POSITION + | GDK_IM_STATUS_NONE + | GDK_IM_STATUS_NOTHING; + + if (!gdk_im_ready()) { + return; + } + + inst->ic_attr = attr = gdk_ic_attr_new(); + if (!attr) { + return; + } + + /* merge styles with what the input method supports: */ + attr->style = gdk_im_decide_style(style); + attr->client_window = inst->area->window; + + attrmask |= set_ic_attrs(inst, attr); + + inst->ic = gdk_ic_new(attr,attrmask); + if (!inst->ic) { + return; + } + + /* Make sure our window gets all events the IM needs */ + mask = gdk_window_get_events(inst->area->window); + mask |= gdk_ic_get_events(inst->ic); + gdk_window_set_events(inst->area->window, mask); + + /* If we already have focus, start editing now: */ + if (GTK_WIDGET_HAS_FOCUS(inst->area)) { + gdk_im_begin(inst->ic, inst->area->window); + } +} +#endif + gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; @@ -451,6 +589,10 @@ if (inst->term) term_invalidate(inst->term); +#ifdef USE_XIM + update_ic_attrs(inst); +#endif + return TRUE; } @@ -482,6 +624,9 @@ char output[32]; wchar_t ucsoutput[2]; int ucsval, start, end, special, use_ucsoutput; +#ifdef USE_XIM + int use_keystring = FALSE; +#endif /* Remember the timestamp. */ inst->input_event_time = event->time; @@ -628,8 +773,12 @@ if (event->state & GDK_MOD1_MASK) { start = 0; if (end == 1) end = 0; - } else + } else { +#ifdef USE_XIM + use_keystring = TRUE; +#endif start = 1; + } /* Control-` is the same as Control-\ (unless gtk has a better idea) */ if (!event->string[0] && event->keyval == '`' && @@ -637,6 +786,9 @@ output[1] = '\x1C'; use_ucsoutput = FALSE; end = 2; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* Control-Break is the same as Control-C */ @@ -646,6 +798,9 @@ use_ucsoutput = FALSE; end = 2; special = TRUE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* We handle Return ourselves, because it needs to be flagged as @@ -655,6 +810,9 @@ use_ucsoutput = FALSE; end = 2; special = TRUE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* Control-2, Control-Space and Control-@ are NUL */ @@ -666,6 +824,16 @@ output[1] = '\0'; use_ucsoutput = FALSE; end = 2; +#ifdef USE_XIM + /* We want to just pass the NUL through, rather than trying + * to do charset conversion on it. Note that the current + * implementation of mb_to_wc in uxucs.c will crash if passed + * NUL (it doesn't handle mbrtowc() returning 0, as it does + * in that case). FIXME -- maybe better to fix that? + */ + special = TRUE; + use_keystring = FALSE; +#endif } /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */ @@ -675,6 +843,9 @@ output[1] = '\240'; use_ucsoutput = FALSE; end = 2; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* We don't let GTK tell us what Backspace is! We know better. */ @@ -684,6 +855,9 @@ use_ucsoutput = FALSE; end = 2; special = TRUE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* For Shift Backspace, do opposite of what is configured. */ if (event->keyval == GDK_BackSpace && @@ -692,6 +866,9 @@ use_ucsoutput = FALSE; end = 2; special = TRUE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* Shift-Tab is ESC [ Z */ @@ -699,6 +876,9 @@ (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) { end = 1 + sprintf(output+1, "\033[Z"); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif } /* @@ -724,6 +904,9 @@ else output[1] = keys[0]; use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } } @@ -777,6 +960,9 @@ } else end = 1 + sprintf(output+1, "\033O%c", xkey); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } } @@ -881,6 +1067,9 @@ if (inst->term->vt52_mode && code > 0 && code <= 6) { end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } @@ -906,6 +1095,9 @@ if (event->state & GDK_CONTROL_MASK) index += 24; end = 1 + sprintf(output+1, "\x1B[%c", codes[index]); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } if (inst->cfg.funky_type == FUNKY_SCO && /* SCO small keypad */ @@ -918,6 +1110,9 @@ end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]); } use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } if ((inst->term->vt52_mode || inst->cfg.funky_type == FUNKY_VT100P) && @@ -933,12 +1128,18 @@ else end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11 - offt); +#ifdef USE_XIM + use_keystring = FALSE; +#endif use_ucsoutput = FALSE; goto done; } if (inst->cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } if (inst->cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { @@ -947,16 +1148,25 @@ else end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } if (inst->cfg.rxvt_homeend && (code == 1 || code == 4)) { end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw"); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } if (code) { end = 1 + sprintf(output+1, "\x1B[%d~", code); use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } } @@ -992,6 +1202,9 @@ end = 1 + sprintf(output+1, "\033[%c", xkey); } use_ucsoutput = FALSE; +#ifdef USE_XIM + use_keystring = FALSE; +#endif goto done; } } @@ -1000,6 +1213,37 @@ done: +#ifdef USE_XIM + if (use_keystring) { + /* We copied from event->string into output earlier, but that might + * have been a truncated version. Remarks below about the encoding + * of the string apply. + * This code almost but doesn't quite neatly roll into the non-XIM + * case below. It could probably be improved upon but that would + * make this patch too invasive. + */ + assert(!special); + assert(!use_ucsoutput); +#ifdef KEY_DEBUGGING + printf("using keystring\n"); +#endif + if (!inst->ldisc || !event->length) + goto output_done; + if (!inst->direct_to_font) { + int codepage = (inst->ic) ? DEFAULT_CODEPAGE : CS_ISO8859_1; + lpage_send(inst->ldisc, codepage, event->string, + event->length, 1); + } else { + /* + * In direct-to-font mode, we just send the string + * exactly as we received it. + */ + ldisc_send(inst->ldisc, event->string, event->length, 1); + } + goto output_done; + } +#endif + if (end-start > 0) { #ifdef KEY_DEBUGGING int i; @@ -1024,15 +1268,29 @@ * 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. + * 8859-1. + * The exception is that if we have and are using + * an input context then gdk uses XmbLookupString + * instead, and you get a multibyte string in the + * encoding of the locale of the input context. + * Unfortunately the X function call to find that + * locale (XLocaleOfIM()) requires a pointer to the + * XIM object, which gdk hides away from us behind + * an abstraction layer. So we have to assume that + * the current locale is the right one. + * I also note in passing that if the current locale + * happens to be a utf8 one then we will carefully + * convert this utf8 string to UCS and back again + * in lpage_send and luni_send... */ + int codepage = CS_ISO8859_1; +#ifdef USE_XIM + if (inst->ic) { + codepage = DEFAULT_CODEPAGE; + } +#endif if (inst->ldisc) - lpage_send(inst->ldisc, CS_ISO8859_1, output+start, + lpage_send(inst->ldisc, codepage, output+start, end-start, 1); } else { /* @@ -1050,7 +1308,7 @@ if (inst->ldisc) ldisc_send(inst->ldisc, output+start, end-start, 1); } - + output_done: show_mouseptr(inst, 0); term_seen_key_event(inst->term); } @@ -1245,6 +1503,15 @@ term_set_focus(inst->term, event->in); term_update(inst->term); show_mouseptr(inst, 1); +#ifdef USE_XIM + if (inst->ic) { + if (event->in) { + gdk_im_begin(inst->ic, inst->area->window); + } else { + gdk_im_end(); + } + } +#endif return FALSE; } @@ -1355,9 +1622,9 @@ void set_window_background(struct gui_data *inst) { if (inst->area && inst->area->window) - gdk_window_set_background(inst->area->window, &inst->cols[258]); + gdk_window_set_background(inst->area->window, &inst->cols[BGCOLIDX]); if (inst->window && inst->window->window) - gdk_window_set_background(inst->window->window, &inst->cols[258]); + gdk_window_set_background(inst->window->window, &inst->cols[BGCOLIDX]); } void palette_set(void *frontend, int n, int r, int g, int b) @@ -1368,8 +1635,13 @@ if (n > NALLCOLOURS) return; real_palette_set(inst, n, r, g, b); - if (n == 258) + if (n == BGCOLIDX) set_window_background(inst); + +#ifdef USE_XIM + if (n == FGCOLIDX || n == BGCOLIDX) + update_ic_attrs(inst); +#endif } void palette_reset(void *frontend) @@ -1422,6 +1694,10 @@ } set_window_background(inst); + +#ifdef USE_XIM + update_ic_attrs(inst); +#endif } /* Ensure that all the cut buffers exist - according to the ICCCM, we must @@ -1815,9 +2091,13 @@ void sys_cursor(void *frontend, int x, int y) { - /* - * This is meaningless under X. - */ +#ifdef USE_XIM + /* The input method wants to know the current cursor location */ + struct gui_data *inst = (struct gui_data *)frontend; + inst->cursor_x = x; + inst->cursor_y = y; + update_ic_attrs(inst); +#endif } /* @@ -2722,6 +3002,11 @@ if (inst->fonts[3]) gdk_font_unref(inst->fonts[3]); +#ifdef USE_XIM + if (inst->ic_fontset) + gdk_font_unref(inst->ic_fontset); +#endif + inst->fonts[0] = gdk_font_load(inst->cfg.font.name); if (!inst->fonts[0]) { fprintf(stderr, "%s: unable to load font \"%s\"\n", appname, @@ -2767,6 +3052,29 @@ inst->cfg.widefont.name); exit(1); } +#ifdef USE_XIM + /* The input method wants not a font but a fontset consisting + * of the normal and the wide-char font. + */ + { + char *fontsetname; + if (!inst->fonts[2]) { + /* No wide font, assume the normal font is good enough */ + fontsetname = inst->cfg.font.name; + } else { + /* We need a comma-separated list of fonts */ + fontsetname = dupcat(inst->cfg.font.name, ",", name, NULL); + } + inst->ic_fontset = gdk_fontset_load(fontsetname); + if (!inst->ic_fontset) { + fprintf(stderr, "%s: unable to load fontset \"%s\"\n", appname, fontsetname); + exit(1); + } + if (fontsetname != inst->cfg.font.name) + sfree(fontsetname); + } +#endif + if (guessed) sfree(name); @@ -2931,7 +3239,7 @@ * repaint the space in between the window border * and the text area. */ - if (i == 258) { + if (i == BGCOLIDX) { set_window_background(inst); draw_backing_rect(inst); } @@ -3350,6 +3658,14 @@ * it */ block_signal(SIGCHLD, 1); +#ifdef USE_XIM + /* Set locale. This is required for XIM support. It must go before gtk_init(). + * FIXME it would be more consistent to do this whether we have XIM support + * or not. + */ + gdk_set_locale(); +#endif + inst->progname = argv[0]; /* * Copy the original argv before letting gtk_init fiddle with @@ -3467,6 +3783,10 @@ GTK_SIGNAL_FUNC(selection_get), inst); gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event", GTK_SIGNAL_FUNC(selection_clear), inst); +#ifdef USE_XIM + gtk_signal_connect(GTK_OBJECT(inst->area), "realize", + GTK_SIGNAL_FUNC(realize_area), inst); +#endif if (inst->cfg.scrollbar) gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", GTK_SIGNAL_FUNC(scrollbar_moved), inst); ===endit=== -- To UNSUBSCRIBE, email to [EMAIL PROTECTED] with a subject of "unsubscribe". Trouble? Contact [EMAIL PROTECTED]