blob: 7b773e3347f3ee5cca3681358d3e2bab7e1f0cfc [file] [log] [blame]
/*
* 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);
WINE_DECLARE_DEBUG_CHANNEL(key);
/* Carbon-style modifier mask definitions from <Carbon/HIToolbox/Events.h>. */
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 <Carbon/HIToolbox/Events.h>. */
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 const struct {
DWORD vkey;
const char *name;
} vkey_names[] = {
{ VK_ADD, "Num +" },
{ VK_BACK, "Backspace" },
{ VK_CAPITAL, "Caps Lock" },
{ VK_CONTROL, "Ctrl" },
{ VK_DECIMAL, "Num Del" },
{ VK_DELETE | 0x100, "Delete" },
{ VK_DIVIDE | 0x100, "Num /" },
{ VK_DOWN | 0x100, "Down" },
{ VK_END | 0x100, "End" },
{ VK_ESCAPE, "Esc" },
{ VK_F1, "F1" },
{ VK_F2, "F2" },
{ VK_F3, "F3" },
{ VK_F4, "F4" },
{ VK_F5, "F5" },
{ VK_F6, "F6" },
{ VK_F7, "F7" },
{ VK_F8, "F8" },
{ VK_F9, "F9" },
{ VK_F10, "F10" },
{ VK_F11, "F11" },
{ VK_F12, "F12" },
{ VK_F13, "F13" },
{ VK_F14, "F14" },
{ VK_F15, "F15" },
{ VK_F16, "F16" },
{ VK_F17, "F17" },
{ VK_F18, "F18" },
{ VK_F19, "F19" },
{ VK_F20, "F20" },
{ VK_F21, "F21" },
{ VK_F22, "F22" },
{ VK_F23, "F23" },
{ VK_F24, "F24" },
{ VK_HELP | 0x100, "Help" },
{ VK_HOME | 0x100, "Home" },
{ VK_INSERT | 0x100, "Insert" },
{ VK_LCONTROL, "Ctrl" },
{ VK_LEFT | 0x100, "Left" },
{ VK_LMENU, "Alt" },
{ VK_LSHIFT, "Shift" },
{ VK_LWIN | 0x100, "Win" },
{ VK_MENU, "Alt" },
{ VK_MULTIPLY, "Num *" },
{ VK_NEXT | 0x100, "Page Down" },
{ VK_NUMLOCK | 0x100, "Num Lock" },
{ VK_NUMPAD0, "Num 0" },
{ VK_NUMPAD1, "Num 1" },
{ VK_NUMPAD2, "Num 2" },
{ VK_NUMPAD3, "Num 3" },
{ VK_NUMPAD4, "Num 4" },
{ VK_NUMPAD5, "Num 5" },
{ VK_NUMPAD6, "Num 6" },
{ VK_NUMPAD7, "Num 7" },
{ VK_NUMPAD8, "Num 8" },
{ VK_NUMPAD9, "Num 9" },
{ VK_OEM_CLEAR, "Num Clear" },
{ VK_OEM_NEC_EQUAL | 0x100, "Num =" },
{ VK_PRIOR | 0x100, "Page Up" },
{ VK_RCONTROL | 0x100, "Right Ctrl" },
{ VK_RETURN, "Return" },
{ VK_RETURN | 0x100, "Num Enter" },
{ VK_RIGHT | 0x100, "Right" },
{ VK_RMENU | 0x100, "Right Alt" },
{ VK_RSHIFT, "Right Shift" },
{ VK_RWIN | 0x100, "Right Win" },
{ VK_SEPARATOR, "Num ," },
{ VK_SHIFT, "Shift" },
{ VK_SPACE, "Space" },
{ VK_SUBTRACT, "Num -" },
{ VK_TAB, "Tab" },
{ VK_UP | 0x100, "Up" },
{ VK_VOLUME_DOWN | 0x100, "Volume Down" },
{ VK_VOLUME_MUTE | 0x100, "Mute" },
{ VK_VOLUME_UP | 0x100, "Volume Up" },
};
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;
}
/* Filter Apple-specific private-use characters (see NSEvent.h) out of a
* string. Returns the length of the string after stripping. */
static int strip_apple_private_chars(LPWSTR bufW, int len)
{
int i;
for (i = 0; i < len; )
{
if (0xF700 <= bufW[i] && bufW[i] <= 0xF8FF)
{
memmove(&bufW[i], &bufW[i+1], (len - i - 1) * sizeof(bufW[0]));
len--;
}
else
i++;
}
return len;
}
/***********************************************************************
* 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);
}
}
/***********************************************************************
* macdrv_send_keyboard_input
*/
static void macdrv_send_keyboard_input(HWND hwnd, WORD vkey, WORD scan, DWORD flags, DWORD time)
{
INPUT input;
TRACE_(key)("hwnd %p vkey=%04x scan=%04x flags=%04x\n", hwnd, vkey, scan, flags);
input.type = INPUT_KEYBOARD;
input.ki.wVk = vkey;
input.ki.wScan = scan;
input.ki.dwFlags = flags;
input.ki.time = time;
input.ki.dwExtraInfo = 0;
__wine_send_input(hwnd, &input);
}
/***********************************************************************
* macdrv_key_event
*
* Handler for KEY_PRESS and KEY_RELEASE events.
*/
void macdrv_key_event(HWND hwnd, const macdrv_event *event)
{
struct macdrv_thread_data *thread_data = macdrv_thread_data();
WORD vkey, scan;
DWORD flags;
TRACE_(key)("win %p/%p key %s keycode %hu modifiers 0x%08llx\n",
hwnd, event->window, (event->type == KEY_PRESS ? "press" : "release"),
event->key.keycode, event->key.modifiers);
thread_data->last_modifiers = event->key.modifiers;
if (event->key.keycode < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]))
{
vkey = thread_data->keyc2vkey[event->key.keycode];
scan = thread_data->keyc2scan[event->key.keycode];
}
else
vkey = scan = 0;
TRACE_(key)("keycode %hu converted to vkey 0x%X scan 0x%02x\n",
event->key.keycode, vkey, scan);
if (!vkey) return;
flags = 0;
if (event->type == KEY_RELEASE) flags |= KEYEVENTF_KEYUP;
if (scan & 0x100) flags |= KEYEVENTF_EXTENDEDKEY;
macdrv_send_keyboard_input(hwnd, vkey, scan & 0xff, flags, event->key.time_ms);
}
/***********************************************************************
* macdrv_keyboard_changed
*
* Handler for KEYBOARD_CHANGED events.
*/
void macdrv_keyboard_changed(const macdrv_event *event)
{
struct macdrv_thread_data *thread_data = macdrv_thread_data();
TRACE("new keyboard layout uchr data %p, type %u, iso %d\n", event->keyboard_changed.uchr,
event->keyboard_changed.keyboard_type, event->keyboard_changed.iso_keyboard);
if (thread_data->keyboard_layout_uchr)
CFRelease(thread_data->keyboard_layout_uchr);
thread_data->keyboard_layout_uchr = CFDataCreateCopy(NULL, event->keyboard_changed.uchr);
thread_data->keyboard_type = event->keyboard_changed.keyboard_type;
thread_data->iso_keyboard = event->keyboard_changed.iso_keyboard;
thread_data->dead_key_state = 0;
macdrv_compute_keyboard_layout(thread_data);
}
/***********************************************************************
* get_locale_keyboard_layout
*/
static HKL get_locale_keyboard_layout(void)
{
ULONG_PTR layout;
LANGID langid;
layout = GetUserDefaultLCID();
/*
* Microsoft Office expects this value to be something specific
* for Japanese and Korean Windows with an IME the value is 0xe001
* We should probably check to see if an IME exists and if so then
* set this word properly.
*/
langid = PRIMARYLANGID(LANGIDFROMLCID(layout));
if (langid == LANG_CHINESE || langid == LANG_JAPANESE || langid == LANG_KOREAN)
layout |= 0xe001 << 16; /* IME */
else
layout |= layout << 16;
return (HKL)layout;
}
/***********************************************************************
* match_keyboard_layout
*/
static BOOL match_keyboard_layout(HKL hkl)
{
const DWORD isIME = 0xE0000000;
HKL current_hkl = get_locale_keyboard_layout();
/* if the layout is an IME, only match the low word (LCID) */
if (((ULONG_PTR)hkl & isIME) == isIME)
return (LOWORD(hkl) == LOWORD(current_hkl));
else
return (hkl == current_hkl);
}
/***********************************************************************
* ActivateKeyboardLayout (MACDRV.@)
*/
HKL CDECL macdrv_ActivateKeyboardLayout(HKL hkl, UINT flags)
{
HKL oldHkl = 0;
struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
/* FIXME: Use Text Input Services or NSTextInputContext to actually
change the Mac keyboard input source. */
FIXME("hkl %p flags %04x: semi-stub!\n", hkl, flags);
if (flags & KLF_SETFORPROCESS)
{
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
FIXME("KLF_SETFORPROCESS not supported\n");
return 0;
}
if (flags)
FIXME("flags %x not supported\n",flags);
if (hkl == (HKL)HKL_NEXT || hkl == (HKL)HKL_PREV)
{
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
FIXME("HKL_NEXT and HKL_PREV not supported\n");
return 0;
}
if (!match_keyboard_layout(hkl))
{
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
FIXME("setting keyboard of different locales not supported\n");
return 0;
}
oldHkl = thread_data->active_keyboard_layout;
if (!oldHkl) oldHkl = get_locale_keyboard_layout();
thread_data->active_keyboard_layout = hkl;
return oldHkl;
}
/***********************************************************************
* Beep (MACDRV.@)
*/
void CDECL macdrv_Beep(void)
{
macdrv_beep();
}
/***********************************************************************
* GetKeyNameText (MACDRV.@)
*/
INT CDECL macdrv_GetKeyNameText(LONG lparam, LPWSTR buffer, INT size)
{
struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
int scan, keyc;
scan = (lparam >> 16) & 0x1FF;
for (keyc = 0; keyc < sizeof(thread_data->keyc2scan)/sizeof(thread_data->keyc2scan[0]); keyc++)
{
if (thread_data->keyc2scan[keyc] == scan)
{
static const WCHAR dead[] = {' ','d','e','a','d',0};
const UCKeyboardLayout *uchr;
UInt32 deadKeyState = 0;
UniCharCount len;
OSStatus status;
uchr = (const UCKeyboardLayout*)CFDataGetBytePtr(thread_data->keyboard_layout_uchr);
status = UCKeyTranslate(uchr, keyc, kUCKeyActionDisplay, 0, thread_data->keyboard_type,
0, &deadKeyState, size - 1, &len, (UniChar*)buffer);
if (status != noErr)
len = 0;
if (len && isgraphW(buffer[0]))
buffer[len] = 0;
else
{
DWORD vkey = thread_data->keyc2vkey[keyc];
int i;
if (scan & 0x100) vkey |= 0x100;
if (lparam & (1 << 25))
{
/* Caller doesn't care about distinctions between left and
right keys. */
switch (vkey)
{
case VK_LSHIFT:
case VK_RSHIFT:
vkey = VK_SHIFT; break;
case VK_LCONTROL:
case VK_RCONTROL:
vkey = VK_CONTROL; break;
case VK_LMENU:
case VK_RMENU:
vkey = VK_MENU; break;
}
}
len = 0;
for (i = 0; i < sizeof(vkey_names) / sizeof(vkey_names[0]); i++)
{
if (vkey_names[i].vkey == vkey)
{
len = MultiByteToWideChar(CP_UTF8, 0, vkey_names[i].name, -1, buffer, size);
if (len) len--;
break;
}
}
if (!len)
{
static const WCHAR format[] = {'K','e','y',' ','0','x','%','0','2','x',0};
snprintfW(buffer, size, format, vkey);
len = strlenW(buffer);
}
}
if (!len)
break;
if (status == noErr && deadKeyState)
{
lstrcpynW(buffer + len, dead, size - len);
len = strlenW(buffer);
}
TRACE("lparam 0x%08x -> %s\n", lparam, debugstr_w(buffer));
return len;
}
}
WARN("found no name for lparam 0x%08x\n", lparam);
return 0;
}
/***********************************************************************
* GetKeyboardLayout (MACDRV.@)
*/
HKL CDECL macdrv_GetKeyboardLayout(DWORD thread_id)
{
if (!thread_id || thread_id == GetCurrentThreadId())
{
struct macdrv_thread_data *thread_data = macdrv_thread_data();
if (thread_data && thread_data->active_keyboard_layout)
return thread_data->active_keyboard_layout;
}
else
FIXME("couldn't return keyboard layout for thread %04x\n", thread_id);
/* FIXME: Use TISGetInputSourceProperty() and kTISPropertyInputSourceLanguages
* to get input source language ID string. Use
* CFLocaleGetWindowsLocaleCodeFromLocaleIdentifier() to convert that
* to a Windows locale ID and from there to a layout handle.
*/
return get_locale_keyboard_layout();
}
/***********************************************************************
* GetKeyboardLayoutName (MACDRV.@)
*/
BOOL CDECL macdrv_GetKeyboardLayoutName(LPWSTR name)
{
static const WCHAR formatW[] = {'%','0','8','x',0};
DWORD layout;
layout = HandleToUlong(get_locale_keyboard_layout());
if (HIWORD(layout) == LOWORD(layout)) layout = LOWORD(layout);
sprintfW(name, formatW, layout);
TRACE("returning %s\n", debugstr_w(name));
return TRUE;
}
/***********************************************************************
* MapVirtualKeyEx (MACDRV.@)
*/
UINT CDECL macdrv_MapVirtualKeyEx(UINT wCode, UINT wMapType, HKL hkl)
{
struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
UINT ret = 0;
int keyc;
TRACE("wCode=0x%x, wMapType=%d, hkl %p\n", wCode, wMapType, hkl);
switch (wMapType)
{
case MAPVK_VK_TO_VSC: /* vkey-code to scan-code */
case MAPVK_VK_TO_VSC_EX:
switch (wCode)
{
case VK_SHIFT: wCode = VK_LSHIFT; break;
case VK_CONTROL: wCode = VK_LCONTROL; break;
case VK_MENU: wCode = VK_LMENU; break;
}
/* vkey -> keycode -> scan */
for (keyc = 0; keyc < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]); keyc++)
{
if (thread_data->keyc2vkey[keyc] == wCode)
{
ret = thread_data->keyc2scan[keyc] & 0xFF;
break;
}
}
break;
case MAPVK_VSC_TO_VK: /* scan-code to vkey-code */
case MAPVK_VSC_TO_VK_EX:
/* scan -> keycode -> vkey */
for (keyc = 0; keyc < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]); keyc++)
if ((thread_data->keyc2scan[keyc] & 0xFF) == (wCode & 0xFF))
{
ret = thread_data->keyc2vkey[keyc];
/* Only stop if it's not a numpad vkey; otherwise keep
looking for a potential better vkey. */
if (ret && (ret < VK_NUMPAD0 || VK_DIVIDE < ret))
break;
}
if (wMapType == MAPVK_VSC_TO_VK)
switch (ret)
{
case VK_LSHIFT:
case VK_RSHIFT:
ret = VK_SHIFT; break;
case VK_LCONTROL:
case VK_RCONTROL:
ret = VK_CONTROL; break;
case VK_LMENU:
case VK_RMENU:
ret = VK_MENU; break;
}
break;
case MAPVK_VK_TO_CHAR: /* vkey-code to character */
{
/* vkey -> keycode -> (UCKeyTranslate) wide char */
struct macdrv_thread_data *thread_data = macdrv_thread_data();
const UCKeyboardLayout *uchr;
UniChar s[10];
OSStatus status;
UInt32 deadKeyState;
UniCharCount len;
BOOL deadKey = FALSE;
if ((VK_PRIOR <= wCode && wCode <= VK_HELP) ||
(VK_F1 <= wCode && wCode <= VK_F24))
break;
if (!thread_data || !thread_data->keyboard_layout_uchr)
{
WARN("No keyboard layout uchr data\n");
break;
}
uchr = (const UCKeyboardLayout*)CFDataGetBytePtr(thread_data->keyboard_layout_uchr);
/* Find the Mac keycode corresponding to the vkey */
for (keyc = 0; keyc < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]); keyc++)
if (thread_data->keyc2vkey[keyc] == wCode) break;
if (keyc >= sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]))
{
WARN("Unknown virtual key %X\n", wCode);
break;
}
TRACE("Found keycode %u\n", keyc);
deadKeyState = 0;
status = UCKeyTranslate(uchr, keyc, kUCKeyActionDown, 0,
thread_data->keyboard_type, 0, &deadKeyState,
sizeof(s)/sizeof(s[0]), &len, s);
if (status == noErr && !len && deadKeyState)
{
deadKey = TRUE;
deadKeyState = 0;
status = UCKeyTranslate(uchr, keyc, kUCKeyActionDown, 0,
thread_data->keyboard_type, kUCKeyTranslateNoDeadKeysMask,
&deadKeyState, sizeof(s)/sizeof(s[0]), &len, s);
}
if (status == noErr && len)
ret = toupperW(s[0]) | (deadKey ? 0x80000000 : 0);
break;
}
default: /* reserved */
FIXME("Unknown wMapType %d\n", wMapType);
break;
}
TRACE("returning 0x%04x\n", ret);
return ret;
}
/***********************************************************************
* ToUnicodeEx (MACDRV.@)
*
* The ToUnicode function translates the specified virtual-key code and keyboard
* state to the corresponding Windows character or characters.
*
* If the specified key is a dead key, the return value is negative. Otherwise,
* it is one of the following values:
* Value Meaning
* -1 The specified virtual key is a dead-key. If possible, the
* non-combining form of the dead character is written to bufW.
* 0 The specified virtual key has no translation for the current
* state of the keyboard.
* 1 One Windows character was copied to the buffer.
* 2 or more Multiple characters were copied to the buffer. This usually
* happens when a dead-key character (accent or diacritic) stored
* in the keyboard layout cannot be composed with the specified
* virtual key to form a single character.
*
*/
INT CDECL macdrv_ToUnicodeEx(UINT virtKey, UINT scanCode, const BYTE *lpKeyState,
LPWSTR bufW, int bufW_size, UINT flags, HKL hkl)
{
struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
INT ret = 0;
int keyc;
BOOL is_menu = (flags & 0x1);
OSStatus status;
const UCKeyboardLayout *uchr;
UInt16 keyAction;
UInt32 modifierKeyState;
OptionBits options;
UInt32 deadKeyState, savedDeadKeyState;
UniCharCount len;
BOOL dead = FALSE;
TRACE_(key)("virtKey 0x%04x scanCode 0x%04x lpKeyState %p bufW %p bufW_size %d flags 0x%08x hkl %p\n",
virtKey, scanCode, lpKeyState, bufW, bufW_size, flags, hkl);
if (!virtKey)
goto done;
/* UCKeyTranslate, below, terminates a dead-key sequence if passed a
modifier key press. We want it to effectively ignore modifier key
presses. I think that one isn't supposed to call it at all for modifier
events (e.g. NSFlagsChanged or kEventRawKeyModifiersChanged), since they
are different event types than key up/down events. */
switch (virtKey)
{
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
case VK_CAPITAL:
case VK_LSHIFT:
case VK_RSHIFT:
case VK_LCONTROL:
case VK_RCONTROL:
case VK_LMENU:
case VK_RMENU:
goto done;
}
/* There are a number of key combinations for which Windows does not
produce characters, but Mac keyboard layouts may. Eat them. Do this
here to avoid the expense of UCKeyTranslate() but also because these
keys shouldn't terminate dead key sequences. */
if ((VK_PRIOR <= virtKey && virtKey <= VK_HELP) || (VK_F1 <= virtKey && virtKey <= VK_F24))
goto done;
/* Shift + <non-digit keypad keys>. */
if ((lpKeyState[VK_SHIFT] & 0x80) && VK_MULTIPLY <= virtKey && virtKey <= VK_DIVIDE)
goto done;
if (lpKeyState[VK_CONTROL] & 0x80)
{
/* Control-Tab, with or without other modifiers. */
if (virtKey == VK_TAB)
goto done;
/* Control-Shift-<key>, Control-Alt-<key>, and Control-Alt-Shift-<key>
for these keys. */
if ((lpKeyState[VK_SHIFT] & 0x80) || (lpKeyState[VK_MENU] & 0x80))
{
switch (virtKey)
{
case VK_CANCEL:
case VK_BACK:
case VK_ESCAPE:
case VK_SPACE:
case VK_RETURN:
goto done;
}
}
}
if (thread_data->keyboard_layout_uchr)
uchr = (const UCKeyboardLayout*)CFDataGetBytePtr(thread_data->keyboard_layout_uchr);
else
uchr = NULL;
keyAction = (scanCode & 0x8000) ? kUCKeyActionUp : kUCKeyActionDown;
modifierKeyState = 0;
if (lpKeyState[VK_SHIFT] & 0x80)
modifierKeyState |= (shiftKey >> 8);
if (lpKeyState[VK_CAPITAL] & 0x01)
modifierKeyState |= (alphaLock >> 8);
if (lpKeyState[VK_CONTROL] & 0x80)
modifierKeyState |= (controlKey >> 8);
if (lpKeyState[VK_MENU] & 0x80)
modifierKeyState |= (cmdKey >> 8);
if (thread_data->last_modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
modifierKeyState |= (optionKey >> 8);
/* Find the Mac keycode corresponding to the vkey */
for (keyc = 0; keyc < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]); keyc++)
if (thread_data->keyc2vkey[keyc] == virtKey) break;
if (keyc >= sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0]))
{
WARN_(key)("Unknown virtual key 0x%04x\n", virtKey);
goto done;
}
TRACE_(key)("Key code 0x%04x %s, faked modifiers = 0x%04x\n", keyc,
(keyAction == kUCKeyActionDown) ? "pressed" : "released", (unsigned)modifierKeyState);
if (is_menu)
{
options = kUCKeyTranslateNoDeadKeysMask;
deadKeyState = 0;
}
else
{
options = 0;
deadKeyState = thread_data->dead_key_state;
}
savedDeadKeyState = deadKeyState;
status = UCKeyTranslate(uchr, keyc, keyAction, modifierKeyState,
thread_data->keyboard_type, options, &deadKeyState, bufW_size,
&len, bufW);
if (status != noErr)
{
ERR_(key)("Couldn't translate keycode 0x%04x, status %ld\n", keyc, status);
goto done;
}
if (!is_menu)
thread_data->dead_key_state = deadKeyState;
if (len == 0 && deadKeyState)
{
/* Repeat the translation, but disabling dead-key generation to
learn which dead key it was. */
status = UCKeyTranslate(uchr, keyc, keyAction, modifierKeyState,
thread_data->keyboard_type, kUCKeyTranslateNoDeadKeysMask,
&savedDeadKeyState, bufW_size, &len, bufW);
if (status != noErr)
{
ERR_(key)("Couldn't translate keycode 0x%04x, status %ld\n", keyc, status);
goto done;
}
dead = TRUE;
}
if (len > 0)
len = strip_apple_private_chars(bufW, len);
if (dead && len > 0) ret = -1;
else ret = len;
/* Control-Return produces line feed instead of carriage return. */
if (ret > 0 && (lpKeyState[VK_CONTROL] & 0x80) && virtKey == VK_RETURN)
{
int i;
for (i = 0; i < len; i++)
if (bufW[i] == '\r')
bufW[i] = '\n';
}
done:
/* Null-terminate the buffer, if there's room. MSDN clearly states that the
caller must not assume this is done, but some programs (e.g. Audiosurf) do. */
if (1 <= ret && ret < bufW_size)
bufW[ret] = 0;
TRACE_(key)("returning %d / %s\n", ret, debugstr_wn(bufW, abs(ret)));
return ret;
}
/***********************************************************************
* VkKeyScanEx (MACDRV.@)
*
* Note: Windows ignores HKL parameter and uses current active layout instead
*/
SHORT CDECL macdrv_VkKeyScanEx(WCHAR wChar, HKL hkl)
{
struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
SHORT ret = -1;
int state;
const UCKeyboardLayout *uchr;
TRACE("%04x, %p\n", wChar, hkl);
uchr = (const UCKeyboardLayout*)CFDataGetBytePtr(thread_data->keyboard_layout_uchr);
if (!uchr)
{
TRACE("no keyboard layout UCHR data; returning -1\n");
return -1;
}
for (state = 0; state < 8; state++)
{
UInt32 modifierKeyState = 0;
int keyc;
if (state & 1)
modifierKeyState |= (shiftKey >> 8);
if ((state & 6) == 6)
modifierKeyState |= (optionKey >> 8);
else
{
if (state & 2)
modifierKeyState |= (controlKey >> 8);
if (state & 4)
modifierKeyState |= (cmdKey >> 8);
}
for (keyc = 0; keyc < sizeof(thread_data->keyc2vkey) / sizeof(thread_data->keyc2vkey[0]); keyc++)
{
UInt32 deadKeyState = 0;
UniChar uchar;
UniCharCount len;
OSStatus status;
if (!thread_data->keyc2vkey[keyc]) continue;
status = UCKeyTranslate(uchr, keyc, kUCKeyActionDown, modifierKeyState,
thread_data->keyboard_type, 0, &deadKeyState,
1, &len, &uchar);
if (status == noErr && len == 1 && uchar == wChar)
{
WORD vkey = thread_data->keyc2vkey[keyc];
ret = vkey | (state << 8);
if ((VK_NUMPAD0 <= vkey && vkey <= VK_DIVIDE) ||
keyc == kVK_ANSI_KeypadClear || keyc == kVK_ANSI_KeypadEnter ||
keyc == kVK_ANSI_KeypadEquals)
{
/* Keep searching for a non-numpad match, which is preferred. */
}
else
goto done;
}
}
}
done:
TRACE(" -> 0x%04x\n", ret);
return ret;
}