diff options
Diffstat (limited to 'pugl')
-rw-r--r-- | pugl/pugl.h | 79 | ||||
-rw-r--r-- | pugl/pugl_osx.m | 160 | ||||
-rw-r--r-- | pugl/pugl_win.c | 177 | ||||
-rw-r--r-- | pugl/pugl_x11.c | 67 |
4 files changed, 278 insertions, 205 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: |