// platform_win32.c — Win32 windowing, input, clipboard, and process spawning #include "platform/platform.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include //////////////////////////////// // Window internals struct PlatformWindow { HWND hwnd; B32 should_close; S32 width; S32 height; PlatformFrameCallback frame_callback; void *frame_callback_user_data; PlatformRawInput input; B32 prev_mouse_down; }; static PlatformWindow *g_main_window = NULL; static B32 g_wndclass_registered = false; //////////////////////////////// // Window procedure // Forward declare ImGui Win32 handler (defined in imgui_impl_win32.cpp) extern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK platform_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // Let ImGui process input first if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam)) return true; PlatformWindow *pw = (PlatformWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (msg) { case WM_SIZE: if (pw && wparam != SIZE_MINIMIZED) { pw->width = (S32)LOWORD(lparam); pw->height = (S32)HIWORD(lparam); if (pw->frame_callback) pw->frame_callback(pw->frame_callback_user_data); } return 0; case WM_CHAR: if (pw && wparam >= 32 && wparam < 0xFFFF) { PlatformRawInput *ev = &pw->input; if (ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) ev->chars[ev->char_count++] = (U16)wparam; } return 0; case WM_KEYDOWN: case WM_SYSKEYDOWN: if (pw) { PlatformRawInput *ev = &pw->input; if (ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME) ev->keys[ev->key_count++] = (U8)wparam; ev->ctrl_held = (GetKeyState(VK_CONTROL) & 0x8000) != 0; ev->shift_held = (GetKeyState(VK_SHIFT) & 0x8000) != 0; } break; case WM_MOUSEWHEEL: if (pw) { S16 wheel_delta = (S16)HIWORD(wparam); pw->input.scroll_delta.y += (F32)wheel_delta / (F32)WHEEL_DELTA * 6.0f; } return 0; case WM_SETCURSOR: if (LOWORD(lparam) == HTCLIENT) { SetCursor(LoadCursor(NULL, IDC_IBEAM)); return TRUE; } break; case WM_DPICHANGED: if (pw) { RECT *suggested = (RECT *)lparam; SetWindowPos(hwnd, NULL, suggested->left, suggested->top, suggested->right - suggested->left, suggested->bottom - suggested->top, SWP_NOZORDER | SWP_NOACTIVATE); } return 0; case WM_CLOSE: if (pw) pw->should_close = true; return 0; case WM_DESTROY: if (pw == g_main_window) PostQuitMessage(0); return 0; case WM_SYSCOMMAND: if ((wparam & 0xfff0) == SC_KEYMENU) return 0; break; } return DefWindowProcW(hwnd, msg, wparam, lparam); } //////////////////////////////// // Window lifecycle PlatformWindow *platform_create_window(PlatformWindowDesc *desc) { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); if (!g_wndclass_registered) { WNDCLASSEXW wc = {0}; wc.cbSize = sizeof(wc); wc.style = CS_CLASSDC; wc.lpfnWndProc = platform_wndproc; wc.hInstance = GetModuleHandleW(NULL); wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(101)); wc.hIconSm = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(101)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = L"codemax_wc"; RegisterClassExW(&wc); g_wndclass_registered = true; } UINT dpi = GetDpiForSystem(); int screen_w = GetSystemMetrics(SM_CXSCREEN); int screen_h = GetSystemMetrics(SM_CYSCREEN); int x = (screen_w - desc->width) / 2; int y = (screen_h - desc->height) / 2; DWORD style = WS_OVERLAPPEDWINDOW; RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height }; AdjustWindowRectExForDpi(&rect, style, FALSE, 0, dpi); int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, NULL, 0); wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count); HWND hwnd = CreateWindowExW( 0, L"codemax_wc", wtitle, style, x, y, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, GetModuleHandleW(NULL), NULL ); _freea(wtitle); if (!hwnd) return NULL; PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow)); window->hwnd = hwnd; window->should_close = false; window->width = desc->width; window->height = desc->height; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window); g_main_window = window; ShowWindow(hwnd, SW_SHOWDEFAULT); UpdateWindow(hwnd); return window; } void platform_destroy_window(PlatformWindow *window) { if (!window) return; if (window->hwnd) DestroyWindow(window->hwnd); if (g_main_window == window) g_main_window = NULL; free(window); } B32 platform_poll_events(PlatformWindow *window) { MSG msg; while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); if (msg.message == WM_QUIT) window->should_close = true; } return !window->should_close; } void platform_get_size(PlatformWindow *window, S32 *w, S32 *h) { if (w) *w = window->width; if (h) *h = window->height; } void *platform_get_native_handle(PlatformWindow *window) { return (void *)window->hwnd; } PlatformRawInput platform_get_input(PlatformWindow *window) { PlatformRawInput result = window->input; POINT cursor; GetCursorPos(&cursor); ScreenToClient(window->hwnd, &cursor); result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y); result.was_mouse_down = window->prev_mouse_down; result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0; window->prev_mouse_down = result.mouse_down; memset(&window->input, 0, sizeof(window->input)); return result; } void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) { window->frame_callback = cb; window->frame_callback_user_data = user_data; } F32 platform_get_dpi_scale(PlatformWindow *window) { if (!window || !window->hwnd) return 1.0f; return (F32)GetDpiForWindow(window->hwnd) / 96.0f; } //////////////////////////////// // Input adapter PlatformInput platform_adapt_input(PlatformRawInput *raw) { PlatformInput pi = {0}; pi.ctrl_held = raw->ctrl_held; pi.shift_held = raw->shift_held; pi.alt_held = (GetKeyState(VK_MENU) & 0x8000) != 0; // Convert UTF-16 chars to ASCII (printable range only) for (S32 i = 0; i < raw->char_count && pi.char_count < PLATFORM_MAX_CHARS_PER_FRAME; i++) { U16 ch = raw->chars[i]; if (ch >= 32 && ch < 127) pi.chars[pi.char_count++] = (char)ch; } // Key codes are identical (both use Windows VK codes) for (S32 i = 0; i < raw->key_count && pi.key_count < PLATFORM_MAX_KEYS_PER_FRAME; i++) pi.keys[pi.key_count++] = raw->keys[i]; return pi; } //////////////////////////////// // Clipboard void platform_clipboard_set(const char *text) { if (!text) return; int len = (int)strlen(text); if (len == 0) return; int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, NULL, 0); if (wlen == 0) return; HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t)); if (!hmem) return; wchar_t *wbuf = (wchar_t *)GlobalLock(hmem); MultiByteToWideChar(CP_UTF8, 0, text, len, wbuf, wlen); wbuf[wlen] = L'\0'; GlobalUnlock(hmem); HWND hwnd = g_main_window ? g_main_window->hwnd : NULL; if (OpenClipboard(hwnd)) { EmptyClipboard(); SetClipboardData(CF_UNICODETEXT, hmem); CloseClipboard(); } else { GlobalFree(hmem); } } const char *platform_clipboard_get(void) { static char buf[64 * 1024]; buf[0] = '\0'; HWND hwnd = g_main_window ? g_main_window->hwnd : NULL; if (!OpenClipboard(hwnd)) return NULL; HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT); if (hmem) { wchar_t *wbuf = (wchar_t *)GlobalLock(hmem); if (wbuf) { int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, NULL, NULL); buf[len > 0 ? len - 1 : 0] = '\0'; GlobalUnlock(hmem); } } CloseClipboard(); return buf[0] ? buf : NULL; } //////////////////////////////// // Process spawning void platform_spawn_terminal(const char *command, const char *working_dir) { char cmd_line[2048]; snprintf(cmd_line, sizeof(cmd_line), "cmd.exe /k %s", command); STARTUPINFOA si = {0}; si.cb = sizeof(si); PROCESS_INFORMATION pi = {0}; CreateProcessA(NULL, cmd_line, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, working_dir, &si, &pi); if (pi.hProcess) CloseHandle(pi.hProcess); if (pi.hThread) CloseHandle(pi.hThread); }