|
|
|
@ -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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}break;
|
|
|
|
|
U32 c = (wparam & 0xFFFF);
|
|
|
|
|
if (c >= ' ' && c != 0x7F){
|
|
|
|
|
|
|
|
|
|
case WM_IME_CHAR:
|
|
|
|
|
{
|
|
|
|
|
U32 cp16 = (wparam & 0xFFFF);
|
|
|
|
|
if (cp16 >= ' ' && cp16 != 0x7F){
|
|
|
|
|
AppTypeCodepoint(cp16);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
@ -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 //
|
|
|
|
|