[spice-gtk 7/9] Implements set_keyboard_lock_modifiers for Windows

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]     [Monitors]