From b78eee3172da2c54c352590480da02e7f48a8575 Mon Sep 17 00:00:00 2001 From: Ken Thomases Date: Sun, 3 Feb 2013 17:20:11 -0600 Subject: [PATCH] winemac: Build a map from Mac virtual key codes to Win32 vkeys and scan codes based on Mac keyboard layout. --- dlls/winemac.drv/Makefile.in | 3 +- dlls/winemac.drv/cocoa_app.h | 4 + dlls/winemac.drv/cocoa_app.m | 34 ++ dlls/winemac.drv/keyboard.c | 663 ++++++++++++++++++++++++++++++++ dlls/winemac.drv/macdrv.h | 7 + dlls/winemac.drv/macdrv_cocoa.h | 4 + dlls/winemac.drv/macdrv_main.c | 5 + 7 files changed, 719 insertions(+), 1 deletion(-) create mode 100644 dlls/winemac.drv/keyboard.c diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in index c0bdbb79151..5b9def0c1a1 100644 --- a/dlls/winemac.drv/Makefile.in +++ b/dlls/winemac.drv/Makefile.in @@ -1,11 +1,12 @@ MODULE = winemac.drv IMPORTS = user32 gdi32 advapi32 -EXTRALIBS = -framework AppKit +EXTRALIBS = -framework AppKit -framework Carbon C_SRCS = \ display.c \ event.c \ gdi.c \ + keyboard.c \ macdrv_main.c \ mouse.c \ scroll.c \ diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index 1b254ee694e..36c45e73d6a 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -40,8 +40,12 @@ NSMutableArray* keyWindows; NSMutableSet* triedWindows; unsigned long windowFocusSerial; + + CGEventSourceKeyboardType keyboardType; } +@property (nonatomic) CGEventSourceKeyboardType keyboardType; + - (void) transformProcessToForeground; - (BOOL) registerEventQueue:(WineEventQueue*)queue; diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index 7b121851485..3751eef0b1d 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#import + #import "cocoa_app.h" #import "cocoa_event.h" #import "cocoa_window.h" @@ -28,6 +30,8 @@ int macdrv_err_on; @implementation WineApplication + @synthesize keyboardType; + - (id) init { self = [super init]; @@ -213,6 +217,8 @@ int macdrv_err_on; NSWindow* window = [note object]; [keyWindows removeObjectIdenticalTo:window]; }]; + + self.keyboardType = LMGetKbdType(); } @end @@ -270,3 +276,31 @@ void macdrv_window_rejected_focus(const macdrv_event *event) [NSApp windowRejectedFocusEvent:event]; }); } + +/*********************************************************************** + * macdrv_get_keyboard_layout + * + * Returns the keyboard layout uchr data. + */ +CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso) +{ + __block CFDataRef result = NULL; + + OnMainThread(^{ + TISInputSourceRef inputSource; + + inputSource = TISCopyCurrentKeyboardLayoutInputSource(); + if (inputSource) + { + CFDataRef uchr = TISGetInputSourceProperty(inputSource, + kTISPropertyUnicodeKeyLayoutData); + result = CFDataCreateCopy(NULL, uchr); + CFRelease(inputSource); + + *keyboard_type = ((WineApplication*)NSApp).keyboardType; + *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO); + } + }); + + return result; +} diff --git a/dlls/winemac.drv/keyboard.c b/dlls/winemac.drv/keyboard.c new file mode 100644 index 00000000000..d81cdd2aa24 --- /dev/null +++ b/dlls/winemac.drv/keyboard.c @@ -0,0 +1,663 @@ +/* + * MACDRV keyboard driver + * + * Copyright 1993 Bob Amstadt + * Copyright 1996 Albrecht Kleine + * Copyright 1997 David Faure + * Copyright 1998 Morten Welinder + * Copyright 1998 Ulrich Weigand + * Copyright 1999 Ove Kåven + * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "config.h" + +#include "macdrv.h" +#include "winuser.h" +#include "wine/unicode.h" + +WINE_DEFAULT_DEBUG_CHANNEL(keyboard); + + +/* Carbon-style modifier mask definitions from . */ +enum { + cmdKeyBit = 8, + shiftKeyBit = 9, + alphaLockBit = 10, + optionKeyBit = 11, + controlKeyBit = 12, +}; + +enum { + cmdKey = 1 << cmdKeyBit, + shiftKey = 1 << shiftKeyBit, + alphaLock = 1 << alphaLockBit, + optionKey = 1 << optionKeyBit, + controlKey = 1 << controlKeyBit, +}; + + +/* Mac virtual key code definitions from . */ +enum { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ISO_Section = 0x0A, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_Return = 0x24, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_ANSI_Grave = 0x32, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_RightCommand = 0x36, /* invented for Wine; co-opt unused key code */ + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_F20 = 0x5A, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C, + kVK_JIS_Yen = 0x5D, + kVK_JIS_Underscore = 0x5E, + kVK_JIS_KeypadComma = 0x5F, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_JIS_Eisu = 0x66, + kVK_F11 = 0x67, + kVK_JIS_Kana = 0x68, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E, +}; + + +/* Indexed by Mac virtual keycode values defined above. */ +static const struct { + WORD vkey; + WORD scan; + BOOL fixed; +} default_map[128] = { + { 'A', 0x1E, FALSE }, /* kVK_ANSI_A */ + { 'S', 0x1F, FALSE }, /* kVK_ANSI_S */ + { 'D', 0x20, FALSE }, /* kVK_ANSI_D */ + { 'F', 0x21, FALSE }, /* kVK_ANSI_F */ + { 'H', 0x23, FALSE }, /* kVK_ANSI_H */ + { 'G', 0x22, FALSE }, /* kVK_ANSI_G */ + { 'Z', 0x2C, FALSE }, /* kVK_ANSI_Z */ + { 'X', 0x2D, FALSE }, /* kVK_ANSI_X */ + { 'C', 0x2E, FALSE }, /* kVK_ANSI_C */ + { 'V', 0x2F, FALSE }, /* kVK_ANSI_V */ + { VK_OEM_102, 0x56, TRUE }, /* kVK_ISO_Section */ + { 'B', 0x30, FALSE }, /* kVK_ANSI_B */ + { 'Q', 0x10, FALSE }, /* kVK_ANSI_Q */ + { 'W', 0x11, FALSE }, /* kVK_ANSI_W */ + { 'E', 0x12, FALSE }, /* kVK_ANSI_E */ + { 'R', 0x13, FALSE }, /* kVK_ANSI_R */ + { 'Y', 0x15, FALSE }, /* kVK_ANSI_Y */ + { 'T', 0x14, FALSE }, /* kVK_ANSI_T */ + { '1', 0x02, FALSE }, /* kVK_ANSI_1 */ + { '2', 0x03, FALSE }, /* kVK_ANSI_2 */ + { '3', 0x04, FALSE }, /* kVK_ANSI_3 */ + { '4', 0x05, FALSE }, /* kVK_ANSI_4 */ + { '6', 0x07, FALSE }, /* kVK_ANSI_6 */ + { '5', 0x06, FALSE }, /* kVK_ANSI_5 */ + { VK_OEM_PLUS, 0x0D, FALSE }, /* kVK_ANSI_Equal */ + { '9', 0x0A, FALSE }, /* kVK_ANSI_9 */ + { '7', 0x08, FALSE }, /* kVK_ANSI_7 */ + { VK_OEM_MINUS, 0x0C, FALSE }, /* kVK_ANSI_Minus */ + { '8', 0x09, FALSE }, /* kVK_ANSI_8 */ + { '0', 0x0B, FALSE }, /* kVK_ANSI_0 */ + { VK_OEM_6, 0x1B, FALSE }, /* kVK_ANSI_RightBracket */ + { 'O', 0x18, FALSE }, /* kVK_ANSI_O */ + { 'U', 0x16, FALSE }, /* kVK_ANSI_U */ + { VK_OEM_4, 0x1A, FALSE }, /* kVK_ANSI_LeftBracket */ + { 'I', 0x17, FALSE }, /* kVK_ANSI_I */ + { 'P', 0x19, FALSE }, /* kVK_ANSI_P */ + { VK_RETURN, 0x1C, TRUE }, /* kVK_Return */ + { 'L', 0x26, FALSE }, /* kVK_ANSI_L */ + { 'J', 0x24, FALSE }, /* kVK_ANSI_J */ + { VK_OEM_7, 0x28, FALSE }, /* kVK_ANSI_Quote */ + { 'K', 0x25, FALSE }, /* kVK_ANSI_K */ + { VK_OEM_1, 0x27, FALSE }, /* kVK_ANSI_Semicolon */ + { VK_OEM_5, 0x2B, FALSE }, /* kVK_ANSI_Backslash */ + { VK_OEM_COMMA, 0x33, FALSE }, /* kVK_ANSI_Comma */ + { VK_OEM_2, 0x35, FALSE }, /* kVK_ANSI_Slash */ + { 'N', 0x31, FALSE }, /* kVK_ANSI_N */ + { 'M', 0x32, FALSE }, /* kVK_ANSI_M */ + { VK_OEM_PERIOD, 0x34, FALSE }, /* kVK_ANSI_Period */ + { VK_TAB, 0x0F, TRUE }, /* kVK_Tab */ + { VK_SPACE, 0x39, TRUE }, /* kVK_Space */ + { VK_OEM_3, 0x29, FALSE }, /* kVK_ANSI_Grave */ + { VK_BACK, 0x0E, TRUE }, /* kVK_Delete */ + { 0, 0, FALSE }, /* 0x34 unused */ + { VK_ESCAPE, 0x01, TRUE }, /* kVK_Escape */ + { VK_RMENU, 0x38 | 0x100, TRUE }, /* kVK_RightCommand */ + { VK_LMENU, 0x38, TRUE }, /* kVK_Command */ + { VK_LSHIFT, 0x2A, TRUE }, /* kVK_Shift */ + { VK_CAPITAL, 0x3A, TRUE }, /* kVK_CapsLock */ + { 0, 0, FALSE }, /* kVK_Option */ + { VK_LCONTROL, 0x1D, TRUE }, /* kVK_Control */ + { VK_RSHIFT, 0x36, TRUE }, /* kVK_RightShift */ + { 0, 0, FALSE }, /* kVK_RightOption */ + { VK_RCONTROL, 0x1D | 0x100, TRUE }, /* kVK_RightControl */ + { 0, 0, FALSE }, /* kVK_Function */ + { VK_F17, 0x68, TRUE }, /* kVK_F17 */ + { VK_DECIMAL, 0x53, TRUE }, /* kVK_ANSI_KeypadDecimal */ + { 0, 0, FALSE }, /* 0x42 unused */ + { VK_MULTIPLY, 0x37, TRUE }, /* kVK_ANSI_KeypadMultiply */ + { 0, 0, FALSE }, /* 0x44 unused */ + { VK_ADD, 0x4E, TRUE }, /* kVK_ANSI_KeypadPlus */ + { 0, 0, FALSE }, /* 0x46 unused */ + { VK_OEM_CLEAR, 0x59, TRUE }, /* kVK_ANSI_KeypadClear */ + { VK_VOLUME_UP, 0 | 0x100, TRUE }, /* kVK_VolumeUp */ + { VK_VOLUME_DOWN, 0 | 0x100, TRUE }, /* kVK_VolumeDown */ + { VK_VOLUME_MUTE, 0 | 0x100, TRUE }, /* kVK_Mute */ + { VK_DIVIDE, 0x35 | 0x100, TRUE }, /* kVK_ANSI_KeypadDivide */ + { VK_RETURN, 0x1C | 0x100, TRUE }, /* kVK_ANSI_KeypadEnter */ + { 0, 0, FALSE }, /* 0x4D unused */ + { VK_SUBTRACT, 0x4A, TRUE }, /* kVK_ANSI_KeypadMinus */ + { VK_F18, 0x69, TRUE }, /* kVK_F18 */ + { VK_F19, 0x6A, TRUE }, /* kVK_F19 */ + { VK_OEM_NEC_EQUAL, 0x0D | 0x100, TRUE }, /* kVK_ANSI_KeypadEquals */ + { VK_NUMPAD0, 0x52, TRUE }, /* kVK_ANSI_Keypad0 */ + { VK_NUMPAD1, 0x4F, TRUE }, /* kVK_ANSI_Keypad1 */ + { VK_NUMPAD2, 0x50, TRUE }, /* kVK_ANSI_Keypad2 */ + { VK_NUMPAD3, 0x51, TRUE }, /* kVK_ANSI_Keypad3 */ + { VK_NUMPAD4, 0x4B, TRUE }, /* kVK_ANSI_Keypad4 */ + { VK_NUMPAD5, 0x4C, TRUE }, /* kVK_ANSI_Keypad5 */ + { VK_NUMPAD6, 0x4D, TRUE }, /* kVK_ANSI_Keypad6 */ + { VK_NUMPAD7, 0x47, TRUE }, /* kVK_ANSI_Keypad7 */ + { VK_F20, 0x6B, TRUE }, /* kVK_F20 */ + { VK_NUMPAD8, 0x48, TRUE }, /* kVK_ANSI_Keypad8 */ + { VK_NUMPAD9, 0x49, TRUE }, /* kVK_ANSI_Keypad9 */ + { 0xFF, 0x7D, TRUE }, /* kVK_JIS_Yen */ + { 0xC1, 0x73, TRUE }, /* kVK_JIS_Underscore */ + { VK_SEPARATOR, 0x7E, TRUE }, /* kVK_JIS_KeypadComma */ + { VK_F5, 0x3F, TRUE }, /* kVK_F5 */ + { VK_F6, 0x40, TRUE }, /* kVK_F6 */ + { VK_F7, 0x41, TRUE }, /* kVK_F7 */ + { VK_F3, 0x3D, TRUE }, /* kVK_F3 */ + { VK_F8, 0x42, TRUE }, /* kVK_F8 */ + { VK_F9, 0x43, TRUE }, /* kVK_F9 */ + { 0xFF, 0x72, TRUE }, /* kVK_JIS_Eisu */ + { VK_F11, 0x57, TRUE }, /* kVK_F11 */ + { VK_OEM_RESET, 0x71, TRUE }, /* kVK_JIS_Kana */ + { VK_F13, 0x64, TRUE }, /* kVK_F13 */ + { VK_F16, 0x67, TRUE }, /* kVK_F16 */ + { VK_F14, 0x65, TRUE }, /* kVK_F14 */ + { 0, 0, FALSE }, /* 0x6C unused */ + { VK_F10, 0x44, TRUE }, /* kVK_F10 */ + { 0, 0, FALSE }, /* 0x6E unused */ + { VK_F12, 0x58, TRUE }, /* kVK_F12 */ + { 0, 0, FALSE }, /* 0x70 unused */ + { VK_F15, 0x66, TRUE }, /* kVK_F15 */ + { VK_INSERT, 0x52 | 0x100, TRUE }, /* kVK_Help */ /* map to Insert */ + { VK_HOME, 0x47 | 0x100, TRUE }, /* kVK_Home */ + { VK_PRIOR, 0x49 | 0x100, TRUE }, /* kVK_PageUp */ + { VK_DELETE, 0x53 | 0x100, TRUE }, /* kVK_ForwardDelete */ + { VK_F4, 0x3E, TRUE }, /* kVK_F4 */ + { VK_END, 0x4F | 0x100, TRUE }, /* kVK_End */ + { VK_F2, 0x3C, TRUE }, /* kVK_F2 */ + { VK_NEXT, 0x51 | 0x100, TRUE }, /* kVK_PageDown */ + { VK_F1, 0x3B, TRUE }, /* kVK_F1 */ + { VK_LEFT, 0x4B | 0x100, TRUE }, /* kVK_LeftArrow */ + { VK_RIGHT, 0x4D | 0x100, TRUE }, /* kVK_RightArrow */ + { VK_DOWN, 0x50 | 0x100, TRUE }, /* kVK_DownArrow */ + { VK_UP, 0x48 | 0x100, TRUE }, /* kVK_UpArrow */ +}; + + +static BOOL char_matches_string(WCHAR wchar, UniChar *string, BOOL ignore_diacritics) +{ + BOOL ret; + CFStringRef s1 = CFStringCreateWithCharactersNoCopy(NULL, (UniChar*)&wchar, 1, kCFAllocatorNull); + CFStringRef s2 = CFStringCreateWithCharactersNoCopy(NULL, string, strlenW(string), kCFAllocatorNull); + CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareWidthInsensitive; + if (ignore_diacritics) + flags |= kCFCompareDiacriticInsensitive; + ret = (CFStringCompare(s1, s2, flags) == kCFCompareEqualTo); + CFRelease(s1); + CFRelease(s2); + return ret; +} + + +/*********************************************************************** + * macdrv_compute_keyboard_layout + */ +void macdrv_compute_keyboard_layout(struct macdrv_thread_data *thread_data) +{ + int keyc; + WCHAR vkey; + const UCKeyboardLayout *uchr; + const UInt32 modifier_combos[] = { + 0, + shiftKey >> 8, + cmdKey >> 8, + (shiftKey | cmdKey) >> 8, + optionKey >> 8, + (shiftKey | optionKey) >> 8, + }; + UniChar map[128][sizeof(modifier_combos) / sizeof(modifier_combos[0])][4 + 1]; + int combo; + BYTE vkey_used[256]; + int ignore_diacritics; + static const struct { + WCHAR wchar; + DWORD vkey; + } symbol_vkeys[] = { + { '-', VK_OEM_MINUS }, + { '+', VK_OEM_PLUS }, + { '_', VK_OEM_MINUS }, + { ',', VK_OEM_COMMA }, + { '.', VK_OEM_PERIOD }, + { '=', VK_OEM_PLUS }, + { '>', VK_OEM_PERIOD }, + { '<', VK_OEM_COMMA }, + { '|', VK_OEM_5 }, + { '\\', VK_OEM_5 }, + { '`', VK_OEM_3 }, + { '[', VK_OEM_4 }, + { '~', VK_OEM_3 }, + { '?', VK_OEM_2 }, + { ']', VK_OEM_6 }, + { '/', VK_OEM_2 }, + { ':', VK_OEM_1 }, + { '}', VK_OEM_6 }, + { '{', VK_OEM_4 }, + { ';', VK_OEM_1 }, + { '\'', VK_OEM_7 }, + { ':', VK_OEM_PERIOD }, + { ';', VK_OEM_COMMA }, + { '"', VK_OEM_7 }, + { 0x00B4, VK_OEM_4 }, /* 0x00B4 is ACUTE ACCENT */ + { '\'', VK_OEM_2 }, + { 0x00A7, VK_OEM_5 }, /* 0x00A7 is SECTION SIGN */ + { '*', VK_OEM_PLUS }, + { 0x00B4, VK_OEM_7 }, + { '`', VK_OEM_4 }, + { '[', VK_OEM_6 }, + { '/', VK_OEM_5 }, + { '^', VK_OEM_6 }, + { '*', VK_OEM_2 }, + { '{', VK_OEM_6 }, + { '~', VK_OEM_1 }, + { '?', VK_OEM_PLUS }, + { '?', VK_OEM_4 }, + { 0x00B4, VK_OEM_3 }, + { '?', VK_OEM_COMMA }, + { '~', VK_OEM_PLUS }, + { ']', VK_OEM_4 }, + { '\'', VK_OEM_3 }, + { 0x00A7, VK_OEM_7 }, + }; + int i; + + /* Vkeys that are suitable for assigning to arbitrary keys, organized in + contiguous ranges. */ + static const struct { + WORD first, last; + } vkey_ranges[] = { + { 'A', 'Z' }, + { '0', '9' }, + { VK_OEM_1, VK_OEM_3 }, + { VK_OEM_4, VK_ICO_CLEAR }, + { 0xe9, 0xf5 }, + { VK_OEM_NEC_EQUAL, VK_OEM_NEC_EQUAL }, + { VK_F1, VK_F24 }, + { 0, 0 } + }; + int vkey_range; + + if (!thread_data->keyboard_layout_uchr) + { + ERR("no keyboard layout UCHR data\n"); + return; + } + + memset(thread_data->keyc2vkey, 0, sizeof(thread_data->keyc2vkey)); + memset(vkey_used, 0, sizeof(vkey_used)); + + for (keyc = 0; keyc < sizeof(default_map) / sizeof(default_map[0]); keyc++) + { + thread_data->keyc2scan[keyc] = default_map[keyc].scan; + if (default_map[keyc].fixed) + { + vkey = default_map[keyc].vkey; + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (fixed)\n", keyc, vkey); + } + } + + if (thread_data->iso_keyboard) + { + /* In almost all cases, the Mac key codes indicate a physical key position + and this corresponds nicely to Win32 scan codes. However, the Mac key + codes differ in one case between ANSI and ISO keyboards. For ANSI + keyboards, the key to the left of the digits and above the Tab key + produces key code kVK_ANSI_Grave. For ISO keyboards, the key in that + some position produces kVK_ISO_Section. The additional key on ISO + keyboards, the one to the right of the left Shift key, produces + kVK_ANSI_Grave, which is just weird. + + Since we want the key in that upper left corner to always produce the + same scan code (0x29), we need to swap the scan codes of those two + Mac key codes for ISO keyboards. */ + DWORD temp = thread_data->keyc2scan[kVK_ANSI_Grave]; + thread_data->keyc2scan[kVK_ANSI_Grave] = thread_data->keyc2scan[kVK_ISO_Section]; + thread_data->keyc2scan[kVK_ISO_Section] = temp; + } + + uchr = (const UCKeyboardLayout*)CFDataGetBytePtr(thread_data->keyboard_layout_uchr); + + /* Using the keyboard layout, build a map of key code + modifiers -> characters. */ + memset(map, 0, sizeof(map)); + for (keyc = 0; keyc < sizeof(map) / sizeof(map[0]); keyc++) + { + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc]) continue; /* assigned a fixed vkey */ + + TRACE("keyc 0x%04x: ", keyc); + + for (combo = 0; combo < sizeof(modifier_combos) / sizeof(modifier_combos[0]); combo++) + { + UInt32 deadKeyState; + UniCharCount len; + OSStatus status; + + deadKeyState = 0; + status = UCKeyTranslate(uchr, keyc, kUCKeyActionDown, modifier_combos[combo], + thread_data->keyboard_type, kUCKeyTranslateNoDeadKeysMask, + &deadKeyState, sizeof(map[keyc][combo])/sizeof(map[keyc][combo][0]) - 1, + &len, map[keyc][combo]); + if (status != noErr) + map[keyc][combo][0] = 0; + + TRACE("%s%s", (combo ? ", " : ""), debugstr_w(map[keyc][combo])); + } + + TRACE("\n"); + } + + /* First try to match key codes to the vkeys for the letters A through Z. + Try unmodified first, then with various modifier combinations in succession. + On the first pass, try to get a match lacking diacritical marks. On the + second pass, accept matches with diacritical marks. */ + for (ignore_diacritics = 0; ignore_diacritics <= 1; ignore_diacritics++) + { + for (combo = 0; combo < sizeof(modifier_combos) / sizeof(modifier_combos[0]); combo++) + { + for (vkey = 'A'; vkey <= 'Z'; vkey++) + { + if (vkey_used[vkey]) + continue; + + for (keyc = 0; keyc < sizeof(map) / sizeof(map[0]); keyc++) + { + if (thread_data->keyc2vkey[keyc] || !map[keyc][combo][0]) + continue; + + if (char_matches_string(vkey, map[keyc][combo], ignore_diacritics)) + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (%s match %s)\n", keyc, vkey, + debugstr_wn(&vkey, 1), debugstr_w(map[keyc][combo])); + break; + } + } + } + } + } + + /* Next try to match key codes to the vkeys for the digits 0 through 9. */ + for (combo = 0; combo < sizeof(modifier_combos) / sizeof(modifier_combos[0]); combo++) + { + for (vkey = '0'; vkey <= '9'; vkey++) + { + if (vkey_used[vkey]) + continue; + + for (keyc = 0; keyc < sizeof(map) / sizeof(map[0]); keyc++) + { + if (thread_data->keyc2vkey[keyc] || !map[keyc][combo][0]) + continue; + + if (char_matches_string(vkey, map[keyc][combo], FALSE)) + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (%s match %s)\n", keyc, vkey, + debugstr_wn(&vkey, 1), debugstr_w(map[keyc][combo])); + break; + } + } + } + } + + /* Now try to match key codes for certain common punctuation characters to + the most common OEM vkeys (e.g. '.' to VK_OEM_PERIOD). */ + for (i = 0; i < sizeof(symbol_vkeys) / sizeof(symbol_vkeys[0]); i++) + { + vkey = symbol_vkeys[i].vkey; + + if (vkey_used[vkey]) + continue; + + for (combo = 0; combo < sizeof(modifier_combos) / sizeof(modifier_combos[0]); combo++) + { + for (keyc = 0; keyc < sizeof(map) / sizeof(map[0]); keyc++) + { + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc] || !map[keyc][combo][0]) + continue; + + if (char_matches_string(symbol_vkeys[i].wchar, map[keyc][combo], FALSE)) + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (%s match %s)\n", keyc, vkey, + debugstr_wn(&symbol_vkeys[i].wchar, 1), debugstr_w(map[keyc][combo])); + break; + } + } + + if (vkey_used[vkey]) + break; + } + } + + /* For those key codes still without a vkey, try to use the default vkey + from the default map, if it's still available. */ + for (keyc = 0; keyc < sizeof(default_map) / sizeof(default_map[0]); keyc++) + { + DWORD vkey = default_map[keyc].vkey; + + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc]) continue; /* already assigned */ + + if (!vkey_used[vkey]) + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (default map)\n", keyc, vkey); + } + } + + /* For any unassigned key codes which would map to a letter in the default + map, but whose normal letter vkey wasn't available, try to find a + different letter. */ + vkey = 'A'; + for (keyc = 0; keyc < sizeof(default_map) / sizeof(default_map[0]); keyc++) + { + if (default_map[keyc].vkey < 'A' || 'Z' < default_map[keyc].vkey) + continue; /* not a letter in ANSI layout */ + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc]) continue; /* already assigned */ + + while (vkey <= 'Z' && vkey_used[vkey]) vkey++; + if (vkey <= 'Z') + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (spare letter)\n", keyc, vkey); + } + else + break; /* no more unused letter vkeys, so stop trying */ + } + + /* Same thing but with the digits. */ + vkey = '0'; + for (keyc = 0; keyc < sizeof(default_map) / sizeof(default_map[0]); keyc++) + { + if (default_map[keyc].vkey < '0' || '9' < default_map[keyc].vkey) + continue; /* not a digit in ANSI layout */ + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc]) continue; /* already assigned */ + + while (vkey <= '9' && vkey_used[vkey]) vkey++; + if (vkey <= '9') + { + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (spare digit)\n", keyc, vkey); + } + else + break; /* no more unused digit vkeys, so stop trying */ + } + + /* Last chance. Assign any available vkey. */ + vkey_range = 0; + vkey = vkey_ranges[vkey_range].first; + for (keyc = 0; keyc < sizeof(default_map) / sizeof(default_map[0]); keyc++) + { + if (!thread_data->keyc2scan[keyc]) continue; /* not a known Mac key code */ + if (thread_data->keyc2vkey[keyc]) continue; /* already assigned */ + + while (vkey && vkey_used[vkey]) + { + if (vkey == vkey_ranges[vkey_range].last) + { + vkey_range++; + vkey = vkey_ranges[vkey_range].first; + } + else + vkey++; + } + + if (!vkey) + { + WARN("No more vkeys available!\n"); + break; + } + + thread_data->keyc2vkey[keyc] = vkey; + vkey_used[vkey] = 1; + TRACE("keyc 0x%04x -> vkey 0x%04x (spare vkey)\n", keyc, vkey); + } +} diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index b88fe282a76..6b50b3d3a7e 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -84,6 +84,11 @@ struct macdrv_thread_data { macdrv_event_queue queue; const macdrv_event *current_event; + CFDataRef keyboard_layout_uchr; + CGEventSourceKeyboardType keyboard_type; + int iso_keyboard; + WORD keyc2vkey[128]; + WORD keyc2scan[128]; }; extern DWORD thread_data_tls_index DECLSPEC_HIDDEN; @@ -128,4 +133,6 @@ extern void macdrv_window_did_unminimize(HWND hwnd) DECLSPEC_HIDDEN; extern void macdrv_mouse_button(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN; +extern void macdrv_compute_keyboard_layout(struct macdrv_thread_data *thread_data) DECLSPEC_HIDDEN; + #endif /* __WINE_MACDRV_H */ diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index c8d32f127ce..61dae5049fe 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -219,4 +219,8 @@ extern void macdrv_clear_window_color_key(macdrv_window w) DECLSPEC_HIDDEN; extern void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha) DECLSPEC_HIDDEN; extern void macdrv_give_cocoa_window_focus(macdrv_window w) DECLSPEC_HIDDEN; + +/* keyboard */ +extern CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso) DECLSPEC_HIDDEN; + #endif /* __WINE_MACDRV_COCOA_H */ diff --git a/dlls/winemac.drv/macdrv_main.c b/dlls/winemac.drv/macdrv_main.c index a21390d872f..5390a9e2fb9 100644 --- a/dlls/winemac.drv/macdrv_main.c +++ b/dlls/winemac.drv/macdrv_main.c @@ -60,6 +60,8 @@ static void thread_detach(void) if (data) { macdrv_destroy_event_queue(data->queue); + if (data->keyboard_layout_uchr) + CFRelease(data->keyboard_layout_uchr); HeapFree(GetProcessHeap(), 0, data); } } @@ -116,6 +118,9 @@ struct macdrv_thread_data *macdrv_init_thread_data(void) ExitProcess(1); } + data->keyboard_layout_uchr = macdrv_copy_keyboard_layout(&data->keyboard_type, &data->iso_keyboard); + macdrv_compute_keyboard_layout(data); + set_queue_display_fd(macdrv_get_event_queue_fd(data->queue)); TlsSetValue(thread_data_tls_index, data);