initial commit

main
Allen Webster 2024-01-19 18:57:30 -08:00
commit d73eba0790
4 changed files with 372 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.*
build/*

11
build.bat Normal file
View File

@ -0,0 +1,11 @@
@echo off
set libs=User32.lib Dwmapi.lib Gdi32.lib
set opts=-FC -GR- -EHa- -nologo -Zi
set code=%cd%
if not exist build ( mkdir build )
pushd build
cl %opts% %code%\win32_typing_test.c %libs% -Fetyping_test
popd

31
project.4coder Normal file
View File

@ -0,0 +1,31 @@
version(2);
project_name = "Typing Test";
patterns = {
"*.c",
"*.h",
"*.bat",
"*.4coder",
};
blacklist_patterns = {
".*",
};
load_paths_base = {
{ ".", .relative = true, .recursive = true, },
};
load_paths = { { load_paths_base, .os = "win", }, };
commands = {
.build =
{ .out = "*compilation*", .footer_panel = true, .save_dirty_files = true,
.win = "build.bat" },
.run =
{ .out = "*run*", .footer_panel = false, .save_dirty_files = false,
.win = "build\\typing_test" },
};
fkey_command = {
.F1 = "build",
.F2 = "run",
};

328
win32_typing_test.c Normal file
View File

@ -0,0 +1,328 @@
/*
** A program for testing win32 text input
*/
#include <Windows.h>
// only for vsync in GDI
#include <dwmapi.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 LRESULT
WindowProc(HWND wnd,
UINT msg,
WPARAM wparam,
LPARAM lparam){
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.
//
// 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.
//
// They also appear to be the only case where a WM_CHAR
// message has a scan code of zero.
//
// Therefore my best solution so far is to filter out WM_CHAR
// messages that have a zero scan code.
U32 scan_code = ((lparam >> 16) & 0xFF);
if (scan_code != 0){
U32 cp16 = (wparam & 0xFFFF);
if (cp16 >= ' ' && cp16 != 0x7F){
AppTypeCodepoint(cp16);
}
}
}break;
case WM_IME_CHAR:
{
U32 cp16 = (wparam & 0xFFFF);
if (cp16 >= ' ' && cp16 != 0x7F){
AppTypeCodepoint(cp16);
}
}break;
case WM_UNICHAR:
{
U32 cp32 = wparam;
if (cp32 >= ' ' && cp32 != 0x7F){
AppTypeCodepoint(cp32);
}
}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_CLOSE:
{
keep_running = 0;
}break;
default:
{
result = DefWindowProcW(wnd, msg, wparam, lparam);
}break;
}
return(result);
}
////////////////////////////////
// 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);
}
////////////////////////////////
// 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 //