summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXKeyEvent.c
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/tkMacOSXKeyEvent.c')
-rw-r--r--macosx/tkMacOSXKeyEvent.c386
1 files 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 <mach-o/dyld.h>
+
+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)(&currentLayout);
+
+ 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**)&currentLayoutId);
+
+ 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 <Script.h>.
+ *
+ * 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;