> > ----- Original Message ----- > > 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" > > I would rather not include the C file, but have the test code in this file > (like the rest of the tests) > Yes, quite a bad hack. There were some reason to do these hacks: - having an executable not relying on Gtk so to have to copy a single .exe file and launch it; - use a static, not exported, function in spice-gtk; - avoid to add ../src/keyboard-modifiers.c in tests/Makefile.am which give errors. First point is no more a big issue (already tested). I'm pondering (beside moving test functions) to add a library to src/Makefile.am to provide functions required (keyboard_cache). Any opinion? Frediano > > + > > +int main(void) > > +{ > > + keyboard_modifiers_test(); > > + return 0; > > +} _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel