342 lines
8.1 KiB
C
342 lines
8.1 KiB
C
/*
|
|
** Win32 Typing Test Program
|
|
** v1.1.0 - Jan 19th 2024
|
|
** by Allen Webster allenw@mr4th.com
|
|
**
|
|
** public domain example program
|
|
** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK
|
|
**
|
|
**
|
|
** Tested on:
|
|
** Windows 10 (English, Latvian, Emojis)
|
|
**
|
|
** This is an example and a tool for gathering information about
|
|
** Win32 text input APIs.
|
|
**
|
|
** You may use it as a starting point for your own Win32 text input
|
|
** implementation.
|
|
**
|
|
** Or you may contribute to the project by:
|
|
** Trying typing in the test program and report any misbehavior you see.
|
|
** Supplying references to relevant documentation/examples that perform
|
|
** this task in a better way.
|
|
**
|
|
*/
|
|
|
|
#include <Windows.h>
|
|
|
|
typedef LONG S32;
|
|
typedef USHORT U16;
|
|
typedef ULONG U32;
|
|
|
|
|
|
////////////////////////////////
|
|
// App Functions
|
|
|
|
static void AppInit(HWND wnd);
|
|
static void AppRender(HWND wnd);
|
|
static void AppTypeBackspace(void);
|
|
static void AppTypeCodepoint(U32 cp);
|
|
|
|
|
|
////////////////////////////////
|
|
// Window Proc
|
|
|
|
static BOOL keep_running = 1;
|
|
|
|
static WCHAR lo_surrogate = 0;
|
|
static WCHAR hi_surrogate = 0;
|
|
|
|
static LRESULT
|
|
WindowProc(HWND wnd,
|
|
UINT msg,
|
|
WPARAM wparam,
|
|
LPARAM lparam){
|
|
LRESULT result = 0;
|
|
|
|
switch (msg){
|
|
case WM_CHAR:
|
|
{
|
|
// When a WM_CHAR generally represents text input that
|
|
// should be inserted, but there are a few twists.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// (Actually the docs don't clearly say one order
|
|
// or the other - I just support both)
|
|
|
|
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;
|
|
}
|
|
|
|
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_KEYDOWN:
|
|
{
|
|
// While it is possible to see newlines/carriage returns
|
|
// and tabs through the WM_CHAR message, it is also possible
|
|
// to get erroneous newlines and tabs in WM_CHAR.
|
|
//
|
|
// "Erroneous" WM_CHAR messages are generated when a user
|
|
// holds down "Ctrl" and presses any alphabetic character.
|
|
// The erroneous values generated this way are always less
|
|
// than 0x20 (0x20 is the ascii for the space character ' ').
|
|
//
|
|
// Thus it's possible to get simple behavior by ignoring
|
|
// all the WM_CHAR messages that have character values less
|
|
// than 0x20 and then directly handle these two relevant
|
|
// cases here.
|
|
|
|
if (wparam == VK_RETURN){
|
|
AppTypeCodepoint('\n');
|
|
}
|
|
if (wparam == VK_TAB){
|
|
AppTypeCodepoint('\t');
|
|
}
|
|
|
|
if (wparam == VK_BACK){
|
|
AppTypeBackspace();
|
|
}
|
|
}break;
|
|
|
|
case WM_SYSCOMMAND:
|
|
{
|
|
// Not directly relevant to the main goal, but if you don't
|
|
// want your Alt+Key combos to play a bell sound you have to
|
|
// disable default handling of this 'menu' path
|
|
|
|
BOOL is_menu = (wparam == SC_KEYMENU && (lparam >> 16) <= 0);
|
|
if (!is_menu){
|
|
result = DefWindowProcW(wnd, msg, wparam, lparam);
|
|
}
|
|
}break;
|
|
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
BeginPaint(wnd, &ps);
|
|
AppRender(wnd);
|
|
EndPaint(wnd, &ps);
|
|
}break;
|
|
|
|
case WM_CLOSE:
|
|
{
|
|
keep_running = 0;
|
|
}break;
|
|
|
|
default:
|
|
{
|
|
result = DefWindowProcW(wnd, msg, wparam, lparam);
|
|
}break;
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
|
|
////////////////////////////////
|
|
// 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
|
|
|
|
static HFONT font = 0;
|
|
|
|
#define TEXT_CAP (1 << 14)
|
|
static U16 text_memory[TEXT_CAP];
|
|
static U32 text_len = 0;
|
|
|
|
static void
|
|
AppInsertCodepoint(U32 cp){
|
|
// convert codepoint to UTF16
|
|
U16 encode_buf[2];
|
|
U32 encode_len = 0;
|
|
if (cp < 0x10000){
|
|
encode_buf[0] = cp;
|
|
encode_len = 1;
|
|
}
|
|
else{
|
|
U32 cpj = cp - 0x10000;
|
|
encode_buf[0] = (cpj >> 10) + 0xD800;
|
|
encode_buf[1] = (cpj & 0x3FF) + 0xDC00;
|
|
encode_len = 2;
|
|
}
|
|
|
|
// insert into string
|
|
if (text_len + encode_len < TEXT_CAP){
|
|
for (U32 i = 0; i < encode_len; i += 1){
|
|
text_memory[text_len] = encode_buf[i];
|
|
text_len += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
AppTypeBackspace(void){
|
|
text_len = 0;
|
|
}
|
|
|
|
static void
|
|
AppTypeCodepoint(U32 cp){
|
|
if (cp == '\n'){
|
|
AppInsertCodepoint('\\');
|
|
AppInsertCodepoint('n');
|
|
}
|
|
else if (cp == '\t'){
|
|
AppInsertCodepoint('\\');
|
|
AppInsertCodepoint('t');
|
|
}
|
|
else{
|
|
AppInsertCodepoint(cp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
AppInit(HWND wnd){
|
|
LOGFONT logfont = {0};
|
|
logfont.lfHeight = 24;
|
|
memcpy(logfont.lfFaceName, "Arial", sizeof("Arial"));
|
|
font = CreateFontIndirectA(&logfont);
|
|
}
|
|
|
|
static void
|
|
AppRender(HWND wnd){
|
|
|
|
// window dimensions
|
|
RECT wnd_rect = {0};
|
|
GetClientRect(wnd, &wnd_rect);
|
|
|
|
// render begin
|
|
HDC wnd_dc = GetDC(wnd);
|
|
HDC dc = CreateCompatibleDC(wnd_dc);
|
|
HBITMAP bitmap =
|
|
CreateCompatibleBitmap(wnd_dc, wnd_rect.right, wnd_rect.bottom);
|
|
SelectObject(dc, bitmap);
|
|
|
|
// render body
|
|
{
|
|
SelectObject(dc, GetStockObject(DC_PEN));
|
|
SelectObject(dc, GetStockObject(DC_BRUSH));
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SelectObject(dc, font);
|
|
|
|
// background
|
|
{
|
|
SetDCPenColor(dc, RGB(255, 255, 255));
|
|
SetDCBrushColor(dc, RGB(255, 255, 255));
|
|
Rectangle(dc, wnd_rect.left, wnd_rect.top, wnd_rect.right, wnd_rect.bottom);
|
|
}
|
|
|
|
// string
|
|
{
|
|
SIZE dim = {0};
|
|
GetTextExtentPoint32W(dc, (WCHAR*)text_memory, text_len, &dim);
|
|
|
|
S32 cx = (wnd_rect.right - wnd_rect.left)/2;
|
|
S32 cy = (wnd_rect.bottom - wnd_rect.top)/2;
|
|
|
|
S32 x = cx - dim.cx/2;
|
|
S32 y = cy - dim.cy/2;
|
|
|
|
TextOutW(dc, x, y, (WCHAR*)text_memory, text_len);
|
|
}
|
|
}
|
|
|
|
// render end
|
|
BitBlt(wnd_dc, 0, 0, wnd_rect.right, wnd_rect.bottom, dc, 0, 0, SRCCOPY);
|
|
DeleteObject(bitmap);
|
|
DeleteDC(dc);
|
|
ReleaseDC(wnd, wnd_dc);
|
|
}
|
|
|
|
|
|
//$ graphical //
|