From ffc77da5a5ed9f95bff5ceac80ccf440fa80580f Mon Sep 17 00:00:00 2001 From: cc_benny Date: Wed, 21 Apr 2004 13:18:32 +0000 Subject: =?UTF-8?q?Fix=20several=20problems=20with=20Icelandic=20(and=20Fa?= =?UTF-8?q?roese)=20keyboards=20reported=20by=20J=E9r=F4me=20Gagnon-Voyer?= =?UTF-8?q?=20maccom>=20on=20tcl-mac=20on=202004-03-?= =?UTF-8?q?22.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (KLSInit): Add. (GetKCHREncoding): Add. (GetKeyboardLayout): Add calls to Keyboard Layout Services, if present. Rework classic handling. Use GetKCHREncoding(). Add parameter encodingPtr. --- macosx/tkMacOSXKeyEvent.c | 386 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 338 insertions(+), 48 deletions(-) diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index fcb5432..3742399 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -95,8 +95,12 @@ static int GenerateKeyEvent ( UInt32 savedModifiers, const UniChar * chars, int numChars); +static void KLSInit(void); + static int GetKeyboardLayout ( - Ptr * resource); + Ptr * resourcePtr, TextEncoding * encodingPtr); +static TextEncoding GetKCHREncoding( + ScriptCode script, SInt32 layoutid); static int KeycodeToUnicodeViaUnicodeResource( UniChar * uniChars, int maxChars, @@ -107,7 +111,7 @@ static int KeycodeToUnicodeViaUnicodeResource( static int KeycodeToUnicodeViaKCHRResource( UniChar * uniChars, int maxChars, - Ptr kchr, + Ptr kchr, TextEncoding encoding, EventKind eKind, UInt32 keycode, UInt32 modifiers, UInt32 * deadKeyStatePtr); @@ -540,17 +544,80 @@ InitKeyEvent( /* *---------------------------------------------------------------------- * + * KLSInit -- + * + * Dynamically initialize Keyboard Layout Services bindings. + * + * Side effects: + * Sets some function pointers (hopefully). + * + *---------------------------------------------------------------------- + */ + +#include + +static int KLSIsInitialized = false; + +/* + * If we have old headers, we need to define these types and constants + * ourself. We use preprocessor macros instead of enums and typedefs, + * because macros work even in case of version misunderstandings, while + * duplicate enums and typedefs would give errrors. + */ + +#if !defined(MAC_OS_X_VERSION_10_2) || \ + (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_2) +#define KeyboardLayoutRef Ptr +#define KeyboardLayoutPropertyTag UInt32 +#define kKLKCHRData 0 +#define kKLuchrData 1 +#define kKLIdentifier 2 +#endif + +static OSStatus (*KLGetCurrentKeyboardLayoutPtr)( + KeyboardLayoutRef * oKeyboardLayout) = NULL; +static OSStatus (*KLGetKeyboardLayoutPropertyPtr)( + KeyboardLayoutRef iKeyboardLayout, + KeyboardLayoutPropertyTag iPropertyTag, + const void ** oValue) = NULL; + +static void +KLSInit(void) +{ + static const char MODULE[] = "HIToolbox"; + static const char GET_LAYOUT[] = "_KLGetCurrentKeyboardLayout"; + static const char GET_PROP[] = "_KLGetKeyboardLayoutProperty"; + + NSSymbol symbol; + + if (NSIsSymbolNameDefinedWithHint(GET_LAYOUT, MODULE)) { + symbol = NSLookupAndBindSymbolWithHint(GET_LAYOUT, MODULE); + KLGetCurrentKeyboardLayoutPtr = NSAddressOfSymbol(symbol); + } + if (NSIsSymbolNameDefinedWithHint(GET_PROP, MODULE)) { + symbol = NSLookupAndBindSymbolWithHint(GET_PROP, MODULE); + KLGetKeyboardLayoutPropertyPtr = NSAddressOfSymbol(symbol); + } + + KLSIsInitialized = true; +} + + +/* + *---------------------------------------------------------------------- + * * GetKeyboardLayout -- * * Queries the OS for a pointer to a keyboard resource. * - * This function works with the keyboard layout switch menu that - * we have in 10.2. + * This function works with the keyboard layout switch menu. It uses + * Keyboard Layout Services, where available. * * Results: - * 1 if there is returned a Unicode 'uchr' resource in - * "*resource", 0 if it is a classic 'KCHR' resource. A pointer - * to the actual resource data goes into *resource. + * 1 if there is returned a Unicode 'uchr' resource in *resourcePtr, 0 + * if it is a classic 'KCHR' resource. A pointer to the actual resource + * data goes into *resourcePtr. If the resource is a 'KCHR' resource, + * the corresponding Mac encoding goes into *encodingPtr. * * Side effects: * Sets some internal static variables. @@ -559,47 +626,175 @@ InitKeyEvent( */ static int -GetKeyboardLayout (Ptr * resource) +GetKeyboardLayout (Ptr * resourcePtr, TextEncoding * encodingPtr) { - static Boolean initialized = false; - static SInt16 lastKeyLayoutID = -1; - static Handle uchrHnd = NULL; - static Handle KCHRHnd = NULL; + static KeyboardLayoutRef lastLayout = NULL; + static SInt32 lastLayoutId; + static TextEncoding lastEncoding = kTextEncodingMacRoman; + static Ptr uchr = NULL; + static Ptr KCHR = NULL; + static Handle handle = NULL; + + int hasLayoutChanged = false; + KeyboardLayoutRef currentLayout = NULL; + SInt32 currentLayoutId = 0; + ScriptCode currentKeyScript; + + /* + * Several code branches need this information. + */ + + currentKeyScript = GetScriptManagerVariable(smKeyScript); - SInt16 keyScript; - SInt16 keyLayoutID; + if (!KLSIsInitialized) { + KLSInit(); + } - keyScript = GetScriptManagerVariable(smKeyScript); - keyLayoutID = GetScriptVariable(keyScript,smScriptKeys); + if (KLGetCurrentKeyboardLayoutPtr != NULL) { - if (!initialized || (lastKeyLayoutID != keyLayoutID)) { - initialized = true; - deadKeyStateUp = deadKeyStateDown = 0; - lastKeyLayoutID = keyLayoutID; - uchrHnd = GetResource('uchr',keyLayoutID); - if (NULL == uchrHnd) { - KCHRHnd = GetResource('KCHR',keyLayoutID); + /* + * Use the Keyboard Layout Services (these functions only exist since + * 10.2). + */ + + (*KLGetCurrentKeyboardLayoutPtr)(¤tLayout); + + if (currentLayout != NULL) { + + /* + * The layout pointer could in theory be the same for different + * layouts, only the id gives us the information that the + * keyboard has actually changed. OTOH the layout object can + * also change and it could still be the same layoutid. + */ + + (*KLGetKeyboardLayoutPropertyPtr)(currentLayout, kKLIdentifier, + (const void**)¤tLayoutId); + + if ((lastLayout != currentLayout) + || (lastLayoutId != currentLayoutId)) { + +#ifdef TK_MAC_DEBUG + fprintf (stderr, "GetKeyboardLayout(): Use KLS\n"); +#endif + + hasLayoutChanged = true; + + /* + * Reinitialize all relevant variables. + */ + + lastLayout = currentLayout; + lastLayoutId = currentLayoutId; + uchr = NULL; + KCHR = NULL; + + if (((*KLGetKeyboardLayoutPropertyPtr)(currentLayout, + kKLuchrData, (const void**)&uchr) + == noErr) + && (uchr != NULL)) { + /* done */ + } else if (((*KLGetKeyboardLayoutPropertyPtr)(currentLayout, + kKLKCHRData, (const void**)&KCHR) + == noErr) + && (KCHR != NULL)) { + /* done */ + } + } } - if ((NULL == uchrHnd) && (NULL == KCHRHnd)) { - initialized = false; - fprintf (stderr, - "GetKeyboardLayout(): " - "Can't get a keyboard layout for layout %d " - "(error code %d)?\n", - (int) keyLayoutID, (int) ResError()); - *resource = (Ptr) GetScriptManagerVariable(smKCHRCache); - fprintf (stderr, - "GetKeyboardLayout(): Trying the cache: %p\n", - *resource); - return 0; + + } else { + + /* + * Use the classic approach as shown in Apple code samples, loading + * the keyboard resources directly. This is broken for 10.3 and + * possibly already in 10.2. + */ + + currentLayoutId = GetScriptVariable(currentKeyScript,smScriptKeys); + + if ((lastLayout == NULL) || (lastLayoutId != currentLayoutId)) { + +#ifdef TK_MAC_DEBUG + fprintf (stderr, "GetKeyboardLayout(): Use GetResource()\n"); +#endif + + hasLayoutChanged = true; + + /* + * Reinitialize all relevant variables. + */ + + lastLayout = (KeyboardLayoutRef)-1; + lastLayoutId = currentLayoutId; + uchr = NULL; + KCHR = NULL; + + /* + * Get the new layout resource in the classic way. + */ + + if (handle != NULL) { + HUnlock(handle); + } + + if ((handle = GetResource('uchr',currentLayoutId)) != NULL) { + HLock(handle); + uchr = *handle; + } else if ((handle = GetResource('KCHR',currentLayoutId)) != NULL) { + HLock(handle); + KCHR = *handle; + } } } - if (NULL != uchrHnd) { - *resource = *uchrHnd; + if (hasLayoutChanged) { + +#ifdef TK_MAC_DEBUG + if (KCHR != NULL) { + fprintf (stderr, "GetKeyboardLayout(): New 'KCHR' layout %d\n", + (int) (short) currentLayoutId); + } else if (uchr != NULL) { + fprintf (stderr, "GetKeyboardLayout(): New 'uchr' layout %d\n", + (int) (short) currentLayoutId); + } else { + fprintf (stderr, "GetKeyboardLayout(): Use cached layout " + "(should have been %d)\n", + (int) (short) currentLayoutId); + } +#endif + + deadKeyStateUp = deadKeyStateDown = 0; + + /* + * If we did get a new 'KCHR', compute its encoding and put it into + * lastEncoding. + * + * If we didn't get a new 'KCHR' and if we have no 'uchr' either, get + * some 'KCHR' from the OS cache and leave lastEncoding at its + * current value. This should better not happen, it doesn't really + * work. + */ + + if (KCHR != NULL) { + lastEncoding = GetKCHREncoding(currentKeyScript, currentLayoutId); +#ifdef TK_MAC_DEBUG + fprintf (stderr, "GetKeyboardLayout(): New 'KCHR' encoding %lu " + "(%lu + 0x%lX)\n", + lastEncoding, lastEncoding & 0xFFFFL, + lastEncoding & ~0xFFFFL); +#endif + } else if (uchr == NULL) { + KCHR = (Ptr) GetScriptManagerVariable(smKCHRCache); + } + } + + if (uchr != NULL) { + *resourcePtr = uchr; return 1; } else { - *resource = *KCHRHnd; + *resourcePtr = KCHR; + *encodingPtr = lastEncoding; return 0; } } @@ -608,6 +803,87 @@ GetKeyboardLayout (Ptr * resource) /* *---------------------------------------------------------------------- * + * GetKCHREncoding -- + * + * Upgrade a WorldScript code to a TEC encoding based on the keyboard + * layout id. + * + * Results: + * The TEC code that corresponds best to the combination of WorldScript + * code and 'KCHR' id. + * + * Side effects: + * None. + * + * Rationale and Notes: + * WorldScript codes are sometimes not unique encodings. E.g. Icelandic + * uses script smRoman (0), but the actual encoding is + * kTextEncodingMacIcelandic (37). ftp://ftp.unicode.org/Public + * /MAPPINGS/VENDORS/APPLE/README.TXT has a good summary of these + * variants. So we need to upgrade the script to an encoding with + * GetTextEncodingFromScriptInfo(). + * + * 'KCHR' ids are usually region codes (see the comments in Script.h). + * Where they are not, we get a paramErr from the OS function and have + * appropriate fallbacks. + * + *---------------------------------------------------------------------- + */ + +static TextEncoding +GetKCHREncoding(ScriptCode script, SInt32 layoutid) +{ + RegionCode region = layoutid; + TextEncoding encoding = script; + + if (GetTextEncodingFromScriptInfo(script, kTextLanguageDontCare, region, + &encoding) == noErr) { + return encoding; + } + + /* + * GetTextEncodingFromScriptInfo() doesn't know about more exotic + * layouts. This provides a fallback for good measure. In an ideal + * world, exotic layouts would always provide a 'uchr' resource anyway, + * so we wouldn't need this. + * + * We can add more keyboard layouts, if we get actual complaints. Farsi + * or other Celtic/Gaelic layouts would be candidates. + */ + + switch (layoutid) { + + /* + * Icelandic and Faroese (planned). These layouts are sold by Apple + * Iceland for legacy applications. + */ + + case 1800: case 1821: + return kTextEncodingMacIcelandic; + + /* + * Irish and Welsh. These layouts are mentioned in . + * + * FIXME: This may have to be kTextEncodingMacGaelic instead, but I + * can't locate layouts of this type for testing. + */ + + case 581: case 779: + return kTextEncodingMacCeltic; + } + + /* + * The valid script codes are also the valid default encoding codes, so + * if nothing else helps, fall back on those. + */ + + return script; +} + + +/* + *---------------------------------------------------------------------- + * * KeycodeToUnicodeViaUnicodeResource -- * * Given MacOS key event data this function generates the Unicode @@ -728,7 +1004,7 @@ KeycodeToUnicodeViaUnicodeResource( static int KeycodeToUnicodeViaKCHRResource( UniChar * uniChars, int maxChars, - Ptr kchr, + Ptr kchr, TextEncoding encoding, EventKind eKind, UInt32 keycode, UInt32 modifiers, UInt32 * deadKeyStatePtr) @@ -797,19 +1073,32 @@ KeycodeToUnicodeViaKCHRResource( if (macStrLen <= 0) { return 0; } else { + /* * Use the CFString conversion routines. This is the easiest and * most compatible way to get from an 8-bit string and a MacOS script * code to a Unicode string. + * + * FIXME: The system ships with an Irish 'KCHR' but without the + * corresponding macCeltic encoding, which triggers the error below. + * Tcl doesn't have the macCeltic encoding either right now, so until + * we get that, we can just as well stick to this code. The right + * fix would be to use the Tcl encodings and add macCeltic and + * probably others there. Suitable Unicode data files for the + * missing encodings are available from www.evertype.com. */ - + CFStringRef cfString; int uniStrLen; cfString = CFStringCreateWithCStringNoCopy( - NULL, macStr, - GetScriptManagerVariable(smKeyScript), - kCFAllocatorNull); + NULL, macStr, encoding, kCFAllocatorNull); + if (cfString == NULL) { + fprintf(stderr, "CFString: Can't convert with encoding %d\n", + (int) encoding); + return 0; + } + uniStrLen = CFStringGetLength(cfString); if (uniStrLen > maxChars) { uniStrLen = maxChars; @@ -855,17 +1144,18 @@ TkMacOSXKeycodeToUnicode( UInt32 * deadKeyStatePtr) { Ptr resource = NULL; + TextEncoding encoding; int len; - if (GetKeyboardLayout(&resource)) { + if (GetKeyboardLayout(&resource,&encoding)) { len = KeycodeToUnicodeViaUnicodeResource( - uniChars, maxChars, resource, eKind, - keycode, modifiers, deadKeyStatePtr); + uniChars, maxChars, resource, eKind, + keycode, modifiers, deadKeyStatePtr); } else { len = KeycodeToUnicodeViaKCHRResource( - uniChars, maxChars, resource, eKind, - keycode, modifiers, deadKeyStatePtr); + uniChars, maxChars, resource, encoding, eKind, + keycode, modifiers, deadKeyStatePtr); } return len; -- cgit v0.12