diff options
author | Philip Zander <philip.zander@gmail.com> | 2022-01-05 15:44:49 +0100 |
---|---|---|
committer | Luca Bacci <luca.bacci982@gmail.com> | 2022-01-12 19:01:35 +0100 |
commit | ea65abc7e2cd056a2c562073664b05faddaf4fad (patch) | |
tree | c6c3c25aa3939a0b20554f7eca6279479c0a511c /gdk/win32 | |
parent | 21d40fc0386794a5c4991ce8f5eeba96cdf23884 (diff) | |
download | gtk+-ea65abc7e2cd056a2c562073664b05faddaf4fad.tar.gz |
Rewrite GdkWin32Keymap (load table directly from layout DLL)
The old code used repeated calls to `ToUnicodeEx` to populate
the translation table, which is slow and buggy. The new code
directly loads the layout driver DLLs from Windows.
See https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4338
Diffstat (limited to 'gdk/win32')
-rw-r--r-- | gdk/win32/gdkdisplay-win32.c | 51 | ||||
-rw-r--r-- | gdk/win32/gdkdisplay-win32.h | 8 | ||||
-rw-r--r-- | gdk/win32/gdkevents-win32.c | 28 | ||||
-rw-r--r-- | gdk/win32/gdkkeys-win32-impl-wow64.c | 26 | ||||
-rw-r--r-- | gdk/win32/gdkkeys-win32-impl.c | 548 | ||||
-rw-r--r-- | gdk/win32/gdkkeys-win32.c | 1828 | ||||
-rw-r--r-- | gdk/win32/gdkkeys-win32.h | 168 | ||||
-rw-r--r-- | gdk/win32/gdkprivate-win32.h | 10 | ||||
-rw-r--r-- | gdk/win32/meson.build | 2 |
9 files changed, 1378 insertions, 1291 deletions
diff --git a/gdk/win32/gdkdisplay-win32.c b/gdk/win32/gdkdisplay-win32.c index d4a275ec7f..675e1af5e2 100644 --- a/gdk/win32/gdkdisplay-win32.c +++ b/gdk/win32/gdkdisplay-win32.c @@ -945,38 +945,71 @@ _gdk_win32_enable_hidpi (GdkWin32Display *display) } } -static void -_gdk_win32_check_on_arm64 (GdkWin32Display *display) +gboolean +_gdk_win32_check_processor (GdkWin32ProcessorCheckType check_type) { static gsize checked = 0; + static gboolean is_arm64 = FALSE; + static gboolean is_wow64 = FALSE; if (g_once_init_enter (&checked)) { + gboolean fallback_wow64_check = FALSE; HMODULE kernel32 = LoadLibraryW (L"kernel32.dll"); if (kernel32 != NULL) { - display->cpu_funcs.isWow64Process2 = + typedef BOOL (WINAPI *funcIsWow64Process2) (HANDLE, USHORT *, USHORT *); + + funcIsWow64Process2 isWow64Process2 = (funcIsWow64Process2) GetProcAddress (kernel32, "IsWow64Process2"); - if (display->cpu_funcs.isWow64Process2 != NULL) + if (isWow64Process2 != NULL) { USHORT proc_cpu = 0; USHORT native_cpu = 0; - display->cpu_funcs.isWow64Process2 (GetCurrentProcess (), - &proc_cpu, - &native_cpu); + isWow64Process2 (GetCurrentProcess (), &proc_cpu, &native_cpu); if (native_cpu == IMAGE_FILE_MACHINE_ARM64) - display->running_on_arm64 = TRUE; + is_arm64 = TRUE; + + if (proc_cpu != IMAGE_FILE_MACHINE_UNKNOWN) + is_wow64 = TRUE; + } + else + { + fallback_wow64_check = TRUE; } FreeLibrary (kernel32); } + else + { + fallback_wow64_check = TRUE; + } + + if (fallback_wow64_check) + IsWow64Process (GetCurrentProcess (), &is_wow64); g_once_init_leave (&checked, 1); } + + switch (check_type) + { + case GDK_WIN32_ARM64: + return is_arm64; + break; + + case GDK_WIN32_WOW64: + return is_wow64; + break; + + default: + g_warning ("unknown CPU check type"); + return FALSE; + break; + } } static void @@ -987,7 +1020,7 @@ gdk_win32_display_init (GdkWin32Display *display_win32) display_win32->monitors = G_LIST_MODEL (g_list_store_new (GDK_TYPE_MONITOR)); _gdk_win32_enable_hidpi (display_win32); - _gdk_win32_check_on_arm64 (display_win32); + display_win32->running_on_arm64 = _gdk_win32_check_processor (GDK_WIN32_ARM64); /* if we have DPI awareness, set up fixed scale if set */ if (display_win32->dpi_aware_type != PROCESS_DPI_UNAWARE && diff --git a/gdk/win32/gdkdisplay-win32.h b/gdk/win32/gdkdisplay-win32.h index 22532cf39a..3b746bbe1b 100644 --- a/gdk/win32/gdkdisplay-win32.h +++ b/gdk/win32/gdkdisplay-win32.h @@ -105,13 +105,6 @@ typedef enum { GDK_WIN32_TABLET_INPUT_API_WINPOINTER } GdkWin32TabletInputAPI; -/* Detect running architecture */ -typedef BOOL (WINAPI *funcIsWow64Process2) (HANDLE, USHORT *, USHORT *); -typedef struct _GdkWin32KernelCPUFuncs -{ - funcIsWow64Process2 isWow64Process2; -} GdkWin32KernelCPUFuncs; - typedef struct { HDC hdc; @@ -177,7 +170,6 @@ struct _GdkWin32Display /* Running CPU items */ guint running_on_arm64 : 1; - GdkWin32KernelCPUFuncs cpu_funcs; }; struct _GdkWin32DisplayClass diff --git a/gdk/win32/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c index bc7590aa13..9e752d12f5 100644 --- a/gdk/win32/gdkevents-win32.c +++ b/gdk/win32/gdkevents-win32.c @@ -19,7 +19,7 @@ */ /* - * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * Modified by the GTK+ Team and others 1997-2020. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. @@ -644,11 +644,9 @@ build_key_event_state (BYTE *key_state) { GdkModifierType state; GdkWin32Keymap *keymap; + keymap = GDK_WIN32_KEYMAP (_gdk_win32_display_get_keymap (_gdk_display)); - state = 0; - - if (key_state[VK_SHIFT] & 0x80) - state |= GDK_SHIFT_MASK; + state = _gdk_win32_keymap_get_mod_mask (keymap); if (key_state[VK_CAPITAL] & 0x01) state |= GDK_LOCK_MASK; @@ -664,26 +662,6 @@ build_key_event_state (BYTE *key_state) if (key_state[VK_XBUTTON2] & 0x80) state |= GDK_BUTTON5_MASK; - keymap = GDK_WIN32_KEYMAP (_gdk_win32_display_get_keymap (_gdk_display)); - - if (_gdk_win32_keymap_has_altgr (keymap) && - (key_state[VK_LCONTROL] & 0x80) && - (key_state[VK_RMENU] & 0x80)) - { - state |= GDK_MOD2_MASK; - if (key_state[VK_RCONTROL] & 0x80) - state |= GDK_CONTROL_MASK; - if (key_state[VK_LMENU] & 0x80) - state |= GDK_ALT_MASK; - } - else - { - if (key_state[VK_CONTROL] & 0x80) - state |= GDK_CONTROL_MASK; - if (key_state[VK_MENU] & 0x80) - state |= GDK_ALT_MASK; - } - return state; } diff --git a/gdk/win32/gdkkeys-win32-impl-wow64.c b/gdk/win32/gdkkeys-win32-impl-wow64.c new file mode 100644 index 0000000000..ada1211d87 --- /dev/null +++ b/gdk/win32/gdkkeys-win32-impl-wow64.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Philip Zander + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _WIN64 +#define GDK_WIN32_COMPILE_FOR_WOW64 1 +#include "gdkkeys-win32-impl.c" +#endif diff --git a/gdk/win32/gdkkeys-win32-impl.c b/gdk/win32/gdkkeys-win32-impl.c new file mode 100644 index 0000000000..f792c4e24b --- /dev/null +++ b/gdk/win32/gdkkeys-win32-impl.c @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2021 Philip Zander + * Copyright (c) 2018 Microsoft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* NOTE: When compiling the 32-bit version of the library, in addition to being + * compiled as a regular source file, this file is also included by + * gdkkeys-win32-impl-wow64.c to generate an alternate version of the code + * intended for running on a 64-bit kernel. Because of the way keyboard layout + * DLLs work on Windows, we have to generate two versions and decide at runtime + * which code path to execute. You can read more about the specifics below, in + * the section about KBD_LONG_POINTER. */ + +#include "gdkkeys-win32.h" + +#ifndef GDK_WIN32_COMPILE_FOR_WOW64 + #define GDK_WIN32_COMPILE_FOR_WOW64 0 +#endif + +/* This is our equivalent of the KBD_LONG_POINTER macro in Microsoft's kbd.h. + * + * A KBD_LONG_POINTER represents a pointer native to the *host*. + * I.e. 32 bits on 32-bit Windows and 64 bits on 64-bit Windows. + * + * This is *not* the same as the the bitness of the application, since it is + * possible to execute 32-bit binaries on either a 32-bit *or* a 64-bit host. + * On a 64-bit host, KBD_LONG_PTR will be 64-bits, even if the application + * itself is 32-bit. (Whereas on a 32-bit host, it will be 32-bit.) + * + * For clarity, here is an overview of the bit-size of KBD_LONG_POINTER on all + * possible host & app combinations: + * + * Host 32 64 + * App +----------- + * 32 | 32 64 + * 64 | - 64 + * + * In the official MS headers, KBD_LONG_POINTER is implemented via a macro + * which expands to the attribute `__ptr64` if the keyboard driver is + * compiled for a 64 bit host. Unfortunately, `__ptr64` is only + * supported by MSVC. We use a union here as a workaround. + * + * For all KBD_LONG_POINTERs, we define an alias starting with "KLP". + * Our naming schema (inspired by the Windows headers) is thus the following: + * - FOO: The type FOO itself + * - PFOO: Regular pointer to the type FOO + * - KLPFOO: Keyboard Long Pointer to the type FOO + */ + +#if GDK_WIN32_COMPILE_FOR_WOW64 + #define DEFINE_KBD_LONG_POINTER(type) \ + typedef union { \ + P##type ptr; \ + UINT64 _align; \ + } KLP##type +#else + #define DEFINE_KBD_LONG_POINTER(type) \ + typedef union { \ + P##type ptr; \ + } KLP##type +#endif + +DEFINE_KBD_LONG_POINTER (USHORT); +DEFINE_KBD_LONG_POINTER (VOID); + +/* Driver definitions + * See + * https://github.com/microsoft/windows-rs/blob/0.28.0/crates/deps/sys/src/Windows/Win32/UI/Input/KeyboardAndMouse/mod.rs + * + * For more information on how these structures work, see also: + * https://github.com/microsoft/Windows-driver-samples/tree/f0adcda012820b1cd44a8b3a1953baf478029738/input/layout + */ + +typedef struct +{ + BYTE Vk; + BYTE ModBits; +} VK_TO_BIT, *PVK_TO_BIT; + +DEFINE_KBD_LONG_POINTER (VK_TO_BIT); + +typedef struct +{ + KLPVK_TO_BIT pVkToBit; + WORD wMaxModBits; + BYTE ModNumber[1]; +} MODIFIERS, *PMODIFIERS; + +DEFINE_KBD_LONG_POINTER (MODIFIERS); + +typedef struct +{ + BYTE Vsc; + USHORT Vk; +} VSC_VK, *PVSC_VK; + +DEFINE_KBD_LONG_POINTER (VSC_VK); + +typedef struct +{ + BYTE Vk; + BYTE Vsc; +} VK_VSC, *PVK_VSC; + +DEFINE_KBD_LONG_POINTER (VK_VSC); + +typedef struct +{ + BYTE VirtualKey; + BYTE Attributes; + WCHAR wch[1]; +} VK_TO_WCHARS, *PVK_TO_WCHARS; + +DEFINE_KBD_LONG_POINTER (VK_TO_WCHARS); + +typedef struct +{ + KLPVK_TO_WCHARS pVkToWchars; + BYTE nModifications; + BYTE cbSize; +} VK_TO_WCHAR_TABLE, *PVK_TO_WCHAR_TABLE; + +DEFINE_KBD_LONG_POINTER (VK_TO_WCHAR_TABLE); + +typedef struct +{ + DWORD dwBoth; + WCHAR wchComposed; + USHORT uFlags; +} DEADKEY, *PDEADKEY; + +DEFINE_KBD_LONG_POINTER (DEADKEY); + +typedef struct +{ + KLPMODIFIERS pCharModifiers; + KLPVK_TO_WCHAR_TABLE pVkToWcharTable; + KLPDEADKEY pDeadKey; + KLPVOID pKeyNames; + KLPVOID pKeyNamesExt; + KLPVOID pKeyNamesDead; + KLPUSHORT pusVSCtoVK; + BYTE bMaxVSCtoVK; + KLPVSC_VK pVSCtoVK_E0; + KLPVSC_VK pVSCtoVK_E1; + DWORD fLocaleFlags; + BYTE nLgMaxd; + BYTE cbLgEntry; + KLPVOID pLigature; +} KBDTABLES, *PKBDTABLES; + +DEFINE_KBD_LONG_POINTER (KBDTABLES); + +/* End of declarations */ + +static BYTE +keystate_to_modbits (GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256]) +{ + PKBDTABLES tables = (PKBDTABLES) info->tables; + PVK_TO_BIT vk_to_bit; + BYTE result = 0; + int i; + + g_return_val_if_fail (tables != NULL, 0); + + vk_to_bit = tables->pCharModifiers.ptr->pVkToBit.ptr; + + for (i = 0; vk_to_bit[i].Vk != 0; ++i) + if (keystate[vk_to_bit[i].Vk] & 0x80) + result |= vk_to_bit[i].ModBits; + + return result; +} + +static BYTE +modbits_to_level (GdkWin32KeymapLayoutInfo *info, + BYTE modbits) +{ + PKBDTABLES tables = (PKBDTABLES) info->tables; + PMODIFIERS modifiers; + + g_return_val_if_fail (tables != NULL, 0); + + modifiers = tables->pCharModifiers.ptr; + if (modbits > modifiers->wMaxModBits) + return 0; + + return modifiers->ModNumber[modbits]; +} + +#define POPCOUNT(b) (!!(b & 0x01) + !!(b & 0x02) + !!(b & 0x04) + !!(b & 0x08) + \ + !!(b & 0x10) + !!(b & 0x20) + !!(b & 0x40) + !!(b & 0x80)) + +/* + * vk_to_char_fuzzy: + * + * For a given key and keystate, return the best-fit character and the + * modifiers used to produce it. Note that not all modifiers need to be used, + * because some modifier combination aren't actually mapped in the keyboard + * layout (for example the Ctrl key typically has no effect, unless used in + * combination with Alt). Such modifiers will not be consumed. + * + * 'Best-fit' means 'consume as many modifiers as possibe'. + * + * For example (assuming a neutral keystate): + * + * - Shift + a -> 'A', consumed_mod_bits: [Shift] + * - Ctrl + a -> 'a', consumed_mod_bits: [] + * - Ctrl + Shift + a -> 'A', consumed_mod_bits: [Shift] + * + * If capslock is active, the result could be: + * + * - Shift + a -> 'a', consumed_mod_bits: [Shift] + * + * The caller can supply additional modifiers to be added to the + * keystate in `extra_mod_bits`. + * + * If the key combination results in a dead key, `is_dead` will be set to TRUE, + * otherwise it will be set to FALSE. + */ +static WCHAR +vk_to_char_fuzzy (GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256], + BYTE extra_mod_bits, + BYTE *consumed_mod_bits, + gboolean *is_dead, + BYTE vk) +{ + PKBDTABLES tables = (PKBDTABLES) info->tables; + + PVK_TO_WCHAR_TABLE wch_tables; + PVK_TO_WCHAR_TABLE wch_table; + PVK_TO_WCHARS entry; + + int table_index; + int entry_index; + int n_levels; + int entry_size; + + /* Initialize with defaults */ + if (consumed_mod_bits) + *consumed_mod_bits = 0; + if (is_dead) + *is_dead = FALSE; + + g_return_val_if_fail (tables != NULL, WCH_NONE); + + wch_tables = tables->pVkToWcharTable.ptr; + + table_index = info->vk_lookup_table[vk].table; + entry_index = info->vk_lookup_table[vk].index; + + if (table_index == -1 || entry_index == -1) + return WCH_NONE; + + wch_table = &wch_tables[table_index]; + + n_levels = wch_table->nModifications; + entry_size = wch_table->cbSize; + + entry = (PVK_TO_WCHARS) ((PBYTE) wch_table->pVkToWchars.ptr + + entry_size*entry_index); + + if (entry->VirtualKey == vk) + { + BYTE modbits; + WCHAR best_char = WCH_NONE; + BYTE best_modifiers = 0; + int best_score = -1; + gboolean best_is_dead = FALSE; + int level; + + /* Add modbits of currently pressed keys. */ + modbits = keystate_to_modbits (info, keystate); + /* Add modbits supplied by caller. */ + modbits |= extra_mod_bits; + + /* Take toggled keys into account. For example, capslock normally inverts the + * state of KBDSHIFT (with some exceptions). */ + + /* Key supporting capslock */ + if ((entry->Attributes & CAPLOK) && + /* Ignore capslock if any modifiers other than shift are pressed. + * E.g. on the German layout, CapsLock + AltGr + q is the same as + * AltGr + q ('@'), but NOT the same as Shift + AltGr + q (not mapped). */ + !(modbits & ~KBDSHIFT) && + (keystate[VK_CAPITAL] & 0x01)) + modbits ^= KBDSHIFT; + + /* Key supporting combination of capslock + altgr */ + if ((entry->Attributes & CAPLOKALTGR) && + (modbits & KBDALTGR) && + (keystate[VK_CAPITAL] & 0x01)) + modbits ^= KBDSHIFT; + + /* In the Swiss German layout, CapsLock + key is different from Shift + key + * for some keys. For such keys, Capslock toggles the KBDCTRL bit. */ + if ((entry->Attributes & SGCAPS) && + (keystate[VK_CAPITAL] & 0x01)) + modbits ^= KBDCTRL; + + /* I'm not totally sure how kanalok behaves, for now I assume that there + * aren't any special cases. */ + if ((entry->Attributes & KANALOK) && + (keystate[VK_KANA] & 0x01)) + modbits ^= KBDKANA; + + /* We try to find the entry with the most matching modifiers */ + for (level = 0; level < n_levels; ++level) + { + BYTE candidate_modbits = info->level_to_modbits[level]; + gboolean candidate_is_dead = FALSE; + WCHAR c; + int score; + + if (candidate_modbits & ~modbits) + continue; + + c = entry->wch[level]; + if (c == WCH_DEAD) + { + /* Next entry contains the undead keys */ + PVK_TO_WCHARS next_entry; + next_entry = (PVK_TO_WCHARS) ((PBYTE) wch_table->pVkToWchars.ptr + + entry_size * (entry_index + 1)); + c = next_entry->wch[level]; + candidate_is_dead = TRUE; + } + + if (c == WCH_DEAD || c == WCH_LGTR || c == WCH_NONE) + continue; + + score = POPCOUNT (candidate_modbits & modbits); + if (score > best_score) + { + best_score = score; + best_char = c; + best_modifiers = candidate_modbits; + best_is_dead = candidate_is_dead; + } + } + + if (consumed_mod_bits) + *consumed_mod_bits = best_modifiers; + + if (is_dead) + *is_dead = best_is_dead; + + return best_char; + } + + return WCH_NONE; +} + +static void +init_vk_lookup_table (GdkWin32KeymapLayoutInfo *info) +{ + PKBDTABLES tables = (PKBDTABLES) info->tables; + PVK_TO_WCHAR_TABLE wch_tables; + PMODIFIERS modifiers; + int i, vk, table_idx; + + g_return_if_fail (tables != NULL); + + wch_tables = tables->pVkToWcharTable.ptr; + + /* Initialize empty table */ + memset (info->vk_lookup_table, -1, sizeof (info->vk_lookup_table)); + + /* Initialize level -> modbits lookup table */ + memset (info->level_to_modbits, 0, sizeof(info->level_to_modbits)); + info->max_level = 0; + + modifiers = tables->pCharModifiers.ptr; + for (i = 0; i <= modifiers->wMaxModBits; ++i) + { + if (modifiers->ModNumber[i] != SHFT_INVALID && + modifiers->ModNumber[i] != 0 /* Workaround for buggy layouts*/) + { + if (modifiers->ModNumber[i] > info->max_level) + info->max_level = modifiers->ModNumber[i]; + info->level_to_modbits[modifiers->ModNumber[i]] = i; + } + } + + info->max_modbit_value = modifiers->wMaxModBits; + + /* For convenience, we add 256 identity-mapped entries corresponding to the VKs. + * This allows us to return a pointer to them from the `gdk_keysym_to_key_entry` + * function. + */ + + for (vk = 0; vk < 256; ++vk) + { + GdkWin32KeymapKeyEntry key_entry = {0}; + key_entry.vk = vk; + key_entry.mod_bits = 0; + key_entry.next = -1; + g_array_append_val (info->key_entries, key_entry); + } + + /* Special entry for ISO_Left_Tab */ + { + GdkWin32KeymapKeyEntry key_entry = {0}; + key_entry.vk = VK_TAB; + key_entry.mod_bits = KBDSHIFT; + key_entry.next = -1; + g_array_append_val (info->key_entries, key_entry); + } + + /* Initialize generic vk <-> char tables */ + + for (table_idx = 0; ; ++table_idx) + { + PVK_TO_WCHAR_TABLE wch_table = &wch_tables[table_idx]; + int entry_size; + int n_levels; + int entry_idx; + + if (wch_table->pVkToWchars.ptr == NULL) + break; + + entry_size = wch_table->cbSize; + n_levels = wch_table->nModifications; + + for (entry_idx = 0; ; ++entry_idx) + { + PVK_TO_WCHARS entry; + int level; + + entry = (PVK_TO_WCHARS) ((PBYTE) wch_table->pVkToWchars.ptr + + entry_size * entry_idx); + + if (entry->VirtualKey == 0) + break; + + /* Lookup table to find entry for a VK in O(1). */ + + info->vk_lookup_table[entry->VirtualKey].table = table_idx; + info->vk_lookup_table[entry->VirtualKey].index = entry_idx; + + /* Create reverse lookup entries to find a VK+modifier combinations + * that results in a given character. */ + for (level = 0; level < n_levels; ++level) + { + GdkWin32KeymapKeyEntry key_entry = {0}; + WCHAR c = entry->wch[level]; + int inserted_idx; + gintptr next_idx; + + key_entry.vk = entry->VirtualKey; + key_entry.mod_bits = info->level_to_modbits[level]; + + /* There can be multiple combinations that produce the same character. + * We store all of them in a linked list. + * Check if we already have an entry for the character, so we can chain + * them together. */ + if (g_hash_table_lookup_extended (info->reverse_lookup_table, + GINT_TO_POINTER (c), + NULL, (gpointer*)&next_idx)) + { + key_entry.next = next_idx; + } + else + { + key_entry.next = -1; + } + + /* We store the KeyEntry in an array. In the hash table we only store + * the index. */ + + g_array_append_val (info->key_entries, key_entry); + inserted_idx = info->key_entries->len - 1; + + g_hash_table_insert (info->reverse_lookup_table, + GINT_TO_POINTER (c), + GINT_TO_POINTER (inserted_idx)); + } + } + } +} + +static gboolean +load_layout_dll (const char *dll, + GdkWin32KeymapLayoutInfo *info) +{ + typedef KLPKBDTABLES (*KbdLayerDescriptor)(VOID); + + HMODULE lib; + KbdLayerDescriptor func; + KLPKBDTABLES tables; + + g_return_val_if_fail (dll != NULL, FALSE); + + lib = LoadLibraryA (dll); + if (lib == NULL) + goto fail1; + + func = (KbdLayerDescriptor) GetProcAddress (lib, "KbdLayerDescriptor"); + if (func == NULL) + goto fail2; + + tables = func(); + + info->lib = lib; + info->tables = (PKBDTABLES) tables.ptr; + + return TRUE; + +fail2: + FreeLibrary (lib); +fail1: + return FALSE; +} + +#if GDK_WIN32_COMPILE_FOR_WOW64 + #define GDK_WIN32_KEYMAP_IMPL_NAME gdkwin32_keymap_impl_wow64 +#else + #define GDK_WIN32_KEYMAP_IMPL_NAME gdkwin32_keymap_impl +#endif + +const GdkWin32KeymapImpl GDK_WIN32_KEYMAP_IMPL_NAME = + { + load_layout_dll, + init_vk_lookup_table, + keystate_to_modbits, + modbits_to_level, + vk_to_char_fuzzy + }; diff --git a/gdk/win32/gdkkeys-win32.c b/gdk/win32/gdkkeys-win32.c index 70d65e97fb..a8f976e2ab 100644 --- a/gdk/win32/gdkkeys-win32.c +++ b/gdk/win32/gdkkeys-win32.c @@ -32,6 +32,7 @@ #include "gdkdisplayprivate.h" #include "gdkkeysyms.h" #include "gdkkeysprivate.h" +#include "gdkkeys-win32.h" #include <ctype.h> #include <stdio.h> @@ -42,106 +43,7 @@ #define GDK_MOD2_MASK (1 << 4) -enum _GdkWin32KeyLevelState -{ - GDK_WIN32_LEVEL_NONE = 0, - GDK_WIN32_LEVEL_SHIFT, - GDK_WIN32_LEVEL_CAPSLOCK, - GDK_WIN32_LEVEL_SHIFT_CAPSLOCK, - GDK_WIN32_LEVEL_ALTGR, - GDK_WIN32_LEVEL_SHIFT_ALTGR, - GDK_WIN32_LEVEL_CAPSLOCK_ALTGR, - GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR, - GDK_WIN32_LEVEL_COUNT -}; - -typedef enum _GdkWin32KeyLevelState GdkWin32KeyLevelState; - -struct _GdkWin32KeyNode -{ - /* Non-spacing version of the dead key */ - guint undead_gdk_keycode; - - /* Virtual key code */ - guint8 vk; - - /* Level for which this virtual key code produces this gdk_keycode */ - GdkWin32KeyLevelState level; - - /* GDK (X11) code for this key */ - guint gdk_keycode; - - /* Array of GdkWin32KeyNode should be sorted by gdk_keycode, then by level */ - GArray *combinations; -}; - -typedef struct _GdkWin32KeyNode GdkWin32KeyNode; - -/* -Example: - GdkWin32KeyNode - { - undead_gdk_keycode = 0x0b4 GDK_KEY_acute (') - vk = 0xde VK_OEM_7 - level = GDK_WIN32_LEVEL_NONE - gdk_keycode = 0xfe51 GDK_KEY_dead_acute - combinations = - { - GdkWin32KeyNode - { - undead_gdk_keycode = 0x061 GDK_KEY_a (a) - level = GDK_WIN32_LEVEL_NONE - vk = 0x41 VK_A - gdk_keycode = 0xe1 GDK_KEY_aacute á - combinations = NULL - }, - GdkWin32KeyNode - { - unicode_char = 0x041 GDK_KEY_A (A) - level = GDK_WIN32_LEVEL_SHIFT - vk = 0x41 VK_A - gdk_keycode = 0x0c1 GDK_KEY_Aacute Á - combinations = NULL - }, - { ... } - } - } - -Thus: - -GDK_KEY_dead_acute + GDK_KEY_a -= GDK_KEY_aacute - -GDK_KEY_dead_acute + GDK_KEY_A -= GDK_KEY_Aacute - -GDK_KEY_dead_acute + GDK_KEY_s -matches partially -(GDK_KEY_dead_acute is a known dead key, but does not combine with GDK_KEY_s) -and resolves into: -GDK_KEY_acute + GDK_KEY_s - -GDK_KEY_dead_somethingelse + GDK_KEY_anything -does not match at all -(W32 API did not provide any deadkey info for GDK_KEY_dead_somethingelse) -and the caller will try other matching mechanisms for compose_buffer -*/ - -struct _GdkWin32KeyGroupOptions -{ - /* character that should be used as the decimal separator */ - wchar_t decimal_mark; - - /* Scancode for the VK_RSHIFT */ - guint scancode_rshift; - - /* TRUE if Ctrl+Alt emulates AltGr */ - gboolean has_altgr; - - GArray *dead_keys; -}; - -typedef struct _GdkWin32KeyGroupOptions GdkWin32KeyGroupOptions; +/* GdkWin32Keymap */ struct _GdkWin32KeymapClass { @@ -152,63 +54,61 @@ struct _GdkWin32Keymap { GdkKeymap parent_instance; - /* length = what GetKeyboardLayoutList() returns, type = HKL. - * When it changes, recreate the keymap and repopulate the options. - */ + /* Array of HKL */ GArray *layout_handles; - /* VirtualKeyCode -> gdk_keyval table - * length = 256 * length(layout_handles) * 2 * 4 - * 256 is the number of virtual key codes, - * 2x4 is the number of Shift/AltGr/CapsLock combinations (level), - * length(layout_handles) is the number of layout handles (group). - */ - guint *keysym_tab; - - /* length = length(layout_handles), type = GdkWin32KeyGroupOptions - * Kept separate from layout_handles because layout_handles is - * populated by W32 API. - */ - GArray *options; + /* Array of GdkWin32KeymapLayoutInfo */ + GArray *layout_infos; /* Index of a handle in layout_handles, * at any point it should be the same handle as GetKeyboardLayout(0) returns, * but GDK caches it to avoid calling GetKeyboardLayout (0) every time. */ guint8 active_layout; + + guint current_serial; + + /* Pointer to the implementation to be used. See comment in gdkkeys-win32.h. + * (we will dynamically choose at runtime for 32-bit builds based on whether + * we are running under WOW64) + */ + const GdkWin32KeymapImpl *gdkwin32_keymap_impl; }; G_DEFINE_TYPE (GdkWin32Keymap, gdk_win32_keymap, GDK_TYPE_KEYMAP) guint _gdk_keymap_serial = 0; - static GdkKeymap *default_keymap = NULL; -#define KEY_STATE_SIZE 256 - -static void update_keymap (GdkKeymap *gdk_keymap); +static void update_keymap (GdkWin32Keymap *gdk_keymap); +static void clear_keyboard_layout_info (gpointer data); static void -gdk_win32_key_group_options_clear (GdkWin32KeyGroupOptions *options) +gdk_win32_keymap_init (GdkWin32Keymap *keymap) { - g_clear_pointer (&options->dead_keys, g_array_unref); -} + /* Regular implementation (32 bit & 64 bit) */ + extern const GdkWin32KeymapImpl gdkwin32_keymap_impl; + /* Implementation for 32 bit applications running on a 64 bit host (WOW64). */ +#ifndef _WIN64 + extern const GdkWin32KeymapImpl gdkwin32_keymap_impl_wow64; +#endif -static void -gdk_win32_key_node_clear (GdkWin32KeyNode *node) -{ - g_clear_pointer (&node->combinations, g_array_unref); -} + keymap->layout_infos = g_array_new (FALSE, TRUE, + sizeof (GdkWin32KeymapLayoutInfo)); + g_array_set_clear_func (keymap->layout_infos, + clear_keyboard_layout_info); -static void -gdk_win32_keymap_init (GdkWin32Keymap *keymap) -{ - keymap->layout_handles = g_array_new (FALSE, FALSE, sizeof (HKL)); - keymap->options = g_array_new (FALSE, FALSE, sizeof (GdkWin32KeyGroupOptions)); - g_array_set_clear_func (keymap->options, (GDestroyNotify) gdk_win32_key_group_options_clear); - keymap->keysym_tab = NULL; + keymap->layout_handles = g_array_new (FALSE, FALSE, + sizeof (GdkWin32KeymapLayoutInfo)); keymap->active_layout = 0; - update_keymap (GDK_KEYMAP (keymap)); + + keymap->gdkwin32_keymap_impl = &gdkwin32_keymap_impl; +#ifndef _WIN64 + if (_gdk_win32_check_processor (GDK_WIN32_WOW64)) + keymap->gdkwin32_keymap_impl = &gdkwin32_keymap_impl_wow64; +#endif + + update_keymap (keymap); } static void @@ -216,933 +116,471 @@ gdk_win32_keymap_finalize (GObject *object) { GdkWin32Keymap *keymap = GDK_WIN32_KEYMAP (object); - g_clear_pointer (&keymap->keysym_tab, g_free); g_clear_pointer (&keymap->layout_handles, g_array_unref); - g_clear_pointer (&keymap->options, g_array_unref); + g_clear_pointer (&keymap->layout_infos, g_array_unref); G_OBJECT_CLASS (gdk_win32_keymap_parent_class)->finalize (object); } -#ifdef G_ENABLE_DEBUG -static void -print_keysym_tab (GdkWin32Keymap *keymap) -{ - int li; - GdkWin32KeyGroupOptions *options; - int vk; - GdkWin32KeyLevelState level; - int group_size = keymap->layout_handles->len; - - for (li = 0; li < group_size; li++) - { - options = &g_array_index (keymap->options, GdkWin32KeyGroupOptions, li); - g_print ("keymap %d (0x%p):%s\n", - li, g_array_index (keymap->layout_handles, HKL, li), - options->has_altgr ? " (uses AltGr)" : ""); - - for (vk = 0; vk < KEY_STATE_SIZE; vk++) - { - g_print ("%#.02x: ", vk); - - for (level = 0; level < GDK_WIN32_LEVEL_COUNT; level++) - { - const char *name = gdk_keyval_name (keymap->keysym_tab[vk * group_size * GDK_WIN32_LEVEL_COUNT + level]); - - g_print ("%s ", name ? name : "(none)"); - } - - g_print ("\n"); - } - } -} -#endif - -static void -handle_special (guint vk, - guint *ksymp, - int shift) +/* Convenience wrapper functions */ +static gboolean +load_layout_dll (GdkWin32Keymap *keymap, + const char *dll, + GdkWin32KeymapLayoutInfo *info) { - switch (vk) - { - case VK_CANCEL: - *ksymp = GDK_KEY_Cancel; break; - case VK_BACK: - *ksymp = GDK_KEY_BackSpace; break; - case VK_TAB: - if (shift & 0x1) - *ksymp = GDK_KEY_ISO_Left_Tab; - else - *ksymp = GDK_KEY_Tab; - break; - case VK_CLEAR: - *ksymp = GDK_KEY_Clear; break; - case VK_RETURN: - *ksymp = GDK_KEY_Return; break; - case VK_SHIFT: - case VK_LSHIFT: - *ksymp = GDK_KEY_Shift_L; break; - case VK_CONTROL: - case VK_LCONTROL: - *ksymp = GDK_KEY_Control_L; break; - case VK_MENU: - case VK_LMENU: - *ksymp = GDK_KEY_Alt_L; break; - case VK_PAUSE: - *ksymp = GDK_KEY_Pause; break; - case VK_ESCAPE: - *ksymp = GDK_KEY_Escape; break; - case VK_PRIOR: - *ksymp = GDK_KEY_Prior; break; - case VK_NEXT: - *ksymp = GDK_KEY_Next; break; - case VK_END: - *ksymp = GDK_KEY_End; break; - case VK_HOME: - *ksymp = GDK_KEY_Home; break; - case VK_LEFT: - *ksymp = GDK_KEY_Left; break; - case VK_UP: - *ksymp = GDK_KEY_Up; break; - case VK_RIGHT: - *ksymp = GDK_KEY_Right; break; - case VK_DOWN: - *ksymp = GDK_KEY_Down; break; - case VK_SELECT: - *ksymp = GDK_KEY_Select; break; - case VK_PRINT: - *ksymp = GDK_KEY_Print; break; - case VK_SNAPSHOT: - *ksymp = GDK_KEY_Print; break; - case VK_EXECUTE: - *ksymp = GDK_KEY_Execute; break; - case VK_INSERT: - *ksymp = GDK_KEY_Insert; break; - case VK_DELETE: - *ksymp = GDK_KEY_Delete; break; - case VK_HELP: - *ksymp = GDK_KEY_Help; break; - case VK_LWIN: - *ksymp = GDK_KEY_Meta_L; break; - case VK_RWIN: - *ksymp = GDK_KEY_Meta_R; break; - case VK_APPS: - *ksymp = GDK_KEY_Menu; break; - case VK_DECIMAL: - *ksymp = GDK_KEY_KP_Decimal; break; - case VK_MULTIPLY: - *ksymp = GDK_KEY_KP_Multiply; break; - case VK_ADD: - *ksymp = GDK_KEY_KP_Add; break; - case VK_SEPARATOR: - *ksymp = GDK_KEY_KP_Separator; break; - case VK_SUBTRACT: - *ksymp = GDK_KEY_KP_Subtract; break; - case VK_DIVIDE: - *ksymp = GDK_KEY_KP_Divide; break; - case VK_NUMPAD0: - *ksymp = GDK_KEY_KP_0; break; - case VK_NUMPAD1: - *ksymp = GDK_KEY_KP_1; break; - case VK_NUMPAD2: - *ksymp = GDK_KEY_KP_2; break; - case VK_NUMPAD3: - *ksymp = GDK_KEY_KP_3; break; - case VK_NUMPAD4: - *ksymp = GDK_KEY_KP_4; break; - case VK_NUMPAD5: - *ksymp = GDK_KEY_KP_5; break; - case VK_NUMPAD6: - *ksymp = GDK_KEY_KP_6; break; - case VK_NUMPAD7: - *ksymp = GDK_KEY_KP_7; break; - case VK_NUMPAD8: - *ksymp = GDK_KEY_KP_8; break; - case VK_NUMPAD9: - *ksymp = GDK_KEY_KP_9; break; - case VK_F1: - *ksymp = GDK_KEY_F1; break; - case VK_F2: - *ksymp = GDK_KEY_F2; break; - case VK_F3: - *ksymp = GDK_KEY_F3; break; - case VK_F4: - *ksymp = GDK_KEY_F4; break; - case VK_F5: - *ksymp = GDK_KEY_F5; break; - case VK_F6: - *ksymp = GDK_KEY_F6; break; - case VK_F7: - *ksymp = GDK_KEY_F7; break; - case VK_F8: - *ksymp = GDK_KEY_F8; break; - case VK_F9: - *ksymp = GDK_KEY_F9; break; - case VK_F10: - *ksymp = GDK_KEY_F10; break; - case VK_F11: - *ksymp = GDK_KEY_F11; break; - case VK_F12: - *ksymp = GDK_KEY_F12; break; - case VK_F13: - *ksymp = GDK_KEY_F13; break; - case VK_F14: - *ksymp = GDK_KEY_F14; break; - case VK_F15: - *ksymp = GDK_KEY_F15; break; - case VK_F16: - *ksymp = GDK_KEY_F16; break; - case VK_F17: - *ksymp = GDK_KEY_F17; break; - case VK_F18: - *ksymp = GDK_KEY_F18; break; - case VK_F19: - *ksymp = GDK_KEY_F19; break; - case VK_F20: - *ksymp = GDK_KEY_F20; break; - case VK_F21: - *ksymp = GDK_KEY_F21; break; - case VK_F22: - *ksymp = GDK_KEY_F22; break; - case VK_F23: - *ksymp = GDK_KEY_F23; break; - case VK_F24: - *ksymp = GDK_KEY_F24; break; - case VK_NUMLOCK: - *ksymp = GDK_KEY_Num_Lock; break; - case VK_SCROLL: - *ksymp = GDK_KEY_Scroll_Lock; break; - case VK_RSHIFT: - *ksymp = GDK_KEY_Shift_R; break; - case VK_RCONTROL: - *ksymp = GDK_KEY_Control_R; break; - case VK_RMENU: - *ksymp = GDK_KEY_Alt_R; break; - } + return keymap->gdkwin32_keymap_impl->load_layout_dll (dll, info); } static void -set_level_vks (guchar *key_state, - GdkWin32KeyLevelState level) +init_vk_lookup_table (GdkWin32Keymap *keymap, + GdkWin32KeymapLayoutInfo *info) { - switch (level) - { - case GDK_WIN32_LEVEL_NONE: - key_state[VK_SHIFT] = 0; - key_state[VK_CAPITAL] = 0; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0; - break; - case GDK_WIN32_LEVEL_SHIFT: - key_state[VK_SHIFT] = 0x80; - key_state[VK_CAPITAL] = 0; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0; - break; - case GDK_WIN32_LEVEL_CAPSLOCK: - key_state[VK_SHIFT] = 0; - key_state[VK_CAPITAL] = 0x01; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0; - break; - case GDK_WIN32_LEVEL_SHIFT_CAPSLOCK: - key_state[VK_SHIFT] = 0x80; - key_state[VK_CAPITAL] = 0x01; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0; - break; - case GDK_WIN32_LEVEL_ALTGR: - key_state[VK_SHIFT] = 0; - key_state[VK_CAPITAL] = 0; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80; - break; - case GDK_WIN32_LEVEL_SHIFT_ALTGR: - key_state[VK_SHIFT] = 0x80; - key_state[VK_CAPITAL] = 0; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80; - break; - case GDK_WIN32_LEVEL_CAPSLOCK_ALTGR: - key_state[VK_SHIFT] = 0; - key_state[VK_CAPITAL] = 0x01; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80; - break; - case GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR: - key_state[VK_SHIFT] = 0x80; - key_state[VK_CAPITAL] = 0x01; - key_state[VK_CONTROL] = key_state[VK_MENU] = 0x80; - break; - case GDK_WIN32_LEVEL_COUNT: - g_assert_not_reached (); - break; - } + keymap->gdkwin32_keymap_impl->init_vk_lookup_table (info); } -static void -reset_after_dead (guchar key_state[KEY_STATE_SIZE], - HKL handle) +static BYTE +keystate_to_modbits (GdkWin32Keymap *keymap, + GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256]) { - guchar temp_key_state[KEY_STATE_SIZE]; - wchar_t wcs[2]; - - memmove (temp_key_state, key_state, KEY_STATE_SIZE); - - temp_key_state[VK_SHIFT] = - temp_key_state[VK_CONTROL] = - temp_key_state[VK_CAPITAL] = - temp_key_state[VK_MENU] = 0; - - ToUnicodeEx (VK_SPACE, MapVirtualKey (VK_SPACE, 0), - temp_key_state, wcs, G_N_ELEMENTS (wcs), - 0, handle); + return keymap->gdkwin32_keymap_impl->keystate_to_modbits (info, keystate); } -static void -handle_dead (guint keysym, - guint *ksymp) +static BYTE +modbits_to_level (GdkWin32Keymap *keymap, + GdkWin32KeymapLayoutInfo *info, + BYTE modbits) { - switch (keysym) - { - case '"': /* 0x022 */ - *ksymp = GDK_KEY_dead_diaeresis; break; - case '\'': /* 0x027 */ - *ksymp = GDK_KEY_dead_acute; break; - case GDK_KEY_asciicircum: /* 0x05e */ - *ksymp = GDK_KEY_dead_circumflex; break; - case GDK_KEY_grave: /* 0x060 */ - *ksymp = GDK_KEY_dead_grave; break; - case GDK_KEY_asciitilde: /* 0x07e */ - *ksymp = GDK_KEY_dead_tilde; break; - case GDK_KEY_diaeresis: /* 0x0a8 */ - *ksymp = GDK_KEY_dead_diaeresis; break; - case GDK_KEY_degree: /* 0x0b0 */ - *ksymp = GDK_KEY_dead_abovering; break; - case GDK_KEY_acute: /* 0x0b4 */ - *ksymp = GDK_KEY_dead_acute; break; - case GDK_KEY_periodcentered: /* 0x0b7 */ - *ksymp = GDK_KEY_dead_abovedot; break; - case GDK_KEY_cedilla: /* 0x0b8 */ - *ksymp = GDK_KEY_dead_cedilla; break; - case GDK_KEY_breve: /* 0x1a2 */ - *ksymp = GDK_KEY_dead_breve; break; - case GDK_KEY_ogonek: /* 0x1b2 */ - *ksymp = GDK_KEY_dead_ogonek; break; - case GDK_KEY_caron: /* 0x1b7 */ - *ksymp = GDK_KEY_dead_caron; break; - case GDK_KEY_doubleacute: /* 0x1bd */ - *ksymp = GDK_KEY_dead_doubleacute; break; - case GDK_KEY_abovedot: /* 0x1ff */ - *ksymp = GDK_KEY_dead_abovedot; break; - case 0x1000384: /* Greek tonos */ - *ksymp = GDK_KEY_dead_acute; break; - case GDK_KEY_Greek_accentdieresis: /* 0x7ae */ - *ksymp = GDK_KEY_Greek_accentdieresis; break; - default: - /* By default use the keysym as such. This takes care of for - * instance the dead U+09CD (BENGALI VIRAMA) on the ekushey - * Bengali layout. - */ - *ksymp = keysym; break; - } + return keymap->gdkwin32_keymap_impl->modbits_to_level (info, modbits); } -/* keypad decimal mark depends on active keyboard layout - * return current decimal mark as unicode character - */ -guint32 -_gdk_win32_keymap_get_decimal_mark (GdkWin32Keymap *keymap) +static WCHAR +vk_to_char_fuzzy (GdkWin32Keymap *keymap, + GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256], + BYTE extra_mod_bits, + BYTE *consumed_mod_bits, + gboolean *is_dead, + BYTE vk) { - if (keymap != NULL && - keymap->layout_handles->len > 0 && - g_array_index (keymap->options, GdkWin32KeyGroupOptions, keymap->active_layout).decimal_mark) - return g_array_index (keymap->options, GdkWin32KeyGroupOptions, keymap->active_layout).decimal_mark; - - return (guint32) '.'; + return keymap->gdkwin32_keymap_impl->vk_to_char_fuzzy (info, keystate, extra_mod_bits, + consumed_mod_bits, is_dead, vk); } -static gboolean -layouts_are_the_same (GArray *array, HKL *hkls, int hkls_len) +/* + * Get the file path of the keyboard layout dll. + * The result is heap-allocated and should be freed with g_free(). + */ +static char* +get_keyboard_layout_file (const char *layout_name) { - int i; + HKEY hkey = 0; + DWORD var_type = REG_SZ; + char *result = NULL; + DWORD file_name_len = 0; + int dir_len = 0; + int buf_len = 0; - if (hkls_len != array->len) - return FALSE; + static const char prefix[] = "SYSTEM\\CurrentControlSet\\Control\\" + "Keyboard Layouts\\"; + char kbdKeyPath[sizeof (prefix) + KL_NAMELENGTH]; - for (i = 0; i < hkls_len; i++) - if (hkls[i] != g_array_index (array, HKL, i)) - return FALSE; + g_snprintf (kbdKeyPath, sizeof (prefix) + KL_NAMELENGTH, "%s%s", prefix, + layout_name); - return TRUE; -} + if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, (LPCSTR) kbdKeyPath, 0, + KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) + goto fail1; -static void -check_that_active_layout_is_in_sync (GdkWin32Keymap *keymap) -{ - HKL hkl; - HKL cached_hkl; - wchar_t hkl_name[KL_NAMELENGTH]; + /* Get sizes */ + if (RegQueryValueExA (hkey, "Layout File", 0, &var_type, 0, + &file_name_len) != ERROR_SUCCESS) + goto fail2; - if (keymap->layout_handles->len <= 0) - return; + dir_len = GetSystemDirectoryA (0, 0); /* includes \0 */ + if (dir_len == 0) + goto fail2; - hkl = GetKeyboardLayout (0); - cached_hkl = g_array_index (keymap->layout_handles, HKL, keymap->active_layout); + /* Allocate buffer */ + buf_len = dir_len + (int) strlen ("\\") + file_name_len; + result = (char*) g_malloc (buf_len); - if (hkl != cached_hkl) - { - if (!GetKeyboardLayoutNameW (hkl_name)) - wcscpy_s (hkl_name, KL_NAMELENGTH, L"(NULL)"); + /* Append system directory. The -1 is because dir_len includes \0 */ + if (GetSystemDirectoryA (&result[0], dir_len) != dir_len - 1) + goto fail3; - g_warning ("Cached active layout #%d (0x%p) does not match actual layout %S, 0x%p", - keymap->active_layout, cached_hkl, hkl_name, hkl); - } -} + /* Append directory separator */ + result[dir_len - 1] = '\\'; -static int -sort_key_nodes_by_gdk_keyval (gconstpointer a, - gconstpointer b) -{ - const GdkWin32KeyNode *one = a; - const GdkWin32KeyNode *two = b; + /* Append file name */ + if (RegQueryValueExA (hkey, "Layout File", 0, &var_type, + (LPBYTE) &result[dir_len], &file_name_len) + != ERROR_SUCCESS) + goto fail3; - if (one->gdk_keycode < two->gdk_keycode) - return -1; - else if (one->gdk_keycode > two->gdk_keycode) - return 1; + result[dir_len + file_name_len] = '\0'; - if (one->level < two->level) - return -1; - else if (one->level > two->level) - return 1; + RegCloseKey (hkey); + return result; - return 0; +fail3: + g_free (result); +fail2: + RegCloseKey (hkey); +fail1: + return NULL; } static void -update_keymap (GdkKeymap *gdk_keymap) -{ - int hkls_len; - static int hkls_size = 0; - static HKL *hkls = NULL; - gboolean no_list; - static guint current_serial = 0; - int i, group; - GdkWin32KeyLevelState level; - GdkWin32KeyGroupOptions *options; - GdkWin32Keymap *keymap = GDK_WIN32_KEYMAP (gdk_keymap); - int keysym_tab_size; - - guchar key_state[KEY_STATE_SIZE]; - guint scancode; - guint vk; - guint *keygroup; - - if (keymap->keysym_tab != NULL && - current_serial == _gdk_keymap_serial) - return; - - no_list = FALSE; - hkls_len = GetKeyboardLayoutList (0, NULL); - - if (hkls_len <= 0) - { - hkls_len = 1; - no_list = TRUE; - } - else if (hkls_len > 255) - { - hkls_len = 255; - } +clear_keyboard_layout_info (gpointer data) +{ + GdkWin32KeymapLayoutInfo *layout_info = (GdkWin32KeymapLayoutInfo*) data; + + g_free (layout_info->file); + + if (layout_info->key_entries != NULL) + g_array_unref (layout_info->key_entries); + + if (layout_info->reverse_lookup_table != NULL) + g_hash_table_destroy (layout_info->reverse_lookup_table); + + if (layout_info->lib != NULL) + FreeLibrary (layout_info->lib); + + memset (layout_info, 0, sizeof (GdkWin32KeymapLayoutInfo)); +} + +#define DEFINE_SPECIAL(map) \ + map (VK_CANCEL, GDK_KEY_Cancel) \ + map (VK_BACK, GDK_KEY_BackSpace) \ + map (VK_CLEAR, GDK_KEY_Clear) \ + map (VK_RETURN, GDK_KEY_Return) \ + map (VK_LSHIFT, GDK_KEY_Shift_L) \ + map (VK_LCONTROL, GDK_KEY_Control_L) \ + map (VK_LMENU, GDK_KEY_Alt_L) \ + map (VK_PAUSE, GDK_KEY_Pause) \ + map (VK_ESCAPE, GDK_KEY_Escape) \ + map (VK_PRIOR, GDK_KEY_Prior) \ + map (VK_NEXT, GDK_KEY_Next) \ + map (VK_END, GDK_KEY_End) \ + map (VK_HOME, GDK_KEY_Home) \ + map (VK_LEFT, GDK_KEY_Left) \ + map (VK_UP, GDK_KEY_Up) \ + map (VK_RIGHT, GDK_KEY_Right) \ + map (VK_DOWN, GDK_KEY_Down) \ + map (VK_SELECT, GDK_KEY_Select) \ + map (VK_PRINT, GDK_KEY_Print) \ + map (VK_EXECUTE, GDK_KEY_Execute) \ + map (VK_INSERT, GDK_KEY_Insert) \ + map (VK_DELETE, GDK_KEY_Delete) \ + map (VK_HELP, GDK_KEY_Help) \ + map (VK_LWIN, GDK_KEY_Meta_L) \ + map (VK_RWIN, GDK_KEY_Meta_R) \ + map (VK_APPS, GDK_KEY_Menu) \ + map (VK_DECIMAL, GDK_KEY_KP_Decimal) \ + map (VK_MULTIPLY, GDK_KEY_KP_Multiply) \ + map (VK_ADD, GDK_KEY_KP_Add) \ + map (VK_SEPARATOR, GDK_KEY_KP_Separator) \ + map (VK_SUBTRACT, GDK_KEY_KP_Subtract) \ + map (VK_DIVIDE, GDK_KEY_KP_Divide) \ + map (VK_NUMPAD0, GDK_KEY_KP_0) \ + map (VK_NUMPAD1, GDK_KEY_KP_1) \ + map (VK_NUMPAD2, GDK_KEY_KP_2) \ + map (VK_NUMPAD3, GDK_KEY_KP_3) \ + map (VK_NUMPAD4, GDK_KEY_KP_4) \ + map (VK_NUMPAD5, GDK_KEY_KP_5) \ + map (VK_NUMPAD6, GDK_KEY_KP_6) \ + map (VK_NUMPAD7, GDK_KEY_KP_7) \ + map (VK_NUMPAD8, GDK_KEY_KP_8) \ + map (VK_NUMPAD9, GDK_KEY_KP_9) \ + map (VK_F1, GDK_KEY_F1) \ + map (VK_F2, GDK_KEY_F2) \ + map (VK_F3, GDK_KEY_F3) \ + map (VK_F4, GDK_KEY_F4) \ + map (VK_F5, GDK_KEY_F5) \ + map (VK_F6, GDK_KEY_F6) \ + map (VK_F7, GDK_KEY_F7) \ + map (VK_F8, GDK_KEY_F8) \ + map (VK_F9, GDK_KEY_F9) \ + map (VK_F10, GDK_KEY_F10) \ + map (VK_F11, GDK_KEY_F11) \ + map (VK_F12, GDK_KEY_F12) \ + map (VK_F13, GDK_KEY_F13) \ + map (VK_F14, GDK_KEY_F14) \ + map (VK_F15, GDK_KEY_F15) \ + map (VK_F16, GDK_KEY_F16) \ + map (VK_F17, GDK_KEY_F17) \ + map (VK_F18, GDK_KEY_F18) \ + map (VK_F19, GDK_KEY_F19) \ + map (VK_F20, GDK_KEY_F20) \ + map (VK_F21, GDK_KEY_F21) \ + map (VK_F22, GDK_KEY_F22) \ + map (VK_F23, GDK_KEY_F23) \ + map (VK_F24, GDK_KEY_F24) \ + map (VK_NUMLOCK, GDK_KEY_Num_Lock) \ + map (VK_SCROLL, GDK_KEY_Scroll_Lock) \ + map (VK_RSHIFT, GDK_KEY_Shift_R) \ + map (VK_RCONTROL, GDK_KEY_Control_R) \ + map (VK_RMENU, GDK_KEY_Alt_R) + + +#define DEFINE_DEAD(map) \ + map ('"', /* 0x022 */ GDK_KEY_dead_diaeresis) \ + map ('\'', /* 0x027 */ GDK_KEY_dead_acute) \ + map (GDK_KEY_asciicircum, /* 0x05e */ GDK_KEY_dead_circumflex) \ + map (GDK_KEY_grave, /* 0x060 */ GDK_KEY_dead_grave) \ + map (GDK_KEY_asciitilde, /* 0x07e */ GDK_KEY_dead_tilde) \ + map (GDK_KEY_diaeresis, /* 0x0a8 */ GDK_KEY_dead_diaeresis) \ + map (GDK_KEY_degree, /* 0x0b0 */ GDK_KEY_dead_abovering) \ + map (GDK_KEY_acute, /* 0x0b4 */ GDK_KEY_dead_acute) \ + map (GDK_KEY_periodcentered, /* 0x0b7 */ GDK_KEY_dead_abovedot) \ + map (GDK_KEY_cedilla, /* 0x0b8 */ GDK_KEY_dead_cedilla) \ + map (GDK_KEY_breve, /* 0x1a2 */ GDK_KEY_dead_breve) \ + map (GDK_KEY_ogonek, /* 0x1b2 */ GDK_KEY_dead_ogonek) \ + map (GDK_KEY_caron, /* 0x1b7 */ GDK_KEY_dead_caron) \ + map (GDK_KEY_doubleacute, /* 0x1bd */ GDK_KEY_dead_doubleacute) \ + map (GDK_KEY_abovedot, /* 0x1ff */ GDK_KEY_dead_abovedot) \ + map (0x1000384, /* Greek tonos */ GDK_KEY_dead_acute) \ + map (GDK_KEY_Greek_accentdieresis, /* 0x7ae */ GDK_KEY_Greek_accentdieresis) - if (hkls_size < hkls_len) - { - hkls = g_renew (HKL, hkls, hkls_len); - hkls_size = hkls_len; - } - if (hkls_len != GetKeyboardLayoutList (hkls_len, hkls)) - { - if (!no_list) - return; +static guint +vk_and_mod_bits_to_gdk_keysym (GdkWin32Keymap *keymap, + GdkWin32KeymapLayoutInfo *info, + guint vk, + const BYTE keystate[256], + BYTE mod_bits, + BYTE *consumed_mod_bits) - hkls[0] = GetKeyboardLayout (0); - hkls_len = 1; - } +{ + gboolean is_dead = FALSE; + gunichar c; + guint sym; - if (layouts_are_the_same (keymap->layout_handles, hkls, hkls_len)) - { - check_that_active_layout_is_in_sync (keymap); - current_serial = _gdk_keymap_serial; + if (consumed_mod_bits) + *consumed_mod_bits = 0; - return; + /* Handle special key: Tab */ + if (vk == VK_TAB) + { + if (consumed_mod_bits) + *consumed_mod_bits = mod_bits & KBDSHIFT; + return (mod_bits & KBDSHIFT) ? GDK_KEY_ISO_Left_Tab : GDK_KEY_Tab; } - GDK_NOTE (EVENTS, g_print ("\nHave %d keyboard layouts:", hkls_len)); - - for (i = 0; i < hkls_len; i++) + /* Handle other special keys */ + switch (vk) { - GDK_NOTE (EVENTS, g_print (" 0x%p", hkls[i])); + #define MAP(a_vk, a_gdk) case a_vk: return a_gdk; - if (GetKeyboardLayout (0) == hkls[i]) - { - wchar_t hkl_name[KL_NAMELENGTH]; + DEFINE_SPECIAL (MAP) - if (!GetKeyboardLayoutNameW (hkl_name)) - wcscpy_s (hkl_name, KL_NAMELENGTH, L"(NULL)"); + /* Non-bijective mappings: */ + MAP (VK_SHIFT, GDK_KEY_Shift_L) + MAP (VK_CONTROL, GDK_KEY_Control_L) + MAP (VK_MENU, GDK_KEY_Alt_L) + MAP (VK_SNAPSHOT, GDK_KEY_Print) - GDK_NOTE (EVENTS, g_print ("(active, %S)", hkl_name)); - } + #undef MAP } - GDK_NOTE (EVENTS, g_print ("\n")); - - keysym_tab_size = hkls_len * 256 * 2 * 4; + /* Handle regular keys (including dead keys) */ + c = vk_to_char_fuzzy (keymap, info, keystate, mod_bits, + consumed_mod_bits, &is_dead, vk); - if (hkls_len != keymap->layout_handles->len) - keymap->keysym_tab = g_renew (guint, keymap->keysym_tab, keysym_tab_size); + if (c == WCH_NONE) + return GDK_KEY_VoidSymbol; - memset (key_state, 0, sizeof(key_state)); - memset (keymap->keysym_tab, 0, keysym_tab_size); - g_array_set_size (keymap->layout_handles, hkls_len); - g_array_set_size (keymap->options, hkls_len); + sym = gdk_unicode_to_keyval (c); - for (i = 0; i < hkls_len; i++) + if (is_dead) { - options = &g_array_index (keymap->options, GdkWin32KeyGroupOptions, i); - - options->decimal_mark = 0; - options->scancode_rshift = 0; - options->has_altgr = FALSE; - options->dead_keys = g_array_new (FALSE, FALSE, sizeof (GdkWin32KeyNode)); - g_array_set_clear_func (options->dead_keys, (GDestroyNotify) gdk_win32_key_node_clear); - - g_array_index (keymap->layout_handles, HKL, i) = hkls[i]; - - if (hkls[i] == _gdk_input_locale) - keymap->active_layout = i; + switch (sym) + { + #define MAP(a_nondead, a_dead) case a_nondead: return a_dead; + DEFINE_DEAD (MAP) + #undef MAP + } } - for (vk = 0; vk < KEY_STATE_SIZE; vk++) - { - for (group = 0; group < hkls_len; group++) - { - options = &g_array_index (keymap->options, GdkWin32KeyGroupOptions, group); - scancode = MapVirtualKeyEx (vk, 0, hkls[group]); - keygroup = &keymap->keysym_tab[(vk * hkls_len + group) * GDK_WIN32_LEVEL_COUNT]; - - /* MapVirtualKeyEx() fails to produce a scancode for VK_DIVIDE and VK_PAUSE. - * Ignore that, handle_special() will figure out a Gdk keyval for these - * without needing a scancode. - */ - if (scancode == 0 && - vk != VK_DIVIDE && - vk != VK_PAUSE) - { - for (level = GDK_WIN32_LEVEL_NONE; level < GDK_WIN32_LEVEL_COUNT; level++) - keygroup[level] = GDK_KEY_VoidSymbol; - - continue; - } - - if (vk == VK_RSHIFT) - options->scancode_rshift = scancode; - - key_state[vk] = 0x80; + return sym; +} - for (level = GDK_WIN32_LEVEL_NONE; level < GDK_WIN32_LEVEL_COUNT; level++) - { - guint *ksymp = &keygroup[level]; +static int +gdk_keysym_to_key_entry_index (GdkWin32KeymapLayoutInfo *info, + guint sym) +{ + gunichar c; + gintptr index; - set_level_vks (key_state, level); + /* Special cases */ + if (sym == GDK_KEY_Tab) + return VK_TAB; + if (sym == GDK_KEY_ISO_Left_Tab) + return 256; - *ksymp = 0; + /* Generic non-printable keys */ + switch (sym) + { + #define MAP(a_vk, a_gdk) case a_gdk: return a_vk; + DEFINE_SPECIAL (MAP) + #undef MAP + } - /* First, handle those virtual keys that we always want - * as special GDK_* keysyms, even if ToAsciiEx might - * turn some them into an ASCII character (like TAB and - * ESC). - */ - handle_special (vk, ksymp, level); + /* Fix up dead keys */ + #define MAP(a_nondead, a_dead) \ + if (sym == a_dead) \ + sym = a_nondead; + DEFINE_DEAD (MAP) + #undef MAP - if ((*ksymp == 0) || - ((vk == VK_DECIMAL) && (level == GDK_WIN32_LEVEL_NONE))) - { - wchar_t wcs[10]; - int k; - guint keysym; - GdkWin32KeyNode dead_key; - - wcs[0] = wcs[1] = 0; - k = ToUnicodeEx (vk, scancode, key_state, - wcs, G_N_ELEMENTS (wcs), - 0, hkls[group]); -#if 0 - g_print ("ToUnicodeEx(%#02x, %d: %d): %d, %04x %04x\n", - vk, scancode, level, k, - wcs[0], wcs[1]); -#endif - switch (k) - { - case 1: - if ((vk == VK_DECIMAL) && (level == GDK_WIN32_LEVEL_NONE)) - options->decimal_mark = wcs[0]; - else - *ksymp = gdk_unicode_to_keyval (wcs[0]); - break; - case -1: - keysym = gdk_unicode_to_keyval (wcs[0]); - - /* It is a dead key, and it has been stored in - * the keyboard layout's state by - * ToAsciiEx()/ToUnicodeEx(). Yes, this is an - * incredibly silly API! Make the keyboard - * layout forget it by calling - * ToAsciiEx()/ToUnicodeEx() once more, with the - * virtual key code and scancode for the - * spacebar, without shift or AltGr. Otherwise - * the next call to ToAsciiEx() with a different - * key would try to combine with the dead key. - */ - reset_after_dead (key_state, hkls[group]); - - /* Use dead keysyms instead of "undead" ones */ - handle_dead (keysym, ksymp); - - dead_key.undead_gdk_keycode = keysym; - dead_key.vk = vk; - dead_key.level = level; - dead_key.gdk_keycode = *ksymp; - dead_key.combinations = NULL; - g_array_append_val (options->dead_keys, dead_key); - break; - case 0: - /* Seems to be necessary to "reset" the keyboard layout - * in this case, too. Otherwise problems on NT4. - */ - reset_after_dead (key_state, hkls[group]); - break; - default: -#if 0 - GDK_NOTE (EVENTS, - g_print ("ToUnicodeEx returns %d " - "for vk:%02x, sc:%02x%s%s\n", - k, vk, scancode, - (shift&0x1 ? " shift" : ""), - (shift&0x2 ? " altgr" : ""))); -#endif - break; - } - } + /* Try converting to Unicode and back */ + c = gdk_keyval_to_unicode (sym); - if (*ksymp == 0) - *ksymp = GDK_KEY_VoidSymbol; - } + g_return_val_if_fail (info->reverse_lookup_table != NULL, -1); - key_state[vk] = 0; - - /* Check if keyboard has an AltGr key by checking if - * the mapping with Control+Alt is different. - * Don't test CapsLock here, as it does not seem to affect - * dead keys themselves, only the results of dead key combinations. - */ - if (!options->has_altgr) - if ((keygroup[GDK_WIN32_LEVEL_ALTGR] != GDK_KEY_VoidSymbol && - keygroup[GDK_WIN32_LEVEL_NONE] != keygroup[GDK_WIN32_LEVEL_ALTGR]) || - (keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR] != GDK_KEY_VoidSymbol && - keygroup[GDK_WIN32_LEVEL_SHIFT] != keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR])) - options->has_altgr = TRUE; - } + index = -1; + if (g_hash_table_lookup_extended (info->reverse_lookup_table, + GINT_TO_POINTER (c), + NULL, (gpointer*) &index)) + { + return index; } - - scancode = 0x0; - - for (group = 0; group < hkls_len; group++) + else { - options = &g_array_index (keymap->options, GdkWin32KeyGroupOptions, group); - - for (i = 0; i < options->dead_keys->len; i++) - { - wchar_t wcs[10]; - int k; - GdkWin32KeyNode *dead_key; - GdkWin32KeyNode combo; - - dead_key = &g_array_index (options->dead_keys, GdkWin32KeyNode, i); - - for (vk = 0; vk < KEY_STATE_SIZE; vk++) - { - for (level = GDK_WIN32_LEVEL_NONE; level < GDK_WIN32_LEVEL_COUNT; level++) - { - /* Prime the ToUnicodeEx() internal state */ - wcs[0] = wcs[1] = 0; - set_level_vks (key_state, dead_key->level); - k = ToUnicodeEx (dead_key->vk, scancode, key_state, - wcs, G_N_ELEMENTS (wcs), - 0, hkls[group]); - switch (k) - { - case -1: - /* Okay */ - break; - default: - /* Expected a dead key, got something else */ - reset_after_dead (key_state, hkls[group]); - continue; - } - - /* Check how it combines with vk */ - wcs[0] = wcs[1] = 0; - set_level_vks (key_state, level); - k = ToUnicodeEx (vk, scancode, key_state, - wcs, G_N_ELEMENTS (wcs), - 0, hkls[group]); - - if (k == 0) - { - reset_after_dead (key_state, hkls[group]); - } - else if (k == -1) - { - /* Dead key chaining? TODO: support this (deeper tree?) */ - reset_after_dead (key_state, hkls[group]); - } - else if (k == 1) - { - combo.vk = vk; - combo.level = level; - combo.gdk_keycode = gdk_unicode_to_keyval (wcs[0]); - combo.undead_gdk_keycode = combo.gdk_keycode; - combo.combinations = NULL; - - if (dead_key->combinations == NULL) - { - dead_key->combinations = g_array_new (FALSE, FALSE, sizeof (GdkWin32KeyNode)); - g_array_set_clear_func (dead_key->combinations, (GDestroyNotify) gdk_win32_key_node_clear); - } - -#if 0 - { - char *dead_key_undead_u8, *wcs_u8; - wchar_t t = gdk_keyval_to_unicode (dead_key->undead_gdk_keycode); - dead_key_undead_u8 = g_utf16_to_utf8 (&t, 1, NULL, NULL, NULL); - wcs_u8 = g_utf16_to_utf8 (wcs, 1, NULL, NULL, NULL); - g_fprintf (stdout, "%d %s%s%s0x%02x (%s) + %s%s%s0x%02x = 0x%04x (%s)\n", group, - (dead_key->level == GDK_WIN32_LEVEL_SHIFT || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_ALTGR || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "SHIFT-" : " ", - (dead_key->level == GDK_WIN32_LEVEL_CAPSLOCK || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK || - dead_key->level == GDK_WIN32_LEVEL_CAPSLOCK_ALTGR || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "CAPSLOCK-" : " ", - (dead_key->level == GDK_WIN32_LEVEL_ALTGR || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_ALTGR || - dead_key->level == GDK_WIN32_LEVEL_CAPSLOCK_ALTGR || - dead_key->level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "ALTGR-" : " ", - dead_key->vk, - dead_key_undead_u8, - (combo.level == GDK_WIN32_LEVEL_SHIFT || - combo.level == GDK_WIN32_LEVEL_SHIFT_ALTGR || - combo.level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK || - combo.level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "SHIFT-" : " ", - (combo.level == GDK_WIN32_LEVEL_CAPSLOCK || - combo.level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK || - combo.level == GDK_WIN32_LEVEL_CAPSLOCK_ALTGR || - combo.level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "CAPSLOCK-" : " ", - (combo.level == GDK_WIN32_LEVEL_ALTGR || - combo.level == GDK_WIN32_LEVEL_SHIFT_ALTGR || - combo.level == GDK_WIN32_LEVEL_CAPSLOCK_ALTGR || - combo.level == GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR) ? "ALTGR-" : " ", - vk, - wcs[0], - wcs_u8); - g_free (dead_key_undead_u8); - g_free (wcs_u8); - } -#endif - - g_array_append_val (dead_key->combinations, combo); - } - } - } - } - - g_array_sort (options->dead_keys, (GCompareFunc) sort_key_nodes_by_gdk_keyval); + return -1; } - - GDK_NOTE (EVENTS, print_keysym_tab (keymap)); - - check_that_active_layout_is_in_sync (keymap); - current_serial = _gdk_keymap_serial; } -static gboolean -find_deadkey_by_keyval (GArray *dead_keys, guint16 keyval, gsize *index) +static GdkModifierType +mod_bits_to_gdk_mod_mask (BYTE mod_bits) { - gsize deadkey_i; - gsize deadkey_i_max; - - if (dead_keys->len == 0) - return FALSE; + GdkModifierType result = 0; + if (mod_bits & KBDSHIFT) + result |= GDK_SHIFT_MASK; + if (mod_bits & KBDCTRL) + result |= GDK_CONTROL_MASK; + if (mod_bits & KBDALT) + result |= GDK_ALT_MASK; + return result; +} - deadkey_i = 0; - deadkey_i_max = dead_keys->len - 1; +static BYTE +gdk_mod_mask_to_mod_bits (GdkModifierType mod_mask) +{ + BYTE result = 0; + if (mod_mask & GDK_SHIFT_MASK) + result |= KBDSHIFT; + if (mod_mask & GDK_CONTROL_MASK) + result |= KBDCTRL; + if (mod_mask & GDK_ALT_MASK) + result |= KBDALT; + return result; +} - while (deadkey_i != deadkey_i_max) +static void +get_lock_state (BYTE lock_state[256]) +{ + static const guint mode_keys[] = { - GdkWin32KeyNode *dead_key; - gsize middle; - - if (g_array_index (dead_keys, GdkWin32KeyNode, deadkey_i).gdk_keycode == keyval) - { - break; - } - else if (g_array_index (dead_keys, GdkWin32KeyNode, deadkey_i_max).gdk_keycode == keyval) - { - deadkey_i = deadkey_i_max; - break; - } - else if (deadkey_i + 1 == deadkey_i_max) - { - break; - } + VK_CAPITAL, + VK_KANA, VK_HANGUL, VK_JUNJA, VK_FINAL, VK_HANJA, VK_KANJI, /* Is this correct? */ + VK_NUMLOCK, VK_SCROLL + }; - middle = deadkey_i + (deadkey_i_max - deadkey_i) / 2; - dead_key = &g_array_index (dead_keys, GdkWin32KeyNode, middle); + BYTE keystate[256] = {0}; + guint i; - if (dead_key->gdk_keycode < keyval) - deadkey_i = middle; - else if (dead_key->gdk_keycode > keyval) - deadkey_i_max = middle; - else - deadkey_i = deadkey_i_max = middle; - } + GetKeyboardState (keystate); - if (g_array_index (dead_keys, GdkWin32KeyNode, deadkey_i).gdk_keycode == keyval) - { - *index = deadkey_i; + /* Copy over some keystates like numlock and capslock */ + for (i = 0; i < G_N_ELEMENTS(mode_keys); ++i) + lock_state[mode_keys[i]] = keystate[mode_keys[i]] & 0x1; +} - return TRUE; - } - return FALSE; +/* keypad decimal mark depends on active keyboard layout + * return current decimal mark as unicode character + */ +guint32 +_gdk_win32_keymap_get_decimal_mark (GdkWin32Keymap *keymap) +{ + guint32 c = MapVirtualKeyW (VK_DECIMAL, MAPVK_VK_TO_CHAR); + if (!c) + c = (guint32) '.'; + return c; } -GdkWin32KeymapMatch -gdk_win32_keymap_check_compose (GdkWin32Keymap *keymap, - guint16 *compose_buffer, - gsize compose_buffer_len, - guint16 *output, - gsize *output_len) +static void +update_keymap (GdkWin32Keymap *keymap) { - int partial_match; - guint8 active_group; - gsize deadkey_i, node_i; - GdkWin32KeyNode *dead_key; - GdkWin32KeyGroupOptions *options; - GdkWin32KeymapMatch match; - gsize output_size; - - g_return_val_if_fail (output != NULL && output_len != NULL, GDK_WIN32_KEYMAP_MATCH_NONE); - - if (compose_buffer_len < 1) - return GDK_WIN32_KEYMAP_MATCH_NONE; - - output_size = *output_len; - - active_group = _gdk_win32_keymap_get_active_group (keymap); - options = &g_array_index (keymap->options, GdkWin32KeyGroupOptions, active_group); + HKL current_layout; + BOOL changed = FALSE; + int n_layouts; + int i; - partial_match = -1; - match = GDK_WIN32_KEYMAP_MATCH_NONE; - - if (find_deadkey_by_keyval (options->dead_keys, compose_buffer[0], &deadkey_i)) + if (keymap->current_serial == _gdk_keymap_serial && + keymap->layout_handles->len > 0) { - while (deadkey_i > 0 && - g_array_index (options->dead_keys, GdkWin32KeyNode, deadkey_i - 1).gdk_keycode == compose_buffer[0]) - deadkey_i--; - - /* Hardcoded 2-tier tree here (dead key + non dead key = character). - * TODO: support trees with arbitrary depth for dead key chaining. - */ - dead_key = &g_array_index (options->dead_keys, GdkWin32KeyNode, deadkey_i); - - /* "Partial match" means "matched the whole sequence except the last key" - * (right now the sequence only has 2 keys, so this turns into "matched - * at least the first key"). - * "last key" should be identified by having NULL further combinations. - * As a heuristic, convert the buffer contents into keyvals and use - * them as-is (normally there should be a separate unichar buffer for - * each combination, but we do not store these). - */ - partial_match = deadkey_i; - - if (compose_buffer_len < 2) - match = GDK_WIN32_KEYMAP_MATCH_INCOMPLETE; - - for (node_i = 0; - match != GDK_WIN32_KEYMAP_MATCH_INCOMPLETE && - node_i < dead_key->combinations->len; - node_i++) - { - GdkWin32KeyNode *node; - - node = &g_array_index (dead_key->combinations, GdkWin32KeyNode, node_i); + return; + } - if (keymap->keysym_tab[(node->vk * keymap->layout_handles->len + active_group) * GDK_WIN32_LEVEL_COUNT + node->level] == compose_buffer[1]) - { - match = GDK_WIN32_KEYMAP_MATCH_EXACT; - *output_len = 0; + n_layouts = GetKeyboardLayoutList (0, 0); + g_array_set_size (keymap->layout_handles, n_layouts); + n_layouts = GetKeyboardLayoutList (n_layouts, + &g_array_index(keymap->layout_handles, + HKL, 0)); - if (*output_len < output_size && node->gdk_keycode != 0) - output[(*output_len)++] = node->gdk_keycode; + g_array_set_size (keymap->layout_infos, n_layouts); - break; - } - } - } + current_layout = GetKeyboardLayout (0); - if (match == GDK_WIN32_KEYMAP_MATCH_EXACT || - match == GDK_WIN32_KEYMAP_MATCH_INCOMPLETE) + for (i = 0; i < n_layouts; ++i) { - return match; - } + GdkWin32KeymapLayoutInfo *info = &g_array_index(keymap->layout_infos, + GdkWin32KeymapLayoutInfo, i); + HKL hkl = g_array_index(keymap->layout_handles, HKL, i); - if (partial_match >= 0) - { - if (compose_buffer_len == 2) + if (info->handle != hkl) { - dead_key = &g_array_index (options->dead_keys, GdkWin32KeyNode, partial_match); - *output_len = 0; + changed = TRUE; + + /* Free old data */ + clear_keyboard_layout_info (info); + + /* Load new data */ + info->handle = hkl; + ActivateKeyboardLayout (hkl, 0); + GetKeyboardLayoutNameA (info->name); - if (output_size >= 1) - output[(*output_len)++] = dead_key->undead_gdk_keycode; + info->file = get_keyboard_layout_file (info->name); - if (output_size >= 2) + if (load_layout_dll (keymap, info->file, info)) { - gsize second_deadkey_i; - - /* Special case for "deadkey + deadkey = space-version-of-deadkey, space-version-of-deadkey" combinations. - * Normally the result is a sequence of 2 unichars, but we do not store this. - * For "deadkey + nondeadkey = space-version-of-deadkey, nondeadkey", we can use compose_buffer - * contents as-is, but space version of a dead key need to be looked up separately. - */ - if (find_deadkey_by_keyval (options->dead_keys, compose_buffer[1], &second_deadkey_i)) - output[(*output_len)++] = g_array_index (options->dead_keys, GdkWin32KeyNode, second_deadkey_i).undead_gdk_keycode; - else - output[(*output_len)++] = compose_buffer[1]; + info->key_entries = g_array_new (FALSE, FALSE, + sizeof (GdkWin32KeymapKeyEntry)); + + info->reverse_lookup_table = g_hash_table_new (g_direct_hash, + g_direct_equal); + init_vk_lookup_table (keymap, info); } } - return GDK_WIN32_KEYMAP_MATCH_PARTIAL; + if (info->handle == current_layout) + keymap->active_layout = i; } - return GDK_WIN32_KEYMAP_MATCH_NONE; + if (changed) + ActivateKeyboardLayout (current_layout, 0); + + keymap->current_serial = _gdk_keymap_serial; } guint8 _gdk_win32_keymap_get_rshift_scancode (GdkWin32Keymap *keymap) { - if (keymap != NULL && - keymap->layout_handles->len > 0) - return g_array_index (keymap->options, GdkWin32KeyGroupOptions, keymap->active_layout).scancode_rshift; - - return 0; + return MapVirtualKey (VK_RSHIFT, MAPVK_VK_TO_VSC); } void @@ -1163,10 +601,11 @@ _gdk_win32_keymap_set_active_layout (GdkWin32Keymap *keymap, gboolean _gdk_win32_keymap_has_altgr (GdkWin32Keymap *keymap) { - if (keymap != NULL && - keymap->layout_handles->len > 0) - return g_array_index (keymap->options, GdkWin32KeyGroupOptions, keymap->active_layout).has_altgr; - + /* We just return FALSE, since it doesn't really matter because AltGr + * is the same as Ctrl + Alt. Hence, we will never get a GDK_MOD2_MASK, + * rather we will just get GDK_CONTROL_MASK | GDK_ALT_MASK. I don't + * think there is any clean way to distinguish <Ctrl + Alt> from + * <AltGr> on Windows. */ return FALSE; } @@ -1191,6 +630,25 @@ _gdk_win32_display_get_keymap (GdkDisplay *display) return default_keymap; } +GdkModifierType +_gdk_win32_keymap_get_mod_mask (GdkWin32Keymap *keymap) +{ + GdkWin32KeymapLayoutInfo *layout_info; + BYTE keystate[256] = {0}; + BYTE mod_bits; + + update_keymap (keymap); + + layout_info = &g_array_index (keymap->layout_infos, GdkWin32KeymapLayoutInfo, + keymap->active_layout); + + GetKeyboardState (keystate); + + mod_bits = keystate_to_modbits (keymap, layout_info, keystate); + + return mod_bits_to_gdk_mod_mask (mod_bits); +} + static PangoDirection get_hkl_direction (HKL hkl) { @@ -1213,21 +671,20 @@ get_hkl_direction (HKL hkl) static PangoDirection gdk_win32_keymap_get_direction (GdkKeymap *gdk_keymap) { - HKL active_hkl; GdkWin32Keymap *keymap; - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); + HKL active_hkl; + + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), PANGO_DIRECTION_LTR); - if (gdk_keymap == NULL || gdk_keymap != default_keymap) - keymap = GDK_WIN32_KEYMAP (default_keymap); - else - keymap = GDK_WIN32_KEYMAP (gdk_keymap); + keymap = GDK_WIN32_KEYMAP (gdk_keymap); - update_keymap (GDK_KEYMAP (keymap)); + update_keymap (keymap); if (keymap->layout_handles->len <= 0) active_hkl = GetKeyboardLayout (0); else - active_hkl = g_array_index (keymap->layout_handles, HKL, keymap->active_layout); + active_hkl = g_array_index (keymap->layout_handles, HKL, + keymap->active_layout); return get_hkl_direction (active_hkl); } @@ -1239,18 +696,17 @@ gdk_win32_keymap_have_bidi_layouts (GdkKeymap *gdk_keymap) gboolean have_rtl = FALSE; gboolean have_ltr = FALSE; int group; - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); - if (gdk_keymap == NULL || gdk_keymap != default_keymap) - keymap = GDK_WIN32_KEYMAP (default_keymap); - else - keymap = GDK_WIN32_KEYMAP (gdk_keymap); + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), FALSE); + + keymap = GDK_WIN32_KEYMAP (gdk_keymap); - update_keymap (GDK_KEYMAP (keymap)); + update_keymap (keymap); for (group = 0; group < keymap->layout_handles->len; group++) { - if (get_hkl_direction (g_array_index (keymap->layout_handles, HKL, group)) == PANGO_DIRECTION_RTL) + if (get_hkl_direction (g_array_index (keymap->layout_handles, HKL, + group)) == PANGO_DIRECTION_RTL) have_rtl = TRUE; else have_ltr = TRUE; @@ -1288,70 +744,76 @@ gdk_win32_keymap_get_entries_for_keyval (GdkKeymap *gdk_keymap, guint keyval, GArray *retval) { - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); - guint len = retval->len; + GdkWin32Keymap *keymap; + BYTE keystate[256] = {0}; + int group; + guint len = retval->len; - g_return_val_if_fail (gdk_keymap == NULL || GDK_IS_KEYMAP (gdk_keymap), FALSE); + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), FALSE); g_return_val_if_fail (keyval != 0, FALSE); - /* Accept only the default keymap */ - if (gdk_keymap == NULL || gdk_keymap == default_keymap) - { - int vk; - GdkWin32Keymap *keymap; + keymap = GDK_WIN32_KEYMAP (gdk_keymap); - if (gdk_keymap == NULL) - keymap = GDK_WIN32_KEYMAP (default_keymap); - else - keymap = GDK_WIN32_KEYMAP (gdk_keymap); + update_keymap (keymap); - update_keymap (gdk_keymap); + for (group = 0; group < keymap->layout_handles->len; group++) + { + GdkWin32KeymapLayoutInfo *info = &g_array_index (keymap->layout_infos, + GdkWin32KeymapLayoutInfo, + group); + int entry_index = gdk_keysym_to_key_entry_index (info, keyval); - for (vk = 0; vk < KEY_STATE_SIZE; vk++) + while (entry_index >= 0) { - int group; - - for (group = 0; group < keymap->layout_handles->len; group++) + GdkWin32KeymapKeyEntry *entry = &g_array_index (info->key_entries, + GdkWin32KeymapKeyEntry, + entry_index); + BYTE base_modbits = entry->mod_bits; + BYTE extra_modbits; + GdkKeymapKey gdk_key = {0}; + + /* Add original key combination */ + gdk_key.keycode = entry->vk; + gdk_key.level = modbits_to_level (keymap, info, entry->mod_bits); + gdk_key.group = group; + + g_array_append_val (retval, gdk_key); + + /* Add combinations with modifiers that do not affect the translation */ + for (extra_modbits = 0; + extra_modbits <= info->max_modbit_value; + ++extra_modbits) { - GdkWin32KeyLevelState level; - - for (level = GDK_WIN32_LEVEL_NONE; level < GDK_WIN32_LEVEL_COUNT; level++) + BYTE modbits; + guint sym; + + /* We are only interested in masks which are orthogonal to the + * original mask. */ + if ((extra_modbits | base_modbits) == base_modbits || + (extra_modbits & base_modbits) != 0) + continue; + + modbits = base_modbits | extra_modbits; + + /* Check if the additional modifiers change the semantics. + * If they do not, add them. */ + sym = vk_and_mod_bits_to_gdk_keysym (keymap, info, entry->vk, + keystate, modbits, + NULL); + if (sym == keyval || sym == GDK_KEY_VoidSymbol) { - guint *keygroup; - - keygroup = &keymap->keysym_tab[(vk * keymap->layout_handles->len + group) * GDK_WIN32_LEVEL_COUNT]; - - if (keygroup[level] == keyval) - { - GdkKeymapKey key; - - key.keycode = vk; - key.group = group; - key.level = level; - g_array_append_val (retval, key); - } + gdk_key.keycode = entry->vk; + gdk_key.level = modbits_to_level (keymap, info, modbits); + gdk_key.group = group; + g_array_append_val (retval, gdk_key); } } - } - } -#ifdef G_ENABLE_DEBUG - if (_gdk_debug_flags & GDK_DEBUG_EVENTS) - { - guint i; - - g_print ("gdk_keymap_get_entries_for_keyval: %#.04x (%s):", - keyval, gdk_keyval_name (keyval)); - for (i = len; i < retval->len; i++) - { - GdkKeymapKey *entry = (GdkKeymapKey *) retval->data + i; - g_print (" %#.02x %d %d", entry->keycode, entry->group, entry->level); + entry_index = entry->next; } - g_print ("\n"); - } -#endif + } - return len < retval->len; + return retval->len > len; } static gboolean @@ -1361,92 +823,71 @@ gdk_win32_keymap_get_entries_for_keycode (GdkKeymap *gdk_keymap, guint **keyvals, int *n_entries) { + GdkWin32Keymap *keymap; GArray *key_array; GArray *keyval_array; int group; - GdkWin32Keymap *keymap; - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); + BYTE keystate[256] = {0}; + BYTE vk; - g_return_val_if_fail (gdk_keymap == NULL || GDK_IS_KEYMAP (gdk_keymap), FALSE); + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), FALSE); g_return_val_if_fail (n_entries != NULL, FALSE); - if (hardware_keycode <= 0 || - hardware_keycode >= KEY_STATE_SIZE || - (keys == NULL && keyvals == NULL) || - (gdk_keymap != NULL && gdk_keymap != default_keymap)) - { - /* Wrong keycode or NULL output arrays or wrong keymap */ - if (keys) - *keys = NULL; - if (keyvals) - *keyvals = NULL; - - *n_entries = 0; - return FALSE; - } + *n_entries = 0; - if (keys) + if (keys != NULL) key_array = g_array_new (FALSE, FALSE, sizeof (GdkKeymapKey)); else key_array = NULL; - if (keyvals) + if (keyvals != NULL) keyval_array = g_array_new (FALSE, FALSE, sizeof (guint)); else keyval_array = NULL; - keymap = GDK_WIN32_KEYMAP (default_keymap); - update_keymap (GDK_KEYMAP (keymap)); + keymap = GDK_WIN32_KEYMAP (gdk_keymap); + update_keymap (keymap); + + vk = hardware_keycode; for (group = 0; group < keymap->layout_handles->len; group++) { - GdkWin32KeyLevelState level; + GdkWin32KeymapLayoutInfo *info = &g_array_index (keymap->layout_infos, + GdkWin32KeymapLayoutInfo, + group); + int level; - for (level = GDK_WIN32_LEVEL_NONE; level < GDK_WIN32_LEVEL_COUNT; level++) + for (level = 0; level <= info->max_level; ++level) { - if (key_array) - { - GdkKeymapKey key; + BYTE modbits = info->level_to_modbits[level]; + BYTE consumed_modbits = 0; + GdkKeymapKey key = {0}; + guint keyval; - key.keycode = hardware_keycode; - key.group = group; - key.level = level; - g_array_append_val (key_array, key); - } + keyval = vk_and_mod_bits_to_gdk_keysym (keymap, info, vk, keystate, modbits, &consumed_modbits); - if (keyval_array) - { - guint keyval = keymap->keysym_tab[(hardware_keycode * keymap->layout_handles->len + group) * GDK_WIN32_LEVEL_COUNT + level]; - - g_array_append_val (keyval_array, keyval); - } - } - } + if (keyval == GDK_KEY_VoidSymbol || consumed_modbits != modbits) + continue; - *n_entries = group * GDK_WIN32_LEVEL_COUNT; + key.keycode = vk; + key.group = group; + key.level = level; - if ((key_array && key_array->len > 0) || - (keyval_array && keyval_array->len > 0)) - { - if (keys) - *keys = (GdkKeymapKey*) key_array->data; + if (key_array) + g_array_append_val (key_array, key); - if (keyvals) - *keyvals = (guint*) keyval_array->data; - } - else - { - if (keys) - *keys = NULL; + if (keyval_array) + g_array_append_val (keyval_array, keyval); - if (keyvals) - *keyvals = NULL; + ++(*n_entries); + } } + + if (keys != NULL) + *keys = (GdkKeymapKey*) g_array_free (key_array, FALSE); - if (key_array) - g_array_free (key_array, key_array->len > 0 ? FALSE : TRUE); - if (keyval_array) - g_array_free (keyval_array, keyval_array->len > 0 ? FALSE : TRUE); + if (keyvals != NULL) + *keyvals = (guint*) g_array_free (keyval_array, FALSE); return *n_entries > 0; } @@ -1455,26 +896,28 @@ static guint gdk_win32_keymap_lookup_key (GdkKeymap *gdk_keymap, const GdkKeymapKey *key) { - guint sym; - GdkWin32Keymap *keymap; - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); + GdkWin32Keymap *keymap; + GdkWin32KeymapLayoutInfo *info; - g_return_val_if_fail (gdk_keymap == NULL || GDK_IS_KEYMAP (gdk_keymap), 0); + BYTE keystate[256] = {0}; + BYTE modbits; + guint sym; + + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), 0); g_return_val_if_fail (key != NULL, 0); - /* Accept only the default keymap */ - if (gdk_keymap != NULL && gdk_keymap != default_keymap) - return 0; + keymap = GDK_WIN32_KEYMAP (gdk_keymap); + update_keymap (keymap); - keymap = GDK_WIN32_KEYMAP (default_keymap); - update_keymap (GDK_KEYMAP (keymap)); + info = &g_array_index (keymap->layout_infos, GdkWin32KeymapLayoutInfo, key->group); - if (key->keycode >= KEY_STATE_SIZE || - key->group < 0 || key->group >= keymap->layout_handles->len || - key->level < 0 || key->level >= GDK_WIN32_LEVEL_COUNT) + if (key->group < 0 || key->group >= keymap->layout_handles->len) return 0; - - sym = keymap->keysym_tab[(key->keycode * keymap->layout_handles->len + key->group) * GDK_WIN32_LEVEL_COUNT + key->level]; + if (key->level < 0 || key->level > info->max_level) + return 0; + + modbits = info->level_to_modbits[key->level]; + sym = vk_and_mod_bits_to_gdk_keysym (keymap, info, key->keycode, keystate, modbits, NULL); if (sym == GDK_KEY_VoidSymbol) return 0; @@ -1492,168 +935,57 @@ gdk_win32_keymap_translate_keyboard_state (GdkKeymap *gdk_keymap, int *level, GdkModifierType *consumed_modifiers) { - GdkWin32Keymap *keymap; - guint tmp_keyval; - guint *keygroup; - GdkWin32KeyLevelState shift_level; - GdkModifierType modifiers = GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK; - GdkKeymap *default_keymap = gdk_display_get_keymap (gdk_display_get_default ()); - - g_return_val_if_fail (gdk_keymap == NULL || GDK_IS_KEYMAP (gdk_keymap), FALSE); - -#if 0 - GDK_NOTE (EVENTS, g_print ("gdk_keymap_translate_keyboard_state: keycode=%#x state=%#x group=%d\n", - hardware_keycode, state, group)); -#endif - if (keyval) - *keyval = 0; - if (effective_group) - *effective_group = 0; - if (level) - *level = 0; - if (consumed_modifiers) - *consumed_modifiers = 0; + GdkWin32Keymap *keymap; + guint tmp_keyval; + int tmp_effective_group; + int tmp_level; + BYTE consumed_mod_bits; - /* Accept only the default keymap */ - if (gdk_keymap != NULL && gdk_keymap != default_keymap) - return FALSE; + GdkWin32KeymapLayoutInfo *layout_info; + guint vk; + BYTE mod_bits; + BYTE keystate[256] = {0}; - if (hardware_keycode >= KEY_STATE_SIZE) - return FALSE; + g_return_val_if_fail (GDK_IS_KEYMAP (gdk_keymap), FALSE); - keymap = GDK_WIN32_KEYMAP (default_keymap); - update_keymap (GDK_KEYMAP (keymap)); + keymap = GDK_WIN32_KEYMAP (gdk_keymap); + update_keymap (keymap); - if (group < 0 || group >= keymap->layout_handles->len) - return FALSE; + g_return_val_if_fail (group >= 0 && group < keymap->layout_infos->len, FALSE); - keygroup = &keymap->keysym_tab[(hardware_keycode * keymap->layout_handles->len + group) * GDK_WIN32_LEVEL_COUNT]; + layout_info = &g_array_index (keymap->layout_infos, GdkWin32KeymapLayoutInfo, + group); - if ((state & (GDK_SHIFT_MASK | GDK_LOCK_MASK)) == (GDK_SHIFT_MASK | GDK_LOCK_MASK)) - shift_level = GDK_WIN32_LEVEL_SHIFT_CAPSLOCK; - else if (state & GDK_SHIFT_MASK) - shift_level = GDK_WIN32_LEVEL_SHIFT; - else if (state & GDK_LOCK_MASK) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK; - else - shift_level = GDK_WIN32_LEVEL_NONE; + vk = hardware_keycode; + mod_bits = gdk_mod_mask_to_mod_bits (state); - if (state & GDK_MOD2_MASK) - { - if (shift_level == GDK_WIN32_LEVEL_NONE) - shift_level = GDK_WIN32_LEVEL_ALTGR; - else if (shift_level == GDK_WIN32_LEVEL_SHIFT) - shift_level = GDK_WIN32_LEVEL_SHIFT_ALTGR; - else if (shift_level == GDK_WIN32_LEVEL_CAPSLOCK) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK_ALTGR; - else - shift_level = GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR; - } + if (vk == VK_SHIFT || vk == VK_LSHIFT || vk == VK_RSHIFT) + mod_bits &= ~KBDSHIFT; + if (vk == VK_CONTROL || vk == VK_LCONTROL || vk == VK_RCONTROL) + mod_bits &= ~KBDCTRL; + if (vk == VK_MENU || vk == VK_LMENU || vk == VK_RMENU) + mod_bits &= ~KBDALT; + if (vk == VK_RMENU) + mod_bits &= ~KBDALTGR; - /* Drop altgr, capslock and shift if there are no keysymbols on - * the key for those. - */ - if (keygroup[shift_level] == GDK_KEY_VoidSymbol) - { - switch (shift_level) - { - case GDK_WIN32_LEVEL_NONE: - case GDK_WIN32_LEVEL_ALTGR: - case GDK_WIN32_LEVEL_SHIFT: - case GDK_WIN32_LEVEL_CAPSLOCK: - if (keygroup[GDK_WIN32_LEVEL_NONE] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_NONE; - break; - case GDK_WIN32_LEVEL_SHIFT_CAPSLOCK: - if (keygroup[GDK_WIN32_LEVEL_CAPSLOCK] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK; - else if (keygroup[GDK_WIN32_LEVEL_SHIFT] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_SHIFT; - else if (keygroup[GDK_WIN32_LEVEL_NONE] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_NONE; - break; - case GDK_WIN32_LEVEL_CAPSLOCK_ALTGR: - if (keygroup[GDK_WIN32_LEVEL_ALTGR] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_ALTGR; - else if (keygroup[GDK_WIN32_LEVEL_CAPSLOCK] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK; - else if (keygroup[GDK_WIN32_LEVEL_NONE] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_NONE; - break; - case GDK_WIN32_LEVEL_SHIFT_ALTGR: - if (keygroup[GDK_WIN32_LEVEL_ALTGR] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_ALTGR; - else if (keygroup[GDK_WIN32_LEVEL_SHIFT] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_SHIFT; - else if (keygroup[GDK_WIN32_LEVEL_NONE] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_NONE; - break; - case GDK_WIN32_LEVEL_SHIFT_CAPSLOCK_ALTGR: - if (keygroup[GDK_WIN32_LEVEL_CAPSLOCK_ALTGR] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK_ALTGR; - else if (keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_SHIFT_ALTGR; - else if (keygroup[GDK_WIN32_LEVEL_ALTGR] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_ALTGR; - else if (keygroup[GDK_WIN32_LEVEL_SHIFT_CAPSLOCK] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_SHIFT_CAPSLOCK; - else if (keygroup[GDK_WIN32_LEVEL_CAPSLOCK] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_CAPSLOCK; - else if (keygroup[GDK_WIN32_LEVEL_SHIFT] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_SHIFT; - else if (keygroup[GDK_WIN32_LEVEL_NONE] != GDK_KEY_VoidSymbol) - shift_level = GDK_WIN32_LEVEL_NONE; - break; - case GDK_WIN32_LEVEL_COUNT: - g_assert_not_reached (); - } - } + /* We need to query the existing keyboard state for NumLock, CapsLock etc. */ + get_lock_state (keystate); - /* See whether the shift level actually mattered - * to know what to put in consumed_modifiers - */ - if ((keygroup[GDK_WIN32_LEVEL_SHIFT] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_NONE] == keygroup[GDK_WIN32_LEVEL_SHIFT]) && - (keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_ALTGR] == keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR]) && - (keygroup[GDK_WIN32_LEVEL_SHIFT_CAPSLOCK] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_CAPSLOCK] == keygroup[GDK_WIN32_LEVEL_SHIFT_CAPSLOCK])) - modifiers &= ~GDK_SHIFT_MASK; - - if ((keygroup[GDK_WIN32_LEVEL_CAPSLOCK] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_NONE] == keygroup[GDK_WIN32_LEVEL_CAPSLOCK]) && - (keygroup[GDK_WIN32_LEVEL_CAPSLOCK_ALTGR] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_ALTGR] == keygroup[GDK_WIN32_LEVEL_CAPSLOCK_ALTGR]) && - (keygroup[GDK_WIN32_LEVEL_SHIFT_CAPSLOCK] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_SHIFT] == keygroup[GDK_WIN32_LEVEL_SHIFT_CAPSLOCK])) - modifiers &= ~GDK_LOCK_MASK; - - if ((keygroup[GDK_WIN32_LEVEL_ALTGR] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_NONE] == keygroup[GDK_WIN32_LEVEL_ALTGR]) && - (keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_SHIFT] == keygroup[GDK_WIN32_LEVEL_SHIFT_ALTGR]) && - (keygroup[GDK_WIN32_LEVEL_CAPSLOCK_ALTGR] == GDK_KEY_VoidSymbol || - keygroup[GDK_WIN32_LEVEL_CAPSLOCK] == keygroup[GDK_WIN32_LEVEL_CAPSLOCK_ALTGR])) - modifiers &= ~GDK_MOD2_MASK; - - tmp_keyval = keygroup[shift_level]; + tmp_keyval = vk_and_mod_bits_to_gdk_keysym (keymap, layout_info, vk, keystate, + mod_bits, &consumed_mod_bits); + tmp_effective_group = group; + tmp_level = modbits_to_level (keymap, layout_info, consumed_mod_bits); + /* Determine consumed modifiers */ + if (keyval) *keyval = tmp_keyval; - if (effective_group) - *effective_group = group; - + *effective_group = tmp_effective_group; if (level) - *level = shift_level; - + *level = tmp_level; if (consumed_modifiers) - *consumed_modifiers = modifiers; - -#if 0 - GDK_NOTE (EVENTS, g_print ("... group=%d level=%d cmods=%#x keyval=%s\n", - group, shift_level, modifiers, gdk_keyval_name (tmp_keyval))); -#endif + *consumed_modifiers = mod_bits_to_gdk_mod_mask (consumed_mod_bits); return tmp_keyval != GDK_KEY_VoidSymbol; } diff --git a/gdk/win32/gdkkeys-win32.h b/gdk/win32/gdkkeys-win32.h new file mode 100644 index 0000000000..17b7d7125c --- /dev/null +++ b/gdk/win32/gdkkeys-win32.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021 Philip Zander + * Copyright (c) 2018 Microsoft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <glib.h> +#include <windows.h> + +/* For lookup table VK -> chars */ +typedef struct +{ + int table; + int index; +} GdkWin32KeymapTableAndIndex; + +/* For reverse lookup char -> VKs */ +typedef struct +{ + BYTE mod_bits; + BYTE vk; + + /* Index of next KeyEntry. -1 if there is no next entry. */ + int next; +} GdkWin32KeymapKeyEntry; + +typedef struct +{ + HKL handle; + + /* Keyboard layout identifier */ + char name[KL_NAMELENGTH]; + + /* Path of the layout DLL */ + char *file; + + /* Handle of the layout DLL */ + HINSTANCE lib; + + /* The actual conversion tables provided by the layout DLL. + * + * This is a pointer to a KBDTABLES structure. The exact definition + * of this structure depends on the kernel on which the executable + * run and can in general only be determined at runtime. That's why + * we have to use a generic gpointer instead of the actual type here. + * + * See comment on GdkWin32KeymapImpl below for more information. */ + gpointer tables; + + /* VK -> chars lookup table so we don't have to do a linear scan + * every time we look up a key. */ + GdkWin32KeymapTableAndIndex vk_lookup_table[256]; + + /* List of entries for reverse (char ->VKs) lookup. */ + GArray *key_entries; + + /* Reverse lookup table (char -> VKs). Key: Unichar. Value: int. + * The value is used to index into the key_entries array. The key_entries + * array can contain multiple consecutive entries for a given char. + * The end of the list for the char is marked by a key entry that has + * mod_bits and vk set to 0xFF. */ + GHashTable *reverse_lookup_table; + + /* Map level to modbits */ + BYTE level_to_modbits[256]; + + /* Max Number of levels */ + BYTE max_level; + + /* Maximum possible value of a modbits bitset. */ + BYTE max_modbit_value; + +} GdkWin32KeymapLayoutInfo; + +/* Some keyboard driver constants + * See https://github.com/microsoft/windows-rs/blob/0.28.0/crates/deps/sys/src/Windows/Win32/UI/Input/KeyboardAndMouse/mod.rs + */ + +/* Modifier bits */ +#define KBDBASE 0x00 +#define KBDSHIFT 0x01 +#define KBDCTRL 0x02 +#define KBDALT 0x04 +#define KBDKANA 0x08 +#define KBDROYA 0x10 +#define KBDLOYA 0x20 +#define KBDGRPSELTAP 0x80 + +#define KBDALTGR (KBDCTRL| KBDALT) + +/* */ +#define SHFT_INVALID 0x0F + +/* Char table constants */ +#define WCH_NONE 0xF000 +#define WCH_DEAD 0xF001 +#define WCH_LGTR 0xF002 + +/* Char table flags */ +#define CAPLOK 0x01 +#define SGCAPS 0x02 +#define CAPLOKALTGR 0x04 +#define KANALOK 0x08 +#define GRPSELTAP 0x80 + +/* IMPORTANT: + * + * Keyboard layout DLLs are dependent on the host architecture. + * + * - 32 bit systems have just one 32 bit DLL in System32. + * - 64 bit systems contain two versions of each layout DLL: One in System32 + * for 64-bit applications, and one in SysWOW64 for 32-bit applications. + * + * Here comes the tricky part: + * + * The 32-bit DLL in SysWOW64 is *not* identical to the DLL you would find + * on a true 32 bit system, because all the pointers there are declared with + * the attribute `__ptr64` (which means they are 64 bits wide, but only the + * lower 32 bits are used). + * + * This leads to the following problems: + * + * (1) GCC does not support `__ptr64` + * (2) When compiling the 32-bit library, we need two versions of the same code + * and decide at run-time which one to execute, because we can't know at + * compile time whether we will be running on a true 32-bit system, or on + * WOW64. + * + * To solve this problem, we generate code for both cases (see + * gdkkeys-win32-impl.c + gdkkeys-win32-impl-wow64.c) and encapsulate + * the resulting functions in a struct of type GdkWin32KeymapImpl, + * allowing us to select the correct implementation at runtime. + * + */ + +typedef struct +{ + gboolean (*load_layout_dll) (const char *dll, + GdkWin32KeymapLayoutInfo *info); + void (*init_vk_lookup_table) (GdkWin32KeymapLayoutInfo *info); + BYTE (*keystate_to_modbits) (GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256]); + BYTE (*modbits_to_level) (GdkWin32KeymapLayoutInfo *info, + BYTE modbits); + WCHAR (*vk_to_char_fuzzy) (GdkWin32KeymapLayoutInfo *info, + const BYTE keystate[256], + BYTE extra_mod_bits, + BYTE *consumed_mod_bits, + gboolean *is_dead, + BYTE vk); +} GdkWin32KeymapImpl; diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h index 723e681c71..58185697a6 100644 --- a/gdk/win32/gdkprivate-win32.h +++ b/gdk/win32/gdkprivate-win32.h @@ -356,7 +356,7 @@ guint8 _gdk_win32_keymap_get_active_group (GdkWin32Keymap *keymap); guint8 _gdk_win32_keymap_get_rshift_scancode (GdkWin32Keymap *keymap); void _gdk_win32_keymap_set_active_layout (GdkWin32Keymap *keymap, HKL hkl); - +GdkModifierType _gdk_win32_keymap_get_mod_mask (GdkWin32Keymap *keymap); GdkKeymap *_gdk_win32_display_get_keymap (GdkDisplay *display); @@ -425,4 +425,12 @@ void _gdk_win32_surfaceing_init (void); void _gdk_drag_init (void); void _gdk_events_init (GdkDisplay *display); +typedef enum _GdkWin32ProcessorCheckType +{ + GDK_WIN32_ARM64, + GDK_WIN32_WOW64, +} GdkWin32ProcessorCheckType; + +gboolean _gdk_win32_check_processor (GdkWin32ProcessorCheckType check_type); + #endif /* __GDK_PRIVATE_WIN32_H__ */ diff --git a/gdk/win32/meson.build b/gdk/win32/meson.build index 0f40e73edc..d368648624 100644 --- a/gdk/win32/meson.build +++ b/gdk/win32/meson.build @@ -19,6 +19,8 @@ gdk_win32_sources = files([ 'gdkhdataoutputstream-win32.c', 'gdkinput-winpointer.c', 'gdkkeys-win32.c', + 'gdkkeys-win32-impl.c', + 'gdkkeys-win32-impl-wow64.c', 'gdkwin32langnotification.c', 'gdkmain-win32.c', 'gdkmonitor-win32.c', |