Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> --- src/spice-gtk-keyboard.c | 408 +++++++++++++++++++++++++++++++++++++++++++++++ src/spice-gtk-keyboard.h | 8 + tests/Makefile.am | 8 + tests/keyboard-test.c | 98 ++++++++++++ 4 files changed, 522 insertions(+) create mode 100644 tests/keyboard-test.c diff --git a/src/spice-gtk-keyboard.c b/src/spice-gtk-keyboard.c index 6db53b4..b0f6b86 100644 --- a/src/spice-gtk-keyboard.c +++ b/src/spice-gtk-keyboard.c @@ -17,6 +17,8 @@ */ #include "config.h" +#include <glib.h> + #ifdef HAVE_X11_XKBLIB_H #include <X11/XKBlib.h> #include <gdk/gdkx.h> @@ -33,6 +35,7 @@ #include <gtk/gtk.h> #include "channel-inputs.h" +#include "spice-util-priv.h" #include "spice-gtk-keyboard.h" guint32 get_keyboard_lock_modifiers(void) @@ -158,6 +161,411 @@ 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); + +/* + * 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); + + SPICE_DEBUG("loading file %S", fn); + return LoadLibraryW(fn); +} + +/* 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, const char *layout_name) +{ + WCHAR buf[256]; + char reg_path[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 */ + snprintf(reg_path, SPICE_N_ELEMENTS(reg_path), LAYOUT_REGKEY "\\%s", layout_name); + err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key); + if (err != ERROR_SUCCESS) { + g_critical("failed getting keyboard layout registry key %s %ld", reg_path, err); + 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)) { + SPICE_DEBUG("dll redirected to %S %u", buf, (unsigned) wcslen(buf)); + HMODULE new_dll = load_keyboard_layout(buf); + if (new_dll) { + SPICE_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()) { + SPICE_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 { + SPICE_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; + } + SPICE_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; + SPICE_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 */ + SPICE_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: + SPICE_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 &= 1; + 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 */ + char curr_layout_name[KL_NAMELENGTH + 1]; + if (GetKeyboardLayoutName(curr_layout_name)) { + keyboard_cache(curr_layout, curr_layout_name); + 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] & 1)) + && 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] & 1)) + && 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] & 1)) + && 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/spice-gtk-keyboard.h b/src/spice-gtk-keyboard.h index 016be84..499b271 100644 --- a/src/spice-gtk-keyboard.h +++ b/src/spice-gtk-keyboard.h @@ -27,9 +27,17 @@ G_BEGIN_DECLS +G_GNUC_INTERNAL guint32 get_keyboard_lock_modifiers(void); +G_GNUC_INTERNAL void set_keyboard_lock_modifiers(guint32 modifiers); +#ifdef G_OS_WIN32 +#include <windows.h> +G_GNUC_INTERNAL +void keyboard_cache(HKL layout, const char *layout_name); +#endif + G_END_DECLS #endif /* KEYBOARD_MODIFIERS_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 6d9cfeb..8c25a77 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -17,11 +17,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 \ @@ -31,6 +38,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..bf2c5b9 --- /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 "spice-gtk-keyboard.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, name); + 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel