Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> --- src/keyboard-modifiers.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ tests/Makefile.am | 15 ++ tests/keyboard-test.c | 9 + 3 files changed, 558 insertions(+) create mode 100644 tests/keyboard-test.c diff --git a/src/keyboard-modifiers.c b/src/keyboard-modifiers.c index dd13fad..cb019c9 100644 --- a/src/keyboard-modifiers.c +++ b/src/keyboard-modifiers.c @@ -17,6 +17,8 @@ */ #include "config.h" +#include <glib.h> + #ifdef HAVE_X11_XKBLIB_H #include <X11/XKBlib.h> #include <gdk/gdkx.h> @@ -35,6 +37,7 @@ #include "channel-inputs.h" #include "keyboard-modifiers.h" +#if !KEYBOARD_MODIFIERS_TEST guint32 get_keyboard_lock_modifiers(void) { guint32 modifiers = 0; @@ -89,6 +92,7 @@ guint32 get_keyboard_lock_modifiers(void) #endif // GTK_CHECK_VERSION(3,18,0) return modifiers; } +#endif #if defined(HAVE_X11_XKBLIB_H) typedef enum SpiceLed { @@ -158,6 +162,536 @@ void set_keyboard_lock_modifiers(guint32 modifiers) set_keyboard_led(x_display, SCROLL_LOCK_LED, !!(modifiers & SPICE_INPUTS_SCROLL_LOCK)); } +#elif defined(G_OS_WIN32) + +// Some definitions from kbd.h to define internal layout file structures +// Note that pointer in Wow64 are 64 bit despite program bits +#define KBDSPECIAL (USHORT)0x0400 + +// type of NLS function key +#define KBDNLS_TYPE_NULL 0 +#define KBDNLS_TYPE_NORMAL 1 +#define KBDNLS_TYPE_TOGGLE 2 + +// action to perform on a specific combination (only needed) +#define KBDNLS_NULL 0 // Invalid function +#define KBDNLS_SEND_BASE_VK 2 // Send Base VK_xxx +#define KBDNLS_SEND_PARAM_VK 3 // Send Parameter VK_xxx + +typedef struct { + BYTE NLSFEProcIndex; + ULONG NLSFEProcParam; +} VK_FPARAM; + +typedef struct { + BYTE Vk; + BYTE NLSFEProcType; + BYTE NLSFEProcCurrent; + BYTE NLSFEProcSwitch; // 8 bits + VK_FPARAM NLSFEProc[8]; + VK_FPARAM NLSFEProcAlt[8]; +} VK_F; + +typedef struct { + USHORT OEMIdentifier; + USHORT LayoutInformation; + UINT NumOfVkToF; + VK_F *pVkToF; + void *dummy; // used to check size +} KBDNLSTABLES; + +typedef void *WINAPI KbdLayerDescriptor_t(void); +typedef BOOL WINAPI KbdLayerRealDllFile_t(HKL hkl, WCHAR *realDllName, LPVOID pClientKbdType, LPVOID reserve1 , LPVOID reserve2); +typedef KBDNLSTABLES *WINAPI KbdNlsLayerDescriptor_t(void); + +// where all keyboard layouts information are in the registry +#define LAYOUT_REGKEY "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts" + +static LONG reg_read_str(HKEY key, LPCWSTR name, WCHAR *value, size_t len); +static HMODULE load_keyboard_layout(const WCHAR *dll_name); +static void keyboard_cache(HKL layout); + +#define test_debug(fmt, ...) do { \ + if (0) printf(fmt "\n", ## __VA_ARGS__); \ +} while(0) + +#if KEYBOARD_MODIFIERS_TEST + +#define fatal(fmt, ...) do { fprintf(stderr, fmt "\n", ## __VA_ARGS__); exit(1); } while(0) + +#undef test_debug +#define test_debug(fmt, ...) printf(fmt "\n", ## __VA_ARGS__) + +// this allow to avoid having to link gtk libraries +#undef g_critical +#define g_critical fatal + +static void keyboard_check_single(HKEY key, const char *name); + +// must be tested on +// - WinXP 32 +// - Win7 32/64 app +// - Win10 32/64 app +void keyboard_modifiers_test(void) +{ + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + // scan all keyboards + // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\<00030402> + // check them all + HKEY key; + LONG err; + err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LAYOUT_REGKEY, 0, KEY_READ, &key); + if (err != ERROR_SUCCESS) { + fatal("RegOpenKeyEx error %ld", err); + return; + } + + unsigned i; + for (i = 0; ; ++i) { + char name[64]; + err = RegEnumKey(key, i, name, G_N_ELEMENTS(name)); + if (err == ERROR_NO_MORE_ITEMS) + break; + if (err != ERROR_SUCCESS) { + fatal("RegEnumKey error %ld", err); + break; + } + keyboard_check_single(key, name); + } + + RegCloseKey(key); + // check for multiple keyboards + // + // KbdLayerDescriptor - no parameter, returns a table + // KbdLayerMultiDescriptor + // pass a pointer, structure like, output + // struct Xxx { + // uint32_t num_layout_valid; + // struct { + // WCHAR dll_name[32]; + // uint32_t unknown1; + // uint32_t unknown2; + // } layers[8]; + // } + // KbdLayerRealDllFile + // BOOL KbdLayerRealDllFile(HKL hkl, WCHAR *realDllName, PCLIENTKEYBOARDTYPE pClientKbdType, LPVOID reserve) + // returns TRUE if we need to load another file (this is just a stub) + // realDllName returned keyboard name + // pClientKbdType used for terminal server, NULL for physical one + // reserve NULL + // KbdLayerRealDllFileNT4 - obsolete + // KbdNlsLayerDescriptor - no parameter, returns NLS table, if not no need to parse but MapVirtualKey works + // +} + +static void keyboard_check_single(HKEY key, const char *name) +{ + char *end = NULL; + errno = 0; + unsigned long num = strtoul(name, &end, 16); + if (errno || *end) { + fatal("wrong value %s", name); + return; + } + + printf("trying keyboard %s\n", name); + keyboard_cache((HKL) (DWORD_PTR) num); + printf("----\n"); +} +#endif + +/** + * Read a string from registry + * @param key registry key to read from + * @param name name of the value to read + * @param value buffer where to store results + * @param size size of value buffer in bytes (not value elements!) + * @returns system error code or ERROR_SUCCESS + */ +static LONG reg_read_str(HKEY key, LPCWSTR name, WCHAR *value, size_t len) +{ + DWORD size = len-sizeof(*value), type; + LONG err = RegQueryValueExW(key, name, 0, &type, (void *) value, &size); + if (err != ERROR_SUCCESS) + return err; + + if (type != REG_SZ) + return ERROR_INVALID_DATA; + + // assure terminated + value[size/sizeof(*value)] = 0; + return ERROR_SUCCESS; +} + +/** + * Load a keyboard layout file given the file name + * @param dll_name dll name (should be just file name without paths) + */ +static HMODULE load_keyboard_layout(const WCHAR *dll_name) +{ + WCHAR fn[MAX_PATH+256]; + +#ifdef _WIN64 + GetSystemDirectoryW(fn, MAX_PATH); +#else + typedef UINT WINAPI GetSystemWow64DirectoryW_t(LPWSTR str, UINT size); + GetSystemWow64DirectoryW_t *pGetSystemWow64DirectoryW = + (GetSystemWow64DirectoryW_t*)GetProcAddress(GetModuleHandle("kernel32"), "GetSystemWow64DirectoryW"); + if (!pGetSystemWow64DirectoryW || pGetSystemWow64DirectoryW(fn, MAX_PATH) == 0) + GetSystemDirectoryW(fn, MAX_PATH); +#endif + wcscat(fn, L"\\"); + wcscat(fn, dll_name); + + test_debug("loading file %S", fn); + return LoadLibraryW(fn); +} + +/** + * Check if current process is running in Wow64 mode + * (32 bit on a 64 system) + */ +#ifndef _WIN64 +static BOOL is_wow64(void) +{ + BOOL bIsWow64 = FALSE; + + typedef BOOL WINAPI IsWow64Process_t(HANDLE, PBOOL); + IsWow64Process_t *pIsWow64Process = + (IsWow64Process_t *) GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process"); + + if (pIsWow64Process) + pIsWow64Process(GetCurrentProcess(), &bIsWow64); + + return bIsWow64; +} +#else +static inline BOOL is_wow64(void) +{ + return FALSE; +} +#endif + +/** + * Check if OS is 64 bit + */ +static BOOL os_is_64bit(void) +{ +#ifdef _WIN64 + return TRUE; +#else + return is_wow64() != FALSE; +#endif +} + + +// keyboard status +// caps lock VK/SC +// combination (ctrl+alt+shift / none) +// ctrl/alt/shift keys VK/SC (2 for each) ? +static WORD vsc_capital = 58; +static int specific_modifiers = -1; + +/** + * Extract information from keyboard. + * Currently scancode of Caps Lock is searched and + * possible modifiers needed to have that Caps Lock. + * @param layout layout to get information + */ +static void keyboard_cache(HKL layout) +{ + WCHAR buf[256]; + HKEY key; + LONG err; + + KbdLayerDescriptor_t *get_desc; + + const BYTE *kbd_table; + int num_keys, i; + const USHORT *keys; + const KBDNLSTABLES *nls_table; + const VK_F *vkf, *vkf_end; + + // set default output, usually work with lot of keyboard layout + // these values will be used in case of errors + vsc_capital = MapVirtualKey(VK_CAPITAL, MAPVK_VK_TO_VSC); + specific_modifiers = -1; + + // get keyboard dll name from registry + swprintf(buf, 256, TEXT(LAYOUT_REGKEY) L"\\%08X", (unsigned)(DWORD_PTR)layout); + err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &key); + if (err != ERROR_SUCCESS) { + g_critical("failed getting keyboard layout registry key"); + return; + } + err = reg_read_str(key, L"Layout File", buf, sizeof(buf)); + RegCloseKey(key); + if (err != ERROR_SUCCESS) { + g_critical("failed getting keyboard layout file name"); + return; + } + + // load keyboard layout file + HMODULE dll = load_keyboard_layout(buf); + if (!dll) { + g_critical("error loading keyboard layout for %08x", (unsigned)(DWORD_PTR)layout); + return; + } + + // see if we need to get another dll + KbdLayerRealDllFile_t *dll_file = (KbdLayerRealDllFile_t *) GetProcAddress(dll, "KbdLayerRealDllFile"); + if (dll_file) { + // load the other file + if (dll_file(layout, buf, NULL, NULL, NULL)) { + test_debug("dll redirected to %S %u", buf, (unsigned) wcslen(buf)); + HMODULE new_dll = load_keyboard_layout(buf); + if (new_dll) { + test_debug("unloading stub"); + FreeLibrary(dll); + dll = new_dll; + } + } + } + + // check if there are NLS function (in this case we must parse tables) + KbdNlsLayerDescriptor_t *get_nls_desc = (KbdNlsLayerDescriptor_t *) GetProcAddress(dll, "KbdNlsLayerDescriptor"); + if (!get_nls_desc) + goto cleanup; + + // get main keyboard table + get_desc = (KbdLayerDescriptor_t *) GetProcAddress(dll, "KbdLayerDescriptor"); + if (!get_desc) { + g_critical("keyboard dll layout has no descriptor"); + goto cleanup; + } + kbd_table = (BYTE *) get_desc(); + + // check table (for Win32 see format 32 or 64) + if (os_is_64bit()) { + test_debug("64 bit"); + if (IsBadReadPtr(kbd_table, 12*8)) { + g_critical("wrong table address"); + goto cleanup; + } + keys = *((USHORT **) &kbd_table[6*8]); + num_keys = kbd_table[7*8]; + } else { + test_debug("32 bit"); + if (IsBadReadPtr(kbd_table, 13*4)) { + g_critical("wrong table address"); + goto cleanup; + } + keys = *((USHORT **) &kbd_table[6*4]); + num_keys = kbd_table[7*4]; + } + + // scan VKs for a VK_CAPITAL not special + if (IsBadReadPtr(keys, num_keys*sizeof(*keys))) { + g_critical("wrong VKs table"); + goto cleanup; + } + for (i = 0; i < num_keys; ++i) { + // ... return if found + if ((keys[i] & KBDSPECIAL) == 0 && (keys[i] & 0xFF) == VK_CAPITAL) { + vsc_capital = i; + specific_modifiers = -1; + goto cleanup; + } + } + + // scan NLS table for VK_CAPITALs + nls_table = get_nls_desc(); + if (IsBadReadPtr(keys, sizeof(*nls_table))) { + g_critical("wrong NLS table"); + goto cleanup; + } + vkf = nls_table->pVkToF; + if (IsBadReadPtr(vkf, sizeof(*vkf) * nls_table->NumOfVkToF)) { + g_critical("wrong function table"); + goto cleanup; + } + test_debug("layout has %u NLS key", (unsigned) nls_table->NumOfVkToF); + vkf_end = vkf + nls_table->NumOfVkToF; + for (; vkf < vkf_end; ++vkf) { + unsigned mask = 0; + const VK_FPARAM *params = vkf->NLSFEProc; + + // scan all functions searching for VK_CAPITAL + // check both part, normal and with alternate (if present) + for (i = 0; i < 16; ++i) { + if ((vkf->Vk == VK_CAPITAL && params[i].NLSFEProcIndex == KBDNLS_SEND_BASE_VK) + || (params[i].NLSFEProcIndex == KBDNLS_SEND_PARAM_VK && params[i].NLSFEProcParam == VK_CAPITAL)) + mask |= 1<<i; + } + // no VK_CAPITAL found + if (!mask) + continue; + test_debug("found mask %x at vk %x", mask, vkf->Vk); + // see if there are a common key between the two tables for + // each special key + unsigned common = (mask >> 8) & mask; + if (common) + mask = common; + for (i = 0; i < 16; ++i) + if (mask & (1<<i)) { + specific_modifiers = i & 7; + break; + } + // get back base VK + for (i = 0; i < num_keys; ++i) { + // ... return if found + test_debug("keys %d = %x vk %x", i, keys[i], vkf->Vk); + if ((keys[i] & KBDSPECIAL) != 0 && (keys[i] & 0xFF) == vkf->Vk) { + vsc_capital = i; + goto cleanup; + } + } + // this is unexpected, there should be a key matching the NLS table + g_critical("NLS key not found in normal table"); + } + specific_modifiers = -1; + +cleanup: + test_debug("unloading dll"); + FreeLibrary(dll); +} + +/** + * Add input keys in order to make the special key (shift/control/alt) + * state the same as wanted one. + * @param vk virtual key of the key + * @param curr_state current state (!=0 is key down) + * @param wanted_state wanted state (!=0 is key down) + * @param begin_inputs beginning of already present input keys + * @param end_inputs end of already present input keys + */ +static void adjust_special(WORD vk, int curr_state, int wanted_state, + INPUT **begin_inputs, INPUT **end_inputs) +{ + KEYBDINPUT *ki; + + curr_state &= 0x80; + if (!!wanted_state == !!curr_state) + return; + + // if there are not the spcific key no need to handle + UINT vsc = MapVirtualKey(vk, MAPVK_VK_TO_VSC); + if (!vsc) + return; + + // make sure modifier key is in the right state before pressing + // main key + --(*begin_inputs); + ki = &(*begin_inputs)->ki; + ki->wVk = vk; + ki->wScan = vsc; + ki->dwFlags = wanted_state ? 0 : KEYEVENTF_KEYUP; + + // make sure key state is restored at the end + ki = &(*end_inputs)->ki; + ki->wVk = vk; + ki->wScan = vsc; + ki->dwFlags = wanted_state ? KEYEVENTF_KEYUP : 0; + ++(*end_inputs); +} + +/** + * Add a key pression and a release to inputs events + */ +static gboolean add_press_release(INPUT *inputs, WORD vk, WORD vsc) +{ + KEYBDINPUT *ki; + + if (!vsc) + return FALSE; + + ki = &inputs[0].ki; + ki->wVk = vk; + ki->wScan = vsc; + ki = &inputs[1].ki; + ki->wVk = vk; + ki->wScan = vsc; + ki->dwFlags = KEYEVENTF_KEYUP; + + return TRUE; +} + +void set_keyboard_lock_modifiers(guint32 modifiers) +{ + static HKL cached_layout = 0; + + // get keyboard layout + HKL curr_layout = GetKeyboardLayout(0); + + // same as before, use old informations.. + if (curr_layout != cached_layout) { + // .. otherwise cache new keyboard layout + keyboard_cache(curr_layout); + cached_layout = curr_layout; + } + + BYTE key_states[256]; + GetKeyboardState(key_states); + + // compute sequence to press + // as documented in SetKeyboardState we must press + // the sequence that cause the state to change + // to modify the global state + int i; + INPUT inputs[6*2+2+4], *begin_inputs, *end_inputs; + memset(inputs, 0, sizeof(inputs)); + for (i = 0; i < G_N_ELEMENTS(inputs); ++i) { + inputs[i].type = INPUT_KEYBOARD; + inputs[i].ki.dwExtraInfo = 0x12345678; + } + + // start pointers, make sure we have enough space + // before and after to insert shift/control/alt keys + begin_inputs = end_inputs = inputs + 6; + + // we surely must press the caps lock key + if ((!!(modifiers & SPICE_INPUTS_CAPS_LOCK) != !!(key_states[VK_CAPITAL] & 0x80)) + && add_press_release(end_inputs, VK_CAPITAL, vsc_capital)) { + end_inputs += 2; + + // unfortunately the key expect a specific combination + // of shift/control/alt, make sure we have that state + if (specific_modifiers >= 0) { + +#define adjust_special(vk, mask) \ + adjust_special((vk), key_states[vk], specific_modifiers & (mask), &begin_inputs, &end_inputs) + + adjust_special(VK_LSHIFT, 1); + adjust_special(VK_RSHIFT, 1); + adjust_special(VK_LCONTROL, 2); + adjust_special(VK_RCONTROL, 2); + adjust_special(VK_LMENU, 4); + adjust_special(VK_RMENU, 4); + } + } + + // sync NUMLOCK + if ((!!(modifiers & SPICE_INPUTS_NUM_LOCK) != !!(key_states[VK_NUMLOCK] & 0x80)) + && add_press_release(end_inputs, VK_NUMLOCK, MapVirtualKey(VK_NUMLOCK, MAPVK_VK_TO_VSC))) { + end_inputs += 2; + } + + // sync SCROLLLOCK + if ((!!(modifiers & SPICE_INPUTS_SCROLL_LOCK) != !!(key_states[VK_SCROLL] & 0x80)) + && add_press_release(end_inputs, VK_SCROLL, MapVirtualKey(VK_SCROLL, MAPVK_VK_TO_VSC))) { + end_inputs += 2; + } + + // press the sequence + if (end_inputs > begin_inputs) { + BlockInput(TRUE); + SendInput(end_inputs - begin_inputs, begin_inputs, sizeof(inputs[0])); + BlockInput(FALSE); + } +} + #else void set_keyboard_lock_modifiers(guint32 modifiers) diff --git a/tests/Makefile.am b/tests/Makefile.am index 1a8b768..f96ee48 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -16,6 +16,21 @@ TESTS += usb-acl-helper noinst_PROGRAMS += mock-acl-helper endif +if OS_WIN32 +TESTS += keyboard-test +keyboard_test_SOURCES = \ + keyboard-test.c \ + $(NULL) +keyboard_test_CPPFLAGS = \ + $(COMMON_CFLAGS) \ + -DSPICE_COMPILATION \ + $(GTK_CFLAGS) \ + -DG_LOG_DOMAIN=\"GSpice\" \ + $(NULL) +keyboard_test_LDADD = \ + $(GTK_LIBS) \ + $(NULL) +endif noinst_PROGRAMS += $(TESTS) AM_CPPFLAGS = \ diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c new file mode 100644 index 0000000..9c7dfba --- /dev/null +++ b/tests/keyboard-test.c @@ -0,0 +1,9 @@ +#define KEYBOARD_MODIFIERS_TEST 1 + +#include "../src/keyboard-modifiers.c" + +int main(void) +{ + keyboard_modifiers_test(); + return 0; +} -- 2.7.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel