aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2015-02-14 21:32:37 -0500
committerDavid Robillard <d@drobilla.net>2015-02-14 21:40:49 -0500
commit70f365258d73ac8cc37025777e13fdf9e016922c (patch)
tree07b08b9bb6fd740c604943634f445a45bdbb653d
parent10606d469c6d3ccc245d293b8637a5be3e95bdb1 (diff)
UTF-8 keyboard input support on X11.
-rw-r--r--AUTHORS5
-rw-r--r--pugl/event.h40
-rw-r--r--pugl/pugl_x11.c102
-rw-r--r--pugl_test.c18
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 <robin@gareus.org> \ No newline at end of file
+ Robin Gareus <robin@gareus.org>
+
+UTF-8 work:
+ Erik Ã…ldstedt Sund <erikalds@gmail.com>
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;
/**
@@ -188,6 +207,16 @@ typedef struct {
} 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.
This is a union of all event structs. The `type` must be checked to
@@ -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);