----- 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) > + > +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 > _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel