diff options
author | culler <culler> | 2020-04-26 22:12:52 (GMT) |
---|---|---|
committer | culler <culler> | 2020-04-26 22:12:52 (GMT) |
commit | 022c2fd5dc1cc83cc6606d7e83495af76e8bdbd8 (patch) | |
tree | 3afc10a28432b557577fc78dd3fb4e97d2eeb302 /macosx | |
parent | 551859ab46ad0d92f0097687308150befab95217 (diff) | |
download | tk-022c2fd5dc1cc83cc6606d7e83495af76e8bdbd8.zip tk-022c2fd5dc1cc83cc6606d7e83495af76e8bdbd8.tar.gz tk-022c2fd5dc1cc83cc6606d7e83495af76e8bdbd8.tar.bz2 |
Tidy up; expand comments; clarify NSTextInputClient logic; deal with keyPad buttons.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/tkMacOSXKeyEvent.c | 76 | ||||
-rw-r--r-- | macosx/tkMacOSXKeyboard.c | 221 |
2 files changed, 182 insertions, 115 deletions
diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index 109c774..fe71ea9 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -17,6 +17,7 @@ #include "tkMacOSXInt.h" #include "tkMacOSXConstants.h" #include "tkMacOSXWm.h" +#define IS_PRINTABLE(keychar) ((keychar >= 0x20) && (keychar < 0xF700)) /* #ifdef TK_MAC_DEBUG @@ -54,7 +55,6 @@ static void setXEventPoint(XEvent *xEvent, Tk_Window tkwin, NSWindow *w); XEvent xEvent; UniChar keychar = 0; Bool can_input_text, has_modifiers = NO, use_text_input = NO; - int length = 0; static NSUInteger savedModifiers = 0; static NSMutableArray *nsEvArray = nil; @@ -117,64 +117,63 @@ static void setXEventPoint(XEvent *xEvent, Tk_Window tkwin, NSWindow *w); has_modifiers = xEvent.xkey.state & XEVENT_MOD_MASK; can_input_text = ((focusWinPtr->flags & TK_CAN_INPUT_TEXT) != 0); - /* - * A KeyDown event targeting the caret window and having an alphanumeric - * keychar should be processed by our TextInputClient. The XEvent will not - * be sent in this case. - */ - #if (NS_KEYLOG) TKLog(@"keyDown: %s compose sequence.\n", processingCompose == YES ? "Continue" : "Begin"); #endif + /* + * Decide whether this event should be processed with the NSTextInputClient + * protocol. + */ + if (processingCompose || (type == NSKeyDown && can_input_text && !has_modifiers && - (keychar >= 0x0020) && (keychar < 0xF700)) + IS_PRINTABLE(keychar)) ) { use_text_input = YES; } + + /* + * If we are processing this KeyDown event as an NSTextInputClient we do + * not queue an XEvent. We pass the NSEvent to our interpretKeyEvents + * method. When the composition sequence is complete, the callback method + * insertText: replacementRange will be called. That method generates a + * keyPress XEvent with the selected character. + */ + if (use_text_input) { /* - * Call our interpretKeyEvents method to handle the event as an - * NSTextInputClient. When the composition sequence is complete, our - * implementation of insertText: replacementRange will be called. That - * method generates a keyPress XEvent with the selected character. + * In IME the Enter key is used to terminate a composition sequence. + * when there are multiple choices of input text available, and the + * user's selected choice is not the default it may be necessary to hit + * the Enter key multiple times before the text is accepted and + * rendered (See ticket 39de9677aa]). So when sending an Enter key + * during composition, we continue sending Enter keys until the + * inputText method has cleared the processingCompose flag. */ - if (!processingCompose) { + if (processingCompose && [theEvent keyCode] == 36) { [nsEvArray addObject: theEvent]; - [[w contentView] interpretKeyEvents: nsEvArray]; - [nsEvArray removeObject: theEvent]; - } else { - - /* - * In IME when there are multiple choices with the same composition - * sequence and the selected choice is not the default it may be - * necessary to hit the Enter key twice before the character is - * accepted and rendered (See ticket 39de9677aa]). So when sending - * an Enter key, we continue sending Enter keys until the inputText - * method has cleared the processingCompose flag. - */ - while(processingCompose) { - [nsEvArray addObject: theEvent]; [[w contentView] interpretKeyEvents: nsEvArray]; - [nsEvArray removeObject: theEvent]; - if ([theEvent keyCode] != 36) { - break; - } } + [nsEvArray removeObject: theEvent]; + } else { + [nsEvArray addObject: theEvent]; + [[w contentView] interpretKeyEvents: nsEvArray]; + [nsEvArray removeObject: theEvent]; } return theEvent; } /* - * We need to send an XEvent. Finish constructing it. + * We are not handling this event as an NSTextInputClient, so we need to + * finish constructing the XEvent and queue it. */ - xEvent.xkey.keycode = (virtual << 16) | keychar; + xEvent.xkey.keycode = (virtual << 24) | keychar; switch (type) { case NSFlagsChanged: @@ -209,15 +208,14 @@ static void setXEventPoint(XEvent *xEvent, Tk_Window tkwin, NSWindow *w); /* * Set the trans_chars for keychars outside of the private-use range. */ - + setXEventPoint(&xEvent, tkwin, w); - if ((keychar >= 0x20) && (keychar < 0xF700)) { - length = TkUniCharToUtf(keychar, xEvent.xkey.trans_chars); + if (IS_PRINTABLE(keychar)) { + xEvent.xkey.nbytes = 0; /* This string is null-terminated. */ + int length = TkUniCharToUtf(keychar, xEvent.xkey.trans_chars); xEvent.xkey.trans_chars[length] = 0; - } else { - xEvent.xkey.nbytes = 0; } - + /* * Finally we can queue the XEvent, inserting a KeyRelease before a * repeated KeyPress. diff --git a/macosx/tkMacOSXKeyboard.c b/macosx/tkMacOSXKeyboard.c index 00b8715..0d023ff 100644 --- a/macosx/tkMacOSXKeyboard.c +++ b/macosx/tkMacOSXKeyboard.c @@ -16,37 +16,107 @@ #include "tkMacOSXConstants.h" #include "tkMacOSXKeysyms.h" +#define IS_PRINTABLE(keychar) ((keychar >= 0x20) && (keychar < 0xF700)) +#define ON_KEYPAD(virtual) ((virtual >= 0x41) && (virtual <= 0x5C)) +#define VIRTUAL_MAX 0x7F +#define MAC_KEYCHAR_MASK 0xFFFF + /* - * When converting native key events to X11 key events, each platform is - * allowed to choose what information to encode in the XEvent.xkey.keycode - * field. On the Macintosh, every non-modifier key has a unicode character - * associated to it. For non-text keys this unicode character is in the - * private use range 0xF700 - 0xF8FF. Modifier keys, however, do not produce - * KeyDown or KeyUp events, rather FlagsChanged events, so they do not have an - * associated unicode character. - * - * When constructing an XEvent from a KeyDown or KeyUo NSEvent, the - * XEvent.xkey.keycode field is constructed by using bits 0-15 to hold the - * first unicode character of the [NSEvent characters] attribute of the - * NSEvent, and bits 16-23 to hold the value of the [NSEvent keyCode] - * attribute. The keyCode attribute identifies a location on the keyboard, - * and Apple calls it a "virtual keycode". It is allowed for the characters - * attribute to have length greater than 1, but that does not appear to happen - * for any known keyboards. - * - * When generating an XEvent with the event generate command, the unicode - * character is determined from the X11 keysym and, if that unicode character - * belongs to a key on the current keyboard layout bits 16-23 are set to the - * virtual keycode of that key. Otherwise they are cleared. - * - * KeyPress or KeyRelease XEvents are also constructed when a FlagsChanged - * NSEvent is processed. For these, the unicode character is set to 0xF8FF, - * the last value of the private use range. + * About keyboards + + * Keyboards are complicated. This long comment is an attempt to provide + * enough information about them to make it possible to read and understand + * the code in this file. + * + * Every key on a keyboard is identified by a number between 0 and 127. In + * macOS, pressing or releasing a key on the keyboard generates an NSEvent of + * type KeyDown, KeyUp or FlagsChanged. The 8-bit identifier of the key that + * was involved in this event is provided in the attribute [NSEvent keyCode]. + * Apple also refers to this number as a "Virtual KeyCode". In this file, to + * avoid confusion with other uses of the word keycode, we will refer to this + * key identifier as a "virtual keycode", usually the value of a variable named + * "virtual". + * + * Some of the keys on a keyboard are "modifier" keys. The effect of + * pressing or releasing a key depends on three quantities: + * - which key is being pressed or released + * - which modifier keys are being held down at the moment + * - the current keyboard layout + * If the key is a modifier key then the effect of pressing or releasing it is + * only to change the list of which modifier keys are being held down. Apple + * reports this by sending an NSEvent of type FlagsChanged. X11 reports this + * as a KeyPress or KeyRelease event for the modifier key. Note that there may + * be combinations of modifier key states and key presses which have no effect. + * + * In X11 every meaningful effect from a key action is identified by a 16 bit + * value known as a keysym. Every keysym has an associated string name, also + * known as a keysym. The Tk bind command uses the X11 keysym string to + * specify a key event which should invoke a certain action and it provides the + * numeric and symbolic keysyms to the bound proc as %K and %N respectively. + * An X11 XEvent which reports a KeyPress or KeyRelease does not include the + * keysym. Instead it includes a platform-specific numerical value called a + * keycode which is available to the bound procedure as %k. A platform port of + * Tk must provide functions which convert between keycodes and numerical + * keysyms. Conversion between numerical and symbolic keysyms is provided by + * the generic Tk code, although platforms are allowed to provide their own by + * defining the XKeysymToString and XStringToKeysym functions and undefining + * the macro REDO_KEYSYM_LOOKUP. This macOS port uses the conversion provided + * by the generic code. + * + * When the keyboard focus is on a Tk widget which provides text input, there + * are some X11 KeyPress events which cause text to be inserted. We will call + * these "printable" events. The text which should be inserted is contained in + * the xkeys.trans_chars field of a key XEvent as a unicode string encoded with + * a special Tcl encoding. The string is assumed to be null-terminated if the + * nbytes field of the XEvent is non-zero. Otherwise the length is given by + * the value of nbytes. The value of the trans_chars string in an Xevent + * depends on more than the three items above. It may also depend on the + * sequence of keypresses that preceded the one being reported by the XEvent. + * For example, on macOS an <Alt-e> event does not cause text to be inserted + * but a following <a> event causes an accented a to be inserted. The events + * in such a composition sequence, other than the final one, are known as + * "dead-key" events. + * + * MacOS packages the information described above in a different way. Every + * meaningful effect from a key action *other than changing the state of + * modifier keys* is identified by a unicode string which is provided as the + * [NSEvent characters] attribute of a KeyDown or KeyUp event. FlagsChanged + * events do not have characters. In principle, the characters attribute could + * be an arbitrary unicode string but in practice it is always a single UTF-16 + * character which we usually store in a variable named keychar. While the + * keychar is a legal unicode code point, it does not necessarily represent a + * glyph. MacOS uses unicode code points in the private-use range 0xF700-0xF8FF + * for non-printable events which have no associated ASCII code point. For + * example, pressing the F2 key generates an NSEvent with the character 0xF705, + * the Backspace key produces 0x7F (ASCII del) and the Delete key produces + * 0xF728. + * + * With the exception of modifier keys, it is possible to translate between + * numerical X11 keysyms and macOS keychars; this file constructs Tcl hash + * tables to do this job, using data defined in the file tkMacOSXKeysyms.h. + * The code here adopts the convention that the keychar of any modifier key + * is 0xF8FF, the last value in the private-use range. + * + * The macosx platform-specific scheme for generating a keycode when mapping an + * NSEvent of type KeyUp, KeyDown or FlagsChanged to an XEvent of type KeyPress + * or KeyRelease is as follows: + * keycode = (virtual << 24) | keychar + * A few remarks are in order. First, we are using 32 bits for the keycode and + * we are allowing room for up to 24 bits for the keychar. This means that + * there is enough room in the keycode to hold a UTF-32 character, which only + * requires 21 bits. So, in the future, when macs have emoji keyboards, no + * change will be needed in how keycodes are generated. Second, the KeyCode + * type for the keycode field in an XEvent is currently defined as unsigned + * long, which means that it is 64 bits on modern macOS systems. Finally, there + * is no obstruction to generating KeyPress events for keys that represent + * letters which do not exist on the current keyboard layout. And different + * keyboard layouts can assign a given letter to different keys. So we need a + * convention for what value to assign to "virtual" when computing the keycode + * for a generated event. The convention used here is as follows: + * If there is a key on the current keyboard which produces the keychar, + * use the virtual keycode of that key. Otherwise set virtual = 0. */ -#define VIRTUAL_MAX 0x7F -#define MAC_KEYCODE_MASK 0xFF - /* * Hash tables used to translate between various key attributes. */ @@ -70,8 +140,8 @@ static BOOL keyboardChanged = YES; static void InitHashTables(void); static void UpdateKeymap(void); -static int KeyDataToUnicode(UniChar * uniChars, int maxChars, - UInt16 keyaction, UInt32 keycode, UInt32 modifiers, +static int KeyDataToUnicode(UniChar *uniChars, int maxChars, + UInt16 keyaction, UInt32 virtual, UInt32 modifiers, UInt32 * deadKeyStatePtr); #pragma mark TKApplication(TKKeyboard) @@ -121,7 +191,7 @@ InitHashTables(void) Tcl_SetHashValue(hPtr, INT2PTR(kPtr->keysym)); hPtr = Tcl_CreateHashEntry(&keysym2keycode, INT2PTR(kPtr->keysym), &dummy); - Tcl_SetHashValue(hPtr, INT2PTR(kPtr->keychar | (kPtr->virtual << 16))); + Tcl_SetHashValue(hPtr, INT2PTR(kPtr->keychar | (kPtr->virtual << 24))); } Tcl_InitHashTable(&keysym2unichar, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&unichar2keysym, TCL_ONE_WORD_KEYS); @@ -143,7 +213,9 @@ InitHashTables(void) * UpdateKeymap -- * * Called when the keyboard changes to update the hash table that - * maps unicode characters to virtual keycodes. + * maps unicode characters to virtual keycodes with states. In order + * for this to be well-defined we have to ignore virtual keycodes for + * keypad keys. * * Results: * None. @@ -158,7 +230,7 @@ static void UpdateKeymap() { static int keymapInitialized = 0; - UniChar keyChar = 0; + UniChar keychar; Tcl_HashEntry *hPtr; int virtual, state, modifiers, dummy; @@ -167,31 +239,34 @@ UpdateKeymap() } else { Tcl_DeleteHashTable(&unichar2virtual); } - for (state = 3; state >= 0; state--) { + for (state = 4; state >= 0; state--) { for (virtual = 0; virtual <= VIRTUAL_MAX; virtual++) { - modifiers = (state & 1 ? shiftKey : 0) | (state & 2 ? optionKey : 0); - KeyDataToUnicode(&keyChar, 1, kUCKeyActionDown, virtual, modifiers, NULL); - hPtr = Tcl_CreateHashEntry(&unichar2virtual, INT2PTR(keyChar), + if (ON_KEYPAD(virtual)) { + continue; + } + modifiers = (state & 1 ? shiftKey : 0) | + (state & 2 ? optionKey : 0); + KeyDataToUnicode(&keychar, 1, kUCKeyActionDown, virtual, modifiers, + NULL); + hPtr = Tcl_CreateHashEntry(&unichar2virtual, INT2PTR(keychar), &dummy); Tcl_SetHashValue(hPtr, INT2PTR(state << 8 | virtual)); } } } - /* *---------------------------------------------------------------------- * * KeyDataToUnicode -- * - * Given MacOS key event data this function generates the unicode - * characters. It does this using OS resources from the Carbon - * framework. + * Given MacOS key event data this function generates the keychar. It + * does this by using OS resources from the Carbon framework. * * The parameter deadKeyStatePtr can be NULL, if no deadkey handling is - * needed. + * needed (which is always the case here). * - * This function is called from XKeycodeToKeysym() in tkMacOSKeyboard.c. + * This function is called from XKeycodeToKeysym(). * * Results: * The number of characters generated if any, 0 if we are waiting for @@ -209,11 +284,11 @@ KeyDataToUnicode( UniChar *uniChars, int maxChars, UInt16 keyaction, - UInt32 keycode, + UInt32 virtual, UInt32 modifiers, UInt32 *deadKeyStatePtr) { - static const void *uchr = NULL; + static const void *layoutData = NULL; static UInt32 keyboardType = 0; UniCharCount actuallength = 0; @@ -226,26 +301,26 @@ KeyDataToUnicode( currentKeyboardLayout, kTISPropertyUnicodeKeyLayoutData); if (keyLayoutData) { - uchr = CFDataGetBytePtr(keyLayoutData); + layoutData = CFDataGetBytePtr(keyLayoutData); keyboardType = LMGetKbdType(); } CFRelease(currentKeyboardLayout); } keyboardChanged = 0; } - if (uchr) { + if (layoutData) { OptionBits options = 0; UInt32 dummyState; OSStatus err; - keycode &= 0xFFFF; + virtual &= MAC_KEYCHAR_MASK; modifiers = (modifiers >> 8) & 0xFF; if (!deadKeyStatePtr) { options = kUCKeyTranslateNoDeadKeysMask; dummyState = 0; deadKeyStatePtr = &dummyState; } - err = ChkErr(UCKeyTranslate, uchr, keycode, keyaction, modifiers, + err = ChkErr(UCKeyTranslate, layoutData, virtual, keyaction, modifiers, keyboardType, options, deadKeyStatePtr, maxChars, &actuallength, uniChars); if (!actuallength && *deadKeyStatePtr) { @@ -269,9 +344,9 @@ KeyDataToUnicode( * * XKeycodeToKeysym -- * - * Stub function which translate from the platform-specific keycode used + * This is a stub function which translates from the keycode used * in an XEvent to an X11 keysym. On the Macintosh, the display input - * is ignored and only the virtual keycode in bits 16-23 is used. + * is ignored and only the virtual keycode in bits 24-31 is used. * * Results: * Returns the corresponding keysym, or NoSymbol if the keysym cannot @@ -304,7 +379,7 @@ XKeycodeToKeysym( * an Fn function key or Tab, Backspace, Home, End, etc. */ - virtual = (keycode >> 16) & 0xFF; + virtual = (keycode >> 24) & MAC_KEYCHAR_MASK; if (virtual) { hPtr = Tcl_FindHashEntry(&virtual2keysym, INT2PTR(virtual)); if (hPtr != NULL) { @@ -313,8 +388,8 @@ XKeycodeToKeysym( } /* - * If not, use Carbon to find the unicode character and translate it - * to a keysym using a hash table. + * If not, use the Carbon Framework to find the unicode character and + * translate it to a keysym using the unicode2keysym hash table. */ modifiers = (state & 1 ? shiftKey : 0) | (state & 2 ? optionKey : 0); @@ -427,7 +502,7 @@ XFreeModifiermap( * XKeysymToString, XStringToKeysym -- * * These X window functions map keysyms to strings & strings to keysyms. - * These are never called because we define REDO_KEYSYM_LOOKUP, which + * They are never called because we define REDO_KEYSYM_LOOKUP, which * instructs tkBind to do the conversion for us. * * Results: @@ -458,15 +533,14 @@ XStringToKeysym( * * XKeysymToKeycode -- * - * Converts an XKeysym to the value which would be used as the keycode - * field of a KeyPress or KeyRelease XEvent for the corresponding key. - * This is an opaque stub function which knows how the keycode field is - * generated on a Mac. + * This is a stub function which onverts a numerical keysym to the keycode + * which should be used as the in a KeyPress or KeyRelease XEvent for the + * corresponding key. * * Results: * * An X11 KeyCode with a unicode character in the low 16 bits and the - * 8-bit "virtual keycode" in the third byte. See the description of + * 8-bit "virtual keycode" in the highest byte. See the description of * keycodes on the Macintosh at the top of this file. * * Side effects: @@ -474,7 +548,7 @@ XStringToKeysym( * *---------------------------------------------------------------------- */ -KeyCode +static KeyCode XKeysymToKeycodeWithState( Display *display, KeySym keysym, @@ -485,6 +559,7 @@ XKeysymToKeycodeWithState( if (!initialized) { InitHashTables(); } + hPtr = Tcl_FindHashEntry(&keysym2unichar, INT2PTR(keysym)); if (hPtr != NULL) { KeyCode character = (KeyCode) Tcl_GetHashValue(hPtr); @@ -493,17 +568,12 @@ XKeysymToKeycodeWithState( KeyCode lookup = ((KeyCode) Tcl_GetHashValue(hPtr)); KeyCode virtual = lookup & 0xFF; *state = lookup >> 8; - return virtual << 16 | character; + return virtual << 24 | character; } else { return character; } } - /* - * This is not a text key. Try doing a hash table lookup to find the - * keycode for a special key. - */ - hPtr = Tcl_FindHashEntry(&keysym2keycode, INT2PTR(keysym)); if (hPtr != NULL) { return (KeyCode) Tcl_GetHashValue(hPtr); @@ -554,23 +624,22 @@ TkpSetKeycodeAndState( if (keysym == NoSymbol) { eventPtr->xkey.keycode = 0; } else { - int state, length = 0; + int state; UniChar keychar; Display *display = Tk_Display(tkwin); eventPtr->xkey.keycode = XKeysymToKeycodeWithState(display, keysym, &state); eventPtr->xkey.state |= state; - keychar = eventPtr->xkey.keycode & 0xFFFF; - + keychar = eventPtr->xkey.keycode & MAC_KEYCHAR_MASK; + /* * Set trans_chars for keychars outside of the private-use range. */ - if ((keychar >= 0x20) && (keychar < 0xF700)) { - length = TkUniCharToUtf(keychar, eventPtr->xkey.trans_chars); + if (IS_PRINTABLE(keychar)) { + eventPtr->xkey.nbytes = 0; /* This string is null-terminated. */ + int length = TkUniCharToUtf(keychar, eventPtr->xkey.trans_chars); eventPtr->xkey.trans_chars[length] = 0; - } else { - eventPtr->xkey.nbytes = 0; } } } @@ -614,8 +683,8 @@ TkpGetKeySym( * Modifier key events have a special mac keycode (see tkProcessKeyEvent). */ - if ((eventPtr->xkey.keycode & MAC_KEYCODE_MASK) == 0xF8FF) { - switch (eventPtr->xkey.keycode >> 16) { /* the virtual keyCode */ + if ((eventPtr->xkey.keycode & MAC_KEYCHAR_MASK) == 0xF8FF) { + switch (eventPtr->xkey.keycode >> 24) { /* the virtual keyCode */ case 54: return XK_Meta_R; case 55: |