diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Window_Win.c |
initial commit
Diffstat (limited to 'src/Window_Win.c')
-rw-r--r-- | src/Window_Win.c | 865 |
1 files changed, 865 insertions, 0 deletions
diff --git a/src/Window_Win.c b/src/Window_Win.c new file mode 100644 index 0000000..58ab5a6 --- /dev/null +++ b/src/Window_Win.c @@ -0,0 +1,865 @@ +#include "Core.h" +#if CC_WIN_BACKEND == CC_WIN_BACKEND_WIN32 +#include "_WindowBase.h" +#include "String.h" +#include "Funcs.h" +#include "Bitmap.h" +#include "Options.h" +#include "Errors.h" + +#define WIN32_LEAN_AND_MEAN +#define NOSERVICE +#define NOMCX +#define NOIME +#ifndef UNICODE +#define UNICODE +#define _UNICODE +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 /* Windows XP */ +/* NOTE: Functions that are not present on Windows 2000 are dynamically loaded. */ +/* Hence the actual minimum supported OS is Windows 2000. This just avoids redeclaring structs. */ +#endif +#include <windows.h> +#include <commdlg.h> + +/* https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setpixelformat */ +#define CC_WIN_STYLE WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN +#define CC_WIN_CLASSNAME TEXT("ClassiCube_Window") +#define Rect_Width(rect) (rect.right - rect.left) +#define Rect_Height(rect) (rect.bottom - rect.top) + +#ifndef WM_XBUTTONDOWN +/* Missing if _WIN32_WINNT isn't defined */ +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#endif +#ifndef WM_INPUT +/* Missing when compiling with some older winapi SDKs */ +#define WM_INPUT 0x00FF +#endif +#ifndef WM_MOUSEHWHEEL +/* Missing when compiling with some older winapi SDKs */ +#define WM_MOUSEHWHEEL 0x020E +#endif + +static BOOL (WINAPI *_RegisterRawInputDevices)(PCRAWINPUTDEVICE devices, UINT numDevices, UINT size); +static UINT (WINAPI *_GetRawInputData)(HRAWINPUT hRawInput, UINT cmd, void* data, UINT* size, UINT headerSize); +static BOOL (WINAPI* _SetProcessDPIAware)(void); + +static HINSTANCE win_instance; +static HWND win_handle; +static HDC win_DC; +static cc_bool suppress_resize; +static int win_totalWidth, win_totalHeight; /* Size of window including titlebar and borders */ +static cc_bool is_ansiWindow, grabCursor; +static int windowX, windowY; + +static const cc_uint8 key_map[] = { +/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* 08 */ CCKEY_BACKSPACE, CCKEY_TAB, 0, 0, CCKEY_F5, CCKEY_ENTER, 0, 0, +/* 10 */ 0, 0, 0, CCKEY_PAUSE, CCKEY_CAPSLOCK, 0, 0, 0, +/* 18 */ 0, 0, 0, CCKEY_ESCAPE, 0, 0, 0, 0, +/* 20 */ CCKEY_SPACE, CCKEY_PAGEUP, CCKEY_PAGEDOWN, CCKEY_END, CCKEY_HOME, CCKEY_LEFT, CCKEY_UP, CCKEY_RIGHT, +/* 28 */ CCKEY_DOWN, 0, CCKEY_PRINTSCREEN, 0, CCKEY_PRINTSCREEN, CCKEY_INSERT, CCKEY_DELETE, 0, +/* 30 */ '0', '1', '2', '3', '4', '5', '6', '7', +/* 38 */ '8', '9', 0, 0, 0, 0, 0, 0, +/* 40 */ 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', +/* 48 */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +/* 50 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', +/* 58 */ 'X', 'Y', 'Z', CCKEY_LWIN, CCKEY_RWIN, CCKEY_MENU, 0, CCKEY_SLEEP, +/* 60 */ CCKEY_KP0, CCKEY_KP1, CCKEY_KP2, CCKEY_KP3, CCKEY_KP4, CCKEY_KP5, CCKEY_KP6, CCKEY_KP7, +/* 68 */ CCKEY_KP8, CCKEY_KP9, CCKEY_KP_MULTIPLY, CCKEY_KP_PLUS, 0, CCKEY_KP_MINUS, CCKEY_KP_DECIMAL, CCKEY_KP_DIVIDE, +/* 70 */ CCKEY_F1, CCKEY_F2, CCKEY_F3, CCKEY_F4, CCKEY_F5, CCKEY_F6, CCKEY_F7, CCKEY_F8, +/* 78 */ CCKEY_F9, CCKEY_F10, CCKEY_F11, CCKEY_F12, CCKEY_F13, CCKEY_F14, CCKEY_F15, CCKEY_F16, +/* 80 */ CCKEY_F17, CCKEY_F18, CCKEY_F19, CCKEY_F20, CCKEY_F21, CCKEY_F22, CCKEY_F23, CCKEY_F24, +/* 88 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* 90 */ CCKEY_NUMLOCK, CCKEY_SCROLLLOCK, 0, 0, 0, 0, 0, 0, +/* 98 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* A0 */ CCKEY_LSHIFT, CCKEY_RSHIFT, CCKEY_LCTRL, CCKEY_RCTRL, CCKEY_LALT, CCKEY_RALT, CCKEY_BROWSER_PREV, CCKEY_BROWSER_NEXT, +/* A8 */ CCKEY_BROWSER_REFRESH, CCKEY_BROWSER_STOP, CCKEY_BROWSER_SEARCH, CCKEY_BROWSER_FAVORITES, CCKEY_BROWSER_HOME, CCKEY_VOLUME_MUTE, CCKEY_VOLUME_DOWN, CCKEY_VOLUME_UP, +/* B0 */ CCKEY_MEDIA_NEXT, CCKEY_MEDIA_PREV, CCKEY_MEDIA_STOP, CCKEY_MEDIA_PLAY, CCKEY_LAUNCH_MAIL, CCKEY_LAUNCH_MEDIA, CCKEY_LAUNCH_APP1, CCKEY_LAUNCH_CALC, +/* B8 */ 0, 0, CCKEY_SEMICOLON, CCKEY_EQUALS, CCKEY_COMMA, CCKEY_MINUS, CCKEY_PERIOD, CCKEY_SLASH, +/* C0 */ CCKEY_TILDE, 0, 0, 0, 0, 0, 0, 0, +/* C8 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* D0 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* D8 */ 0, 0, 0, CCKEY_LBRACKET, CCKEY_BACKSLASH, CCKEY_RBRACKET, CCKEY_QUOTE, 0, +}; + +static int MapNativeKey(WPARAM vk_key, LPARAM meta) { + LPARAM ext = meta & (1UL << 24); + LPARAM scancode = (meta >> 16) & 0xFF; + int key; + if (ext) scancode |= 0xE000; + + switch (vk_key) + { + case VK_CONTROL: + return ext ? CCKEY_RCTRL : CCKEY_LCTRL; + case VK_MENU: + return ext ? CCKEY_RALT : CCKEY_LALT; + case VK_RETURN: + return ext ? CCKEY_KP_ENTER : CCKEY_ENTER; + } + + key = vk_key < Array_Elems(key_map) ? key_map[vk_key] : 0; + if (!key) Platform_Log2("Unknown key: %x, %x", &vk_key, &scancode); + return key; +} + +static void RefreshWindowBounds(void) { + RECT rect; + POINT topLeft = { 0, 0 }; + + GetWindowRect(win_handle, &rect); + win_totalWidth = Rect_Width(rect); + win_totalHeight = Rect_Height(rect); + + GetClientRect(win_handle, &rect); + Window_Main.Width = Rect_Width(rect); + Window_Main.Height = Rect_Height(rect); + + /* GetClientRect always returns 0,0 for left,top (see MSDN) */ + ClientToScreen(win_handle, &topLeft); + windowX = topLeft.x; windowY = topLeft.y; +} + +static void GrabCursor(void) { + RECT rect; + if (!grabCursor || !Input.RawMode) return; + + GetWindowRect(win_handle, &rect); + ClipCursor(&rect); +} + +static LRESULT CALLBACK Window_Procedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) { + float wheelDelta; + + switch (message) { + case WM_ACTIVATE: + Window_Main.Focused = LOWORD(wParam) != 0; + Event_RaiseVoid(&WindowEvents.FocusChanged); + break; + + case WM_ERASEBKGND: + return 1; /* Avoid flickering */ + + case WM_PAINT: + ValidateRect(win_handle, NULL); + Event_RaiseVoid(&WindowEvents.RedrawNeeded); + return 0; + + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS* pos = (WINDOWPOS*)lParam; + cc_bool sized = pos->cx != win_totalWidth || pos->cy != win_totalHeight; + if (pos->hwnd != win_handle) break; + + GrabCursor(); + RefreshWindowBounds(); + if (sized && !suppress_resize) Event_RaiseVoid(&WindowEvents.Resized); + } break; + + case WM_SIZE: + Event_RaiseVoid(&WindowEvents.StateChanged); + break; + + case WM_CHAR: + /* TODO: Use WM_UNICHAR instead, as WM_CHAR is just utf16 */ + Event_RaiseInt(&InputEvents.Press, (cc_unichar)wParam); + break; + + case WM_MOUSEMOVE: + /* Set before position change, in case mouse buttons changed when outside window */ + if (!(wParam & 0x01)) Input_SetReleased(CCMOUSE_L); + //Input_SetNonRepeatable(CCMOUSE_L, wParam & 0x01); + Input_SetNonRepeatable(CCMOUSE_R, wParam & 0x02); + Input_SetNonRepeatable(CCMOUSE_M, wParam & 0x10); + /* TODO: do we need to set XBUTTON1/XBUTTON2 here */ + Pointer_SetPosition(0, LOWORD(lParam), HIWORD(lParam)); + break; + + case WM_MOUSEWHEEL: + wheelDelta = ((short)HIWORD(wParam)) / (float)WHEEL_DELTA; + Mouse_ScrollVWheel(wheelDelta); + return 0; + case WM_MOUSEHWHEEL: + wheelDelta = ((short)HIWORD(wParam)) / (float)WHEEL_DELTA; + Mouse_ScrollHWheel(wheelDelta); + return 0; + + case WM_LBUTTONDOWN: + Input_SetPressed(CCMOUSE_L); break; + case WM_MBUTTONDOWN: + Input_SetPressed(CCMOUSE_M); break; + case WM_RBUTTONDOWN: + Input_SetPressed(CCMOUSE_R); break; + case WM_XBUTTONDOWN: + Input_SetPressed(HIWORD(wParam) == 1 ? CCMOUSE_X1 : CCMOUSE_X2); + break; + + case WM_LBUTTONUP: + Input_SetReleased(CCMOUSE_L); break; + case WM_MBUTTONUP: + Input_SetReleased(CCMOUSE_M); break; + case WM_RBUTTONUP: + Input_SetReleased(CCMOUSE_R); break; + case WM_XBUTTONUP: + Input_SetReleased(HIWORD(wParam) == 1 ? CCMOUSE_X1 : CCMOUSE_X2); + break; + + case WM_INPUT: + { + int dx, dy, width, height, absX, absY, isVirtual; + UINT ret, rawSize = sizeof(RAWINPUT); + RAWINPUT raw; + + ret = _GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); + if (ret == -1 || raw.header.dwType != RIM_TYPEMOUSE) break; + + if (raw.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { + /* Majority of mouse input devices provide relative coordinates */ + dx = raw.data.mouse.lLastX; + dy = raw.data.mouse.lLastY; + } else if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + /* This mouse mode is produced by VirtualBox with Mouse Integration on */ + /* To understand reasoning behind the following code, see Remarks in */ + /* https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawmouse */ + static int prevPosX, prevPosY; + isVirtual = raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP; + + width = GetSystemMetrics(isVirtual ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); + height = GetSystemMetrics(isVirtual ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); + absX = (int)((raw.data.mouse.lLastX / 65535.0f) * width); + absY = (int)((raw.data.mouse.lLastY / 65535.0f) * height); + + dx = absX - prevPosX; prevPosX = absX; + dy = absY - prevPosY; prevPosY = absY; + } else { break; } + + if (Input.RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, (float)dx, (float)dy); + } break; + + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + { + cc_bool pressed = message == WM_KEYDOWN || message == WM_SYSKEYDOWN; + /* Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed + and released. It looks like neither key is released in this case, or that the wrong key is + released in the case of Control and Alt. + To combat this, we are going to release both keys when either is released. Hacky, but should work. + Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0). + In this case, both keys will be reported as pressed. */ + cc_bool lShiftDown, rShiftDown; + int key; + + if (wParam == VK_SHIFT) { + /* The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit + to distinguish between left and right keys. Moreover, pressing both keys and releasing one + may result in both keys being held down (but not always).*/ + lShiftDown = ((USHORT)GetKeyState(VK_LSHIFT)) >> 15; + rShiftDown = ((USHORT)GetKeyState(VK_RSHIFT)) >> 15; + + if (!pressed || lShiftDown != rShiftDown) { + Input_Set(CCKEY_LSHIFT, lShiftDown); + Input_Set(CCKEY_RSHIFT, rShiftDown); + } + } else { + key = MapNativeKey(wParam, lParam); + if (key) Input_Set(key, pressed); + } + return 0; + } break; + + case WM_SYSCHAR: + return 0; + + case WM_KILLFOCUS: + /* TODO: Keep track of keyboard when focus is lost */ + Input_Clear(); + break; + + case WM_CLOSE: + Event_RaiseVoid(&WindowEvents.Closing); + if (Window_Main.Exists) DestroyWindow(win_handle); + Window_Main.Exists = false; + break; + + case WM_DESTROY: + Window_Main.Exists = false; + UnregisterClassW(CC_WIN_CLASSNAME, win_instance); + + if (win_DC) ReleaseDC(win_handle, win_DC); + break; + } + return is_ansiWindow ? DefWindowProcA(handle, message, wParam, lParam) + : DefWindowProcW(handle, message, wParam, lParam); +} + + +/*########################################################################################################################* +*--------------------------------------------------Public implementation--------------------------------------------------* +*#########################################################################################################################*/ +void Window_PreInit(void) { } +void Window_Init(void) { + static const struct DynamicLibSym funcs[] = { + DynamicLib_Sym(RegisterRawInputDevices), + DynamicLib_Sym(GetRawInputData), + DynamicLib_Sym(SetProcessDPIAware) + }; + static const cc_string user32 = String_FromConst("USER32.DLL"); + void* lib; + HDC hdc; + + DynamicLib_LoadAll(&user32, funcs, Array_Elems(funcs), &lib); + Input.Sources = INPUT_SOURCE_NORMAL; + + /* Enable high DPI support */ + DisplayInfo.DPIScaling = Options_GetBool(OPT_DPI_SCALING, false); + if (DisplayInfo.DPIScaling && _SetProcessDPIAware) _SetProcessDPIAware(); + + hdc = GetDC(NULL); + DisplayInfo.Width = GetSystemMetrics(SM_CXSCREEN); + DisplayInfo.Height = GetSystemMetrics(SM_CYSCREEN); + DisplayInfo.Depth = GetDeviceCaps(hdc, BITSPIXEL); + DisplayInfo.ScaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f; + DisplayInfo.ScaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f; + ReleaseDC(NULL, hdc); + + /* https://docs.microsoft.com/en-us/windows/win32/opengl/reading-color-values-from-the-framebuffer */ + /* https://devblogs.microsoft.com/oldnewthing/20101013-00/?p=12543 */ + /* TODO probably should always multiply? not just for 16 colors */ + if (DisplayInfo.Depth != 1) return; + DisplayInfo.Depth *= GetDeviceCaps(hdc, PLANES); +} + +void Window_Free(void) { } + +static ATOM DoRegisterClass(void) { + ATOM atom; + WNDCLASSEXW wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_OWNDC; /* https://stackoverflow.com/questions/48663815/getdc-releasedc-cs-owndc-with-opengl-and-gdi */ + wc.hInstance = win_instance; + wc.lpfnWndProc = Window_Procedure; + wc.lpszClassName = CC_WIN_CLASSNAME; + + wc.hIcon = (HICON)LoadImageA(win_instance, MAKEINTRESOURCEA(1), IMAGE_ICON, + GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0); + wc.hIconSm = (HICON)LoadImageA(win_instance, MAKEINTRESOURCEA(1), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + wc.hCursor = LoadCursorA(NULL, (LPCSTR)IDC_ARROW); + + if ((atom = RegisterClassExW(&wc))) return atom; + /* Windows 9x does not support W API functions */ + return RegisterClassExA((const WNDCLASSEXA*)&wc); +} + +static void CreateWindowHandle(ATOM atom, int width, int height) { + cc_result res; + RECT r; + /* Calculate final window rectangle after window decorations are added (titlebar, borders etc) */ + r.left = Display_CentreX(width); r.right = r.left + width; + r.top = Display_CentreY(height); r.bottom = r.top + height; + AdjustWindowRect(&r, CC_WIN_STYLE, false); + + if ((win_handle = CreateWindowExW(0, MAKEINTATOM(atom), NULL, CC_WIN_STYLE, + r.left, r.top, Rect_Width(r), Rect_Height(r), NULL, NULL, win_instance, NULL))) return; + res = GetLastError(); + + /* Windows 9x does not support W API functions */ + if (res == ERROR_CALL_NOT_IMPLEMENTED) { + is_ansiWindow = true; + if ((win_handle = CreateWindowExA(0, (LPCSTR)MAKEINTATOM(atom), NULL, CC_WIN_STYLE, + r.left, r.top, Rect_Width(r), Rect_Height(r), NULL, NULL, win_instance, NULL))) return; + res = GetLastError(); + } + Logger_Abort2(res, "Failed to create window"); +} + +static void DoCreateWindow(int width, int height) { + ATOM atom; + win_instance = GetModuleHandleA(NULL); + /* TODO: UngroupFromTaskbar(); */ + width = Display_ScaleX(width); + height = Display_ScaleY(height); + + atom = DoRegisterClass(); + CreateWindowHandle(atom, width, height); + RefreshWindowBounds(); + + win_DC = GetDC(win_handle); + if (!win_DC) Logger_Abort2(GetLastError(), "Failed to get device context"); + + Window_Main.Exists = true; + Window_Main.Handle = win_handle; + grabCursor = Options_GetBool(OPT_GRAB_CURSOR, false); +} +void Window_Create2D(int width, int height) { DoCreateWindow(width, height); } +void Window_Create3D(int width, int height) { DoCreateWindow(width, height); } + +void Window_SetTitle(const cc_string* title) { + cc_winstring str; + Platform_EncodeString(&str, title); + if (SetWindowTextW(win_handle, str.uni)) return; + + /* Windows 9x does not support W API functions */ + SetWindowTextA(win_handle, str.ansi); +} + +void Clipboard_GetText(cc_string* value) { + cc_bool unicode; + HANDLE hGlobal; + LPVOID src; + SIZE_T size; + int i; + + /* retry up to 50 times */ + for (i = 0; i < 50; i++) { + if (!OpenClipboard(win_handle)) { + Thread_Sleep(10); + continue; + } + + unicode = true; + hGlobal = GetClipboardData(CF_UNICODETEXT); + if (!hGlobal) { + hGlobal = GetClipboardData(CF_TEXT); + unicode = false; + } + + if (!hGlobal) { CloseClipboard(); return; } + src = GlobalLock(hGlobal); + size = GlobalSize(hGlobal); + + /* ignore trailing NULL at end */ + /* TODO: Verify it's always there */ + if (unicode) { + String_AppendUtf16(value, src, size - 2); + } else { + String_DecodeCP1252(value, src, size - 1); + } + + GlobalUnlock(hGlobal); + CloseClipboard(); + return; + } +} + +void Clipboard_SetText(const cc_string* value) { + cc_unichar* text; + HANDLE hGlobal; + int i; + + /* retry up to 10 times */ + for (i = 0; i < 10; i++) { + if (!OpenClipboard(win_handle)) { + Thread_Sleep(100); + continue; + } + + hGlobal = GlobalAlloc(GMEM_MOVEABLE, (value->length + 1) * 2); + if (!hGlobal) { CloseClipboard(); return; } + + text = (cc_unichar*)GlobalLock(hGlobal); + for (i = 0; i < value->length; i++, text++) { + *text = Convert_CP437ToUnicode(value->buffer[i]); + } + *text = '\0'; + + GlobalUnlock(hGlobal); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, hGlobal); + CloseClipboard(); + return; + } +} + +int Window_GetWindowState(void) { + DWORD s = GetWindowLongA(win_handle, GWL_STYLE); + + if ((s & WS_MINIMIZE)) return WINDOW_STATE_MINIMISED; + if ((s & WS_MAXIMIZE) && (s & WS_POPUP)) return WINDOW_STATE_FULLSCREEN; + return WINDOW_STATE_NORMAL; +} + +static void ToggleFullscreen(cc_bool fullscreen, UINT finalShow) { + DWORD style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + style |= (fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW); + + suppress_resize = true; + { + ShowWindow(win_handle, SW_RESTORE); /* reset maximised state */ + SetWindowLongA(win_handle, GWL_STYLE, style); + SetWindowPos(win_handle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + ShowWindow(win_handle, finalShow); + Window_ProcessEvents(0.0); + } + suppress_resize = false; + + /* call Resized event only once */ + RefreshWindowBounds(); + Event_RaiseVoid(&WindowEvents.Resized); +} + +static UINT win_show; +cc_result Window_EnterFullscreen(void) { + WINDOWPLACEMENT w = { 0 }; + w.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(win_handle, &w); + + win_show = w.showCmd; + ToggleFullscreen(true, SW_MAXIMIZE); + return 0; +} + +cc_result Window_ExitFullscreen(void) { + ToggleFullscreen(false, win_show); + return 0; +} + +int Window_IsObscured(void) { return 0; } + +void Window_Show(void) { + ShowWindow(win_handle, SW_SHOW); + BringWindowToTop(win_handle); + SetForegroundWindow(win_handle); +} + +void Window_SetSize(int width, int height) { + DWORD style = GetWindowLongA(win_handle, GWL_STYLE); + RECT rect = { 0, 0, width, height }; + AdjustWindowRect(&rect, style, false); + + SetWindowPos(win_handle, NULL, 0, 0, + Rect_Width(rect), Rect_Height(rect), SWP_NOMOVE); +} + +void Window_RequestClose(void) { + PostMessageA(win_handle, WM_CLOSE, 0, 0); +} + +void Window_ProcessEvents(float delta) { + HWND foreground; + MSG msg; + + if (is_ansiWindow) { + while (PeekMessageA(&msg, NULL, 0, 0, 1)) { + TranslateMessage(&msg); DispatchMessageA(&msg); + } + } else { + while (PeekMessageW(&msg, NULL, 0, 0, 1)) { + TranslateMessage(&msg); DispatchMessageW(&msg); + } + } + + foreground = GetForegroundWindow(); + if (foreground) { + Window_Main.Focused = foreground == win_handle; + } +} + +void Window_ProcessGamepads(float delta) { } + +static void Cursor_GetRawPos(int* x, int* y) { + POINT point; + GetCursorPos(&point); + *x = point.x; *y = point.y; +} + +void Cursor_SetPosition(int x, int y) { + SetCursorPos(x + windowX, y + windowY); +} +static void Cursor_DoSetVisible(cc_bool visible) { + int i; + /* ShowCursor actually is a counter (returns > 0 if visible, <= 0 if not) */ + /* Try multiple times in case cursor count was changed by something else */ + /* (there's at least system out there that requires 30 times) */ + if (visible) { + for (i = 0; i < 50 && ShowCursor(true) < 0; i++) { } + } else { + for (i = 0; i < 50 && ShowCursor(false) >= 0; i++) {} + } +} + +static void ShowDialogCore(const char* title, const char* msg) { + MessageBoxA(win_handle, msg, title, 0); +} + +static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, cc_bool load, + const char* const* fileExts, const cc_string* defaultName) { + union OPENFILENAME_union { + OPENFILENAMEW wide; + OPENFILENAMEA ansi; + } ofn = { 0 }; // less compiler warnings this way + + cc_string path; char pathBuffer[NATIVE_STR_LEN]; + cc_winstring str = { 0 }; + cc_winstring filter; + cc_result res; + BOOL ok; + int i; + + Platform_EncodeString(&str, defaultName); + Platform_EncodeString(&filter, filters); + /* NOTE: OPENFILENAME_SIZE_VERSION_400 is used instead of sizeof(OFN), because the size of */ + /* OPENFILENAME increased after Windows 9x/NT4 with the addition of pvReserved and later fields */ + /* (and Windows 9x/NT4 return an error if a lStructSize > OPENFILENAME_SIZE_VERSION_400 is used) */ + ofn.wide.lStructSize = OPENFILENAME_SIZE_VERSION_400; + /* also note that this only works when OFN_HOOK is *not* included in Flags - if it is, then */ + /* on modern Windows versions the dialogs are altered to show an old Win 9x style appearance */ + /* (see https://github.com/geany/geany/issues/578 for example of this problem) */ + + ofn.wide.hwndOwner = win_handle; + ofn.wide.lpstrFile = str.uni; + ofn.wide.nMaxFile = MAX_PATH; + ofn.wide.lpstrFilter = filter.uni; + ofn.wide.nFilterIndex = 1; + ofn.wide.Flags = OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | (load ? OFN_FILEMUSTEXIST : OFN_OVERWRITEPROMPT); + + String_InitArray(path, pathBuffer); + ok = load ? GetOpenFileNameW(&ofn.wide) : GetSaveFileNameW(&ofn.wide); + + if (ok) { + /* Successfully got a unicode filesystem path */ + for (i = 0; i < MAX_PATH && str.uni[i]; i++) + { + String_Append(&path, Convert_CodepointToCP437(str.uni[i])); + } + } else if ((res = CommDlgExtendedError()) == 2) { + /* CDERR_INITIALIZATION - probably running on Windows 9x */ + ofn.ansi.lpstrFile = str.ansi; + ofn.ansi.lpstrFilter = filter.ansi; + + ok = load ? GetOpenFileNameA(&ofn.ansi) : GetSaveFileNameA(&ofn.ansi); + if (!ok) return CommDlgExtendedError(); + + for (i = 0; i < MAX_PATH && str.ansi[i]; i++) + { + String_Append(&path, str.ansi[i]); + } + } else { + return res; + } + + /* Add default file extension if user didn't provide one */ + if (!load && ofn.wide.nFileExtension == 0 && ofn.wide.nFilterIndex > 0) { + String_AppendConst(&path, fileExts[ofn.wide.nFilterIndex - 1]); + } + callback(&path); + return 0; +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + const char* const* fileExts = args->filters; + cc_string filters; char buffer[NATIVE_STR_LEN]; + int i; + + /* Filter tokens are \0 separated - e.g. "Maps (*.cw;*.dat)\0*.cw;*.dat\0 */ + String_InitArray(filters, buffer); + String_Format1(&filters, "%c (", args->description); + for (i = 0; fileExts[i]; i++) + { + if (i) String_Append(&filters, ';'); + String_Format1(&filters, "*%c", fileExts[i]); + } + String_Append(&filters, ')'); + String_Append(&filters, '\0'); + + for (i = 0; fileExts[i]; i++) + { + if (i) String_Append(&filters, ';'); + String_Format1(&filters, "*%c", fileExts[i]); + } + String_Append(&filters, '\0'); + + return OpenSaveFileDialog(&filters, args->Callback, true, fileExts, &String_Empty); +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + const char* const* titles = args->titles; + const char* const* fileExts = args->filters; + cc_string filters; char buffer[NATIVE_STR_LEN]; + int i; + + /* Filter tokens are \0 separated - e.g. "Map (*.cw)\0*.cw\0 */ + String_InitArray(filters, buffer); + for (i = 0; fileExts[i]; i++) + { + String_Format2(&filters, "%c (*%c)", titles[i], fileExts[i]); + String_Append(&filters, '\0'); + String_Format1(&filters, "*%c", fileExts[i]); + String_Append(&filters, '\0'); + } + return OpenSaveFileDialog(&filters, args->Callback, false, fileExts, &args->defaultName); +} + +static HDC draw_DC; +static HBITMAP draw_DIB; +void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) { + BITMAPINFO hdr = { 0 }; + if (!draw_DC) draw_DC = CreateCompatibleDC(win_DC); + + /* sizeof(BITMAPINFO) does not work on Windows 9x */ + hdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + hdr.bmiHeader.biWidth = width; + hdr.bmiHeader.biHeight = -height; + hdr.bmiHeader.biBitCount = 32; + hdr.bmiHeader.biPlanes = 1; + + draw_DIB = CreateDIBSection(draw_DC, &hdr, DIB_RGB_COLORS, (void**)&bmp->scan0, NULL, 0); + if (!draw_DIB) Logger_Abort2(GetLastError(), "Failed to create DIB"); + bmp->width = width; + bmp->height = height; +} + +void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { + HGDIOBJ oldSrc = SelectObject(draw_DC, draw_DIB); + BitBlt(win_DC, r.x, r.y, r.width, r.height, draw_DC, r.x, r.y, SRCCOPY); + SelectObject(draw_DC, oldSrc); +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + DeleteObject(draw_DIB); +} + +static cc_bool rawMouseInited, rawMouseSupported; +static void InitRawMouse(void) { + RAWINPUTDEVICE rid; + + rawMouseSupported = _RegisterRawInputDevices && _GetRawInputData; + rawMouseSupported &= Options_GetBool(OPT_RAW_INPUT, true); + if (!rawMouseSupported) { Platform_LogConst("## Raw input unsupported!"); return; } + + rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */ + rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */ + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = win_handle; + + if (_RegisterRawInputDevices(&rid, 1, sizeof(rid))) return; + Logger_SysWarn(GetLastError(), "initing raw mouse"); + rawMouseSupported = false; +} + +void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { } +void OnscreenKeyboard_SetText(const cc_string* text) { } +void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) { } +void OnscreenKeyboard_Draw3D(void) { } +void OnscreenKeyboard_Close(void) { } + +void Window_EnableRawMouse(void) { + DefaultEnableRawMouse(); + if (!rawMouseInited) InitRawMouse(); + + rawMouseInited = true; + GrabCursor(); +} + +void Window_UpdateRawMouse(void) { + if (rawMouseSupported) { + /* handled in WM_INPUT messages */ + CentreMousePosition(); + } else { + DefaultUpdateRawMouse(); + } +} + +void Window_DisableRawMouse(void) { + DefaultDisableRawMouse(); + if (grabCursor) ClipCursor(NULL); +} + + +/*########################################################################################################################* +*-------------------------------------------------------WGL OpenGL--------------------------------------------------------* +*#########################################################################################################################*/ +#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) && !defined CC_BUILD_EGL +static HGLRC ctx_handle; +static HDC ctx_DC; +typedef BOOL (WINAPI *FP_SWAPINTERVAL)(int interval); +static FP_SWAPINTERVAL wglSwapIntervalEXT; + +static void GLContext_SelectGraphicsMode(struct GraphicsMode* mode) { + PIXELFORMATDESCRIPTOR pfd = { 0 }; + int modeIndex; + + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + /* TODO: PFD_SUPPORT_COMPOSITION FLAG? CHECK IF IT WORKS ON XP */ + + pfd.cColorBits = mode->R + mode->G + mode->B; + pfd.cDepthBits = GLCONTEXT_DEFAULT_DEPTH; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cAlphaBits = mode->A; /* TODO not needed? test on Intel */ + + modeIndex = ChoosePixelFormat(win_DC, &pfd); + if (modeIndex == 0) { Logger_Abort("Requested graphics mode not available"); } + + Mem_Set(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + + /* TODO DescribePixelFormat might be unnecessary? */ + DescribePixelFormat(win_DC, modeIndex, pfd.nSize, &pfd); + if (!SetPixelFormat(win_DC, modeIndex, &pfd)) { + Logger_Abort2(GetLastError(), "SetPixelFormat failed"); + } +} + +void GLContext_Create(void) { + struct GraphicsMode mode; + InitGraphicsMode(&mode); + GLContext_SelectGraphicsMode(&mode); + + ctx_handle = wglCreateContext(win_DC); + if (!ctx_handle) { + Logger_Abort2(GetLastError(), "Failed to create OpenGL context"); + } + + if (!wglMakeCurrent(win_DC, ctx_handle)) { + Logger_Abort2(GetLastError(), "Failed to make OpenGL context current"); + } + + ctx_DC = wglGetCurrentDC(); + wglSwapIntervalEXT = (FP_SWAPINTERVAL)GLContext_GetAddress("wglSwapIntervalEXT"); +} + +void GLContext_Update(void) { } +cc_bool GLContext_TryRestore(void) { return true; } +void GLContext_Free(void) { + if (!ctx_handle) return; + wglDeleteContext(ctx_handle); + ctx_handle = NULL; +} + +/* https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions#Windows */ +#define GLContext_IsInvalidAddress(ptr) (ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr == (void*)2) + +void* GLContext_GetAddress(const char* function) { + static const cc_string glPath = String_FromConst("OPENGL32.dll"); + static void* lib; + + void* addr = (void*)wglGetProcAddress(function); + if (!GLContext_IsInvalidAddress(addr)) return addr; + + /* Some drivers return NULL from wglGetProcAddress for core OpenGL functions */ + if (!lib) lib = DynamicLib_Load2(&glPath); + return DynamicLib_Get2(lib, function); +} + +cc_bool GLContext_SwapBuffers(void) { + if (!SwapBuffers(ctx_DC)) Logger_Abort2(GetLastError(), "Failed to swap buffers"); + return true; +} + +void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { + if (!wglSwapIntervalEXT) return; + wglSwapIntervalEXT(vsync); +} +void GLContext_GetApiInfo(cc_string* info) { } +#endif +#endif |