Compare commits

...

2 Commits

3 changed files with 113 additions and 121 deletions

View File

@ -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%

View File

@ -12,7 +12,7 @@ blacklist_patterns = {
load_paths_base = {
{ ".", .relative = true, .recursive = true, },
};
load_paths = { { load_paths_base, .os = "win", }, };
load_paths = { .win = load_paths_base, };
commands = {
.build =

View File

@ -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 //