aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-07-20 11:19:02 +0200
committerDavid Robillard <d@drobilla.net>2019-07-24 01:02:52 +0200
commit3526bf913402b4061fd19b2f77b9f1dd1a60b2a5 (patch)
tree84dba6491f1832c9a29e0bae43daa926f43244cd
parent1deb98f57f7b597be941fd944a91f2daa1a1f10a (diff)
downloadpugl-3526bf913402b4061fd19b2f77b9f1dd1a60b2a5.tar.gz
pugl-3526bf913402b4061fd19b2f77b9f1dd1a60b2a5.tar.bz2
pugl-3526bf913402b4061fd19b2f77b9f1dd1a60b2a5.zip
Unify key and character fields and separate text events
Only one field is necessary to store any kind of key, including special keys, since PuglKey occupies a reserved Unicode region. This is generally much simpler to deal with since there is only one value to dispatch on. Text events are separated from key events (like Windows but unlike MacOS or X11) because it is not possible to derive text events from key press events when they occur on Windows. Since merging the two has been the source of some confusion, this approach has some advantages anyway, even though it introduces the need to handle another event type. In the process, text input has been almost completely rewritten. I have tested this with a compose key on X11 and dead keys on Windows and MacOS and everything seems to work correctly, though there may (as always) still be issues with more exotic input methods.
-rw-r--r--pugl/pugl.h79
-rw-r--r--pugl/pugl_osx.m160
-rw-r--r--pugl/pugl_win.c177
-rw-r--r--pugl/pugl_x11.c67
-rw-r--r--test/pugl_cairo_test.c4
-rw-r--r--test/pugl_test.c4
-rw-r--r--test/test_utils.h17
7 files changed, 288 insertions, 220 deletions
diff --git a/pugl/pugl.h b/pugl/pugl.h
index d3bcc7f..0f36bfe 100644
--- a/pugl/pugl.h
+++ b/pugl/pugl.h
@@ -113,15 +113,6 @@ typedef enum {
} PuglWindowHintValue;
/**
- Convenience symbols for ASCII control characters.
-*/
-typedef enum {
- PUGL_CHAR_BACKSPACE = 0x08,
- PUGL_CHAR_ESCAPE = 0x1B,
- PUGL_CHAR_DELETE = 0x7F
-} PuglChar;
-
-/**
Keyboard modifier flags.
*/
typedef enum {
@@ -132,14 +123,24 @@ typedef enum {
} PuglMod;
/**
- Special (non-Unicode) keyboard keys.
+ Special keyboard keys.
+
+ All keys, special or not, are expressed as a Unicode code point. This
+ enumeration defines constants for special keys that do not have a standard
+ code point, and some convenience constants for control characters.
- The numerical values of these symbols occupy a reserved range of Unicode
- points, so it is possible to express either a PuglKey value or a Unicode
- character in the same variable. This is sometimes useful for interfacing
- with APIs that do not make this distinction.
+ Keys that do not have a standard code point use values in the Private Use
+ Area in the Basic Multilingual Plane (U+E000 to U+F8FF). Applications must
+ take care to not interpret these values beyond key detection, the mapping
+ used here is arbitrary and specific to Pugl.
*/
typedef enum {
+ // ASCII control codes
+ PUGL_KEY_BACKSPACE = 0x08,
+ PUGL_KEY_ESCAPE = 0x1B,
+ PUGL_KEY_DELETE = 0x7F,
+
+ // Unicode Private Use Area
PUGL_KEY_F1 = 0xE000,
PUGL_KEY_F2,
PUGL_KEY_F3,
@@ -179,6 +180,7 @@ typedef enum {
PUGL_CLOSE, /**< Close view */
PUGL_KEY_PRESS, /**< Key press */
PUGL_KEY_RELEASE, /**< Key release */
+ PUGL_TEXT, /**< Character entry */
PUGL_ENTER_NOTIFY, /**< Pointer entered view */
PUGL_LEAVE_NOTIFY, /**< Pointer left view */
PUGL_MOTION_NOTIFY, /**< Pointer motion */
@@ -261,23 +263,14 @@ typedef struct {
/**
Key press/release event.
- Keys that correspond to a Unicode character have `character` and `string`
- set. Other keys will have `character` 0, but `special` may be set if this
- is a known special key.
+ This represents low-level key press and release events. This event type
+ should be used for "raw" keyboard handing (key bindings, for example), but
+ must not be interpreted as text input.
- 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 `string` (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 `string` (ignoring releases and events
- with `filter` 1). Note that blindly appending `string` will yield incorrect
- text, since press events are sent for both individually composed keys and
- the resulting synthetic multi-byte press.
+ Keys are represented as Unicode code points, using the "natural" code point
+ for the key wherever possible (see @ref PuglKey for details). The `key`
+ field will be set to the code for the pressed key, without any modifiers
+ applied (by the shift or control keys).
*/
typedef struct {
PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */
@@ -289,13 +282,30 @@ typedef struct {
double y_root; /**< Root-relative Y coordinate. */
uint32_t state; /**< Bitwise OR of PuglMod flags. */
uint32_t keycode; /**< Raw key code. */
- uint32_t character; /**< Unicode character code, or 0. */
- PuglKey special; /**< Special key, or 0. */
- char string[8]; /**< UTF-8 string. */
- bool filter; /**< True if part of a multi-key sequence. */
+ uint32_t key; /**< Unshifted Unicode character code, or 0. */
} PuglEventKey;
/**
+ Character input event.
+
+ This represents text input, usually as the result of a key press. The text
+ is given both as a Unicode character code and a UTF-8 string.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CHAR. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t keycode; /**< Raw key code. */
+ uint32_t character; /**< Unicode character code */
+ char string[8]; /**< UTF-8 string. */
+} PuglEventText;
+
+/**
Pointer crossing event (enter and leave).
*/
typedef struct {
@@ -372,6 +382,7 @@ typedef union {
PuglEventExpose expose; /**< PUGL_EXPOSE. */
PuglEventClose close; /**< PUGL_CLOSE. */
PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */
+ PuglEventText text; /**< PUGL_TEXT. */
PuglEventCrossing crossing; /**< PUGL_ENTER_NOTIFY, PUGL_LEAVE_NOTIFY. */
PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */
PuglEventScroll scroll; /**< PUGL_SCROLL. */
diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m
index 0546eec..6f3d4bc 100644
--- a/pugl/pugl_osx.m
+++ b/pugl/pugl_osx.m
@@ -92,13 +92,14 @@ struct PuglInternalsImpl {
@end
-@interface PuglOpenGLView : NSOpenGLView
+@interface PuglOpenGLView : NSOpenGLView<NSTextInputClient>
{
@public
- PuglView* puglview;
- NSTrackingArea* trackingArea;
- NSTimer* timer;
- NSTimer* urgentTimer;
+ PuglView* puglview;
+ NSTrackingArea* trackingArea;
+ NSMutableAttributedString* markedText;
+ NSTimer* timer;
+ NSTimer* urgentTimer;
}
@end
@@ -421,10 +422,13 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type)
const NSPoint wloc = [self eventLocation:event];
const NSPoint rloc = [NSEvent mouseLocation];
- const NSString* chars = [event characters];
- const char* str = [chars UTF8String];
- const uint32_t code = puglDecodeUTF8((const uint8_t*)str);
- PuglEventKey ev = {
+ const PuglKey spec = keySymToSpecial(puglview, event);
+ const NSString* chars = [event charactersIgnoringModifiers];
+ const char* str = [[chars lowercaseString] UTF8String];
+ const uint32_t code = (
+ spec ? spec : puglDecodeUTF8((const uint8_t*)str));
+
+ const PuglEventKey ev = {
PUGL_KEY_PRESS,
0,
[event timestamp],
@@ -434,22 +438,27 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(puglview, event),
[event keyCode],
- (code != 0xFFFD) ? code : 0,
- keySymToSpecial(puglview, event),
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false
+ (code != 0xFFFD) ? code : 0
};
- strncpy(ev.string, str, 8);
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+
+ if (!spec) {
+ [self interpretKeyEvents:@[event]];
+ }
}
- (void) keyUp:(NSEvent*)event
{
const NSPoint wloc = [self eventLocation:event];
const NSPoint rloc = [NSEvent mouseLocation];
- const NSString* chars = [event characters];
- const char* str = [chars UTF8String];
- PuglEventKey ev = {
+ const PuglKey spec = keySymToSpecial(puglview, event);
+ const NSString* chars = [event charactersIgnoringModifiers];
+ const char* str = [[chars lowercaseString] UTF8String];
+ const uint32_t code =
+ (spec ? spec : puglDecodeUTF8((const uint8_t*)str));
+
+ const PuglEventKey ev = {
PUGL_KEY_RELEASE,
0,
[event timestamp],
@@ -459,15 +468,109 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(puglview, event),
[event keyCode],
- puglDecodeUTF8((const uint8_t*)str),
- keySymToSpecial(puglview, event),
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false,
+ (code != 0xFFFD) ? code : 0
};
- strncpy(ev.string, str, 8);
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
+- (BOOL) hasMarkedText
+{
+ return [markedText length] > 0;
+}
+
+- (NSRange) markedRange
+{
+ return (([markedText length] > 0)
+ ? NSMakeRange(0, [markedText length] - 1)
+ : NSMakeRange(NSNotFound, 0));
+}
+
+- (NSRange) selectedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(id)string
+ selectedRange:(NSRange)selected
+ replacementRange:(NSRange)replacement
+{
+ [markedText release];
+ markedText = (
+ [string isKindOfClass:[NSAttributedString class]]
+ ? [[NSMutableAttributedString alloc] initWithAttributedString:string]
+ : [[NSMutableAttributedString alloc] initWithString:string]);
+}
+
+- (void) unmarkText
+{
+ [[markedText mutableString] setString:@""];
+}
+
+- (NSArray*) validAttributesForMarkedText
+{
+ return @[];
+}
+
+- (NSAttributedString*)
+ attributedSubstringForProposedRange:(NSRange)range
+ actualRange:(NSRangePointer)actual
+{
+ return nil;
+}
+
+- (NSUInteger) characterIndexForPoint:(NSPoint)point
+{
+ return 0;
+}
+
+- (NSRect) firstRectForCharacterRange:(NSRange)range
+ actualRange:(NSRangePointer)actual
+{
+ const NSRect frame = [(id)puglview bounds];
+ return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
+}
+
+- (void) insertText:(id)string
+ replacementRange:(NSRange)replacement
+{
+ NSEvent* const event = [NSApp currentEvent];
+ NSString* const characters =
+ ([string isKindOfClass:[NSAttributedString class]]
+ ? [string string]
+ : (NSString*)string);
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ for (size_t i = 0; i < [characters length]; ++i) {
+ const uint32_t code = [characters characterAtIndex:i];
+ char utf8[8] = {0};
+ NSUInteger len = 0;
+
+ [characters getBytes:utf8
+ maxLength:sizeof(utf8)
+ usedLength:&len
+ encoding:NSUTF8StringEncoding
+ options:0
+ range:NSMakeRange(i, i + 1)
+ remainingRange:nil];
+
+ PuglEventText ev = { PUGL_TEXT,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event keyCode],
+ code,
+ { 0, 0, 0, 0, 0, 0, 0, 0 } };
+
+ memcpy(ev.string, utf8, len);
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+ }
+}
+
- (void) flagsChanged:(NSEvent*)event
{
const uint32_t mods = getModifiers(puglview, event);
@@ -501,10 +604,7 @@ handleCrossing(PuglOpenGLView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
mods,
[event keyCode],
- 0,
- special,
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false
+ special
};
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -648,8 +748,10 @@ puglCreateWindow(PuglView* view, const char* title)
[NSAutoreleasePool new];
impl->app = [NSApplication sharedApplication];
- impl->glview = [PuglOpenGLView alloc];
- impl->glview->puglview = view;
+ impl->glview = [PuglOpenGLView alloc];
+ impl->glview->trackingArea = nil;
+ impl->glview->markedText = [[NSMutableAttributedString alloc] init];
+ impl->glview->puglview = view;
[impl->glview initWithFrame:NSMakeRect(0, 0, view->width, view->height)];
[impl->glview addConstraint:
@@ -706,6 +808,8 @@ puglCreateWindow(PuglView* view, const char* title)
[window makeKeyAndOrderFront:window];
}
+ [impl->glview updateTrackingAreas];
+
return 0;
}
diff --git a/pugl/pugl_win.c b/pugl/pugl_win.c
index c2ccff3..afbae31 100644
--- a/pugl/pugl_win.c
+++ b/pugl/pugl_win.c
@@ -484,32 +484,31 @@ initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam)
event->scroll.dy = 0;
}
+/** Return the code point for buf, or the replacement character on error. */
static uint32_t
-utf16_to_code_point(const wchar_t* input, const int input_size)
-{
- uint32_t code_unit = *input;
- // Equiv. range check between 0xD800 to 0xDBFF inclusive
- if ((code_unit & 0xFC00) == 0xD800) {
- if (input_size < 2) {
- // "Error: is surrogate but input_size too small"
- return 0xFFFD; // replacement character
+puglDecodeUTF16(const wchar_t* buf, const int len)
+{
+ const uint32_t c0 = buf[0];
+ const uint32_t c1 = buf[0];
+ if (c0 >= 0xD800 && c0 < 0xDC00) {
+ if (len < 2) {
+ return 0xFFFD; // Surrogate, but length is only 1
+ } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) {
+ return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000;
}
- uint32_t code_unit_2 = *++input;
- // Equiv. range check between 0xDC00 to 0xDFFF inclusive
- if ((code_unit_2 & 0xFC00) == 0xDC00) {
- return (code_unit << 10) + code_unit_2 - 0x35FDC00;
- }
-
- // TODO: push_back(code_unit_2);
- // "Error: Unpaired surrogates."
- return 0xFFFD; // replacement character
+ return 0xFFFD; // Unpaired surrogates
}
- return code_unit;
+
+ return c0;
}
static void
-initKeyEvent(PuglEvent* event, PuglView* view, bool press, LPARAM lParam)
+initKeyEvent(PuglEventKey* event,
+ PuglView* view,
+ bool press,
+ WPARAM wParam,
+ LPARAM lParam)
{
POINT rpos = { 0, 0 };
GetCursorPos(&rpos);
@@ -517,92 +516,46 @@ initKeyEvent(PuglEvent* event, PuglView* view, bool press, LPARAM lParam)
POINT cpos = { rpos.x, rpos.y };
ScreenToClient(view->impl->hwnd, &rpos);
- event->key.type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- event->key.time = GetMessageTime() / 1e3;
- event->key.state = getModifiers();
- event->key.x_root = rpos.x;
- event->key.y_root = rpos.y;
- event->key.x = cpos.x;
- event->key.y = cpos.y;
- event->key.keycode = (uint32_t)((lParam & 0xFF0000) >> 16);
- event->key.character = 0;
- event->key.special = (PuglKey)0;
- event->key.filter = 0;
-}
-
-static void
-wcharBufToEvent(wchar_t* buf, int n, PuglEvent* event)
-{
- if (n > 0) {
- char* charp = (char*)event->key.string;
- if (!WideCharToMultiByte(CP_UTF8, 0, buf, n,
- charp, 8, NULL, NULL)) {
- /* error: could not convert to utf-8,
- GetLastError has details */
- memset(event->key.string, 0, 8);
- // replacement character
- event->key.string[0] = 0xEF;
- event->key.string[1] = 0xBF;
- event->key.string[2] = 0xBD;
- }
-
- event->key.character = utf16_to_code_point(buf, n);
- } else {
- // replacement character
- event->key.string[0] = 0xEF;
- event->key.string[1] = 0xBF;
- event->key.string[2] = 0xBD;
- event->key.character = 0xFFFD;
+ const unsigned vkey = (unsigned)wParam;
+ const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
+ const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR);
+ const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1;
+
+ event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ event->time = GetMessageTime() / 1e3;
+ event->state = getModifiers();
+ event->x_root = rpos.x;
+ event->y_root = rpos.y;
+ event->x = cpos.x;
+ event->y = cpos.y;
+ event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16);
+ event->key = 0;
+
+ const PuglKey special = keySymToSpecial(vkey);
+ if (special) {
+ event->key = special;
+ } else if (!dead) {
+ // Translate unshifted key
+ BYTE keyboardState[256] = {0};
+ wchar_t buf[5] = {0};
+ const int ulen = ToUnicode(vkey, vcode, keyboardState, buf, 4, 1<<2);
+ event->key = puglDecodeUTF16(buf, ulen);
}
}
static void
-translateMessageParamsToEvent(LPARAM lParam, WPARAM wParam, PuglEvent* event)
-{
- (void)lParam;
-
- /* TODO: This is a kludge. Would be nice to use ToUnicode here, but this
- breaks composed keys because it messes with the keyboard state. Not
- sure how to correctly handle this on Windows. */
-
- // This is how I really want to do this, but it breaks composed keys (é,
- // è, ü, ö, and so on) because ToUnicode messes with the keyboard state.
-
- //wchar_t buf[5];
- //BYTE keyboard_state[256];
- //int wcharCount = 0;
- //GetKeyboardState(keyboard_state);
- //wcharCount = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC),
- // keyboard_state, buf, 4, 0);
- //wcharBufToEvent(buf, wcharCount, event);
-
- // So, since Google refuses to give me a better solution, and if no one
- // else has a better solution, I will make a hack...
- wchar_t buf[5] = { 0, 0, 0, 0, 0 };
- WPARAM c = MapVirtualKey((unsigned)wParam, MAPVK_VK_TO_CHAR);
- buf[0] = c & 0xffff;
- // TODO: This does not take caps lock into account
- // TODO: Dead keys should affect key releases as well
- if (!(event->key.state && PUGL_MOD_SHIFT))
- buf[0] = towlower(buf[0]);
- wcharBufToEvent(buf, 1, event);
- event->key.filter = ((c >> 31) & 0x1);
-}
+initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam)
+{
+ const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF };
-static void
-translateCharEventToEvent(WPARAM wParam, PuglEvent* event)
-{
- wchar_t buf[2];
- int wcharCount;
- if (wParam & 0xFFFF0000) {
- wcharCount = 2;
- buf[0] = (wParam & 0xFFFF);
- buf[1] = ((wParam >> 16) & 0xFFFF);
- } else {
- wcharCount = 1;
- buf[0] = (wParam & 0xFFFF);
+ initKeyEvent(&event->key, view, true, wParam, lParam);
+ event->type = PUGL_TEXT;
+ event->text.character = puglDecodeUTF16(utf16, 2);
+
+ if (!WideCharToMultiByte(
+ CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) {
+ memset(event->text.string, 0, 8);
}
- wcharBufToEvent(buf, wcharCount, event);
}
static bool
@@ -780,30 +733,14 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
break;
case WM_KEYDOWN:
if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- if (!(event.key.special = keySymToSpecial(wParam))) {
- event.key.type = PUGL_NOTHING;
- }
- }
- break;
- case WM_CHAR:
- if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- translateCharEventToEvent(wParam, &event);
- }
- break;
- case WM_DEADCHAR:
- if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- translateCharEventToEvent(wParam, &event);
- event.key.filter = 1;
+ initKeyEvent(&event.key, view, true, wParam, lParam);
}
break;
case WM_KEYUP:
- initKeyEvent(&event, view, false, lParam);
- if (!(event.key.special = keySymToSpecial(wParam))) {
- translateMessageParamsToEvent(lParam, wParam, &event);
- }
+ initKeyEvent(&event.key, view, false, wParam, lParam);
+ break;
+ case WM_CHAR:
+ initCharEvent(&event, view, wParam, lParam);
break;
case WM_SETFOCUS:
stopFlashing(view);
diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c
index d5e51b4..f10902f 100644
--- a/pugl/pugl_x11.c
+++ b/pugl/pugl_x11.c
@@ -253,34 +253,55 @@ keySymToSpecial(KeySym sym)
return (PuglKey)0;
}
-static void
-translateKey(PuglView* view, XEvent* xevent, PuglEventKey* event)
+static int
+lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym)
{
- KeySym sym = 0;
- memset(event->string, 0, 8);
- event->filter = XFilterEvent(xevent, None);
- if (xevent->type == KeyRelease || event->filter || !view->impl->xic) {
- if (XLookupString(&xevent->xkey, event->string, 7, &sym, NULL) == 1) {
- event->character = (uint8_t)event->string[0];
- }
- } else {
- /* TODO: Not sure about this. On my system, some characters work with
- Xutf8LookupString but not with XmbLookupString, and some are the
- opposite. */
- Status status = 0;
+ Status status = 0;
+
#ifdef X_HAVE_UTF8_STRING
- const int n = Xutf8LookupString(
- view->impl->xic, &xevent->xkey, event->string, 7, &sym, &status);
+ const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status);
#else
- const int n = XmbLookupString(
- view->impl->xic, &xevent->xkey, event->string, 7, &sym, &status);
+ const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status);
#endif
- if (n > 0) {
- event->character = puglDecodeUTF8((const uint8_t*)event->string);
+
+ return status == XBufferOverflow ? 0 : n;
+}
+
+static void
+translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
+{
+ const unsigned state = xevent->xkey.state;
+ const bool filter = XFilterEvent(xevent, None);
+
+ event->key.keycode = xevent->xkey.keycode;
+ xevent->xkey.state = 0;
+
+ // Lookup unshifted key
+ char ustr[8] = {0};
+ KeySym sym = 0;
+ const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL);
+ const PuglKey special = keySymToSpecial(sym);
+
+ event->key.key = ((special || ufound <= 0)
+ ? special
+ : puglDecodeUTF8((const uint8_t*)ustr));
+
+ if (xevent->type == KeyPress && !filter && !special) {
+ // Lookup shifted key for possible text event
+ xevent->xkey.state = state;
+
+ char sstr[8] = {0};
+ const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym);
+ if (sfound > 0) {
+ // Dispatch key event now
+ puglDispatchEvent(view, event);
+
+ // "Return" a text event in its place
+ event->text.type = PUGL_TEXT;
+ event->text.character = puglDecodeUTF8((const uint8_t*)sstr);
+ memcpy(event->text.string, sstr, sizeof(sstr));
}
}
- event->special = keySymToSpecial(sym);
- event->keycode = xevent->xkey.keycode;
}
static uint32_t
@@ -387,7 +408,7 @@ translateEvent(PuglView* view, XEvent xevent)
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.key);
+ translateKey(view, &xevent, &event);
break;
case EnterNotify:
case LeaveNotify:
diff --git a/test/pugl_cairo_test.c b/test/pugl_cairo_test.c
index 524e8b0..47907db 100644
--- a/test/pugl_cairo_test.c
+++ b/test/pugl_cairo_test.c
@@ -129,9 +129,7 @@ onEvent(PuglView* view, const PuglEvent* event)
{
switch (event->type) {
case PUGL_KEY_PRESS:
- if (event->key.character == 'q' ||
- event->key.character == 'Q' ||
- event->key.character == PUGL_CHAR_ESCAPE) {
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
quit = 1;
}
break;
diff --git a/test/pugl_test.c b/test/pugl_test.c
index fdf78c1..a609027 100644
--- a/test/pugl_test.c
+++ b/test/pugl_test.c
@@ -103,9 +103,7 @@ onEvent(PuglView* view, const PuglEvent* event)
quit = 1;
break;
case PUGL_KEY_PRESS:
- if (event->key.character == 'q' ||
- event->key.character == 'Q' ||
- event->key.character == PUGL_CHAR_ESCAPE) {
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
quit = 1;
}
break;
diff --git a/test/test_utils.h b/test/test_utils.h
index 285cc83..fb04f4b 100644
--- a/test/test_utils.h
+++ b/test/test_utils.h
@@ -101,16 +101,15 @@ printEvent(const PuglEvent* event, const char* prefix)
{
switch (event->type) {
case PUGL_KEY_PRESS:
- return fprintf(stderr, "%sKey %u (char U+%04X special U+%04X) press (%s)%s\n",
- prefix,
- event->key.keycode, event->key.character, event->key.special,
- event->key.string, event->key.filter ? " (filtered)" : "");
-
+ return fprintf(stderr, "%sKey press code %3u key U+%04X\n",
+ prefix, event->key.keycode, event->key.key);
case PUGL_KEY_RELEASE:
- return fprintf(stderr, "%sKey %u (char U+%04X special U+%04X) release (%s)%s\n",
- prefix,
- event->key.keycode, event->key.character, event->key.special,
- event->key.string, event->key.filter ? " (filtered)" : "");
+ return fprintf(stderr, "%sKey release code %3u key U+%04X\n",
+ prefix, event->key.keycode, event->key.key);
+ case PUGL_TEXT:
+ return fprintf(stderr, "%sText entry code %3u char U+%04X (%s)\n",
+ prefix, event->text.keycode,
+ event->text.character, event->text.string);
case PUGL_BUTTON_PRESS:
case PUGL_BUTTON_RELEASE:
return (fprintf(stderr, "%sMouse %d %s at %f,%f ",