From b25858ad056e90f549b9a73cdcffc333879c5241 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Fri, 30 Aug 2019 17:58:18 +0200 Subject: [PATCH] First attempt at Windows native bindings --- build.rs | 1 + native/libwinbridge/bridge.cpp | 171 +++++++++++++++++++++++++++++++++ native/libwinbridge/bridge.h | 13 ++- src/main.rs | 7 +- 4 files changed, 185 insertions(+), 7 deletions(-) diff --git a/build.rs b/build.rs index 1c9611d..951a45b 100644 --- a/build.rs +++ b/build.rs @@ -7,4 +7,5 @@ fn main() println!("cargo:rustc-link-search=native={}", dst.display()); println!("cargo:rustc-link-lib=static=winbridge"); + println!("cargo:rustc-link-lib=static=user32"); } \ No newline at end of file diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 6de07b6..fbac7e3 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -1 +1,172 @@ #include "bridge.h" +#include +#include +#include +#include + +#define UNICODE + +#include + +// How many milliseconds must pass between keystrokes to refresh the keyboard layout +const long refreshKeyboardLayoutInterval = 2000; + +DWORD lastKeyboardPressTick = 0; +HKL currentKeyboardLayout; +HWND window; + +const wchar_t* const winclass = L"Espanso"; + +/* + * Message handler procedure for the Worker window + */ +LRESULT CALLBACK workerWindowProcedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp) +{ + switch (msg) + { + case WM_DESTROY: + std::cout << "\ndestroying window\n"; + PostQuitMessage(0); + return 0L; + case WM_INPUT: // Message relative to the RAW INPUT events + { + // Get the input size + UINT dwSize; + GetRawInputData( + (HRAWINPUT)lp, + RID_INPUT, + NULL, + &dwSize, + sizeof(RAWINPUTHEADER) + ); + + // Create a proper sized structure to hold the data + std::vector lpb(dwSize); + + // Request the Raw input data + if (GetRawInputData((HRAWINPUT)lp, RID_INPUT, lpb.data(), &dwSize, + sizeof(RAWINPUTHEADER)) != dwSize) { + std::cerr << "GetRawInputData does not return correct size!" << std::endl; + } + + // Convert the input data + RAWINPUT* raw = reinterpret_cast(lpb.data()); + + // Make sure it's a keyboard type event, relative to a key release. + if (raw->header.dwType == RIM_TYPEKEYBOARD && raw->data.keyboard.Message == WM_KEYUP) + { + DWORD currentTick = GetTickCount(); + + // If enough time has passed between the last keypress and now, refresh the keyboard layout + if ((currentTick - lastKeyboardPressTick) > refreshKeyboardLayoutInterval) { + + // Because keyboard layouts on windows are Window-specific, to get the current + // layout we need to get the foreground window and get its layout. + + HWND hwnd = GetForegroundWindow(); + if (hwnd) { + DWORD threadID = GetWindowThreadProcessId(hwnd, NULL); + HKL newKeyboardLayout = GetKeyboardLayout(threadID); + + // It's not always valid, so update the current value only if available. + if (newKeyboardLayout != 0) { + currentKeyboardLayout = newKeyboardLayout; + } + } + + lastKeyboardPressTick = currentTick; + } + + // Get keyboard state ( necessary to decode the associated Unicode char ) + std::vector lpKeyState(256); + if (GetKeyboardState(lpKeyState.data())) { + // Convert the virtual key to an unicode char + std::array buffer; + int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), buffer.data(), buffer.size(), 0, currentKeyboardLayout); + + // If a result is available, invoke the callback + if (result >= 1) { + std::cout << buffer[0] << " " << buffer[1] << " res=" << result << " vk=" << raw->data.keyboard.VKey << " rsc=" << raw->data.keyboard.MakeCode << std::endl; + std::cout << static_cast(buffer[0]) << std::endl; + } + } + } + + return 0; + } + default: + return DefWindowProc(window, msg, wp, lp); + } +} + +void initialize() { + // Initialize the default keyboard layout + currentKeyboardLayout = GetKeyboardLayout(0); + + // Initialize the Worker window + + // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa + WNDCLASSEX wndclass = { + sizeof(WNDCLASSEX), // cbSize: Size of this structure + 0, // style: Class styles + workerWindowProcedure, // lpfnWndProc: Pointer to the window procedure + 0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure + 0, // cbWndExtra: The number of extra bytes to allocate following the window instance. + GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class. + NULL, // hIcon: A handle to the class icon. + LoadCursor(0,IDC_ARROW), // hCursor: A handle to the class cursor. + NULL, // hbrBackground: A handle to the class background brush. + NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu + winclass, // lpszClassName: A pointer to a null-terminated string or is an atom. + NULL // hIconSm: A handle to a small icon that is associated with the window class. + }; + + if (RegisterClassEx(&wndclass)) + { + // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw + window = CreateWindowEx( + 0, // dwExStyle: The extended window style of the window being created. + winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass + L"Espanso Worker Window", // lpWindowName: The window name. + WS_OVERLAPPEDWINDOW, // dwStyle: The style of the window being created. + CW_USEDEFAULT, // X: The initial horizontal position of the window. + CW_USEDEFAULT, // Y: The initial vertical position of the window. + 100, // nWidth: The width, in device units, of the window. + 100, // nHeight: The height, in device units, of the window. + NULL, // hWndParent: handle to the parent or owner window of the window being created. + NULL, // hMenu: A handle to a menu, or specifies a child-window identifier, depending on the window style. + GetModuleHandle(0), // hInstance: A handle to the instance of the module to be associated with the window. + NULL // lpParam: Pointer to a value to be passed to the window + ); + + // Register raw inputs + RAWINPUTDEVICE Rid[1]; + + Rid[0].usUsagePage = 0x01; + Rid[0].usUsage = 0x06; + Rid[0].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; // adds HID keyboard and also ignores legacy keyboard messages + Rid[0].hwndTarget = window; + + if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error. + // TODO: error callback + } + }else{ + // Something went wrong, error. + // TODO: error callback + } +} + +void eventloop() { + if (window) + { + // Hide the window + ShowWindow(window, SW_HIDE); + + // Enter the Event loop + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg); + }else{ // Something went wrong, error + // TODO: error callback + } +} + diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index 1420fb5..3701f88 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -3,9 +3,14 @@ #include -extern "C" void testcall(float value) -{ - printf("Hello, world from C! Value passed: %f\n",value); -} +/* + * Initialize the Windows worker's parameters + */ +extern "C" void initialize(); + +/* + * Start the event loop indefinitely. Blocking call. + */ +extern "C" void eventloop(); #endif //ESPANSO_BRIDGE_H diff --git a/src/main.rs b/src/main.rs index d8ac64a..6cf98cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #[link(name="winbridge", kind="static")] extern { - // this is rustified prototype of the function from our C library - fn testcall(v: f32); + fn initialize(); + fn eventloop(); } fn main() { @@ -9,6 +9,7 @@ fn main() { // calling the function from foo library unsafe { - testcall(3.14159); + initialize(); + eventloop(); }; } \ No newline at end of file