From 70f365258d73ac8cc37025777e13fdf9e016922c Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 14 Feb 2015 21:32:37 -0500 Subject: UTF-8 keyboard input support on X11. --- AUTHORS | 5 ++- pugl/event.h | 40 +++++++++++++++++++--- pugl/pugl_x11.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++------- pugl_test.c | 18 ++++++---- 4 files changed, 140 insertions(+), 25 deletions(-) diff --git a/AUTHORS b/AUTHORS index de75b21..362287b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,4 +5,7 @@ Original GLX inspiration, portability fixes: Ben Loftis Various fixes and improvements: - Robin Gareus \ No newline at end of file + Robin Gareus + +UTF-8 work: + Erik Ã…ldstedt Sund diff --git a/pugl/event.h b/pugl/event.h index 5cba84b..68fdcda 100644 --- a/pugl/event.h +++ b/pugl/event.h @@ -44,7 +44,9 @@ typedef enum { PUGL_LEAVE_NOTIFY, PUGL_MOTION_NOTIFY, PUGL_NOTHING, - PUGL_SCROLL + PUGL_SCROLL, + PUGL_FOCUS_IN, + PUGL_FOCUS_OUT } PuglEventType; /** @@ -113,9 +115,23 @@ typedef struct { /** Key press/release event. - Keys that correspond to a Unicode character are expressed as a character - code. For other keys, `character` will be 0 and `special` indicates the key - pressed. + Keys that correspond to a Unicode character have `character` and `utf8` set. + Other keys will have `character` 0, but `special` may be set if this is a + known special key. + + A key press may be part of a multi-key sequence to generate a wide + character. If `filter` is set, this event is part of a multi-key sequence + and should be ignored if the application is reading textual input. + Following the series of filtered press events, a press event with + `character` and `utf8` (but `keycode` 0) will be sent. This event will have + no corresponding release event. + + Generally, an application should either work with raw keyboard press/release + events based on `keycode` (ignoring events with `keycode` 0), or + read textual input based on `character` or `utf8` (ignoring releases and + events with `filter` 1). Note that blindly appending `utf8` will yield + incorrect text, since press events are sent for both individually composed + keys and the resulting synthetic multi-byte press. */ typedef struct { PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */ @@ -127,8 +143,11 @@ typedef struct { double x_root; /**< Root-relative X coordinate. */ double y_root; /**< Root-relative Y coordinate. */ unsigned state; /**< Bitwise OR of PuglMod flags. */ + unsigned keycode; /**< Raw key code. */ uint32_t character; /**< Unicode character code, or 0. */ - PuglKey special; /**< Special key, if character is 0. */ + PuglKey special; /**< Special key, or 0. */ + uint8_t utf8[8]; /**< UTF-8 string. */ + bool filter; /**< True if part of a multi-key sequence. */ } PuglEventKey; /** @@ -187,6 +206,16 @@ typedef struct { double dy; /**< Scroll Y distance in lines. */ } PuglEventScroll; +/** + Keyboard focus event. +*/ +typedef struct { + PuglEventType type; /**< PUGL_FOCUS_IN or PUGL_FOCUS_OUT. */ + PuglView* view; /**< View that received this event. */ + bool send_event; /**< True iff event was sent explicitly. */ + bool grab; /**< True iff this is a grab/ungrab event. */ +} PuglEventFocus; + /** Interface event. @@ -204,6 +233,7 @@ typedef union { PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */ PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */ PuglEventScroll scroll; /**< PUGL_SCROLL. */ + PuglEventFocus focus; /**< PUGL_FOCUS_IN, PUGL_FOCUS_OUT. */ } PuglEvent; /** diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c index 4bf1f65..532ff40 100644 --- a/pugl/pugl_x11.c +++ b/pugl/pugl_x11.c @@ -46,6 +46,8 @@ struct PuglInternalsImpl { Display* display; int screen; Window win; + XIM xim; + XIC xic; #ifdef PUGL_HAVE_CAIRO cairo_t* cr; #endif @@ -192,7 +194,7 @@ puglCreateWindow(PuglView* view, const char* title) EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | - PointerMotionMask); + PointerMotionMask | FocusChangeMask); impl->win = XCreateWindow( impl->display, xParent, @@ -231,6 +233,21 @@ puglCreateWindow(PuglView* view, const char* title) (Window)(view->transient_parent)); } + if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) { + XSetLocaleModifiers("@im="); + if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) { + fprintf(stderr, "warning: XOpenIM failed\n"); + } + } + + if (!(impl->xic = XCreateIC(impl->xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, impl->win, + XNFocusWindow, impl->win, + NULL))) { + fprintf(stderr, "warning: XCreateIC failed\n"); + } + XFree(vi); return 0; @@ -300,16 +317,64 @@ keySymToSpecial(KeySym sym) return (PuglKey)0; } +/** Return the code point for buf, or the replacement character on error. */ +static uint32_t +utf8Decode(const uint8_t* buf, size_t len) +{ +#define FAIL_IF(cond) { if (cond) return 0xFFFD; } + + /* http://en.wikipedia.org/wiki/UTF-8 */ + + if (buf[0] < 0x80) { + return buf[0]; + } else if (buf[0] < 0xC2) { + return 0xFFFD; + } else if (buf[0] < 0xE0) { + FAIL_IF(len != 2); + FAIL_IF((buf[1] & 0xC0) != 0x80); + return (buf[0] << 6) + buf[1] - 0x3080; + } else if (buf[0] < 0xF0) { + FAIL_IF(len != 3); + FAIL_IF((buf[1] & 0xC0) != 0x80); + FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); + FAIL_IF((buf[2] & 0xC0) != 0x80); + return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080; + } else if (buf[0] < 0xF5) { + FAIL_IF(len != 4); + FAIL_IF((buf[1] & 0xC0) != 0x80); + FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); + FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); + FAIL_IF((buf[2] & 0xC0) != 0x80); + FAIL_IF((buf[3] & 0xC0) != 0x80); + return ((buf[0] << 18) + + (buf[1] << 12) + + (buf[2] << 6) + + buf[3] - 0x3C82080); + } + return 0xFFFD; +} + static void translateKey(PuglView* view, XEvent* xevent, PuglEvent* event) { - KeySym sym; - char str[5]; - const int n = XLookupString(&xevent->xkey, str, 4, &sym, NULL); - if (n == 1) { - event->key.character = str[0]; // TODO: multi-byte support + KeySym sym = 0; + char* str = (char*)event->key.utf8; + memset(str, 0, 7); + event->key.filter = XFilterEvent(xevent, None); + if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) { + if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) { + event->key.character = str[0]; + } + } else { + Status status = 0; + const int n = XmbLookupString( + view->impl->xic, &xevent->xkey, str, 7, &sym, &status); + if (n > 0) { + event->key.character = utf8Decode((const uint8_t*)str, n); + } } event->key.special = keySymToSpecial(sym); + event->key.keycode = xevent->xkey.keycode; } static unsigned @@ -396,12 +461,12 @@ translateEvent(PuglView* view, XEvent xevent) event.type = ((xevent.type == KeyPress) ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE); - event.key.time = xevent.xbutton.time; - event.key.x = xevent.xbutton.x; - event.key.y = xevent.xbutton.y; - event.key.x_root = xevent.xbutton.x_root; - event.key.y_root = xevent.xbutton.y_root; - event.key.state = translateModifiers(xevent.xbutton.state); + event.key.time = xevent.xkey.time; + event.key.x = xevent.xkey.x; + event.key.y = xevent.xkey.y; + event.key.x_root = xevent.xkey.x_root; + event.key.y_root = xevent.xkey.y_root; + event.key.state = translateModifiers(xevent.xkey.state); translateKey(view, &xevent, &event); break; case EnterNotify: @@ -422,6 +487,15 @@ translateEvent(PuglView* view, XEvent xevent) event.crossing.mode = PUGL_CROSSING_UNGRAB; } break; + + case FocusIn: + case FocusOut: + event.type = ((xevent.type == EnterNotify) + ? PUGL_FOCUS_IN + : PUGL_FOCUS_OUT); + event.focus.grab = (xevent.xfocus.mode != NotifyNormal); + break; + default: break; } @@ -466,6 +540,10 @@ puglProcessEvents(PuglView* view) ignore = true; } } + } else if (xevent.type == FocusIn) { + XSetICFocus(view->impl->xic); + } else if (xevent.type == FocusOut) { + XUnsetICFocus(view->impl->xic); } if (!ignore) { diff --git a/pugl_test.c b/pugl_test.c index 9b942f4..248a49a 100644 --- a/pugl_test.c +++ b/pugl_test.c @@ -99,12 +99,16 @@ printModifiers(PuglView* view) } static void -onKeyboard(PuglView* view, bool press, uint32_t key) +onEvent(PuglView* view, const PuglEvent* event) { - fprintf(stderr, "Key %c %s ", (char)key, press ? "down" : "up"); - printModifiers(view); - if (key == 'q' || key == 'Q' || key == PUGL_CHAR_ESCAPE || - key == PUGL_CHAR_DELETE || key == PUGL_CHAR_BACKSPACE) { + const uint32_t ucode = event->key.character; + if (event->type == PUGL_KEY_PRESS) { + fprintf(stderr, "Key %u (char %u) down (%s)%s\n", + event->key.keycode, event->key.character, event->key.utf8, + event->key.filter ? " (filtered)" : ""); + } + if (ucode == 'q' || ucode == 'Q' || ucode == PUGL_CHAR_ESCAPE || + ucode == PUGL_CHAR_DELETE || ucode == PUGL_CHAR_BACKSPACE) { quit = 1; } } @@ -172,9 +176,9 @@ main(int argc, char** argv) puglInitWindowSize(view, 512, 512); puglInitWindowMinSize(view, 256, 256); puglInitResizable(view, resizable); - + puglIgnoreKeyRepeat(view, ignoreKeyRepeat); - puglSetKeyboardFunc(view, onKeyboard); + puglSetEventFunc(view, onEvent); puglSetMotionFunc(view, onMotion); puglSetMouseFunc(view, onMouse); puglSetScrollFunc(view, onScroll); -- cgit v1.2.3