diff --git a/build.bat b/build.bat index 0b4b8cb..95202c6 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,6 @@ @echo off -set libs=User32.lib Dwmapi.lib Gdi32.lib +set libs=User32.lib Gdi32.lib set opts=-FC -GR- -EHa- -nologo -Zi set code=%cd% diff --git a/win32_typing_test.c b/win32_typing_test.c index af12721..80b20d5 100644 --- a/win32_typing_test.c +++ b/win32_typing_test.c @@ -1,14 +1,14 @@ /* ** Win32 Typing Test Program -** v1.0.0 - Jan 19th 2024 +** v1.1.0 - Jan 19th 2024 ** by Allen Webster allenw@mr4th.com ** ** public domain example program ** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK ** ** -** Tested on: -** Windows 10 English +** Tested on: +** Windows 10 (English, Latvian, Emojis) ** ** This is an example and a tool for gathering information about ** Win32 text input APIs. @@ -25,9 +25,6 @@ #include -// only for vsync in GDI -#include - typedef LONG S32; typedef USHORT U16; typedef ULONG U32; @@ -47,6 +44,9 @@ static void AppTypeCodepoint(U32 cp); static BOOL keep_running = 1; +static WCHAR lo_surrogate = 0; +static WCHAR hi_surrogate = 0; + static LRESULT WindowProc(HWND wnd, UINT msg, @@ -55,51 +55,53 @@ WindowProc(HWND wnd, LRESULT result = 0; switch (msg){ - case WM_PAINT: - { - PAINTSTRUCT ps; - BeginPaint(wnd, &ps); - AppRender(wnd); - EndPaint(wnd, &ps); - }break; - case WM_CHAR: { - // When a user uses IME, their input is handled by - // WM_IME_CHAR messages, but additional WM_CHAR messages - // get generated in the process. + // When a WM_CHAR generally represents text input that + // should be inserted, but there are a few twists. // - // I'm not entirely sure what the best thing is to do with - // them - but they appear to always be messages I want to ignore. + // Sometimes WM_CHAR is triggered by controls that are + // not text input, like Ctrl + Letter. To filter out + // these cases we ignore values less than ' ' (0x20) + // and the value 0x7F. See WM_KEYDOWN for a little + // more on this issue. // - // They also appear to be the only case where a WM_CHAR - // message has a scan code of zero. + // When the user types a character that does not fit + // in a single U16, a pair of WM_CHAR messages are sent. + // Each WM_CHAR message in the pair contains a + // "surrogate" (defined by UTF16) one "high" surrogate + // followed by a "low" surrogate. A pair of surrogates + // translates into a single typed codepoint. // - // Therefore my best solution so far is to filter out WM_CHAR - // messages that have a zero scan code. + // (Actually the docs don't clearly say one order + // or the other - I just support both) - U32 scan_code = ((lparam >> 16) & 0xFF); - if (scan_code != 0){ - U32 cp16 = (wparam & 0xFFFF); - if (cp16 >= ' ' && cp16 != 0x7F){ - AppTypeCodepoint(cp16); + U32 c = (wparam & 0xFFFF); + if (c >= ' ' && c != 0x7F){ + + if (IS_LOW_SURROGATE(c)){ + lo_surrogate = c; } - } - }break; - - case WM_IME_CHAR: - { - U32 cp16 = (wparam & 0xFFFF); - if (cp16 >= ' ' && cp16 != 0x7F){ - AppTypeCodepoint(cp16); - } - }break; - - case WM_UNICHAR: - { - U32 cp32 = wparam; - if (cp32 >= ' ' && cp32 != 0x7F){ - AppTypeCodepoint(cp32); + else if (IS_HIGH_SURROGATE(c)){ + hi_surrogate = c; + } + else{ + AppTypeCodepoint(c); + lo_surrogate = 0; + hi_surrogate = 0; + } + + if (hi_surrogate != 0 && lo_surrogate != 0){ + if (IS_SURROGATE_PAIR(hi_surrogate, lo_surrogate)){ + U32 cp = (0x10000 + + ((hi_surrogate & 0x3FF) << 10) + + (lo_surrogate & 0x3FF) ); + AppTypeCodepoint(cp); + } + hi_surrogate = 0; + lo_surrogate = 0; + } + } }break; @@ -143,6 +145,14 @@ WindowProc(HWND wnd, } }break; + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(wnd, &ps); + AppRender(wnd); + EndPaint(wnd, &ps); + }break; + case WM_CLOSE: { keep_running = 0; @@ -158,6 +168,63 @@ WindowProc(HWND wnd, } +//////////////////////////////// +// Main + +static BOOL composition_enabled = 0; + +int +WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nShowCmd){ + WNDCLASSW window_class = {0}; + window_class.style = CS_HREDRAW|CS_VREDRAW; + window_class.lpfnWndProc = WindowProc; + window_class.hInstance = hInstance; + window_class.hCursor = LoadCursorA(0, IDC_ARROW); + window_class.lpszClassName = L"WindowClass"; + + ATOM atom = RegisterClassW(&window_class); + if (atom == 0){ + MessageBoxW(0, (WCHAR*)L"RegisterClassW failed\n", (WCHAR*)L"Fatal", MB_OK); + ExitProcess(1); + } + + DWORD style = WS_OVERLAPPEDWINDOW; + HWND wnd = CreateWindowW(L"WindowClass", L"Type Test", style, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + 0, 0, hInstance, 0); + + // init + AppInit(wnd); + + // main loop + ShowWindow(wnd, SW_SHOW); + + keep_running = 1; + for (;keep_running;){ + + // Handle Message Queue + BOOL wait = 1; + MSG msg; + for (;(wait?GetMessageW(&msg, 0, 0, 0): + PeekMessageW(&msg, 0, 0, 0, PM_REMOVE));){ + wait = 0; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + // render + AppRender(wnd); + + // sleep + Sleep(33); + } +} + + //////////////////////////////// // App Implementation @@ -271,79 +338,4 @@ AppRender(HWND wnd){ } -//////////////////////////////// -// Main - -static BOOL composition_enabled = 0; - -int -WinMain(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - LPSTR lpCmdLine, - int nShowCmd){ - // window setup -#define WINDOW_CLASS L"InputWindowClass" - - WNDCLASSW window_class = {0}; - window_class.style = CS_HREDRAW|CS_VREDRAW; - window_class.lpfnWndProc = WindowProc; - window_class.hInstance = hInstance; - window_class.hCursor = LoadCursorA(0, IDC_ARROW); - window_class.lpszClassName = WINDOW_CLASS; - - ATOM atom = RegisterClassW(&window_class); - if (atom == 0){ - MessageBoxW(0, (WCHAR*)L"RegisterClassW failed\n", (WCHAR*)L"Fatal", MB_OK); - ExitProcess(1); - } - - DWORD style = WS_OVERLAPPEDWINDOW; - HWND wnd = CreateWindowW(WINDOW_CLASS, L"Type Test", style, - CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, - 0, 0, hInstance, 0); - - // setup vsync - if (DwmIsCompositionEnabled(&composition_enabled) != S_OK){ - composition_enabled = 0; - } - if (!composition_enabled){ - MessageBoxW(0, (WCHAR*)L"No Vsync\n", (WCHAR*)L"Issue", MB_OK); - } - - // init - AppInit(wnd); - - // main loop - ShowWindow(wnd, SW_SHOW); - - keep_running = 1; - for (;keep_running;){ - - // flush message queue - { - BOOL wait_for_message = 1; - MSG msg; - for (;(wait_for_message? - GetMessageW(&msg, 0, 0, 0): - PeekMessageW(&msg, 0, 0, 0, PM_REMOVE));){ - wait_for_message = 0; - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - // render - AppRender(wnd); - - // vsync or sleep - if (composition_enabled){ - DwmFlush(); - } - else{ - Sleep(33); - } - } -} - //$ graphical //