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
|
@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%
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
** 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
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
**
|
**
|
||||||
**
|
**
|
||||||
** 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){
|
|
||||||
AppTypeCodepoint(cp16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}break;
|
|
||||||
|
|
||||||
case WM_IME_CHAR:
|
if (IS_LOW_SURROGATE(c)){
|
||||||
{
|
lo_surrogate = c;
|
||||||
U32 cp16 = (wparam & 0xFFFF);
|
}
|
||||||
if (cp16 >= ' ' && cp16 != 0x7F){
|
else if (IS_HIGH_SURROGATE(c)){
|
||||||
AppTypeCodepoint(cp16);
|
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;
|
|
||||||
|
|
||||||
case WM_UNICHAR:
|
|
||||||
{
|
|
||||||
U32 cp32 = wparam;
|
|
||||||
if (cp32 >= ' ' && cp32 != 0x7F){
|
|
||||||
AppTypeCodepoint(cp32);
|
|
||||||
}
|
}
|
||||||
}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 //
|
||||||
|
|
Loading…
Reference in New Issue