simplify - only need DispatchMessageW and WM_CHAR actually - no need for vsync - handle surrogate pairs
parent
ac13615d64
commit
6681cddf72
|
@ -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%
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
** 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
|
||||
|
@ -8,7 +8,7 @@
|
|||
**
|
||||
**
|
||||
** Tested on:
|
||||
** Windows 10 English
|
||||
** 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 <Windows.h>
|
||||
|
||||
// only for vsync in GDI
|
||||
#include <dwmapi.h>
|
||||
|
||||
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;
|
||||
}
|
||||
else if (IS_HIGH_SURROGATE(c)){
|
||||
hi_surrogate = c;
|
||||
}
|
||||
else{
|
||||
AppTypeCodepoint(c);
|
||||
lo_surrogate = 0;
|
||||
hi_surrogate = 0;
|
||||
}
|
||||
}
|
||||
}break;
|
||||
|
||||
case WM_IME_CHAR:
|
||||
{
|
||||
U32 cp16 = (wparam & 0xFFFF);
|
||||
if (cp16 >= ' ' && cp16 != 0x7F){
|
||||
AppTypeCodepoint(cp16);
|
||||
}
|
||||
}break;
|
||||
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;
|
||||
}
|
||||
|
||||
case WM_UNICHAR:
|
||||
{
|
||||
U32 cp32 = wparam;
|
||||
if (cp32 >= ' ' && cp32 != 0x7F){
|
||||
AppTypeCodepoint(cp32);
|
||||
}
|
||||
}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 //
|
||||
|
|
Loading…
Reference in New Issue