forked from AuroraMiddleware/gtk
90ab8b8dd3
Some keymaps on Windows contain bogus mappings for Ctrl+key for certain keys, e.g. Ctrl+Backspace = Delete, or Ctrl+[ = 0x1B. These are never used on Windows, so we should ignore them. Fixes #4667
566 lines
18 KiB
C
566 lines
18 KiB
C
/*
|
|
* 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;
|
|
|
|
if (tables == NULL)
|
|
return 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;
|
|
|
|
if (tables == NULL)
|
|
return 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 modifier state, 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 lock state):
|
|
*
|
|
* - a -> 'a', consumed_mod_bits: []
|
|
* - 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:
|
|
*
|
|
* - a -> 'A', consumed_mod_bits: [Shift]
|
|
* - Shift + a -> 'a', consumed_mod_bits: []
|
|
* - Ctrl + a -> 'a', consumed_mod_bits: []
|
|
* - Ctrl + Shift + a -> 'A', consumed_mod_bits: [Shift]
|
|
*
|
|
* The held down modifiers are supplied in `mod_bits` as a bitmask of KBDSHIFT,
|
|
* KBDCTRL, KBDALT etc.
|
|
*
|
|
* The toggled modifiers are supplied in `lock_state` as a bitmask of CAPLOK and KANALOK.
|
|
*
|
|
* 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,
|
|
BYTE mod_bits,
|
|
BYTE lock_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;
|
|
|
|
if (tables == NULL)
|
|
return 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)
|
|
{
|
|
gboolean have_sgcaps = FALSE;
|
|
WCHAR best_char = WCH_NONE;
|
|
BYTE best_modifiers = 0;
|
|
int best_score = -1;
|
|
gboolean best_is_dead = FALSE;
|
|
int level;
|
|
|
|
/* 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). */
|
|
!(mod_bits & ~KBDSHIFT) &&
|
|
(lock_bits & CAPLOK))
|
|
mod_bits ^= KBDSHIFT;
|
|
|
|
/* Key supporting combination of capslock + altgr */
|
|
if ((entry->Attributes & CAPLOKALTGR) &&
|
|
(mod_bits & KBDALTGR) &&
|
|
(lock_bits & CAPLOK))
|
|
mod_bits ^= KBDSHIFT;
|
|
|
|
/* In the Swiss German layout, CapsLock + key is different from Shift + key
|
|
* for some keys. For such keys, the characters for active capslock are
|
|
* in the next entry. */
|
|
if ((entry->Attributes & SGCAPS) &&
|
|
(lock_bits & CAPLOK))
|
|
have_sgcaps = TRUE;
|
|
|
|
/* I'm not totally sure how kanalok behaves, for now I assume that there
|
|
* aren't any special cases. */
|
|
if ((entry->Attributes & KANALOK) &&
|
|
(lock_bits & KANALOK))
|
|
mod_bits ^= 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 & ~mod_bits)
|
|
continue;
|
|
|
|
/* Some keys have bogus mappings for the control key, e.g.
|
|
* Ctrl + Backspace = Delete, or Ctrl + [ = 0x1B. These are
|
|
* never used on Windows, so we ignore them.
|
|
*/
|
|
if (candidate_modbits == KBDCTRL)
|
|
continue;
|
|
|
|
c = entry->wch[level];
|
|
if (c == WCH_DEAD || have_sgcaps)
|
|
{
|
|
/* Next entry contains the undead/capslocked 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 & mod_bits);
|
|
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). */
|
|
|
|
/* Only add the first entry, as some layouts (Swiss German) contain
|
|
* multiple successive entries for the same VK (SGCAPS). */
|
|
if (info->vk_lookup_table[entry->VirtualKey].table < 0)
|
|
{
|
|
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
|
|
};
|