I discovered a bug in my patch to support input methods on pterm -- it
broke ctrl-space (which should send NUL) so it had no effect.
Here's the updated patch. The fix relates to this caveat I mentioned
in my initial patch:
* I chose to work around a bug in mb_to_wc() where it crashes if passed
"\0"; you might prefer to fix it instead.
The workaround was broken -- this time round I did what I should have
done in the first place and fixed mb_to_wc().
-- PMM
diff -ur putty-0.58/unix/gtkwin.c putty-0.58-patched/unix/gtkwin.c
--- putty-0.58/unix/gtkwin.c 2005-04-05 20:37:47.000000000 +0100
+++ putty-0.58-patched/unix/gtkwin.c 2006-03-04 22:43:30.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,9 @@
output[1] = '\0';
use_ucsoutput = FALSE;
end = 2;
+#ifdef USE_XIM
+ use_keystring = FALSE;
+#endif
}
/* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
@@ -675,6 +836,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 +848,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 +859,9 @@
use_ucsoutput = FALSE;
end = 2;
special = TRUE;
+#ifdef USE_XIM
+ use_keystring = FALSE;
+#endif
}
/* Shift-Tab is ESC [ Z */
@@ -699,6 +869,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 +897,9 @@
else
output[1] = keys[0];
use_ucsoutput = FALSE;
+#ifdef USE_XIM
+ use_keystring = FALSE;
+#endif
goto done;
}
}
@@ -777,6 +953,9 @@
} else
end = 1 + sprintf(output+1, "\033O%c", xkey);
use_ucsoutput = FALSE;
+#ifdef USE_XIM
+ use_keystring = FALSE;
+#endif
goto done;
}
}
@@ -881,6 +1060,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 +1088,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 +1103,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 +1121,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 +1141,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 +1195,9 @@
end = 1 + sprintf(output+1, "\033[%c", xkey);
}
use_ucsoutput = FALSE;
+#ifdef USE_XIM
+ use_keystring = FALSE;
+#endif
goto done;
}
}
@@ -1000,6 +1206,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 +1261,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 +1301,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 +1496,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 +1615,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 +1628,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 +1687,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 +2084,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 +2995,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 +3045,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 +3232,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 +3651,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 +3776,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);
diff -ur putty-0.58/unix/uxucs.c putty-0.58-patched/unix/uxucs.c
--- putty-0.58/unix/uxucs.c 2005-04-05 20:37:47.000000000 +0100
+++ putty-0.58-patched/unix/uxucs.c 2006-03-04 22:41:10.000000000 +0000
@@ -35,6 +35,12 @@
size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state);
if (i == (size_t)-1 || i == (size_t)-2)
break;
+ /* If we just read NUL then the widechar entry is filled
+ * but the return value is 0 instead of the length of the
+ * multibyte NUL. We assume it's always 1.
+ */
+ if (i == 0)
+ i = 1;
n++;
mbstr += i;
mblen -= i;
--
To UNSUBSCRIBE, email to [EMAIL PROTECTED]
with a subject of "unsubscribe". Trouble? Contact [EMAIL PROTECTED]