simplify - only need DispatchMessageW and WM_CHAR actually - no need for vsync - handle surrogate pairs

main
Allen Webster 2024-01-19 20:41:53 -08:00
parent ac13615d64
commit 6681cddf72
2 changed files with 112 additions and 120 deletions

View File

@ -1,6 +1,6 @@
@echo off @echo off
set libs=User32.lib Dwmapi.lib Gdi32.lib set libs=User32.lib Gdi32.lib
set opts=-FC -GR- -EHa- -nologo -Zi set opts=-FC -GR- -EHa- -nologo -Zi
set code=%cd% set code=%cd%

View File

@ -1,14 +1,14 @@
/* /*
** Win32 Typing Test Program ** Win32 Typing Test Program
** v1.0.0 - Jan 19th 2024 ** v1.1.0 - Jan 19th 2024
** by Allen Webster allenw@mr4th.com ** by Allen Webster allenw@mr4th.com
** **
** public domain example program ** public domain example program
** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK ** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK
** **
** **
** Tested on: ** Tested on:
** Windows 10 English ** Windows 10 (English, Latvian, Emojis)
** **
** This is an example and a tool for gathering information about ** This is an example and a tool for gathering information about
** Win32 text input APIs. ** Win32 text input APIs.
@ -25,9 +25,6 @@
#include <Windows.h> #include <Windows.h>
// only for vsync in GDI
#include <dwmapi.h>
typedef LONG S32; typedef LONG S32;
typedef USHORT U16; typedef USHORT U16;
typedef ULONG U32; typedef ULONG U32;
@ -47,6 +44,9 @@ static void AppTypeCodepoint(U32 cp);
static BOOL keep_running = 1; static BOOL keep_running = 1;
static WCHAR lo_surrogate = 0;
static WCHAR hi_surrogate = 0;
static LRESULT static LRESULT
WindowProc(HWND wnd, WindowProc(HWND wnd,
UINT msg, UINT msg,
@ -55,51 +55,53 @@ WindowProc(HWND wnd,
LRESULT result = 0; LRESULT result = 0;
switch (msg){ switch (msg){
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(wnd, &ps);
AppRender(wnd);
EndPaint(wnd, &ps);
}break;
case WM_CHAR: case WM_CHAR:
{ {
// When a user uses IME, their input is handled by // When a WM_CHAR generally represents text input that
// WM_IME_CHAR messages, but additional WM_CHAR messages // should be inserted, but there are a few twists.
// get generated in the process.
// //
// I'm not entirely sure what the best thing is to do with // Sometimes WM_CHAR is triggered by controls that are
// them - but they appear to always be messages I want to ignore. // 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 // When the user types a character that does not fit
// message has a scan code of zero. // 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 // (Actually the docs don't clearly say one order
// messages that have a zero scan code. // or the other - I just support both)
U32 scan_code = ((lparam >> 16) & 0xFF); U32 c = (wparam & 0xFFFF);
if (scan_code != 0){ if (c >= ' ' && c != 0x7F){
U32 cp16 = (wparam & 0xFFFF);
if (cp16 >= ' ' && cp16 != 0x7F){ if (IS_LOW_SURROGATE(c)){
AppTypeCodepoint(cp16); lo_surrogate = c;
} }
} else if (IS_HIGH_SURROGATE(c)){
}break; hi_surrogate = c;
}
case WM_IME_CHAR: else{
{ AppTypeCodepoint(c);
U32 cp16 = (wparam & 0xFFFF); lo_surrogate = 0;
if (cp16 >= ' ' && cp16 != 0x7F){ hi_surrogate = 0;
AppTypeCodepoint(cp16); }
}
}break; if (hi_surrogate != 0 && lo_surrogate != 0){
if (IS_SURROGATE_PAIR(hi_surrogate, lo_surrogate)){
case WM_UNICHAR: U32 cp = (0x10000 +
{ ((hi_surrogate & 0x3FF) << 10) +
U32 cp32 = wparam; (lo_surrogate & 0x3FF) );
if (cp32 >= ' ' && cp32 != 0x7F){ AppTypeCodepoint(cp);
AppTypeCodepoint(cp32); }
hi_surrogate = 0;
lo_surrogate = 0;
}
} }
}break; }break;
@ -143,6 +145,14 @@ WindowProc(HWND wnd,
} }
}break; }break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(wnd, &ps);
AppRender(wnd);
EndPaint(wnd, &ps);
}break;
case WM_CLOSE: case WM_CLOSE:
{ {
keep_running = 0; 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 // 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 // //$ graphical //