[Spice-devel] [spice-gtk v2] Implements set_keyboard_lock_modifiers for Windows
Frediano Ziglio
fziglio at redhat.com
Wed Jun 8 14:26:46 UTC 2016
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
---
src/keyboard-modifiers.c | 445 +++++++++++++++++++++++++++++++++++++++++++++++
src/keyboard-modifiers.h | 5 +
tests/Makefile.am | 8 +
tests/keyboard-test.c | 98 +++++++++++
4 files changed, 556 insertions(+)
create mode 100644 tests/keyboard-test.c
Changes from v1:
- move test functions to .c file;
- update code style.
diff --git a/src/keyboard-modifiers.c b/src/keyboard-modifiers.c
index dd13fad..23c55f3 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>
@@ -158,6 +160,449 @@ 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);
+
+#define test_debug(fmt, ...) do { \
+ if (0) printf(fmt "\n", ## __VA_ARGS__); \
+} while(0)
+
+/**
+ * Read a string from registry
+ * @key registry key to read from
+ * @name name of the value to read
+ * @value buffer where to store results
+ * @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
+ * @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.
+ * @layout layout to get information
+ */
+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.
+ * @vk virtual key of the key
+ * @curr_state current state (!=0 is key down)
+ * @wanted_state wanted state (!=0 is key down)
+ * @begin_inputs beginning of already present input keys
+ * @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/src/keyboard-modifiers.h b/src/keyboard-modifiers.h
index 016be84..6dad37e 100644
--- a/src/keyboard-modifiers.h
+++ b/src/keyboard-modifiers.h
@@ -30,6 +30,11 @@ G_BEGIN_DECLS
guint32 get_keyboard_lock_modifiers(void);
void set_keyboard_lock_modifiers(guint32 modifiers);
+#ifdef G_OS_WIN32
+#include <windows.h>
+void keyboard_cache(HKL layout);
+#endif
+
G_END_DECLS
#endif /* KEYBOARD_MODIFIERS_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1a8b768..1d2006b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -16,11 +16,18 @@ TESTS += usb-acl-helper
noinst_PROGRAMS += mock-acl-helper
endif
+if OS_WIN32
+TESTS += keyboard-test
+keyboard_test_SOURCES = \
+ keyboard-test.c \
+ $(NULL)
+endif
noinst_PROGRAMS += $(TESTS)
AM_CPPFLAGS = \
$(COMMON_CFLAGS) \
$(GIO_CFLAGS) \
+ $(GTK_CFLAGS) \
$(SMARTCARD_CFLAGS) \
-I$(top_srcdir)/src \
-I$(top_builddir)/src \
@@ -30,6 +37,7 @@ AM_CPPFLAGS = \
AM_LDFLAGS = $(GIO_LIBS) -static
LDADD = \
+ $(top_builddir)/src/libspice-client-gtk-3.0.la \
$(top_builddir)/src/libspice-client-glib-2.0.la \
$(NULL)
diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c
new file mode 100644
index 0000000..377156e
--- /dev/null
+++ b/tests/keyboard-test.c
@@ -0,0 +1,98 @@
+#include "config.h"
+
+#include <glib.h>
+
+#include <windows.h>
+#include <stdio.h>
+
+#include "spice-client-gtk.h"
+
+#define SPICE_COMPILATION
+#include "keyboard-modifiers.h"
+
+#define LAYOUT_REGKEY "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts"
+
+static void keyboard_check_single(HKEY key, const char *name);
+
+/* must be tested on
+ * - WinXP 32
+ * - Win7 32/64 app
+ * - Win10 32/64 app
+ */
+static 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) {
+ g_error("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) {
+ g_error("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) {
+ g_error("wrong value %s", name);
+ return;
+ }
+
+ printf("trying keyboard %s\n", name);
+ keyboard_cache((HKL) (DWORD_PTR) num);
+ printf("----\n");
+}
+
+int main(void)
+{
+ /* make g_critical abort */
+ g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_FATAL_MASK|G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL);
+
+ keyboard_modifiers_test();
+ return 0;
+}
--
2.7.4
More information about the Spice-devel
mailing list