/* ** A program for testing win32 text input */ #include // only for vsync in GDI #include 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 //